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..059fd1c
--- /dev/null
+++ b/FodyWeavers.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ Scriban
+ MoonSharp.Interpreter
+
+
+
\ No newline at end of file
diff --git a/PolyMod.csproj b/PolyMod.csproj
index 072a080..9ded5b4 100644
--- a/PolyMod.csproj
+++ b/PolyMod.csproj
@@ -1,4 +1,4 @@
-
+net6.0enable
@@ -20,6 +20,11 @@
+
+ all
+
+
+
@@ -29,9 +34,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..7ab9fe9
--- /dev/null
+++ b/docs/articles/modding/polyscript.md
@@ -0,0 +1,219 @@
+# Lua PolyScripting
+
+- [A Brief Introduction to Lua](#a-brief-introduction-to-lua)
+ - [Comments](#comments)
+ - [Variables and Data Types](#variables-and-data-types)
+ - [Tables](#tables)
+ - [Functions](#functions)
+ - [Control Flow](#control-flow)
+ - [Core APIs](#core-apis)
+ - [Patch](#patch)
+ - [Input](#input)
+ - [Config and ExposedConfig](#config-and-exposedconfig)
+ - [Game and Unity Globals](#game-and-unity-globals)
+
+PolyScript is a powerful scripting system that allows you to create mods for Polytopia using the Lua programming language. You can modify game logic, respond to player input, and interact with a wide range of game and engine components.
+
+For a complete reference of all available C# types and functions, please see the [API documentation](~/api/).
+
+This page assumes you already know the basics of coding. If you already know lua, skip to [Core APIs](#core-apis)
+## A Brief Introduction to Lua
+
+If you're new to Lua, here are the basics to get you started.
+
+### Comments
+
+Comments start with `--`. For multi-line comments, use `--[[` and `]]--`.
+
+```lua
+-- This is a single-line comment
+
+--[[
+This is a
+multi-line comment.
+]]--
+```
+
+### Variables and Data Types
+
+Variables are dynamically typed. You can declare them with `local` to limit their scope or leave it off to make them global (though `local` is generally recommended).
+
+```lua
+local myString = "Hello" -- String
+local myNumber = 42 -- Number (double-precision float)
+local myBoolean = true -- Boolean
+local myTable = { key = "value" } -- Table (the only data structure in Lua)
+local myNil = nil -- Represents the absence of a value
+```
+
+### Tables
+
+Tables are associative arrays and can be used as arrays, dictionaries, or objects.
+
+```lua
+-- Array-like table (indices start at 1!)
+local list = { "apple", "banana", "orange" }
+print(list[1]) -- Outputs "apple"
+
+-- Dictionary-like table
+local person = { name = "Bob", age = 30 }
+print(person.name) -- Outputs "Bob"
+print(person["age"]) -- Outputs 30
+```
+
+### Functions
+
+Functions are first-class citizens.
+
+```lua
+local function greet(name)
+ return "Hello, " .. name .. "!" -- '..' is the string concatenation operator
+end
+
+print(greet("PolyModder"))
+```
+
+**Functions on tables:**
+Tables can also have functions. These can be called with table:function or table.function.
+
+```lua
+local person = { name = "Bob", age = 30 }
+function person:greet()
+ print("Hi, I'm " .. self.name .. " and I'm " .. self.age .. " years old.")
+end
+
+person:greet() -- Calls person.greet(person)
+person.greet(person) -- Equivalent to the above
+```
+
+Note: when C# objects are passed:
+```lua
+local myVector = Vector3.__new(10, 20, 0)
+print(myVector.Distance()) -- doesn't require passing self
+```
+### Control Flow
+
+```lua
+local score = 100
+
+if score > 90 then
+ print("Excellent!")
+elseif score > 60 then
+ print("Good job.")
+else
+ print("Needs improvement.")
+end
+
+-- Loop from 1 to 5
+for i = 1, 5 do
+ print("Iteration: " .. i)
+end
+```
+
+### Multiple files
+
+You can create multiple polyscripts in a mod. These will all share the same globals and config, but not the same locals.
+
+## Core APIs
+
+PolyScript exposes several global objects to interact with the game.
+
+### `Patch`
+
+The `Patch` API allows you to hook into existing C# methods in the game, effectively changing or extending their behavior. This is the foundation of most mods.
+
+**`Patch.Wrap(methodName, hookFunction)`**
+
+This function "wraps" a target method with your own Lua function.
+
+* `methodName`: A string representing the full name of the method to patch, in the format `"Namespace.TypeName.MethodName"`.
+> [!NOTE]
+> A suprising number of Polytopian types have no namespace.
+* `hookFunction`: A Lua function that will be executed instead of the original method.
+
+Your hook function receives a special function, `orig`, as its first argument. Calling `orig()` will execute the original method's code (or the next patch in the chain).
+
+**Hook Function Signatures:**
+
+* For instance methods: `function(orig, self, args)`
+ * For static methods: `function(orig, ..args.)`
+
+Where `args` is a table containing a list of the args(e.g. args[1] is the first argument, args[2] the second, etc.)
+
+`orig()` will automatically be passed the args table. If you want to modify the args, you can modify said table.
+**Example: Logging when a turn starts**
+
+```lua
+Patch.Wrap("Polytopia.Game.GameManager.startTurn", function(orig, self, args)
+ print("A new turn has started for tribe: " .. args[1].Name)
+ -- It's crucial to call the original function!
+ orig()
+end)
+```
+
+**Multiple patches:**
+The first patch will be called first. When the first patch calls orig(), it will execute the second patch, etc.
+
+### `Input`
+
+The `Input` API lets you listen for keyboard events and execute code when keys are pressed.
+
+**`Input.On(keys, callback)`**
+
+* `keys`: A Lua table containing one or more `KeyCode` values. The last key in the table is the main action key, and any preceding keys are treated as modifiers that must be held down.
+ * `callback`: A Lua function to execute when the key combination is pressed.
+
+**Example: Binding an action to `Ctrl + R`**
+
+```lua
+-- A list of available KeyCodes can be found in the Unity documentation.
+local reloadKey = { KeyCode.LeftControl, KeyCode.R }
+
+Input.On(reloadKey, function()
+ print("Reloading!")
+ -- Add your reload logic here
+end)
+
+-- Binding to a single key
+Input.On({ KeyCode.F1 }, function()
+ print("Help key pressed!")
+end)
+```
+
+### `Config` and `ExposedConfig`
+
+PolyScript provides two global objects for saving and loading data:
+
+* `Config`: A key-value store private to your mod. Use this for internal data that should persist across game restarts.
+ * `ExposedConfig`: A key-value store for settings that the **user can configure**. This data is typically exposed in a settings menu or a user-editable file.
+
+These objects behave like standard Lua tables. **Important:** After changing a value, you must call `SaveChanges()` to write the data to disk.
+
+**Example: Storing a user preference**
+
+```lua
+-- Set a default value if one doesn't exist
+if ExposedConfig.playerGreeting == nil then
+ ExposedConfig.playerGreeting = "Hello, player!"
+end
+
+-- To save any changes you make to the configuration:
+ExposedConfig.SaveChanges()
+
+-- Read the value on startup
+print(ExposedConfig.playerGreeting)
+```
+
+### Game and Unity Globals
+
+Many of Unity's core types and the game's own types are available as globals in the Lua environment. This allows you to create new objects and call static methods directly.
+
+**Available Unity Globals (Examples):**
+`Vector2`, `Vector3`, `GameObject`, `Color`, `Mathf`, `Time`
+
+**Example: Creating a new Vector3**
+
+```lua
+local myVector = Vector3.new(10, 20, 0)
+print("Created a vector: " .. myVector.x .. ", " .. myVector.y)
+```
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..9dd8845 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:",
@@ -198,7 +198,7 @@
"Russian": "{0} задан на {1}!"
},
"polymod_hub_spriteinfo_update": {
- "English": "UPDATE SPRITES",
+ "English": "RELOAD SPRITES&LUA",
"Russian": "ОБНОВИТЬ СПРАЙТЫ"
},
"polymod_spriteinfo_updated": {
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/GLDConfig.cs b/src/GLDConfig.cs
new file mode 100644
index 0000000..8bb9871
--- /dev/null
+++ b/src/GLDConfig.cs
@@ -0,0 +1,94 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using Scriban;
+using Scriban.Runtime;
+
+namespace PolyMod.modApi;
+
+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/Loader.cs b/src/Loader.cs
index bdf79a2..573f165 100644
--- a/src/Loader.cs
+++ b/src/Loader.cs
@@ -4,7 +4,6 @@
using MonoMod.Utils;
using Newtonsoft.Json.Linq;
using PolyMod.Json;
-using PolyMod.Managers;
using Polytopia.Data;
using PolytopiaBackendBase.Game;
using System.Data;
@@ -12,13 +11,18 @@
using System.Globalization;
using System.IO.Compression;
using System.Reflection;
+using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
+using PolyMod.modApi;
+using PolyMod.Patches;
using UnityEngine;
+using Console = Il2CppSystem.Console;
+using Input = PolyMod.modApi.Input;
namespace PolyMod;
-public static class Loader
+internal static class Loader
{
internal static Dictionary typeMappings = new()
{
@@ -132,27 +136,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 +187,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 +218,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 +227,124 @@ internal static void LoadMods(Dictionary mods)
Plugin.logger.LogInfo($"Registered mod {manifest.id}");
}
+ CheckDependencies(mods);
+ // var dependencyCycle = !SortMods(Registry.mods);
+ // return dependencyCycle;
+ return false;
+ }
+ internal static void LoadMods(Dictionary mods, bool onlyUnloaded=false)
+ {
+ StringBuilder checksumString = new();
+ foreach (var (id, mod) in mods)
+ {
+ if (mod.status != Mod.Status.Success) continue;
+ Plugin.logger.LogInfo($"loading mod: {id}");
+ var luaFiles = new List();
+ foreach (var file in mod.files)
+ {
+ checksumString.Append(JsonSerializer.Serialize(file));
+ if (Path.GetExtension(file.name) == ".dll")
+ {
+ if (!onlyUnloaded)
+ LoadAssemblyFile(mod, file);
+ }
+ if (Path.GetFileName(file.name) == "sprites.json")
+ {
+ if (!onlyUnloaded)
+ LoadSpriteInfoFile(mod, file);
+ }
+ if (Path.GetExtension(file.name) == ".lua")
+ {
+ luaFiles.Add(file);
+ }
+ }
+ if (luaFiles.Any())
+ {
+ LoadLuaFiles(mod, luaFiles);
+ }
+ if (!mod.client && id != "polytopia")
+ {
+ checksumString.Append(id);
+ checksumString.Append(mod.version.ToString());
+ }
+ }
+ Compatibility.HashSignatures(checksumString);
+
+ }
+ private static void LoadLuaFiles(Mod mod, List files)
+ {
+ var manager = new LuaManager(mod);
+ Plugin.logger.LogInfo($"loading lua files for mod {mod.id}");
+ foreach (var file in files)
+ {
+ manager.Execute(Encoding.UTF8.GetString(file.bytes), file.name);
+ }
+ }
+ 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 +382,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 +441,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(PolyScriptBase)))
+ is { } modType)
+ {
+ var modInstance = (PolyScriptBase) 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 +485,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 +499,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 +521,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 +536,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 +566,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 +575,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 +688,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 +747,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 +755,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())
{
@@ -702,4 +823,14 @@ public static void HandleSkins(JObject gld, JObject patch)
}
patch.Remove("skinData");
}
+ public static void UnloadMods(Dictionary mods)
+ {
+ Patch.Clear();
+ Input.Clear();
+ // TODO GLD
+ // TODO asm
+ // TODO sprites
+
+ mods.Clear();
+ }
}
diff --git a/src/LuaManager.cs b/src/LuaManager.cs
new file mode 100644
index 0000000..b84f249
--- /dev/null
+++ b/src/LuaManager.cs
@@ -0,0 +1,193 @@
+using System.Linq;
+using System.Text.Json.Nodes;
+using BepInEx.Logging;
+using Il2CppInterop.Runtime.InteropTypes.Arrays;
+using Il2CppSystem.Collections;
+using MoonSharp.Interpreter;
+using MoonSharp.Interpreter.Debugging;
+using MoonSharp.Interpreter.Interop;
+using Newtonsoft.Json.Linq;
+using PolyMod.modApi;
+using Polytopia.Data;
+using PolytopiaBackendBase.Game;
+using UnityEngine;
+using IDisposable = Il2CppSystem.IDisposable;
+using Input = PolyMod.modApi.Input;
+using Object = Il2CppSystem.Object;
+
+namespace PolyMod;
+
+public class LuaManager
+{
+ private Script lua;
+ private ManualLogSource logger;
+ static LuaManager()
+ {
+
+ }
+ public LuaManager(string modName)
+ {
+ logger = BepInEx.Logging.Logger.CreateLogSource($"PolyMod] [{modName}");
+
+ lua = new Script
+ {
+ Options =
+ {
+ DebugPrint = (message) => logger.LogInfo(message),
+ }
+ };
+
+ void RegisterTypesAndExtensions(IEnumerable types)
+ {
+ foreach (var type in types)
+ {
+ if (type is not { IsPublic: true }) continue;
+ if (type is { IsSealed: true, IsAbstract: true }) UserData.RegisterExtensionType(type);
+ else
+ {
+ UserData.RegisterType(type);
+ lua.Globals[type.Name] = type;
+ }
+ }
+ }
+
+ RegisterTypesAndExtensions(typeof(GameLogicData).Assembly.GetTypes());
+ RegisterTypesAndExtensions(typeof(PopupButtonContainer).Assembly.GetTypes());
+ RegisterTypesAndExtensions(typeof(Enumerable).Assembly.GetTypes()); // linq
+
+ #region PolyMod.*
+
+ UserData.RegisterType(typeof(Registry));
+ lua.Globals["Registry"] = typeof(Registry);
+
+ UserData.RegisterType(typeof(Patch));
+ lua.Globals["Patch"] = typeof(Patch);
+
+ UserData.RegisterType(typeof(General));
+ lua.Globals["General"] = typeof(General);
+
+ UserData.RegisterType(typeof(LuaEnumCache));
+ lua.Globals["EnumCache"] = typeof(LuaEnumCache);
+
+ LuaEnumCache.RegisterEnum("GameMode");
+ LuaEnumCache.RegisterEnum("TribeType");
+ LuaEnumCache.RegisterEnum("TechType");
+ LuaEnumCache.RegisterEnum("UnitType");
+ LuaEnumCache.RegisterEnum("ImprovementType");
+ LuaEnumCache.RegisterEnum("TerrainType");
+ LuaEnumCache.RegisterEnum("ResourceType");
+ LuaEnumCache.RegisterEnum("TaskType");
+ LuaEnumCache.RegisterEnum("TribeAbilityType");
+ LuaEnumCache.RegisterEnum("UnitAbilityType");
+ LuaEnumCache.RegisterEnum("ImprovementAbilityType");
+ LuaEnumCache.RegisterEnum("PlayerAbilityType");
+ LuaEnumCache.RegisterEnum("WeaponType");
+ LuaEnumCache.RegisterEnum("KeyCode");
+ LuaEnumCache.RegisterEnum("SkinType");
+
+
+ UserData.RegisterType();
+ lua.Globals["Config"] = new LuaConfig(modName, Config.ConfigTypes.PerMod, lua);
+ lua.Globals["ExposedConfig"] = new LuaConfig(modName, Config.ConfigTypes.Exposed, lua);
+
+ UserData.RegisterType(typeof(Input));
+ lua.Globals["Input"] = typeof(Input);
+
+ UserData.RegisterExtensionType(typeof(Il2cppEnumerableExtensions));
+
+ lua.Globals["clrTypeOf"] = DynValue.NewCallback((_, args) => DynValue.NewString(args[0].ToObject().GetType().FullName));
+ #endregion
+
+ #region Il2cppSystem.*
+ UserData.RegisterType(typeof(Il2CppReferenceArray<>));
+ UserData.RegisterType(typeof(Il2CppSystem.Collections.Generic.Dictionary<,>));
+ UserData.RegisterType(typeof(Il2CppSystem.Collections.Generic.List<>));
+ UserData.RegisterType(typeof(Il2CppSystem.Collections.Generic.IEnumerable<>));
+
+ UserData.RegisterType();
+ UserData.RegisterType();
+ UserData.RegisterType();
+
+ lua.Globals["JToken"] = typeof(JToken);
+ lua.Globals["JObject"] = typeof(JObject);
+ lua.Globals["JArray"] = typeof(JArray);
+ #endregion
+
+ #region UnityEngine.*
+ UserData.RegisterType();
+ UserData.RegisterType();
+ UserData.RegisterType();
+ UserData.RegisterType();
+ UserData.RegisterType();
+
+ UserData.RegisterType();
+ UserData.RegisterType();
+ UserData.RegisterType();
+ UserData.RegisterType();
+
+ UserData.RegisterType();
+ UserData.RegisterType();
+
+ UserData.RegisterType