This commit is contained in:
Evert Prants 2025-01-19 13:11:40 +02:00
commit 3d15c1e3fd
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
348 changed files with 11145 additions and 3649 deletions

262
.github/workflows/build.yml vendored Normal file
View File

@ -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/*

View File

@ -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

View File

@ -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/*

View File

@ -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/*

1
.gitignore vendored
View File

@ -15,6 +15,7 @@ enc_temp_folder/
.cache/ .cache/
/attic/user/ /attic/user/
/attic/chucklefish/ /attic/chucklefish/
/attic/debug/
/tiled/ /tiled/
/assets/user/ /assets/user/
/assets/devel/ /assets/devel/

View File

@ -14,22 +14,26 @@ It is still **work-in-progress**.
## Installation ## 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. 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). 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 ### Nightly Builds
These link directly to the latest build from the [Actions](https://github.com/OpenStarbound/OpenStarbound/actions?query=branch%3Amain) tab. 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): **Windows**
[Installer](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Installer.zip), [Installer](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Windows-Installer.zip),
[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Client.zip), [Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Windows-Client.zip),
[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Server.zip) [Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Windows-Server.zip)
[**Linux**](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_linux/main): **Linux**
[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_linux/main/OpenStarbound-Linux-Client.zip), [Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Linux-Client.zip),
[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_linux/main/OpenStarbound-Linux-Server.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"): **macOS**
[Intel](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main/OpenStarbound-Dev-macOS-Intel.zip), [Intel](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build/main/OpenStarbound-Dev-macOS-Intel.zip),
[ARM](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main/OpenStarbound-Dev-macOS-Silicon.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 ## 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. 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! * These scripts can modify, read, patch and create new assets!
* Lua patch files now exist - **.patch.lua** * Lua patch files now exist - **.patch.lua**
* These can patch JSON assets, as well as images! * 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 ### Bug Fixes
* Invalid character inventories are updated when loading in, allowing players to swap inventory mods with pre-existing characters. * 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 ### 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) * 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) * Character swapping (rewrite from StarExtensions, currently command-only: `/swap name` case-insensitive, only substring required)

View File

@ -3,7 +3,9 @@
"groups": { "groups": {
"camera": { "name": "Camera" }, "camera": { "name": "Camera" },
"voice": { "name": "Voice" }, "voice": { "name": "Voice" },
"building": { "name": "Building" } "building": { "name": "Building" },
"inventory": { "name": "Inventory" },
"editing" : { "name" : "Editing" }
}, },
"name": "Open^#ebd74a;Starbound", "name": "Open^#ebd74a;Starbound",
"binds": { "binds": {
@ -15,6 +17,11 @@
"group": "camera", "group": "camera",
"name": "Zoom In" "name": "Zoom In"
}, },
"takeAll": {
"default": [],
"group": "inventory",
"name": "Take All From Container"
},
"zoomOut": { "zoomOut": {
"default": [{ "default": [{
"type": "key", "type": "key",
@ -42,6 +49,18 @@
"default": [], "default": [],
"group": "building", "group": "building",
"name": "Shrink Building Radius" "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"]
} }
} }
} }

View File

@ -10,5 +10,6 @@
"scissor" : false, "scissor" : false,
"letterbox" : false "letterbox" : false
}, },
"postProcessLayers": [] "postProcessLayers": [],
"postProcessGroups": {}
} }

View File

@ -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

Binary file not shown.

View File

@ -41,6 +41,7 @@
// Change planet name to support the new internal string formatting. // Change planet name to support the new internal string formatting.
"planetNameFormatString" : "- {} -", "planetNameFormatString" : "- {} -",
"planetTextOffset" : [0, 120],
"planetTextStyle" : { "planetTextStyle" : {
"backDirectives" : "border=5;fff;fff?border=1;fff;fff7?multiply=000", "backDirectives" : "border=5;fff;fff?border=1;fff;fff7?multiply=000",
"fontSize" : 24 "fontSize" : 24

Binary file not shown.

Before

Width:  |  Height:  |  Size: 739 B

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 B

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 B

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 B

After

Width:  |  Height:  |  Size: 94 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

After

Width:  |  Height:  |  Size: 285 B

View File

@ -35,7 +35,7 @@ local function finishBind(a, b)
if (type(a) == "table") then if (type(a) == "table") then
snareFinished(a) snareFinished(a)
else else
snareFinished{ type = a, value = b, mods = getMods(value) } snareFinished{ type = a, value = b, mods = getMods(b) }
for i, mod in ipairs(mods) do for i, mod in ipairs(mods) do
mod.active = false mod.active = false
end end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 743 B

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 B

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 B

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 B

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 B

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 B

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -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"
}
}
}
}
}
}

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 B

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 839 B

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 B

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 B

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 B

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 B

After

Width:  |  Height:  |  Size: 140 B

View File

@ -54,6 +54,24 @@
"instrumentSlider" : { "type" : "slider", "position" : [62, 158], "gridImage" : "/interface/optionsmenu/largeselection.png" }, "instrumentSlider" : { "type" : "slider", "position" : [62, 158], "gridImage" : "/interface/optionsmenu/largeselection.png" },
"instrumentLabel" : { "type" : "label", "position" : [32, 158], "value" : "Tunes" }, "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
}
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 B

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 B

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 B

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

@ -0,0 +1,9 @@
{
"createCharButton" : {
"type" : "button",
"base" : "/interface/title/createcharacter.png",
"hover" : "/interface/title/createcharacterover.png",
"position" : [23, 241],
"pressedOffset" : [0, 0]
}
}

View File

@ -4,7 +4,11 @@
// Disables the crafting timer if true. // Disables the crafting timer if true.
"disableTimer" : false, "disableTimer" : false,
// This is only used if the crafting timer is enabled. // This is only used if the crafting timer is enabled.
// This is how many crafts are ran when the crafting timer wraps. // This is how many crafts are ran when the crafting timer wraps.
"craftCount" : 1 "craftCount" : 1,
} }
// This is how many of any item can be crafted at once.
// This is also used by merchants.
"maxSpinCount" : 9999
} }

View File

@ -13,6 +13,12 @@ local function shift(thing, x, y)
return thing return thing
end 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 -- patch function, called by the game
function patch(config) function patch(config)
local layout = config.paneLayout local layout = config.paneLayout
@ -41,10 +47,14 @@ function patch(config)
-- Create hardware cursor toggle -- Create hardware cursor toggle
shift(clone(layout, "multiTextureLabel", "hardwareCursorLabel"), 98, -11).value = "HARDWARE CURSOR" shift(clone(layout, "multiTextureLabel", "hardwareCursorLabel"), 98, -11).value = "HARDWARE CURSOR"
shift(clone(layout, "multiTextureCheckbox", "hardwareCursorCheckbox"), 99, -11) 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.title, 0, 24)
shift(layout.resLabel, 0, 28) shift(layout.resLabel, 0, 28)
shift(layout.resSlider, 0, 28) shift(layout.resSlider, 0, 28)
shift(layout.resValueLabel, 0, 28) shift(layout.resValueLabel, 0, 28)
return config return config
end end

View File

@ -0,0 +1,12 @@
[
{
"op" : "test",
"path" : "/paneLayout/bgShine/position/1",
"value" : -12
},
{
"op" : "replace",
"path" : "/paneLayout/bgShine/position/1",
"value" : -19
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 B

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -12,5 +12,7 @@
"maxWireTrans" : 0.4 "maxWireTrans" : 0.4
}, },
"inventoryFilters" : { "default" : {} },
"swapDance" : null // Set this to a valid dance to trigger on character swap. "swapDance" : null // Set this to a valid dance to trigger on character swap.
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 B

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 168 B

View File

@ -2,7 +2,7 @@ local module = {}
modules.commands = module modules.commands = module
local commands = {} local commands = {}
local function command(name, func) local function register(name, func)
commands[name] = func commands[name] = func
end end
@ -12,8 +12,7 @@ function module.init()
end end
end end
register("run", function(src)
command("run", function(src)
local success, result = pcall(loadstring, src, "/run") local success, result = pcall(loadstring, src, "/run")
if not success then if not success then
return "^#f00;compile error: " .. result return "^#f00;compile error: " .. result
@ -35,4 +34,6 @@ command("run", function(src)
end end
end end
end end
end) end)
module.register = register

View File

@ -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)

View File

@ -1,2 +1,2 @@
require "/scripts/opensb/util/modules.lua" require "/scripts/opensb/util/modules.lua"
modules("/scripts/opensb/player/", {"commands"}) modules("/scripts/opensb/player/", {"commands", "copy_paste"})

View File

@ -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

View File

@ -1,2 +1,2 @@
require "/scripts/opensb/util/modules.lua" require "/scripts/opensb/util/modules.lua"
modules("/scripts/opensb/universeclient/", {"voicemanager"}) modules("/scripts/opensb/universeclient/", {"voicemanager","loadconfig"})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 B

After

Width:  |  Height:  |  Size: 145 B

View File

@ -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. Without metadata: Returns an array with all the asset source paths.
With metadata: Returns a table, key/value being source path/metadata. 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) #### `JsonArray` root.assetPatches(`String` asset)
@ -112,6 +116,131 @@ Sets a configuration value in `/storage/starbound.config` by path.
Returns all recipes. 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.
<details><summary><b>Panes</b></summary>
EscapeDialog<br>
Inventory<br>
Codex<br>
Cockpit<br>
Tech<br>
Songbook<br>
Ai<br>
Popup<br>
Confirmation<br>
JoinRequest<br>
Options<br>
QuestLog<br>
ActionBar<br>
TeamBar<br>
StatusPane<br>
Chat<br>
WireInterface<br>
PlanetText<br>
RadioMessagePopup<br>
CraftingPlain<br>
QuestTracker<br>
MmUpgrade<br>
Collections<br>
</details>
#### `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 # 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. 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. 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.
<details><summary><b>Player States</b></summary>
idle<br>
walk<br>
run<br>
jump<br>
fall<br>
swim<br>
swimIdle<br>
lounge<br>
crouch<br>
teleportIn<br>
teleportOut<br>
</details>
---
#### `List<Json>` 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.
---

View File

@ -51,6 +51,5 @@ cp \
scripts/steam_appid.txt \ scripts/steam_appid.txt \
server_distribution/linux/ server_distribution/linux/
tar -cvf dist.tar dist
tar -cvf client.tar client_distribution tar -cvf client.tar client_distribution
tar -cvf server.tar server_distribution tar -cvf server.tar server_distribution

View File

@ -80,6 +80,8 @@ if(NOT DEFINED STAR_SYSTEM)
set(STAR_SYSTEM "linux") set(STAR_SYSTEM "linux")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") elseif(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
set(STAR_SYSTEM "freebsd") set(STAR_SYSTEM "freebsd")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "NetBSD")
set(STAR_SYSTEM "netbsd")
elseif(UNIX) elseif(UNIX)
set(STAR_SYSTEM "unix") set(STAR_SYSTEM "unix")
else() else()
@ -208,6 +210,8 @@ elseif(STAR_SYSTEM STREQUAL "linux")
set_flag(STAR_SYSTEM_LINUX) set_flag(STAR_SYSTEM_LINUX)
elseif(STAR_SYSTEM STREQUAL "freebsd") elseif(STAR_SYSTEM STREQUAL "freebsd")
set_flag(STAR_SYSTEM_FREEBSD) set_flag(STAR_SYSTEM_FREEBSD)
elseif(STAR_SYSTEM STREQUAL "netbsd")
set_flag(STAR_SYSTEM_NETBSD)
endif() endif()
if(STAR_SYSTEM_FAMILY STREQUAL "windows") if(STAR_SYSTEM_FAMILY STREQUAL "windows")
@ -281,14 +285,14 @@ if(STAR_COMPILER_GNU)
set(CMAKE_C_FLAGS_DEBUG "-g -Og") set(CMAKE_C_FLAGS_DEBUG "-g -Og")
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og") set(CMAKE_CXX_FLAGS_DEBUG "-g -Og")
set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -Ofast") set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math")
set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -Ofast") set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math")
set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -Ofast") set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math")
set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Ofast") set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math")
set(CMAKE_SKIP_BUILD_RPATH TRUE) set(CMAKE_SKIP_BUILD_RPATH TRUE)
@ -312,14 +316,14 @@ elseif(STAR_COMPILER_CLANG)
set(CMAKE_C_FLAGS_DEBUG "-g") set(CMAKE_C_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_DEBUG "-g") set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -Ofast") set(CMAKE_C_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math")
set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -Ofast") set(CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -O3 -ffast-math")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -O3 -ffast-math")
set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -Ofast") set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math")
set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Ofast") set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O3 -ffast-math")
set(CMAKE_SKIP_BUILD_RPATH TRUE) 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_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lpthread -lrt")
set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_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() endif()
# Find all required external libraries, based on build settings... # Find all required external libraries, based on build settings...

View File

@ -46,12 +46,14 @@
"displayName": "Linux x64", "displayName": "Linux x64",
"binaryDir": "${sourceParentDir}/build/linux-release", "binaryDir": "${sourceParentDir}/build/linux-release",
"cacheVariables": { "cacheVariables": {
"CMAKE_BUILD_TYPE": "Release", "CMAKE_BUILD_TYPE": "RelWithDebInfo",
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++",
"VCPKG_TARGET_TRIPLET": "x64-linux-mixed", "VCPKG_TARGET_TRIPLET": "x64-linux-mixed",
"CMAKE_INCLUDE_PATH": "${sourceParentDir}/lib/linux/include", "CMAKE_INCLUDE_PATH": "${sourceParentDir}/lib/linux/include",
"CMAKE_LIBRARY_PATH": "${sourceParentDir}/lib/linux", "CMAKE_LIBRARY_PATH": "${sourceParentDir}/lib/linux",
"STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX": true, "STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX": true,
"STAR_USE_JEMALLOC": true "STAR_USE_JEMALLOC": false
}, },
"vendor": { "vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": { "microsoft.com/VisualStudioSettings/CMake/1.0": {

View File

@ -314,10 +314,12 @@ public:
int height; int height;
SDL_GetWindowSize(m_sdlWindow, &width, &height); SDL_GetWindowSize(m_sdlWindow, &width, &height);
m_windowSize = Vec2U(width, height); m_windowSize = Vec2U(width, height);
#ifdef __APPLE__
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
#endif
m_sdlGlContext = SDL_GL_CreateContext(m_sdlWindow); m_sdlGlContext = SDL_GL_CreateContext(m_sdlWindow);
if (!m_sdlGlContext) if (!m_sdlGlContext)

View File

@ -120,7 +120,7 @@ public:
virtual void set(List<RenderPrimitive>& primitives) = 0; virtual void set(List<RenderPrimitive>& primitives) = 0;
}; };
typedef Variant<bool, int, float, Vec2F, Vec3F, Vec4F> RenderEffectParameter; typedef Variant<float, int, Vec2F, Vec3F, Vec4F, bool> RenderEffectParameter;
class Renderer { class Renderer {
public: public:
@ -141,6 +141,9 @@ public:
// The effect config will specify named parameters and textures which can be // The effect config will specify named parameters and textures which can be
// set here. // set here.
virtual void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) = 0; 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<RenderEffectParameter> getEffectScriptableParameter(String const& effectName, String const& parameterName) = 0;
virtual Maybe<VariantTypeIndex> getEffectScriptableParameterType(String const& effectName, String const& parameterName) = 0;
virtual void setEffectTexture(String const& textureName, ImageView const& image) = 0; virtual void setEffectTexture(String const& textureName, ImageView const& image) = 0;
virtual bool switchEffectConfig(String const& name) = 0; virtual bool switchEffectConfig(String const& name) = 0;

View File

@ -107,8 +107,10 @@ OpenGlRenderer::OpenGlRenderer() {
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
//glEnable(GL_DEBUG_OUTPUT); if (GLEW_VERSION_4_3) {
//glDebugMessageCallback(GlMessageCallback, this); //glEnable(GL_DEBUG_OUTPUT);
//glDebugMessageCallback(GlMessageCallback, this);
}
m_whiteTexture = createGlTexture(Image::filled({1, 1}, Vec4B(255, 255, 255, 255), PixelFormat::RGBA32), m_whiteTexture = createGlTexture(Image::filled({1, 1}, Vec4B(255, 255, 255, 255), PixelFormat::RGBA32),
TextureAddressing::Clamp, TextureAddressing::Clamp,
@ -306,21 +308,39 @@ void OpenGlRenderer::loadEffectConfig(String const& name, Json const& effectConf
throw RendererException::format("Unrecognized effect parameter type '{}'", type); throw RendererException::format("Unrecognized effect parameter type '{}'", type);
} }
effect.parameters[p.first] = effectParameter; if (p.second.getBool("scriptable",false)) {
if (Json def = p.second.get("default", {})) {
if (Json def = p.second.get("default", {})) { if (type == "bool") {
if (type == "bool") { effectParameter.parameterValue = (RenderEffectParameter)def.toBool();
setEffectParameter(p.first, def.toBool()); } else if (type == "int") {
} else if (type == "int") { effectParameter.parameterValue = (RenderEffectParameter)(int)def.toInt();
setEffectParameter(p.first, (int)def.toInt()); } else if (type == "float") {
} else if (type == "float") { effectParameter.parameterValue = (RenderEffectParameter)def.toFloat();
setEffectParameter(p.first, def.toFloat()); } else if (type == "vec2") {
} else if (type == "vec2") { effectParameter.parameterValue = (RenderEffectParameter)jsonToVec2F(def);
setEffectParameter(p.first, jsonToVec2F(def)); } else if (type == "vec3") {
} else if (type == "vec3") { effectParameter.parameterValue = (RenderEffectParameter)jsonToVec3F(def);
setEffectParameter(p.first, jsonToVec3F(def)); } else if (type == "vec4") {
} else if (type == "vec4") { effectParameter.parameterValue = (RenderEffectParameter)jsonToVec4F(def);
setEffectParameter(p.first, 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; 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<RenderEffectParameter> 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<VariantTypeIndex> 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) { void OpenGlRenderer::setEffectTexture(String const& textureName, ImageView const& image) {
auto ptr = m_currentEffect->textures.ptr(textureName); auto ptr = m_currentEffect->textures.ptr(textureName);
if (!ptr) if (!ptr)
@ -1061,6 +1125,26 @@ void OpenGlRenderer::setupGlUniforms(Effect& effect, Vec2U screenSize) {
} }
glUniform2f(m_screenSizeUniform, screenSize[0], screenSize[1]); glUniform2f(m_screenSizeUniform, screenSize[0], screenSize[1]);
for (auto& param : effect.scriptables) {
auto ptr = &param.second;
auto mvalue = ptr->parameterValue;
if (mvalue) {
RenderEffectParameter value = mvalue.value();
if (auto v = value.ptr<bool>())
glUniform1i(ptr->parameterUniform, *v);
else if (auto v = value.ptr<int>())
glUniform1i(ptr->parameterUniform, *v);
else if (auto v = value.ptr<float>())
glUniform1f(ptr->parameterUniform, *v);
else if (auto v = value.ptr<Vec2F>())
glUniform2f(ptr->parameterUniform, (*v)[0], (*v)[1]);
else if (auto v = value.ptr<Vec3F>())
glUniform3f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2]);
else if (auto v = value.ptr<Vec4F>())
glUniform4f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2], (*v)[3]);
}
}
} }
RefPtr<OpenGlRenderer::GlFrameBuffer> OpenGlRenderer::getGlFrameBuffer(String const& id) { RefPtr<OpenGlRenderer::GlFrameBuffer> OpenGlRenderer::getGlFrameBuffer(String const& id) {

View File

@ -25,6 +25,9 @@ public:
void loadEffectConfig(String const& name, Json const& effectConfig, StringMap<String> const& shaders) override; void loadEffectConfig(String const& name, Json const& effectConfig, StringMap<String> const& shaders) override;
void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override; void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override;
void setEffectScriptableParameter(String const& effectName, String const& parameterName, RenderEffectParameter const& parameter) override;
Maybe<RenderEffectParameter> getEffectScriptableParameter(String const& effectName, String const& parameterName) override;
Maybe<VariantTypeIndex> getEffectScriptableParameterType(String const& effectName, String const& parameterName) override;
void setEffectTexture(String const& textureName, ImageView const& image) override; void setEffectTexture(String const& textureName, ImageView const& image) override;
void setScissorRect(Maybe<RectI> const& scissorRect) override; void setScissorRect(Maybe<RectI> const& scissorRect) override;
@ -191,6 +194,7 @@ private:
GLuint program = 0; GLuint program = 0;
Json config; Json config;
StringMap<EffectParameter> parameters; StringMap<EffectParameter> parameters;
StringMap<EffectParameter> scriptables; // scriptable parameters which can be changed when the effect is not loaded
StringMap<EffectTexture> textures; StringMap<EffectTexture> textures;
StringMap<GLuint> attributes; StringMap<GLuint> attributes;

View File

@ -18,7 +18,6 @@ SET (star_base_HEADERS
StarMixer.hpp StarMixer.hpp
StarPackedAssetSource.hpp StarPackedAssetSource.hpp
StarRootBase.hpp StarRootBase.hpp
StarVersion.hpp
StarVersionOptionParser.hpp StarVersionOptionParser.hpp
StarWorldGeometry.hpp StarWorldGeometry.hpp
scripting/StarImageLuaBindings.hpp scripting/StarImageLuaBindings.hpp
@ -40,8 +39,7 @@ SET (star_base_SOURCES
scripting/StarImageLuaBindings.cpp 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})
ADD_LIBRARY (star_base OBJECT ${star_base_SOURCES} ${star_base_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp)
IF(STAR_PRECOMPILED_HEADERS) IF(STAR_PRECOMPILED_HEADERS)
TARGET_PRECOMPILE_HEADERS (star_base REUSE_FROM star_core) TARGET_PRECOMPILE_HEADERS (star_base REUSE_FROM star_core)

View File

@ -68,7 +68,7 @@ static void validatePath(AssetPath const& components, bool canContainSubPath, bo
throw AssetException::format("Path '{}' cannot contain directives", components); 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(); std::string_view const& str = path.utf8();
size_t end = str.find_first_of(":?"); size_t end = str.find_first_of(":?");
@ -102,6 +102,14 @@ Maybe<RectU> 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) { Assets::Assets(Settings settings, StringList assetSources) {
const char* AssetsPatchSuffix = ".patch"; const char* AssetsPatchSuffix = ".patch";
const char* AssetsPatchListSuffix = ".patchlist"; const char* AssetsPatchListSuffix = ".patchlist";
@ -139,6 +147,12 @@ Assets::Assets(Settings settings, StringList assetSources) {
return *assetImage; 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<String> const& a, Maybe<String> const& b) -> StringList { callbacks.registerCallback("scan", [this](Maybe<String> const& a, Maybe<String> const& b) -> StringList {
return b ? scan(a.value(), *b) : scan(a.value()); return b ? scan(a.value(), *b) : scan(a.value());
}); });

View File

@ -27,7 +27,8 @@ struct FramesSpecification {
// Get the target sub-rect of a given frame name (which can be an alias). // Get the target sub-rect of a given frame name (which can be an alias).
// Returns nothing if the frame name is not found. // Returns nothing if the frame name is not found.
Maybe<RectU> getRect(String const& frame) const; Maybe<RectU> getRect(String const& frame) const;
// Converts to Json.
Json toJson() const;
// The full path to the .frames file from which this was loaded. // The full path to the .frames file from which this was loaded.
String framesFile; String framesFile;
// Named sub-frames // Named sub-frames

View File

@ -12,10 +12,12 @@ INCLUDE_DIRECTORIES (
SET (star_client_HEADERS SET (star_client_HEADERS
StarClientApplication.hpp StarClientApplication.hpp
StarRenderingLuaBindings.hpp
) )
SET (star_client_SOURCES SET (star_client_SOURCES
StarClientApplication.cpp StarClientApplication.cpp
StarRenderingLuaBindings.cpp
) )
IF (STAR_SYSTEM_WINDOWS) IF (STAR_SYSTEM_WINDOWS)
@ -29,12 +31,16 @@ ADD_EXECUTABLE (starbound WIN32
$<TARGET_OBJECTS:star_application> $<TARGET_OBJECTS:star_rendering> $<TARGET_OBJECTS:star_windowing> $<TARGET_OBJECTS:star_frontend> $<TARGET_OBJECTS:star_application> $<TARGET_OBJECTS:star_rendering> $<TARGET_OBJECTS:star_windowing> $<TARGET_OBJECTS:star_frontend>
${star_client_HEADERS} ${star_client_SOURCES} ${star_client_RESOURCES}) ${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) TARGET_PRECOMPILE_HEADERS (starbound REUSE_FROM star_core)
ENDIF() ENDIF()
IF(UNIX) IF (UNIX)
set_target_properties (starbound PROPERTIES LINK_FLAGS "-Wl,-rpath,'$ORIGIN'") SET_TARGET_PROPERTIES (starbound PROPERTIES LINK_FLAGS "-Wl,-rpath,'$ORIGIN'")
ENDIF() ENDIF()
TARGET_LINK_LIBRARIES (starbound ${STAR_EXT_LIBS} ${STAR_EXT_GUI_LIBS}) 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})

View File

@ -22,7 +22,9 @@
#include "StarInterfaceLuaBindings.hpp" #include "StarInterfaceLuaBindings.hpp"
#include "StarInputLuaBindings.hpp" #include "StarInputLuaBindings.hpp"
#include "StarVoiceLuaBindings.hpp" #include "StarVoiceLuaBindings.hpp"
#include "StarCameraLuaBindings.hpp"
#include "StarClipboardLuaBindings.hpp" #include "StarClipboardLuaBindings.hpp"
#include "StarRenderingLuaBindings.hpp"
#if defined STAR_SYSTEM_WINDOWS #if defined STAR_SYSTEM_WINDOWS
#include <windows.h> #include <windows.h>
@ -404,10 +406,12 @@ void ClientApplication::render() {
auto size = Vec2F(renderer->screenSize()); auto size = Vec2F(renderer->screenSize());
auto quad = renderFlatRect(RectF::withSize(size / -2, size), Vec4B::filled(0), 0.0f); auto quad = renderFlatRect(RectF::withSize(size / -2, size), Vec4B::filled(0), 0.0f);
for (auto& layer : m_postProcessLayers) { for (auto& layer : m_postProcessLayers) {
for (unsigned i = 0; i < layer.passes; i++) { if (layer.group ? layer.group->enabled : true) {
for (auto& effect : layer.effects) { for (unsigned i = 0; i < layer.passes; i++) {
renderer->switchEffectConfig(effect); for (auto& effect : layer.effects) {
renderer->render(quad); 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() { void ClientApplication::renderReload() {
auto assets = m_root->assets(); auto assets = m_root->assets();
auto renderer = Application::renderer(); auto renderer = Application::renderer();
@ -463,18 +469,55 @@ void ClientApplication::renderReload() {
loadEffectConfig("world"); 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(); m_postProcessLayers.clear();
auto postProcessLayers = assets->json("/client.config:postProcessLayers").toArray(); auto postProcessLayers = assets->json("/client.config:postProcessLayers").toArray();
for (auto& layer : postProcessLayers) { for (auto& layer : postProcessLayers) {
auto effects = jsonToStringList(layer.getArray("effects")); auto effects = jsonToStringList(layer.getArray("effects"));
for (auto& effect : effects) for (auto& effect : effects)
loadEffectConfig(effect); 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"); loadEffectConfig("interface");
} }
void ClientApplication::setPostProcessGroupEnabled(String group, bool enabled, Maybe<bool> 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) { void ClientApplication::changeState(MainAppState newState) {
MainAppState oldState = m_state; MainAppState oldState = m_state;
m_state = newState; m_state = newState;
@ -541,8 +584,11 @@ void ClientApplication::changeState(MainAppState newState) {
m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks()); m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks());
m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks()); m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks());
if (!m_root->configuration()->get("safeScripts").toBool()) m_universeClient->setLuaCallbacks("camera", LuaBindings::makeCameraCallbacks(&m_worldPainter->camera()));
m_universeClient->setLuaCallbacks("clipboard", LuaBindings::makeClipboardCallbacks(appController())); 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<List<MainInterface::ScriptPaneInfo>>(); auto heldScriptPanes = make_shared<List<MainInterface::ScriptPaneInfo>>();
@ -565,7 +611,7 @@ void ClientApplication::changeState(MainAppState newState) {
}; };
m_mainMixer->setUniverseClient(m_universeClient); m_mainMixer->setUniverseClient(m_universeClient);
m_titleScreen = make_shared<TitleScreen>(m_playerStorage, m_mainMixer->mixer()); m_titleScreen = make_shared<TitleScreen>(m_playerStorage, m_mainMixer->mixer(), m_universeClient);
if (auto renderer = Application::renderer()) if (auto renderer = Application::renderer())
m_titleScreen->renderInit(renderer); m_titleScreen->renderInit(renderer);
} }
@ -682,8 +728,9 @@ void ClientApplication::changeState(MainAppState newState) {
m_mainInterface = make_shared<MainInterface>(m_universeClient, m_worldPainter, m_cinematicOverlay); m_mainInterface = make_shared<MainInterface>(m_universeClient, m_worldPainter, m_cinematicOverlay);
m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get())); m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get()));
m_universeClient->setLuaCallbacks("chat", LuaBindings::makeChatCallbacks(m_mainInterface.get(), m_universeClient.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); m_mainMixer->setWorldPainter(m_worldPainter);
if (auto renderer = Application::renderer()) { if (auto renderer = Application::renderer()) {

View File

@ -18,6 +18,11 @@ STAR_CLASS(Input);
STAR_CLASS(Voice); STAR_CLASS(Voice);
class ClientApplication : public Application { class ClientApplication : public Application {
public:
void setPostProcessGroupEnabled(String group, bool enabled, Maybe<bool> save);
bool postProcessGroupEnabled(String group);
Json postProcessGroups();
protected: protected:
virtual void startup(StringList const& cmdLineArgs) override; virtual void startup(StringList const& cmdLineArgs) override;
virtual void shutdown() override; virtual void shutdown() override;
@ -53,9 +58,14 @@ private:
String password; String password;
}; };
struct PostProcessGroup {
bool enabled;
};
struct PostProcessLayer { struct PostProcessLayer {
List<String> effects; List<String> effects;
unsigned passes; unsigned passes;
PostProcessGroup* group;
}; };
void renderReload(); void renderReload();
@ -104,6 +114,7 @@ private:
WorldRenderData m_renderData; WorldRenderData m_renderData;
MainInterfacePtr m_mainInterface; MainInterfacePtr m_mainInterface;
StringMap<PostProcessGroup> m_postProcessGroups;
List<PostProcessLayer> m_postProcessLayers; List<PostProcessLayer> m_postProcessLayers;
// Valid if main app state == SinglePlayer // Valid if main app state == SinglePlayer

View File

@ -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<void, String, bool, Maybe<bool>>("setPostProcessGroupEnabled", bind(mem_fn(&ClientApplication::setPostProcessGroupEnabled), app, _1, _2, _3));
callbacks.registerCallbackWithSignature<bool, String>("postProcessGroupEnabled", bind(mem_fn(&ClientApplication::postProcessGroupEnabled), app, _1));
// not entirely necessary (root.assetJson can achieve the same purpose) but may as well
callbacks.registerCallbackWithSignature<Json>("postProcessGroups", bind(mem_fn(&ClientApplication::postProcessGroups), app));
// typedef Variant<float, int, Vec2F, Vec3F, Vec4F, bool> 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<float>()) {
renderer->setEffectScriptableParameter(effectName, effectParameter, (int)value.get<float>());
} 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;
}
}

View File

@ -0,0 +1,13 @@
#pragma once
#include "StarLua.hpp"
namespace Star {
STAR_CLASS(ClientApplication);
namespace LuaBindings {
LuaCallbacks makeRenderingCallbacks(ClientApplication* app);
}
}

View File

@ -41,6 +41,7 @@ SET (star_core_HEADERS
StarIdMap.hpp StarIdMap.hpp
StarImage.hpp StarImage.hpp
StarImageProcessing.hpp StarImageProcessing.hpp
StarImageScaling.hpp
StarInputEvent.hpp StarInputEvent.hpp
StarInterpolation.hpp StarInterpolation.hpp
StarRefPtr.hpp StarRefPtr.hpp
@ -70,10 +71,12 @@ SET (star_core_HEADERS
StarMultiArray.hpp StarMultiArray.hpp
StarMultiArrayInterpolator.hpp StarMultiArrayInterpolator.hpp
StarMultiTable.hpp StarMultiTable.hpp
StarNetCompatibility.hpp
StarNetElement.hpp StarNetElement.hpp
StarNetElementBasicFields.hpp StarNetElementBasicFields.hpp
StarNetElementContainers.hpp StarNetElementContainers.hpp
StarNetElementDynamicGroup.hpp StarNetElementDynamicGroup.hpp
StarNetElementExt.hpp
StarNetElementFloatFields.hpp StarNetElementFloatFields.hpp
StarNetElementGroup.hpp StarNetElementGroup.hpp
StarNetElementSignal.hpp StarNetElementSignal.hpp
@ -122,6 +125,7 @@ SET (star_core_HEADERS
StarUnicode.hpp StarUnicode.hpp
StarUuid.hpp StarUuid.hpp
StarVector.hpp StarVector.hpp
StarVersion.hpp
StarVlqEncoding.hpp StarVlqEncoding.hpp
StarWeightedPool.hpp StarWeightedPool.hpp
StarWorkerPool.hpp StarWorkerPool.hpp
@ -149,6 +153,7 @@ SET (star_core_SOURCES
StarIODevice.cpp StarIODevice.cpp
StarImage.cpp StarImage.cpp
StarImageProcessing.cpp StarImageProcessing.cpp
StarImageScaling.cpp
StarInputEvent.cpp StarInputEvent.cpp
StarJson.cpp StarJson.cpp
StarJsonBuilder.cpp StarJsonBuilder.cpp
@ -157,11 +162,13 @@ SET (star_core_SOURCES
StarJsonPatch.cpp StarJsonPatch.cpp
StarJsonRpc.cpp StarJsonRpc.cpp
StarFormattedJson.cpp StarFormattedJson.cpp
StarLexicalCast.cpp
StarListener.cpp StarListener.cpp
StarLogging.cpp StarLogging.cpp
StarLua.cpp StarLua.cpp
StarLuaConverters.cpp StarLuaConverters.cpp
StarMemory.cpp StarMemory.cpp
StarNetCompatibility.cpp
StarNetElement.cpp StarNetElement.cpp
StarNetElementBasicFields.cpp StarNetElementBasicFields.cpp
StarNetElementGroup.cpp StarNetElementGroup.cpp
@ -217,7 +224,8 @@ ELSEIF (STAR_SYSTEM_FAMILY_WINDOWS)
ENDIF () 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) IF(STAR_PRECOMPILED_HEADERS)
TARGET_PRECOMPILE_HEADERS (star_core PUBLIC StarPch.hpp) TARGET_PRECOMPILE_HEADERS (star_core PUBLIC StarPch.hpp)

View File

@ -1,6 +1,7 @@
#include "StarBTreeDatabase.hpp" #include "StarBTreeDatabase.hpp"
#include "StarSha256.hpp" #include "StarSha256.hpp"
#include "StarVlqEncoding.hpp" #include "StarVlqEncoding.hpp"
#include "StarLogging.hpp"
namespace Star { namespace Star {
@ -243,7 +244,7 @@ uint32_t BTreeDatabase::freeBlockCount() {
indexBlockIndex = indexBlock.nextFreeBlock; 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. // Include untracked blocks at the end of the file in the free count.
count += (m_device->size() - m_deviceSize) / m_blockSize; count += (m_device->size() - m_deviceSize) / m_blockSize;
@ -272,7 +273,7 @@ uint32_t BTreeDatabase::leafBlockCount() {
return true; return true;
} }
BTreeDatabase* parent; BTreeDatabase* parent = nullptr;
BlockIndex leafBlockCount = 0; BlockIndex leafBlockCount = 0;
}; };
@ -293,8 +294,8 @@ void BTreeDatabase::rollback() {
m_availableBlocks.clear(); m_availableBlocks.clear();
m_indexCache.clear(); m_indexCache.clear();
m_uncommittedWrites.clear();
m_uncommitted.clear(); m_uncommitted.clear();
m_pendingFree.clear();
readRoot(); readRoot();
@ -305,7 +306,8 @@ void BTreeDatabase::rollback() {
void BTreeDatabase::close(bool closeDevice) { void BTreeDatabase::close(bool closeDevice) {
WriteLocker writeLocker(m_lock); WriteLocker writeLocker(m_lock);
if (m_open) { if (m_open) {
doCommit(); if (!tryFlatten())
doCommit();
m_indexCache.clear(); m_indexCache.clear();
@ -536,7 +538,7 @@ auto BTreeDatabase::BTreeImpl::loadIndex(Pointer pointer) -> Index {
index->pointers.resize(s); index->pointers.resize(s);
for (uint32_t i = 0; i < s; ++i) { for (uint32_t i = 0; i < s; ++i) {
auto& e = index->pointers[i]; auto& e = index->pointers[i];
e.key =buffer.readBytes(parent->m_keySize); e.key = buffer.readBytes(parent->m_keySize);
e.pointer = buffer.read<BlockIndex>(); e.pointer = buffer.read<BlockIndex>();
} }
@ -896,17 +898,25 @@ void BTreeDatabase::rawReadBlock(BlockIndex blockIndex, size_t blockOffset, char
if (size <= 0) if (size <= 0)
return; 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) if (blockOffset > m_blockSize || size > m_blockSize - blockOffset)
throw DBException::format("Write past end of block, offset: {} size {}", blockOffset, size); throw DBException::format("Write past end of block, offset: {} size {}", blockOffset, size);
if (size <= 0) if (size <= 0)
return; 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 { auto BTreeDatabase::readFreeIndexBlock(BlockIndex blockIndex) -> FreeIndexBlock {
@ -991,12 +1001,12 @@ auto BTreeDatabase::leafTailBlocks(BlockIndex leafPointer) -> List<BlockIndex> {
} }
void BTreeDatabase::freeBlock(BlockIndex b) { void BTreeDatabase::freeBlock(BlockIndex b) {
if (m_uncommitted.contains(b)) { if (m_uncommitted.contains(b))
m_uncommitted.remove(b); m_uncommitted.remove(b);
m_availableBlocks.add(b); if (m_uncommittedWrites.contains(b))
} else { m_uncommittedWrites.remove(b);
m_pendingFree.append(b);
} m_availableBlocks.add(b);
} }
auto BTreeDatabase::reserveBlock() -> BlockIndex { auto BTreeDatabase::reserveBlock() -> BlockIndex {
@ -1007,10 +1017,7 @@ auto BTreeDatabase::reserveBlock() -> BlockIndex {
FreeIndexBlock indexBlock = readFreeIndexBlock(m_headFreeIndexBlock); FreeIndexBlock indexBlock = readFreeIndexBlock(m_headFreeIndexBlock);
for (auto const& b : indexBlock.freeBlocks) for (auto const& b : indexBlock.freeBlocks)
m_availableBlocks.add(b); m_availableBlocks.add(b);
// We cannot make available the block itself, because we must maintain m_availableBlocks.add(m_headFreeIndexBlock);
// atomic consistency. We will need to free this block later and commit
// the new free index block chain.
m_pendingFree.append(m_headFreeIndexBlock);
m_headFreeIndexBlock = indexBlock.nextFreeBlock; m_headFreeIndexBlock = indexBlock.nextFreeBlock;
} }
@ -1068,65 +1075,168 @@ void BTreeDatabase::readRoot() {
} }
void BTreeDatabase::doCommit() { void BTreeDatabase::doCommit() {
if (m_availableBlocks.empty() && m_pendingFree.empty() && m_uncommitted.empty()) if (m_availableBlocks.empty() && m_uncommitted.empty())
return; return;
if (!m_availableBlocks.empty() || !m_pendingFree.empty()) { if (!m_availableBlocks.empty()) {
// First, read the existing head FreeIndexBlock, if it exists // First, read the existing head FreeIndexBlock, if it exists
FreeIndexBlock indexBlock = FreeIndexBlock{InvalidBlockIndex, {}}; 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); indexBlock = readFreeIndexBlock(m_headFreeIndexBlock);
if (indexBlock.freeBlocks.size() >= maxFreeIndexLength()) { else
// If the existing head free index block is full, then we should start a m_headFreeIndexBlock = newBlock();
// 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);
}
}
// Then, we need to write all the available blocks, which are safe to write // Then, we need to write all the available blocks to the FreeIndexBlock chain.
// to, and the pending free blocks, which are NOT safe to write to, to the
// FreeIndexBlock chain.
while (true) { 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.
// If we have room on our current FreeIndexblock, just add a block to if (!m_availableBlocks.empty() && indexBlock.freeBlocks.size() < maxFreeIndexLength()) {
// it. Prioritize the pending free blocks, because we cannot use those BlockIndex toAdd = m_availableBlocks.takeFirst();
// to write to.
BlockIndex toAdd;
if (m_pendingFree.empty())
toAdd = m_availableBlocks.takeFirst();
else
toAdd = m_pendingFree.takeFirst();
indexBlock.freeBlocks.append(toAdd); indexBlock.freeBlocks.append(toAdd);
} else { } else {
// If our index block is full OR we are out of blocks to free, then // Update the current head free index block.
// need to write a new head free index block.
if (m_availableBlocks.empty())
m_headFreeIndexBlock = makeEndBlock();
else
m_headFreeIndexBlock = m_availableBlocks.takeFirst();
writeFreeIndexBlock(m_headFreeIndexBlock, indexBlock); writeFreeIndexBlock(m_headFreeIndexBlock, indexBlock);
// If we're out of blocks to free, then we're done // If we're out of blocks to free, then we're done
if (m_availableBlocks.empty() && m_pendingFree.empty()) if (m_availableBlocks.empty())
break; break;
indexBlock.nextFreeBlock = m_headFreeIndexBlock; // If our head free index block is full, then
indexBlock.freeBlocks.clear(); // 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(); writeRoot();
m_uncommitted.clear(); 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<BlockIndex> 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 { void BTreeDatabase::checkIfOpen(char const* methodName, bool shouldBeOpen) const {
if (shouldBeOpen && !m_open) if (shouldBeOpen && !m_open)
throw DBException::format("BTreeDatabase method '{}' called when not open, must be open.", methodName); 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 { 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() { BTreeSha256Database::BTreeSha256Database() {

View File

@ -230,7 +230,7 @@ private:
void updateBlock(BlockIndex blockIndex, ByteArray const& block); void updateBlock(BlockIndex blockIndex, ByteArray const& block);
void rawReadBlock(BlockIndex blockIndex, size_t blockOffset, char* block, size_t size) const; 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); void updateHeadFreeIndexBlock(BlockIndex newHead);
@ -251,6 +251,9 @@ private:
void writeRoot(); void writeRoot();
void readRoot(); void readRoot();
void doCommit(); void doCommit();
void commitWrites();
bool tryFlatten();
bool flattenVisitor(BTreeImpl::Index& index, BlockIndex& count);
void checkIfOpen(char const* methodName, bool shouldBeOpen) const; void checkIfOpen(char const* methodName, bool shouldBeOpen) const;
void checkBlockIndex(size_t blockIndex) const; void checkBlockIndex(size_t blockIndex) const;
@ -285,14 +288,14 @@ private:
bool m_dirty; bool m_dirty;
// Blocks that can be freely allocated and written to without violating // Blocks that can be freely allocated and written to without violating
// atomic consistency // atomic consistency.
Set<BlockIndex> m_availableBlocks; Set<BlockIndex> m_availableBlocks;
// Blocks to be freed on next commit.
Deque<BlockIndex> m_pendingFree;
// Blocks that have been written in uncommitted portions of the tree. // Blocks that have been written in uncommitted portions of the tree.
Set<BlockIndex> m_uncommitted; Set<BlockIndex> m_uncommitted;
// Temporarily holds written data so that it can be rolled back.
mutable Map<BlockIndex, ByteArray> m_uncommittedWrites;
}; };
// Version of BTreeDatabase that hashes keys with SHA-256 to produce a unique // Version of BTreeDatabase that hashes keys with SHA-256 to produce a unique

View File

@ -322,6 +322,8 @@ Vec3F Color::toRgbF() const {
return Vec3F(redF(), greenF(), blueF()); return Vec3F(redF(), greenF(), blueF());
} }
#pragma GCC push_options
#pragma GCC optimize("-fno-fast-math")
Vec4F Color::toHsva() const { Vec4F Color::toHsva() const {
float h, s, v; float h, s, v;
@ -365,6 +367,7 @@ Vec4F Color::toHsva() const {
return Vec4F(h, s, v, alphaF()); return Vec4F(h, s, v, alphaF());
} }
#pragma GCC pop_options
String Color::toHex() const { String Color::toHex() const {
auto rgba = toRgba(); auto rgba = toRgba();

Some files were not shown because too many files have changed in this diff Show More