diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml index 951cc40..1cfd12c 100644 --- a/.github/workflows/build_macos.yml +++ b/.github/workflows/build_macos.yml @@ -51,7 +51,7 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v4 with: - name: OpenStarbound-Dev-macOS-Intel + name: OpenStarbound-macOS-Intel path: dist/* build-arm: @@ -93,5 +93,5 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v4 with: - name: OpenStarbound-Dev-macOS-Silicon + name: OpenStarbound-macOS-Silicon path: dist/* \ No newline at end of file diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index cf50594..d3ee36b 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -85,5 +85,5 @@ jobs: - name: Upload Installer uses: actions/upload-artifact@v4 with: - name: Installer + name: OpenStarbound-Windows-Installer path: installer/* diff --git a/README.md b/README.md index 4e8074b..5dd2b87 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,17 @@ Original README is as follows, if you're here reading it, you can skip the steps This is a fork of Starbound. Contributions are welcome! You **must** own a copy of Starbound to use it. Base game assets are not provided for obvious reasons. -It is still **work-in-progress**. You can download the very latest build below, or the occasional releases (though those aren't very up to date yet!) -## Nightly Builds +It is still **work-in-progress**. -At the moment, you must copy the game assets (**packed.pak**) from your normal Starbound install to the OpenStarbound assets directory. +## 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. + +An installer is available for Windows. otherwise, extract the client/server zip for your platform and copy the game assets (packed.pak) to the OpenStarbound assets folder. the macOS releases currently lack the sbinit.config and folder structure that the Linux & Windows zips have, so you'll need to create those before running them. For macOS releases, it is recommended to build them from source (See guide below). +### Nightly Builds +These link directly to the latest build from the [Actions](https://github.com/OpenStarbound/OpenStarbound/actions?query=branch%3Amain) tab. [**Windows**](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main): -[Installer](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/Installer.zip), +[Installer](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Installer.zip), [Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Client.zip), [Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Server.zip) @@ -27,8 +31,6 @@ At the moment, you must copy the game assets (**packed.pak**) from your normal S [Intel](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main/OpenStarbound-Dev-macOS-Intel.zip), [ARM](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main/OpenStarbound-Dev-macOS-Silicon.zip) -These link directly to the latest build from the [Actions](https://github.com/OpenStarbound/OpenStarbound/actions?query=branch%3Amain) tab. - ## 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. @@ -42,8 +44,10 @@ Note: Not every function from [StarExtensions](https://github.com/StarExtensions * These scripts can modify, read, patch and create new assets! * Lua patch files now exist - **.patch.lua** * These can patch JSON assets, as well as images! +### Bug Fixes +* Invalid character inventories are updated when loading in, allowing players to swap inventory mods with pre-existing characters. ### Misc -* Player functions for saving/loading, modifying the humanoid identity +* 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) * Custom user input support with a keybindings menu (rewrite from StarExtensions) * Positional Voice Chat that works on completely vanilla servers, uses Opus for crisp, HD audio (rewrite from StarExtensions) @@ -52,11 +56,15 @@ Note: Not every function from [StarExtensions](https://github.com/StarExtensions * **.woff2** fonts are much smaller than **.ttf**, [here's a web conversion tool](https://kombu.kanejaku.org/)! * Experimental changes to the storage of directives in memory to reduce copying - can reduce their impact on frametimes when very long directives are present * Works especially well when extremely long directives are used for "vanilla multiplayer-compatible" creations, like [generated clothing](https://silverfeelin.github.io/Starbound-NgOutfitGenerator/) or custom items/objects. +* Perfectly Generic Items will retain the data for what item they were if a mod is uninstalled, and will attempt to restore themselves if re-installed. +* Musical instruments have their own volume slider in the options menu. +* Players can use items while lounging * Client-side tile placement prediction (rewrite from StarExtensions) * You can also resize the placement area of tiles on the fly. * Support for placing foreground tiles with a custom collision type (rewrite from StarExtensions, requires OpenSB server) * Additionally, objects can be placed under non-solid foreground tiles. + * Admin characters have unlimited and unobstructed interaction/placement ranges * Some minor polish to UI * The Skybox's sun now matches the system type you're currently in. @@ -203,6 +211,44 @@ LD_LIBRARY_PATH="$LD_LIBRARY_PATH:./" padsp ./starbound "$@"` +
+macOS + +* First, you will need to have brew installed. Check out how to install [Homebrew](https://brew.sh/) +* Install cmake using `brew install cmake` +* Install ninja using `brew install ninja` +* Install pkg config using `brew install pkg-config` +* Next, install vcpkg by following the commands below. + * Run `cd ~`. This is just so that everything is local to here. + * Run ` git clone https://github.com/microsoft/vcpkg.git ` + * Run `cd vcpkg && ./bootstrap-vcpkg.sh` + * Lastly, run ``` export VCPKG_ROOT=~/vcpkg && export PATH=$VCPKG_ROOT:$PATH ``` + * This last command makes vcpkg added to the current terminal path. This lasts only while the terminal is active, and will have to be rerun for new terminal instances. +* Download the source code [here](https://github.com/OpenStarbound/OpenStarbound/archive/refs/heads/main.zip). This is the current code in main. Unpack the code to your downloads folder. +* Unpack the zip, and open it up. Navigate to OpenStarbound-main/source using the terminal -> `cd ~/Downloads/OpenStarbound-main`. Then navigate to the source folder, using `cd source`. +
+ If using an Arm Mac -### macOS -To be written. + * While in the source folder in your terminal, run ` cmake --preset macos-arm-release `. This will get dependencies. + * After that command has finished, run ` cmake --build --preset macos-arm-release `. Wait for this to finish, then go to Finder. Navigate to the OpenStarbound-main folder using Finder. + * There will be a folder called dist. Inside dist will be your game files, but you still need to do a few more things to run it. + * First, in the OpenStarbound-main folder, there will be lib. Open lib, and open the osx folder. Inside is libsteam_api.dylib. Copy this file, and paste it into OpenStarbound-main/dist, so that it is in the same directory as the game files. + * Navigate back to OpenStarbound-main/lib/osx, and open up the folder arm64. Here, rename libdiscord_game_sdk.dylib to discord_game_sdk.dylib. The name must be that, or else the game won't be able to load. + * Grab the packed.pak file from your current Starbound install. It will be located in the assets folder. Copy that file into OpenStarbound-main/assets. + * Make a new file called sbinit.config (Make sure it is .config, not .somethingelse), and copy and paste in the sbinit.config text from above, located right underneath the title Building. Place sbinit.config inside OpenStarbound-main/dist. To make a new file, open the program called TextEdit on your mac, paste in the sbinit.config text from above, and click File (located at the very top of your screen), then click Save. It will prompt you, asking where to save it. Save As: sbinit.config, Where: Navigate to OpenStarbound-main/dist. Find the file you just saved, and rename it to get rid of the wrong extension, making sure the full name and extension looks like sbinit.config. + * You can now run the game by double clicking on the file called starbound in dist/. If it says unverified developer, open up the same folder where the game is in in the terminal. ` xattr -d com.apple.quarantine starbound `, which will get rid of the lock on the file. If that doesn't work, run ` sudo spctl --master-disable ` to allow all unverified apps. +
+
+ If using an Intel Mac + + * While in the source folder in your terminal, run ` cmake --preset macos-release `. This will get dependencies. + * After that command has finished, run ` cmake --build --preset macos-release `. Wait for this to finish, then go to Finder. Navigate to the OpenStarbound-main folder using Finder. + * There will be a folder called dist. Inside dist will be your game files, but you still need to do a few more things to run it. + * First, in the OpenStarbound-main folder, there will be lib. Open lib, and open the osx folder. Inside is libsteam_api.dylib. Copy this file, and paste it into OpenStarbound-main/dist, so that it is in the same directory as the game files. + * Navigate back to OpenStarbound-main/lib/osx, and open up the folder x64. Here, rename libdiscord_game_sdk.dylib to discord_game_sdk.dylib. The name must be that, or else the game won't be able to load. + * Grab the packed.pak file from your current Starbound install. It will be located in the assets folder. Copy that file into OpenStarbound-main/assets. + * Make a new file called sbinit.config (Make sure it is .config, not .somethingelse), and copy and paste in the sbinit.config text from above, located right underneath the title Building. Place sbinit.config inside OpenStarbound-main/dist. To make a new file, open the program called TextEdit on your mac, paste in the sbinit.config text from above, and click File (located at the very top of your screen), then click Save. It will prompt you, asking where to save it. Save As: sbinit.config, Where: Navigate to OpenStarbound-main/dist. Find the file you just saved, and rename it to get rid of the wrong extension, making sure the full name and extension looks like sbinit.config. + * You can now run the game by double clicking on the file called starbound in dist/. If it says unverified developer, open up the same folder where the game is in in the terminal. ` xattr -d com.apple.quarantine starbound `, which will get rid of the lock on the file. If that doesn't work, run ` sudo spctl --master-disable ` to allow all unverified apps. + +
+
diff --git a/assets/opensb/client.config.patch b/assets/opensb/client.config.patch index a0de983..619a767 100644 --- a/assets/opensb/client.config.patch +++ b/assets/opensb/client.config.patch @@ -9,5 +9,6 @@ "deployCinematicBase" : { "scissor" : false, "letterbox" : false - } -} \ No newline at end of file + }, + "postProcessLayers": [] +} diff --git a/assets/opensb/cursors/inspect.cursor.patch b/assets/opensb/cursors/inspect.cursor.patch deleted file mode 100644 index 7ca7bef..0000000 --- a/assets/opensb/cursors/inspect.cursor.patch +++ /dev/null @@ -1,3 +0,0 @@ -{ - "scale" : 1 -} \ No newline at end of file diff --git a/assets/opensb/cursors/link.cursor.patch b/assets/opensb/cursors/link.cursor.patch deleted file mode 100644 index 7ca7bef..0000000 --- a/assets/opensb/cursors/link.cursor.patch +++ /dev/null @@ -1,3 +0,0 @@ -{ - "scale" : 1 -} \ No newline at end of file diff --git a/assets/opensb/cursors/pointer.cursor.patch b/assets/opensb/cursors/pointer.cursor.patch deleted file mode 100644 index 7ca7bef..0000000 --- a/assets/opensb/cursors/pointer.cursor.patch +++ /dev/null @@ -1,3 +0,0 @@ -{ - "scale" : 1 -} \ No newline at end of file diff --git a/assets/opensb/help.config.patch b/assets/opensb/help.config.patch index deddd21..ad1da12 100644 --- a/assets/opensb/help.config.patch +++ b/assets/opensb/help.config.patch @@ -1,5 +1,16 @@ { - "basicHelpText" : "Basic commands are: {}", - "adminHelpText" : "Admin commands are: {}", - "debugHelpText" : "Debug commands are: {}" -} \ No newline at end of file + "basicHelpText": "Basic commands are: {}", + "adminHelpText": "Admin commands are: {}", + "debugHelpText": "Debug commands are: {}", + "openSbHelpText": "OpenSB commands are: {}", + "openSbDebugHelpText": "OpenSB Debug commands are: {}", + + "openSbDebugCommands": { + "run": "Usage /run . Executes a script on the player and outputs the return value to chat." + }, + + "openSbCommands": { + "swap": "Usage /swap . Swaps the current character, case-insensitive, only substring required.", + "respawninworld": "Usage /respawninworld. Sets the respawn flag for the current world until you teleport away." + } +} diff --git a/assets/opensb/interface/graphicsmenu/body.png.patch.lua b/assets/opensb/interface/graphicsmenu/body.png.patch.lua index 85ac17e..16051ab 100644 --- a/assets/opensb/interface/graphicsmenu/body.png.patch.lua +++ b/assets/opensb/interface/graphicsmenu/body.png.patch.lua @@ -4,7 +4,7 @@ function patch(original) image:copyInto({0, 0}, original:process("?crop=0;0;236;96")) local checkbox = image:process("?crop=19;26;117;35") image:copyInto({119, 26}, checkbox) -- Anti-Aliasing - image:copyInto({19, 15}, checkbox) -- Object Lighting + image:copyInto({19, 15}, checkbox) -- New Lighting image:copyInto({119, 15}, checkbox) -- Hardware Cursor image:copyInto({119, 68}, image:process("?crop=19;68;117;87")) -- Camera Pan Speed diff --git a/assets/opensb/interface/opensb/bindings/bindings.lua b/assets/opensb/interface/opensb/bindings/bindings.lua index 1287636..cafcc15 100644 --- a/assets/opensb/interface/opensb/bindings/bindings.lua +++ b/assets/opensb/interface/opensb/bindings/bindings.lua @@ -29,12 +29,16 @@ local function getMods(key) if bindMods[1] then return bindMods end end -local function finishBind(type, value) +local function finishBind(a, b) widget.blur("snare") snared = false - snareFinished{ type = type, value = value, mods = getMods(value) } - for i, mod in ipairs(mods) do - mod.active = false + if (type(a) == "table") then + snareFinished(a) + else + snareFinished{ type = a, value = b, mods = getMods(value) } + for i, mod in ipairs(mods) do + mod.active = false + end end end @@ -50,6 +54,8 @@ local function scanInputEvents() return finishBind("key", data.key) elseif type == "MouseButtonDown" then return finishBind("mouse", data.mouseButton) + elseif type == "ControllerButtonDown" then + return finishBind{ type = "controller", value = data.controllerButton, controller = data.controller } end end end @@ -127,9 +133,9 @@ function bindsToString(binds) str = str .. v .. " + " end end - if bind.type == "key" then - str = str .. bind.value - elseif bind.type == "mouse" then + if bind.type == "controller" then + str = str .. "🎮 " .. bind.value + else str = str .. bind.value end local _i = (i - 1) * 2 @@ -362,7 +368,7 @@ local function initCallbacks() end function init() - --log = sb.logInfo + --log = chat and chat.addMessage or sb.logInfo widget.clearListItems(CATEGORY_LIST_WIDGET) initCallbacks() diff --git a/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua b/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua index 02d305c..7d7ab44 100644 --- a/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua +++ b/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua @@ -35,9 +35,9 @@ function patch(config) -- Create anti-aliasing toggle shift(clone(layout, "multiTextureLabel", "antiAliasingLabel"), 98).value = "SUPER-SAMPLED AA" shift(clone(layout, "multiTextureCheckbox", "antiAliasingCheckbox"), 99) - -- Create object lighting toggle - shift(clone(layout, "multiTextureLabel", "objectLightingLabel"), 0, -11).value = "NEW OBJECT LIGHTS" - shift(clone(layout, "multiTextureCheckbox", "objectLightingCheckbox"), 0, -11) + -- Create new lighting toggle + shift(clone(layout, "multiTextureLabel", "newLightingLabel"), 0, -11).value = "NEW LIGHTING" + shift(clone(layout, "multiTextureCheckbox", "newLightingCheckbox"), 0, -11) -- Create hardware cursor toggle shift(clone(layout, "multiTextureLabel", "hardwareCursorLabel"), 98, -11).value = "HARDWARE CURSOR" shift(clone(layout, "multiTextureCheckbox", "hardwareCursorCheckbox"), 99, -11) diff --git a/assets/opensb/interface/windowconfig/multiplayer.config.patch b/assets/opensb/interface/windowconfig/multiplayer.config.patch index 8830e54..8bd27c1 100644 --- a/assets/opensb/interface/windowconfig/multiplayer.config.patch +++ b/assets/opensb/interface/windowconfig/multiplayer.config.patch @@ -1,5 +1,8 @@ { - "password" : { - "hidden" : true - } + "panefeature" : { + "type" : "panefeature", + "anchor" : "center", + "positionLocked" : true + }, + "password" : { "hidden" : true } } \ No newline at end of file diff --git a/assets/opensb/interface/windowconfig/songbook.config.patch b/assets/opensb/interface/windowconfig/songbook.config.patch deleted file mode 100644 index 0ef4c54..0000000 --- a/assets/opensb/interface/windowconfig/songbook.config.patch +++ /dev/null @@ -1,22 +0,0 @@ -{ - "paneLayout" : { - "group" : { - "position" : [8, 71] - }, - "search" : { - "type" : "textbox", - "position" : [86, 71], - "hint" : "Search", - "maxWidth" : 50 - }, - "lblBandInput" : { - "position" : [3, 68] - }, - "lblSearchInput" : { - "type" : "image", - "file" : "/interface/songbook/band.png", - "position" : [81, 68], - "zlevel" : -3 - } - } -} \ No newline at end of file diff --git a/assets/opensb/interface/windowconfig/songbook_search_patch.lua b/assets/opensb/interface/windowconfig/songbook_search_patch.lua new file mode 100644 index 0000000..474c2c1 --- /dev/null +++ b/assets/opensb/interface/windowconfig/songbook_search_patch.lua @@ -0,0 +1,12 @@ +function patch(config) + local scrollBG = config.paneLayout.scrollBG + local scrollSize = assets.image(scrollBG.file):size() + config.paneLayout.search = { + type = "textbox", + position = {scrollBG.position[1] + 3, + scrollBG.position[2] + scrollSize[2] - 10}, + hint = "^#999;Type here to search for a song", + maxWidth = scrollSize[1] - 6 + } + return config +end \ No newline at end of file diff --git a/assets/opensb/interface/windowconfig/title.config.patch.lua b/assets/opensb/interface/windowconfig/title.config.patch.lua index c5d9e57..34d65c3 100644 --- a/assets/opensb/interface/windowconfig/title.config.patch.lua +++ b/assets/opensb/interface/windowconfig/title.config.patch.lua @@ -8,10 +8,10 @@ function patch(data) data.backdropImages = jarray{ jarray{ jarray{0, 0}, - "/interface/title/" .. (sb.makeRandomSource():randUInt(100) == 0 and "barst" or "starb") .. "ound.png", + "/interface/title/" .. (sb.makeRandomSource():randUInt(300) == 0 and "barst" or "starb") .. "ound.png", 0.5, jarray{0.5, 0.5} } } return data -end \ No newline at end of file +end diff --git a/assets/opensb/lighting.config.patch b/assets/opensb/lighting.config.patch index 92f7c62..44c0716 100644 --- a/assets/opensb/lighting.config.patch +++ b/assets/opensb/lighting.config.patch @@ -1,5 +1,5 @@ { "lighting" : { - "brightnessLimit" : 1.5 + "brightnessLimit" : 1.4 } } \ No newline at end of file diff --git a/assets/opensb/objects/opensb/object.patch.lua b/assets/opensb/objects/opensb/object.patch.lua index df783ac..45ecf23 100644 --- a/assets/opensb/objects/opensb/object.patch.lua +++ b/assets/opensb/objects/opensb/object.patch.lua @@ -1,6 +1,22 @@ +--local function drop(color) +-- if type(color) == "table" then +-- for i = 1, #color do +-- color[i] = color[i] * 0.8 +-- end +-- end +--end + function patch(object, path) if object.pointLight ~= true and (object.lightColor or object.lightColors) then object.lightType = "PointAsSpread" - return object; + return object + --elseif type(object.lightColor) == "table" then + -- drop(object.lightColor) + -- return object + --elseif type(object.lightColors) == "table" then + -- for i, v in pairs(object.lightColors) do + -- drop(v) + -- end + -- return object end end \ No newline at end of file diff --git a/assets/opensb/opensb/coconut.png b/assets/opensb/opensb/coconut.png new file mode 100644 index 0000000..3a5192b Binary files /dev/null and b/assets/opensb/opensb/coconut.png differ diff --git a/assets/opensb/rendering/effects/basic.vert b/assets/opensb/rendering/effects/basic.vert new file mode 100644 index 0000000..362dc6c --- /dev/null +++ b/assets/opensb/rendering/effects/basic.vert @@ -0,0 +1,7 @@ +#version 140 + +in vec2 vertexPosition; + +void main() { + gl_Position = vec4(vertexPosition, 0.0, 1.0); +} diff --git a/assets/opensb/rendering/effects/interface.config b/assets/opensb/rendering/effects/interface.config index f153068..4616e72 100644 --- a/assets/opensb/rendering/effects/interface.config +++ b/assets/opensb/rendering/effects/interface.config @@ -1,13 +1,7 @@ { "blitFrameBuffer" : "main", - "effectParameters" : { - "vertexRounding" : { - "type" : "bool", - "default" : false, - "uniform" : "vertexRounding" - } - }, + "effectParameters" : {}, "effectTextures" : {}, "effectShaders" : { diff --git a/assets/opensb/rendering/effects/interface.frag b/assets/opensb/rendering/effects/interface.frag index 3dd79c8..7d37737 100644 --- a/assets/opensb/rendering/effects/interface.frag +++ b/assets/opensb/rendering/effects/interface.frag @@ -1,4 +1,4 @@ -#version 130 +#version 140 uniform sampler2D texture0; uniform sampler2D texture1; @@ -14,13 +14,13 @@ out vec4 outColor; void main() { vec4 texColor; if (fragmentTextureIndex == 3) - texColor = texture2D(texture3, fragmentTextureCoordinate); + texColor = texture(texture3, fragmentTextureCoordinate); else if (fragmentTextureIndex == 2) - texColor = texture2D(texture2, fragmentTextureCoordinate); + texColor = texture(texture2, fragmentTextureCoordinate); else if (fragmentTextureIndex == 1) - texColor = texture2D(texture1, fragmentTextureCoordinate); + texColor = texture(texture1, fragmentTextureCoordinate); else - texColor = texture2D(texture0, fragmentTextureCoordinate); + texColor = texture(texture0, fragmentTextureCoordinate); if (texColor.a <= 0.0) discard; diff --git a/assets/opensb/rendering/effects/interface.vert b/assets/opensb/rendering/effects/interface.vert index dfd94d4..1cbd059 100644 --- a/assets/opensb/rendering/effects/interface.vert +++ b/assets/opensb/rendering/effects/interface.vert @@ -1,4 +1,4 @@ -#version 130 +#version 140 uniform vec2 textureSize0; uniform vec2 textureSize1; @@ -6,7 +6,6 @@ uniform vec2 textureSize2; uniform vec2 textureSize3; uniform vec2 screenSize; uniform mat3 vertexTransform; -uniform bool vertexRounding; in vec2 vertexPosition; in vec4 vertexColor; @@ -21,13 +20,6 @@ void main() { vec2 screenPosition = (vertexTransform * vec3(vertexPosition, 1.0)).xy; gl_Position = vec4(screenPosition / screenSize * 2.0 - 1.0, 0.0, 1.0); - if (vertexRounding) { - if (((vertexData >> 3) & 0x1) == 1) - screenPosition.x = round(screenPosition.x); - if (((vertexData >> 4) & 0x1) == 1) - screenPosition.y = round(screenPosition.y); - } - int vertexTextureIndex = vertexData & 0x3; if (vertexTextureIndex == 3) fragmentTextureCoordinate = vertexTextureCoordinate / textureSize3; diff --git a/assets/opensb/rendering/effects/world.frag b/assets/opensb/rendering/effects/world.frag index 8b4d77c..8d18a2e 100644 --- a/assets/opensb/rendering/effects/world.frag +++ b/assets/opensb/rendering/effects/world.frag @@ -1,4 +1,4 @@ -#version 130 +#version 140 uniform sampler2D texture0; uniform sampler2D texture1; @@ -27,7 +27,7 @@ vec4 cubic(float v) { return vec4(x, y, z, w); } -vec4 bicubicSample(sampler2D texture, vec2 texcoord, vec2 texscale) { +vec4 bicubicSample(sampler2D tex, vec2 texcoord, vec2 texscale) { texcoord = texcoord - vec2(0.5, 0.5); float fx = fract(texcoord.x); @@ -42,10 +42,10 @@ vec4 bicubicSample(sampler2D texture, vec2 texcoord, vec2 texscale) { vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w); vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s; - vec4 sample0 = texture2D(texture, vec2(offset.x, offset.z) * texscale); - vec4 sample1 = texture2D(texture, vec2(offset.y, offset.z) * texscale); - vec4 sample2 = texture2D(texture, vec2(offset.x, offset.w) * texscale); - vec4 sample3 = texture2D(texture, vec2(offset.y, offset.w) * texscale); + vec4 sample0 = texture(tex, vec2(offset.x, offset.z) * texscale); + vec4 sample1 = texture(tex, vec2(offset.y, offset.z) * texscale); + vec4 sample2 = texture(tex, vec2(offset.x, offset.w) * texscale); + vec4 sample3 = texture(tex, vec2(offset.y, offset.w) * texscale); float sx = s.x / (s.x + s.y); float sy = s.z / (s.z + s.w); @@ -67,13 +67,13 @@ vec3 sampleLight(vec2 coord, vec2 scale) { void main() { vec4 texColor; if (fragmentTextureIndex == 3) - texColor = texture2D(texture3, fragmentTextureCoordinate); + texColor = texture(texture3, fragmentTextureCoordinate); else if (fragmentTextureIndex == 2) - texColor = texture2D(texture2, fragmentTextureCoordinate); + texColor = texture(texture2, fragmentTextureCoordinate); else if (fragmentTextureIndex == 1) - texColor = texture2D(texture1, fragmentTextureCoordinate); + texColor = texture(texture1, fragmentTextureCoordinate); else - texColor = texture2D(texture0, fragmentTextureCoordinate); + texColor = texture(texture0, fragmentTextureCoordinate); if (texColor.a <= 0.0) discard; diff --git a/assets/opensb/rendering/effects/world.vert b/assets/opensb/rendering/effects/world.vert index f41c0a8..fb50af7 100644 --- a/assets/opensb/rendering/effects/world.vert +++ b/assets/opensb/rendering/effects/world.vert @@ -1,4 +1,4 @@ -#version 130 +#version 140 uniform vec2 textureSize0; uniform vec2 textureSize1; diff --git a/assets/opensb/rendering/error.png b/assets/opensb/rendering/sprites/error.png similarity index 100% rename from assets/opensb/rendering/error.png rename to assets/opensb/rendering/sprites/error.png diff --git a/assets/opensb/rendering/error_left.png b/assets/opensb/rendering/sprites/error_left.png similarity index 100% rename from assets/opensb/rendering/error_left.png rename to assets/opensb/rendering/sprites/error_left.png diff --git a/assets/opensb/rendering/error_right.png b/assets/opensb/rendering/sprites/error_right.png similarity index 100% rename from assets/opensb/rendering/error_right.png rename to assets/opensb/rendering/sprites/error_right.png diff --git a/assets/opensb/scripts/opensb/assets/postload.lua b/assets/opensb/scripts/opensb/assets/postload.lua index d84dd75..256e199 100644 --- a/assets/opensb/scripts/opensb/assets/postload.lua +++ b/assets/opensb/scripts/opensb/assets/postload.lua @@ -1,4 +1,5 @@ -- Revert cursor frames if a mod replaced cursors.png with a SD version again +-- Otherwise, scale down our HD cursors if assets.image("/cursors/cursors.png"):size()[1] == 64 then local path = "/cursors/opensb/revert.cursor.patch" assets.add(path, '{"scale":null}') @@ -8,6 +9,14 @@ if assets.image("/cursors/cursors.png"):size()[1] == 64 then path = "/cursors/opensb/revert.frames.patch" assets.add(path, '{"frameGrid":{"size":[16,16]}}') assets.patch("/cursors/cursors.frames", path) +else + local cursors = assets.json("/cursors/cursors.frames:frameGrid.names") + local path = "/cursors/%s.cursor.patch" + for i = 1, #cursors do + for j = 1, #cursors[i] do + assets.add(string.format(path, cursors[i][j]), '{"scale":1}') + end + end end -- Add object patches @@ -15,4 +24,9 @@ local objects = assets.byExtension("object") local path = "/objects/opensb/object.patch.lua" for i = 1, #objects do assets.patch(objects[i], path) -end \ No newline at end of file +end + +assets.patch( + "/interface/windowconfig/songbook.config", + "/interface/windowconfig/songbook_search_patch.lua" +) \ No newline at end of file diff --git a/assets/opensb/scripts/opensb/player/commands.lua b/assets/opensb/scripts/opensb/player/commands.lua index 0387805..2b42f48 100644 --- a/assets/opensb/scripts/opensb/player/commands.lua +++ b/assets/opensb/scripts/opensb/player/commands.lua @@ -21,6 +21,8 @@ command("run", function(src) local success, result = pcall(result) if not success then return "^#f00;error: " .. result + elseif result == nil then + return nil else local success, printed = pcall(sb.printJson, result) if not success then diff --git a/assets/opensb/scripts/opensb/servercommands/servercommands.lua b/assets/opensb/scripts/opensb/servercommands/servercommands.lua index ca30435..8df7928 100644 --- a/assets/opensb/scripts/opensb/servercommands/servercommands.lua +++ b/assets/opensb/scripts/opensb/servercommands/servercommands.lua @@ -3,41 +3,57 @@ local logHelp = "Available OpenStarbound server commands:\n" local userHelp = logHelp .. "^cyan;" local adminHelp = userHelp -local function cmd(name, description, permission, func) +local function cmd(meta, func) local first = next(commands) == nil - logHelp = logHelp .. (first and name or ", " .. name) - userHelp = userHelp .. (first and name or ", ^cyan;" .. name) - adminHelp = adminHelp .. (first and name or ", ^cyan;" .. name) + local name = meta.name + local description = meta.description + local permission = meta.permission + if not meta.hidden then + logHelp = logHelp .. (first and name or ", " .. name) + userHelp = userHelp .. (first and name or ", ^cyan;" .. name) + adminHelp = adminHelp .. (first and name or ", ^cyan;" .. name) + end local keyName = name:lower() if permission == "tell" then - commands[keyName] = function(connectionId, ...) + commands[keyName] = {meta = meta, func = function(connectionId, ...) return func(universe.isAdmin(connectionId), connectionId, ...) - end + end} elseif permission == "admin" then - commands[keyName] = function(connectionId, ...) + commands[keyName] = {meta = meta, func = function(connectionId, ...) local error = CommandProcessor.adminCheck(connectionId, description:sub(1, 1):lower() .. description:sub(2)) if error then return error else return func(connectionId, ...) end - end + end} elseif permission == "user" then - commands[keyName] = func + commands[keyName] = {meta = meta, func = func} else error(string.format("Command '%s' has invalid permission", name)) end end -cmd("openhelp", "Get help", "tell", function(isAdmin, connectionId) +cmd({ + name = "openhelp", + description = "Get help", + permission = "tell" +}, +function(isAdmin, connectionId) return isAdmin and adminHelp or userHelp end) do local objects = nil -cmd("packetTest", "Do science", "admin", function(connectionId) +cmd({ + name = "packetTest", + description = "Do science", + permission = "admin", + hidden = true +}, +function(connectionId) if not objects then objects = {} local paths = root.assetsByExtension("object") @@ -69,7 +85,7 @@ function command(commandName, connectionId, args) local command = commands[commandName:lower()] if command then - local success, ret = pcall(command, connectionId, table.unpack(args)) + local success, ret = pcall(command.func, connectionId, table.unpack(args)) if not success then sb.logError("Error in OpenStarbound server command /%s: %s", commandName, ret) return "command error: " .. ret diff --git a/doc/lua/openstarbound.md b/doc/lua/openstarbound.md new file mode 100644 index 0000000..ad8483d --- /dev/null +++ b/doc/lua/openstarbound.md @@ -0,0 +1,415 @@ +# Unsorted + +These are functions that aren't in any specific table. + +--- + +#### `Maybe, Maybe` loadstring(`String` source, [`String` name, [`LuaValue` env]]) + +Compiles the provided **source** and returns it as a callable function. +If there are any syntax errors, returns `nil` and the error as a string instead. + +- **name** is used for error messages, the default is the name of the script that called `loadstring`. +- **env** is used as the environment of the returned function, the default is `_ENV`. + +--- + +# Root + +The root table now contains extra asset bindings and bindings to return the tile variant that is used for material and matmods at any position. + +--- + +#### `String[]` root.assetsByExtension(`String` extension) + +Returns an array containing all assets with the specified file extension. + +By the way, here's a list of every file extension the game does Special Things™ for when loading assets. + +
File Extensions + +- Items: `item`, `liqitem`, `matitem`, `miningtool`, `flashlight`, `wiretool`, `beamaxe`, `tillingtool`, `painttool`, `harvestingtool`, `head`, `chest`, `legs`, `back`, `currency`, `consumable`, `blueprint`, `inspectiontool`, `instrument`, `thrownitem`, `unlock`, `activeitem`, `augment` +- Materials: `material`, `matmod` +- Liquids: `liquid` +- NPCs: `npctype` +- Tenants: `tenant` +- Objects: `object` +- Vehicles: `vehicle` +- Monsters: `monstertype`, `monsterpart`, `monsterskill`, `monstercolors` +- Plants: `modularstem`, `modularfoliage`, `grass`, `bush` +- Projectiles: `projectile` +- Particles: `particle` +- Name Gen: `namesource` +- AI Missions: `aimission` +- Quests: `questtemplate` +- Radio Messages: `radiomessages` +- Spawn Types: `spawntypes` +- Species: `species` +- Stagehand: `stagehand` +- Behaviors: `nodes`, `behavior` +- Biomes: `biome`, `weather` +- Terrain: `terrain` +- Treasure: `treasurepools`, `treasurechests` +- Codex Entries: `codex` +- Collections: `collection` +- Statistics: `event`, `achievement` +- Status Effects: `statuseffect` +- Functions: `functions`, `2functions`, `configfunctions` +- Tech: `tech` +- Damage: `damage` +- Dances: `dance` +- Effect Sources: `effectsource` +- Command Macros: `macros` +- Recipes: `recipe` +
+ +#### `String` root.assetData(`String` path) + +Returns the raw data of an asset. + +#### `String, Maybe` root.assetOrigin(`String` path, [`bool` getPatches]) + +Returns the asset source path of an asset, or nil if the asset doesn't exist. If you specify getPatches as true then also returns the patches for the asset as an array, each element containing the source path and patch path in indexes 1 and 2 respectively. + +#### `LuaTable` root.assetSourcePaths([`bool` withMetadata]) + +Without metadata: Returns an array with all the asset source paths. +With metadata: Returns a table, key/value being source path/metadata. + +#### `?` root.assetImage(`String` image) + +*TODO* + +#### `JsonArray` root.assetPatches(`String` asset) + +Returns a list of asset sources which patch the specified asset and the paths to those patches. + +--- + +#### `Json` root.getConfiguration(`String` key) + +Gets a configuration value in `/storage/starbound.config`. + +#### `Json` root.getConfigurationPath(`String` path) + +Gets a configuration value in `/storage/starbound.config` by path. + +*Both getters will error if you try to get `title`, as that can contain the player's saved server login.* + +#### `Json` root.setConfiguration(`String` key, `Json` value) + +Sets a configuration value in `/storage/starbound.config`. + +#### `Json` root.setConfigurationPath(`String` path, `Json` value) + +Sets a configuration value in `/storage/starbound.config` by path. + +*Both setters will error if you try to set `safeScripts`, as that can break Starbound's sandbox.* + +--- + +#### `JsonArray` root.allRecipes() + +Returns all recipes. + +--- + +# Player + +The player table now contains bindings which contains functions to save/load, access and modify the player's identity, mode, aim, emote and more. + +--- + +#### `Json` player.save() + +Serializes the player to Json the same way Starbound does for disk storage and returns it. + +#### `void` player.load(`Json` save) + +Reloads the player from a Json **save**. This will reset active ScriptPanes and scripts running on the player. + +--- + +#### `String` player.name() + +Returns the player's name. + +#### `void` player.setName(`String` name) + +Sets the player's name. + +--- + +#### `String` player.description() + +Returns the player's description. + +#### `void` player.setDescription(`String` description) + +Sets the player's description. The new description will not be networked buntil the player warps or respawns. + +--- + +#### `void` player.setSpecies(`String` species) + +Sets the player's species. Must be a valid species. + +--- + +#### `void` player.setGender(`String` gender) + +Sets the player's gender. + +--- + +#### `String` player.imagePath() + +If the player has a custom humanoid image path set, returns it. otherwise, returns `nil`. + +#### `void` player.setImagePath(`String` imagePath) + +Sets the player's image path. Specify `nil` to remove the image path. + +--- + +#### `Personality` player.personality() + +Returns the player's personality as a `table` containing a `string` idle, `string` armIdle, `Vec2F` headOffset and `Vec2F` armOffset. + +#### `void` player.setPersonality(`Personality` personality) + +Sets the player's personality. The **personality** must be a `table` containing at least one value as returned by `player.personality()`. + +--- + +#### `String` player.bodyDirectives() + +Returns the player's body directives. + +#### `void` player.setBodyDirectives(`String` bodyDirectives) + +Sets the player's body directives. + +--- + +#### `String` player.emoteDirectives() + +Returns the player's emote directives. + +#### `void` player.setEmoteDirectives(`String` emoteDirectives) + +Sets the player's emote directives. + +--- + +#### `String` player.hair() + +Returns the player's hair group. + +#### `void` player.setHair(`String` hairGroup) + +Sets the player's hair group. + +--- + +#### `String` player.hairType() + +Returns the player's hair type. + +#### `void` player.setHairType(`String` hairType) + +Sets the player's hair type. + +--- + +#### `String` player.hairDirectives() + +Returns the player's hair directives. + +#### `void` player.setHairDirectives(`String` hairDirectives) + +Sets the player's hair directives. + +--- + +#### `String` player.facialHair() + +Returns the player's facial hair type. Same as player.facialHairType? + +#### `void` player.setFacialHair(`String` facialHairGroup, `String` facialHairType, `String` facialHairDirectives) + +Sets the player's facial hair group, type, and directives. + +--- + +#### `String` player.facialHairType() + +Returns the player's facial hair type. + +#### `void` player.setFacialHairType(`String` facialHairType) + +Sets the player's facial hair type. + +--- + +#### `String` player.facialHairGroup() + +Returns the player's facial hair group. + +#### `void` player.setFacialHairGroup(`String` facialHairGroup) + +Sets the player's facial hair group. + +--- + +#### `String` player.facialHairDirectives() + +Returns the player's facial hair directives. + +#### `void` player.setFacialHairDirectives(`String` facialHairDirectives) + +Sets the player's facial hair directives. + +--- + +#### `String` player.facialMask() + +Returns the player's facial mask group. + +#### `void` player.setFacialMask(`String` facialMaskGroup, `String` facialMaskType, `String` facialMaskDirectives) + +Sets the player's facial mask group, type, and directives. + +--- + +#### `String` player.facialMaskDirectives() + +Returns the player's facial mask directives. + +#### `void` player.setFacialMaskDirectives(`String` facialMaskDirectives) + +Sets the player's facial mask directives. + +--- + +#### `PlayerMode` player.mode() + +Returns the player's mode. + +#### `void` player.setMode(`String` mode) + +Sets the player's mode. **mode** must be either `"casual"`, `"survival"` or `"hardcore"`. + +--- + +#### `Color` player.favoriteColor() + +Returns the player's favorite color. +It is used for the beam shown when wiring, placing, and highlighting with beam-tools (Matter Manipulator). + +#### `void` player.setFavoriteColor(`Color` color) + +Sets the player's favorite color. **color** can have an optional fourth value for transparency. + +--- + +#### `Vec2F` player.aimPosition() + +Returns the player's aim position. + +--- + +#### `void` player.emote(`String` emote, [`float` cooldown]) + +Makes the player do an emote with the default cooldown unless a **cooldown** is specified. + +#### `String, float` player.currentEmote() + +Returns the player's current emote and the seconds left in it. + +--- + +#### `unsigned` player.actionBarGroup() + +Returns the player's active action bar. + +#### `void` player.setActionBarGroup(`unsigned` barId) + +Sets the player's active action bar. + +#### `Variant` player.selectedActionBarSlot() + +Returns the player's selected action bar slot. + +#### `void` player.setSelectedActionBarSlot(`Variant` slot) + +Sets the player's selected action bar slot. + +#### `void` player.setDamageTeam(`DamageTeam` team) + +Sets the player's damage team. This must be called every frame to override the current damage team that the server has given the player (normally controlled by /pvp) + +--- + +#### `void` player.say(`String` line) + +Makes the player say a string. + +--- + +#### `Json` player.humanoidIdentity() + +Returns the specific humanoid identity of the player, containing information such as hair style and idle pose. + +#### `void` player.setHumanoidIdentity(`Json` humanoidIdentity) + +Sets the specific humanoid identity of the player. + +--- + +#### `ItemDescriptor` player.item(`ItemSlot` itemSlot) + +Returns the contents of the specified itemSlot. + +#### `void` player.setItem(`ItemSlot` itemSlot, `ItemDescriptor` item) + +Puts the specified item into the specified itemSlot. +Item slots in item bags are structured like so: `{String bagName, int slot}` + +--- + +#### `int` player.itemBagSize(`String` itemBagName) + +Returns the size of an item bag. + +#### `bool` player.itemAllowedInBag(`String` itemBagName, `ItemDescriptor` item) + +Returns whether the specified item can enter the specified item bag. + +--- + +#### `ActionBarLink` player.actionBarSlotLink(`int` slot, `String` hand) + +Returns the contents of the specified action bar link slot's specified hand. + +#### `bool` player.setActionBarSlotLink(`String` itemBagName, `ItemDescriptor` item) + +Returns whether the specified item can enter the specified item bag. + +--- + +#### `Float` player.interactRadius() + +Returns the player's interact radius. + +#### `void` player.setInteractRadius(`Float` interactRadius) + +Sets the player's interact radius. This does not persist upon returning to the main menu. + +--- + +#### `JsonArray` player.availableRecipes() + +Returns all the recipes the player can craft with their currently held items and currencies. + +--- diff --git a/scripts/ci/linux/assemble.sh b/scripts/ci/linux/assemble.sh index 965d1b3..dbdfbe0 100755 --- a/scripts/ci/linux/assemble.sh +++ b/scripts/ci/linux/assemble.sh @@ -21,6 +21,7 @@ cp \ lib/linux/libsteam_api.so \ scripts/ci/linux/sbinit.config \ scripts/ci/linux/run-client.sh \ + scripts/steam_appid.txt \ client_distribution/linux/ mkdir -p server_distribution @@ -33,11 +34,21 @@ touch server_distribution/mods/mods_go_here mkdir -p server_distribution/linux +# makes the server function on older Linux versions (this is so stupid) +nm --dynamic --undefined-only --with-symbol-versions dist/starbound_server | grep GLIBC_2.29 +./scripts/ci/linux/patchelf dist/starbound_server \ + --clear-symbol-version exp \ + --clear-symbol-version exp2 \ + --clear-symbol-version log \ + --clear-symbol-version log2 \ + --clear-symbol-version pow + cp \ dist/starbound_server \ dist/btree_repacker \ scripts/ci/linux/run-server.sh \ scripts/ci/linux/sbinit.config \ + scripts/steam_appid.txt \ server_distribution/linux/ tar -cvf dist.tar dist diff --git a/scripts/ci/linux/patchelf b/scripts/ci/linux/patchelf new file mode 100755 index 0000000..6f0ba61 Binary files /dev/null and b/scripts/ci/linux/patchelf differ diff --git a/scripts/ci/macos/assemble.sh b/scripts/ci/macos/assemble.sh index e60b2c0..2e8164d 100755 --- a/scripts/ci/macos/assemble.sh +++ b/scripts/ci/macos/assemble.sh @@ -24,4 +24,5 @@ cp \ dist/planet_mapgen \ scripts/ci/macos/sbinit.config \ scripts/ci/macos/run-server.sh \ + scripts/steam_appid.txt \ client_distribution/osx/ \ No newline at end of file diff --git a/scripts/ci/windows/assemble.bat b/scripts/ci/windows/assemble.bat index 68532c0..e7f4dc3 100644 --- a/scripts/ci/windows/assemble.bat +++ b/scripts/ci/windows/assemble.bat @@ -8,6 +8,7 @@ mkdir %client%\mods mkdir %client%\logs mkdir %client%\assets mkdir %client%\win +echo 211820 > %client%\win\steam_appid.txt set server=server_distribution if exist %server% rmdir %server% /S /Q diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 68b65e8..d13d59d 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -136,6 +136,8 @@ endif() option(STAR_LUA_APICHECK "Use lua api checks" OFF) option(STAR_USE_JEMALLOC "Use jemalloc allocators" OFF) +option(STAR_USE_MIMALLOC "Use mimalloc allocators" OFF) +option(STAR_USE_RPMALLOC "Use rpmalloc allocators" OFF) # Report all the discovered system / environment settings and all options. @@ -174,6 +176,8 @@ endif() message(STATUS "Using Lua API checks: ${STAR_LUA_APICHECK}") message(STATUS "Using jemalloc: ${STAR_USE_JEMALLOC}") +message(STATUS "Using mimalloc: ${STAR_USE_MIMALLOC}") +message(STATUS "Using rpmalloc: ${STAR_USE_RPMALLOC}") # Set C defines and cmake variables based on the build settings we have now # determined... @@ -244,6 +248,10 @@ endif() if(STAR_USE_JEMALLOC) add_definitions(-DSTAR_USE_JEMALLOC) +elseif(STAR_USE_MIMALLOC) + add_definitions(-DSTAR_USE_MIMALLOC) +elseif(STAR_USE_RPMALLOC) + add_definitions(-DSTAR_USE_RPMALLOC -DENABLE_PRELOAD) endif() # Set C/C++ compiler flags based on build environment... @@ -449,6 +457,11 @@ if(STAR_USE_JEMALLOC) set(STAR_EXT_LIBS ${JEMALLOC_LIBRARY}) endif() +if (STAR_USE_MIMALLOC) + find_package(mimalloc CONFIG REQUIRED) + set(STAR_EXT_LIBS ${STAR_EXT_LIBS} $,mimalloc-static,mimalloc>) +endif() + find_package(ZLIB REQUIRED) find_package(PNG REQUIRED) find_package(Freetype REQUIRED) diff --git a/source/CMakePresets.json b/source/CMakePresets.json index 0799e8a..c19adae 100644 --- a/source/CMakePresets.json +++ b/source/CMakePresets.json @@ -31,7 +31,8 @@ "VCPKG_TARGET_TRIPLET": "x64-windows-mixed-md", "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL", "CMAKE_INCLUDE_PATH": "${sourceParentDir}/lib/windows/include", - "CMAKE_LIBRARY_PATH": "${sourceParentDir}/lib/windows" + "CMAKE_LIBRARY_PATH": "${sourceParentDir}/lib/windows", + "STAR_USE_RPMALLOC": true }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { diff --git a/source/application/StarMainApplication.hpp b/source/application/StarMainApplication.hpp index 18c9fa5..5ba06e7 100644 --- a/source/application/StarMainApplication.hpp +++ b/source/application/StarMainApplication.hpp @@ -18,6 +18,10 @@ namespace Star { LPWSTR* argsList = CommandLineToArgvW(GetCommandLineW(), &nArgs); \ Star::StringList args; \ for (int i = 0; i < nArgs; ++i) args.append(Star::String(argsList[i])); \ + if (IsDebuggerPresent() && AllocConsole()) { \ + freopen("CONOUT$", "w", stdout); \ + freopen("CONOUT$", "w", stderr); \ + } \ return Star::runMainApplication(Star::make_unique(), args); \ } diff --git a/source/application/StarMainApplication_sdl.cpp b/source/application/StarMainApplication_sdl.cpp index 277df57..6b0ca4c 100644 --- a/source/application/StarMainApplication_sdl.cpp +++ b/source/application/StarMainApplication_sdl.cpp @@ -90,23 +90,23 @@ Maybe keyFromSdlKeyCode(SDL_Keycode sym) { {SDLK_y, Key::Y}, {SDLK_z, Key::Z}, {SDLK_DELETE, Key::Delete}, - {SDLK_KP_0, Key::Kp0}, - {SDLK_KP_1, Key::Kp1}, - {SDLK_KP_2, Key::Kp2}, - {SDLK_KP_3, Key::Kp3}, - {SDLK_KP_4, Key::Kp4}, - {SDLK_KP_5, Key::Kp5}, - {SDLK_KP_6, Key::Kp6}, - {SDLK_KP_7, Key::Kp7}, - {SDLK_KP_8, Key::Kp8}, - {SDLK_KP_9, Key::Kp9}, - {SDLK_KP_PERIOD, Key::Kp_period}, - {SDLK_KP_DIVIDE, Key::Kp_divide}, - {SDLK_KP_MULTIPLY, Key::Kp_multiply}, - {SDLK_KP_MINUS, Key::Kp_minus}, - {SDLK_KP_PLUS, Key::Kp_plus}, - {SDLK_KP_ENTER, Key::Kp_enter}, - {SDLK_KP_EQUALS, Key::Kp_equals}, + {SDLK_KP_0, Key::Keypad0}, + {SDLK_KP_1, Key::Keypad1}, + {SDLK_KP_2, Key::Keypad2}, + {SDLK_KP_3, Key::Keypad3}, + {SDLK_KP_4, Key::Keypad4}, + {SDLK_KP_5, Key::Keypad5}, + {SDLK_KP_6, Key::Keypad6}, + {SDLK_KP_7, Key::Keypad7}, + {SDLK_KP_8, Key::Keypad8}, + {SDLK_KP_9, Key::Keypad9}, + {SDLK_KP_PERIOD, Key::KeypadPeriod}, + {SDLK_KP_DIVIDE, Key::KeypadDivide}, + {SDLK_KP_MULTIPLY, Key::KeypadMultiply}, + {SDLK_KP_MINUS, Key::KeypadMinus}, + {SDLK_KP_PLUS, Key::KeypadPlus}, + {SDLK_KP_ENTER, Key::KeypadEnter}, + {SDLK_KP_EQUALS, Key::KeypadEquals}, {SDLK_UP, Key::Up}, {SDLK_DOWN, Key::Down}, {SDLK_RIGHT, Key::Right}, @@ -131,6 +131,15 @@ Maybe keyFromSdlKeyCode(SDL_Keycode sym) { {SDLK_F13, Key::F13}, {SDLK_F14, Key::F14}, {SDLK_F15, Key::F15}, + {SDLK_F16, Key::F16}, + {SDLK_F17, Key::F17}, + {SDLK_F18, Key::F18}, + {SDLK_F19, Key::F19}, + {SDLK_F20, Key::F20}, + {SDLK_F21, Key::F21}, + {SDLK_F22, Key::F22}, + {SDLK_F23, Key::F23}, + {SDLK_F24, Key::F24}, {SDLK_NUMLOCKCLEAR, Key::NumLock}, {SDLK_CAPSLOCK, Key::CapsLock}, {SDLK_SCROLLLOCK, Key::ScrollLock}, @@ -235,7 +244,9 @@ public: SDL_free(basePath); } +#if SDL_VERSION_ATLEAST(2, 0, 18) SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); +#endif m_signalHandler.setHandleInterrupt(true); m_signalHandler.setHandleFatal(true); @@ -303,6 +314,10 @@ public: int height; SDL_GetWindowSize(m_sdlWindow, &width, &height); m_windowSize = Vec2U(width, height); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); m_sdlGlContext = SDL_GL_CreateContext(m_sdlWindow); if (!m_sdlGlContext) diff --git a/source/application/StarP2PNetworkingService_pc.cpp b/source/application/StarP2PNetworkingService_pc.cpp index 2d20415..0e13dee 100644 --- a/source/application/StarP2PNetworkingService_pc.cpp +++ b/source/application/StarP2PNetworkingService_pc.cpp @@ -81,7 +81,11 @@ void PcP2PNetworkingService::setJoinRemote(HostAddressWithPort location) { setJoinLocation(JoinRemote(location)); } -void Star::PcP2PNetworkingService::setActivityData([[maybe_unused]] String const& title, [[maybe_unused]] Maybe> party) { +void Star::PcP2PNetworkingService::setActivityData( + [[maybe_unused]] const char* title, + [[maybe_unused]] const char* details, + [[maybe_unused]] int64_t startTime, + [[maybe_unused]] Maybe> party) { #ifdef STAR_ENABLE_DISCORD_INTEGRATION MutexLocker discordLocker(m_state->discordMutex); #endif @@ -92,19 +96,25 @@ void Star::PcP2PNetworkingService::setActivityData([[maybe_unused]] String const if (m_discordUpdatingActivity) return; - if (title != m_discordActivityTitle || party != m_discordPartySize || m_discordForceUpdateActivity) { + if (title != m_discordActivityTitle + || details != m_discordActivityDetails + || startTime != m_discordActivityStartTime || party != m_discordPartySize || m_discordForceUpdateActivity) { m_discordForceUpdateActivity = false; m_discordPartySize = party; m_discordActivityTitle = title; + m_discordActivityDetails = details; + m_discordActivityStartTime = startTime; discord::Activity activity = {}; activity.SetType(discord::ActivityType::Playing); activity.SetName("Starbound"); - activity.SetState(title.utf8Ptr()); - - if (auto p = party) { - activity.GetParty().GetSize().SetCurrentSize(p->first); - activity.GetParty().GetSize().SetMaxSize(p->second); + activity.SetState(title); + activity.SetDetails(details); + activity.GetTimestamps().SetStart(startTime); + if (party) { + auto& size = activity.GetParty().GetSize(); + size.SetCurrentSize(party->first); + size.SetMaxSize(party->second); } if (auto lobby = m_discordServerLobby) diff --git a/source/application/StarP2PNetworkingService_pc.hpp b/source/application/StarP2PNetworkingService_pc.hpp index 20fbefb..293b6bf 100644 --- a/source/application/StarP2PNetworkingService_pc.hpp +++ b/source/application/StarP2PNetworkingService_pc.hpp @@ -18,7 +18,7 @@ public: void setJoinUnavailable() override; void setJoinLocal(uint32_t capacity) override; void setJoinRemote(HostAddressWithPort location) override; - void setActivityData(String const& title, Maybe>) override; + void setActivityData(const char* title, const char* details, int64_t startTime, Maybe>) override; MVariant pullPendingJoin() override; Maybe>> pullJoinRequest() override; @@ -125,6 +125,8 @@ private: HashMap m_discordOpenSockets; String m_discordActivityTitle; + String m_discordActivityDetails; + int64_t m_discordActivityStartTime = 0; Maybe> m_discordPartySize; bool m_discordForceUpdateActivity = false; bool m_discordUpdatingActivity = false; diff --git a/source/application/StarRenderer_opengl.cpp b/source/application/StarRenderer_opengl.cpp index f0e4dfd..0c9e7ec 100644 --- a/source/application/StarRenderer_opengl.cpp +++ b/source/application/StarRenderer_opengl.cpp @@ -8,7 +8,7 @@ namespace Star { size_t const MultiTextureCount = 4; char const* DefaultVertexShader = R"SHADER( -#version 130 +#version 140 uniform vec2 textureSize0; uniform vec2 textureSize1; @@ -49,7 +49,7 @@ void main() { )SHADER"; char const* DefaultFragmentShader = R"SHADER( -#version 130 +#version 140 uniform sampler2D texture0; uniform sampler2D texture1; @@ -65,13 +65,13 @@ out vec4 outColor; void main() { vec4 texColor; if (fragmentTextureIndex == 3) - texColor = texture2D(texture3, fragmentTextureCoordinate); + texColor = texture(texture3, fragmentTextureCoordinate); else if (fragmentTextureIndex == 2) - texColor = texture2D(texture2, fragmentTextureCoordinate); + texColor = texture(texture2, fragmentTextureCoordinate); else if (fragmentTextureIndex == 1) - texColor = texture2D(texture1, fragmentTextureCoordinate); + texColor = texture(texture1, fragmentTextureCoordinate); else - texColor = texture2D(texture0, fragmentTextureCoordinate); + texColor = texture(texture0, fragmentTextureCoordinate); if (texColor.a <= 0.0) discard; @@ -80,9 +80,19 @@ void main() { } )SHADER"; +/* +static void GLAPIENTRY GlMessageCallback(GLenum, GLenum type, GLuint, GLenum, GLsizei, const GLchar* message, const void* renderer) { + if (type == GL_DEBUG_TYPE_ERROR) { + Logger::error("GL ERROR: {}", message); + __debugbreak(); + } +} +*/ + OpenGlRenderer::OpenGlRenderer() { - if (glewInit() != GLEW_OK) - throw RendererException("Could not initialize GLEW"); + auto glewResult = glewInit(); + if (glewResult != GLEW_OK && glewResult != GLEW_ERROR_NO_GLX_DISPLAY) + throw RendererException::format("Could not initialize GLEW: {}", (char*)glewGetErrorString(glewResult)); if (!GLEW_VERSION_2_0) throw RendererException("OpenGL 2.0 not available!"); @@ -94,10 +104,11 @@ OpenGlRenderer::OpenGlRenderer() { (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); glClearColor(0.0, 0.0, 0.0, 1.0); - glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); + //glEnable(GL_DEBUG_OUTPUT); + //glDebugMessageCallback(GlMessageCallback, this); m_whiteTexture = createGlTexture(Image::filled({1, 1}, Vec4B(255, 255, 255, 255), PixelFormat::RGBA32), TextureAddressing::Clamp, @@ -142,12 +153,32 @@ OpenGlRenderer::GlFrameBuffer::GlFrameBuffer(Json const& fbConfig) : config(fbCo GLenum target = multisample ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; glBindTexture(target, texture->glTextureId()); - Vec2U size = jsonToVec2U(config.getArray("size", { 256, 256 })); + sizeDiv = config.getUInt("sizeDiv", 1); + Vec2U size = jsonToVec2U(config.getArray("size", { 256, 256 })) / sizeDiv; if (multisample) glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, multisample, GL_RGBA8, size[0], size[1], GL_TRUE); - else + else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0], size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + } + auto addressing = TextureAddressingNames.getLeft(config.getString("textureAddressing", "clamp")); + auto filtering = TextureFilteringNames.getLeft(config.getString("textureFiltering", "nearest")); + if (addressing == TextureAddressing::Clamp) { + glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } else { + glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + if (!multisample) { + if (filtering == TextureFiltering::Nearest) { + glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } else { + glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + } glGenFramebuffers(1, &id); if (!id) @@ -173,6 +204,7 @@ void OpenGlRenderer::loadConfig(Json const& config) { for (auto& pair : config.getObject("frameBuffers", {})) { Json config = pair.second; config = config.set("multisample", m_multiSampling); + Logger::info("Creating framebuffer {}", pair.first); m_frameBuffers[pair.first] = make_ref(config); } @@ -246,15 +278,16 @@ void OpenGlRenderer::loadEffectConfig(String const& name, Json const& effectConf auto& effect = m_effects.emplace(name, Effect()).first->second; effect.program = m_program; effect.config = effectConfig; + effect.includeVBTextures = effectConfig.getBool("includeVBTextures",true); m_currentEffect = &effect; - setupGlUniforms(effect); + setupGlUniforms(effect, m_screenSize); for (auto const& p : effectConfig.getObject("effectParameters", {})) { EffectParameter effectParameter; effectParameter.parameterUniform = glGetUniformLocation(m_program, p.second.getString("uniform").utf8Ptr()); if (effectParameter.parameterUniform == -1) { - Logger::warn("OpenGL20 effect parameter '{}' has no associated uniform, skipping", p.first); + Logger::warn("OpenGL20 effect parameter '{}' in effect '{}' has no associated uniform, skipping", p.first, name); } else { String type = p.second.getString("type"); if (type == "bool") { @@ -296,7 +329,7 @@ void OpenGlRenderer::loadEffectConfig(String const& name, Json const& effectConf // Assign each texture parameter a texture unit starting with MultiTextureCount, the first // few texture units are used by the primary textures being drawn. Currently, // maximum texture units are not checked. - unsigned parameterTextureUnit = MultiTextureCount; + unsigned parameterTextureUnit = effect.includeVBTextures ? MultiTextureCount : 0; for (auto const& p : effectConfig.getObject("effectTextures", {})) { EffectTexture effectTexture; @@ -383,19 +416,39 @@ bool OpenGlRenderer::switchEffectConfig(String const& name) { if (auto blitFrameBufferId = effect.config.optString("blitFrameBuffer")) blitGlFrameBuffer(getGlFrameBuffer(*blitFrameBufferId)); - if (auto frameBufferId = effect.config.optString("frameBuffer")) - switchGlFrameBuffer(getGlFrameBuffer(*frameBufferId)); - else { + auto effectScreenSize = m_screenSize; + if (auto frameBufferId = effect.config.optString("frameBuffer")) { + auto buf = getGlFrameBuffer(*frameBufferId); + switchGlFrameBuffer(buf); + effectScreenSize = m_screenSize / (buf->sizeDiv); + } else { m_currentFrameBuffer.reset(); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } glUseProgram(m_program = effect.program); - setupGlUniforms(effect); + setupGlUniforms(effect, effectScreenSize); m_currentEffect = &effect; setEffectParameter("vertexRounding", m_multiSampling > 0); - + if (auto fbts = effect.config.optArray("frameBufferTextures")) { + for (auto const& fbt : *fbts) { + if (auto frameBufferId = fbt.optString("framebuffer")) { + auto textureUniform = fbt.getString("texture"); + auto ptr = m_currentEffect->textures.ptr(textureUniform); + if (ptr) { + if (!ptr->textureValue || ptr->textureValue->textureId == 0) { + auto texture = getGlFrameBuffer(*frameBufferId)->texture; + ptr->textureValue = texture; + if (ptr->textureSizeUniform != -1) { + auto textureSize = ptr->textureValue->glTextureSize(); + glUniform2f(ptr->textureSizeUniform, textureSize[0], textureSize[1]); + } + } + } + } + } + } return true; } @@ -494,12 +547,13 @@ void OpenGlRenderer::setScreenSize(Vec2U screenSize) { glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]); for (auto& frameBuffer : m_frameBuffers) { + unsigned sizeDiv = frameBuffer.second->sizeDiv; if (unsigned multisample = frameBuffer.second->multisample) { glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, frameBuffer.second->texture->glTextureId()); - glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, multisample, GL_RGBA8, m_screenSize[0], m_screenSize[1], GL_TRUE); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, multisample, GL_RGBA8, m_screenSize[0] / sizeDiv, m_screenSize[1] / sizeDiv, GL_TRUE); } else { glBindTexture(GL_TEXTURE_2D, frameBuffer.second->texture->glTextureId()); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_screenSize[0], m_screenSize[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_screenSize[0] / sizeDiv, m_screenSize[1] / sizeDiv, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); } } } @@ -694,6 +748,10 @@ Vec2U OpenGlRenderer::GlLoneTexture::glTextureCoordinateOffset() const { return Vec2U(); } +OpenGlRenderer::GlRenderBuffer::GlRenderBuffer() { + glGenVertexArrays(1, &vertexArray); +} + OpenGlRenderer::GlRenderBuffer::~GlRenderBuffer() { for (auto const& texture : usedTextures) { if (auto gt = as(texture.get())) @@ -701,6 +759,7 @@ OpenGlRenderer::GlRenderBuffer::~GlRenderBuffer() { } for (auto const& vb : vertexBuffers) glDeleteBuffers(1, &vb.vertexBuffer); + glDeleteVertexArrays(1, &vertexArray); } void OpenGlRenderer::GlRenderBuffer::set(List& primitives) { @@ -715,7 +774,7 @@ void OpenGlRenderer::GlRenderBuffer::set(List& primitives) { List currentTextures; List currentTextureSizes; size_t currentVertexCount = 0; - + glBindVertexArray(vertexArray); auto finishCurrentBuffer = [&]() { if (currentVertexCount > 0) { GlVertexBuffer vb; @@ -947,10 +1006,12 @@ void OpenGlRenderer::renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F co for (auto const& vb : renderBuffer.vertexBuffers) { glUniformMatrix3fv(m_vertexTransformUniform, 1, GL_TRUE, transformation.ptr()); - for (size_t i = 0; i < vb.textures.size(); ++i) { - glUniform2f(m_textureSizeUniforms[i], vb.textures[i].size[0], vb.textures[i].size[1]); - glActiveTexture(GL_TEXTURE0 + i); - glBindTexture(GL_TEXTURE_2D, vb.textures[i].texture); + if (m_currentEffect->includeVBTextures) { + for (size_t i = 0; i < vb.textures.size(); ++i) { + glUniform2f(m_textureSizeUniforms[i], vb.textures[i].size[0], vb.textures[i].size[1]); + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, vb.textures[i].texture); + } } for (auto const& p : m_currentEffect->textures) { @@ -977,7 +1038,7 @@ void OpenGlRenderer::renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F co } //Assumes the passed effect program is currently in use. -void OpenGlRenderer::setupGlUniforms(Effect& effect) { +void OpenGlRenderer::setupGlUniforms(Effect& effect, Vec2U screenSize) { m_positionAttribute = effect.getAttribute("vertexPosition"); m_colorAttribute = effect.getAttribute("vertexColor"); m_texCoordAttribute = effect.getAttribute("vertexTextureCoordinate"); @@ -985,17 +1046,21 @@ void OpenGlRenderer::setupGlUniforms(Effect& effect) { m_textureUniforms.clear(); m_textureSizeUniforms.clear(); - for (size_t i = 0; i < MultiTextureCount; ++i) { - m_textureUniforms.append(effect.getUniform(strf("texture{}", i).c_str())); - m_textureSizeUniforms.append(effect.getUniform(strf("textureSize{}", i).c_str())); + if (effect.includeVBTextures) { + for (size_t i = 0; i < MultiTextureCount; ++i) { + m_textureUniforms.append(effect.getUniform(strf("texture{}", i).c_str())); + m_textureSizeUniforms.append(effect.getUniform(strf("textureSize{}", i).c_str())); + } } m_screenSizeUniform = effect.getUniform("screenSize"); m_vertexTransformUniform = effect.getUniform("vertexTransform"); - for (size_t i = 0; i < MultiTextureCount; ++i) - glUniform1i(m_textureUniforms[i], i); + if (effect.includeVBTextures) { + for (size_t i = 0; i < MultiTextureCount; ++i) + glUniform1i(m_textureUniforms[i], i); + } - glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]); + glUniform2f(m_screenSizeUniform, screenSize[0], screenSize[1]); } RefPtr OpenGlRenderer::getGlFrameBuffer(String const& id) { diff --git a/source/application/StarRenderer_opengl.hpp b/source/application/StarRenderer_opengl.hpp index 61c67c9..36b8354 100644 --- a/source/application/StarRenderer_opengl.hpp +++ b/source/application/StarRenderer_opengl.hpp @@ -143,6 +143,7 @@ private: size_t vertexCount = 0; }; + GlRenderBuffer(); ~GlRenderBuffer(); void set(List& primitives) override; @@ -152,6 +153,7 @@ private: HashSet usedTextures; List vertexBuffers; + GLuint vertexArray = 0; bool useMultiTexturing{true}; }; @@ -178,6 +180,7 @@ private: Json config; bool blitted = false; unsigned multisample = 0; + unsigned sizeDiv = 1; GlFrameBuffer(Json const& config); ~GlFrameBuffer(); @@ -195,6 +198,7 @@ private: GLuint getAttribute(String const& name); GLuint getUniform(String const& name); + bool includeVBTextures; }; static bool logGlErrorSummary(String prefix); @@ -209,7 +213,7 @@ private: void renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation); - void setupGlUniforms(Effect& effect); + void setupGlUniforms(Effect& effect, Vec2U screenSize); RefPtr getGlFrameBuffer(String const& id); void blitGlFrameBuffer(RefPtr const& frameBuffer); diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp index 68e307a..a188291 100644 --- a/source/base/StarAssets.cpp +++ b/source/base/StarAssets.cpp @@ -104,6 +104,7 @@ Maybe FramesSpecification::getRect(String const& frame) const { Assets::Assets(Settings settings, StringList assetSources) { const char* AssetsPatchSuffix = ".patch"; + const char* AssetsPatchListSuffix = ".patchlist"; const char* AssetsLuaPatchSuffix = ".patch.lua"; m_settings = std::move(settings); @@ -123,6 +124,7 @@ Assets::Assets(Settings settings, StringList assetSources) { LuaCallbacks callbacks; callbacks.registerCallbackWithSignature("byExtension", bind(&Assets::scanExtension, this, _1)); callbacks.registerCallbackWithSignature("json", bind(&Assets::json, this, _1)); + callbacks.registerCallbackWithSignature("exists", bind(&Assets::assetExists, this, _1)); callbacks.registerCallback("bytes", [this](String const& path) -> String { auto assetBytes = bytes(path); @@ -204,6 +206,24 @@ Assets::Assets(Settings settings, StringList assetSources) { auto targetPatchFile = filename.substr(0, filename.size() - strlen(AssetsLuaPatchSuffix)); if (auto p = m_files.ptr(targetPatchFile)) p->patchSources.append({filename, source}); + } else if (filename.endsWith(AssetsPatchListSuffix, String::CaseInsensitive)) { + auto stream = source->read(filename); + size_t patchIndex = 0; + for (auto const& patchPair : inputUtf8Json(stream.begin(), stream.end(), JsonParseType::Top).iterateArray()) { + auto patches = patchPair.getArray("patches"); + for (auto& path : patchPair.getArray("paths")) { + if (auto p = m_files.ptr(path.toString())) { + for (size_t i = 0; i != patches.size(); ++i) { + auto& patch = patches[i]; + if (patch.isType(Json::Type::String)) + p->patchSources.append({patch.toString(), source}); + else + p->patchSources.append({strf("{}:[{}].patches[{}]", filename, patchIndex, i), source}); + } + } + } + patchIndex++; + } } else { for (int i = 0; i < 10; i++) { if (filename.endsWith(AssetsPatchSuffix + toString(i), String::CaseInsensitive)) { @@ -289,7 +309,7 @@ Assets::Assets(Settings settings, StringList assetSources) { digest.push(assetPath); digest.push(DataStreamBuffer::serialize(descriptor.source->open(descriptor.sourceName)->size())); for (auto const& pair : descriptor.patchSources) - digest.push(DataStreamBuffer::serialize(pair.second->open(pair.first)->size())); + digest.push(DataStreamBuffer::serialize(pair.second->open(AssetPath::removeSubPath(pair.first))->size())); } } @@ -956,32 +976,35 @@ Json Assets::readJson(String const& path) const { try { Json result = inputUtf8Json(streamData.begin(), streamData.end(), JsonParseType::Top); for (auto const& pair : m_files.get(path).patchSources) { - auto& patchPath = pair.first; + auto patchAssetPath = AssetPath::split(pair.first); + auto& patchBasePath = patchAssetPath.basePath; auto& patchSource = pair.second; - auto patchStream = patchSource->read(patchPath); - if (patchPath.endsWith(".lua")) { + auto patchStream = patchSource->read(patchBasePath); + if (patchBasePath.endsWith(".lua")) { RecursiveMutexLocker luaLocker(m_luaMutex); // Kae: i don't like that lock. perhaps have a LuaEngine and patch context cache per worker thread later on? - LuaContextPtr& context = m_patchContexts[patchPath]; + LuaContextPtr& context = m_patchContexts[patchBasePath]; if (!context) { context = make_shared(as(m_luaEngine.get())->createContext()); - context->load(patchStream, patchPath); + context->load(patchStream, patchBasePath); } auto newResult = context->invokePath("patch", result, path); if (newResult) result = std::move(newResult); } else { auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), JsonParseType::Top); + if (patchAssetPath.subPath) + patchJson = patchJson.query(*patchAssetPath.subPath); if (patchJson.isType(Json::Type::Array)) { - auto patchData = patchJson.toArray(); - try { - result = checkPatchArray(patchPath, patchSource, result, patchData, {}); - } catch (JsonPatchTestFail const& e) { - Logger::debug("Patch test failure from file {} in source: '{}' at '{}'. Caused by: {}", patchPath, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what()); - } catch (JsonPatchException const& e) { - Logger::error("Could not apply patch from file {} in source: '{}' at '{}'. Caused by: {}", patchPath, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what()); - } - } else if (patchJson.isType(Json::Type::Object)) {//Kae: Do a good ol' json merge instead if the .patch file is a Json object + auto patchData = patchJson.toArray(); + try { + result = checkPatchArray(pair.first, patchSource, result, patchData, {}); + } catch (JsonPatchTestFail const& e) { + Logger::debug("Patch test failure from file {} in source: '{}' at '{}'. Caused by: {}", pair.first, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what()); + } catch (JsonPatchException const& e) { + Logger::error("Could not apply patch from file {} in source: '{}' at '{}'. Caused by: {}", pair.first, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what()); + } + } else if (patchJson.isType(Json::Type::Object)) { result = jsonMergeNulling(result, patchJson.toObject()); } } @@ -1219,7 +1242,7 @@ shared_ptr Assets::loadImage(AssetPath const& path) const { shared_ptr Assets::loadAudio(AssetPath const& path) const { return unlockDuring([&]() { auto newData = make_shared(); - newData->audio = make_shared