diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..d152dce
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,262 @@
+name: Build
+
+on:
+ workflow_dispatch:
+ inputs:
+ linux:
+ type: boolean
+ description: Linux
+ default: true
+ windows:
+ type: boolean
+ description: Windows
+ default: false
+ macOS:
+ type: boolean
+ description: macOS
+ default: false
+
+ push:
+ branches:
+ - "*"
+
+ pull_request:
+ branches:
+ - "*"
+
+jobs:
+ build_windows:
+ name: Build OpenStarbound Windows x64
+ runs-on: windows-latest
+ if: ${{ (github.event_name != 'workflow_dispatch') || (inputs.windows == true) }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: 'recursive'
+
+ - name: Install CMake & Ninja
+ uses: lukka/get-cmake@latest
+ with:
+ cmakeVersion: 3.29.2
+
+ - name: sccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ variant: sccache
+ key: ${{ github.job }}-${{ runner.os }}
+ max-size: 1000M
+
+ - uses: ilammy/msvc-dev-cmd@v1
+
+ - name: vcpkg
+ uses: lukka/run-vcpkg@v11
+ id: runvcpkg
+ with:
+ vcpkgJsonGlob: '**/source/vcpkg.json'
+ vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json'
+
+ - name: Run CMake
+ uses: lukka/run-cmake@v10
+ with:
+ cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt'
+ configurePreset: 'windows-release'
+ buildPreset: 'windows-release'
+ testPreset: 'windows-release'
+
+ - name: Run Post-Build Task
+ working-directory: ${{ github.workspace }}
+ run: scripts\ci\windows\post_build.bat
+
+ - name: Assemble Files
+ working-directory: ${{ github.workspace }}
+ run: scripts\ci\windows\assemble.bat
+
+ - name: Upload Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: OpenStarbound-Windows-All-DevOnly
+ path: dist/*
+
+ - name: Upload Client
+ uses: actions/upload-artifact@v4
+ with:
+ name: OpenStarbound-Windows-Client
+ path: client_distribution/*
+
+ - name: Upload Server
+ uses: actions/upload-artifact@v4
+ with:
+ name: OpenStarbound-Windows-Server
+ path: server_distribution/*
+
+ - name: Create Installer
+ working-directory: ${{ github.workspace }}
+ run: |
+ & "C:\Program Files (x86)\Inno Setup 6\iscc.exe" /Oinstaller scripts\inno\setup.iss
+
+ - name: Upload Installer
+ uses: actions/upload-artifact@v4
+ with:
+ name: OpenStarbound-Windows-Installer
+ path: installer/*
+
+ build_linux:
+ name: Build OpenStarbound Linux x86_64
+ runs-on: ubuntu-22.04
+ if: ${{ (github.event_name != 'workflow_dispatch') || (inputs.linux == true) }}
+ env:
+ CC: clang
+ CXX: clang++
+
+ steps:
+ - name: Install Packages
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y pkg-config libxmu-dev libxi-dev libgl-dev libglu1-mesa-dev libsdl2-dev
+
+ - name: Install CMake & Ninja
+ uses: lukka/get-cmake@latest
+ with:
+ cmakeVersion: 3.29.2
+
+ - uses: actions/checkout@v4
+ with:
+ submodules: 'recursive'
+
+ - name: sccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ variant: sccache
+ key: ${{ github.job }}-${{ runner.os }}
+ max-size: 250M
+
+ - name: vcpkg
+ uses: lukka/run-vcpkg@v11
+ id: runvcpkg
+ with:
+ vcpkgJsonGlob: '**/source/vcpkg.json'
+ vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json'
+
+ - name: Run CMake
+ uses: lukka/run-cmake@v10
+ with:
+ cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt'
+ configurePreset: 'linux-release'
+ buildPreset: 'linux-release'
+
+ - name: Prepare Artifacts
+ working-directory: ${{ github.workspace }}
+ run: tar -cvf dist.tar dist
+
+ - name: Upload Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: OpenStarbound-Linux
+ path: dist.tar
+
+ - name: Run Tests
+ uses: lukka/run-cmake@v10
+ with:
+ cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt'
+ testPreset: 'linux-release'
+
+ - name: Assemble Files
+ working-directory: ${{ github.workspace }}
+ run: scripts/ci/linux/assemble.sh
+
+ - name: Upload Client Files
+ uses: actions/upload-artifact@v4
+ with:
+ name: OpenStarbound-Linux-Client
+ path: client.tar
+
+ - name: Upload Server Files
+ uses: actions/upload-artifact@v4
+ with:
+ name: OpenStarbound-Linux-Server
+ path: server.tar
+
+ build-mac-intel:
+ name: Build OpenStarbound macOS x86_64
+ runs-on: macos-13
+ if: ${{ (github.event_name != 'workflow_dispatch') || (inputs.macOS == true) }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: 'recursive'
+
+ - name: Install CMake & Ninja
+ uses: lukka/get-cmake@latest
+ with:
+ cmakeVersion: 3.29.0
+
+ - name: sccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ variant: sccache
+ key: ${{ github.job }}-Intel-${{ runner.os }}
+ max-size: 250M
+
+ - name: vcpkg
+ uses: lukka/run-vcpkg@v11
+ id: runvcpkg
+ with:
+ vcpkgJsonGlob: '**/source/vcpkg.json'
+ vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json'
+
+ - name: Run CMake
+ uses: lukka/run-cmake@v10
+ with:
+ cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt'
+ configurePreset: 'macos-release'
+ buildPreset: 'macos-release'
+ testPreset: 'macos-release'
+
+ - name: Upload Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: OpenStarbound-macOS-Intel
+ path: dist/*
+
+ build-mac-arm:
+ name: Build OpenStarbound macOS arm64
+ runs-on: macos-14
+ if: ${{ (github.event_name != 'workflow_dispatch') || (inputs.macOS == true) }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: 'recursive'
+
+ - name: Install CMake & Ninja
+ uses: lukka/get-cmake@latest
+ with:
+ cmakeVersion: 3.29.2
+
+ - name: sccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ variant: sccache
+ key: ${{ github.job }}-ARM-${{ runner.os }}
+ max-size: 250M
+
+ - name: vcpkg
+ uses: lukka/run-vcpkg@v11
+ id: runvcpkg
+ with:
+ vcpkgJsonGlob: '**/source/vcpkg.json'
+ vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json'
+
+ - name: Run CMake
+ uses: lukka/run-cmake@v10
+ with:
+ cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt'
+ configurePreset: 'macos-arm-release'
+ buildPreset: 'macos-arm-release'
+ testPreset: 'macos-arm-release'
+
+ - name: Upload Artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: OpenStarbound-macOS-Silicon
+ path: dist/*
diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml
deleted file mode 100644
index 307b30f..0000000
--- a/.github/workflows/build_linux.yml
+++ /dev/null
@@ -1,76 +0,0 @@
-name: Ubuntu Linux
-
-on:
- push:
- branches:
- - "*"
- tags:
- - "*"
-
- pull_request:
- branches:
- - "*"
-
-jobs:
- build:
- name: OpenStarbound Linux x86_64
- runs-on: ubuntu-20.04
-
- steps:
- - name: Install Packages
- run: |
- sudo apt-get update
- sudo apt-get install -y pkg-config libxmu-dev libxi-dev libgl-dev libglu1-mesa-dev libsdl2-dev
-
- - name: Install CMake & Ninja
- uses: lukka/get-cmake@latest
- with:
- cmakeVersion: 3.29.2
-
- - uses: actions/checkout@v4
- with:
- submodules: 'recursive'
-
- - name: sccache
- uses: hendrikmuhs/ccache-action@v1.2
- with:
- variant: sccache
- key: ${{ github.job }}-${{ runner.os }}
- max-size: 250M
-
- - name: vcpkg
- uses: lukka/run-vcpkg@v11
- id: runvcpkg
- with:
- vcpkgJsonGlob: '**/source/vcpkg.json'
- vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json'
-
- - name: Run CMake
- uses: lukka/run-cmake@v10
- with:
- cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt'
- configurePreset: 'linux-release'
- buildPreset: 'linux-release'
- testPreset: 'linux-release'
-
- - name: Assemble Files
- working-directory: ${{ github.workspace }}
- run: scripts/ci/linux/assemble.sh
-
- - name: Upload Artifacts
- uses: actions/upload-artifact@v4
- with:
- name: OpenStarbound-Linux
- path: dist.tar
-
- - name: Upload Client Files
- uses: actions/upload-artifact@v4
- with:
- name: OpenStarbound-Linux-Client
- path: client.tar
-
- - name: Upload Server Files
- uses: actions/upload-artifact@v4
- with:
- name: OpenStarbound-Linux-Server
- path: server.tar
\ No newline at end of file
diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml
deleted file mode 100644
index 1cfd12c..0000000
--- a/.github/workflows/build_macos.yml
+++ /dev/null
@@ -1,97 +0,0 @@
-name: macOS
-
-on:
- push:
- branches:
- - "*"
- tags:
- - "*"
-
- pull_request:
- branches:
- - "*"
-
-jobs:
- build-intel:
- name: OpenStarbound macOS x86_64
- runs-on: macos-13
-
- steps:
- - uses: actions/checkout@v4
- with:
- submodules: 'recursive'
-
- - name: Install CMake & Ninja
- uses: lukka/get-cmake@latest
- with:
- cmakeVersion: 3.29.0
-
- - name: sccache
- uses: hendrikmuhs/ccache-action@v1.2
- with:
- variant: sccache
- key: ${{ github.job }}-Intel-${{ runner.os }}
- max-size: 250M
-
- - name: vcpkg
- uses: lukka/run-vcpkg@v11
- id: runvcpkg
- with:
- vcpkgJsonGlob: '**/source/vcpkg.json'
- vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json'
-
- - name: Run CMake
- uses: lukka/run-cmake@v10
- with:
- cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt'
- configurePreset: 'macos-release'
- buildPreset: 'macos-release'
- testPreset: 'macos-release'
-
- - name: Upload Artifacts
- uses: actions/upload-artifact@v4
- with:
- name: OpenStarbound-macOS-Intel
- path: dist/*
-
- build-arm:
- name: OpenStarbound macOS arm64
- runs-on: macos-14
-
- steps:
- - uses: actions/checkout@v4
- with:
- submodules: 'recursive'
-
- - name: Install CMake & Ninja
- uses: lukka/get-cmake@latest
- with:
- cmakeVersion: 3.29.2
-
- - name: sccache
- uses: hendrikmuhs/ccache-action@v1.2
- with:
- variant: sccache
- key: ${{ github.job }}-ARM-${{ runner.os }}
- max-size: 250M
-
- - name: vcpkg
- uses: lukka/run-vcpkg@v11
- id: runvcpkg
- with:
- vcpkgJsonGlob: '**/source/vcpkg.json'
- vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json'
-
- - name: Run CMake
- uses: lukka/run-cmake@v10
- with:
- cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt'
- configurePreset: 'macos-arm-release'
- buildPreset: 'macos-arm-release'
- testPreset: 'macos-arm-release'
-
- - name: Upload Artifacts
- uses: actions/upload-artifact@v4
- with:
- name: OpenStarbound-macOS-Silicon
- path: dist/*
\ No newline at end of file
diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml
deleted file mode 100644
index d3ee36b..0000000
--- a/.github/workflows/build_windows.yml
+++ /dev/null
@@ -1,89 +0,0 @@
-name: Windows
-
-on:
- push:
- branches:
- - "*"
- tags:
- - "*"
-
- pull_request:
- branches:
- - "*"
-
-jobs:
- build:
- name: Build OpenStarbound Windows x64
- runs-on: windows-latest
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- submodules: 'recursive'
-
- - name: Install CMake & Ninja
- uses: lukka/get-cmake@latest
- with:
- cmakeVersion: 3.29.2
-
- - name: sccache
- uses: hendrikmuhs/ccache-action@v1.2
- with:
- variant: sccache
- key: ${{ github.job }}-${{ runner.os }}
- max-size: 1000M
-
- - uses: ilammy/msvc-dev-cmd@v1
-
- - name: vcpkg
- uses: lukka/run-vcpkg@v11
- id: runvcpkg
- with:
- vcpkgJsonGlob: '**/source/vcpkg.json'
- vcpkgConfigurationJsonGlob: '**/source/vcpkg-configuration.json'
-
- - name: Run CMake
- uses: lukka/run-cmake@v10
- with:
- cmakeListsTxtPath: '${{ github.workspace }}/source/CMakeLists.txt'
- configurePreset: 'windows-release'
- buildPreset: 'windows-release'
- testPreset: 'windows-release'
-
- - name: Run Post-Build Task
- working-directory: ${{ github.workspace }}
- run: scripts\ci\windows\post_build.bat
-
- - name: Assemble Files
- working-directory: ${{ github.workspace }}
- run: scripts\ci\windows\assemble.bat
-
- - name: Upload Artifacts
- uses: actions/upload-artifact@v4
- with:
- name: OpenStarbound-Windows-All-DevOnly
- path: dist/*
-
- - name: Upload Client
- uses: actions/upload-artifact@v4
- with:
- name: OpenStarbound-Windows-Client
- path: client_distribution/*
-
- - name: Upload Server
- uses: actions/upload-artifact@v4
- with:
- name: OpenStarbound-Windows-Server
- path: server_distribution/*
-
- - name: Create Installer
- working-directory: ${{ github.workspace }}
- run: |
- & "C:\Program Files (x86)\Inno Setup 6\iscc.exe" /Oinstaller scripts\inno\setup.iss
-
- - name: Upload Installer
- uses: actions/upload-artifact@v4
- with:
- name: OpenStarbound-Windows-Installer
- path: installer/*
diff --git a/.gitignore b/.gitignore
index 9420f19..9979294 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ enc_temp_folder/
.cache/
/attic/user/
/attic/chucklefish/
+/attic/debug/
/tiled/
/assets/user/
/assets/devel/
diff --git a/README.md b/README.md
index 5dd2b87..b97cca0 100644
--- a/README.md
+++ b/README.md
@@ -14,22 +14,26 @@ It is still **work-in-progress**.
## Installation
You can download a nightly build below, or the [latest release](https://github.com/OpenStarbound/OpenStarbound/releases/latest). At the moment, you must copy the game assets (**packed.pak**) from your normal Starbound install to the OpenStarbound assets directory before playing.
+OpenStarbound is a separate installation/executable than Starbound. You can copy your `storage` folder from Starbound to transfer your save data and settings. Launching OpenStarbound with Steam open will load your subscribed Steam mods.
+
An installer is available for Windows. otherwise, extract the client/server zip for your platform and copy the game assets (packed.pak) to the OpenStarbound assets folder. the macOS releases currently lack the sbinit.config and folder structure that the Linux & Windows zips have, so you'll need to create those before running them. For macOS releases, it is recommended to build them from source (See guide below).
### Nightly Builds
These link directly to the latest build from the [Actions](https://github.com/OpenStarbound/OpenStarbound/actions?query=branch%3Amain) tab.
-[**Windows**](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main):
-[Installer](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Installer.zip),
-[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Client.zip),
-[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Server.zip)
+**Windows**
+[Installer](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Windows-Installer.zip),
+[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Windows-Client.zip),
+[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Windows-Server.zip)
-[**Linux**](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_linux/main):
-[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_linux/main/OpenStarbound-Linux-Client.zip),
-[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_linux/main/OpenStarbound-Linux-Server.zip)
+**Linux**
+[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Linux-Client.zip),
+[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Linux-Server.zip)
-[**macOS**](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main "overpriced aluminium"):
-[Intel](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main/OpenStarbound-Dev-macOS-Intel.zip),
-[ARM](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main/OpenStarbound-Dev-macOS-Silicon.zip)
+**macOS**
+[Intel](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Dev-macOS-Intel.zip),
+[ARM](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Dev-macOS-Silicon.zip)
+
+[All Nightly Builds](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main)
## Changes
Note: Not every function from [StarExtensions](https://github.com/StarExtensions/StarExtensions) has been ported yet, but near-full compatibility with mods that use StarExtensions features is planned.
@@ -44,8 +48,16 @@ Note: Not every function from [StarExtensions](https://github.com/StarExtensions
* These scripts can modify, read, patch and create new assets!
* Lua patch files now exist - **.patch.lua**
* These can patch JSON assets, as well as images!
+### Commands
+**View OpenStarbound commands with `/help`! You can also view them [here](https://github.com/OpenStarbound/OpenStarbound/blob/main/assets/opensb/help.config.patch)**
+ * Changes to vanilla commands:
+ * `/settileprotection`
+ * You can now specify as many dungeon IDs as you want: `/settileprotection 69 420 false`
+ * You can now specify a range: /settileprotection 0..65535 true
### Bug Fixes
* Invalid character inventories are updated when loading in, allowing players to swap inventory mods with pre-existing characters.
+* Fix vanilla world file size bloating issue.
+* Modifying a single status property no longer re-networks every status property on the entity (server and client must be running at least OpenStarbound 0.15)
### Misc
* Player functions for saving/loading, modifying the humanoid identity, manipulating the inventory. [Documentation](https://github.com/OpenStarbound/OpenStarbound/blob/main/doc/lua/openstarbound.md)
* Character swapping (rewrite from StarExtensions, currently command-only: `/swap name` case-insensitive, only substring required)
diff --git a/assets/opensb/binds/opensb.binds b/assets/opensb/binds/opensb.binds
index c52385a..d06b6f4 100644
--- a/assets/opensb/binds/opensb.binds
+++ b/assets/opensb/binds/opensb.binds
@@ -3,7 +3,9 @@
"groups": {
"camera": { "name": "Camera" },
"voice": { "name": "Voice" },
- "building": { "name": "Building" }
+ "building": { "name": "Building" },
+ "inventory": { "name": "Inventory" },
+ "editing" : { "name" : "Editing" }
},
"name": "Open^#ebd74a;Starbound",
"binds": {
@@ -15,6 +17,11 @@
"group": "camera",
"name": "Zoom In"
},
+ "takeAll": {
+ "default": [],
+ "group": "inventory",
+ "name": "Take All From Container"
+ },
"zoomOut": {
"default": [{
"type": "key",
@@ -42,6 +49,18 @@
"default": [],
"group": "building",
"name": "Shrink Building Radius"
+ },
+ "editingCopyItemJson": {
+ "default": [], // [{"type": "key", "value": "C","mods": ["LShift"]}],
+ "name": "Copy Item in Cursor",
+ "group": "editing",
+ "tags" : ["clipboard"]
+ },
+ "editingPasteItemJson": {
+ "default": [], // [{"type": "key", "value": "V", "mods": ["LShift"]}],
+ "name": "Paste Item in Cursor",
+ "group": "editing",
+ "tags" : ["clipboard"]
}
}
}
diff --git a/assets/opensb/client.config.patch b/assets/opensb/client.config.patch
index 619a767..7bd18d6 100644
--- a/assets/opensb/client.config.patch
+++ b/assets/opensb/client.config.patch
@@ -10,5 +10,6 @@
"scissor" : false,
"letterbox" : false
},
- "postProcessLayers": []
+ "postProcessLayers": [],
+ "postProcessGroups": {}
}
diff --git a/assets/opensb/font/sources.txt b/assets/opensb/font/sources.txt
new file mode 100644
index 0000000..6265300
--- /dev/null
+++ b/assets/opensb/font/sources.txt
@@ -0,0 +1,7 @@
+m6x11: managore.itch.io/m6x11
+beech: 04.jp.org
+twemoji: github.com/jdecked/twemoji
+unifont: github.com/stgiga/UnifontEX
+dotsies: dotsies.org
+newspaper: onlygfx.com/newspaper-cutout
+pixelhobo: Chucklefish
\ No newline at end of file
diff --git a/assets/opensb/font/unifont.woff2 b/assets/opensb/font/unifont.woff2
index 5da6c98..3563d52 100644
Binary files a/assets/opensb/font/unifont.woff2 and b/assets/opensb/font/unifont.woff2 differ
diff --git a/assets/opensb/interface.config.patch b/assets/opensb/interface.config.patch
index eedd728..a43a7bc 100644
--- a/assets/opensb/interface.config.patch
+++ b/assets/opensb/interface.config.patch
@@ -41,6 +41,7 @@
// Change planet name to support the new internal string formatting.
"planetNameFormatString" : "- {} -",
+ "planetTextOffset" : [0, 120],
"planetTextStyle" : {
"backDirectives" : "border=5;fff;fff?border=1;fff;fff7?multiply=000",
"fontSize" : 24
diff --git a/assets/opensb/interface/actionbar/actionbarbottombg.png b/assets/opensb/interface/actionbar/actionbarbottombg.png
index 685ce2a..2d3246d 100644
Binary files a/assets/opensb/interface/actionbar/actionbarbottombg.png and b/assets/opensb/interface/actionbar/actionbarbottombg.png differ
diff --git a/assets/opensb/interface/building/collisionblock.png b/assets/opensb/interface/building/collisionblock.png
index 5f78e28..09bea46 100644
Binary files a/assets/opensb/interface/building/collisionblock.png and b/assets/opensb/interface/building/collisionblock.png differ
diff --git a/assets/opensb/interface/building/collisionempty.png b/assets/opensb/interface/building/collisionempty.png
index 6f8f66f..261ed74 100644
Binary files a/assets/opensb/interface/building/collisionempty.png and b/assets/opensb/interface/building/collisionempty.png differ
diff --git a/assets/opensb/interface/building/collisionplatform.png b/assets/opensb/interface/building/collisionplatform.png
index 4744f82..97a00f2 100644
Binary files a/assets/opensb/interface/building/collisionplatform.png and b/assets/opensb/interface/building/collisionplatform.png differ
diff --git a/assets/opensb/interface/graphicsmenu/shine.png b/assets/opensb/interface/graphicsmenu/shine.png
index fb130dd..909cef3 100644
Binary files a/assets/opensb/interface/graphicsmenu/shine.png and b/assets/opensb/interface/graphicsmenu/shine.png differ
diff --git a/assets/opensb/interface/merchant/shine.png b/assets/opensb/interface/merchant/shine.png
deleted file mode 100644
index 639bae9..0000000
Binary files a/assets/opensb/interface/merchant/shine.png and /dev/null differ
diff --git a/assets/opensb/interface/opensb/bindings/bind.png b/assets/opensb/interface/opensb/bindings/bind.png
index fcf492a..8ae7370 100644
Binary files a/assets/opensb/interface/opensb/bindings/bind.png and b/assets/opensb/interface/opensb/bindings/bind.png differ
diff --git a/assets/opensb/interface/opensb/bindings/bindings.lua b/assets/opensb/interface/opensb/bindings/bindings.lua
index cafcc15..32c19e8 100644
--- a/assets/opensb/interface/opensb/bindings/bindings.lua
+++ b/assets/opensb/interface/opensb/bindings/bindings.lua
@@ -35,7 +35,7 @@ local function finishBind(a, b)
if (type(a) == "table") then
snareFinished(a)
else
- snareFinished{ type = a, value = b, mods = getMods(value) }
+ snareFinished{ type = a, value = b, mods = getMods(b) }
for i, mod in ipairs(mods) do
mod.active = false
end
diff --git a/assets/opensb/interface/opensb/bindings/bindname.png b/assets/opensb/interface/opensb/bindings/bindname.png
index f7c4500..71de45f 100644
Binary files a/assets/opensb/interface/opensb/bindings/bindname.png and b/assets/opensb/interface/opensb/bindings/bindname.png differ
diff --git a/assets/opensb/interface/opensb/bindings/body.png b/assets/opensb/interface/opensb/bindings/body.png
index 272ed29..07d1cad 100644
Binary files a/assets/opensb/interface/opensb/bindings/body.png and b/assets/opensb/interface/opensb/bindings/body.png differ
diff --git a/assets/opensb/interface/opensb/bindings/categoryback.png b/assets/opensb/interface/opensb/bindings/categoryback.png
index ed838f3..df244c4 100644
Binary files a/assets/opensb/interface/opensb/bindings/categoryback.png and b/assets/opensb/interface/opensb/bindings/categoryback.png differ
diff --git a/assets/opensb/interface/opensb/bindings/footer.png b/assets/opensb/interface/opensb/bindings/footer.png
index 426f8a6..6c6c483 100644
Binary files a/assets/opensb/interface/opensb/bindings/footer.png and b/assets/opensb/interface/opensb/bindings/footer.png differ
diff --git a/assets/opensb/interface/opensb/bindings/garbage.png b/assets/opensb/interface/opensb/bindings/garbage.png
index 3069741..2f3ffaa 100644
Binary files a/assets/opensb/interface/opensb/bindings/garbage.png and b/assets/opensb/interface/opensb/bindings/garbage.png differ
diff --git a/assets/opensb/interface/opensb/bindings/groupname.png b/assets/opensb/interface/opensb/bindings/groupname.png
index 3019556..c4d837e 100644
Binary files a/assets/opensb/interface/opensb/bindings/groupname.png and b/assets/opensb/interface/opensb/bindings/groupname.png differ
diff --git a/assets/opensb/interface/opensb/bindings/header.png b/assets/opensb/interface/opensb/bindings/header.png
index 287d220..ccc4785 100644
Binary files a/assets/opensb/interface/opensb/bindings/header.png and b/assets/opensb/interface/opensb/bindings/header.png differ
diff --git a/assets/opensb/interface/opensb/bindings/reset.png b/assets/opensb/interface/opensb/bindings/reset.png
index a75fd9c..c91f11c 100644
Binary files a/assets/opensb/interface/opensb/bindings/reset.png and b/assets/opensb/interface/opensb/bindings/reset.png differ
diff --git a/assets/opensb/interface/opensb/shaders/body.png b/assets/opensb/interface/opensb/shaders/body.png
new file mode 100644
index 0000000..07d1cad
Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/body.png differ
diff --git a/assets/opensb/interface/opensb/shaders/categoryname.png b/assets/opensb/interface/opensb/shaders/categoryname.png
new file mode 100644
index 0000000..c4d837e
Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/categoryname.png differ
diff --git a/assets/opensb/interface/opensb/shaders/footer.png b/assets/opensb/interface/opensb/shaders/footer.png
new file mode 100644
index 0000000..6c6c483
Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/footer.png differ
diff --git a/assets/opensb/interface/opensb/shaders/group.png b/assets/opensb/interface/opensb/shaders/group.png
new file mode 100644
index 0000000..66f5cdb
Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/group.png differ
diff --git a/assets/opensb/interface/opensb/shaders/groupback.png b/assets/opensb/interface/opensb/shaders/groupback.png
new file mode 100644
index 0000000..df244c4
Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/groupback.png differ
diff --git a/assets/opensb/interface/opensb/shaders/header.png b/assets/opensb/interface/opensb/shaders/header.png
new file mode 100644
index 0000000..d8042ac
Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/header.png differ
diff --git a/assets/opensb/interface/opensb/shaders/optionname.png b/assets/opensb/interface/opensb/shaders/optionname.png
new file mode 100644
index 0000000..71de45f
Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/optionname.png differ
diff --git a/assets/opensb/interface/opensb/shaders/select.png b/assets/opensb/interface/opensb/shaders/select.png
new file mode 100644
index 0000000..dc649b4
Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/select.png differ
diff --git a/assets/opensb/interface/opensb/shaders/selectend.png b/assets/opensb/interface/opensb/shaders/selectend.png
new file mode 100644
index 0000000..99908d6
Binary files /dev/null and b/assets/opensb/interface/opensb/shaders/selectend.png differ
diff --git a/assets/opensb/interface/opensb/shaders/shaders.config b/assets/opensb/interface/opensb/shaders/shaders.config
new file mode 100644
index 0000000..864574e
--- /dev/null
+++ b/assets/opensb/interface/opensb/shaders/shaders.config
@@ -0,0 +1,150 @@
+{
+ "scripts" : ["/interface/opensb/shaders/shaders.lua"],
+ "scriptDelta" : 0,
+ "scriptWidgetCallbacks" : [
+ "selectGroup",
+ "toggleGroupEnabled",
+ "sliderOptionModified",
+ "textboxOptionModified",
+ "selectOptionPressed"
+ ],
+
+ "gui" : {
+ "panefeature" : {
+ "type" : "panefeature",
+ "positionLocked" : false
+ },
+ "background" : {
+ "type" : "background",
+ "fileHeader" : "/interface/opensb/shaders/header.png",
+ "fileBody" : "/interface/opensb/shaders/body.png",
+ "fileFooter" : "/interface/opensb/shaders/footer.png"
+ },
+ "banner" : {
+ "type" : "canvas",
+ "rect" : [146, 187, 398, 215]
+ },
+ "groups" : {
+ "type" : "scrollArea",
+ "rect" : [4, 16, 145, 214],
+ "children" : {
+ "list" : {
+ "type" : "list",
+ "schema" : {
+ "selectedBG" : "/interface/opensb/shaders/groupback.png?multiply=0f0",
+ "unselectedBG" : "/interface/opensb/shaders/groupback.png?multiply=222",
+ "spacing" : [0, 1],
+ "memberSize" : [130, 16],
+ "listTemplate" : {
+ "background" : {
+ "type" : "image",
+ "file" : "/interface/opensb/shaders/groupback.png?multiply=222",
+ "position" : [0, 0],
+ "zlevel" : -1
+ },
+ "button" : {
+ "type" : "button",
+ "callback" : "selectGroup",
+ "caption" : "Unnamed",
+ "base" : "/interface/opensb/shaders/group.png?replace;fff=fff0;000=0007",
+ "hover" : "/interface/opensb/shaders/group.png?replace;fff=fff7;000=3337",
+ "press" : "/interface/opensb/shaders/group.png?replace;fff=000;000=7777",
+ "pressedOffset" : [0, 0],
+ "position" : [0, 0]
+ }
+ }
+ }
+ }
+ },
+ "buttons" : {
+ "horizontal" : {
+ "forward" : { "base" : "", "hover" : "", "pressed" : "" },
+ "backward" : { "base" : "", "hover": "", "pressed" : "" }
+ },
+ "vertical" : {
+ "forward" : {
+ "base" : "/interface/scrollarea/varrow-forward.png?setcolor=fff",
+ "hover" : "/interface/scrollarea/varrow-forwardhover.png",
+ "pressed" : ""
+ },
+ "backward" : {
+ "base" : "/interface/scrollarea/varrow-backward.png?setcolor=fff",
+ "hover" : "/interface/scrollarea/varrow-backwardhover.png",
+ "pressed" : ""
+ }
+ }
+ },
+ "thumbs" : {
+ "horizontal" : {
+ "base" : { "begin" : "", "end" : "", "inner" : "" },
+ "hover" : { "begin" : "", "end" : "", "inner" : "" },
+ "pressed" : { "begin" : "", "end" : "", "inner" : "" }
+ },
+ "vertical" : {
+ "base" : {
+ "begin" : "/interface/scrollarea/vthumb-begin.png",
+ "end" : "/interface/scrollarea/vthumb-end.png",
+ "inner" : "/interface/scrollarea/vthumb-inner.png"
+ },
+ "hover" : {
+ "begin" : "/interface/scrollarea/vthumb-beginhover.png",
+ "end" : "/interface/scrollarea/vthumb-endhover.png",
+ "inner" : "/interface/scrollarea/vthumb-innerhover.png"
+ },
+ "pressed" : {
+ "begin" : "/interface/scrollarea/vthumb-beginhover.png",
+ "end" : "/interface/scrollarea/vthumb-endhover.png",
+ "inner" : "/interface/scrollarea/vthumb-innerhover.png"
+ }
+ }
+ }
+ },
+ "options" : {
+ "type" : "scrollArea",
+ "rect" : [147, 16, 398, 185],
+ "children" : {},
+ "buttons" : {
+ "horizontal" : {
+ "forward" : { "base" : "", "hover" : "", "pressed" : "" },
+ "backward" : { "base" : "", "hover": "", "pressed" : "" }
+ },
+ "vertical" : {
+ "forward" : {
+ "base" : "/interface/scrollarea/varrow-forward.png?setcolor=fff",
+ "hover" : "/interface/scrollarea/varrow-forwardhover.png",
+ "pressed" : ""
+ },
+ "backward" : {
+ "base" : "/interface/scrollarea/varrow-backward.png?setcolor=fff",
+ "hover" : "/interface/scrollarea/varrow-backwardhover.png",
+ "pressed" : ""
+ }
+ }
+ },
+ "thumbs" : {
+ "horizontal" : {
+ "base" : { "begin" : "", "end" : "", "inner" : "" },
+ "hover" : { "begin" : "", "end" : "", "inner" : "" },
+ "pressed" : { "begin" : "", "end" : "", "inner" : "" }
+ },
+ "vertical" : {
+ "base" : {
+ "begin" : "/interface/scrollarea/vthumb-begin.png",
+ "end" : "/interface/scrollarea/vthumb-end.png",
+ "inner" : "/interface/scrollarea/vthumb-inner.png"
+ },
+ "hover" : {
+ "begin" : "/interface/scrollarea/vthumb-beginhover.png",
+ "end" : "/interface/scrollarea/vthumb-endhover.png",
+ "inner" : "/interface/scrollarea/vthumb-innerhover.png"
+ },
+ "pressed" : {
+ "begin" : "/interface/scrollarea/vthumb-beginhover.png",
+ "end" : "/interface/scrollarea/vthumb-endhover.png",
+ "inner" : "/interface/scrollarea/vthumb-innerhover.png"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/assets/opensb/interface/opensb/shaders/shaders.lua b/assets/opensb/interface/opensb/shaders/shaders.lua
new file mode 100644
index 0000000..f9b6a32
--- /dev/null
+++ b/assets/opensb/interface/opensb/shaders/shaders.lua
@@ -0,0 +1,355 @@
+--constants
+local PATH = "/interface/opensb/shaders/"
+local GROUP_LIST_WIDGET = "groups.list"
+local OPTIONS_WIDGET = "options"
+
+local fmt = string.format
+local log = function() end
+
+function update()
+end
+
+
+local function alphabeticalNameSortGreater(a, b) return a.name > b.name end
+local function alphabeticalNameSortLesser(a, b) return a.name < b.name end
+local sortedGroups = {}
+local groups = {}
+
+local widgetsToGroups = {}
+local allSettings = {}
+local shaderConfig
+
+local function addGroupToList(data)
+ local name = widget.addListItem(GROUP_LIST_WIDGET)
+ widget.setText(fmt("%s.%s.button", GROUP_LIST_WIDGET, name), data.friendlyName)
+ log("Added group ^cyan;%s^reset; to list", data.friendlyName)
+ return name
+end
+
+local function parseGroups()
+ for name, data in next, renderer.postProcessGroups() do
+ if not data.hidden then
+ if not data.friendlyName then data.friendlyName = name end
+ data.groupId = name
+ data.name = data.friendlyName
+ groups[data.groupId] = data
+ end
+ end
+
+ for groupId, data in pairs(groups) do
+ sortedGroups[#sortedGroups + 1] = data
+ end
+ table.sort(sortedGroups, alphabeticalNameSortLesser)
+ for i = 1, #sortedGroups do
+ local data = sortedGroups[i]
+ data.index = i
+ local name = addGroupToList(data)
+ data.widget = name
+ widgetsToGroups[name] = data
+ end
+ if sortedGroups[1] then
+ local first = sortedGroups[1].widget
+ widget.setListSelected(GROUP_LIST_WIDGET, first)
+ selectGroup(first)
+ end
+end
+
+local activeGroup
+
+local function addOptionGroup(data, i, added)
+ local y = (i - 1) * -14
+ local bg = {
+ type = "image",
+ file = PATH .. "groupname.png",
+ position = {-12, y}
+ }
+ local name = "group_" .. i
+ widget.addChild(BINDS_WIDGET, bg, name)
+ added[#added + 1] = name
+ local label = {
+ type = "label",
+ value = data.label,
+ wrapWidth = 120,
+ fontSize = 8,
+ hAnchor = "mid",
+ vAnchor = "mid",
+ position = {120, 6}
+ }
+ widget.addChild(fmt("%s.%s", BINDS_WIDGET, name), label, "text")
+end
+
+local function digitRegex(n)
+ local i = 0
+ while n ~= math.floor(n) do
+ n = n * 10
+ i = i + 1
+ end
+ return fmt("%%.%df",i) -- create format string %.nf, where n = %d (i with no decimal points)
+end
+local optionPrefix = "option_"
+local optionOffset = #optionPrefix+1
+local function addOption(data, i, added)
+ local y = (i - 1) * -14
+ local bg = {
+ type = "image",
+ file = PATH .. "optionname.png",
+ position = {-12, y}
+ }
+ local name = "label_" .. data.name
+ widget.addChild(OPTIONS_WIDGET, bg, name)
+ added[#added + 1] = name
+ local label = {
+ type = "label",
+ value = data.label,
+ wrapWidth = 120,
+ fontSize = 8,
+ hAnchor = "mid",
+ vAnchor = "mid",
+ position = {62, 6}
+ }
+ widget.addChild(fmt("%s.%s", OPTIONS_WIDGET, name), label, "text")
+ local value = data.default or 0
+ if not shaderConfig[activeGroup] then
+ shaderConfig[activeGroup] = {parameters={}}
+ end
+ if not shaderConfig[activeGroup].parameters then
+ shaderConfig[activeGroup].parameters = {}
+ end
+ if shaderConfig[activeGroup].parameters[data.name] ~= nil then
+ value = shaderConfig[activeGroup].parameters[data.name]
+ end
+
+ -- todo: finish this
+ if data.type == "slider" then
+ local range = data.range or {0,1}
+ local delta = data.delta or 0.01
+ -- for some reason ranges require ints so
+ local r = {range[1]/delta, range[2]/delta, 1}
+
+ local slider = {
+ type = "slider",
+ callback = "sliderOptionModified",
+ position = {110, y + 2},
+ gridImage = "/interface/optionsmenu/smallselection.png",
+ range = r
+ }
+ name = optionPrefix..data.name
+ added[#added + 1] = name
+ widget.addChild(OPTIONS_WIDGET, slider, name)
+
+ widget.setSliderValue(fmt("%s.%s",OPTIONS_WIDGET,name), value/delta)
+ local valLabel = {
+ type = "label",
+ value = fmt(digitRegex(delta),value),
+ position = {186, y + 2}
+ }
+ added[#added + 1] = name.."_value"
+ widget.addChild(OPTIONS_WIDGET, valLabel, name.."_value")
+ elseif data.type == "select" then
+ local n = #data.values
+ local width = math.floor(118/n)
+ local by = y
+ local buttons = {}
+ for i=0,n-1 do
+ local img = fmt("%sselect.png?crop=0;0;%.0f;13",PATH,width)
+ local button = {
+ type = "button",
+ callback="selectOptionPressed",
+ caption = data.values[i+1],
+ base = img,
+ hover = img.."?brightness=-25",
+ baseImageChecked = img.."?multiply=00ff00",
+ hoverImageChecked = img.."?multiply=00ff00?brightness=-25",
+ position = {110+width*i, by},
+ pressedOffset = {0, 0},
+ checkable = true,
+ checked = (value == i)
+ }
+ name = "select_"..data.name.."_"..i
+ added[#added + 1] = name
+ widget.addChild(OPTIONS_WIDGET, button, name)
+ table.insert(buttons,{widget=fmt("%s.%s",OPTIONS_WIDGET,name),index=i})
+ end
+ for k,v in next, buttons do
+ widget.setData(v.widget,{option=data,index=v.index,buttons=buttons})
+ end
+ --[[local bge = {
+ type = "image",
+ file = PATH.."selectend.png",
+ position = {110+width*3, by}
+ }
+ name = "bgend_"..data.name
+ added[#added + 1] = name
+ widget.addChild(OPTIONS_WIDGET, bge, name)]]
+ end
+end
+
+function sliderOptionModified(option, odata)
+ local oname = string.sub(option, optionOffset)
+ local parameter = groups[activeGroup].parameters[oname]
+ local value = widget.getSliderValue(fmt("%s.%s",OPTIONS_WIDGET,option))*parameter.delta
+
+ widget.setText(fmt("%s.%s",OPTIONS_WIDGET,option.."_value"), fmt(digitRegex(parameter.delta),value))
+
+ for k,v in next, parameter.effects do
+ renderer.setEffectParameter(v, oname, value)
+ end
+ shaderConfig[activeGroup].parameters[oname] = value
+ root.setConfiguration("postProcessGroups",shaderConfig)
+end
+
+function selectOptionPressed(button,bdata)
+ sb.logInfo(sb.print(bdata))
+
+ for k,v in next, bdata.buttons do
+ if v.index ~= bdata.index then
+ widget.setChecked(v.widget, false)
+ end
+ end
+
+ value = bdata.index
+
+ local oname = bdata.option.name
+ local parameter = groups[activeGroup].parameters[oname]
+
+ for k,v in next, parameter.effects do
+ renderer.setEffectParameter(v, oname, value)
+ end
+ shaderConfig[activeGroup].parameters[oname] = value
+ root.setConfiguration("postProcessGroups",shaderConfig)
+end
+
+local function addEnabled(groupname, i, added)
+ local y = (i - 1) * -14
+ local bg = {
+ type = "image",
+ file = PATH .. "optionname.png",
+ position = {-12, y}
+ }
+ local name = "label_" .. i
+ widget.addChild(OPTIONS_WIDGET, bg, name)
+ added[#added + 1] = name
+ local label = {
+ type = "label",
+ value = "Enabled",
+ wrapWidth = 120,
+ fontSize = 8,
+ hAnchor = "mid",
+ vAnchor = "mid",
+ position = {62, 6}
+ }
+ widget.addChild(fmt("%s.%s", OPTIONS_WIDGET, name), label, "text")
+ local checkbox = {
+ type = "button",
+ callback = "toggleGroupEnabled",
+ position = {165, y + 2},
+ pressedOffset = {0, 0},
+ base = "/interface/optionsmenu/checkboxnocheck.png",
+ hover = "/interface/optionsmenu/checkboxnocheckhover.png",
+ baseImageChecked = "/interface/optionsmenu/checkboxcheck.png",
+ hoverImageChecked = "/interface/optionsmenu/checkboxcheckhover.png",
+ checkable = true,
+ checked = renderer.postProcessGroupEnabled(groupname)
+ }
+ name = "check_"..groupname
+ added[#added + 1] = name
+ widget.addChild(OPTIONS_WIDGET, checkbox, name)
+end
+
+function toggleGroupEnabled(checkbox, cdata)
+ renderer.setPostProcessGroupEnabled(activeGroup, widget.getChecked(fmt("%s.%s",OPTIONS_WIDGET,checkbox)), true)
+ shaderConfig = root.getConfiguration("postProcessGroups")
+end
+
+function selectGroup()
+ local selected = widget.getListSelected(GROUP_LIST_WIDGET)
+ local group = widgetsToGroups[selected]
+ local dataFromPrev = widget.getData(OPTIONS_WIDGET)
+ if dataFromPrev then
+ for i, v in pairs(dataFromPrev) do
+ widget.removeChild(OPTIONS_WIDGET, v)
+ end
+ end
+ widgetsToOptions = {}
+
+ activeGroup = group.groupId
+
+ local bannerOptions = widget.bindCanvas("banner")
+ bannerOptions:clear()
+
+ local bannerName = tostring(group.bannerName or group.name or group.groupId)
+ bannerOptions:drawText(bannerName, {position = {127, 13}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16)
+
+ local added = {}
+ local index = 0
+ addEnabled(group.groupId,index,added)
+ if group.categories then
+
+ elseif group.parameters then
+ local sortedParams = {}
+ for k,v in next, group.parameters do
+ v.name = k
+ sortedParams[#sortedParams+1] = v
+ end
+ table.sort(sortedParams, alphabeticalNameSortLesser)
+
+ for k,v in next, sortedParams do
+ index = index + 1
+ addOption(v, index, added)
+ end
+ end
+ --[[
+ local categories = group.categories or {}
+ if not categories.unsorted then
+ categories.unsorted = {name = "Unsorted"}
+ end
+
+ local sortedCategories = {}
+ for categoryId, data in pairs(categories) do
+ data.name = tostring(data.name or categoryId)
+ data.sortedOptions = {}
+ sortedCategories[#sortedCategories + 1] = data
+ end
+
+ table.sort(sortedCategories, alphabeticalNameSortLesser)
+
+ for categoryId, data in pairs(categories) do
+ table.sort(data.sortedOptions, alphabeticalNameSortLesser)
+ end
+
+ local onlyUnsorted = not sortedGroups[2] and sortedGroups[1] == categories.unsorted
+
+ for iA = 1, #sortedCategories do
+ local category = sortedCategories[iA]
+ local optionsCount = #category.sortedOptions
+ if optionsCount > 0 then
+ if not onlyUnsorted then
+ index = index + 1
+ addOptionCategory(category, index, added)
+ end
+ for iB = 1, optionsCount do
+ index = index + 1
+ addOption(category.sortedOptions[iB], index, added)
+ end
+ end
+ end
+ ]]
+
+ widget.setData(OPTIONS_WIDGET, added)
+end
+
+
+local function initCallbacks()
+ widget.registerMemberCallback(GROUP_LIST_WIDGET, "selectGroup", selectGroup)
+end
+
+function init()
+ shaderConfig = root.getConfiguration("postProcessGroups")
+ --log = chat and chat.addMessage or sb.logInfo
+
+ widget.clearListItems(GROUP_LIST_WIDGET)
+ initCallbacks()
+ parseGroups()
+
+ script.setUpdateDelta(1)
+end
diff --git a/assets/opensb/interface/opensb/voicechat/activityback.png b/assets/opensb/interface/opensb/voicechat/activityback.png
index a88f7f0..b603eca 100644
Binary files a/assets/opensb/interface/opensb/voicechat/activityback.png and b/assets/opensb/interface/opensb/voicechat/activityback.png differ
diff --git a/assets/opensb/interface/opensb/voicechat/bigbuttonback.png b/assets/opensb/interface/opensb/voicechat/bigbuttonback.png
index 663da60..6287d8d 100644
Binary files a/assets/opensb/interface/opensb/voicechat/bigbuttonback.png and b/assets/opensb/interface/opensb/voicechat/bigbuttonback.png differ
diff --git a/assets/opensb/interface/opensb/voicechat/body.png b/assets/opensb/interface/opensb/voicechat/body.png
index c76bbb4..0eb134c 100644
Binary files a/assets/opensb/interface/opensb/voicechat/body.png and b/assets/opensb/interface/opensb/voicechat/body.png differ
diff --git a/assets/opensb/interface/opensb/voicechat/deviceback.png b/assets/opensb/interface/opensb/voicechat/deviceback.png
index 8f25473..0d0509f 100644
Binary files a/assets/opensb/interface/opensb/voicechat/deviceback.png and b/assets/opensb/interface/opensb/voicechat/deviceback.png differ
diff --git a/assets/opensb/interface/opensb/voicechat/footer.png b/assets/opensb/interface/opensb/voicechat/footer.png
index 98fa09b..244d106 100644
Binary files a/assets/opensb/interface/opensb/voicechat/footer.png and b/assets/opensb/interface/opensb/voicechat/footer.png differ
diff --git a/assets/opensb/interface/opensb/voicechat/header.png b/assets/opensb/interface/opensb/voicechat/header.png
index ab6b123..01977d5 100644
Binary files a/assets/opensb/interface/opensb/voicechat/header.png and b/assets/opensb/interface/opensb/voicechat/header.png differ
diff --git a/assets/opensb/interface/opensb/voicechat/indicator/back.png b/assets/opensb/interface/opensb/voicechat/indicator/back.png
index 44b8147..0e31e86 100644
Binary files a/assets/opensb/interface/opensb/voicechat/indicator/back.png and b/assets/opensb/interface/opensb/voicechat/indicator/back.png differ
diff --git a/assets/opensb/interface/opensb/voicechat/indicator/front.png b/assets/opensb/interface/opensb/voicechat/indicator/front.png
index 3a8e511..0982dbd 100644
Binary files a/assets/opensb/interface/opensb/voicechat/indicator/front.png and b/assets/opensb/interface/opensb/voicechat/indicator/front.png differ
diff --git a/assets/opensb/interface/opensb/voicechat/indicator/front_muted.png b/assets/opensb/interface/opensb/voicechat/indicator/front_muted.png
index e444545..0a33038 100644
Binary files a/assets/opensb/interface/opensb/voicechat/indicator/front_muted.png and b/assets/opensb/interface/opensb/voicechat/indicator/front_muted.png differ
diff --git a/assets/opensb/interface/opensb/voicechat/pushtotalkback.png b/assets/opensb/interface/opensb/voicechat/pushtotalkback.png
index 7fc85c0..a30c306 100644
Binary files a/assets/opensb/interface/opensb/voicechat/pushtotalkback.png and b/assets/opensb/interface/opensb/voicechat/pushtotalkback.png differ
diff --git a/assets/opensb/interface/optionsmenu/body_blank.png b/assets/opensb/interface/optionsmenu/body_blank.png
index d770341..4127905 100644
Binary files a/assets/opensb/interface/optionsmenu/body_blank.png and b/assets/opensb/interface/optionsmenu/body_blank.png differ
diff --git a/assets/opensb/interface/optionsmenu/duocontrolsbutton.png b/assets/opensb/interface/optionsmenu/duocontrolsbutton.png
index 5ae49fa..d491bd6 100644
Binary files a/assets/opensb/interface/optionsmenu/duocontrolsbutton.png and b/assets/opensb/interface/optionsmenu/duocontrolsbutton.png differ
diff --git a/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png b/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png
index b43f732..6709e71 100644
Binary files a/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png and b/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png differ
diff --git a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch
index a169bef..4755b45 100644
--- a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch
+++ b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch
@@ -54,6 +54,24 @@
"instrumentSlider" : { "type" : "slider", "position" : [62, 158], "gridImage" : "/interface/optionsmenu/largeselection.png" },
"instrumentLabel" : { "type" : "label", "position" : [32, 158], "value" : "Tunes" },
- "instrumentValueLabel" : { "type" : "label", "position" : [192, 158], "hAnchor" : "mid", "value" : "Replace Me" }
+ "instrumentValueLabel" : { "type" : "label", "position" : [192, 158], "hAnchor" : "mid", "value" : "Replace Me" },
+
+ "headRotationLabel" : {
+ "type" : "label",
+ "position" : [25, 51],
+ "hAnchor" : "left",
+ "value" : "HEAD ROTATION"
+ },
+ "headRotationCheckbox" : {
+ "type" : "button",
+ "pressedOffset" : [0, 0],
+ "position" : [104, 51],
+ "base" : "/interface/optionsmenu/checkboxnocheck.png",
+ "hover" : "/interface/optionsmenu/checkboxnocheckhover.png",
+ "baseImageChecked" : "/interface/optionsmenu/checkboxcheck.png",
+ "hoverImageChecked" : "/interface/optionsmenu/checkboxcheckhover.png",
+ "checkable" : true,
+ "checked" : true
+ }
}
}
\ No newline at end of file
diff --git a/assets/opensb/interface/optionsmenu/shine.png b/assets/opensb/interface/optionsmenu/shine.png
index bbc7c5e..ad962fe 100644
Binary files a/assets/opensb/interface/optionsmenu/shine.png and b/assets/opensb/interface/optionsmenu/shine.png differ
diff --git a/assets/opensb/interface/optionsmenu/tricontrolsbutton.png b/assets/opensb/interface/optionsmenu/tricontrolsbutton.png
index 0c2ad7a..5d505aa 100644
Binary files a/assets/opensb/interface/optionsmenu/tricontrolsbutton.png and b/assets/opensb/interface/optionsmenu/tricontrolsbutton.png differ
diff --git a/assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png b/assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png
index 7bf9653..3147be6 100644
Binary files a/assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png and b/assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png differ
diff --git a/assets/opensb/interface/title/barstound.png b/assets/opensb/interface/title/barstound.png
index 6c17d8d..d6e14af 100644
Binary files a/assets/opensb/interface/title/barstound.png and b/assets/opensb/interface/title/barstound.png differ
diff --git a/assets/opensb/interface/title/starbound.png b/assets/opensb/interface/title/starbound.png
index 24ea4d4..33647cc 100644
Binary files a/assets/opensb/interface/title/starbound.png and b/assets/opensb/interface/title/starbound.png differ
diff --git a/assets/opensb/interface/windowconfig/charselection.config.patch b/assets/opensb/interface/windowconfig/charselection.config.patch
new file mode 100644
index 0000000..c904b44
--- /dev/null
+++ b/assets/opensb/interface/windowconfig/charselection.config.patch
@@ -0,0 +1,9 @@
+{
+ "createCharButton" : {
+ "type" : "button",
+ "base" : "/interface/title/createcharacter.png",
+ "hover" : "/interface/title/createcharacterover.png",
+ "position" : [23, 241],
+ "pressedOffset" : [0, 0]
+ }
+}
diff --git a/assets/opensb/interface/windowconfig/crafting.config.patch b/assets/opensb/interface/windowconfig/crafting.config.patch
index 0728507..9febcd4 100644
--- a/assets/opensb/interface/windowconfig/crafting.config.patch
+++ b/assets/opensb/interface/windowconfig/crafting.config.patch
@@ -4,7 +4,11 @@
// Disables the crafting timer if true.
"disableTimer" : false,
- // This is only used if the crafting timer is enabled.
- // This is how many crafts are ran when the crafting timer wraps.
- "craftCount" : 1
-} }
\ No newline at end of file
+ // This is only used if the crafting timer is enabled.
+ // This is how many crafts are ran when the crafting timer wraps.
+ "craftCount" : 1,
+
+ // This is how many of any item can be crafted at once.
+ // This is also used by merchants.
+ "maxSpinCount" : 9999
+} }
diff --git a/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua b/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua
index 7d7ab44..b17053a 100644
--- a/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua
+++ b/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua
@@ -13,6 +13,12 @@ local function shift(thing, x, y)
return thing
end
+local function moveto(thing, otherthing)
+ thing.position[1] = otherthing.position[1]
+ thing.position[2] = otherthing.position[2]
+ return thing
+end
+
-- patch function, called by the game
function patch(config)
local layout = config.paneLayout
@@ -41,10 +47,14 @@ function patch(config)
-- Create hardware cursor toggle
shift(clone(layout, "multiTextureLabel", "hardwareCursorLabel"), 98, -11).value = "HARDWARE CURSOR"
shift(clone(layout, "multiTextureCheckbox", "hardwareCursorCheckbox"), 99, -11)
+
+ -- Create shader menu button
+ shift(moveto(clone(layout, "accept", "showShadersMenu"), layout.interfaceScaleSlider), 112, -2).caption = "Shaders"
+
shift(layout.title, 0, 24)
shift(layout.resLabel, 0, 28)
shift(layout.resSlider, 0, 28)
shift(layout.resValueLabel, 0, 28)
return config
-end
\ No newline at end of file
+end
diff --git a/assets/opensb/interface/windowconfig/merchant.config.patch b/assets/opensb/interface/windowconfig/merchant.config.patch
new file mode 100644
index 0000000..fd483c2
--- /dev/null
+++ b/assets/opensb/interface/windowconfig/merchant.config.patch
@@ -0,0 +1,12 @@
+[
+ {
+ "op" : "test",
+ "path" : "/paneLayout/bgShine/position/1",
+ "value" : -12
+ },
+ {
+ "op" : "replace",
+ "path" : "/paneLayout/bgShine/position/1",
+ "value" : -19
+ }
+]
diff --git a/assets/opensb/items/armors/avian/avian-tier6separator/old/bsleeve.png b/assets/opensb/items/armors/avian/avian-tier6separator/old/bsleeve.png
new file mode 100644
index 0000000..7d52748
Binary files /dev/null and b/assets/opensb/items/armors/avian/avian-tier6separator/old/bsleeve.png differ
diff --git a/assets/opensb/items/armors/avian/avian-tier6separator/old/chestf.png b/assets/opensb/items/armors/avian/avian-tier6separator/old/chestf.png
new file mode 100644
index 0000000..5779930
Binary files /dev/null and b/assets/opensb/items/armors/avian/avian-tier6separator/old/chestf.png differ
diff --git a/assets/opensb/items/armors/avian/avian-tier6separator/old/chestm.png b/assets/opensb/items/armors/avian/avian-tier6separator/old/chestm.png
new file mode 100644
index 0000000..4073c18
Binary files /dev/null and b/assets/opensb/items/armors/avian/avian-tier6separator/old/chestm.png differ
diff --git a/assets/opensb/items/armors/avian/avian-tier6separator/old/fsleeve.png b/assets/opensb/items/armors/avian/avian-tier6separator/old/fsleeve.png
new file mode 100644
index 0000000..db91be2
Binary files /dev/null and b/assets/opensb/items/armors/avian/avian-tier6separator/old/fsleeve.png differ
diff --git a/assets/opensb/items/tools/inspectiontool/inspectionmodeicon.png b/assets/opensb/items/tools/inspectiontool/inspectionmodeicon.png
index 2ba0112..b110a66 100644
Binary files a/assets/opensb/items/tools/inspectiontool/inspectionmodeicon.png and b/assets/opensb/items/tools/inspectiontool/inspectionmodeicon.png differ
diff --git a/assets/opensb/opensb/coconut.png b/assets/opensb/opensb/coconut.png
index 3a5192b..265dfdc 100644
Binary files a/assets/opensb/opensb/coconut.png and b/assets/opensb/opensb/coconut.png differ
diff --git a/assets/opensb/player.config.patch b/assets/opensb/player.config.patch
index 5960507..bd5cbc8 100644
--- a/assets/opensb/player.config.patch
+++ b/assets/opensb/player.config.patch
@@ -12,5 +12,7 @@
"maxWireTrans" : 0.4
},
+ "inventoryFilters" : { "default" : {} },
+
"swapDance" : null // Set this to a valid dance to trigger on character swap.
}
\ No newline at end of file
diff --git a/assets/opensb/rendering/sprites/error_left.png b/assets/opensb/rendering/sprites/error_left.png
index 2f4ec6f..a2cf6b6 100644
Binary files a/assets/opensb/rendering/sprites/error_left.png and b/assets/opensb/rendering/sprites/error_left.png differ
diff --git a/assets/opensb/rendering/sprites/error_right.png b/assets/opensb/rendering/sprites/error_right.png
index 1fadd17..d7c8820 100644
Binary files a/assets/opensb/rendering/sprites/error_right.png and b/assets/opensb/rendering/sprites/error_right.png differ
diff --git a/assets/opensb/scripts/opensb/player/commands.lua b/assets/opensb/scripts/opensb/player/commands.lua
index 2b42f48..9696e0d 100644
--- a/assets/opensb/scripts/opensb/player/commands.lua
+++ b/assets/opensb/scripts/opensb/player/commands.lua
@@ -2,7 +2,7 @@ local module = {}
modules.commands = module
local commands = {}
-local function command(name, func)
+local function register(name, func)
commands[name] = func
end
@@ -12,8 +12,7 @@ function module.init()
end
end
-
-command("run", function(src)
+register("run", function(src)
local success, result = pcall(loadstring, src, "/run")
if not success then
return "^#f00;compile error: " .. result
@@ -35,4 +34,6 @@ command("run", function(src)
end
end
end
-end)
\ No newline at end of file
+end)
+
+module.register = register
\ No newline at end of file
diff --git a/assets/opensb/scripts/opensb/player/copy_paste.lua b/assets/opensb/scripts/opensb/player/copy_paste.lua
new file mode 100644
index 0000000..2a15af4
--- /dev/null
+++ b/assets/opensb/scripts/opensb/player/copy_paste.lua
@@ -0,0 +1,81 @@
+local module = {}
+modules.copy_paste = module
+
+local commands = modules.commands
+local function getItemName(item)
+ return item.parameters.shortdescription
+ or root.itemConfig(item.name).config.shortdescription
+ or item.name
+end
+
+local function popupError(prefix, msg)
+ sb.logError("%s: %s", prefix, msg)
+ msg = #msg > 80 and msg:sub(1, 80) .. "..." or msg
+ local findNewLine = msg:find("\n", 1, true)
+ interface.queueMessage("^#f00;error:^reset; " .. (findNewLine and msg:sub(1, findNewLine - 1) or msg), 7)
+end
+
+local function getClipboardText()
+ local text = clipboard.hasText() and clipboard.getText()
+ return text and text:sub(1, 1) == "{" and text or nil
+end
+
+local function copyItem()
+ local item = player.swapSlotItem() or player.primaryHandItem() or player.altHandItem()
+ if not item then return end
+
+ clipboard.setText(sb.printJson(item, 2))
+ local message = string.format("Copied ^cyan;%s^reset; to clipboard", getItemName(item))
+ interface.queueMessage(message, 4, 0.5)
+end
+
+local function pasteItem()
+ if player.swapSlotItem() then return end
+ local data = getClipboardText()
+ if not data then return end
+
+ local success, result = pcall(sb.parseJson, data)
+ if not success then
+ popupError("Error parsing clipboard item", result)
+ else
+ local success, err = pcall(player.setSwapSlotItem, result)
+ if not success then popupError("Error loading clipboard item", err)
+ else
+ local message = string.format("Pasted ^cyan;%s^reset; from clipboard", getItemName(result))
+ interface.queueMessage(message, 4, 0.5)
+ end
+ end
+end
+
+function module.update()
+ if input.bindDown("opensb", "editingCopyItemJson") then
+ copyItem()
+ end
+
+ if input.bindDown("opensb", "editingPasteItemJson") then
+ pasteItem()
+ end
+end
+
+commands.register("exportplayer", function()
+ if not clipboard.available() then
+ return "Clipboard unavailable"
+ end
+ local success, text = pcall(sb.printJson, player.save(), 2)
+ if not success then return text end
+ clipboard.setText(text)
+ return "Exported player to clipboard"
+end)
+
+commands.register("importplayer", function()
+ local data = getClipboardText()
+ if not data then return "Clipboard does not contain JSON" end
+
+ local success, result = pcall(sb.parseJson, data)
+ if not success then
+ return "Error parsing player: " .. result
+ else
+ success, result = pcall(player.load, result)
+ return success and "Loaded player from clipboard" or "Error loading player: " .. result
+ end
+end)
\ No newline at end of file
diff --git a/assets/opensb/scripts/opensb/player/player.lua b/assets/opensb/scripts/opensb/player/player.lua
index 05cbfb9..7670595 100644
--- a/assets/opensb/scripts/opensb/player/player.lua
+++ b/assets/opensb/scripts/opensb/player/player.lua
@@ -1,2 +1,2 @@
require "/scripts/opensb/util/modules.lua"
-modules("/scripts/opensb/player/", {"commands"})
\ No newline at end of file
+modules("/scripts/opensb/player/", {"commands", "copy_paste"})
\ No newline at end of file
diff --git a/assets/opensb/scripts/opensb/universeclient/loadconfig.lua b/assets/opensb/scripts/opensb/universeclient/loadconfig.lua
new file mode 100644
index 0000000..2bb3ef8
--- /dev/null
+++ b/assets/opensb/scripts/opensb/universeclient/loadconfig.lua
@@ -0,0 +1,22 @@
+-- Meant to manage loading various miscellaneous things from configuration, such as shader parameters.
+
+local module = {}
+modules.config_loader = module
+
+function module.init()
+ local shaderConfig = root.getConfiguration("postProcessGroups") or {}
+ local postProcessGroups = renderer.postProcessGroups()
+ local changes = false
+ for k,v in next, shaderConfig do
+ local group = postProcessGroups[k]
+ if v.parameters then
+ for k2,v2 in next, group.parameters do
+ if v.parameters[k2] ~= nil then
+ for _,e in next, v2.effects do
+ renderer.setEffectParameter(e,k2,v.parameters[k2])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/assets/opensb/scripts/opensb/universeclient/universeclient.lua b/assets/opensb/scripts/opensb/universeclient/universeclient.lua
index cad342b..a534c25 100644
--- a/assets/opensb/scripts/opensb/universeclient/universeclient.lua
+++ b/assets/opensb/scripts/opensb/universeclient/universeclient.lua
@@ -1,2 +1,2 @@
require "/scripts/opensb/util/modules.lua"
-modules("/scripts/opensb/universeclient/", {"voicemanager"})
\ No newline at end of file
+modules("/scripts/opensb/universeclient/", {"voicemanager","loadconfig"})
diff --git a/assets/opensb/sky/orbitals/orangestar.png b/assets/opensb/sky/orbitals/orangestar.png
index 788a070..b2ea72e 100644
Binary files a/assets/opensb/sky/orbitals/orangestar.png and b/assets/opensb/sky/orbitals/orangestar.png differ
diff --git a/assets/opensb/sky/orbitals/redstar.png b/assets/opensb/sky/orbitals/redstar.png
index 7f6837b..6adb1a9 100644
Binary files a/assets/opensb/sky/orbitals/redstar.png and b/assets/opensb/sky/orbitals/redstar.png differ
diff --git a/assets/opensb/sky/orbitals/whitestar.png b/assets/opensb/sky/orbitals/whitestar.png
index f4eb5a2..87e11eb 100644
Binary files a/assets/opensb/sky/orbitals/whitestar.png and b/assets/opensb/sky/orbitals/whitestar.png differ
diff --git a/assets/opensb/sky/orbitals/yellowstar.png b/assets/opensb/sky/orbitals/yellowstar.png
index 596d8ef..8979000 100644
Binary files a/assets/opensb/sky/orbitals/yellowstar.png and b/assets/opensb/sky/orbitals/yellowstar.png differ
diff --git a/assets/opensb/tiles/shadows.png b/assets/opensb/tiles/shadows.png
index 28bb7a3..af4df9e 100644
Binary files a/assets/opensb/tiles/shadows.png and b/assets/opensb/tiles/shadows.png differ
diff --git a/doc/lua/openstarbound.md b/doc/lua/openstarbound.md
index ad8483d..f4eff06 100644
--- a/doc/lua/openstarbound.md
+++ b/doc/lua/openstarbound.md
@@ -76,9 +76,13 @@ Returns the asset source path of an asset, or nil if the asset doesn't exist. If
Without metadata: Returns an array with all the asset source paths.
With metadata: Returns a table, key/value being source path/metadata.
-#### `?` root.assetImage(`String` image)
+#### `Image` root.assetImage(`String` image)
-*TODO*
+Returns an image.
+
+#### `Json` root.assetFrames(`String` imagePath)
+
+Returns an array containing a `file` (the frames file used for the image) and `frames` (the frame data of the image).
#### `JsonArray` root.assetPatches(`String` asset)
@@ -112,6 +116,131 @@ Sets a configuration value in `/storage/starbound.config` by path.
Returns all recipes.
+---
+# Interface
+
+The interface table contains bindings which allow scripts to display a message at the bottom of the screen, among other things.
+
+#### `void` interface.queueMessage(`String` message, [`float` cooldown, [`float` springState]])
+
+Queues a message popup at the bottom of the screen with an optional **cooldown** and **springState**.
+
+#### `void` interface.setHudVisible(`bool` visible)
+
+Sets the HUD's visibility.
+
+#### `bool` interface.hudVisible()
+
+Returns the HUD's visibility.
+
+#### `PaneId` interface.bindRegisteredPane(`string` paneName)
+Binds a registered pane (defined in `/source/frontend/StarMainInterfaceTypes`) to a Lua value, which can then call widget functions on that pane.
+Panes
+EscapeDialog
+Inventory
+Codex
+Cockpit
+Tech
+Songbook
+Ai
+Popup
+Confirmation
+JoinRequest
+Options
+QuestLog
+ActionBar
+TeamBar
+StatusPane
+Chat
+WireInterface
+PlanetText
+RadioMessagePopup
+CraftingPlain
+QuestTracker
+MmUpgrade
+Collections
+
+
+#### `void` interface.displayRegisteredPane(`string` paneName)
+Displays a registered pane.
+
+
+#### `?` interface.bindCanvas()
+TODO
+
+
+#### `int` interface.scale()
+Returns the scale used for interfaces.
+
+
+---
+
+# World
+
+The world table now contains extra bindings.
+
+---
+
+#### `bool` world.isServer()
+
+Returns whether the script is running on the server or client.
+
+---
+
+#### `bool` world.isClient()
+
+Returns whether the script is running on the server or client.
+
+---
+
+The following additional world bindings are available only for scripts running on the client.
+
+---
+
+#### `entityId` world.mainPlayer()
+
+Returns the entity ID of the player hosting the world.
+
+---
+
+#### `Vec2F` world.entityAimPosition(`entityId` entityId)
+
+Returns the current cursor aim position of the specified entity.
+
+---
+
+#### `bool` world.inWorld()
+
+Returns whether any players are in the world.
+
+---
+
+The following additional world bindings are available only for scripts running on the server.
+
+---
+
+#### `void` world.setExpiryTime(`float` expiryTime)
+
+Sets the amount of time to persist a ephemeral world when it is inactive.
+
+---
+
+#### `string` world.id()
+
+Returns a `String` representation of the world's id.
+
+---
+
+#### `?` world.callScriptContext(`?` ?)
+
+TODO
+
+---
+
+#### `?` world.sendPacket(`?` ?)
+
+?
+
---
# Player
@@ -392,9 +521,9 @@ Returns whether the specified item can enter the specified item bag.
Returns the contents of the specified action bar link slot's specified hand.
-#### `bool` player.setActionBarSlotLink(`String` itemBagName, `ItemDescriptor` item)
+#### `bool` player.setActionBarSlotLink(`int` slot, `String` hand, `ItemSlot` itemSlot)
-Returns whether the specified item can enter the specified item bag.
+Links the specified slot's hand to the specified itemSlot.
---
@@ -413,3 +542,64 @@ Sets the player's interact radius. This does not persist upon returning to the m
Returns all the recipes the player can craft with their currently held items and currencies.
---
+#### `String` player.currentState()
+
+Returns the player's current movement state.
+
+Player States
+idle
+walk
+run
+jump
+fall
+swim
+swimIdle
+lounge
+crouch
+teleportIn
+teleportOut
+
+---
+
+#### `List` player.teamMembers()
+
+Returns an array, each entry being a table with `name`, `uuid`, `entity`, `healthPercentage` and `energyPercentage`
+
+---
+
+# Renderer
+
+The new renderer table is accessible from almost every clientside script and allows configuring shaders.
+
+---
+
+#### `void` renderer.setPostProcessGroupEnabled(String group, bool enabled, [bool save])
+
+Enables or disables a post process shader group. If save is true, this change is saved to configuration as well.
+
+---
+
+#### `bool` renderer.postProcessGroupEnabled(String group)
+
+Returns true if the specified post process group is enabled.
+
+---
+
+#### `Json` renderer.postProcessGroups()
+
+Returns every post process group. Identical to grabbing them from client.config with root.assetJson.
+
+---
+
+#### `Json` renderer.setEffectParameter(String effectName, String parameterName, RenderEffectParameter value)
+
+Sets the specified scriptable parameter of the specified shader effect to the provided value.
+This is accessed from the shader as a uniform and must be defined in the effect's configuration.
+
+---
+
+#### `RenderEffectParameter` renderer.getEffectParameter(String effectName, String parameterName)
+
+Returns the specified scriptable parameter of the specified shader effect.
+
+---
diff --git a/scripts/ci/linux/assemble.sh b/scripts/ci/linux/assemble.sh
index dbdfbe0..06a25aa 100755
--- a/scripts/ci/linux/assemble.sh
+++ b/scripts/ci/linux/assemble.sh
@@ -51,6 +51,5 @@ cp \
scripts/steam_appid.txt \
server_distribution/linux/
-tar -cvf dist.tar dist
tar -cvf client.tar client_distribution
tar -cvf server.tar server_distribution
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index d13d59d..ad3026b 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -80,6 +80,8 @@ if(NOT DEFINED STAR_SYSTEM)
set(STAR_SYSTEM "linux")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
set(STAR_SYSTEM "freebsd")
+ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "NetBSD")
+ set(STAR_SYSTEM "netbsd")
elseif(UNIX)
set(STAR_SYSTEM "unix")
else()
@@ -208,6 +210,8 @@ elseif(STAR_SYSTEM STREQUAL "linux")
set_flag(STAR_SYSTEM_LINUX)
elseif(STAR_SYSTEM STREQUAL "freebsd")
set_flag(STAR_SYSTEM_FREEBSD)
+elseif(STAR_SYSTEM STREQUAL "netbsd")
+ set_flag(STAR_SYSTEM_NETBSD)
endif()
if(STAR_SYSTEM_FAMILY STREQUAL "windows")
@@ -281,14 +285,14 @@ if(STAR_COMPILER_GNU)
set(CMAKE_C_FLAGS_DEBUG "-g -Og")
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og")
- set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -Ofast")
- set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -Ofast")
+ set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math")
+ set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math")
- set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast")
- set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast")
+ set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math")
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math")
- set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -Ofast")
- set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Ofast")
+ set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math")
+ set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math")
set(CMAKE_SKIP_BUILD_RPATH TRUE)
@@ -312,14 +316,14 @@ elseif(STAR_COMPILER_CLANG)
set(CMAKE_C_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
- set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -Ofast")
- set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -Ofast")
+ set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math")
+ set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math")
- set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast")
- set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast")
+ set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math")
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math")
- set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -Ofast")
- set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Ofast")
+ set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math")
+ set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math")
set(CMAKE_SKIP_BUILD_RPATH TRUE)
@@ -445,6 +449,10 @@ elseif(STAR_SYSTEM_FREEBSD)
set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lpthread -lrt")
set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lpthread -lrt")
+elseif(STAR_SYSTEM_NETBSD)
+ set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lpthread -lrt -lexecinfo")
+ set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lpthread -lrt -lexecinfo")
+
endif()
# Find all required external libraries, based on build settings...
diff --git a/source/CMakePresets.json b/source/CMakePresets.json
index c19adae..46ebbb7 100644
--- a/source/CMakePresets.json
+++ b/source/CMakePresets.json
@@ -46,12 +46,14 @@
"displayName": "Linux x64",
"binaryDir": "${sourceParentDir}/build/linux-release",
"cacheVariables": {
- "CMAKE_BUILD_TYPE": "Release",
+ "CMAKE_BUILD_TYPE": "RelWithDebInfo",
+ "CMAKE_C_COMPILER": "clang",
+ "CMAKE_CXX_COMPILER": "clang++",
"VCPKG_TARGET_TRIPLET": "x64-linux-mixed",
"CMAKE_INCLUDE_PATH": "${sourceParentDir}/lib/linux/include",
"CMAKE_LIBRARY_PATH": "${sourceParentDir}/lib/linux",
"STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX": true,
- "STAR_USE_JEMALLOC": true
+ "STAR_USE_JEMALLOC": false
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
diff --git a/source/application/StarMainApplication_sdl.cpp b/source/application/StarMainApplication_sdl.cpp
index 6b0ca4c..557e02d 100644
--- a/source/application/StarMainApplication_sdl.cpp
+++ b/source/application/StarMainApplication_sdl.cpp
@@ -314,10 +314,12 @@ public:
int height;
SDL_GetWindowSize(m_sdlWindow, &width, &height);
m_windowSize = Vec2U(width, height);
-
+
+#ifdef __APPLE__
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+#endif
m_sdlGlContext = SDL_GL_CreateContext(m_sdlWindow);
if (!m_sdlGlContext)
diff --git a/source/application/StarRenderer.hpp b/source/application/StarRenderer.hpp
index 2a156f7..2b106ff 100644
--- a/source/application/StarRenderer.hpp
+++ b/source/application/StarRenderer.hpp
@@ -120,7 +120,7 @@ public:
virtual void set(List& primitives) = 0;
};
-typedef Variant RenderEffectParameter;
+typedef Variant RenderEffectParameter;
class Renderer {
public:
@@ -141,6 +141,9 @@ public:
// The effect config will specify named parameters and textures which can be
// set here.
virtual void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) = 0;
+ virtual void setEffectScriptableParameter(String const& effectName, String const& parameterName, RenderEffectParameter const& parameter) = 0;
+ virtual Maybe getEffectScriptableParameter(String const& effectName, String const& parameterName) = 0;
+ virtual Maybe getEffectScriptableParameterType(String const& effectName, String const& parameterName) = 0;
virtual void setEffectTexture(String const& textureName, ImageView const& image) = 0;
virtual bool switchEffectConfig(String const& name) = 0;
diff --git a/source/application/StarRenderer_opengl.cpp b/source/application/StarRenderer_opengl.cpp
index 0c9e7ec..ceb945d 100644
--- a/source/application/StarRenderer_opengl.cpp
+++ b/source/application/StarRenderer_opengl.cpp
@@ -107,8 +107,10 @@ OpenGlRenderer::OpenGlRenderer() {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
- //glEnable(GL_DEBUG_OUTPUT);
- //glDebugMessageCallback(GlMessageCallback, this);
+ if (GLEW_VERSION_4_3) {
+ //glEnable(GL_DEBUG_OUTPUT);
+ //glDebugMessageCallback(GlMessageCallback, this);
+ }
m_whiteTexture = createGlTexture(Image::filled({1, 1}, Vec4B(255, 255, 255, 255), PixelFormat::RGBA32),
TextureAddressing::Clamp,
@@ -306,21 +308,39 @@ void OpenGlRenderer::loadEffectConfig(String const& name, Json const& effectConf
throw RendererException::format("Unrecognized effect parameter type '{}'", type);
}
- effect.parameters[p.first] = effectParameter;
-
- if (Json def = p.second.get("default", {})) {
- if (type == "bool") {
- setEffectParameter(p.first, def.toBool());
- } else if (type == "int") {
- setEffectParameter(p.first, (int)def.toInt());
- } else if (type == "float") {
- setEffectParameter(p.first, def.toFloat());
- } else if (type == "vec2") {
- setEffectParameter(p.first, jsonToVec2F(def));
- } else if (type == "vec3") {
- setEffectParameter(p.first, jsonToVec3F(def));
- } else if (type == "vec4") {
- setEffectParameter(p.first, jsonToVec4F(def));
+ if (p.second.getBool("scriptable",false)) {
+ if (Json def = p.second.get("default", {})) {
+ if (type == "bool") {
+ effectParameter.parameterValue = (RenderEffectParameter)def.toBool();
+ } else if (type == "int") {
+ effectParameter.parameterValue = (RenderEffectParameter)(int)def.toInt();
+ } else if (type == "float") {
+ effectParameter.parameterValue = (RenderEffectParameter)def.toFloat();
+ } else if (type == "vec2") {
+ effectParameter.parameterValue = (RenderEffectParameter)jsonToVec2F(def);
+ } else if (type == "vec3") {
+ effectParameter.parameterValue = (RenderEffectParameter)jsonToVec3F(def);
+ } else if (type == "vec4") {
+ effectParameter.parameterValue = (RenderEffectParameter)jsonToVec4F(def);
+ }
+ }
+ effect.scriptables[p.first] = effectParameter;
+ } else {
+ effect.parameters[p.first] = effectParameter;
+ if (Json def = p.second.get("default", {})) {
+ if (type == "bool") {
+ setEffectParameter(p.first, def.toBool());
+ } else if (type == "int") {
+ setEffectParameter(p.first, (int)def.toInt());
+ } else if (type == "float") {
+ setEffectParameter(p.first, def.toFloat());
+ } else if (type == "vec2") {
+ setEffectParameter(p.first, jsonToVec2F(def));
+ } else if (type == "vec3") {
+ setEffectParameter(p.first, jsonToVec3F(def));
+ } else if (type == "vec4") {
+ setEffectParameter(p.first, jsonToVec4F(def));
+ }
}
}
}
@@ -382,6 +402,50 @@ void OpenGlRenderer::setEffectParameter(String const& parameterName, RenderEffec
ptr->parameterValue = value;
}
+void OpenGlRenderer::setEffectScriptableParameter(String const& effectName, String const& parameterName, RenderEffectParameter const& value) {
+ auto find = m_effects.find(effectName);
+ if (find == m_effects.end())
+ return;
+
+ Effect& effect = find->second;
+
+ auto ptr = effect.scriptables.ptr(parameterName);
+ if (!ptr || (ptr->parameterValue && *ptr->parameterValue == value))
+ return;
+
+ if (ptr->parameterType != value.typeIndex())
+ throw RendererException::format("OpenGlRenderer::setEffectScriptableParameter '{}' parameter type mismatch", parameterName);
+
+ ptr->parameterValue = value;
+}
+
+Maybe OpenGlRenderer::getEffectScriptableParameter(String const& effectName, String const& parameterName) {
+ auto find = m_effects.find(effectName);
+ if (find == m_effects.end())
+ return {};
+
+ Effect& effect = find->second;
+
+ auto ptr = effect.scriptables.ptr(parameterName);
+ if (!ptr)
+ return {};
+
+ return ptr->parameterValue;
+}
+Maybe OpenGlRenderer::getEffectScriptableParameterType(String const& effectName, String const& parameterName) {
+ auto find = m_effects.find(effectName);
+ if (find == m_effects.end())
+ return {};
+
+ Effect& effect = find->second;
+
+ auto ptr = effect.scriptables.ptr(parameterName);
+ if (!ptr)
+ return {};
+
+ return ptr->parameterType;
+}
+
void OpenGlRenderer::setEffectTexture(String const& textureName, ImageView const& image) {
auto ptr = m_currentEffect->textures.ptr(textureName);
if (!ptr)
@@ -1061,6 +1125,26 @@ void OpenGlRenderer::setupGlUniforms(Effect& effect, Vec2U screenSize) {
}
glUniform2f(m_screenSizeUniform, screenSize[0], screenSize[1]);
+
+ for (auto& param : effect.scriptables) {
+ auto ptr = ¶m.second;
+ auto mvalue = ptr->parameterValue;
+ if (mvalue) {
+ RenderEffectParameter value = mvalue.value();
+ if (auto v = value.ptr())
+ glUniform1i(ptr->parameterUniform, *v);
+ else if (auto v = value.ptr())
+ glUniform1i(ptr->parameterUniform, *v);
+ else if (auto v = value.ptr())
+ glUniform1f(ptr->parameterUniform, *v);
+ else if (auto v = value.ptr())
+ glUniform2f(ptr->parameterUniform, (*v)[0], (*v)[1]);
+ else if (auto v = value.ptr())
+ glUniform3f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2]);
+ else if (auto v = value.ptr())
+ glUniform4f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2], (*v)[3]);
+ }
+ }
}
RefPtr OpenGlRenderer::getGlFrameBuffer(String const& id) {
diff --git a/source/application/StarRenderer_opengl.hpp b/source/application/StarRenderer_opengl.hpp
index 36b8354..79e5400 100644
--- a/source/application/StarRenderer_opengl.hpp
+++ b/source/application/StarRenderer_opengl.hpp
@@ -25,6 +25,9 @@ public:
void loadEffectConfig(String const& name, Json const& effectConfig, StringMap const& shaders) override;
void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override;
+ void setEffectScriptableParameter(String const& effectName, String const& parameterName, RenderEffectParameter const& parameter) override;
+ Maybe getEffectScriptableParameter(String const& effectName, String const& parameterName) override;
+ Maybe getEffectScriptableParameterType(String const& effectName, String const& parameterName) override;
void setEffectTexture(String const& textureName, ImageView const& image) override;
void setScissorRect(Maybe const& scissorRect) override;
@@ -191,6 +194,7 @@ private:
GLuint program = 0;
Json config;
StringMap parameters;
+ StringMap scriptables; // scriptable parameters which can be changed when the effect is not loaded
StringMap textures;
StringMap attributes;
diff --git a/source/base/CMakeLists.txt b/source/base/CMakeLists.txt
index e813e45..e77d9ff 100644
--- a/source/base/CMakeLists.txt
+++ b/source/base/CMakeLists.txt
@@ -18,7 +18,6 @@ SET (star_base_HEADERS
StarMixer.hpp
StarPackedAssetSource.hpp
StarRootBase.hpp
- StarVersion.hpp
StarVersionOptionParser.hpp
StarWorldGeometry.hpp
scripting/StarImageLuaBindings.hpp
@@ -40,8 +39,7 @@ SET (star_base_SOURCES
scripting/StarImageLuaBindings.cpp
)
-CONFIGURE_FILE (StarVersion.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp)
-ADD_LIBRARY (star_base OBJECT ${star_base_SOURCES} ${star_base_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp)
+ADD_LIBRARY (star_base OBJECT ${star_base_SOURCES} ${star_base_HEADERS})
IF(STAR_PRECOMPILED_HEADERS)
TARGET_PRECOMPILE_HEADERS (star_base REUSE_FROM star_core)
diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp
index a188291..ecd2068 100644
--- a/source/base/StarAssets.cpp
+++ b/source/base/StarAssets.cpp
@@ -68,7 +68,7 @@ static void validatePath(AssetPath const& components, bool canContainSubPath, bo
throw AssetException::format("Path '{}' cannot contain directives", components);
}
-static void validatePath(StringView const& path, bool canContainSubPath, bool canContainDirectives) {
+static void validatePath(StringView path, bool canContainSubPath, bool canContainDirectives) {
std::string_view const& str = path.utf8();
size_t end = str.find_first_of(":?");
@@ -102,6 +102,14 @@ Maybe FramesSpecification::getRect(String const& frame) const {
}
}
+Json FramesSpecification::toJson() const {
+ return JsonObject{
+ {"aliases", jsonFromMap(aliases)},
+ {"frames", jsonFromMapV(frames, jsonFromRectU)},
+ {"file", framesFile}
+ };
+}
+
Assets::Assets(Settings settings, StringList assetSources) {
const char* AssetsPatchSuffix = ".patch";
const char* AssetsPatchListSuffix = ".patchlist";
@@ -139,6 +147,12 @@ Assets::Assets(Settings settings, StringList assetSources) {
return *assetImage;
});
+ callbacks.registerCallback("frames", [this](String const& path) -> Json {
+ if (auto frames = imageFrames(path))
+ return frames->toJson();
+ return Json();
+ });
+
callbacks.registerCallback("scan", [this](Maybe const& a, Maybe const& b) -> StringList {
return b ? scan(a.value(), *b) : scan(a.value());
});
diff --git a/source/base/StarAssets.hpp b/source/base/StarAssets.hpp
index 6b454e8..e7b8611 100644
--- a/source/base/StarAssets.hpp
+++ b/source/base/StarAssets.hpp
@@ -27,7 +27,8 @@ struct FramesSpecification {
// Get the target sub-rect of a given frame name (which can be an alias).
// Returns nothing if the frame name is not found.
Maybe getRect(String const& frame) const;
-
+ // Converts to Json.
+ Json toJson() const;
// The full path to the .frames file from which this was loaded.
String framesFile;
// Named sub-frames
diff --git a/source/client/CMakeLists.txt b/source/client/CMakeLists.txt
index 57b8231..474f2f8 100644
--- a/source/client/CMakeLists.txt
+++ b/source/client/CMakeLists.txt
@@ -12,10 +12,12 @@ INCLUDE_DIRECTORIES (
SET (star_client_HEADERS
StarClientApplication.hpp
+ StarRenderingLuaBindings.hpp
)
SET (star_client_SOURCES
StarClientApplication.cpp
+ StarRenderingLuaBindings.cpp
)
IF (STAR_SYSTEM_WINDOWS)
@@ -29,12 +31,16 @@ ADD_EXECUTABLE (starbound WIN32
$ $ $ $
${star_client_HEADERS} ${star_client_SOURCES} ${star_client_RESOURCES})
-IF(STAR_PRECOMPILED_HEADERS)
+IF (STAR_PRECOMPILED_HEADERS)
TARGET_PRECOMPILE_HEADERS (starbound REUSE_FROM star_core)
ENDIF()
-IF(UNIX)
- set_target_properties (starbound PROPERTIES LINK_FLAGS "-Wl,-rpath,'$ORIGIN'")
+IF (UNIX)
+ SET_TARGET_PROPERTIES (starbound PROPERTIES LINK_FLAGS "-Wl,-rpath,'$ORIGIN'")
ENDIF()
-TARGET_LINK_LIBRARIES (starbound ${STAR_EXT_LIBS} ${STAR_EXT_GUI_LIBS})
\ No newline at end of file
+IF (STAR_SYSTEM_MACOS)
+ SET_TARGET_PROPERTIES (starbound PROPERTIES XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES)
+ENDIF()
+
+TARGET_LINK_LIBRARIES (starbound ${STAR_EXT_LIBS} ${STAR_EXT_GUI_LIBS})
diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp
index 5f0c22e..09ebf0c 100644
--- a/source/client/StarClientApplication.cpp
+++ b/source/client/StarClientApplication.cpp
@@ -22,7 +22,9 @@
#include "StarInterfaceLuaBindings.hpp"
#include "StarInputLuaBindings.hpp"
#include "StarVoiceLuaBindings.hpp"
+#include "StarCameraLuaBindings.hpp"
#include "StarClipboardLuaBindings.hpp"
+#include "StarRenderingLuaBindings.hpp"
#if defined STAR_SYSTEM_WINDOWS
#include
@@ -404,10 +406,12 @@ void ClientApplication::render() {
auto size = Vec2F(renderer->screenSize());
auto quad = renderFlatRect(RectF::withSize(size / -2, size), Vec4B::filled(0), 0.0f);
for (auto& layer : m_postProcessLayers) {
- for (unsigned i = 0; i < layer.passes; i++) {
- for (auto& effect : layer.effects) {
- renderer->switchEffectConfig(effect);
- renderer->render(quad);
+ if (layer.group ? layer.group->enabled : true) {
+ for (unsigned i = 0; i < layer.passes; i++) {
+ for (auto& effect : layer.effects) {
+ renderer->switchEffectConfig(effect);
+ renderer->render(quad);
+ }
}
}
}
@@ -433,6 +437,8 @@ void ClientApplication::getAudioData(int16_t* sampleData, size_t frameCount) {
}
}
+auto postProcessGroupsRoot = "postProcessGroups";
+
void ClientApplication::renderReload() {
auto assets = m_root->assets();
auto renderer = Application::renderer();
@@ -463,18 +469,55 @@ void ClientApplication::renderReload() {
loadEffectConfig("world");
+ // define post process groups and set them to be enabled/disabled based on config
+
+ auto config = m_root->configuration();
+ if (!config->get(postProcessGroupsRoot).isType(Json::Type::Object))
+ config->set(postProcessGroupsRoot, JsonObject());
+ auto groupsConfig = config->get(postProcessGroupsRoot);
+
+ m_postProcessGroups.clear();
+ auto postProcessGroups = assets->json("/client.config:postProcessGroups").toObject();
+ for (auto& pair : postProcessGroups) {
+ auto name = pair.first;
+ auto groupConfig = groupsConfig.opt(name);
+ auto def = pair.second.getBool("enabledDefault",true);
+ if (!groupConfig)
+ config->setPath(strf("{}.{}", postProcessGroupsRoot, name),JsonObject());
+ m_postProcessGroups.add(name,PostProcessGroup{ groupConfig ? groupConfig.value().getBool("enabled", def) : def });
+ }
+
+ // define post process layers and optionally assign them to groups
m_postProcessLayers.clear();
auto postProcessLayers = assets->json("/client.config:postProcessLayers").toArray();
for (auto& layer : postProcessLayers) {
auto effects = jsonToStringList(layer.getArray("effects"));
for (auto& effect : effects)
loadEffectConfig(effect);
- m_postProcessLayers.append(PostProcessLayer{ std::move(effects), (unsigned)layer.getUInt("passes", 1) });
+ PostProcessGroup* group = nullptr;
+ auto gname = layer.optString("group");
+ if (gname) {
+ group = &m_postProcessGroups.get(gname.value());
+ }
+ m_postProcessLayers.append(PostProcessLayer{ std::move(effects), (unsigned)layer.getUInt("passes", 1), group });
}
loadEffectConfig("interface");
}
+void ClientApplication::setPostProcessGroupEnabled(String group, bool enabled, Maybe save) {
+ m_postProcessGroups.get(group).enabled = enabled;
+ if (save && save.value())
+ m_root->configuration()->setPath(strf("{}.{}.enabled", postProcessGroupsRoot, group),enabled);
+}
+bool ClientApplication::postProcessGroupEnabled(String group) {
+ return m_postProcessGroups.get(group).enabled;
+}
+
+Json ClientApplication::postProcessGroups() {
+ return m_root->assets()->json("/client.config:postProcessGroups");
+}
+
void ClientApplication::changeState(MainAppState newState) {
MainAppState oldState = m_state;
m_state = newState;
@@ -541,8 +584,11 @@ void ClientApplication::changeState(MainAppState newState) {
m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks());
m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks());
- if (!m_root->configuration()->get("safeScripts").toBool())
- m_universeClient->setLuaCallbacks("clipboard", LuaBindings::makeClipboardCallbacks(appController()));
+ m_universeClient->setLuaCallbacks("camera", LuaBindings::makeCameraCallbacks(&m_worldPainter->camera()));
+ m_universeClient->setLuaCallbacks("renderer", LuaBindings::makeRenderingCallbacks(this));
+
+ Json alwaysAllow = m_root->configuration()->getPath("safe.alwaysAllowClipboard");
+ m_universeClient->setLuaCallbacks("clipboard", LuaBindings::makeClipboardCallbacks(appController(), alwaysAllow && alwaysAllow.toBool()));
auto heldScriptPanes = make_shared>();
@@ -565,7 +611,7 @@ void ClientApplication::changeState(MainAppState newState) {
};
m_mainMixer->setUniverseClient(m_universeClient);
- m_titleScreen = make_shared(m_playerStorage, m_mainMixer->mixer());
+ m_titleScreen = make_shared(m_playerStorage, m_mainMixer->mixer(), m_universeClient);
if (auto renderer = Application::renderer())
m_titleScreen->renderInit(renderer);
}
@@ -682,8 +728,9 @@ void ClientApplication::changeState(MainAppState newState) {
m_mainInterface = make_shared(m_universeClient, m_worldPainter, m_cinematicOverlay);
m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get()));
m_universeClient->setLuaCallbacks("chat", LuaBindings::makeChatCallbacks(m_mainInterface.get(), m_universeClient.get()));
- m_universeClient->startLua();
+ m_mainInterface->displayDefaultPanes();
+ m_universeClient->startLua();
m_mainMixer->setWorldPainter(m_worldPainter);
if (auto renderer = Application::renderer()) {
diff --git a/source/client/StarClientApplication.hpp b/source/client/StarClientApplication.hpp
index 840dc9a..323f2b7 100644
--- a/source/client/StarClientApplication.hpp
+++ b/source/client/StarClientApplication.hpp
@@ -18,6 +18,11 @@ STAR_CLASS(Input);
STAR_CLASS(Voice);
class ClientApplication : public Application {
+public:
+ void setPostProcessGroupEnabled(String group, bool enabled, Maybe save);
+ bool postProcessGroupEnabled(String group);
+ Json postProcessGroups();
+
protected:
virtual void startup(StringList const& cmdLineArgs) override;
virtual void shutdown() override;
@@ -53,9 +58,14 @@ private:
String password;
};
+ struct PostProcessGroup {
+ bool enabled;
+ };
+
struct PostProcessLayer {
List effects;
unsigned passes;
+ PostProcessGroup* group;
};
void renderReload();
@@ -104,6 +114,7 @@ private:
WorldRenderData m_renderData;
MainInterfacePtr m_mainInterface;
+ StringMap m_postProcessGroups;
List m_postProcessLayers;
// Valid if main app state == SinglePlayer
diff --git a/source/client/StarRenderingLuaBindings.cpp b/source/client/StarRenderingLuaBindings.cpp
new file mode 100644
index 0000000..39dc1a1
--- /dev/null
+++ b/source/client/StarRenderingLuaBindings.cpp
@@ -0,0 +1,45 @@
+#include "StarRenderingLuaBindings.hpp"
+#include "StarJsonExtra.hpp"
+#include "StarLuaConverters.hpp"
+#include "StarClientApplication.hpp"
+#include "StarRenderer.hpp"
+
+namespace Star {
+
+LuaCallbacks LuaBindings::makeRenderingCallbacks(ClientApplication* app) {
+ LuaCallbacks callbacks;
+
+ // if the last argument is defined and true, this change will also be saved to starbound.config and read on next game start, use for things such as an interface that does this
+ callbacks.registerCallbackWithSignature>("setPostProcessGroupEnabled", bind(mem_fn(&ClientApplication::setPostProcessGroupEnabled), app, _1, _2, _3));
+ callbacks.registerCallbackWithSignature("postProcessGroupEnabled", bind(mem_fn(&ClientApplication::postProcessGroupEnabled), app, _1));
+
+
+ // not entirely necessary (root.assetJson can achieve the same purpose) but may as well
+ callbacks.registerCallbackWithSignature("postProcessGroups", bind(mem_fn(&ClientApplication::postProcessGroups), app));
+
+ // typedef Variant RenderEffectParameter;
+ // feel free to change this if there's a better way to do this
+ // specifically checks if the effect parameter is an int since Lua prefers converting the values to floats
+ callbacks.registerCallback("setEffectParameter", [app](String const& effectName, String const& effectParameter, RenderEffectParameter const& value) {
+ auto renderer = app->renderer();
+ auto mtype = renderer->getEffectScriptableParameterType(effectName, effectParameter);
+ if (mtype) {
+ auto type = mtype.value();
+ if (type == 1 && value.is()) {
+ renderer->setEffectScriptableParameter(effectName, effectParameter, (int)value.get());
+ } else {
+ renderer->setEffectScriptableParameter(effectName, effectParameter, value);
+ }
+ }
+ });
+
+ callbacks.registerCallback("getEffectParameter", [app](String const& effectName, String const& effectParameter) {
+ auto renderer = app->renderer();
+ return renderer->getEffectScriptableParameter(effectName, effectParameter);
+ });
+
+ return callbacks;
+}
+
+
+}
diff --git a/source/client/StarRenderingLuaBindings.hpp b/source/client/StarRenderingLuaBindings.hpp
new file mode 100644
index 0000000..68e709f
--- /dev/null
+++ b/source/client/StarRenderingLuaBindings.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "StarLua.hpp"
+
+namespace Star {
+
+STAR_CLASS(ClientApplication);
+
+namespace LuaBindings {
+ LuaCallbacks makeRenderingCallbacks(ClientApplication* app);
+}
+
+}
diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt
index fb31fca..3d00baf 100644
--- a/source/core/CMakeLists.txt
+++ b/source/core/CMakeLists.txt
@@ -41,6 +41,7 @@ SET (star_core_HEADERS
StarIdMap.hpp
StarImage.hpp
StarImageProcessing.hpp
+ StarImageScaling.hpp
StarInputEvent.hpp
StarInterpolation.hpp
StarRefPtr.hpp
@@ -70,10 +71,12 @@ SET (star_core_HEADERS
StarMultiArray.hpp
StarMultiArrayInterpolator.hpp
StarMultiTable.hpp
+ StarNetCompatibility.hpp
StarNetElement.hpp
StarNetElementBasicFields.hpp
StarNetElementContainers.hpp
StarNetElementDynamicGroup.hpp
+ StarNetElementExt.hpp
StarNetElementFloatFields.hpp
StarNetElementGroup.hpp
StarNetElementSignal.hpp
@@ -122,6 +125,7 @@ SET (star_core_HEADERS
StarUnicode.hpp
StarUuid.hpp
StarVector.hpp
+ StarVersion.hpp
StarVlqEncoding.hpp
StarWeightedPool.hpp
StarWorkerPool.hpp
@@ -149,6 +153,7 @@ SET (star_core_SOURCES
StarIODevice.cpp
StarImage.cpp
StarImageProcessing.cpp
+ StarImageScaling.cpp
StarInputEvent.cpp
StarJson.cpp
StarJsonBuilder.cpp
@@ -157,11 +162,13 @@ SET (star_core_SOURCES
StarJsonPatch.cpp
StarJsonRpc.cpp
StarFormattedJson.cpp
+ StarLexicalCast.cpp
StarListener.cpp
StarLogging.cpp
StarLua.cpp
StarLuaConverters.cpp
StarMemory.cpp
+ StarNetCompatibility.cpp
StarNetElement.cpp
StarNetElementBasicFields.cpp
StarNetElementGroup.cpp
@@ -217,7 +224,8 @@ ELSEIF (STAR_SYSTEM_FAMILY_WINDOWS)
ENDIF ()
-ADD_LIBRARY (star_core OBJECT ${star_core_SOURCES} ${star_core_HEADERS})
+CONFIGURE_FILE (StarVersion.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp)
+ADD_LIBRARY (star_core OBJECT ${star_core_SOURCES} ${star_core_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp)
IF(STAR_PRECOMPILED_HEADERS)
TARGET_PRECOMPILE_HEADERS (star_core PUBLIC StarPch.hpp)
diff --git a/source/core/StarBTreeDatabase.cpp b/source/core/StarBTreeDatabase.cpp
index 19b0bd2..f66bea1 100644
--- a/source/core/StarBTreeDatabase.cpp
+++ b/source/core/StarBTreeDatabase.cpp
@@ -1,6 +1,7 @@
#include "StarBTreeDatabase.hpp"
#include "StarSha256.hpp"
#include "StarVlqEncoding.hpp"
+#include "StarLogging.hpp"
namespace Star {
@@ -243,7 +244,7 @@ uint32_t BTreeDatabase::freeBlockCount() {
indexBlockIndex = indexBlock.nextFreeBlock;
}
- count += m_availableBlocks.size() + m_pendingFree.size();
+ count += m_availableBlocks.size();
// Include untracked blocks at the end of the file in the free count.
count += (m_device->size() - m_deviceSize) / m_blockSize;
@@ -272,7 +273,7 @@ uint32_t BTreeDatabase::leafBlockCount() {
return true;
}
- BTreeDatabase* parent;
+ BTreeDatabase* parent = nullptr;
BlockIndex leafBlockCount = 0;
};
@@ -293,8 +294,8 @@ void BTreeDatabase::rollback() {
m_availableBlocks.clear();
m_indexCache.clear();
+ m_uncommittedWrites.clear();
m_uncommitted.clear();
- m_pendingFree.clear();
readRoot();
@@ -305,7 +306,8 @@ void BTreeDatabase::rollback() {
void BTreeDatabase::close(bool closeDevice) {
WriteLocker writeLocker(m_lock);
if (m_open) {
- doCommit();
+ if (!tryFlatten())
+ doCommit();
m_indexCache.clear();
@@ -536,7 +538,7 @@ auto BTreeDatabase::BTreeImpl::loadIndex(Pointer pointer) -> Index {
index->pointers.resize(s);
for (uint32_t i = 0; i < s; ++i) {
auto& e = index->pointers[i];
- e.key =buffer.readBytes(parent->m_keySize);
+ e.key = buffer.readBytes(parent->m_keySize);
e.pointer = buffer.read();
}
@@ -896,17 +898,25 @@ void BTreeDatabase::rawReadBlock(BlockIndex blockIndex, size_t blockOffset, char
if (size <= 0)
return;
- m_device->readFullAbsolute(HeaderSize + blockIndex * (StreamOffset)m_blockSize + blockOffset, block, size);
+ if (auto buffer = m_uncommittedWrites.ptr(blockIndex))
+ buffer->copyTo(block, blockOffset, size);
+ else
+ m_device->readFullAbsolute(HeaderSize + blockIndex * (StreamOffset)m_blockSize + blockOffset, block, size);
}
-void BTreeDatabase::rawWriteBlock(BlockIndex blockIndex, size_t blockOffset, char const* block, size_t size) const {
+void BTreeDatabase::rawWriteBlock(BlockIndex blockIndex, size_t blockOffset, char const* block, size_t size) {
if (blockOffset > m_blockSize || size > m_blockSize - blockOffset)
throw DBException::format("Write past end of block, offset: {} size {}", blockOffset, size);
if (size <= 0)
return;
- m_device->writeFullAbsolute(HeaderSize + blockIndex * (StreamOffset)m_blockSize + blockOffset, block, size);
+ StreamOffset blockStart = HeaderSize + blockIndex * (StreamOffset)m_blockSize;
+ auto buffer = m_uncommittedWrites.find(blockIndex);
+ if (buffer == m_uncommittedWrites.end())
+ buffer = m_uncommittedWrites.emplace(blockIndex, m_device->readBytesAbsolute(blockStart, m_blockSize)).first;
+
+ buffer->second.writeFrom(block, blockOffset, size);
}
auto BTreeDatabase::readFreeIndexBlock(BlockIndex blockIndex) -> FreeIndexBlock {
@@ -991,12 +1001,12 @@ auto BTreeDatabase::leafTailBlocks(BlockIndex leafPointer) -> List {
}
void BTreeDatabase::freeBlock(BlockIndex b) {
- if (m_uncommitted.contains(b)) {
+ if (m_uncommitted.contains(b))
m_uncommitted.remove(b);
- m_availableBlocks.add(b);
- } else {
- m_pendingFree.append(b);
- }
+ if (m_uncommittedWrites.contains(b))
+ m_uncommittedWrites.remove(b);
+
+ m_availableBlocks.add(b);
}
auto BTreeDatabase::reserveBlock() -> BlockIndex {
@@ -1007,10 +1017,7 @@ auto BTreeDatabase::reserveBlock() -> BlockIndex {
FreeIndexBlock indexBlock = readFreeIndexBlock(m_headFreeIndexBlock);
for (auto const& b : indexBlock.freeBlocks)
m_availableBlocks.add(b);
- // We cannot make available the block itself, because we must maintain
- // atomic consistency. We will need to free this block later and commit
- // the new free index block chain.
- m_pendingFree.append(m_headFreeIndexBlock);
+ m_availableBlocks.add(m_headFreeIndexBlock);
m_headFreeIndexBlock = indexBlock.nextFreeBlock;
}
@@ -1068,65 +1075,168 @@ void BTreeDatabase::readRoot() {
}
void BTreeDatabase::doCommit() {
- if (m_availableBlocks.empty() && m_pendingFree.empty() && m_uncommitted.empty())
+ if (m_availableBlocks.empty() && m_uncommitted.empty())
return;
- if (!m_availableBlocks.empty() || !m_pendingFree.empty()) {
+ if (!m_availableBlocks.empty()) {
// First, read the existing head FreeIndexBlock, if it exists
FreeIndexBlock indexBlock = FreeIndexBlock{InvalidBlockIndex, {}};
- if (m_headFreeIndexBlock != InvalidBlockIndex) {
+
+ auto newBlock = [&]() -> BlockIndex {
+ if (!m_availableBlocks.empty())
+ return m_availableBlocks.takeFirst();
+ else
+ return makeEndBlock();
+ };
+
+ if (m_headFreeIndexBlock != InvalidBlockIndex)
indexBlock = readFreeIndexBlock(m_headFreeIndexBlock);
- if (indexBlock.freeBlocks.size() >= maxFreeIndexLength()) {
- // If the existing head free index block is full, then we should start a
- // new one and leave it alone
- indexBlock.nextFreeBlock = m_headFreeIndexBlock;
- indexBlock.freeBlocks.clear();
- } else {
- // If we are copying an existing free index block, the old free index
- // block will be a newly freed block
- indexBlock.freeBlocks.append(m_headFreeIndexBlock);
- }
- }
+ else
+ m_headFreeIndexBlock = newBlock();
- // Then, we need to write all the available blocks, which are safe to write
- // to, and the pending free blocks, which are NOT safe to write to, to the
- // FreeIndexBlock chain.
+ // Then, we need to write all the available blocks to the FreeIndexBlock chain.
while (true) {
- if (indexBlock.freeBlocks.size() < maxFreeIndexLength() && (!m_availableBlocks.empty() || !m_pendingFree.empty())) {
- // If we have room on our current FreeIndexblock, just add a block to
- // it. Prioritize the pending free blocks, because we cannot use those
- // to write to.
- BlockIndex toAdd;
- if (m_pendingFree.empty())
- toAdd = m_availableBlocks.takeFirst();
- else
- toAdd = m_pendingFree.takeFirst();
-
+ // If we have room on our current FreeIndexBlock, just add a block to it.
+ if (!m_availableBlocks.empty() && indexBlock.freeBlocks.size() < maxFreeIndexLength()) {
+ BlockIndex toAdd = m_availableBlocks.takeFirst();
indexBlock.freeBlocks.append(toAdd);
} else {
- // If our index block is full OR we are out of blocks to free, then
- // need to write a new head free index block.
- if (m_availableBlocks.empty())
- m_headFreeIndexBlock = makeEndBlock();
- else
- m_headFreeIndexBlock = m_availableBlocks.takeFirst();
+ // Update the current head free index block.
writeFreeIndexBlock(m_headFreeIndexBlock, indexBlock);
// If we're out of blocks to free, then we're done
- if (m_availableBlocks.empty() && m_pendingFree.empty())
+ if (m_availableBlocks.empty())
break;
- indexBlock.nextFreeBlock = m_headFreeIndexBlock;
- indexBlock.freeBlocks.clear();
+ // If our head free index block is full, then
+ // need to write a new head free index block.
+ if (indexBlock.freeBlocks.size() >= maxFreeIndexLength()) {
+ indexBlock.nextFreeBlock = m_headFreeIndexBlock;
+ indexBlock.freeBlocks.clear();
+
+ m_headFreeIndexBlock = newBlock();
+ writeFreeIndexBlock(m_headFreeIndexBlock, indexBlock);
+ }
}
}
}
+
+ commitWrites();
writeRoot();
-
m_uncommitted.clear();
}
+void BTreeDatabase::commitWrites() {
+ for (auto& write : m_uncommittedWrites)
+ m_device->writeFullAbsolute(HeaderSize + write.first * (StreamOffset)m_blockSize, write.second.ptr(), m_blockSize);
+
+ m_device->sync();
+ m_uncommittedWrites.clear();
+}
+
+bool BTreeDatabase::tryFlatten() {
+ if (m_headFreeIndexBlock == InvalidBlockIndex || m_rootIsLeaf || !m_device->isWritable())
+ return false;
+
+ BlockIndex freeBlockCount = 0;
+ BlockIndex indexBlockIndex = m_headFreeIndexBlock;
+ while (indexBlockIndex != InvalidBlockIndex) {
+ FreeIndexBlock indexBlock = readFreeIndexBlock(indexBlockIndex);
+ freeBlockCount += 1 + indexBlock.freeBlocks.size();
+ indexBlockIndex = indexBlock.nextFreeBlock;
+ }
+
+ BlockIndex expectedBlockCount = (m_deviceSize - HeaderSize) / m_blockSize;
+ float free = float(freeBlockCount) / float(expectedBlockCount);
+ if (free < 0.05f)
+ return false;
+
+ Logger::info("[BTreeDatabase] File '{}' is {:.2f}% free space, flattening", m_device->deviceName(), free * 100.f);
+
+ indexBlockIndex = m_headFreeIndexBlock;
+ {
+ List availableBlocksList;
+ do {
+ FreeIndexBlock indexBlock = readFreeIndexBlock(indexBlockIndex);
+ availableBlocksList.appendAll(indexBlock.freeBlocks);
+ availableBlocksList.append(indexBlockIndex);
+ indexBlockIndex = indexBlock.nextFreeBlock;
+ } while (indexBlockIndex != InvalidBlockIndex);
+ m_headFreeIndexBlock = InvalidBlockIndex;
+
+ sort(availableBlocksList);
+ for (auto& availableBlock : availableBlocksList)
+ m_availableBlocks.insert(m_availableBlocks.end(), availableBlock);
+ }
+
+ BlockIndex count = 1; // 1 to include root index
+
+ double start = Time::monotonicTime();
+ auto index = m_impl.loadIndex(m_impl.rootPointer());
+ if (flattenVisitor(index, count)) {
+ m_impl.deleteIndex(index);
+ index->self = InvalidBlockIndex;
+ m_root = m_impl.storeIndex(index);
+ }
+
+ m_availableBlocks.clear();
+ m_device->resize(m_deviceSize = HeaderSize + (StreamOffset)m_blockSize * count);
+
+ m_indexCache.clear();
+ commitWrites();
+ writeRoot();
+ m_uncommitted.clear();
+
+ Logger::info("[BTreeDatabase] Finished flattening '{}' in {:.2f} milliseconds", m_device->deviceName(), (Time::monotonicTime() - start) * 1000.f);
+ return true;
+}
+
+bool BTreeDatabase::flattenVisitor(BTreeImpl::Index& index, BlockIndex& count) {
+ auto pointerCount = index->pointerCount();
+ count += pointerCount;
+ bool canStore = !m_availableBlocks.empty();
+
+ bool needsStore = false;
+ if (m_impl.indexLevel(index) == 0) {
+ for (size_t i = 0; i != pointerCount; ++i) {
+ auto indexPointer = index->pointer(i);
+ auto tailBlocks = leafTailBlocks(indexPointer);
+ if (canStore) {
+ bool leafNeedsStore = m_availableBlocks.first() < indexPointer;
+
+ if (!leafNeedsStore)
+ for (size_t i = 0; !leafNeedsStore && i != tailBlocks.size(); ++i)
+ if (m_availableBlocks.first() < tailBlocks[i])
+ leafNeedsStore = true;
+
+ if (leafNeedsStore) {
+ auto leaf = m_impl.loadLeaf(indexPointer);
+ m_impl.deleteLeaf(leaf);
+ leaf->self = InvalidBlockIndex;
+ index->updatePointer(i, m_impl.storeLeaf(leaf));
+ tailBlocks = leafTailBlocks(leaf->self);
+ needsStore = true;
+ }
+ canStore = !m_availableBlocks.empty();
+ }
+ count += tailBlocks.size();
+ }
+ } else {
+ for (size_t i = 0; i != pointerCount; ++i) {
+ auto childIndex = m_impl.loadIndex(index->pointer(i));
+ if (canStore && flattenVisitor(childIndex, count)) {
+ m_impl.deleteIndex(childIndex);
+ childIndex->self = InvalidBlockIndex;
+ index->updatePointer(i, m_impl.storeIndex(childIndex));
+ canStore = !m_availableBlocks.empty();
+ needsStore = true;
+ }
+ }
+ }
+ return needsStore || (canStore && m_availableBlocks.first() < index->self);
+}
+
void BTreeDatabase::checkIfOpen(char const* methodName, bool shouldBeOpen) const {
if (shouldBeOpen && !m_open)
throw DBException::format("BTreeDatabase method '{}' called when not open, must be open.", methodName);
@@ -1146,7 +1256,7 @@ void BTreeDatabase::checkKeySize(ByteArray const& k) const {
}
uint32_t BTreeDatabase::maxFreeIndexLength() const {
- return (m_blockSize - 2 - sizeof(BlockIndex) - 4) / sizeof(BlockIndex);
+ return (m_blockSize / sizeof(BlockIndex)) - 2 - sizeof(BlockIndex) - 4;
}
BTreeSha256Database::BTreeSha256Database() {
diff --git a/source/core/StarBTreeDatabase.hpp b/source/core/StarBTreeDatabase.hpp
index f1b88a1..b3a530f 100644
--- a/source/core/StarBTreeDatabase.hpp
+++ b/source/core/StarBTreeDatabase.hpp
@@ -230,7 +230,7 @@ private:
void updateBlock(BlockIndex blockIndex, ByteArray const& block);
void rawReadBlock(BlockIndex blockIndex, size_t blockOffset, char* block, size_t size) const;
- void rawWriteBlock(BlockIndex blockIndex, size_t blockOffset, char const* block, size_t size) const;
+ void rawWriteBlock(BlockIndex blockIndex, size_t blockOffset, char const* block, size_t size);
void updateHeadFreeIndexBlock(BlockIndex newHead);
@@ -251,6 +251,9 @@ private:
void writeRoot();
void readRoot();
void doCommit();
+ void commitWrites();
+ bool tryFlatten();
+ bool flattenVisitor(BTreeImpl::Index& index, BlockIndex& count);
void checkIfOpen(char const* methodName, bool shouldBeOpen) const;
void checkBlockIndex(size_t blockIndex) const;
@@ -285,14 +288,14 @@ private:
bool m_dirty;
// Blocks that can be freely allocated and written to without violating
- // atomic consistency
+ // atomic consistency.
Set m_availableBlocks;
- // Blocks to be freed on next commit.
- Deque m_pendingFree;
-
// Blocks that have been written in uncommitted portions of the tree.
Set m_uncommitted;
+
+ // Temporarily holds written data so that it can be rolled back.
+ mutable Map m_uncommittedWrites;
};
// Version of BTreeDatabase that hashes keys with SHA-256 to produce a unique
diff --git a/source/core/StarColor.cpp b/source/core/StarColor.cpp
index 7da8dd4..4eb726c 100644
--- a/source/core/StarColor.cpp
+++ b/source/core/StarColor.cpp
@@ -322,6 +322,8 @@ Vec3F Color::toRgbF() const {
return Vec3F(redF(), greenF(), blueF());
}
+#pragma GCC push_options
+#pragma GCC optimize("-fno-fast-math")
Vec4F Color::toHsva() const {
float h, s, v;
@@ -365,6 +367,7 @@ Vec4F Color::toHsva() const {
return Vec4F(h, s, v, alphaF());
}
+#pragma GCC pop_options
String Color::toHex() const {
auto rgba = toRgba();
diff --git a/source/core/StarDataStream.cpp b/source/core/StarDataStream.cpp
index 18456b6..7097d63 100644
--- a/source/core/StarDataStream.cpp
+++ b/source/core/StarDataStream.cpp
@@ -6,6 +6,8 @@
namespace Star {
+unsigned const CurrentStreamVersion = 3;
+
DataStream::DataStream()
: m_byteOrder(ByteOrder::BigEndian),
m_nullTerminatedStrings(false),
@@ -35,6 +37,10 @@ void DataStream::setStreamCompatibilityVersion(unsigned streamCompatibilityVersi
m_streamCompatibilityVersion = streamCompatibilityVersion;
}
+void DataStream::setStreamCompatibilityVersion(NetCompatibilityRules const& rules) {
+ m_streamCompatibilityVersion = rules.version();
+}
+
ByteArray DataStream::readBytes(size_t len) {
ByteArray ba;
ba.resize(len);
diff --git a/source/core/StarDataStream.hpp b/source/core/StarDataStream.hpp
index 02cb922..d5f9bcb 100644
--- a/source/core/StarDataStream.hpp
+++ b/source/core/StarDataStream.hpp
@@ -1,10 +1,12 @@
#pragma once
#include "StarString.hpp"
+#include "StarNetCompatibility.hpp"
namespace Star {
STAR_EXCEPTION(DataStreamException, IOException);
+extern unsigned const CurrentStreamVersion;
// Writes complex types to bytes in a portable big-endian fashion.
class DataStream {
@@ -12,8 +14,6 @@ public:
DataStream();
virtual ~DataStream() = default;
- static unsigned const CurrentStreamVersion = 1;
-
// DataStream defaults to big-endian order for all primitive types
ByteOrder byteOrder() const;
void setByteOrder(ByteOrder byteOrder);
@@ -27,7 +27,7 @@ public:
// changed for compatibility with older versions of DataStream serialization.
unsigned streamCompatibilityVersion() const;
void setStreamCompatibilityVersion(unsigned streamCompatibilityVersion);
-
+ void setStreamCompatibilityVersion(NetCompatibilityRules const& rules);
// Do direct reads and writes
virtual void readData(char* data, size_t len) = 0;
virtual void writeData(char const* data, size_t len) = 0;
diff --git a/source/core/StarDataStreamDevices.hpp b/source/core/StarDataStreamDevices.hpp
index 3f34a72..4452592 100644
--- a/source/core/StarDataStreamDevices.hpp
+++ b/source/core/StarDataStreamDevices.hpp
@@ -126,8 +126,8 @@ private:
class DataStreamExternalBuffer : public DataStream {
public:
DataStreamExternalBuffer();
- explicit DataStreamExternalBuffer(ByteArray const& byteArray);
- explicit DataStreamExternalBuffer(DataStreamBuffer const& buffer);
+ DataStreamExternalBuffer(ByteArray const& byteArray);
+ DataStreamExternalBuffer(DataStreamBuffer const& buffer);
DataStreamExternalBuffer(DataStreamExternalBuffer const& buffer) = default;
DataStreamExternalBuffer(char const* externalData, size_t len);
diff --git a/source/core/StarDirectives.cpp b/source/core/StarDirectives.cpp
index ea18bc9..ea9d565 100644
--- a/source/core/StarDirectives.cpp
+++ b/source/core/StarDirectives.cpp
@@ -156,6 +156,17 @@ void Directives::parse(String&& directives) {
}
}
+StringView Directives::prefix() const {
+ if (!m_shared)
+ return "";
+ else if (m_shared->empty())
+ return m_shared->string;
+ else if (m_shared->string.utf8().at(0) == '?')
+ return "";
+ else
+ return m_shared->entries.front().string(*m_shared);
+}
+
String Directives::string() const {
if (!m_shared)
return "";
diff --git a/source/core/StarDirectives.hpp b/source/core/StarDirectives.hpp
index 9290649..d83a3e8 100644
--- a/source/core/StarDirectives.hpp
+++ b/source/core/StarDirectives.hpp
@@ -55,6 +55,7 @@ public:
void loadOperations() const;
void parse(String&& directives);
+ StringView prefix() const;
String string() const;
String const* stringPtr() const;
String buildString() const;
diff --git a/source/core/StarFile_windows.cpp b/source/core/StarFile_windows.cpp
index 0b5c286..6352bb7 100644
--- a/source/core/StarFile_windows.cpp
+++ b/source/core/StarFile_windows.cpp
@@ -18,13 +18,11 @@
namespace Star {
-namespace {
- OVERLAPPED makeOverlapped(StreamOffset offset) {
- OVERLAPPED overlapped = {};
- overlapped.Offset = offset;
- overlapped.OffsetHigh = offset >> 32;
- return overlapped;
- }
+OVERLAPPED makeOverlapped(StreamOffset offset) {
+ OVERLAPPED overlapped = {};
+ overlapped.Offset = offset;
+ overlapped.OffsetHigh = offset >> 32;
+ return overlapped;
}
String File::convertDirSeparators(String const& path) {
@@ -378,6 +376,7 @@ size_t File::pread(void* f, char* data, size_t len, StreamOffset position) {
DWORD numRead = 0;
OVERLAPPED overlapped = makeOverlapped(position);
int ret = ReadFile(file, data, len, &numRead, &overlapped);
+ fseek(f, -(StreamOffset)numRead, IOSeek::Relative);
if (ret == 0) {
auto err = GetLastError();
if (err != ERROR_IO_PENDING)
@@ -392,6 +391,7 @@ size_t File::pwrite(void* f, char const* data, size_t len, StreamOffset position
DWORD numWritten = 0;
OVERLAPPED overlapped = makeOverlapped(position);
int ret = WriteFile(file, data, len, &numWritten, &overlapped);
+ fseek(f, -(StreamOffset)numWritten, IOSeek::Relative);
if (ret == 0) {
auto err = GetLastError();
if (err != ERROR_IO_PENDING)
diff --git a/source/core/StarFont.cpp b/source/core/StarFont.cpp
index 47569f2..d854d52 100644
--- a/source/core/StarFont.cpp
+++ b/source/core/StarFont.cpp
@@ -30,8 +30,6 @@ FTContext ftContext;
struct FontImpl {
FT_Face face;
- unsigned loadedPixelSize = 0;
- String::Char loadedChar = 0;
};
FontPtr Font::loadFont(String const& fileName, unsigned pixelSize) {
@@ -97,19 +95,11 @@ tuple Font::render(String::Char c) {
throw FontException("Font::render called on uninitialized font.");
FT_Face face = m_fontImpl->face;
+ if (FT_Load_Glyph(face, FT_Get_Char_Index(face, c), FontLoadFlags) != 0)
+ return {};
- if (m_fontImpl->loadedPixelSize != m_pixelSize || m_fontImpl->loadedChar != c) {
- FT_UInt glyph_index = FT_Get_Char_Index(face, c);
- if (FT_Load_Glyph(face, glyph_index, FontLoadFlags) != 0)
- return {};
-
- // convert to an anti-aliased bitmap
- if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL) != 0)
- return {};
- }
-
- m_fontImpl->loadedPixelSize = m_pixelSize;
- m_fontImpl->loadedChar = c;
+ if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL) != 0)
+ return {};
FT_GlyphSlot slot = face->glyph;
if (!slot->bitmap.buffer)
@@ -134,7 +124,7 @@ tuple Font::render(String::Char c) {
}
}
}
- } else if (colored = slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
+ } else if (colored = (slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA)) {
unsigned bpp = image.bytesPerPixel();
uint8_t* data = image.data() + bpp + ((image.width() * (image.height() - 2)) * bpp); // offset by 1 pixel as it's padded
for (size_t y = 0; y != height; ++y) {
diff --git a/source/core/StarImage.cpp b/source/core/StarImage.cpp
index 82e3b05..c7d4962 100644
--- a/source/core/StarImage.cpp
+++ b/source/core/StarImage.cpp
@@ -17,8 +17,13 @@ void readPngData(png_structp pngPtr, png_bytep data, png_size_t length) {
((IODevice*)png_get_io_ptr(pngPtr))->readFull((char*)data, length);
};
+bool Image::isPng(IODevicePtr device) {
+ png_byte header[8]{};
+ return !png_sig_cmp(header, 0, device->readAbsolute(0, (char*)header, sizeof(header)));
+}
+
Image Image::readPng(IODevicePtr device) {
- png_byte header[8];
+ png_byte header[8]{};
device->readFull((char*)header, sizeof(header));
if (png_sig_cmp(header, 0, sizeof(header)))
diff --git a/source/core/StarImage.hpp b/source/core/StarImage.hpp
index 478d074..aac0105 100644
--- a/source/core/StarImage.hpp
+++ b/source/core/StarImage.hpp
@@ -27,6 +27,7 @@ STAR_CLASS(Image);
class Image {
public:
static Image readPng(IODevicePtr device);
+ static bool isPng(IODevicePtr device);
// Returns the size and pixel format that would be constructed from the given
// png file, without actually loading it.
static tuple readPngMetadata(IODevicePtr device);
diff --git a/source/core/StarImageProcessing.cpp b/source/core/StarImageProcessing.cpp
index 6d2c51d..1cfd847 100644
--- a/source/core/StarImageProcessing.cpp
+++ b/source/core/StarImageProcessing.cpp
@@ -1,4 +1,5 @@
#include "StarImageProcessing.hpp"
+#include "StarImageScaling.hpp"
#include "StarMatrix3.hpp"
#include "StarInterpolation.hpp"
#include "StarLexicalCast.hpp"
@@ -10,109 +11,6 @@
namespace Star {
-Image scaleNearest(Image const& srcImage, Vec2F scale) {
- if (scale[0] < 0.0f || scale[1] < 0.0f) {
- Logger::warn("Negative scale in scaleNearest({})", scale);
- scale = scale.piecewiseMax(Vec2F::filled(0.f));
- }
- Vec2U srcSize = srcImage.size();
- Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale));
- destSize[0] = max(destSize[0], 1u);
- destSize[1] = max(destSize[1], 1u);
-
- Image destImage(destSize, srcImage.pixelFormat());
-
- for (unsigned y = 0; y < destSize[1]; ++y) {
- for (unsigned x = 0; x < destSize[0]; ++x)
- destImage.set({x, y}, srcImage.clamp(Vec2I::round(vdiv(Vec2F(x, y), scale))));
- }
- return destImage;
-}
-
-Image scaleBilinear(Image const& srcImage, Vec2F scale) {
- if (scale[0] < 0.0f || scale[1] < 0.0f) {
- Logger::warn("Negative scale in scaleBilinear({})", scale);
- scale = scale.piecewiseMax(Vec2F::filled(0.f));
- }
- Vec2U srcSize = srcImage.size();
- Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale));
- destSize[0] = max(destSize[0], 1u);
- destSize[1] = max(destSize[1], 1u);
-
- Image destImage(destSize, srcImage.pixelFormat());
-
- for (unsigned y = 0; y < destSize[1]; ++y) {
- for (unsigned x = 0; x < destSize[0]; ++x) {
- auto pos = vdiv(Vec2F(x, y), scale);
- auto ipart = Vec2I::floor(pos);
- auto fpart = pos - Vec2F(ipart);
-
- auto result = lerp(fpart[1], lerp(fpart[0], Vec4F(srcImage.clamp(ipart[0], ipart[1])), Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1]))), lerp(fpart[0],
- Vec4F(srcImage.clamp(ipart[0], ipart[1] + 1)), Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 1))));
-
- destImage.set({x, y}, Vec4B(result));
- }
- }
-
- return destImage;
-}
-
-Image scaleBicubic(Image const& srcImage, Vec2F scale) {
- if (scale[0] < 0.0f || scale[1] < 0.0f) {
- Logger::warn("Negative scale in scaleBicubic({})", scale);
- scale = scale.piecewiseMax(Vec2F::filled(0.f));
- }
- Vec2U srcSize = srcImage.size();
- Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale));
- destSize[0] = max(destSize[0], 1u);
- destSize[1] = max(destSize[1], 1u);
-
- Image destImage(destSize, srcImage.pixelFormat());
-
- for (unsigned y = 0; y < destSize[1]; ++y) {
- for (unsigned x = 0; x < destSize[0]; ++x) {
- auto pos = vdiv(Vec2F(x, y), scale);
- auto ipart = Vec2I::floor(pos);
- auto fpart = pos - Vec2F(ipart);
-
- Vec4F a = cubic4(fpart[0],
- Vec4F(srcImage.clamp(ipart[0], ipart[1])),
- Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1])),
- Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1])),
- Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1])));
-
- Vec4F b = cubic4(fpart[0],
- Vec4F(srcImage.clamp(ipart[0], ipart[1] + 1)),
- Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 1)),
- Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 1)),
- Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 1)));
-
- Vec4F c = cubic4(fpart[0],
- Vec4F(srcImage.clamp(ipart[0], ipart[1] + 2)),
- Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 2)),
- Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 2)),
- Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 2)));
-
- Vec4F d = cubic4(fpart[0],
- Vec4F(srcImage.clamp(ipart[0], ipart[1] + 3)),
- Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 3)),
- Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 3)),
- Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 3)));
-
- auto result = cubic4(fpart[1], a, b, c, d);
-
- destImage.set({x, y}, Vec4B(
- clamp(result[0], 0.0f, 255.0f),
- clamp(result[1], 0.0f, 255.0f),
- clamp(result[2], 0.0f, 255.0f),
- clamp(result[3], 0.0f, 255.0f)
- ));
- }
- }
-
- return destImage;
-}
-
StringList colorDirectivesFromConfig(JsonArray const& directives) {
List result;
@@ -214,7 +112,7 @@ ImageOperation imageOperationFromString(StringView string) {
else // we're in A of A=B. In vanilla only A=B pairs are evaluated, so only throw an error if B is also there.
return operation;
- if (which = !which)
+ if ((which = !which))
operation.colorReplaceMap[*(Vec4B*)&a] = *(Vec4B*)&b;
hexLen = 0;
@@ -631,12 +529,17 @@ void processImageOperation(ImageOperation const& operation, Image& image, ImageR
image = borderImage;
} else if (auto op = operation.ptr()) {
+ auto scale = op->scale;
+ if (scale[0] < 0.0f || scale[1] < 0.0f) {
+ Logger::warn("Negative scale in ScaleImageOperation ({})", scale);
+ scale = scale.piecewiseMax(Vec2F::filled(0.f));
+ }
if (op->mode == ScaleImageOperation::Nearest)
- image = scaleNearest(image, op->scale);
+ image = scaleNearest(image, scale);
else if (op->mode == ScaleImageOperation::Bilinear)
- image = scaleBilinear(image, op->scale);
+ image = scaleBilinear(image, scale);
else if (op->mode == ScaleImageOperation::Bicubic)
- image = scaleBicubic(image, op->scale);
+ image = scaleBicubic(image, scale);
} else if (auto op = operation.ptr()) {
image = image.subImage(Vec2U(op->subset.min()), Vec2U(op->subset.size()));
diff --git a/source/core/StarImageProcessing.hpp b/source/core/StarImageProcessing.hpp
index 4022d38..dfc9420 100644
--- a/source/core/StarImageProcessing.hpp
+++ b/source/core/StarImageProcessing.hpp
@@ -10,10 +10,6 @@ STAR_CLASS(Image);
STAR_EXCEPTION(ImageOperationException, StarException);
-Image scaleNearest(Image const& srcImage, Vec2F scale);
-Image scaleBilinear(Image const& srcImage, Vec2F scale);
-Image scaleBicubic(Image const& srcImage, Vec2F scale);
-
StringList colorDirectivesFromConfig(JsonArray const& directives);
String paletteSwapDirectivesFromConfig(Json const& swaps);
diff --git a/source/core/StarImageScaling.cpp b/source/core/StarImageScaling.cpp
new file mode 100644
index 0000000..aa94359
--- /dev/null
+++ b/source/core/StarImageScaling.cpp
@@ -0,0 +1,97 @@
+#include "StarImage.hpp"
+#include "StarImageScaling.hpp"
+#include "StarInterpolation.hpp"
+
+namespace Star {
+
+Image scaleNearest(Image const& srcImage, Vec2F const& scale) {
+ Vec2U srcSize = srcImage.size();
+ Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale));
+ destSize[0] = max(destSize[0], 1u);
+ destSize[1] = max(destSize[1], 1u);
+
+ Image destImage(destSize, srcImage.pixelFormat());
+
+ for (unsigned y = 0; y < destSize[1]; ++y) {
+ for (unsigned x = 0; x < destSize[0]; ++x)
+ destImage.set({x, y}, srcImage.clamp(Vec2I::round(vdiv(Vec2F(x, y), scale))));
+ }
+ return destImage;
+}
+
+Image scaleBilinear(Image const& srcImage, Vec2F const& scale) {
+ Vec2U srcSize = srcImage.size();
+ Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale));
+ destSize[0] = max(destSize[0], 1u);
+ destSize[1] = max(destSize[1], 1u);
+
+ Image destImage(destSize, srcImage.pixelFormat());
+
+ for (unsigned y = 0; y < destSize[1]; ++y) {
+ for (unsigned x = 0; x < destSize[0]; ++x) {
+ auto pos = vdiv(Vec2F(x, y), scale);
+ auto ipart = Vec2I::floor(pos);
+ auto fpart = pos - Vec2F(ipart);
+
+ auto result = lerp(fpart[1], lerp(fpart[0], Vec4F(srcImage.clamp(ipart[0], ipart[1])), Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1]))), lerp(fpart[0], Vec4F(srcImage.clamp(ipart[0], ipart[1] + 1)), Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 1))));
+
+ destImage.set({x, y}, Vec4B(result));
+ }
+ }
+
+ return destImage;
+}
+
+Image scaleBicubic(Image const& srcImage, Vec2F const& scale) {
+ Vec2U srcSize = srcImage.size();
+ Vec2U destSize = Vec2U::round(vmult(Vec2F(srcSize), scale));
+ destSize[0] = max(destSize[0], 1u);
+ destSize[1] = max(destSize[1], 1u);
+
+ Image destImage(destSize, srcImage.pixelFormat());
+
+ for (unsigned y = 0; y < destSize[1]; ++y) {
+ for (unsigned x = 0; x < destSize[0]; ++x) {
+ auto pos = vdiv(Vec2F(x, y), scale);
+ auto ipart = Vec2I::floor(pos);
+ auto fpart = pos - Vec2F(ipart);
+
+ Vec4F a = cubic4(fpart[0],
+ Vec4F(srcImage.clamp(ipart[0], ipart[1])),
+ Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1])),
+ Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1])),
+ Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1])));
+
+ Vec4F b = cubic4(fpart[0],
+ Vec4F(srcImage.clamp(ipart[0], ipart[1] + 1)),
+ Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 1)),
+ Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 1)),
+ Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 1)));
+
+ Vec4F c = cubic4(fpart[0],
+ Vec4F(srcImage.clamp(ipart[0], ipart[1] + 2)),
+ Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 2)),
+ Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 2)),
+ Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 2)));
+
+ Vec4F d = cubic4(fpart[0],
+ Vec4F(srcImage.clamp(ipart[0], ipart[1] + 3)),
+ Vec4F(srcImage.clamp(ipart[0] + 1, ipart[1] + 3)),
+ Vec4F(srcImage.clamp(ipart[0] + 2, ipart[1] + 3)),
+ Vec4F(srcImage.clamp(ipart[0] + 3, ipart[1] + 3)));
+
+ auto result = cubic4(fpart[1], a, b, c, d);
+
+ destImage.set({x, y}, Vec4B(
+ clamp(result[0], 0.0f, 255.0f),
+ clamp(result[1], 0.0f, 255.0f),
+ clamp(result[2], 0.0f, 255.0f),
+ clamp(result[3], 0.0f, 255.0f)
+ ));
+ }
+ }
+
+ return destImage;
+}
+
+}
\ No newline at end of file
diff --git a/source/core/StarImageScaling.hpp b/source/core/StarImageScaling.hpp
new file mode 100644
index 0000000..d758169
--- /dev/null
+++ b/source/core/StarImageScaling.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace Star {
+
+STAR_CLASS(Image);
+Image scaleNearest(Image const& srcImage, Vec2F const& scale);
+Image scaleBilinear(Image const& srcImage, Vec2F const& scale);
+Image scaleBicubic(Image const& srcImage, Vec2F const& scale);
+
+}
\ No newline at end of file
diff --git a/source/core/StarJsonExtra.hpp b/source/core/StarJsonExtra.hpp
index 2a0ca28..c8fc185 100644
--- a/source/core/StarJsonExtra.hpp
+++ b/source/core/StarJsonExtra.hpp
@@ -211,32 +211,32 @@ Json jsonFromList(List const& list, Converter&& valueConvert) {
return res;
}
-template
-Set jsonToSet(Json const& v) {
- return jsonToSet(v, construct());
+template
+SetType jsonToSet(Json const& v) {
+ return jsonToSet(v, construct());
}
-template
-Set jsonToSet(Json const& v, Converter&& valueConvert) {
+template
+SetType jsonToSet(Json const& v, Converter&& valueConvert) {
if (v.type() != Json::Type::Array)
throw JsonException("Json type is not an array in jsonToSet");
- Set res;
+ SetType res;
for (auto const& entry : v.iterateArray())
res.add(valueConvert(entry));
return res;
}
-template
-Json jsonFromSet(Set const& Set) {
- return jsonFromSet(Set, construct());
+template
+Json jsonFromSet(SetType const& Set) {
+ return jsonFromSet(Set, construct());
}
-template
-Json jsonFromSet(Set const& Set, Converter&& valueConvert) {
+template
+Json jsonFromSet(SetType const& Set, Converter&& valueConvert) {
JsonArray res;
- for (auto entry : Set)
+ for (auto& entry : Set)
res.push_back(valueConvert(entry));
return res;
diff --git a/source/core/StarJsonPatch.cpp b/source/core/StarJsonPatch.cpp
index 14505e5..88f9377 100644
--- a/source/core/StarJsonPatch.cpp
+++ b/source/core/StarJsonPatch.cpp
@@ -12,7 +12,7 @@ Json jsonPatch(Json const& base, JsonArray const& patch) {
}
return res;
} catch (JsonException const& e) {
- throw JsonPatchException(strf("Could not apply patch to base. {}", e.what()));
+ throw JsonPatchException(strf("Could not apply patch to base. {}", e.what()), false);
}
}
@@ -26,7 +26,7 @@ size_t findJsonMatch(Json const& searchable, Json const& value, JsonPath::Pointe
return i + 1;
}
} else {
- throw JsonPatchException(strf("Search operation failure, value at '{}' is not an array.", pointer.path()));
+ throw JsonPatchException(strf("Search operation failure, value at '{}' is not an array.", pointer.path()), false);
}
return 0;
}
@@ -49,9 +49,9 @@ namespace JsonPatching {
auto operation = op.getString("op");
return JsonPatching::functionMap.get(operation)(base, op);
} catch (JsonException const& e) {
- throw JsonPatchException(strf("Could not apply operation to base. {}", e.what()));
+ throw JsonPatchException(strf("Could not apply operation to base. {}", e.what()), false);
} catch (MapException const&) {
- throw JsonPatchException(strf("Invalid operation: {}", op.getString("op")));
+ throw JsonPatchException(strf("Invalid operation: {}", op.getString("op")), false);
}
}
@@ -66,28 +66,28 @@ namespace JsonPatching {
auto searchValue = op.get("search");
bool found = findJsonMatch(searchable, searchValue, pointer);
if (found && inverseTest)
- throw JsonPatchTestFail(strf("Test operation failure, expected {} to be missing.", searchValue));
+ throw JsonPatchTestFail(strf("Test operation failure, expected {} to be missing.", searchValue), false);
else if (!found && !inverseTest)
- throw JsonPatchTestFail(strf("Test operation failure, could not find {}.", searchValue));
+ throw JsonPatchTestFail(strf("Test operation failure, could not find {}.", searchValue), false);
return base;
} else {
auto value = op.opt("value");
auto testValue = pointer.get(base);
if (!value) {
if (inverseTest)
- throw JsonPatchTestFail(strf("Test operation failure, expected {} to be missing.", path));
+ throw JsonPatchTestFail(strf("Test operation failure, expected {} to be missing.", path), false);
return base;
}
if ((value && (testValue == *value)) ^ inverseTest)
return base;
else
- throw JsonPatchTestFail(strf("Test operation failure, expected {} found {}.", value, testValue));
+ throw JsonPatchTestFail(strf("Test operation failure, expected {} found {}.", value, testValue), false);
}
} catch (JsonPath::TraversalException& e) {
if (inverseTest)
return base;
- throw JsonPatchTestFail(strf("Test operation failure: {}", e.what()));
+ throw JsonPatchTestFail(strf("Test operation failure: {}", e.what()), false);
}
}
diff --git a/source/core/StarLexicalCast.cpp b/source/core/StarLexicalCast.cpp
new file mode 100644
index 0000000..e607ee5
--- /dev/null
+++ b/source/core/StarLexicalCast.cpp
@@ -0,0 +1,36 @@
+#include "StarLexicalCast.hpp"
+
+namespace Star {
+
+void throwLexicalCastError(std::errc ec, const char* first, const char* last) {
+ StringView str(first, last - first);
+ if (ec == std::errc::invalid_argument)
+ throw BadLexicalCast(strf("Lexical cast failed on '{}' (invalid argument)", str));
+ else
+ throw BadLexicalCast(strf("Lexical cast failed on '{}'", str));
+}
+
+template <>
+bool tryLexicalCast(bool& result, const char* first, const char* last) {
+ size_t len = last - first;
+ if (strncmp(first, "true", len) == 0)
+ result = true;
+ else if (strncmp(first, "false", len) != 0)
+ return false;
+
+ result = false;
+ return true;
+}
+
+template <>
+bool lexicalCast(const char* first, const char* last) {
+ size_t len = last - first;
+ if (strncmp(first, "true", len) == 0)
+ return true;
+ else if (strncmp(first, "false", len) != 0)
+ throwLexicalCastError(std::errc(), first, last);
+
+ return false;
+}
+
+}
\ No newline at end of file
diff --git a/source/core/StarLexicalCast.hpp b/source/core/StarLexicalCast.hpp
index 9c671e9..08dab14 100644
--- a/source/core/StarLexicalCast.hpp
+++ b/source/core/StarLexicalCast.hpp
@@ -5,40 +5,63 @@
#include "StarStringView.hpp"
#include "StarMaybe.hpp"
-#include
-#include
+#include "fast_float.h"
namespace Star {
STAR_EXCEPTION(BadLexicalCast, StarException);
-// Very simple basic lexical cast using stream input. Always operates in the
-// "C" locale.
+void throwLexicalCastError(std::errc ec, const char* first, const char* last);
+
template
-Maybe maybeLexicalCast(StringView s, std::ios_base::fmtflags flags = std::ios_base::boolalpha) {
- Type result;
- std::istringstream stream(std::string(s.utf8()));
- stream.flags(flags);
- stream.imbue(std::locale::classic());
+bool tryLexicalCast(Type& result, const char* first, const char* last) {
+ auto res = fast_float::from_chars(first, last, result);
+ return res.ptr == last && (res.ec == std::errc() || res.ec == std::errc::result_out_of_range);
+}
- if (!(stream >> result))
+template <>
+bool tryLexicalCast(bool& result, const char* first, const char* last);
+
+template
+bool tryLexicalCast(Type& result, String const& s) {
+ return tryLexicalCast(result, s.utf8Ptr(), s.utf8Ptr() + s.utf8Size());
+}
+
+template
+bool tryLexicalCast(Type& result, StringView s) {
+ return tryLexicalCast(result, s.utf8Ptr(), s.utf8Ptr() + s.utf8Size());
+}
+
+template
+Maybe maybeLexicalCast(const char* first, const char* last) {
+ Type result{};
+ if (tryLexicalCast(result, first, last))
+ return result;
+ else
return {};
+}
- // Confirm that we read everything out of the stream
- char ch;
- if (stream >> ch)
- return {};
+template
+Maybe maybeLexicalCast(StringView s) {
+ return maybeLexicalCast(s.utf8Ptr(), s.utf8Ptr() + s.utf8Size());
+}
+template
+Type lexicalCast(const char* first, const char* last) {
+ Type result{};
+ auto res = fast_float::from_chars(first, last, result);
+ if ((res.ec != std::errc() && res.ec != std::errc::result_out_of_range) || res.ptr != last)
+ throwLexicalCastError(res.ec, first, last);
+
return result;
}
+template <>
+bool lexicalCast(const char* first, const char* last);
+
template
-Type lexicalCast(StringView s, std::ios_base::fmtflags flags = std::ios_base::boolalpha) {
- auto m = maybeLexicalCast(s, flags);
- if (m)
- return m.take();
- else
- throw BadLexicalCast(strf("Lexical cast failed on '{}'", s));
+Type lexicalCast(StringView s) {
+ return lexicalCast(s.utf8Ptr(), s.utf8Ptr() + s.utf8Size());
}
}
diff --git a/source/core/StarLua.cpp b/source/core/StarLua.cpp
index 5a2effa..91aebab 100644
--- a/source/core/StarLua.cpp
+++ b/source/core/StarLua.cpp
@@ -537,8 +537,16 @@ LuaContext LuaEngine::createContext() {
LuaDetail::shallowCopy(m_state, -1, -2);
lua_pop(m_state, 1);
+ auto context = LuaContext(LuaDetail::LuaHandle(RefPtr(this), popHandle(m_state)));
+ // Add loadstring
+ auto handleIndex = context.handleIndex();
+ context.set("loadstring", createFunction([this, handleIndex](String const& source, Maybe const& name, Maybe const& env) -> LuaFunction {
+ String functionName = name ? strf("loadstring: {}", *name) : "loadstring";
+ return createFunctionFromSource(env ? env->handleIndex() : handleIndex, source.utf8Ptr(), source.utf8Size(), functionName.utf8Ptr());
+ }));
+
// Then set that environment as the new context environment in the registry.
- return LuaContext(LuaDetail::LuaHandle(RefPtr(this), popHandle(m_state)));
+ return context;
}
void LuaEngine::collectGarbage(Maybe steps) {
diff --git a/source/core/StarLuaConverters.hpp b/source/core/StarLuaConverters.hpp
index d9dfc67..c79cbdc 100644
--- a/source/core/StarLuaConverters.hpp
+++ b/source/core/StarLuaConverters.hpp
@@ -168,11 +168,11 @@ struct LuaConverter> {
template
struct LuaConverter> {
static LuaValue from(LuaEngine& engine, Variant const& variant) {
- return variant.call([&engine](auto const& a) { return luaFrom(engine, a); });
+ return variant.call([&engine](auto const& a) { return engine.luaFrom(a); });
}
static LuaValue from(LuaEngine& engine, Variant&& variant) {
- return variant.call([&engine](auto& a) { return luaFrom(engine, std::move(a)); });
+ return variant.call([&engine](auto& a) { return engine.luaFrom(std::move(a)); });
}
static Maybe> to(LuaEngine& engine, LuaValue const& v) {
@@ -217,7 +217,7 @@ struct LuaConverter> {
static LuaValue from(LuaEngine& engine, MVariant const& variant) {
LuaValue value;
variant.call([&value, &engine](auto const& a) {
- value = luaFrom(engine, a);
+ value = engine.luaFrom(a);
});
return value;
}
@@ -225,7 +225,7 @@ struct LuaConverter> {
static LuaValue from(LuaEngine& engine, MVariant&& variant) {
LuaValue value;
variant.call([&value, &engine](auto& a) {
- value = luaFrom(engine, std::move(a));
+ value = engine.luaFrom(std::move(a));
});
return value;
}
diff --git a/source/core/StarMathCommon.hpp b/source/core/StarMathCommon.hpp
index 1627edb..3890403 100644
--- a/source/core/StarMathCommon.hpp
+++ b/source/core/StarMathCommon.hpp
@@ -10,11 +10,11 @@ namespace Star {
STAR_EXCEPTION(MathException, StarException);
namespace Constants {
- double const pi = 3.14159265358979323846;
- double const rad2deg = 57.2957795130823208768;
- double const deg2rad = 1 / rad2deg;
- double const sqrt2 = 1.41421356237309504880;
- double const log2e = 1.44269504088896340736;
+ double constexpr pi = 3.14159265358979323846;
+ double constexpr rad2deg = 57.2957795130823208768;
+ double constexpr deg2rad = 1 / rad2deg;
+ double constexpr sqrt2 = 1.41421356237309504880;
+ double constexpr log2e = 1.44269504088896340736;
}
// Really common std namespace includes, and replacements for std libraries
diff --git a/source/core/StarMemory.cpp b/source/core/StarMemory.cpp
index 6ecc509..dc13d08 100644
--- a/source/core/StarMemory.cpp
+++ b/source/core/StarMemory.cpp
@@ -51,7 +51,7 @@ namespace Star {
void free(void* ptr, size_t size) {
if (ptr)
- ::sdallocx(ptr, size, 0);
+ je_sdallocx(ptr, size, 0);
}
#endif
#elif STAR_USE_MIMALLOC
diff --git a/source/core/StarNetCompatibility.cpp b/source/core/StarNetCompatibility.cpp
new file mode 100644
index 0000000..94e1ca5
--- /dev/null
+++ b/source/core/StarNetCompatibility.cpp
@@ -0,0 +1,7 @@
+#include "StarNetCompatibility.hpp"
+
+namespace Star {
+
+VersionNumber const OpenProtocolVersion = 3;
+
+}
\ No newline at end of file
diff --git a/source/core/StarNetCompatibility.hpp b/source/core/StarNetCompatibility.hpp
new file mode 100644
index 0000000..4b950ab
--- /dev/null
+++ b/source/core/StarNetCompatibility.hpp
@@ -0,0 +1,55 @@
+#pragma once
+#include "StarVersion.hpp"
+#include "StarHash.hpp"
+
+namespace Star {
+
+extern VersionNumber const OpenProtocolVersion;
+
+constexpr VersionNumber AnyVersion = 0xFFFFFFFF;
+constexpr VersionNumber LegacyVersion = 0;
+
+class NetCompatibilityRules {
+public:
+ NetCompatibilityRules();
+ NetCompatibilityRules(uint64_t) = delete;
+ NetCompatibilityRules(VersionNumber version);
+
+ VersionNumber version() const;
+ void setVersion(VersionNumber version);
+ bool isLegacy() const;
+
+ bool operator==(NetCompatibilityRules const& a) const;
+
+private:
+ VersionNumber m_version = OpenProtocolVersion;
+};
+
+inline NetCompatibilityRules::NetCompatibilityRules() : m_version(OpenProtocolVersion) {}
+
+inline NetCompatibilityRules::NetCompatibilityRules(VersionNumber v) : m_version(v) {}
+
+inline VersionNumber NetCompatibilityRules::version() const {
+ return m_version;
+}
+
+inline void NetCompatibilityRules::setVersion(VersionNumber version) {
+ m_version = version;
+}
+
+inline bool NetCompatibilityRules::isLegacy() const {
+ return m_version == LegacyVersion;
+}
+
+inline bool NetCompatibilityRules::operator==(NetCompatibilityRules const& a) const {
+ return m_version == a.m_version;
+}
+
+template <>
+struct hash {
+ size_t operator()(NetCompatibilityRules const& s) const {
+ return s.version();
+ }
+};
+
+}
\ No newline at end of file
diff --git a/source/core/StarNetElement.cpp b/source/core/StarNetElement.cpp
index 914badd..343d45a 100644
--- a/source/core/StarNetElement.cpp
+++ b/source/core/StarNetElement.cpp
@@ -7,8 +7,8 @@ uint64_t NetElementVersion::current() const {
return m_version;
}
-void NetElementVersion::increment() {
- ++m_version;
+uint64_t NetElementVersion::increment() {
+ return ++m_version;
}
void NetElement::enableNetInterpolation(float) {}
diff --git a/source/core/StarNetElement.hpp b/source/core/StarNetElement.hpp
index dd909b8..7d73b85 100644
--- a/source/core/StarNetElement.hpp
+++ b/source/core/StarNetElement.hpp
@@ -9,7 +9,7 @@ namespace Star {
class NetElementVersion {
public:
uint64_t current() const;
- void increment();
+ uint64_t increment();
private:
uint64_t m_version = 0;
@@ -20,15 +20,15 @@ class NetElement {
public:
virtual ~NetElement() = default;
- // A network of NetElements will have a shared monotinically increasing
+ // A network of NetElements will have a shared monotonically increasing
// NetElementVersion. When elements are updated, they will mark the version
// number at the time they are updated so that a delta can be constructed
// that contains only changes since any past version.
virtual void initNetVersion(NetElementVersion const* version = nullptr) = 0;
// Full store / load of the entire element.
- virtual void netStore(DataStream& ds) const = 0;
- virtual void netLoad(DataStream& ds) = 0;
+ virtual void netStore(DataStream& ds, NetCompatibilityRules rules) const = 0;
+ virtual void netLoad(DataStream& ds, NetCompatibilityRules rules) = 0;
// Enables interpolation mode. If interpolation mode is enabled, then
// NetElements will delay presenting incoming delta data for the
@@ -46,14 +46,34 @@ public:
// the version at the time of the *last* call to writeDelta, + 1. If
// fromVersion is 0, this will always write the full state. Should return
// true if a delta was needed and was written to DataStream, false otherwise.
- virtual bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const = 0;
+ virtual bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const = 0;
// Read a delta written by writeNetDelta. 'interpolationTime' is the time in
// the future that data from this delta should be delayed and smoothed into,
// if interpolation is enabled.
- virtual void readNetDelta(DataStream& ds, float interpolationTime = 0.0) = 0;
+ virtual void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) = 0;
// When extrapolating, it is important to notify when a delta WOULD have been
// received even if no deltas are produced, so no extrapolation takes place.
virtual void blankNetDelta(float interpolationTime);
+
+ VersionNumber compatibilityVersion() const;
+ void setCompatibilityVersion(VersionNumber version);
+ bool checkWithRules(NetCompatibilityRules const& rules) const;
+private:
+ VersionNumber m_netCompatibilityVersion = AnyVersion;
};
+inline VersionNumber NetElement::compatibilityVersion() const {
+ return m_netCompatibilityVersion;
+}
+
+inline void NetElement::setCompatibilityVersion(VersionNumber version) {
+ m_netCompatibilityVersion = version;
+}
+
+inline bool NetElement::checkWithRules(NetCompatibilityRules const& rules) const {
+ if (m_netCompatibilityVersion != AnyVersion)
+ return rules.version() >= m_netCompatibilityVersion;
+ return true;
+}
+
}
diff --git a/source/core/StarNetElementBasicFields.cpp b/source/core/StarNetElementBasicFields.cpp
index bbb5c5b..618c4a6 100644
--- a/source/core/StarNetElementBasicFields.cpp
+++ b/source/core/StarNetElementBasicFields.cpp
@@ -49,8 +49,8 @@ void NetElementEvent::setIgnoreOccurrencesOnNetLoad(bool ignoreOccurrencesOnNetL
m_ignoreOccurrencesOnNetLoad = ignoreOccurrencesOnNetLoad;
}
-void NetElementEvent::netLoad(DataStream& ds) {
- NetElementUInt::netLoad(ds);
+void NetElementEvent::netLoad(DataStream& ds, NetCompatibilityRules rules) {
+ NetElementUInt::netLoad(ds, rules);
if (m_ignoreOccurrencesOnNetLoad)
ignoreOccurrences();
}
diff --git a/source/core/StarNetElementBasicFields.hpp b/source/core/StarNetElementBasicFields.hpp
index 693c6c3..3b46cc0 100644
--- a/source/core/StarNetElementBasicFields.hpp
+++ b/source/core/StarNetElementBasicFields.hpp
@@ -38,11 +38,11 @@ public:
void disableNetInterpolation() override;
void tickNetInterpolation(float dt) override;
- void netStore(DataStream& ds) const override;
- void netLoad(DataStream& ds) override;
+ void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override;
+ void netLoad(DataStream& ds, NetCompatibilityRules rules) override;
- bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override;
- void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override;
+ bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override;
+ void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override;
protected:
virtual void readData(DataStream& ds, T& t) const = 0;
@@ -107,7 +107,7 @@ public:
void ignoreOccurrences();
void setIgnoreOccurrencesOnNetLoad(bool ignoreOccurrencesOnNetLoad);
- void netLoad(DataStream& ds) override;
+ void netLoad(DataStream& ds, NetCompatibilityRules rules) override;
protected:
void updated() override;
@@ -211,7 +211,8 @@ void NetElementBasicField::tickNetInterpolation(float dt) {
}
template
-void NetElementBasicField::netStore(DataStream& ds) const {
+void NetElementBasicField::netStore(DataStream& ds, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return;
if (m_pendingInterpolatedValues && !m_pendingInterpolatedValues->empty())
writeData(ds, m_pendingInterpolatedValues->last().second);
else
@@ -219,7 +220,8 @@ void NetElementBasicField::netStore(DataStream& ds) const {
}
template
-void NetElementBasicField::netLoad(DataStream& ds) {
+void NetElementBasicField::netLoad(DataStream& ds, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
readData(ds, m_value);
m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0;
updated();
@@ -228,7 +230,8 @@ void NetElementBasicField::netLoad(DataStream& ds) {
}
template
-bool NetElementBasicField::writeNetDelta(DataStream& ds, uint64_t fromVersion) const {
+bool NetElementBasicField::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return false;
if (m_latestUpdateVersion < fromVersion)
return false;
@@ -236,11 +239,13 @@ bool NetElementBasicField::writeNetDelta(DataStream& ds, uint64_t fromVersion
writeData(ds, m_pendingInterpolatedValues->last().second);
else
writeData(ds, m_value);
+
return true;
}
template
-void NetElementBasicField::readNetDelta(DataStream& ds, float interpolationTime) {
+void NetElementBasicField::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
T t;
readData(ds, t);
m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0;
diff --git a/source/core/StarNetElementContainers.hpp b/source/core/StarNetElementContainers.hpp
index 62cce86..2e16b30 100644
--- a/source/core/StarNetElementContainers.hpp
+++ b/source/core/StarNetElementContainers.hpp
@@ -26,11 +26,12 @@ public:
void disableNetInterpolation() override;
void tickNetInterpolation(float dt) override;
- void netStore(DataStream& ds) const override;
- void netLoad(DataStream& ds) override;
+ void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override;
+ void netLoad(DataStream& ds, NetCompatibilityRules rules) override;
- bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override;
- void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override;
+ bool shouldWriteNetDelta(uint64_t fromVersion, NetCompatibilityRules rules = {}) const;
+ bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override;
+ void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override;
mapped_type const& get(key_type const& key) const;
mapped_type const* ptr(key_type const& key) const;
@@ -77,6 +78,8 @@ public:
template
void setContents(MapType const& values);
+ uint64_t changeDataLastVersion() const;
+
private:
// If a delta is written from further back than this many steps, the delta
// will fall back to a full serialization of the entire state.
@@ -152,7 +155,8 @@ void NetElementMapWrapper::tickNetInterpolation(float dt) {
}
template
-void NetElementMapWrapper::netStore(DataStream& ds) const {
+void NetElementMapWrapper::netStore(DataStream& ds, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return;
ds.writeVlqU(BaseMap::size() + m_pendingChangeData.size());
for (auto const& pair : *this)
writeChange(ds, SetChange{pair.first, pair.second});
@@ -162,7 +166,8 @@ void NetElementMapWrapper::netStore(DataStream& ds) const {
}
template
-void NetElementMapWrapper::netLoad(DataStream& ds) {
+void NetElementMapWrapper::netLoad(DataStream& ds, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
m_changeData.clear();
m_changeDataLastVersion = m_netVersion ? m_netVersion->current() : 0;
m_pendingChangeData.clear();
@@ -181,13 +186,27 @@ void NetElementMapWrapper::netLoad(DataStream& ds) {
}
template
-bool NetElementMapWrapper::writeNetDelta(DataStream& ds, uint64_t fromVersion) const {
+bool NetElementMapWrapper::shouldWriteNetDelta(uint64_t fromVersion, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return false;
+ if (fromVersion < m_changeDataLastVersion)
+ return true;
+
+ for (auto const& p : m_changeData)
+ if (p.first >= fromVersion)
+ return true;
+
+ return false;
+}
+
+template
+bool NetElementMapWrapper::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return false;
bool deltaWritten = false;
if (fromVersion < m_changeDataLastVersion) {
deltaWritten = true;
ds.writeVlqU(1);
- netStore(ds);
+ netStore(ds, rules);
} else {
for (auto const& p : m_changeData) {
@@ -206,13 +225,14 @@ bool NetElementMapWrapper::writeNetDelta(DataStream& ds, uint64_t fromV
}
template
-void NetElementMapWrapper::readNetDelta(DataStream& ds, float interpolationTime) {
+void NetElementMapWrapper::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
while (true) {
uint64_t code = ds.readVlqU();
if (code == 0) {
break;
} else if (code == 1) {
- netLoad(ds);
+ netLoad(ds, rules);
} else if (code == 2) {
auto change = readChange(ds);
addChangeData(change);
@@ -381,6 +401,11 @@ void NetElementMapWrapper::setContents(MapType const& values) {
reset(BaseMap::from(values));
}
+template
+uint64_t NetElementMapWrapper::changeDataLastVersion() const {
+ return m_changeDataLastVersion;
+}
+
template
void NetElementMapWrapper::writeChange(DataStream& ds, ElementChange const& change) {
if (auto sc = change.template ptr()) {
diff --git a/source/core/StarNetElementDynamicGroup.hpp b/source/core/StarNetElementDynamicGroup.hpp
index 4f3a2d3..dbd0c12 100644
--- a/source/core/StarNetElementDynamicGroup.hpp
+++ b/source/core/StarNetElementDynamicGroup.hpp
@@ -45,11 +45,11 @@ public:
void disableNetInterpolation() override;
void tickNetInterpolation(float dt) override;
- void netStore(DataStream& ds) const override;
- void netLoad(DataStream& ds) override;
+ void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override;
+ void netLoad(DataStream& ds, NetCompatibilityRules rules) override;
- bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override;
- void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override;
+ bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override;
+ void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override;
void blankNetDelta(float interpolationTime = 0.0f) override;
private:
@@ -89,7 +89,7 @@ template
auto NetElementDynamicGroup::addNetElement(ElementPtr element) -> ElementId {
readyElement(element);
DataStreamBuffer storeBuffer;
- element->netStore(storeBuffer);
+ element->netStore(storeBuffer, {});
auto id = m_idMap.add(std::move(element));
addChangeData(ElementAddition(id, storeBuffer.takeData()));
@@ -134,7 +134,7 @@ void NetElementDynamicGroup::initNetVersion(NetElementVersion const* ve
for (auto& pair : m_idMap) {
pair.second->initNetVersion(m_netVersion);
DataStreamBuffer storeBuffer;
- pair.second->netStore(storeBuffer);
+ pair.second->netStore(storeBuffer, {});
addChangeData(ElementAddition(pair.first, storeBuffer.takeData()));
}
}
@@ -162,19 +162,22 @@ void NetElementDynamicGroup::tickNetInterpolation(float dt) {
}
template
-void NetElementDynamicGroup::netStore(DataStream& ds) const {
+void NetElementDynamicGroup::netStore(DataStream& ds, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return;
ds.writeVlqU(m_idMap.size());
+ m_buffer.setStreamCompatibilityVersion(rules);
for (auto& pair : m_idMap) {
ds.writeVlqU(pair.first);
- pair.second->netStore(m_buffer);
+ pair.second->netStore(m_buffer, rules);
ds.write(m_buffer.data());
m_buffer.clear();
}
}
template
-void NetElementDynamicGroup::netLoad(DataStream& ds) {
+void NetElementDynamicGroup::netLoad(DataStream& ds, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
m_changeData.clear();
m_changeDataLastVersion = m_netVersion ? m_netVersion->current() : 0;
m_idMap.clear();
@@ -188,7 +191,7 @@ void NetElementDynamicGroup::netLoad(DataStream& ds) {
DataStreamBuffer storeBuffer(ds.read());
ElementPtr element = make_shared();
- element->netLoad(storeBuffer);
+ element->netLoad(storeBuffer, rules);
readyElement(element);
m_idMap.add(id, std::move(element));
@@ -197,10 +200,11 @@ void NetElementDynamicGroup::netLoad(DataStream& ds) {
}
template
-bool NetElementDynamicGroup::writeNetDelta(DataStream& ds, uint64_t fromVersion) const {
+bool NetElementDynamicGroup::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return false;
if (fromVersion < m_changeDataLastVersion) {
ds.write(true);
- netStore(ds);
+ netStore(ds, rules);
return true;
} else {
@@ -220,8 +224,9 @@ bool NetElementDynamicGroup::writeNetDelta(DataStream& ds, uint64_t fro
}
}
+ m_buffer.setStreamCompatibilityVersion(rules);
for (auto& p : m_idMap) {
- if (p.second->writeNetDelta(m_buffer, fromVersion)) {
+ if (p.second->writeNetDelta(m_buffer, fromVersion, rules)) {
willWrite();
ds.writeVlqU(p.first + 1);
ds.writeBytes(m_buffer.data());
@@ -237,10 +242,11 @@ bool NetElementDynamicGroup::writeNetDelta(DataStream& ds, uint64_t fro
}
template
-void NetElementDynamicGroup::readNetDelta(DataStream& ds, float interpolationTime) {
+void NetElementDynamicGroup::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
bool isFull = ds.read();
if (isFull) {
- netLoad(ds);
+ netLoad(ds, rules);
} else {
while (true) {
uint64_t code = ds.readVlqU();
@@ -256,7 +262,7 @@ void NetElementDynamicGroup::readNetDelta(DataStream& ds, float interpo
} else if (auto addition = changeUpdate.template ptr()) {
ElementPtr element = make_shared();
DataStreamBuffer storeBuffer(std::move(get<1>(*addition)));
- element->netLoad(storeBuffer);
+ element->netLoad(storeBuffer, rules);
readyElement(element);
m_idMap.add(get<0>(*addition), std::move(element));
} else if (auto removal = changeUpdate.template ptr()) {
@@ -265,7 +271,7 @@ void NetElementDynamicGroup::readNetDelta(DataStream& ds, float interpo
} else {
ElementId elementId = code - 1;
auto const& element = m_idMap.get(elementId);
- element->readNetDelta(ds, interpolationTime);
+ element->readNetDelta(ds, interpolationTime, rules);
if (m_interpolationEnabled)
m_receivedDeltaIds.add(elementId);
}
diff --git a/source/core/StarNetElementExt.hpp b/source/core/StarNetElementExt.hpp
new file mode 100644
index 0000000..50802fe
--- /dev/null
+++ b/source/core/StarNetElementExt.hpp
@@ -0,0 +1,85 @@
+#pragma once
+
+#include "StarNetElement.hpp"
+
+namespace Star {
+
+template
+class NetElementOverride : public BaseNetElement {
+public:
+ void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override;
+ void netLoad(DataStream& ds, NetCompatibilityRules rules) override;
+
+ bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override;
+ void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override;
+
+ typedef std::function NetStorer;
+ typedef std::function NetLoader;
+ typedef std::function NetDeltaWriter;
+ typedef std::function NetDeltaReader;
+
+ void setNetStorer(NetStorer);
+ void setNetLoader(NetLoader);
+ void setNetDeltaWriter(NetDeltaWriter);
+ void setNetDeltaReader(NetDeltaReader);
+ void setOverrides(NetStorer netStorer, NetLoader netLoader,
+ NetDeltaWriter netDeltaWriter, NetDeltaReader netDeltaReader);
+
+private:
+ NetStorer m_netStorer;
+ NetLoader m_netLoader;
+
+ NetDeltaWriter m_netDeltaWriter;
+ NetDeltaReader m_netDeltaReader;
+};
+
+template
+void NetElementOverride::netStore(DataStream& ds, NetCompatibilityRules rules) const {
+ if (m_netStorer)
+ m_netStorer(ds, rules);
+ else
+ BaseNetElement::netStore(ds, rules);
+}
+
+template
+void NetElementOverride::netLoad(DataStream& ds, NetCompatibilityRules rules) {
+ if (m_netLoader)
+ m_netLoader(ds, rules);
+ else
+ BaseNetElement::netLoad(ds, rules);
+}
+
+template
+bool NetElementOverride::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const {
+ if (m_netDeltaWriter)
+ return m_netDeltaWriter(ds, fromVersion, rules);
+ else
+ return BaseNetElement::writeNetDelta(ds, fromVersion, rules);
+}
+
+template
+void NetElementOverride::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) {
+ if (m_netDeltaReader)
+ m_netDeltaReader(ds, interpolationTime, rules);
+ else
+ BaseNetElement::readNetDelta(ds, interpolationTime, rules);
+}
+
+template
+inline void NetElementOverride::setNetStorer(NetStorer f) { m_netStorer = std::move(f); }
+template
+inline void NetElementOverride::setNetLoader(NetLoader f) { m_netLoader = std::move(f); }
+template
+inline void NetElementOverride::setNetDeltaWriter(NetDeltaWriter f) { m_netDeltaWriter = std::move(f); }
+template
+inline void NetElementOverride::setNetDeltaReader(NetDeltaReader f) { m_netDeltaReader = std::move(f); }
+
+template
+inline void NetElementOverride::setOverrides(NetStorer netStorer, NetLoader netLoader, NetDeltaWriter netDeltaWriter, NetDeltaReader netDeltaReader) {
+ m_netStorer = std::move(netStorer);
+ m_netLoader = std::move(netLoader);
+ m_netDeltaWriter = std::move(netDeltaWriter);
+ m_netDeltaReader = std::move(netDeltaReader);
+}
+
+}
\ No newline at end of file
diff --git a/source/core/StarNetElementFloatFields.hpp b/source/core/StarNetElementFloatFields.hpp
index 57f74a9..97cacb7 100644
--- a/source/core/StarNetElementFloatFields.hpp
+++ b/source/core/StarNetElementFloatFields.hpp
@@ -36,11 +36,11 @@ public:
void disableNetInterpolation() override;
void tickNetInterpolation(float dt) override;
- void netStore(DataStream& ds) const override;
- void netLoad(DataStream& ds) override;
+ void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override;
+ void netLoad(DataStream& ds, NetCompatibilityRules rules) override;
- bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override;
- void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override;
+ bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override;
+ void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override;
void blankNetDelta(float interpolationTime = 0.0f) override;
private:
@@ -131,7 +131,8 @@ void NetElementFloating::tickNetInterpolation(float dt) {
}
template
-void NetElementFloating::netStore(DataStream& ds) const {
+void NetElementFloating::netStore(DataStream& ds, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return;
if (m_interpolationDataPoints)
writeValue(ds, m_interpolationDataPoints->last().second);
else
@@ -139,7 +140,8 @@ void NetElementFloating::netStore(DataStream& ds) const {
}
template
-void NetElementFloating::netLoad(DataStream& ds) {
+void NetElementFloating::netLoad(DataStream& ds, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
m_value = readValue(ds);
m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0;
if (m_interpolationDataPoints) {
@@ -149,7 +151,8 @@ void NetElementFloating::netLoad(DataStream& ds) {
}
template
-bool NetElementFloating::writeNetDelta(DataStream& ds, uint64_t fromVersion) const {
+bool NetElementFloating::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return false;
if (m_latestUpdateVersion < fromVersion)
return false;
@@ -162,7 +165,8 @@ bool NetElementFloating::writeNetDelta(DataStream& ds, uint64_t fromVersion)
}
template
-void NetElementFloating::readNetDelta(DataStream& ds, float interpolationTime) {
+void NetElementFloating::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) {
+ _unused(rules);
T t = readValue(ds);
m_latestUpdateVersion = m_netVersion ? m_netVersion->current() : 0;
diff --git a/source/core/StarNetElementGroup.cpp b/source/core/StarNetElementGroup.cpp
index 249c978..6626e2b 100644
--- a/source/core/StarNetElementGroup.cpp
+++ b/source/core/StarNetElementGroup.cpp
@@ -21,14 +21,18 @@ void NetElementGroup::initNetVersion(NetElementVersion const* version) {
p.first->initNetVersion(m_version);
}
-void NetElementGroup::netStore(DataStream& ds) const {
+void NetElementGroup::netStore(DataStream& ds, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return;
for (auto& p : m_elements)
- p.first->netStore(ds);
+ if (p.first->checkWithRules(rules))
+ p.first->netStore(ds, rules);
}
-void NetElementGroup::netLoad(DataStream& ds) {
+void NetElementGroup::netLoad(DataStream& ds, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
for (auto& p : m_elements)
- p.first->netLoad(ds);
+ if (p.first->checkWithRules(rules))
+ p.first->netLoad(ds, rules);
}
void NetElementGroup::enableNetInterpolation(float extrapolationHint) {
@@ -56,17 +60,23 @@ void NetElementGroup::tickNetInterpolation(float dt) {
}
}
-bool NetElementGroup::writeNetDelta(DataStream& ds, uint64_t fromStep) const {
+bool NetElementGroup::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return false;
if (m_elements.size() == 0) {
return false;
} else if (m_elements.size() == 1) {
- return m_elements[0].first->writeNetDelta(ds, fromStep);
+ return m_elements[0].first->writeNetDelta(ds, fromVersion, rules);
} else {
bool deltaWritten = false;
- for (uint64_t i = 0; i < m_elements.size(); ++i) {
- if (m_elements[i].first->writeNetDelta(m_buffer, fromStep)) {
+ uint64_t i = 0;
+ m_buffer.setStreamCompatibilityVersion(rules);
+ for (auto& element : m_elements) {
+ if (!element.first->checkWithRules(rules))
+ continue;
+ ++i;
+ if (element.first->writeNetDelta(m_buffer, fromVersion, rules)) {
deltaWritten = true;
- ds.writeVlqU(i + 1);
+ ds.writeVlqU(i);
ds.writeBytes(m_buffer.data());
m_buffer.clear();
}
@@ -77,23 +87,28 @@ bool NetElementGroup::writeNetDelta(DataStream& ds, uint64_t fromStep) const {
}
}
-void NetElementGroup::readNetDelta(DataStream& ds, float interpolationTime) {
+void NetElementGroup::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
if (m_elements.size() == 0) {
throw IOException("readNetDelta called on empty NetElementGroup");
} else if (m_elements.size() == 1) {
- m_elements[0].first->readNetDelta(ds, interpolationTime);
+ m_elements[0].first->readNetDelta(ds, interpolationTime, rules);
} else {
uint64_t readIndex = ds.readVlqU();
- for (uint64_t i = 0; i < m_elements.size(); ++i) {
+ uint64_t i = 0;
+ for (auto& element : m_elements) {
+ if (!element.first->checkWithRules(rules))
+ continue;
if (readIndex == 0 || readIndex - 1 > i) {
if (m_interpolationEnabled)
m_elements[i].first->blankNetDelta(interpolationTime);
} else if (readIndex - 1 == i) {
- m_elements[i].first->readNetDelta(ds, interpolationTime);
+ m_elements[i].first->readNetDelta(ds, interpolationTime, rules);
readIndex = ds.readVlqU();
} else {
throw IOException("group indexes out of order in NetElementGroup::readNetDelta");
}
+ ++i;
}
}
}
diff --git a/source/core/StarNetElementGroup.hpp b/source/core/StarNetElementGroup.hpp
index fd1bf58..1904a33 100644
--- a/source/core/StarNetElementGroup.hpp
+++ b/source/core/StarNetElementGroup.hpp
@@ -24,15 +24,15 @@ public:
void initNetVersion(NetElementVersion const* version = nullptr) override;
- void netStore(DataStream& ds) const override;
- void netLoad(DataStream& ds) override;
+ void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override;
+ void netLoad(DataStream& ds, NetCompatibilityRules rules) override;
void enableNetInterpolation(float extrapolationHint = 0.0f) override;
void disableNetInterpolation() override;
void tickNetInterpolation(float dt) override;
- bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override;
- void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override;
+ bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override;
+ void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override;
void blankNetDelta(float interpolationTime) override;
NetElementVersion const* netVersion() const;
diff --git a/source/core/StarNetElementSignal.hpp b/source/core/StarNetElementSignal.hpp
index 9faa127..61a108d 100644
--- a/source/core/StarNetElementSignal.hpp
+++ b/source/core/StarNetElementSignal.hpp
@@ -16,15 +16,15 @@ public:
void initNetVersion(NetElementVersion const* version = nullptr) override;
- void netStore(DataStream& ds) const override;
- void netLoad(DataStream& ds) override;
+ void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override;
+ void netLoad(DataStream& ds, NetCompatibilityRules rules) override;
void enableNetInterpolation(float extrapolationHint = 0.0f) override;
void disableNetInterpolation() override;
void tickNetInterpolation(float dt) override;
- bool writeNetDelta(DataStream& ds, uint64_t fromVersion) const override;
- void readNetDelta(DataStream& ds, float interpolationTime = 0.0) override;
+ bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override;
+ void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override;
void send(Signal signal);
List receive();
@@ -55,10 +55,10 @@ void NetElementSignal::initNetVersion(NetElementVersion const* version)
}
template
-void NetElementSignal::netStore(DataStream&) const {}
+void NetElementSignal::netStore(DataStream&, NetCompatibilityRules) const {}
template
-void NetElementSignal::netLoad(DataStream&) {
+void NetElementSignal::netLoad(DataStream&, NetCompatibilityRules) {
}
template
@@ -83,7 +83,8 @@ void NetElementSignal::tickNetInterpolation(float dt) {
}
template
-bool NetElementSignal::writeNetDelta(DataStream& ds, uint64_t fromVersion) const {
+bool NetElementSignal::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return false;
size_t numToWrite = 0;
for (auto const& p : m_signals) {
if (p.version >= fromVersion)
@@ -103,7 +104,8 @@ bool NetElementSignal::writeNetDelta(DataStream& ds, uint64_t fromVersio
}
template
-void NetElementSignal::readNetDelta(DataStream& ds, float interpolationTime) {
+void NetElementSignal::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
size_t numToRead = ds.readVlqU();
for (size_t i = 0; i < numToRead; ++i) {
Signal s;
diff --git a/source/core/StarNetElementSyncGroup.cpp b/source/core/StarNetElementSyncGroup.cpp
index ac8da92..cd98638 100644
--- a/source/core/StarNetElementSyncGroup.cpp
+++ b/source/core/StarNetElementSyncGroup.cpp
@@ -26,23 +26,27 @@ void NetElementSyncGroup::tickNetInterpolation(float dt) {
}
}
-void NetElementSyncGroup::netStore(DataStream& ds) const {
+void NetElementSyncGroup::netStore(DataStream& ds, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return;
const_cast(this)->netElementsNeedStore();
- return NetElementGroup::netStore(ds);
+ return NetElementGroup::netStore(ds, rules);
}
-void NetElementSyncGroup::netLoad(DataStream& ds) {
- NetElementGroup::netLoad(ds);
+void NetElementSyncGroup::netLoad(DataStream& ds, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
+ NetElementGroup::netLoad(ds, rules);
netElementsNeedLoad(true);
}
-bool NetElementSyncGroup::writeNetDelta(DataStream& ds, uint64_t fromVersion) const {
+bool NetElementSyncGroup::writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules) const {
+ if (!checkWithRules(rules)) return false;
const_cast(this)->netElementsNeedStore();
- return NetElementGroup::writeNetDelta(ds, fromVersion);
+ return NetElementGroup::writeNetDelta(ds, fromVersion, rules);
}
-void NetElementSyncGroup::readNetDelta(DataStream& ds, float interpolationTime) {
- NetElementGroup::readNetDelta(ds, interpolationTime);
+void NetElementSyncGroup::readNetDelta(DataStream& ds, float interpolationTime, NetCompatibilityRules rules) {
+ if (!checkWithRules(rules)) return;
+ NetElementGroup::readNetDelta(ds, interpolationTime, rules);
m_hasRecentChanges = true;
m_recentDeltaTime = interpolationTime;
diff --git a/source/core/StarNetElementSyncGroup.hpp b/source/core/StarNetElementSyncGroup.hpp
index b01200b..30dafe8 100644
--- a/source/core/StarNetElementSyncGroup.hpp
+++ b/source/core/StarNetElementSyncGroup.hpp
@@ -13,11 +13,11 @@ public:
void disableNetInterpolation() override;
void tickNetInterpolation(float dt) override;
- void netStore(DataStream& ds) const override;
- void netLoad(DataStream& ds) override;
+ void netStore(DataStream& ds, NetCompatibilityRules rules = {}) const override;
+ void netLoad(DataStream& ds, NetCompatibilityRules rules) override;
- bool writeNetDelta(DataStream& ds, uint64_t fromStep) const override;
- void readNetDelta(DataStream& ds, float interpolationTime = 0.0f) override;
+ bool writeNetDelta(DataStream& ds, uint64_t fromVersion, NetCompatibilityRules rules = {}) const override;
+ void readNetDelta(DataStream& ds, float interpolationTime = 0.0f, NetCompatibilityRules rules = {}) override;
void blankNetDelta(float interpolationTime = 0.0f) override;
protected:
diff --git a/source/core/StarNetElementTop.hpp b/source/core/StarNetElementTop.hpp
index 454b34f..cf3cfb4 100644
--- a/source/core/StarNetElementTop.hpp
+++ b/source/core/StarNetElementTop.hpp
@@ -1,5 +1,4 @@
-#ifndef STAR_NET_ELEMENT_TOP_HPP
-#define STAR_NET_ELEMENT_TOP_HPP
+#pragma once
#include "StarNetElement.hpp"
@@ -12,10 +11,11 @@ class NetElementTop : public BaseNetElement {
public:
NetElementTop();
- // Returns the state update, combined with the version code that should be
- // passed to the next call to writeState. If 'fromVersion' is 0, then this
- // is a full write for an initial read of a slave NetElementTop.
- pair writeNetState(uint64_t fromVersion = 0);
+ // Writes the state update to the given DataStream then returns the version
+ // code that should be passed to the next call to writeState. If
+ // 'fromVersion' is 0, then this is a full write for an initial read of a
+ // slave NetElementTop.
+ pair writeNetState(uint64_t fromVersion = 0, NetCompatibilityRules rules = {});
// Reads a state produced by a call to writeState, optionally with the
// interpolation delay time for the data contained in this state update. If
// the state is a full update rather than a delta, the interoplation delay
@@ -23,7 +23,7 @@ public:
// readState, *unless* extrapolation is enabled. If extrapolation is
// enabled, reading a blank update calls 'blankNetDelta' which is necessary
// to not improperly extrapolate past the end of incoming deltas.
- void readNetState(ByteArray data, float interpolationTime = 0.0);
+ void readNetState(ByteArray data, float interpolationTime = 0.0f, NetCompatibilityRules rules = {});
private:
using BaseNetElement::initNetVersion;
@@ -32,6 +32,7 @@ private:
using BaseNetElement::writeNetDelta;
using BaseNetElement::readNetDelta;
using BaseNetElement::blankNetDelta;
+ using BaseNetElement::checkWithRules;
NetElementVersion m_netVersion;
};
@@ -42,41 +43,34 @@ NetElementTop::NetElementTop() {
}
template
-pair NetElementTop::writeNetState(uint64_t fromVersion) {
+pair NetElementTop::writeNetState(uint64_t fromVersion, NetCompatibilityRules rules) {
+ DataStreamBuffer ds;
+ ds.setStreamCompatibilityVersion(rules);
if (fromVersion == 0) {
- DataStreamBuffer ds;
ds.write(true);
- BaseNetElement::netStore(ds);
- m_netVersion.increment();
- return {ds.takeData(), m_netVersion.current()};
-
+ BaseNetElement::netStore(ds, rules);
+ return {ds.takeData(), m_netVersion.increment()};
} else {
- DataStreamBuffer ds;
ds.write(false);
- if (!BaseNetElement::writeNetDelta(ds, fromVersion)) {
+ if (!BaseNetElement::writeNetDelta(ds, fromVersion, rules))
return {ByteArray(), m_netVersion.current()};
- } else {
- m_netVersion.increment();
- return {ds.takeData(), m_netVersion.current()};
- }
+ else
+ return {ds.takeData(), m_netVersion.increment()};
}
}
template
-void NetElementTop