diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
new file mode 100644
index 0000000..ffd4dca
--- /dev/null
+++ b/.github/workflows/pages.yml
@@ -0,0 +1,44 @@
+on:
+ push:
+ branches:
+ - main
+ - documentation-tm # for testing
+ workflow_dispatch:
+
+# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+permissions:
+ actions: read
+ pages: write
+ id-token: write
+
+# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
+# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
+concurrency:
+ group: "pages"
+ cancel-in-progress: false
+
+jobs:
+ publish-docs:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Dotnet Setup
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 8.x
+
+ - run: dotnet tool update -g docfx
+ - run: docfx docs/docfx.json
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ # Upload entire repository
+ path: 'docs/_site'
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/FodyWeavers.xml b/FodyWeavers.xml
new file mode 100644
index 0000000..d24cbce
--- /dev/null
+++ b/FodyWeavers.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ Scriban
+
+
+
\ No newline at end of file
diff --git a/PolyMod.csproj b/PolyMod.csproj
index 072a080..1530dcb 100644
--- a/PolyMod.csproj
+++ b/PolyMod.csproj
@@ -1,4 +1,4 @@
-
+
net6.0
enable
@@ -20,6 +20,10 @@
+
+ all
+
+
@@ -29,9 +33,9 @@
[!WARNING]
+> Manually setting `idx` to something other than `-1` breaks mod compatibility!
+
+Additionally, make sure that all the internal names you use are in lowercase. PolyMod will crash otherwise.
+Here's part of an example gld patch which adds new tribe:
+```json
+{
+ "tribeData": {
+ "testingtribe": {
+ "color":15713321,
+ "language":"az,bar,bryn,dûm,girt,hall,kar,khâz,kol,kruk,lok,rûdh,ruf,und,vorn,zak",
+ "startingTech":[
+ "basic",
+ "mining"
+ ],
+ "startingResource":[
+ "metal"
+ ],
+ "skins":[
+ "Testingskin"
+ ],
+ "priceTier":0,
+ "category":2,
+ "bonus":0,
+ "startingUnit":"warrior",
+ "idx":-1,
+ "preview": [
+ {
+ "x": 0,
+ "y": 0,
+ "terrainType": "mountain",
+ "unitType": "swordsman",
+ "improvementType ": "mine"
+ }
+ ]
+ }
+ }
+}
+```
+
+## Custom Tribe Preview
+As you could have noticed, our testingtribe has a field `preview` which does not exist in GLD. This field was added in order to modify tribe previews if needed.
+```json
+{
+ "preview": [
+ {
+ "x": 0,
+ "y": 0,
+ "terrainType": "mountain",
+ "unitType": "swordsman",
+ "improvementType ": "mine"
+ }
+ ]
+}
+```
+Each tile of preview consists of:
+* `x` X coordinate of the tile in the preview
+* `y` Y coordinate of the tile in the preview
+* `terrainType` terrain of the original tile will be replaced with the one you choose here
+* `resourceType` resource of the original tile will be replaced with the one you choose here
+* `unitType` unit of the original tile will be replaced with the one you choose here
+* `improvementType` improvement of the original tile will be replaced with the one you choose here
+
+Based on that, our chosen preview tile will have `mountain`, `swordsman` and `mine`.
+You can see all tiles and their coordinates in Tribe Preview by enabling PolyMod debug mode in `PolyMod.json`
+
+## Custom Skins
+Also, our tribe has a non-existing skin. By writing such, PolyMod will create a skin automatically:
+```json
+{
+ "skins": [
+ "Testingskin"
+ ]
+}
+```
+
+## Prefabs
+Let's look at this patch which adds new unit:
+```json
+{
+ "unitData": {
+ "dashbender": {
+ "health": 100,
+ "defence": 10,
+ "movement": 1,
+ "range": 1,
+ "attack": 0,
+ "cost": 15,
+ "unitAbilities": [
+ "dash",
+ "convert",
+ "stiff",
+ "land"
+ ],
+ "weapon": 4,
+ "promotionLimit": 3,
+ "idx": -1,
+ "prefab": "mindbender"
+ }
+ }
+}
+```
+
+By default, when creating a new unit, improvement or resource PolyMod will set basic sprites for them, such as:
+
+* New **Unit** have explorer's sprites by default
+* New **Improvement**s have custom house's sprites by default
+* New **Resource**s have animal's sprites by default
+
+If you want to change it to another already existing type, you can do just what we did for `dashbender`:
+```json
+{
+ "prefab": "mindbender"
+}
+```
+That sets `mindbender`'s sprites as our base sprites for `dashbender`.
+
+## Config
+Say that, in your mod, you created a new unit, but you aren't a balancing expert and thus you want the user to be able to configure how expensive it is. Before, you would need polyscript to do this. However, in polymod 1.2 there is a new feature that allows you to have configurable options in gld patches!
+Say that, for example, you wanted to change the warrior's cost to whatever the user wants.
+you can use `{{ config key defaultValue}}`.
+```
+{
+ "unitData" : {
+ "warrior" : {
+ "cost" : {{ config "warriorCost" 5 }}
+ }
+ }
+}
+```
+but what if you want to disable or enable a unit based on config? For that, you need to do more advanced templating. Here is an example that gives ai-mo a dashbender if dashbenders are enabled, otherwise a mindbender. In reality you will also need to modify tech etc.
+```
+{
+ "unitData": {
+ {% if config "dashbenders" true %}
+ "dashbender": {
+ "health": 100,
+ "defence": 10,
+ "movement": 1,
+ "range": 1,
+ "attack": 0,
+ "cost": 15,
+ "unitAbilities": [
+ "dash",
+ "convert",
+ "stiff",
+ "land"
+ ],
+ "weapon": 4,
+ "promotionLimit": 3,
+ "idx": -1,
+ "prefab": "mindbender"
+ }
+ }
+ {% aimo-starting = "dashbender" %}
+ {% else %}
+ {% aimo-starting = "mindbender" %}
+ {% end %}
+ "tribeData":{
+ "ai-mo":{
+ "startingUnit": "{{ aimo-starting }}"
+ }
+ }
+}
+```
+For a full list of templates, see [Scriban docs](https://github.com/scriban/scriban/blob/master/doc/language.md).
\ No newline at end of file
diff --git a/docs/articles/modding/index.md b/docs/articles/modding/index.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/articles/modding/localization.md b/docs/articles/modding/localization.md
new file mode 100644
index 0000000..d54c09a
--- /dev/null
+++ b/docs/articles/modding/localization.md
@@ -0,0 +1,24 @@
+# Localization
+Localization is a json file in a mod that declares mod's localization strings. Mod localization is declared in `localization.json` file.
+
+If you've ever used the custom language system before it was removed, you'll notice that the keys follow the same format as custom langs, but **with all the periods replaced with underscores**.
+If you want to translate your strings to other languages, you can simply use a comma and enter another language's name with its corresponding string.
+
+### Languages
+* `English`
+* `Portuguese (Brazil)`
+* `Russian`
+* `Italian (Italy)`
+* `French (France)`
+* `Spanish (Mexico)`
+* `German (Germany)`
+* `Japanese`
+* `Korean`
+* `Hindi`
+* `Indonesian`
+* `Malay`
+* `Polish`
+* `Thai`
+* `Turkish`
+* `Vietnamese`
+* `Elyrion`
diff --git a/docs/articles/modding/polyscript.md b/docs/articles/modding/polyscript.md
new file mode 100644
index 0000000..30404ce
--- /dev/null
+++ b/docs/articles/modding/polyscript.md
@@ -0,0 +1 @@
+TODO
\ No newline at end of file
diff --git a/docs/articles/modding/sprites.md b/docs/articles/modding/sprites.md
new file mode 100644
index 0000000..6ca9c5d
--- /dev/null
+++ b/docs/articles/modding/sprites.md
@@ -0,0 +1,86 @@
+# Texture
+A texture is an image file in png format, which is being loaded by PolyMod in order to be used ingame.
+
+PolyMod decides how to load the texture based on filename. Texture's file name consists of `name`, `style` and `level`.
+* `name` id of the texture
+* `style` tribe or skin id, for which this texture should be set
+* `level` level of the texture
+
+### Format
+Here is all possible combinations of how you can name the texture file:
+* `name__.png` will replace the texture of chosen target for all tribes, skins and possible levels
+* `name_style_.png` will replace the texture of chosen target for chosen tribe or skin and for all possible levels
+* `name__level.png` will replace the texture of chosen target for all tribes and skins, but for chosen level
+* `name_style_level.png` will replace the texture of chosen target for chosen tribe or skin and for chosen level
+
+### Example
+You want to replace all lumberhut textures for all tribes.
+* We want to replace it for **all** tribes and skins, so we dont specify the style.
+* Lumber hut has only one level, which means we dont want to specify it.
+ In such case, you should name it as `lumberhut__.png`
+
+# Sprites
+Sprites file is a json file which declares advanced settings for how each texture should be transformed into the sprite. Mod sprites is declared in `sprites.json` file.
+
+### Format
+* `pixelsPerUnit` _(optional, default `2112`)_ in Unity, a **sprite PPU** is a property that determines how many pixels from a sprite texture correspond to one unit in the Unity game world.
+
+* `pivot` _(optional, default `[0.5, 0.5]`)_ in Unity, a **sprite pivot** is the reference point that determines how a sprite is positioned within the Unity game world. It acts as the point relative to which happen all movements, rotation and scaling of the sprite.
+
+> [!TIP]
+> You can find more info in [Unity Documentation](https://docs.unity.com/).
+
+### Example
+```json
+{
+ "lumberhut__": {
+ "pixelsPerUnit": 256,
+ "pivot": [0.1, 0.5]
+ }
+}
+```
+
+# Prefab
+A prefab is a json file in a mod that describes a unit prefab. Prefabs are declared in `prefab_name.json` files (name is your prefab name).
+
+### Format
+* `type` int value of the type of a prefab. Here is all current prefab types:
+```
+Unit,
+Improvement,
+Resource
+```
+* `name` name of your prefab
+* `visualParts` array of prfab's visual parts
+ * `gameObjectName` name of the GameObject of the visual part
+ * `baseName` sprite name of the visual part
+ * `coordinates` position of the visual part
+ * `rotation` _(optional, default `0`)_ rotation of the visual part
+ * `scale` scale of the visual part
+ * `tintable` _(optional, default `false`)_ is visual part a tint
+
+### Example
+```json
+{
+ "type": 0,
+ "name": "Yuukwi",
+ "visualParts": [
+ {
+ "gameObjectName": "Body",
+ "baseName": "body",
+ "coordinates": [0, 0.2],
+ "rotation": 180,
+ "scale": [1, 1],
+ "tintable": false
+ },
+ {
+ "gameObjectName": "Body_Tint",
+ "baseName": "body_tint",
+ "coordinates": [0, 0.2],
+ "rotation": 90,
+ "scale": [1, 1],
+ "tintable": true
+ }
+ ]
+}
+```
\ No newline at end of file
diff --git a/docs/articles/modding/toc.yml b/docs/articles/modding/toc.yml
new file mode 100644
index 0000000..c61c3a1
--- /dev/null
+++ b/docs/articles/modding/toc.yml
@@ -0,0 +1,11 @@
+- name: Your first mod
+ href: your-first-mod.md
+- name: Gld Patching
+ href: gld.md
+- name: Sprites
+ href: sprites.md
+- name: Localization
+ href: localization.md
+- name: Polyscript
+ href: polyscript.md
+
\ No newline at end of file
diff --git a/docs/articles/modding/your-first-mod.md b/docs/articles/modding/your-first-mod.md
new file mode 100644
index 0000000..5c3f9e2
--- /dev/null
+++ b/docs/articles/modding/your-first-mod.md
@@ -0,0 +1,47 @@
+# Creating your first mod
+
+## Step 1: Setting up your environment
+Before you start writing mods, you should ensure you have the following:
+- A text editor. Notepad or Text Editor will be enough.
+> [!TIP]
+> Using a text editor meant for writing code, like [vscode](https://code.visualstudio.com/) or Kate(preinstalled on most linux distros) is a lot easier when writing and editing code. However, this isn't required.
+
+[Install PolyMod.](../using/installing.md)
+Inside your mods folder, create a folder, e.g. `example`. If you have an IDE, open that folder with your IDE. Else, open the folder with file manager(or finder, or dolphin etc.)
+
+## Step 2: Writing the manifest
+Create `manifest.json` and paste the following into it:
+```json
+{
+ "id" : "my_first_mod",
+ "name": "My first mod",
+ "version": "1.0.0",
+ "authors": ["your-name"],
+ "dependencies": [
+ {
+ "id": "polytopia"
+ }
+ ]
+}
+```
+
+## Step 3: Creating a patch
+Create `patch.json` and paste the following into it:
+```
+{
+ "unitData" : {
+ "warrior" : {
+ "attack" : 100
+ }
+ }
+}
+```
+This will make the warrior have a large attack value. For more information about GLD patching, see (gld.md)
+
+## Step 4: loading the mod
+Start the game. Thats it! You should see your mod listed under the polymod hub.
+
+## Step 5: publishing your mod
+Once you have finished your mod, you may want to bundle it into a single file for easier sharing. Here's how to do that:
+1. zip the folder of the mod
+2. rename the zipfile to `example.polymod`
\ No newline at end of file
diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml
new file mode 100644
index 0000000..8cedef0
--- /dev/null
+++ b/docs/articles/toc.yml
@@ -0,0 +1,6 @@
+- name: Introduction
+ href: introduction.md
+- name: Modding
+ href: modding/toc.yml
+- name: Using
+ href: using/toc.yml
diff --git a/docs/articles/using/adding-mods.md b/docs/articles/using/adding-mods.md
new file mode 100644
index 0000000..9936734
--- /dev/null
+++ b/docs/articles/using/adding-mods.md
@@ -0,0 +1 @@
+go to steam path and go into mods folder and add mod there
\ No newline at end of file
diff --git a/docs/articles/using/configure.md b/docs/articles/using/configure.md
new file mode 100644
index 0000000..78f9a75
--- /dev/null
+++ b/docs/articles/using/configure.md
@@ -0,0 +1 @@
+edit mods.json in ur polytopia folder
\ No newline at end of file
diff --git a/docs/articles/using/installing.md b/docs/articles/using/installing.md
new file mode 100644
index 0000000..82cd82c
--- /dev/null
+++ b/docs/articles/using/installing.md
@@ -0,0 +1,12 @@
+# Installing PolyMod
+
+Choose your OS: // TODO
+
+## Windows
+Download the installer and launch it. Locate your steam directory and click install.
+
+## Linux
+TODO
+
+## MacOS
+// idk
\ No newline at end of file
diff --git a/docs/articles/using/toc.yml b/docs/articles/using/toc.yml
new file mode 100644
index 0000000..dc7a19a
--- /dev/null
+++ b/docs/articles/using/toc.yml
@@ -0,0 +1,6 @@
+- name: Installing
+ href: installing.md
+- name: Adding Mods
+ href: adding-mods.md
+- name: Configure
+ href: configure.md
diff --git a/docs/docfx.json b/docs/docfx.json
new file mode 100644
index 0000000..5167ddf
--- /dev/null
+++ b/docs/docfx.json
@@ -0,0 +1,44 @@
+{
+ "$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json",
+ "metadata": [{
+ "src": [
+ {
+ "src": "../",
+ "files": [
+ "**/*.csproj"
+ ],
+ }
+ ],
+ "dest": "api"
+ }],
+ "build": {
+ "content": [
+ {
+ "files": [
+ "**/*.{md,yml}"
+ ],
+ "exclude": [
+ "_site/**"
+ ]
+ }
+ ],
+ "resource": [
+ {
+ "files": [
+ "images/**"
+ ]
+ }
+ ],
+ "output": "_site",
+ "template": [
+ "default",
+ "modern"
+ ],
+ "globalMetadata": {
+ "_appName": "polyDocs",
+ "_appTitle": "polyDocs",
+ "_enableSearch": true,
+ "pdf": false
+ }
+ }
+}
diff --git a/docs/gld/Improvement.yml b/docs/gld/Improvement.yml
new file mode 100644
index 0000000..f8b3c4f
--- /dev/null
+++ b/docs/gld/Improvement.yml
@@ -0,0 +1,109 @@
+### YamlMime:ManagedReference
+items:
+- uid: GameData.ImprovementData
+ id: ImprovementData
+ name: Improvement Data
+ fullName: Improvement Data
+ type: Class
+ summary: |
+ Defines the properties for a tile improvement. Each improvement is an object within the `improvementData` section, keyed by its unique name.
+ syntax:
+ content: '"improvementData": { "your_improvement_name": { ... } }'
+ children:
+ - GameData.ImprovementData.cost
+ - GameData.ImprovementData.rewards
+ - GameData.ImprovementData.terrainRequirements
+ - GameData.ImprovementData.adjacencyRequirements
+ - GameData.ImprovementData.improvementAbilities
+ - GameData.ImprovementData.creates
+ - GameData.ImprovementData.maxLevel
+ - GameData.ImprovementData.growthRewards
+ - GameData.ImprovementData.idx
+
+- uid: GameData.ImprovementData.cost
+ id: cost
+ parent: GameData.ImprovementData
+ name: cost
+ fullName: cost
+ type: Property
+ summary: An integer for the star cost to build the improvement.
+ syntax:
+ content: '"cost": 5'
+- uid: GameData.ImprovementData.rewards
+ id: rewards
+ parent: GameData.ImprovementData
+ name: rewards
+ fullName: rewards
+ type: Property
+ summary: An array of objects defining the immediate rewards for building the improvement (e.g., population or currency).
+ syntax:
+ content: '"rewards": [{ "population": 2 }]'
+- uid: GameData.ImprovementData.terrainRequirements
+ id: terrainRequirements
+ parent: GameData.ImprovementData
+ name: terrainRequirements
+ fullName: terrainRequirements
+ type: Property
+ summary: An array of objects specifying where the improvement can be built. Can require a specific `terrain` or `resource`.
+ syntax:
+ content: '"terrainRequirements": [{ "resource": "crop" }]'
+- uid: GameData.ImprovementData.adjacencyRequirements
+ id: adjacencyRequirements
+ parent: GameData.ImprovementData
+ name: adjacencyRequirements
+ fullName: adjacencyRequirements
+ type: Property
+ summary: An array of objects specifying what must be adjacent for this improvement to be built (e.g., a Forge requires a Mine).
+ syntax:
+ content: '"adjacencyRequirements": [{ "improvement": "mine" }]'
+- uid: GameData.ImprovementData.improvementAbilities
+ id: improvementAbilities
+ parent: GameData.ImprovementData
+ name: improvementAbilities
+ fullName: improvementAbilities
+ type: Property
+ summary: |
+ An array of strings for special properties. Possible values include:
+ - `consumed` - The improvement action can only be done once and removes the resource (e.g., `harvestfruit`).
+ - `limited` - Only one can be built per city.
+ - `unique` - Only one can be built per player.
+ - `patina` - The improvement levels up over time.
+ - `network` - Connects to other network improvements.
+ syntax:
+ content: '"improvementAbilities": ["consumed"]'
+- uid: GameData.ImprovementData.creates
+ id: creates
+ parent: GameData.ImprovementData
+ name: creates
+ fullName: creates
+ type: Property
+ summary: An array of objects defining what is created after the action is complete. Can create a `terrain`, `resource`, or `unit`.
+ syntax:
+ content: '"creates": [{ "terrain": "field" }]'
+- uid: GameData.ImprovementData.maxLevel
+ id: maxLevel
+ parent: GameData.ImprovementData
+ name: maxLevel
+ fullName: maxLevel
+ type: Property
+ summary: An integer for the maximum level the improvement can reach. 0 means it does not level up.
+ syntax:
+ content: '"maxLevel": 4'
+- uid: GameData.ImprovementData.growthRewards
+ id: growthRewards
+ parent: GameData.ImprovementData
+ name: growthRewards
+ fullName: growthRewards
+ type: Property
+ summary: An array of objects defining the rewards gained each time the improvement levels up (e.g., population or score).
+ syntax:
+ content: '"growthRewards": [{ "score": 50, "population": 0 }]'
+- uid: GameData.ImprovementData.idx
+ id: idx
+ parent: GameData.ImprovementData
+ name: idx
+ fullName: idx
+ type: Property
+ summary: A unique integer index for the improvement. Must be unique across all improvements. When adding a new entry, it is recommended to set this value to -1 to ensure uniqueness and prevent conflicts with game updates.
+ syntax:
+ content: '"idx": 5'
diff --git a/docs/gld/MinorDatas.md b/docs/gld/MinorDatas.md
new file mode 100644
index 0000000..df74a6a
--- /dev/null
+++ b/docs/gld/MinorDatas.md
@@ -0,0 +1,130 @@
+# Minor Data Structures
+
+This page documents the smaller data structures found in the game's data file, including terrain, resources, tasks, skins, and diplomacy settings.
+
+---
+
+## Terrain Data (`terrainData`)
+
+Defines all possible terrain types in the game. Each terrain is an object within the `terrainData` section, keyed by its name. To add a new terrain, simply add a new entry with a unique name and `idx`.
+
+### Syntax
+
+```json
+"terrainData": {
+ "water": {
+ "idx": 1
+ },
+ "field": {
+ "idx": 3
+ }
+}
+```
+
+### Properties
+
+| Property | Type | Description | Example Value |
+| :------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ |
+| `idx` | Integer | A unique integer index for the terrain type. When adding a new entry, it is recommended to set this value to **-1** to ensure uniqueness and prevent conflicts with game updates. | `"idx": 1` |
+
+---
+
+## Resource Data (`resourceData`)
+
+Defines all possible resource types that can spawn on the map.
+
+### Syntax
+
+```json
+"resourceData": {
+ "game": {
+ "resourceTerrainRequirements": [
+ "forest"
+ ],
+ "idx": 1
+ }
+}
+```
+
+### Properties
+
+| Property | Type | Description | Example Value |
+| :------------------------------ | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------- |
+| `resourceTerrainRequirements` | Array of Strings | An array of terrain IDs where this resource can naturally spawn. | `["forest"]` |
+| `idx` | Integer | A unique integer index for the resource type. When adding a new entry, it is recommended to set this value to **-1** to ensure uniqueness and prevent conflicts with game updates. | `"idx": 1` |
+
+---
+
+## Task Data (`taskData`)
+
+Defines special in-game achievements or "tasks" that unlock unique monuments upon completion.
+
+### Syntax
+
+```json
+"taskData": {
+ "pacifist": {
+ "improvementUnlocks": [
+ "monument1"
+ ],
+ "idx": 1
+ }
+}
+```
+
+### Properties
+
+| Property | Type | Description | Example Value |
+| :--------------------- | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------- |
+| `improvementUnlocks` | Array of Strings | An array of improvement IDs (usually a monument) unlocked when this task is completed. | `["monument1"]` |
+| `idx` | Integer | A unique integer index for the task. When adding a new entry, it is recommended to set this value to **-1** to ensure uniqueness and prevent conflicts with game updates. | `"idx": 1` |
+
+---
+
+## Skin Data (`skinData`)
+
+Defines alternate appearances (skins) for tribes. A tribe must list the skin's key in its `skins` array to use it.
+
+### Syntax
+
+```json
+"skinData": {
+ "swamp" : {
+ "color" : 6786096,
+ "language": "bub,ly,sq,ee,to,ad"
+ }
+}
+```
+
+### Properties
+
+| Property | Type | Description | Example Value |
+| :--------- | :------ | :---------------------------------------------------------------------------------- | :----------------------------------- |
+| `color` | Integer | An integer representing the skin's primary color, overriding the tribe's default. | `"color": 6786096` |
+| `language` | String | A comma-separated string of syllables, overriding the tribe's default language. | `"language": "bub,ly,sq,ee,to,ad"` |
+
+---
+
+## Diplomacy Data (`diplomacyData`)
+
+Contains key-value pairs that define the global game mechanics for diplomacy, such as embassies.
+
+### Syntax
+
+```json
+"diplomacyData": {
+ "embassyCost" : 5,
+ "embassyIncome" : 2,
+ "embassyMaxLevel" : 3,
+ "embassyUpgradeCost" : 20
+}
+```
+
+### Properties
+
+| Property | Type | Description | Example Value |
+| :--------------------- | :------ | :---------------------------------------------------------------- | :--------------------- |
+| `embassyCost` | Integer | The base star cost to build an embassy in another player's capital. | `"embassyCost": 5` |
+| `embassyIncome` | Integer | The number of stars an embassy generates per turn for its owner. | `"embassyIncome": 2` |
+| `embassyMaxLevel` | Integer | The maximum level an embassy can be upgraded to. | `"embassyMaxLevel": 3` |
+| `embassyUpgradeCost` | Integer | The star cost to upgrade an embassy to the next level. | `"embassyUpgradeCost": 20` |
diff --git a/docs/gld/Technology.yml b/docs/gld/Technology.yml
new file mode 100644
index 0000000..fee1dbe
--- /dev/null
+++ b/docs/gld/Technology.yml
@@ -0,0 +1,95 @@
+### YamlMime:ManagedReference
+items:
+- uid: GameData.TechData
+ id: TechData
+ name: Tech Data
+ fullName: Tech Data
+ type: Class
+ summary: |
+ Defines the properties for a technology in the tech tree. Each tech is an object within the `techData` section, keyed by its unique name.
+
+ To add a new tech, create a new entry with a unique key and define its properties. You must also link it from another tech's `techUnlocks` property to make it accessible.
+ syntax:
+ content: '"techData": { "your_tech_name": { ... } }'
+ children:
+ - GameData.TechData.techUnlocks
+ - GameData.TechData.unitUnlocks
+ - GameData.TechData.improvementUnlocks
+ - GameData.TechData.abilityUnlocks
+ - GameData.TechData.movementUnlocks
+ - GameData.TechData.defenceBonusUnlocks
+ - GameData.TechData.cost
+ - GameData.TechData.idx
+
+- uid: GameData.TechData.techUnlocks
+ id: techUnlocks
+ parent: GameData.TechData
+ name: techUnlocks
+ fullName: techUnlocks
+ type: Property
+ summary: An array of strings listing the tech IDs that become available after researching this one. This defines the tech tree's structure.
+ syntax:
+ content: '"techUnlocks": ["roads", "freespirit"]'
+- uid: GameData.TechData.unitUnlocks
+ id: unitUnlocks
+ parent: GameData.TechData
+ name: unitUnlocks
+ fullName: unitUnlocks
+ type: Property
+ summary: An array of strings listing the unit IDs that can be trained after researching this tech.
+ syntax:
+ content: '"unitUnlocks": ["rider"]'
+- uid: GameData.TechData.improvementUnlocks
+ id: improvementUnlocks
+ parent: GameData.TechData
+ name: improvementUnlocks
+ fullName: improvementUnlocks
+ type: Property
+ summary: An array of strings listing the improvement IDs that can be built after researching this tech.
+ syntax:
+ content: '"improvementUnlocks": ["road", "bridge"]'
+- uid: GameData.TechData.abilityUnlocks
+ id: abilityUnlocks
+ parent: GameData.TechData
+ name: abilityUnlocks
+ fullName: abilityUnlocks
+ type: Property
+ summary: An array of strings listing new player or unit actions unlocked by this tech (e.g., "disband", "destroy").
+ syntax:
+ content: '"abilityUnlocks": ["destroy"]'
+- uid: GameData.TechData.movementUnlocks
+ id: movementUnlocks
+ parent: GameData.TechData
+ name: movementUnlocks
+ fullName: movementUnlocks
+ type: Property
+ summary: An object where keys are terrain IDs and values are the new movement cost for that terrain.
+ syntax:
+ content: '"movementUnlocks": { "mountain": 2 }'
+- uid: GameData.TechData.defenceBonusUnlocks
+ id: defenceBonusUnlocks
+ parent: GameData.TechData
+ name: defenceBonusUnlocks
+ fullName: defenceBonusUnlocks
+ type: Property
+ summary: An object where keys are terrain IDs and values are the defense bonus percentage (e.g., 15 for +15%) for units on that terrain.
+ syntax:
+ content: '"defenceBonusUnlocks": { "mountain": 15 }'
+- uid: GameData.TechData.cost
+ id: cost
+ parent: GameData.TechData
+ name: cost
+ fullName: cost
+ type: Property
+ summary: An integer representing the base star cost to research this technology.
+ syntax:
+ content: '"cost": 1'
+- uid: GameData.TechData.idx
+ id: idx
+ parent: GameData.TechData
+ name: idx
+ fullName: idx
+ type: Property
+ summary: A unique integer index for the tech. Must be unique across all technologies. When adding a new entry, it is recommended to set this value to -1 to ensure uniqueness and prevent conflicts with game updates.
+ syntax:
+ content: '"idx": 1'
diff --git a/docs/gld/Tribe.yml b/docs/gld/Tribe.yml
new file mode 100644
index 0000000..d585ad0
--- /dev/null
+++ b/docs/gld/Tribe.yml
@@ -0,0 +1,195 @@
+### YamlMime:ManagedReference
+items:
+- uid: GameData.TribeData
+ id: TribeData
+ name: Tribe Data
+ fullName: Tribe Data
+ type: Class
+ summary: |
+ Defines the properties for a tribe. Each tribe is an object within the `tribeData` section of the JSON file, keyed by its unique name (e.g., "aimo", "bardur").
+
+ To add a new tribe, create a new entry with a unique key and define its properties as described below.
+ syntax:
+ content: '"tribeData": { "your_tribe_name": { ... } }'
+ children:
+ - GameData.TribeData.color
+ - GameData.TribeData.style
+ - GameData.TribeData.climate
+ - GameData.TribeData.language
+ - GameData.TribeData.startingTech
+ - GameData.TribeData.startingResource
+ - GameData.TribeData.startingUnit
+ - GameData.TribeData.priceTier
+ - GameData.TribeData.category
+ - GameData.TribeData.bonus
+ - GameData.TribeData.terrainModifier
+ - GameData.TribeData.resourceModifier
+ - GameData.TribeData.unitOverrides
+ - GameData.TribeData.techOverrides
+ - GameData.TribeData.improvementOverrides
+ - GameData.TribeData.tribeAbilities
+ - GameData.TribeData.skins
+ - GameData.TribeData.idx
+
+- uid: GameData.TribeData.color
+ id: color
+ parent: GameData.TribeData
+ name: color
+ fullName: color
+ type: Property
+ summary: An integer representing the tribe's primary color. This is often a decimal representation of a hex color code.
+ syntax:
+ content: '"color": 3596970'
+- uid: GameData.TribeData.style
+ id: style
+ parent: GameData.TribeData
+ name: style
+ fullName: style
+ type: Property
+ summary: An integer ID that determines the visual style of the tribe's cities and units.
+ syntax:
+ content: '"style": 10'
+- uid: GameData.TribeData.climate
+ id: climate
+ parent: GameData.TribeData
+ name: climate
+ fullName: climate
+ type: Property
+ summary: An integer ID that determines the environmental look and feel, including terrain colors and vegetation.
+ syntax:
+ content: '"climate": 10'
+- uid: GameData.TribeData.language
+ id: language
+ parent: GameData.TribeData
+ name: language
+ fullName: language
+ type: Property
+ summary: A comma-separated string of syllables used to generate names and sounds for the tribe.
+ syntax:
+ content: '"language": "gu,rø,lak,bu,gru,tof,fla,ork,lin,ark,ur"'
+- uid: GameData.TribeData.startingTech
+ id: startingTech
+ parent: GameData.TribeData
+ name: startingTech
+ fullName: startingTech
+ type: Property
+ summary: An array of strings listing the tech IDs the tribe begins the game with. "basic" is required for all tribes.
+ syntax:
+ content: '"startingTech": ["basic", "meditation"]'
+- uid: GameData.TribeData.startingResource
+ id: startingResource
+ parent: GameData.TribeData
+ name: startingResource
+ fullName: startingResource
+ type: Property
+ summary: An array of strings listing the resource IDs guaranteed to spawn near the tribe's starting city.
+ syntax:
+ content: '"startingResource": ["game"]'
+- uid: GameData.TribeData.startingUnit
+ id: startingUnit
+ parent: GameData.TribeData
+ name: startingUnit
+ fullName: startingUnit
+ type: Property
+ summary: The string ID of the unit the tribe starts with (e.g., "warrior", "rider").
+ syntax:
+ content: '"startingUnit": "warrior"'
+- uid: GameData.TribeData.priceTier
+ id: priceTier
+ parent: GameData.TribeData
+ name: priceTier
+ fullName: priceTier
+ type: Property
+ summary: An integer (0-3) determining the tribe's cost in the selection screen. 0 is free, 1-3 correspond to increasing costs.
+ syntax:
+ content: '"priceTier": 1'
+- uid: GameData.TribeData.category
+ id: category
+ parent: GameData.TribeData
+ name: category
+ fullName: category
+ type: Property
+ summary: An integer ID for grouping tribes. (e.g., 1 for Normal, 2 for Special).
+ syntax:
+ content: '"category": 1'
+- uid: GameData.TribeData.bonus
+ id: bonus
+ parent: GameData.TribeData
+ name: bonus
+ fullName: bonus
+ type: Property
+ summary: An integer that determines the tribe's starting star bonus. 0 means no bonus, 1 gives a larger capital and more starting stars (e.g., Luxidoor).
+ syntax:
+ content: '"bonus": 0'
+- uid: GameData.TribeData.terrainModifier
+ id: terrainModifier
+ parent: GameData.TribeData
+ name: terrainModifier
+ fullName: terrainModifier
+ type: Property
+ summary: An object where keys are terrain IDs (e.g., "mountain") and values are numeric multipliers for their spawn rate. >1 increases spawn rate, <1 decreases it.
+ syntax:
+ content: '"terrainModifier": { "mountain": 1.5 }'
+- uid: GameData.TribeData.resourceModifier
+ id: resourceModifier
+ parent: GameData.TribeData
+ name: resourceModifier
+ fullName: resourceModifier
+ type: Property
+ summary: An object where keys are resource IDs (e.g., "crop") and values are numeric multipliers for their spawn rate. >1 increases spawn rate, <1 decreases it. A value of 0 prevents the resource from spawning.
+ syntax:
+ content: '"resourceModifier": { "crop": 0.1 }'
+- uid: GameData.TribeData.unitOverrides
+ id: unitOverrides
+ parent: GameData.TribeData
+ name: unitOverrides
+ fullName: unitOverrides
+ type: Property
+ summary: An object that replaces standard units with tribe-specific ones. The key is the base unit ID (e.g., "giant"), and the value is the replacement unit ID (e.g., "crab").
+ syntax:
+ content: '"unitOverrides": { "giant": "crab" }'
+- uid: GameData.TribeData.techOverrides
+ id: techOverrides
+ parent: GameData.TribeData
+ name: techOverrides
+ fullName: techOverrides
+ type: Property
+ summary: An object that replaces standard technologies with tribe-specific ones. The key is the base tech ID (e.g., "sailing"), and the value is the replacement tech ID (e.g., "marinelife").
+ syntax:
+ content: '"techOverrides": { "sailing": "marinelife" }'
+- uid: GameData.TribeData.improvementOverrides
+ id: improvementOverrides
+ parent: GameData.TribeData
+ name: improvementOverrides
+ fullName: improvementOverrides
+ type: Property
+ summary: An object that replaces standard improvements with tribe-specific ones. The key is the base improvement ID (e.g., "burnforest"), and the value is the replacement ID or "none" to disable it.
+ syntax:
+ content: '"improvementOverrides": { "burnforest": "none" }'
+- uid: GameData.TribeData.tribeAbilities
+ id: tribeAbilities
+ parent: GameData.TribeData
+ name: tribeAbilities
+ fullName: tribeAbilities
+ type: Property
+ summary: An array of strings representing special abilities unique to the tribe, such as "poisonresist" or "freeze".
+ syntax:
+ content: '"tribeAbilities": ["poisonresist"]'
+- uid: GameData.TribeData.skins
+ id: skins
+ parent: GameData.TribeData
+ name: skins
+ fullName: skins
+ type: Property
+ summary: An array of string IDs referencing skins from `skinData` that are available for this tribe.
+ syntax:
+ content: '"skins":["Aibo"]'
+- uid: GameData.TribeData.idx
+ id: idx
+ parent: GameData.TribeData
+ name: idx
+ fullName: idx
+ type: Property
+ summary: A unique integer index for the tribe. Must be unique across all tribes. When adding a new entry, it is recommended to set this value to -1 to ensure uniqueness and prevent conflicts with game updates.
+ syntax:
+ content: '"idx": 2'
diff --git a/docs/gld/Unit.yml b/docs/gld/Unit.yml
new file mode 100644
index 0000000..1d4e117
--- /dev/null
+++ b/docs/gld/Unit.yml
@@ -0,0 +1,138 @@
+### YamlMime:ManagedReference
+items:
+- uid: GameData.UnitData
+ id: UnitData
+ name: Unit Data
+ fullName: Unit Data
+ type: Class
+ summary: |
+ Defines the properties for a unit. Each unit is an object within the `unitData` section of the JSON file, keyed by its unique name (e.g., "warrior", "knight").
+
+ To add a new unit, create a new entry with a unique key and define its properties as described below.
+ syntax:
+ content: '"unitData": { "your_unit_name": { ... } }'
+ children:
+ - GameData.UnitData.health
+ - GameData.UnitData.defence
+ - GameData.UnitData.attack
+ - GameData.UnitData.movement
+ - GameData.UnitData.range
+ - GameData.UnitData.cost
+ - GameData.UnitData.unitAbilities
+ - GameData.UnitData.hidden
+ - GameData.UnitData.upgradesFrom
+ - GameData.UnitData.promotionLimit
+ - GameData.UnitData.idx
+
+- uid: GameData.UnitData.health
+ id: health
+ parent: GameData.UnitData
+ name: health
+ fullName: health
+ type: Property
+ summary: An integer representing the unit's maximum health points.
+ syntax:
+ content: '"health": 100'
+- uid: GameData.UnitData.defence
+ id: defence
+ parent: GameData.UnitData
+ name: defence
+ fullName: defence
+ type: Property
+ summary: An integer representing the unit's defense stat.
+ syntax:
+ content: '"defence": 20'
+- uid: GameData.UnitData.attack
+ id: attack
+ parent: GameData.UnitData
+ name: attack
+ fullName: attack
+ type: Property
+ summary: An integer representing the unit's attack stat.
+ syntax:
+ content: '"attack": 20'
+- uid: GameData.UnitData.movement
+ id: movement
+ parent: GameData.UnitData
+ name: movement
+ fullName: movement
+ type: Property
+ summary: An integer for the number of tiles the unit can move per turn.
+ syntax:
+ content: '"movement": 1'
+- uid: GameData.UnitData.range
+ id: range
+ parent: GameData.UnitData
+ name: range
+ fullName: range
+ type: Property
+ summary: An integer for the unit's attack range in tiles. A value of 1 means melee.
+ syntax:
+ content: '"range": 1'
+- uid: GameData.UnitData.cost
+ id: cost
+ parent: GameData.UnitData
+ name: cost
+ fullName: cost
+ type: Property
+ summary: The number of stars required to train this unit. A cost of 0 often means the unit cannot be trained directly.
+ syntax:
+ content: '"cost": 2'
+- uid: GameData.UnitData.unitAbilities
+ id: unitAbilities
+ parent: GameData.UnitData
+ name: unitAbilities
+ fullName: unitAbilities
+ type: Property
+ summary: |
+ An array of strings listing the special abilities of the unit. Possible values include:
+ - `dash` - Can attack after moving.
+ - `fortify` - Can gain a defensive bonus by not moving.
+ - `escape` - Can move after attacking.
+ - `persist` - Can attack multiple enemies in one turn if the first is destroyed.
+ - `fly` - Can move over any terrain.
+ - `swim` - Can move on water tiles.
+ - `amphibious` - Can move on both land and water tiles.
+ - `skate` - Can move on ice tiles.
+ - `creep` - Can ignore enemy zone of control.
+ - `convert` - Can convert enemy units.
+ - `heal` - Can heal nearby friendly units.
+ - `poison` - Applies a damage-over-time effect on attack.
+ syntax:
+ content: '"unitAbilities": ["dash", "fortify", "land"]'
+- uid: GameData.UnitData.hidden
+ id: hidden
+ parent: GameData.UnitData
+ name: hidden
+ fullName: hidden
+ type: Property
+ summary: A boolean (`true`/`false`) indicating if the unit should be hidden from standard training menus. Often used for summoned units or Giants.
+ syntax:
+ content: '"hidden": true'
+- uid: GameData.UnitData.upgradesFrom
+ id: upgradesFrom
+ parent: GameData.UnitData
+ name: upgradesFrom
+ fullName: upgradesFrom
+ type: Property
+ summary: A string ID of the unit that this unit upgrades from (e.g., "firedragon" upgrades from "babydragon").
+ syntax:
+ content: '"upgradesFrom": "babydragon"'
+- uid: GameData.UnitData.promotionLimit
+ id: promotionLimit
+ parent: GameData.UnitData
+ name: promotionLimit
+ fullName: promotionLimit
+ type: Property
+ summary: An integer for the maximum number of promotions this unit can receive. 0 means it cannot be promoted.
+ syntax:
+ content: '"promotionLimit": 3'
+- uid: GameData.UnitData.idx
+ id: idx
+ parent: GameData.UnitData
+ name: idx
+ fullName: idx
+ type: Property
+ summary: A unique integer index for the unit. Must be unique across all units. When adding a new entry, it is recommended to set this value to -1 to ensure uniqueness and prevent conflicts with game updates.
+ syntax:
+ content: '"idx": 2'
diff --git a/docs/gld/index.md b/docs/gld/index.md
new file mode 100644
index 0000000..1460a84
--- /dev/null
+++ b/docs/gld/index.md
@@ -0,0 +1,5 @@
+# Game Data Reference
+
+Welcome to the game data reference documentation. This section provides a detailed breakdown of the structure of the main JSON data file. Use this reference to understand how to modify existing game elements or create your own custom content, such as new tribes, units, or technologies.
+
+Each page documents the schema for a top-level section of the JSON file (e.g., `tribeData`, `unitData`). It explains the purpose of each property and the types of values it expects.
diff --git a/docs/gld/toc.yml b/docs/gld/toc.yml
new file mode 100644
index 0000000..ff104d1
--- /dev/null
+++ b/docs/gld/toc.yml
@@ -0,0 +1,12 @@
+- name: Game Data Reference
+ href: index.md
+- name: Tribe Data
+ href: Tribe.yml
+- name: Unit Data
+ href: Unit.yml
+- name: Technology Data
+ href: Technology.yml
+- name: Improvement Data
+ href: Improvement.yml
+- name: Minor Data Structures
+ href: MinorDatas.md
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..6023cdd
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,11 @@
+---
+_layout: landing
+---
+
+# This is the **HOMEPAGE**.
+
+Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files.
+
+## Quick Start Notes:
+
+1. Add images to the *images* folder if the file is referencing an image.
\ No newline at end of file
diff --git a/docs/toc.yml b/docs/toc.yml
new file mode 100644
index 0000000..d0885f7
--- /dev/null
+++ b/docs/toc.yml
@@ -0,0 +1,6 @@
+- name: Docs
+ href: articles/
+- name: API
+ href: api/
+- name: GLD
+ href: gld/
diff --git a/resources/localization.json b/resources/localization.json
index e9afd56..c74653e 100644
--- a/resources/localization.json
+++ b/resources/localization.json
@@ -22,15 +22,15 @@
"German (Germany)": "UNSER DISCORD"
},
"polymod_hub_footer": {
- "English": "Join our discord! Feel free to discuss mods, create them and ask for help!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
- "Russian": "Присоединяйтесь к нашему дискорду! Не стесняйтесь обсуждать моды, создавать их и просить о помощи!\n\n{0}Особая благодарность{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
- "Turkish": "Discord sunucumuza katıl! Orada modlar oluşturabilir, tartışabilir ve yardım isteyebilirsin!\n\n{0}Hepinize çok teşekkür ederim:{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
- "Spanish (Mexico)": "Unete a nuestro discord! Aqui se puede discutir sobre la modificacion del juego, guias para crear su propio, preguntar a los creadores, y mas!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
- "French (France)": "Rejoignez notre discord! N'hésitez pas à discuter des mods, à en créer et à demander de l'aide!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
- "Polish": "Dołącz do naszego discorda! Zachęcamy do omawiania modów, tworzenia ich lub proszenia o pomoc!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
- "Portuguese (Brazil)": "Entre no nosso Discord! Sinta-se à vontade para discutir sobre os mods, criar novos mods e pedir ajuda!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
- "Elyrion": "§ii∫ Δi^#ȱrΔ! Δi^#₺^^ mȱΔ#, ȱrrȱ ỹ a^š ỹȱπ!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
- "German (Germany)": "Tritt unserem Discord bei, um Hilfe zu bekommen, Mods zu diskutieren oder sogar selbst zu erstellen!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon"
+ "English": "Join our discord! Feel free to discuss mods, create them and ask for help!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
+ "Russian": "Присоединяйтесь к нашему дискорду! Не стесняйтесь обсуждать моды, создавать их и просить о помощи!\n\n{0}Особая благодарность{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
+ "Turkish": "Discord sunucumuza katıl! Orada modlar oluşturabilir, tartışabilir ve yardım isteyebilirsin!\n\n{0}Hepinize çok teşekkür ederim:{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
+ "Spanish (Mexico)": "Unete a nuestro discord! Aqui se puede discutir sobre la modificacion del juego, guias para crear su propio, preguntar a los creadores, y mas!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
+ "French (France)": "Rejoignez notre discord! N'hésitez pas à discuter des mods, à en créer et à demander de l'aide!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
+ "Polish": "Dołącz do naszego discorda! Zachęcamy do omawiania modów, tworzenia ich lub proszenia o pomoc!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
+ "Portuguese (Brazil)": "Entre no nosso Discord! Sinta-se à vontade para discutir sobre os mods, criar novos mods e pedir ajuda!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
+ "Elyrion": "§ii∫ Δi^#ȱrΔ! Δi^#₺^^ mȱΔ#, ȱrrȱ ỹ a^š ỹȱπ!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
+ "German (Germany)": "Tritt unserem Discord bei, um Hilfe zu bekommen, Mods zu diskutieren oder sogar selbst zu erstellen!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon"
},
"polymod_hub_header": {
"English": "{0}Welcome!{1}\nHere you can see the list of all currently loaded mods:",
diff --git a/src/Constants.cs b/src/Constants.cs
new file mode 100644
index 0000000..3a74747
--- /dev/null
+++ b/src/Constants.cs
@@ -0,0 +1,35 @@
+namespace PolyMod;
+
+///
+/// useful constant values.
+///
+public partial class Constants
+{
+ ///
+ /// Path of the polytopia folder
+ ///
+ public static readonly string BASE_PATH = Path.Combine(BepInEx.Paths.BepInExRootPath, "..");
+ ///
+ /// path of the mods folder
+ ///
+ public static readonly string MODS_PATH = Path.Combine(BASE_PATH, "Mods");
+ internal static readonly string CONFIG_PATH = Path.Combine(BASE_PATH, "PolyMod.json");
+ internal static readonly string DISCORD_LINK = "https://discord.gg/eWPdhWtfVy";
+ internal const int AUTOIDX_STARTS_FROM = 1000;
+ internal static readonly List LOG_MESSAGES_IGNORE = new()
+ {
+ "Failed to find atlas",
+ "Could not find sprite",
+ "Couldn't find prefab for type",
+ "MARKET: id:",
+ "Missing name for value",
+ };
+ ///
+ /// kFilename of the dumped data
+ ///
+ public static readonly string DUMPED_DATA_PATH = Path.Combine(BASE_PATH, "DumpedData");
+ internal static readonly string CHECKSUM_PATH
+ = Path.Combine(BASE_PATH, "CHECKSUM");
+ internal const string INCOMPATIBILITY_WARNING_LAST_VERSION_KEY
+ = "INCOMPATIBILITY_WARNING_LAST_VERSION";
+}
diff --git a/src/Loader.cs b/src/Loader.cs
index bdf79a2..87b8ce4 100644
--- a/src/Loader.cs
+++ b/src/Loader.cs
@@ -12,13 +12,14 @@
using System.Globalization;
using System.IO.Compression;
using System.Reflection;
+using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using UnityEngine;
namespace PolyMod;
-public static class Loader
+internal static class Loader
{
internal static Dictionary typeMappings = new()
{
@@ -132,27 +133,15 @@ public static class Loader
};
public record GameModeButtonsInformation(int gameModeIndex, UIButtonBase.ButtonAction action, int? buttonIndex, Sprite? sprite);
+
+
- public static void AddGameModeButton(string id, UIButtonBase.ButtonAction action, Sprite? sprite)
+ internal static bool RegisterMods(Dictionary mods)
{
- EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx);
- EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx);
- gamemodes.Add(new GameModeButtonsInformation(Registry.gameModesAutoidx, action, null, sprite));
- Registry.gameModesAutoidx++;
- }
-
- public static void AddPatchDataType(string typeId, Type type)
- {
- if (!typeMappings.ContainsKey(typeId))
- typeMappings.Add(typeId, type);
- }
-
- internal static void LoadMods(Dictionary mods)
- {
- Directory.CreateDirectory(Plugin.MODS_PATH);
- string[] modContainers = Directory.GetDirectories(Plugin.MODS_PATH)
- .Union(Directory.GetFiles(Plugin.MODS_PATH, "*.polymod"))
- .Union(Directory.GetFiles(Plugin.MODS_PATH, "*.zip"))
+ Directory.CreateDirectory(Constants.MODS_PATH);
+ string[] modContainers = Directory.GetDirectories(Constants.MODS_PATH)
+ .Union(Directory.GetFiles(Constants.MODS_PATH, "*.polymod"))
+ .Union(Directory.GetFiles(Constants.MODS_PATH, "*.zip"))
.ToArray();
foreach (var modContainer in modContainers)
{
@@ -195,7 +184,7 @@ internal static void LoadMods(Dictionary mods)
files.Add(new(entry.FullName, entry.ReadBytes()));
}
}
-
+ #region ValidateManifest()
if (manifest == null)
{
Plugin.logger.LogError($"Mod manifest not found in {modContainer}");
@@ -226,6 +215,7 @@ internal static void LoadMods(Dictionary mods)
Plugin.logger.LogError($"Mod {manifest.id} already exists");
continue;
}
+ #endregion
mods.Add(manifest.id, new(
manifest,
Mod.Status.Success,
@@ -234,6 +224,102 @@ internal static void LoadMods(Dictionary mods)
Plugin.logger.LogInfo($"Registered mod {manifest.id}");
}
+ CheckDependencies(mods);
+ var dependencyCycle = !SortMods(Registry.mods);
+ return dependencyCycle;
+ }
+ internal static void LoadMods(Dictionary mods)
+ {
+ StringBuilder checksumString = new();
+ foreach (var (id, mod) in Registry.mods)
+ {
+ if (mod.status != Mod.Status.Success) continue;
+ foreach (var file in mod.files)
+ {
+ checksumString.Append(JsonSerializer.Serialize(file));
+ if (Path.GetExtension(file.name) == ".dll")
+ {
+ LoadAssemblyFile(mod, file);
+ }
+ if (Path.GetFileName(file.name) == "sprites.json")
+ {
+ LoadSpriteInfoFile(mod, file);
+ }
+ }
+ if (!mod.client && id != "polytopia")
+ {
+ checksumString.Append(id);
+ checksumString.Append(mod.version.ToString());
+ }
+ }
+ Compatibility.HashSignatures(checksumString);
+
+ }
+ internal static void PatchGLD(JObject gameLogicdata, Dictionary mods)
+ {
+ Loc.BuildAndLoadLocalization(
+ JsonSerializer.Deserialize>>(
+ Plugin.GetResource("localization.json")
+ )!
+ );
+ foreach (var (id, mod) in mods)
+ {
+ if (mod.status != Mod.Status.Success) continue;
+ foreach (var file in mod.files)
+ {
+ if (Path.GetFileName(file.name) == "localization.json")
+ {
+ Loader.LoadLocalizationFile(mod, file);
+ continue;
+ }
+ if (Regex.IsMatch(Path.GetFileName(file.name), @"^patch(_.*)?\.json$"))
+ {
+ var patchText = new StreamReader(new MemoryStream(file.bytes)).ReadToEnd();
+ var template = new GldConfigTemplate(patchText, mod.id);
+ var text = template.Render();
+ if (text is null)
+ {
+ mod.status = Mod.Status.Error;
+ continue;
+ }
+ Loader.LoadGameLogicDataPatch(
+ mod,
+ gameLogicdata,
+ JObject.Parse(text)
+ );
+ continue;
+ }
+ if (Regex.IsMatch(Path.GetFileName(file.name), @"^prefab(_.*)?\.json$"))
+ {
+ Loader.LoadPrefabInfoFile(
+ mod,
+ file
+ );
+ continue;
+ }
+
+ switch (Path.GetExtension(file.name))
+ {
+ case ".png":
+ Loader.LoadSpriteFile(mod, file);
+ break;
+ case ".wav":
+ Loader.LoadAudioFile(mod, file);
+ break;
+ case ".bundle":
+ Loader.LoadAssetBundle(mod, file);
+ break;
+ }
+ }
+ }
+ TechItem.techTierFirebaseId.Clear();
+ for (int i = 0; i <= Main.MAX_TECH_TIER; i++)
+ {
+ TechItem.techTierFirebaseId.Add($"tech_research_{i}");
+ }
+ }
+ private static void CheckDependencies(Dictionary mods)
+ {
foreach (var (id, mod) in mods)
{
foreach (var dependency in mod.dependencies ?? Array.Empty())
@@ -271,7 +357,7 @@ internal static void LoadMods(Dictionary mods)
}
}
- internal static bool SortMods(Dictionary mods)
+ private static bool SortMods(Dictionary mods)
{
Stopwatch s = new();
Dictionary> graph = new();
@@ -330,11 +416,21 @@ internal static bool SortMods(Dictionary mods)
return true;
}
- public static void LoadAssemblyFile(Mod mod, Mod.File file)
+ internal static void LoadAssemblyFile(Mod mod, Mod.File file)
{
try
{
Assembly assembly = Assembly.Load(file.bytes);
+ if (assembly
+ .GetTypes()
+ .FirstOrDefault(t => t.IsSubclassOf(typeof(PolyScriptModBase)))
+ is { } modType)
+ {
+ var modInstance = (PolyScriptModBase) Activator.CreateInstance(modType)!;
+ modInstance.Initialize(mod.id, BepInEx.Logging.Logger.CreateLogSource($"PolyMod] [{mod.id}"));
+ modInstance.Load();
+ return;
+ }
foreach (Type type in assembly.GetTypes())
{
MethodInfo? loadWithLogger = type.GetMethod("Load", new Type[] { typeof(ManualLogSource) });
@@ -364,7 +460,7 @@ public static void LoadAssemblyFile(Mod mod, Mod.File file)
}
}
- public static void LoadLocalizationFile(Mod mod, Mod.File file)
+ internal static void LoadLocalizationFile(Mod mod, Mod.File file)
{
try
{
@@ -378,7 +474,7 @@ public static void LoadLocalizationFile(Mod mod, Mod.File file)
}
}
- public static void LoadSpriteFile(Mod mod, Mod.File file)
+ internal static void LoadSpriteFile(Mod mod, Mod.File file)
{
string name = Path.GetFileNameWithoutExtension(file.name);
Vector2 pivot = name.Split("_")[0] switch
@@ -400,7 +496,7 @@ public static void LoadSpriteFile(Mod mod, Mod.File file)
Registry.sprites.Add(name, sprite);
}
- public static void UpdateSprite(string name)
+ internal static void UpdateSprite(string name)
{
if (Registry.spriteInfos.ContainsKey(name) && Registry.sprites.ContainsKey(name))
{
@@ -415,7 +511,7 @@ public static void UpdateSprite(string name)
}
}
- public static Dictionary? LoadSpriteInfoFile(Mod mod, Mod.File file)
+ internal static Dictionary? LoadSpriteInfoFile(Mod mod, Mod.File file)
{
try
{
@@ -445,7 +541,7 @@ public static void UpdateSprite(string name)
}
}
- public static void LoadAudioFile(Mod mod, Mod.File file)
+ internal static void LoadAudioFile(Mod mod, Mod.File file)
{
// AudioSource audioSource = new GameObject().AddComponent();
// GameObject.DontDestroyOnLoad(audioSource);
@@ -454,7 +550,7 @@ public static void LoadAudioFile(Mod mod, Mod.File file)
// TODO: issue #71
}
- public static void LoadPrefabInfoFile(Mod mod, Mod.File file)
+ internal static void LoadPrefabInfoFile(Mod mod, Mod.File file)
{
try
{
@@ -567,7 +663,7 @@ private static SkinVisualsReference.VisualPart CreateVisualPart(
return visualPart;
}
- public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch)
+ internal static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch)
{
try
{
@@ -626,7 +722,7 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch)
}
}
- public static void LoadAssetBundle(Mod mod, Mod.File file)
+ internal static void LoadAssetBundle(Mod mod, Mod.File file)
{
Registry.assetBundles.Add(
Path.GetFileNameWithoutExtension(file.name),
@@ -634,7 +730,7 @@ public static void LoadAssetBundle(Mod mod, Mod.File file)
);
}
- public static void HandleSkins(JObject gld, JObject patch)
+ internal static void HandleSkins(JObject gld, JObject patch)
{
foreach (JToken jtoken in patch.SelectTokens("$.tribeData.*").ToArray())
{
diff --git a/src/Managers/Config.cs b/src/Managers/Config.cs
new file mode 100644
index 0000000..4b91e55
--- /dev/null
+++ b/src/Managers/Config.cs
@@ -0,0 +1,120 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace PolyMod.Managers;
+
+///
+/// Allows mods to save config.
+///
+public class Config where T : class
+{
+ private T? currentConfig;
+ private readonly string modName;
+ private readonly ConfigTypes configType;
+ private static readonly string ExposedConfigPath = Path.Combine(Constants.BASE_PATH, "mods.json");
+ private readonly string perModConfigPath;
+ private T? defaultConfig;
+ public Config(string modName, ConfigTypes configType)
+ {
+ this.modName = modName;
+ this.configType = configType;
+ perModConfigPath = Path.Combine(Constants.MODS_PATH, $"{modName}.json");
+ Load();
+ }
+
+ internal void Load() // can be called internally if config changes; gui config not implemented yet
+ {
+ switch (configType)
+ {
+ case ConfigTypes.PerMod:
+ {
+ if (!File.Exists(perModConfigPath))
+ {
+ return;
+ }
+ var jsonText = File.ReadAllText(perModConfigPath);
+ currentConfig = JsonSerializer.Deserialize(jsonText);
+ break;
+ }
+ case ConfigTypes.Exposed:
+ {
+ if (!File.Exists(ExposedConfigPath))
+ {
+ return;
+ }
+ var jsonText = File.ReadAllText(ExposedConfigPath);
+ currentConfig = JsonNode.Parse(jsonText)![modName]?.Deserialize();
+ break;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ ///
+ /// Sets the default if the config does not exist yet. Always call this before reading from the config.
+ ///
+ public void SetDefaultConfig(T defaultValue)
+ {
+ defaultConfig = defaultValue;
+ if (currentConfig is not null) return;
+ Write(defaultConfig);
+ SaveChanges();
+ }
+
+ ///
+ /// Writes the **entire** config. Usage not recommended, use Edit() instead
+ ///
+ public void Write(T config)
+ {
+ currentConfig = config;
+ }
+ ///
+ /// Gets the config. Should only be called after setting a default.
+ ///
+ public T Get()
+ {
+ return currentConfig ?? throw new InvalidOperationException("Must set default before reading config.");
+ }
+ ///
+ /// Edits the config. Should only be called after setting a default.
+ ///
+ /// Call SaveChanges after editing
+ public void Edit(Action editor)
+ {
+ editor(currentConfig ?? throw new InvalidOperationException("Must set default before reading config."));
+ }
+ ///
+ /// Gets part of the config. Should only be called after setting a default
+ ///
+ public TResult Get(Func getter)
+ {
+ return getter(currentConfig ?? throw new InvalidOperationException("Must set default before reading config."));
+ }
+ ///
+ /// Writes the config to disk
+ ///
+ public void SaveChanges()
+ {
+ switch (configType)
+ {
+ case ConfigTypes.PerMod:
+ var perModJson = JsonSerializer.Serialize(currentConfig, new JsonSerializerOptions { WriteIndented = true });
+ File.WriteAllText(perModConfigPath, perModJson);
+ break;
+ case ConfigTypes.Exposed:
+ var modsConfigText = File.ReadAllText(ExposedConfigPath);
+ var modsConfigJson = JsonNode.Parse(modsConfigText)!.AsObject();
+ modsConfigJson[modName] = JsonSerializer.SerializeToNode(currentConfig!);
+ File.WriteAllText(ExposedConfigPath, modsConfigJson.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ public enum ConfigTypes
+ {
+ PerMod,
+ Exposed
+ }
+}
\ No newline at end of file
diff --git a/src/Managers/GLDConfig.cs b/src/Managers/GLDConfig.cs
new file mode 100644
index 0000000..86d29cb
--- /dev/null
+++ b/src/Managers/GLDConfig.cs
@@ -0,0 +1,94 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using Scriban;
+using Scriban.Runtime;
+
+namespace PolyMod.Managers;
+
+internal class GldConfigTemplate
+{
+ private static readonly string ConfigPath = Path.Combine(Constants.BASE_PATH, "mods.json");
+
+ private readonly string templateText;
+ private JsonObject currentConfig = new();
+ private string modName;
+
+ public GldConfigTemplate(string templateText, string modName)
+ {
+ this.templateText = templateText;
+ this.modName = modName;
+ Load();
+ }
+ private void Load()
+ {
+ if (File.Exists(ConfigPath))
+ {
+ var json = File.ReadAllText(ConfigPath);
+ if (JsonNode.Parse(json) is JsonObject modsConfig
+ && modsConfig.TryGetPropertyValue(modName, out var modConfigNode)
+ && modConfigNode is JsonObject modConfig)
+ {
+ currentConfig = modConfig;
+ return;
+ }
+ }
+ currentConfig = new JsonObject();
+ }
+
+ public string? Render()
+ {
+ if (!templateText.Contains("{{")) return templateText;
+ var template = Template.Parse(templateText);
+ var context = new TemplateContext();
+ var scriptObject = new ScriptObject();
+
+ bool changedConfig = false;
+ scriptObject.Import("config",
+ new Func((key, defaultValue) =>
+ {
+ if (currentConfig.TryGetPropertyValue(key, out var token) && token != null)
+ {
+ return token.ToString();
+ }
+
+ changedConfig = true;
+ currentConfig[key] = defaultValue;
+
+ return defaultValue;
+ })
+ );
+ context.PushGlobal(scriptObject);
+ string? result;
+ try
+ {
+ result = template.Render(context);
+ }
+ catch (Exception e)
+ {
+ Plugin.logger.LogError("error during parse of gld patch template: " + e.ToString());
+ result = null;
+ }
+ if (changedConfig)
+ {
+ SaveChanges();
+ }
+ return result;
+ }
+
+ public void SaveChanges()
+ {
+ JsonObject modsConfigJson;
+ if (File.Exists(ConfigPath))
+ {
+ var modsConfigText = File.ReadAllText(ConfigPath);
+ modsConfigJson = (JsonNode.Parse(modsConfigText) as JsonObject) ?? new JsonObject();
+ }
+ else
+ {
+ modsConfigJson = new JsonObject();
+ }
+
+ modsConfigJson[modName] = currentConfig;
+ File.WriteAllText(ConfigPath, modsConfigJson.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
+ }
+}
\ No newline at end of file
diff --git a/src/Managers/General.cs b/src/Managers/General.cs
new file mode 100644
index 0000000..23c959c
--- /dev/null
+++ b/src/Managers/General.cs
@@ -0,0 +1,31 @@
+using PolytopiaBackendBase.Game;
+using UnityEngine;
+
+namespace PolyMod.Managers;
+
+public static class General
+{
+ ///
+ /// Adds a button to the "select game mode" screen
+ ///
+ /// The text to display on the button
+ /// The action to take when the button is pressed
+ /// The image on the button.
+ public static void AddGameModeButton(string id, UIButtonBase.ButtonAction action, Sprite? sprite)
+ {
+ EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx);
+ EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx);
+ Loader.gamemodes.Add(new Loader.GameModeButtonsInformation(Registry.gameModesAutoidx, action, null, sprite));
+ Registry.gameModesAutoidx++;
+ }
+ ///
+ /// Adds a custom type to the gld.
+ ///
+ ///
+ ///
+ public static void AddPatchDataType(string typeId, Type type)
+ {
+ if (!Loader.typeMappings.ContainsKey(typeId))
+ Loader.typeMappings.Add(typeId, type);
+ }
+}
diff --git a/src/Mod.cs b/src/Mod.cs
index 36e81c1..4ee9e30 100644
--- a/src/Mod.cs
+++ b/src/Mod.cs
@@ -1,27 +1,76 @@
namespace PolyMod;
+///
+/// Represents a mod, containing its manifest information, status, and files.
+///
public class Mod
{
+ ///
+ /// Represents a dependency for a mod.
+ ///
+ /// The unique identifier of the dependency.
+ /// The minimum compatible version of the dependency.
+ /// The maximum compatible version of the dependency.
+ /// Whether the dependency is required for the mod to function.
public record Dependency(string id, Version min, Version max, bool required = true);
+
+ ///
+ /// Represents the manifest of a mod, containing metadata.
+ ///
+ /// The unique identifier of the mod.
+ /// The display name of the mod.
+ /// A description of the mod.
+ /// The version of the mod.
+ /// The authors of the mod.
+ /// The dependencies of the mod.
+ /// Whether the mod is client-side only.
public record Manifest(string id, string? name, string? description, Version version, string[] authors, Dependency[]? dependencies, bool client = false);
+
+ ///
+ /// Represents a file included in a mod.
+ ///
+ /// The name of the file.
+ /// The raw byte content of the file.
public record File(string name, byte[] bytes);
+
+ ///
+ /// Represents the loading status of a mod.
+ ///
public enum Status
{
+ /// The mod was loaded successfully.
Success,
+ /// An error occurred while loading the mod.
Error,
+ /// The mod's dependencies were not satisfied.
DependenciesUnsatisfied,
}
+ /// The unique identifier of the mod.
public string id;
+ /// The display name of the mod.
public string? name;
+ /// A description of the mod.
public string? description;
+ /// The version of the mod.
public Version version;
+ /// The authors of the mod.
public string[] authors;
+ /// The dependencies of the mod.
public Dependency[]? dependencies;
+ /// Whether the mod is client-side only.
public bool client;
+ /// The loading status of the mod.
public Status status;
+ /// The files included in the mod.
public List files;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The mod's manifest data.
+ /// The initial loading status of the mod.
+ /// The list of files included in the mod.
public Mod(Manifest manifest, Status status, List files)
{
id = manifest.id;
@@ -34,4 +83,13 @@ public Mod(Manifest manifest, Status status, List files)
this.status = status;
this.files = files;
}
+
+ ///
+ /// Returns a string that represents the current mod.
+ ///
+ /// A string representation of the mod.
+ public override string ToString()
+ {
+ return $"mod: id={id} dependencies={dependencies} status={status}";
+ }
}
diff --git a/src/Managers/Audio.cs b/src/Patches/Audio.cs
similarity index 98%
rename from src/Managers/Audio.cs
rename to src/Patches/Audio.cs
index f93ee78..b15ef4d 100644
--- a/src/Managers/Audio.cs
+++ b/src/Patches/Audio.cs
@@ -5,7 +5,7 @@
namespace PolyMod.Managers;
-public static class Audio
+internal static class Audio
{
[HarmonyPostfix]
[HarmonyPatch(typeof(AudioManager), nameof(AudioManager.SetupData))]
diff --git a/src/Managers/AutoUpdate.cs b/src/Patches/AutoUpdate.cs
similarity index 90%
rename from src/Managers/AutoUpdate.cs
rename to src/Patches/AutoUpdate.cs
index ef7a043..4ea6670 100644
--- a/src/Managers/AutoUpdate.cs
+++ b/src/Patches/AutoUpdate.cs
@@ -35,7 +35,7 @@ private static void StartScreen_Start()
break;
}
string newVersion = latest?.GetProperty("tag_name").GetString()!.TrimStart('v')!;
- if (newVersion.IsVersionOlderOrEqual(Plugin.VERSION)) return;
+ if (newVersion.IsVersionOlderOrEqual(Constants.POLYMOD_VERSION)) return;
string os = Application.platform switch
{
RuntimePlatform.WindowsPlayer => "win",
@@ -56,20 +56,20 @@ void Update()
{
Time.timeScale = 0;
File.WriteAllBytes(
- Path.Combine(Plugin.BASE_PATH, "PolyMod.new.dll"),
+ Path.Combine(Constants.BASE_PATH, "PolyMod.new.dll"),
client.GetAsync(latest?.GetProperty("assets")[0].GetProperty("browser_download_url").GetString()!).UnwrapAsync()
.Content.ReadAsByteArrayAsync().UnwrapAsync()
);
using ZipArchive bepinex = new(client.GetAsync(bepinex_url).UnwrapAsync().Content.ReadAsStream());
- bepinex.ExtractToDirectory(Path.Combine(Plugin.BASE_PATH, "New"), overwriteFiles: true);
+ bepinex.ExtractToDirectory(Path.Combine(Constants.BASE_PATH, "New"), overwriteFiles: true);
ProcessStartInfo info = new()
{
- WorkingDirectory = Path.Combine(Plugin.BASE_PATH),
+ WorkingDirectory = Path.Combine(Constants.BASE_PATH),
CreateNoWindow = true,
};
if (Application.platform == RuntimePlatform.WindowsPlayer)
{
- string batchPath = Path.Combine(Plugin.BASE_PATH, "update.bat");
+ string batchPath = Path.Combine(Constants.BASE_PATH, "update.bat");
File.WriteAllText(batchPath, $@"
@echo off
echo Waiting for Polytopia.exe to exit...
@@ -93,13 +93,13 @@ echo Launching game...
");
info.FileName = "cmd.exe";
info.Arguments = $"/C start \"\" \"{batchPath}\"";
- info.WorkingDirectory = Plugin.BASE_PATH;
+ info.WorkingDirectory = Constants.BASE_PATH;
info.CreateNoWindow = true;
info.UseShellExecute = false;
}
if (Application.platform == RuntimePlatform.LinuxPlayer || Application.platform == RuntimePlatform.OSXPlayer)
{
- string bashPath = Path.Combine(Plugin.BASE_PATH, "update.sh");
+ string bashPath = Path.Combine(Constants.BASE_PATH, "update.sh");
File.WriteAllText(bashPath, $@"
#!/bin/bash
@@ -130,7 +130,7 @@ exit 0
info.FileName = "/bin/bash";
info.Arguments = $"\"{bashPath}\"";
- info.WorkingDirectory = Plugin.BASE_PATH;
+ info.WorkingDirectory = Constants.BASE_PATH;
info.CreateNoWindow = true;
info.UseShellExecute = false;
}
diff --git a/src/Managers/Compatibility.cs b/src/Patches/Compatibility.cs
similarity index 95%
rename from src/Managers/Compatibility.cs
rename to src/Patches/Compatibility.cs
index c0b2321..401c4b4 100644
--- a/src/Managers/Compatibility.cs
+++ b/src/Patches/Compatibility.cs
@@ -68,12 +68,12 @@ private static void StartScreen_Start()
string lastChecksum = checksum;
try
{
- lastChecksum = new(File.ReadAllText(Plugin.CHECKSUM_PATH));
+ lastChecksum = new(File.ReadAllText(Constants.CHECKSUM_PATH));
}
catch (FileNotFoundException) { }
File.WriteAllText(
- Plugin.CHECKSUM_PATH,
+ Constants.CHECKSUM_PATH,
checksum
);
if (lastChecksum != checksum)
@@ -82,13 +82,13 @@ private static void StartScreen_Start()
}
Version incompatibilityWarningLastVersion = new(PlayerPrefs.GetString(
- Plugin.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY,
- Plugin.POLYTOPIA_VERSION.CutRevision().ToString()
+ Constants.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY,
+ Constants.POLYTOPIA_VERSION.CutRevision().ToString()
));
if (VersionManager.SemanticVersion.Cast().CutRevision() > incompatibilityWarningLastVersion)
{
PlayerPrefs.SetString(
- Plugin.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY,
+ Constants.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY,
VersionManager.SemanticVersion.Cast().CutRevision().ToString()
);
PlayerPrefs.Save();
diff --git a/src/Managers/Hub.cs b/src/Patches/Hub.cs
similarity index 95%
rename from src/Managers/Hub.cs
rename to src/Patches/Hub.cs
index 43b186f..8399350 100644
--- a/src/Managers/Hub.cs
+++ b/src/Patches/Hub.cs
@@ -91,7 +91,7 @@ private static void StartScreen_Start()
textComponent.fontSize = 18;
textComponent.alignment = TextAlignmentOptions.BottomLeft;
- text.GetComponent().Text = $"PolyMod {Plugin.VERSION}";
+ text.GetComponent().Text = $"PolyMod {Constants.POLYMOD_VERSION}";
text.AddComponent().ignoreLayout = true;
}
else if (parentName == "NewsButton")
@@ -143,7 +143,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData)
new(
"polymod.hub.discord",
callback: (UIButtonBase.ButtonAction)((_, _) =>
- NativeHelpers.OpenURL(Plugin.DISCORD_LINK, false))
+ NativeHelpers.OpenURL(Constants.DISCORD_LINK, false))
),
new(
"polymod.hub.config",
@@ -159,18 +159,18 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData)
"polymod.hub.dump",
callback: (UIButtonBase.ButtonAction)((_, _) =>
{
- Directory.CreateDirectory(Plugin.DUMPED_DATA_PATH);
+ Directory.CreateDirectory(Constants.DUMPED_DATA_PATH);
File.WriteAllTextAsync(
- Path.Combine(Plugin.DUMPED_DATA_PATH, "gameLogicData.json"),
+ Path.Combine(Constants.DUMPED_DATA_PATH, "gameLogicData.json"),
PolytopiaDataManager.provider.LoadGameLogicData(VersionManager.GameLogicDataVersion)
);
File.WriteAllTextAsync(
- Path.Combine(Plugin.DUMPED_DATA_PATH, "avatarData.json"),
+ Path.Combine(Constants.DUMPED_DATA_PATH, "avatarData.json"),
PolytopiaDataManager.provider.LoadAvatarData(1337)
);
foreach (var category in LocalizationManager.Sources[0].GetCategories())
File.WriteAllTextAsync(
- Path.Combine(Plugin.DUMPED_DATA_PATH, $"localization_{category}.csv"),
+ Path.Combine(Constants.DUMPED_DATA_PATH, $"localization_{category}.csv"),
LocalizationManager.Sources[0].Export_CSV(category)
);
foreach (KeyValuePair entry in Registry.mods)
@@ -179,7 +179,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData)
{
if (Path.GetFileName(file.name) == "sprites.json")
{
- File.WriteAllBytes(Path.Combine(Plugin.DUMPED_DATA_PATH, $"sprites_{entry.Key}.json"), file.bytes);
+ File.WriteAllBytes(Path.Combine(Constants.DUMPED_DATA_PATH, $"sprites_{entry.Key}.json"), file.bytes);
}
}
}
@@ -208,7 +208,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData)
}
}
File.WriteAllTextAsync(
- Path.Combine(Plugin.DUMPED_DATA_PATH, $"preview_{type}.json"),
+ Path.Combine(Constants.DUMPED_DATA_PATH, $"preview_{type}.json"),
JsonSerializer.Serialize(previewTiles, new JsonSerializerOptions { WriteIndented = true })
);
}
@@ -262,9 +262,9 @@ private static void GameManager_Update()
internal static void UpdateSpriteInfos()
{
string message = string.Empty;
- Directory.CreateDirectory(Plugin.DUMPED_DATA_PATH);
+ Directory.CreateDirectory(Constants.DUMPED_DATA_PATH);
- foreach (var file in Directory.GetFiles(Plugin.DUMPED_DATA_PATH))
+ foreach (var file in Directory.GetFiles(Constants.DUMPED_DATA_PATH))
{
string? name = Path.GetFileNameWithoutExtension(file);
List subnames = new();
diff --git a/src/Managers/Loc.cs b/src/Patches/Loc.cs
similarity index 95%
rename from src/Managers/Loc.cs
rename to src/Patches/Loc.cs
index 5b1c70f..4697599 100644
--- a/src/Managers/Loc.cs
+++ b/src/Patches/Loc.cs
@@ -7,13 +7,13 @@
namespace PolyMod.Managers;
-public static class Loc
+internal static class Loc
{
[HarmonyPostfix]
[HarmonyPatch(typeof(SelectTribePopup), nameof(SelectTribePopup.SetDescription))]
private static void SetDescription(SelectTribePopup __instance)
{
- if ((int)__instance.SkinType >= Plugin.AUTOIDX_STARTS_FROM)
+ if ((int)__instance.SkinType >= Constants.AUTOIDX_STARTS_FROM)
{
string description = Localization.Get(__instance.SkinType.GetLocalizationDescriptionKey());
if (description == __instance.SkinType.GetLocalizationDescriptionKey())
@@ -42,7 +42,7 @@ private static bool Localization_Get(ref string key, Il2CppReferenceArray= Plugin.AUTOIDX_STARTS_FROM)
+ if (parsedIdx >= Constants.AUTOIDX_STARTS_FROM)
{
idx = parsedIdx;
}
diff --git a/src/Managers/Main.cs b/src/Patches/Main.cs
similarity index 83%
rename from src/Managers/Main.cs
rename to src/Patches/Main.cs
index 68892ed..6e3e7e4 100644
--- a/src/Managers/Main.cs
+++ b/src/Patches/Main.cs
@@ -11,7 +11,7 @@
namespace PolyMod.Managers;
-public static class Main
+internal static class Main
{
internal const int MAX_TECH_TIER = 100;
internal static readonly Stopwatch stopwatch = new();
@@ -110,7 +110,7 @@ private static void GameLogicData_Parse(GameLogicData __instance, JObject rootOb
[HarmonyPatch(typeof(PurchaseManager), nameof(PurchaseManager.IsSkinUnlockedInternal))]
private static bool PurchaseManager_IsSkinUnlockedInternal(ref bool __result, SkinType skinType)
{
- __result = (int)skinType >= Plugin.AUTOIDX_STARTS_FROM && skinType != SkinType.Test;
+ __result = (int)skinType >= Constants.AUTOIDX_STARTS_FROM && skinType != SkinType.Test;
return !__result;
}
@@ -118,7 +118,7 @@ private static bool PurchaseManager_IsSkinUnlockedInternal(ref bool __result, Sk
[HarmonyPatch(typeof(PurchaseManager), nameof(PurchaseManager.IsTribeUnlocked))]
private static void PurchaseManager_IsTribeUnlocked(ref bool __result, TribeData.Type type)
{
- __result = (int)type >= Plugin.AUTOIDX_STARTS_FROM || __result;
+ __result = (int)type >= Constants.AUTOIDX_STARTS_FROM || __result;
}
[HarmonyPostfix]
@@ -135,7 +135,7 @@ private static void PurchaseManager_GetUnlockedTribes(
[HarmonyPatch(typeof(IL2CPPUnityLogSource), nameof(IL2CPPUnityLogSource.UnityLogCallback))]
private static bool IL2CPPUnityLogSource_UnityLogCallback(string logLine, string exception, LogType type)
{
- foreach (string stringToIgnore in Plugin.LOG_MESSAGES_IGNORE)
+ foreach (string stringToIgnore in Constants.LOG_MESSAGES_IGNORE)
{
if (logLine.Contains(stringToIgnore))
return false;
@@ -330,94 +330,15 @@ internal static void Init()
Array.Empty()
);
Registry.mods.Add(polytopia.id, new(polytopia, Mod.Status.Success, new()));
- Loader.LoadMods(Registry.mods);
- dependencyCycle = !Loader.SortMods(Registry.mods);
- if (dependencyCycle) return;
-
- StringBuilder checksumString = new();
- foreach (var (id, mod) in Registry.mods)
- {
- if (mod.status != Mod.Status.Success) continue;
- foreach (var file in mod.files)
- {
- checksumString.Append(JsonSerializer.Serialize(file));
- if (Path.GetExtension(file.name) == ".dll")
- {
- Loader.LoadAssemblyFile(mod, file);
- }
- if (Path.GetFileName(file.name) == "sprites.json")
- {
- Loader.LoadSpriteInfoFile(mod, file);
- }
- }
- if (!mod.client && id != "polytopia")
- {
- checksumString.Append(id);
- checksumString.Append(mod.version.ToString());
- }
- }
- Compatibility.HashSignatures(checksumString);
-
+ dependencyCycle = Loader.RegisterMods(Registry.mods);
+ if (!dependencyCycle) Loader.LoadMods(Registry.mods);
stopwatch.Stop();
}
internal static void Load(JObject gameLogicdata)
{
stopwatch.Start();
- Loc.BuildAndLoadLocalization(
- JsonSerializer.Deserialize>>(
- Plugin.GetResource("localization.json")
- )!
- );
- if (dependencyCycle) return;
-
- foreach (var (id, mod) in Registry.mods)
- {
- if (mod.status != Mod.Status.Success) continue;
- foreach (var file in mod.files)
- {
- if (Path.GetFileName(file.name) == "localization.json")
- {
- Loader.LoadLocalizationFile(mod, file);
- continue;
- }
- if (Regex.IsMatch(Path.GetFileName(file.name), @"^patch(_.*)?\.json$"))
- {
- Loader.LoadGameLogicDataPatch(
- mod,
- gameLogicdata,
- JObject.Parse(new StreamReader(new MemoryStream(file.bytes)).ReadToEnd())
- );
- continue;
- }
- if (Regex.IsMatch(Path.GetFileName(file.name), @"^prefab(_.*)?\.json$"))
- {
- Loader.LoadPrefabInfoFile(
- mod,
- file
- );
- continue;
- }
-
- switch (Path.GetExtension(file.name))
- {
- case ".png":
- Loader.LoadSpriteFile(mod, file);
- break;
- case ".wav":
- Loader.LoadAudioFile(mod, file);
- break;
- case ".bundle":
- Loader.LoadAssetBundle(mod, file);
- break;
- }
- }
- }
- TechItem.techTierFirebaseId.Clear();
- for (int i = 0; i <= MAX_TECH_TIER; i++)
- {
- TechItem.techTierFirebaseId.Add($"tech_research_{i}");
- }
+ if (!dependencyCycle) Loader.PatchGLD(gameLogicdata, Registry.mods);
stopwatch.Stop();
Plugin.logger.LogInfo($"Loaded all mods in {stopwatch.ElapsedMilliseconds}ms");
}
diff --git a/src/Managers/Visual.cs b/src/Patches/Visual.cs
similarity index 86%
rename from src/Managers/Visual.cs
rename to src/Patches/Visual.cs
index e816f0b..d2a9318 100644
--- a/src/Managers/Visual.cs
+++ b/src/Patches/Visual.cs
@@ -10,40 +10,93 @@
namespace PolyMod.Managers;
-public static class Visual
+///
+/// Manages visual aspects of the game, including sprites, UI elements, and in-game object appearances.
+///
+internal static class Visual
{
+ ///
+ /// Represents a single tile in a tribe's world preview.
+ ///
public class PreviewTile
{
+ /// The X-coordinate of the tile.
[JsonInclude]
public int? x = null;
+ /// The Y-coordinate of the tile.
[JsonInclude]
public int? y = null;
+ /// The type of terrain on the tile.
[JsonInclude]
[JsonConverter(typeof(EnumCacheJson))]
public Polytopia.Data.TerrainData.Type terrainType = Polytopia.Data.TerrainData.Type.Ocean;
+ /// The type of resource on the tile.
[JsonInclude]
[JsonConverter(typeof(EnumCacheJson))]
public ResourceData.Type resourceType = ResourceData.Type.None;
+ /// The type of unit on the tile.
[JsonInclude]
[JsonConverter(typeof(EnumCacheJson))]
public UnitData.Type unitType = UnitData.Type.None;
+ /// The type of improvement on the tile.
[JsonInclude]
[JsonConverter(typeof(EnumCacheJson))]
public ImprovementData.Type improvementType = ImprovementData.Type.None;
}
+
+ ///
+ /// Holds information for creating a custom sprite.
+ ///
+ /// The number of pixels per unit for the sprite.
+ /// The pivot point of the sprite.
public record SpriteInfo(float? pixelsPerUnit, Vector2? pivot);
+
+ ///
+ /// Holds information about a custom skin.
+ ///
+ /// The index of the skin.
+ /// The unique identifier for the skin.
+ /// The data associated with the skin.
public record SkinInfo(int idx, string id, SkinData? skinData);
+
+ ///
+ /// A dictionary mapping BasicPopup instance IDs to their custom widths.
+ ///
public static Dictionary basicPopupWidths = new();
private static bool firstTimeOpeningPreview = true;
private static UnitData.Type currentUnitTypeUI = UnitData.Type.None;
private static TribeData.Type attackerTribe = TribeData.Type.None;
+
+ ///
+ /// Defines the types of prefabs that can be customized.
+ ///
public enum PrefabType
{
+ /// A unit prefab.
Unit,
+ /// An improvement prefab.
Improvement,
+ /// A resource prefab.
Resource
}
+
+ ///
+ /// Holds information for a custom prefab.
+ ///
+ /// The type of the prefab.
+ /// The name of the prefab.
+ /// A list of visual parts that make up the prefab.
public record PrefabInfo(PrefabType type, string name, List visualParts);
+
+ ///
+ /// Represents a visual part of a custom prefab.
+ ///
+ /// The name of the GameObject for this visual part.
+ /// The base name for sprite lookups.
+ /// The rotation of the visual part.
+ /// The local position of the visual part.
+ /// The local scale of the visual part.
+ /// Whether the visual part can be tinted.
public record VisualPartInfo(
string gameObjectName,
string baseName,
@@ -257,7 +310,7 @@ private static void TerrainRenderer_UpdateGraphics(TerrainRenderer __instance, T
if (tile.data.terrain is Polytopia.Data.TerrainData.Type.Forest or Polytopia.Data.TerrainData.Type.Mountain)
{
string propertyName = terrain.ToLower();
- terrain = "field";
+ terrain = "field";
PropertyInfo? rendererProperty = tile.GetType().GetProperty(propertyName + "Renderer",
BindingFlags.Public | BindingFlags.Instance);
@@ -565,6 +618,11 @@ private static void PopupBase_Hide(PopupBase __instance)
basicPopupWidths.Remove(__instance.GetInstanceID());
}
+ ///
+ /// Shows a BasicPopup and sets its width.
+ ///
+ /// The BasicPopup instance.
+ /// The desired width of the popup.
public static void ShowSetWidth(this BasicPopup self, int width)
{
basicPopupWidths.Add(self.GetInstanceID(), width);
@@ -594,6 +652,13 @@ private static void UpdateVisualPart(SkinVisualsReference.VisualPart visualPart,
}
}
+ ///
+ /// Creates a from raw image data.
+ ///
+ /// The byte array containing the image data.
+ /// The pivot point for the sprite. Defaults to the center.
+ /// The number of pixels per unit for the sprite.
+ /// A new instance.
public static Sprite BuildSprite(byte[] data, Vector2? pivot = null, float pixelsPerUnit = 2112f)
{
Texture2D texture = new(1, 1, TextureFormat.RGBA32, true);
@@ -609,6 +674,13 @@ public static Sprite BuildSprite(byte[] data, Vector2? pivot = null, float pixel
return BuildSpriteWithTexture(texture, pivot, pixelsPerUnit);
}
+ ///
+ /// Creates a from an existing .
+ ///
+ /// The texture to create the sprite from.
+ /// The pivot point for the sprite. Defaults to the center.
+ /// The number of pixels per unit for the sprite.
+ /// A new instance.
public static Sprite BuildSpriteWithTexture(Texture2D texture, Vector2? pivot = null, float? pixelsPerUnit = 2112f)
{
return Sprite.Create(
diff --git a/src/Plugin.cs b/src/Plugin.cs
index 025258c..61a757e 100644
--- a/src/Plugin.cs
+++ b/src/Plugin.cs
@@ -8,8 +8,8 @@
namespace PolyMod;
-[BepInPlugin("com.polymod", "PolyMod", VERSION)]
-public partial class Plugin : BepInEx.Unity.IL2CPP.BasePlugin
+[BepInPlugin("com.polymod", "PolyMod", Constants.POLYMOD_VERSION)]
+internal partial class Plugin : BepInEx.Unity.IL2CPP.BasePlugin
{
internal record PolyConfig(
bool debug = false,
@@ -17,25 +17,6 @@ internal record PolyConfig(
bool updatePrerelease = false
);
- internal const int AUTOIDX_STARTS_FROM = 1000;
- internal const string INCOMPATIBILITY_WARNING_LAST_VERSION_KEY
- = "INCOMPATIBILITY_WARNING_LAST_VERSION";
- public static readonly string BASE_PATH = Path.Combine(BepInEx.Paths.BepInExRootPath, "..");
- public static readonly string MODS_PATH = Path.Combine(BASE_PATH, "Mods");
- public static readonly string DUMPED_DATA_PATH = Path.Combine(BASE_PATH, "DumpedData");
- internal static readonly string CONFIG_PATH = Path.Combine(BASE_PATH, "PolyMod.json");
- internal static readonly string CHECKSUM_PATH
- = Path.Combine(BASE_PATH, "CHECKSUM");
- internal static readonly string DISCORD_LINK = "https://discord.gg/eWPdhWtfVy";
- internal static readonly List LOG_MESSAGES_IGNORE = new()
- {
- "Failed to find atlas",
- "Could not find sprite",
- "Couldn't find prefab for type",
- "MARKET: id:",
- "Missing name for value",
- };
-
#pragma warning disable CS8618
internal static PolyConfig config;
@@ -46,7 +27,7 @@ public override void Load()
{
try
{
- config = JsonSerializer.Deserialize(File.ReadAllText(CONFIG_PATH))!;
+ config = JsonSerializer.Deserialize(File.ReadAllText(Constants.CONFIG_PATH))!;
}
catch
{
@@ -77,7 +58,7 @@ internal static Stream GetResource(string id)
internal static void WriteConfig()
{
- File.WriteAllText(CONFIG_PATH, JsonSerializer.Serialize(config));
+ File.WriteAllText(Constants.CONFIG_PATH, JsonSerializer.Serialize(config));
}
internal static void UpdateConsole()
diff --git a/src/PolyScriptMod.cs b/src/PolyScriptMod.cs
new file mode 100644
index 0000000..08fdd80
--- /dev/null
+++ b/src/PolyScriptMod.cs
@@ -0,0 +1,34 @@
+using BepInEx.Logging;
+using Newtonsoft.Json.Linq;
+using PolyMod.Managers;
+
+namespace PolyMod;
+
+public abstract class PolyScriptModBase
+{
+ internal abstract void Initialize(string name, ManualLogSource logger);
+ public abstract void Load();
+ public abstract void UnLoad();
+ internal PolyScriptModBase()
+ {
+ }
+}
+public abstract class PolyScriptMod : PolyScriptModBase where TConfig : class where TExposedConfig : class
+{
+ internal override void Initialize(string name, ManualLogSource logger)
+ {
+ ModName = name;
+ Config = new Config(name, Config.ConfigTypes.PerMod);
+ ExposedConfig = new Config(name, Config.ConfigTypes.Exposed);
+ Logger = logger;
+ }
+
+ public string ModName { get; private set; } = null!;
+ protected Config Config { get; private set; } = null!;
+ protected Config ExposedConfig { get; private set; } = null!;
+ protected ManualLogSource Logger { get; private set; } = null!;
+}
+
+public abstract class PolyScriptMod : PolyScriptMod
+{
+}
\ No newline at end of file
diff --git a/src/Registry.cs b/src/Registry.cs
index 207d225..a9134f4 100644
--- a/src/Registry.cs
+++ b/src/Registry.cs
@@ -6,24 +6,60 @@
namespace PolyMod;
+///
+/// Provides central registries for custom content managed by PolyMod.
+///
public static class Registry
{
- public static int autoidx = Plugin.AUTOIDX_STARTS_FROM;
- public static Dictionary sprites = new();
- public static Dictionary audioClips = new();
- internal static Dictionary mods = new();
- public static Dictionary tribePreviews = new();
- public static Dictionary spriteInfos = new();
- public static Dictionary prefabNames = new();
- public static Dictionary unitPrefabs = new();
- public static Dictionary resourcePrefabs = new();
- public static Dictionary improvementsPrefabs = new();
+ internal static int autoidx = Constants.AUTOIDX_STARTS_FROM;
+
+ /// The registry for custom sprites.
+ internal static Dictionary sprites = new();
+
+ /// The registry for custom audio clips.
+ internal static Dictionary audioClips = new();
+
+ /// The registry for loaded mods.
+ public static Dictionary mods = new();
+
+ /// The registry for custom tribe previews.
+ internal static Dictionary tribePreviews = new();
+
+ /// The registry for custom sprite information.
+ internal static Dictionary spriteInfos = new();
+
+ /// The registry for custom prefab names.
+ internal static Dictionary prefabNames = new();
+
+ /// The registry for custom unit prefabs.
+ internal static Dictionary unitPrefabs = new();
+
+ /// The registry for custom resource prefabs.
+ internal static Dictionary resourcePrefabs = new();
+
+ /// The registry for custom improvement prefabs.
+ internal static Dictionary improvementsPrefabs = new();
+
+ /// The registry for loaded asset bundles.
public static Dictionary assetBundles = new();
+
+ /// The registry for custom tribes.
public static List customTribes = new();
- public static List skinInfo = new();
- public static int climateAutoidx = (int)Enum.GetValues(typeof(TribeData.Type)).Cast().Last();
- public static int gameModesAutoidx = Enum.GetValues(typeof(GameMode)).Length;
+ /// The registry for custom skin information.
+ internal static List skinInfo = new();
+
+ internal static int climateAutoidx = (int)Enum.GetValues(typeof(TribeData.Type)).Cast().Last();
+ internal static int gameModesAutoidx = Enum.GetValues(typeof(GameMode)).Length;
+
+ ///
+ /// Retrieves a sprite from the registry based on its name, style, and level.
+ /// The method searches for multiple key formats to find the best match.
+ ///
+ /// The base name of the sprite.
+ /// The style variant of the sprite (e.g., tribe-specific).
+ /// The level variant of the sprite (e.g., for a city).
+ /// The matching , or null if no sprite is found.
public static Sprite? GetSprite(string name, string style = "", int level = 0)
{
Sprite? sprite = null;
@@ -36,6 +72,12 @@ public static class Registry
return sprite;
}
+ ///
+ /// Retrieves an audio clip from the registry.
+ ///
+ /// The name of the audio clip.
+ /// The style of the audio clip.
+ /// The matching , or null if not found.
public static AudioClip? GetAudioClip(string name, string style)
{
AudioSource? audioSource = null;
@@ -45,4 +87,4 @@ public static class Registry
if (audioSource == null) return null;
return audioSource.clip;
}
-}
\ No newline at end of file
+}