Compare commits

...

15 commits

Author SHA1 Message Date
RandomityGuy
59c0842e64 update rreadme for mbg update 2026-06-16 01:38:54 +01:00
RandomityGuy
3d3f7b816a add info about the linux port 2026-06-15 18:11:14 +01:00
RandomityGuy
cde152c6a1 add steam deck conf 2026-06-15 17:34:44 +01:00
RandomityGuy
5698770502 linux define 2026-06-15 14:05:21 +01:00
RandomityGuy
ee23828f4d add linux support 2026-06-15 13:42:21 +01:00
RandomityGuy
6b32f92d4d update readme for iOS version 2026-06-15 02:25:07 +01:00
RandomityGuy
9b4d8a9b5e - fix some loading issues on web
- now properly load bitmaps via browser api instead for js
- fix negative material values not handled
- fix multiplayer not loading with multiple people on web
2026-05-21 14:55:08 +01:00
RandomityGuy
786dcb169e fix this texture load lagspike issue 2026-05-15 02:30:43 +01:00
RandomityGuy
717d4eb5c6 update readme 2026-04-06 22:42:11 +01:00
RandomityGuy
6fd8d12386 next update 2026-04-06 13:24:40 +01:00
RandomityGuy
6ffd04038c fix chat escaping 2026-04-06 12:31:24 +01:00
RandomityGuy
7a648a8aeb this button too 2026-04-05 02:40:36 +01:00
RandomityGuy
14c7885f58 this last change 2026-04-03 17:33:49 +01:00
RandomityGuy
1ec04251e8 update changelog 2026-04-03 00:22:31 +01:00
RandomityGuy
d7b1e68b21 try this to make them be singleplayer 2026-04-02 20:46:13 +01:00
17 changed files with 449 additions and 68 deletions

View file

@ -452,6 +452,235 @@ jobs:
# - store_artifacts: # - store_artifacts:
# path: ~/project/MBHaxe-Platinum-Win.zip # path: ~/project/MBHaxe-Platinum-Win.zip
build-lin:
docker:
- image: registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest
environment:
COMMIT_TAG: pipeline.git.tag
steps:
- run:
name: Install build dependencies
command: |
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt remove -y libsdl2-compat-shim libsdl2-compat-shim:i386
apt-get install -y --no-install-recommends \
git curl ca-certificates openssh-client patchelf file \
cmake ninja-build pkg-config build-essential \
libpng-dev libturbojpeg0-dev libvorbis-dev libopenal-dev \
libmbedtls-dev libuv1-dev libsqlite3-dev \
zlib1g-dev libssl-dev libsdl2-dev
- add_ssh_keys:
fingerprints:
- "82:42:56:a0:57:43:95:4e:00:c0:8c:c1:7f:70:74:47"
- checkout:
path: ~/MBHaxe
- run:
name: Install Haxe
command: |
set -eux
rm -rf "$HOME/haxe" "$HOME/neko" "$HOME/haxelib"
mkdir -p "$HOME/haxe" "$HOME/neko" "$HOME/haxelib"
curl -fsSL --retry 3 --retry-delay 5 \
https://github.com/HaxeFoundation/haxe/releases/download/4.3.6/haxe-4.3.6-linux64.tar.gz \
| tar xz -C "$HOME/haxe" --strip-components=1
curl -fsSL --retry 3 --retry-delay 5 \
https://github.com/HaxeFoundation/neko/releases/download/v2-4-0/neko-2.4.0-linux64.tar.gz \
| tar xz -C "$HOME/neko" --strip-components=1
export PATH="$HOME/haxe:$HOME/neko:$PATH"
export HAXE_STD_PATH="$HOME/haxe/std"
export LD_LIBRARY_PATH="$HOME/neko${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
haxelib setup "$HOME/haxelib"
- run:
name: Install HashLink
command: |
rm -rf "$HOME/deps"
mkdir -p "$HOME/deps"
cd "$HOME/deps"
git clone --depth=1 https://github.com/RandomityGuy/hashlink
git clone --depth=1 https://github.com/RandomityGuy/hxDatachannel
cd hashlink
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_FIND_FRAMEWORK=LAST \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DWITH_SQLITE=OFF \
-DBUILD_TESTING=OFF \
-DHASHLINK_INCLUDE_DIR="$HOME/deps/hashlink/src" \
-DHASHLINK_LIBRARY_DIR="/usr/local/lib/"
cmake --build build --config Release -j"$(nproc)"
cmake --install build
ldconfig
- run:
name: Build hxDatachannel
command: |
cd "$HOME/deps/hxDatachannel/cpp"
sed -i 's/target_link_libraries(hxdatachannel.hdll libhl datachannel-static)/target_link_libraries(hxdatachannel.hdll hl datachannel-static)/' CMakeLists.txt || true
sed -i 's/agent->selected_entry = ATOMIC_VAR_INIT(NULL);/atomic_init(\&agent->selected_entry, NULL);/' \
libdatachannel/deps/libjuice/src/agent.c || true
cmake -S . -B build -G Ninja \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DCMAKE_C_STANDARD=11 \
-DCMAKE_C_EXTENSIONS=ON \
-DCMAKE_C_FLAGS="-std=gnu11 -fvisibility=hidden" \
-DCMAKE_CXX_FLAGS="-fvisibility=hidden" \
-DCMAKE_SHARED_LINKER_FLAGS="-Wl,-Bsymbolic -Wl,--exclude-libs,ALL" \
-DUSE_MBEDTLS=OFF \
-DUSE_GNUTLS=OFF \
-DHASHLINK_INCLUDE_DIR="$HOME/deps/hashlink/src" \
-DHASHLINK_LIBRARY_DIR="/usr/local/lib/" \
-DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"
DATACHANNEL_HDLL="$(find build -name 'datachannel.hdll' -type f | head -n 1)"
[ -n "$DATACHANNEL_HDLL" ] || {
echo "ERROR: datachannel.hdll not built"
exit 1
}
cp "$DATACHANNEL_HDLL" /usr/local/lib/
ldconfig
- run:
name: Install Haxe dependencies
command: |
export PATH="$HOME/haxe:$HOME/neko:$PATH"
export HAXE_STD_PATH="$HOME/haxe/std"
export LD_LIBRARY_PATH="$HOME/neko${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
haxelib dev hashlink "$HOME/deps/hashlink/other/haxelib"
haxelib git heaps https://github.com/RandomityGuy/heaps
haxelib dev hlopenal "$HOME/deps/hashlink/libs/openal"
haxelib dev hlsdl "$HOME/deps/hashlink/libs/sdl"
haxelib dev datachannel "$HOME/deps/hxDatachannel"
haxelib install colyseus-websocket --always
- run:
name: Compile MBHaxe
command: |
export PATH="$HOME/haxe:$HOME/neko:$PATH"
export HAXE_STD_PATH="$HOME/haxe/std"
export LD_LIBRARY_PATH="$HOME/neko${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
cd "$HOME/MBHaxe"
haxe compile-linux.hxml
cd native
cp "$HOME/deps/hashlink/src/hlc_main.c" .
gcc -o marblegame -O2 \
-I . \
-L /usr/local/lib \
marblegame.c \
/usr/local/lib/{ui.hdll,openal.hdll,fmt.hdll,sdl.hdll,uv.hdll,ssl.hdll,datachannel.hdll} \
-lSDL2 -lhl -lm -luv \
-Wl,-rpath,'$ORIGIN'
strip marblegame
- run:
name: Package portable bundle
command: |
DIST=$HOME/MBHaxe-Platinum-Linux
mkdir -p "$DIST"
# Native binary
cp $HOME/MBHaxe/native/marblegame "$DIST/"
# HashLink plugins
cp /usr/local/lib/{fmt,openal,sdl,ssl,ui,uv,datachannel}.hdll "$DIST/"
# libhl — copy versioned file and create soname symlink
LIBHL_REAL="$(realpath /usr/local/lib/libhl.so)"
LIBHL_BASE="$(basename "$LIBHL_REAL")"
cp "$LIBHL_REAL" "$DIST/$LIBHL_BASE"
LIBHL_SONAME="$(readelf -d "$LIBHL_REAL" | grep -oP '(?<=\[)libhl\.so[^\]]+(?=\])' || true)"
if [ -n "$LIBHL_SONAME" ] && [ "$LIBHL_SONAME" != "$LIBHL_BASE" ]; then
ln -sf "$LIBHL_BASE" "$DIST/$LIBHL_SONAME"
fi
ln -sf "${LIBHL_SONAME:-$LIBHL_BASE}" "$DIST/libhl.so"
ln -sf "${LIBHL_SONAME:-$LIBHL_BASE}" "$DIST/libhl.so.1"
# Bundle Steam Runtime system libs for portability
get_lib() {
ldconfig -p | grep "^\s*$1\b" | awk '{print $NF}' | head -n 1
}
for soname in libSDL2-2.0.so.0 libopenal.so.1 libuv.so.1 libturbojpeg.so.0; do
path="$(get_lib "$soname" || true)"
if [ -n "$path" ]; then
cp -L "$path" "$DIST/$soname"
else
echo "Warning: $soname not found"
fi
done
# mbedtls soname varies by version
for libbase in libmbedtls libmbedx509 libmbedcrypto; do
path="$(ldconfig -p | grep "^\s*${libbase}\.so\." | awk '{print $NF}' | head -n 1 || true)"
if [ -n "$path" ]; then
cp -L "$path" "$DIST/$(basename "$path")"
else
echo "Warning: $libbase not found"
fi
done
# Game data
cp -a $HOME/MBHaxe/data "$DIST/"
echo '#!/usr/bin/env bash' > "$DIST/run-mbu.sh"
echo 'set -e' >> "$DIST/run-mbu.sh"
echo 'DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"' >> "$DIST/run-mbu.sh"
echo 'export LD_LIBRARY_PATH="$DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"' >> "$DIST/run-mbu.sh"
echo 'cd "$DIR"' >> "$DIST/run-mbu.sh"
echo 'exec "$DIR/marblegame" "$@"' >> "$DIST/run-mbu.sh"
chmod +x "$DIST/run-mbu.sh"
echo '#!/usr/bin/env bash' > "$DIST/run-mbu-steam-deck.sh"
echo 'set -e' >> "$DIST/run-mbu-steam-deck.sh"
echo 'DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"' >> "$DIST/run-mbu-steam-deck.sh"
echo 'export LD_LIBRARY_PATH="$DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"' >> "$DIST/run-mbu-steam-deck.sh"
echo 'export ALSOFT_DRIVERS="pulse,alsa"' >> "$DIST/run-mbu-steam-deck.sh"
echo 'export SDL_AUDIODRIVER="pulse"' >> "$DIST/run-mbu-steam-deck.sh"
echo 'cd "$DIR"' >> "$DIST/run-mbu-steam-deck.sh"
echo 'exec "$DIR/marblegame" "$@"' >> "$DIST/run-mbu-steam-deck.sh"
chmod +x "$DIST/run-mbu-steam-deck.sh"
# Set rpath=$ORIGIN so the bundle is self-contained
patchelf --set-rpath '$ORIGIN' "$DIST/marblegame"
for f in "$DIST"/*.hdll "$DIST"/*.so "$DIST"/*.so.*; do
[ -f "$f" ] || continue
if file "$f" | grep -q ELF; then
patchelf --set-rpath '$ORIGIN' "$f" || true
fi
done
echo "Checking linked libraries..."
cd "$DIST"
LD_LIBRARY_PATH="$DIST" ldd ./marblegame | tee ldd.txt
if grep -q "not found" ldd.txt; then
echo "ERROR: missing libraries:"
grep "not found" ldd.txt
exit 1
fi
rm -f libhl.so libhl.so.1
ln -sf libhl.so.1.13.0 libhl.so.1
ln -sf libhl.so.1 libhl.so
cd $HOME
tar -czvf MBHaxe-Platinum-Linux.tar.gz MBHaxe-Platinum-Linux
- run:
name: Upload to Artifact Storage
command: |
scp -o StrictHostKeyChecking=no -i $KEYPATH -P $PORT \
$HOME/MBHaxe-Platinum-Linux.tar.gz \
$REMOTEDIR/MBHaxe-Platinum-Linux.tar.gz
# Invoke jobs via workflows # Invoke jobs via workflows
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows
@ -473,3 +702,12 @@ workflows:
only: /^\d+.\d+.\d+$/ only: /^\d+.\d+.\d+$/
branches: branches:
ignore: /.*/ ignore: /.*/
build-linux:
jobs:
- build-lin:
filters:
tags:
only: /^\d+.\d+.\d+$/
branches:
ignore: /.*/

View file

@ -1,3 +1,26 @@
# 1.7.3
Hotfix time!
- Fixed chat messages being escaped a bit too much.
# 1.7.2
This update brings the following bugfixes:
- Added Import and Export Progress to Options menu to transfer game progress between devices.
- Added momentum based scrolling on touch devices for menus.
- Added TURN server support for multiplayer. Players behind strict NATs should now be able to play multiplayer without issues.
- Made the game files to be case insensitive to allow running the game on case sensitive filesystems without issues.
- Escaped all user input to prevent HTML injection in the UI.
- Fixed various race condition issues.
- Improved camera sensitivity on touch devices.
- Implemented camera centering for touch controls when free look is disabled.
- Various performance improvements and crash fixes.
- Implemented console cheat commands. DefaultMarble.attribute = value; to change marble attributes.
- Fixed a bug with the timer when playing a replay.
- Fixed a crash that could happen in multiplayer.
- Fixed not being able to load textures in certain custom levels.
- Fixed softlock when playing a user installed custom level.
- Fixed gravity changes not rewinding properly.
- Fixed skies not rendering correctly at times in the web version.
# 1.7.1 # 1.7.1
This update brings the following bugfixes: This update brings the following bugfixes:
- Fixed a crash when the marble goes out of bounds. - Fixed a crash when the marble goes out of bounds.

View file

@ -10,29 +10,40 @@ The browser port supports touch controls, meaning it can be played on mobile dev
### Marble Blast Gold: [Play](https://marbleblastgold.randomityguy.me/) ### Marble Blast Gold: [Play](https://marbleblastgold.randomityguy.me/)
### Marble Blast Platinum: [Play](https://marbleblast.randomityguy.me/) ### Marble Blast Platinum: [Play](https://marbleblast.randomityguy.me/)
### Marble Blast Ultra: [Play](https://marbleblastultra.randomityguy.me/) ### Marble Blast Ultra: [Play](https://marbleblastultra.randomityguy.me/)
## Windows and Mac ## Windows, Mac and Linux
### Marble Blast Gold: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.1.12) ### Marble Blast Gold: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.1.14)
### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.7.1) ### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.7.3)
### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.2.5-mbu) ### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.2.5-mbu)
Linux port by [boucymatt](https://github.com/boucymatt). Supports Steam Deck.
## Mac Instructions - Important ## Mac Instructions - Important
Put the .app file in either /Applications or ~/Applications in order to run it properly. Put the .app file in either /Applications or ~/Applications in order to run it properly.
You will also have to bypass Gatekeeper since the .app is not signed. You will also have to bypass Gatekeeper since the .app is not signed.
## Android ## Android
### Marble Blast Gold: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.1.12/MBHaxe-Gold.apk) ### Marble Blast Gold: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.1.14/MBHaxe-Gold.apk)
### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.7.1/MBHaxe-Platinum.apk) ### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.7.3/MBHaxe-Platinum.apk)
### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.2.5-mbu/MBHaxe-Ultra.apk) ### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.2.5-mbu/MBHaxe-Ultra.apk)
## iOS (NEW!)
### Marble Blast Gold: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.1.14/MBHaxe-Gold-iOS.ipa)
### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.7.3/MBHaxe-Platinum-iOS.ipa)
### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.2.5-mbu/MBHaxe-Ultra-iOS.ipa)
Ported to iOS by [boucymatt](https://github.com/boucymatt).
iOS builds ship as `.ipa` files that must be sideloaded, as Apple does not allow direct installs outside the App Store. Use [LiveContainer](https://github.com/LiveContainer/LiveContainer) or an AltStore-compatible store (AltStore, SideStore, etc.). To install and update all builds from one place, add the AltStore source.
<a href="https://stikstore.app/altdirect/?url=https://dl.randomityguy.me/altstore-source.json" target="_blank">
<img src="https://raw.githubusercontent.com/StikStore/altdirect/refs/heads/main/assets/png/AltSource_Blue.png" alt="Add AltSource" width="200"/>
</a>
## Xbox (NEW!) ## Xbox (NEW!)
### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.2.5-mbu/MBHaxe-Ultra-UWP-Xbox.msix) ### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.2.5-mbu/MBHaxe-Ultra-UWP-Xbox.msix)
Ported to Xbox via UWP by [Daniel Worley](https://github.com/worleydl). Ported to Xbox via UWP by [Daniel Worley](https://github.com/worleydl).
You will need to enable Developer Mode on your Xbox in order to sideload the app. The walkthrough can be found at https://www.youtube.com/watch?v=2Ly9TIdu9uw. You will need to enable Developer Mode on your Xbox in order to sideload the app. The walkthrough can be found at https://www.youtube.com/watch?v=2Ly9TIdu9uw.
## Additional Features ## Additional Features
- Cross Platform Multiplayer: Available in Ultra and Platinum. You can host and join multiplayer matches in any of these platforms: Windows, Mac, Web, Android. - Cross Platform Multiplayer: Available in Ultra and Platinum. You can host and join multiplayer matches in any of these platforms: Windows, Mac, Linux, Web, Android, and iOS.
- Replay System: You can record your run using the built in replay system and watch it later. - Replay System: You can record your run using the built in replay system and watch it later.
- Rewind: You can rewind your marble by enabling rewind in the Options and holding down the rewind key (defaults to R). - Rewind: You can rewind your marble by enabling rewind in the Options and holding down the rewind key (defaults to R).
- Controller Support: Full controller support is added to Marble Blast Ultra, with incomplete support for the rest. - Controller Support: Full controller support is added to Marble Blast Ultra, with incomplete support for the rest.
- Touch Controls: Available in the web (mobile) and android versions. - Touch Controls: Available in the Web (mobile) and Android and iOS versions.
# Screenshots # Screenshots
<img src="https://imgur.com/Ncb4atl.png" width="640"> <img src="https://imgur.com/Ncb4atl.png" width="640">
@ -82,13 +93,16 @@ If the build dependencies are fullfilled, compile with `haxe compile-js.hxml` an
See [here](README-macOS.md) See [here](README-macOS.md)
## Android ## Android
The branches used for Android builds are `mbg-android`, `mbp-android-new` or `mbu-android`. The branches used for Android builds are `mbg-mobile`, `mbp-mobile` or `mbu-mobile`.
Clone [this repository](https://github.com/RandomityGuy/MBHaxeAndroidLibs) containing the necessary libraries for the build and merge its src folder with that of Export/android/app/src folder. Clone [this repository](https://github.com/RandomityGuy/MBHaxeAndroidLibs) containing the necessary libraries for the build and merge its src folder with that of Export/android/app/src folder.
Android NDK version 18.1.5063045 and platform SDK version 31 is needed. Android NDK version 18.1.5063045 and platform SDK version 31 is needed.
Install zyheaps haxelib as well. Install zyheaps haxelib as well.
Finally run `gradlew` in Export/android folder and run `gradlew assembleRelease` Finally run `gradlew` in Export/android folder and run `gradlew assembleRelease`
This will build the apk file at Export/android/app/build/outputs/apk/release/app-release-unsigned.apk which you can sign yourself and install on your device. This will build the apk file at Export/android/app/build/outputs/apk/release/app-release-unsigned.apk which you can sign yourself and install on your device.
## iOS
The branches used for iOS builds are `mbg-mobile`, `mbp-mobile` or `mbu-mobile`. Follow the CI scripts in the .circleci folder to build the iOS version. You will need a Mac with Xcode installed and an Apple Developer account to build the iOS version. The resulting .ipa file can be sideloaded onto your device using AltStore or similar tools.
# FAQ # FAQ
## Help I am able to reproduce a crash! ## Help I am able to reproduce a crash!
@ -103,12 +117,11 @@ In browser, you can just resize your window. You can use the browser zoom featur
In native version, you can just resize the window if windowed or use the resolution options in the menu or just directly modify settings.json In native version, you can just resize the window if windowed or use the resolution options in the menu or just directly modify settings.json
## How do I change my FOV? ## How do I change my FOV?
Edit settings.json for native version, edit the MBHaxeSettings key in LocalStorage in browser. There is an FOV slider in the options menu.
In the Platinum and Ultra versions, there is an FOV slider.
## How do I unlock/lock FPS? ## How do I unlock/lock FPS?
You cannot unlock fps in the browser, it is forever set to vsync. You cannot unlock fps in the browser, it is forever set to vsync.
In the native version, use the options menu to unlock/lock fps, or edit settings.json and set "vsync" to false to unlock fps. In the native version, use the options menu to unlock/lock fps.
## Hey can you please add this new feature? ## Hey can you please add this new feature?
If this new feature of yours already exists in MBG but not in this port, then I will try to add it, if I get time to do so, otherwise chances are, I won't add it since I have other things to do and would rather not waste my time on this any further. You are free to do pull requests if you have already implemented said feature. If this new feature of yours already exists in MBG but not in this port, then I will try to add it, if I get time to do so, otherwise chances are, I won't add it since I have other things to do and would rather not waste my time on this any further. You are free to do pull requests if you have already implemented said feature.

View file

@ -1,7 +1,11 @@
-cp src -cp src
-lib heaps -lib heaps
-lib hlsdl -lib hlsdl
-lib colyseus-websocket
-lib datachannel
-D highDPI -D highDPI
-D flow_border -D flow_border
-D analyzer-optimize
-D linux
-hl native/marblegame.c -hl native/marblegame.c
--main Main --main Main

BIN
data/ui/discord.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -81,7 +81,7 @@ class DtsObject extends GameObject {
var level:MarbleWorld; var level:MarbleWorld;
var materials:Array<Material> = []; var materials:Array<Material> = [];
var materialInfos:Map<Material, Array<String>> = new Map(); var materialInfos:Map<Material, Array<Texture>> = new Map();
var matNameOverride:Map<String, String> = new Map(); var matNameOverride:Map<String, String> = new Map();
var sequenceKeyframeOverride:Array<Float> = []; var sequenceKeyframeOverride:Array<Float> = [];
@ -348,8 +348,7 @@ class DtsObject extends GameObject {
var completion = 0 / (iflSequence[0].duration); var completion = 0 / (iflSequence[0].duration);
var keyframe = Math.floor(completion * info.length) % info.length; var keyframe = Math.floor(completion * info.length) % info.length;
var currentFile = info[keyframe]; var texture = info[keyframe];
var texture = ResourceLoader.getResource(this.directoryPath + '/' + currentFile, ResourceLoader.getTexture, this.textureResources);
var flags = this.dts.matFlags[i]; var flags = this.dts.matFlags[i];
if (flags & 1 > 0 || flags & 2 > 0) if (flags & 1 > 0 || flags & 2 > 0)
@ -422,7 +421,13 @@ class DtsObject extends GameObject {
} }
} else if (Path.extension(fullName) == "ifl") { } else if (Path.extension(fullName) == "ifl") {
var keyframes = parseIfl(fullName); var keyframes = parseIfl(fullName);
this.materialInfos.set(material, keyframes); // compute textures for keyframes
var textures = [];
for (keyframe in keyframes) {
var texture = ResourceLoader.getResource(this.directoryPath + '/' + keyframe, ResourceLoader.getTexture, this.textureResources);
textures.push(texture);
}
this.materialInfos.set(material, textures);
iflMaterial = true; iflMaterial = true;
} else { } else {
var texture = ResourceLoader.getResource(fullName, ResourceLoader.getTexture, this.textureResources); var texture = ResourceLoader.getResource(fullName, ResourceLoader.getTexture, this.textureResources);
@ -1051,8 +1056,8 @@ class DtsObject extends GameObject {
var completion = timeState.timeSinceLoad / (iflSequence[0].duration); var completion = timeState.timeSinceLoad / (iflSequence[0].duration);
var keyframe = Math.floor(completion * info.length) % info.length; var keyframe = Math.floor(completion * info.length) % info.length;
var currentFile = info[keyframe]; var texture = info[keyframe];
var texture = ResourceLoader.getResource(this.directoryPath + '/' + currentFile, ResourceLoader.getTexture, this.textureResources); // var texture = ResourceLoader.getResource(this.directoryPath + '/' + currentFile, ResourceLoader.getTexture, this.textureResources);
var flags = this.dts.matFlags[i]; var flags = this.dts.matFlags[i];
if (flags & 1 > 0 || flags & 2 > 0) if (flags & 1 > 0 || flags & 2 > 0)

View file

@ -42,7 +42,7 @@ class MarbleGame {
static var instance:MarbleGame; static var instance:MarbleGame;
static var currentVersion = "1.7.2"; static var currentVersion = "1.7.3";
var world:MarbleWorld; var world:MarbleWorld;

View file

@ -142,7 +142,17 @@ class ResourceLoader {
} }
var worker = new ResourceLoaderWorker(onFinish); var worker = new ResourceLoaderWorker(onFinish);
for (file in toloadfiles) { for (file in toloadfiles) {
worker.addTaskParallel((fwd) -> file.load(fwd)); worker.addTaskParallel((fwd) -> {
// if its a jpg, png or gif, load it as bitmap else load as file
var fileExtension = file.extension.toLowerCase();
if (fileExtension == "jpg" || fileExtension == "png" || fileExtension == "bmp") {
file.loadBitmap(v -> {
fwd();
});
} else {
file.load(fwd);
}
});
} }
worker.run(); worker.run();
} }
@ -567,6 +577,8 @@ class ResourceLoader {
if (zipFilesystem.exists(path.toLowerCase() + ".dds")) { if (zipFilesystem.exists(path.toLowerCase() + ".dds")) {
return [path + ".dds"]; return [path + ".dds"];
} }
var dirPath = Path.directory(path);
if (fileSystem.exists(dirPath)) {
var files = fileSystem.dir(Path.directory(path)); // FileSystem.readDirectory(Path.directory(path)); var files = fileSystem.dir(Path.directory(path)); // FileSystem.readDirectory(Path.directory(path));
var names = []; var names = [];
var fname = Path.withoutDirectory(path).toLowerCase(); var fname = Path.withoutDirectory(path).toLowerCase();
@ -576,6 +588,9 @@ class ResourceLoader {
names.push(file.path); names.push(file.path);
} }
return names; return names;
} else {
return [];
}
} }
public static function loadZip(entries:Array<haxe.zip.Entry>, game:String) { public static function loadZip(entries:Array<haxe.zip.Entry>, game:String) {

View file

@ -28,13 +28,22 @@ class ResourceLoaderWorker {
parallelstarted = true; parallelstarted = true;
var taskcount = paralleltasks.length; var taskcount = paralleltasks.length;
var tasksdone = 0; var tasksdone = 0;
var doneTasks = new Map();
var i = 0;
for (task in paralleltasks) { for (task in paralleltasks) {
var taskIndex = i;
task(() -> { task(() -> {
if (doneTasks.exists(taskIndex)) {
trace('Warning: Task already marked as done!');
} else {
doneTasks.set(taskIndex, true);
tasksdone++; tasksdone++;
}
if (tasksdone == taskcount) { if (tasksdone == taskcount) {
this.run(); this.run();
} }
}); });
i++;
} }
return; return;
} }
@ -54,6 +63,22 @@ class ResourceLoaderWorker {
} }
public function loadFile(path:String) { public function loadFile(path:String) {
// if its a jpg, png or gif, load it as bitmap else load as file
var fileExtension = path.split('.').pop();
if (fileExtension != null) {
fileExtension = fileExtension.toLowerCase();
if (fileExtension == "jpg" || fileExtension == "png" || fileExtension == "bmp") {
paralleltasks.push(fwd -> {
var file = ResourceLoader.load(path);
file.entry.loadBitmap(v -> {
fwd();
});
});
} else if (fileExtension != "") {
paralleltasks.push(fwd -> ResourceLoader.load(path).entry.load(fwd)); paralleltasks.push(fwd -> ResourceLoader.load(path).entry.load(fwd));
} }
} else {
paralleltasks.push(fwd -> ResourceLoader.load(path).entry.load(fwd));
}
}
} }

View file

@ -525,6 +525,8 @@ class Util {
#if hl #if hl
#if MACOS_BUNDLE #if MACOS_BUNDLE
return "MacOS"; return "MacOS";
#elseif linux
return "Linux";
#else #else
return "Windows"; return "Windows";
#end #end

View file

@ -47,6 +47,7 @@ class ManifestEntry extends FileEntry {
private var bytes:Bytes; private var bytes:Bytes;
private var readPos:Int; private var readPos:Int;
private var loaded:Bool; private var loaded:Bool;
var loadedBmp:LoadedBitmap;
#end #end
public function new(fs:ManifestFileSystem, name:String, relPath:String, file:String, ?originalFile:String) { public function new(fs:ManifestFileSystem, name:String, relPath:String, file:String, ?originalFile:String) {
@ -164,9 +165,16 @@ class ManifestEntry extends FileEntry {
var bmp = new hxd.res.Image(this).toBitmap(); var bmp = new hxd.res.Image(this).toBitmap();
onLoaded(new hxd.fs.LoadedBitmap(bmp)); onLoaded(new hxd.fs.LoadedBitmap(bmp));
#elseif js #elseif js
if (loadedBmp != null) {
onLoaded(loadedBmp);
return;
}
load(() -> { load(() -> {
var img:js.html.Image = new js.html.Image(); var img:js.html.Image = new js.html.Image();
img.onload = (_) -> onLoaded(new LoadedBitmap(img)); img.onload = (_) -> {
loadedBmp = new LoadedBitmap(img);
onLoaded(loadedBmp);
};
img.src = file; img.src = file;
}); });
#else #else

View file

@ -50,30 +50,53 @@ class TorqueFileSystem extends LocalFileSystem {
root = new TorqueFileEntry(this, "root", null, baseDir); root = new TorqueFileEntry(this, "root", null, baseDir);
} }
override function checkPath(path:String) { // Maps a directory (lowercased absolute path) to a map of lowercased entry name -> real on-disk name.
// make sure the file is loaded with correct case ! // Lets us resolve a requested path to its actual casing on case-sensitive filesystems.
var baseDir = new haxe.io.Path(path).dir; var realCaseCache:Map<String, Map<String, String>> = new Map();
var c = directoryCache.get(baseDir.toLowerCase());
var isNew = false; function listRealCase(dir:String, refresh:Bool):Map<String, String> {
var key = dir.toLowerCase();
var c = refresh ? null : realCaseCache.get(key);
if (c == null) { if (c == null) {
isNew = true;
c = new Map(); c = new Map();
for (f in try for (f in try
sys.FileSystem.readDirectory(baseDir) sys.FileSystem.readDirectory(dir)
catch (e:Dynamic) catch (e:Dynamic)
[]) [])
c.set(f.toLowerCase(), true); c.set(f.toLowerCase(), f);
directoryCache.set(baseDir.toLowerCase(), c); realCaseCache.set(key, c);
} }
if (!c.exists(path.substr(baseDir.length + 1).toLowerCase())) { return c;
// added since then?
if (!isNew) {
directoryCache.remove(baseDir.toLowerCase());
return checkPath(path);
} }
return false;
function lookupRealCase(dir:String, name:String):String {
var lower = name.toLowerCase();
var real = listRealCase(dir, false).get(lower);
if (real == null) // maybe added since cached, refresh once
real = listRealCase(dir, true).get(lower);
return real;
} }
return true;
// Resolves a relative path (under baseDir) to its real on-disk casing, walking it
// component by component. Returns null if any component does not exist.
function resolveRealPath(path:String):String {
// Fast path: case already matches what's on disk (always true on case-insensitive
// filesystems like Windows/NTFS), so skip the per-directory enumeration entirely.
if (sys.FileSystem.exists(baseDir + path))
return path;
var current = StringTools.endsWith(baseDir, "/") ? baseDir.substr(0, baseDir.length - 1) : baseDir;
var out = [];
for (part in path.split("/")) {
if (part == "" || part == ".")
continue;
var real = lookupRealCase(current, part);
if (real == null)
return null;
out.push(real);
current = current + "/" + real;
}
return out.join("/");
} }
override function open(path:String, check = true) { override function open(path:String, check = true) {
@ -81,18 +104,33 @@ class TorqueFileSystem extends LocalFileSystem {
if (r != null) if (r != null)
return r.r; return r.r;
var e = null; var e = null;
var f = sys.FileSystem.fullPath(baseDir + path); // resolve to the actual on-disk casing so lookups work on case-sensitive filesystems
if (f == null) var realPath = check ? resolveRealPath(path) : path;
return null; if (realPath != null) {
var f = sys.FileSystem.fullPath(baseDir + realPath);
if (f != null) {
f = f.split("\\").join("/"); f = f.split("\\").join("/");
if (!check || (sys.FileSystem.exists(f) && checkPath(f))) { if (!check || sys.FileSystem.exists(f)) {
e = new TorqueFileEntry(this, path.split("/").pop(), path, f); e = new TorqueFileEntry(this, realPath.split("/").pop(), realPath, f);
convert.run(e); convert.run(e);
if (e.file == null) if (e.file == null)
e = null; e = null;
} }
}
}
fileCache.set(path.toLowerCase(), {r: e}); fileCache.set(path.toLowerCase(), {r: e});
return e; return e;
} }
override public function dir(path:String):Array<hxd.fs.FileEntry> {
var realPath = resolveRealPath(path);
if (realPath == null || !sys.FileSystem.isDirectory(baseDir + realPath))
throw new hxd.fs.NotFound(baseDir + path);
var files = sys.FileSystem.readDirectory(baseDir + realPath);
var r:Array<hxd.fs.FileEntry> = [];
for (f in files)
r.push(open((realPath == "" ? "" : realPath + "/") + f, false));
return r;
}
#end #end
} }

View file

@ -89,10 +89,10 @@ class ChatCtrl extends GuiControl {
if (StringTools.trim(this.chatHudInput.text.text) != "") { if (StringTools.trim(this.chatHudInput.text.text) != "") {
sendText = '<font color="#F29515">${StringTools.htmlEscape(Settings.highscoreName.substr(0, 20))}:</font> ${StringTools.htmlEscape(this.chatHudInput.text.text.substr(0, 150))}'; sendText = '<font color="#F29515">${StringTools.htmlEscape(Settings.highscoreName.substr(0, 20))}:</font> ${StringTools.htmlEscape(this.chatHudInput.text.text.substr(0, 150))}';
if (Net.isClient) { if (Net.isClient) {
NetCommands.sendChatMessage(StringTools.htmlEscape(sendText)); NetCommands.sendChatMessage(sendText);
} }
if (Net.isHost) { if (Net.isHost) {
NetCommands.sendServerChatMessage(StringTools.htmlEscape(sendText)); NetCommands.sendServerChatMessage(sendText);
} }
} }
this.chatHudInput.text.text = ""; this.chatHudInput.text.text = "";
@ -118,7 +118,7 @@ class ChatCtrl extends GuiControl {
} }
public function addChatMessage(text:String) { public function addChatMessage(text:String) {
var realText = StringTools.htmlEscape(text); var realText = text;
this.chats.push({ this.chats.push({
text: realText, text: realText,
age: 10.0 age: 10.0

View file

@ -434,10 +434,10 @@ class MPPlayMissionGui extends GuiImage {
if (StringTools.trim(chatInput.text.text) != "") { if (StringTools.trim(chatInput.text.text) != "") {
var sendText = '<font color="#F29515">${StringTools.htmlEscape(Settings.highscoreName.substr(0, 20))}:</font> ${StringTools.htmlEscape(chatInput.text.text.substr(0, 100))}'; var sendText = '<font color="#F29515">${StringTools.htmlEscape(Settings.highscoreName.substr(0, 20))}:</font> ${StringTools.htmlEscape(chatInput.text.text.substr(0, 100))}';
if (Net.isClient) { if (Net.isClient) {
NetCommands.sendChatMessage(StringTools.htmlEscape(sendText)); NetCommands.sendChatMessage(sendText);
} }
if (Net.isHost) { if (Net.isHost) {
NetCommands.sendServerChatMessage(StringTools.htmlEscape(sendText)); NetCommands.sendServerChatMessage(sendText);
} }
} }
chatInput.text.text = ""; chatInput.text.text = "";
@ -728,7 +728,7 @@ class MPPlayMissionGui extends GuiImage {
} }
public static function addChatMessage(s:String) { public static function addChatMessage(s:String) {
var realText = StringTools.htmlEscape(s); var realText = s;
allChats.push(realText); allChats.push(realText);
if (allChats.length > 100) { if (allChats.length > 100) {
allChats = allChats.slice(allChats.length - 100); allChats = allChats.slice(allChats.length - 100);

View file

@ -259,6 +259,16 @@ class MainMenuGui extends GuiImage {
js.Browser.window.open("https://marbleblastultra.randomityguy.me"); js.Browser.window.open("https://marbleblastultra.randomityguy.me");
} }
this.addChild(mbu); this.addChild(mbu);
var discord = new GuiButton(loadStaticButtonImages("data/ui/discord"));
discord.horizSizing = Right;
discord.vertSizing = Top;
discord.position = new Vector(0, 320);
discord.extent = new Vector(152, 60);
discord.pressedAction = (sender) -> {
js.Browser.window.open("https://discord.gg/q4JdnRbVhF");
}
this.addChild(discord);
#end #end
#if js #if js

View file

@ -32,7 +32,7 @@ final lineCommentRegEx = ~/\/\/.*/g;
final assignmentRegEx = ~/(\$(?:\w|\d)+)\s*=\s*(.+?);/g; final assignmentRegEx = ~/(\$(?:\w|\d)+)\s*=\s*(.+?);/g;
final marbleAttributesRegEx = ~/setMarbleAttributes\("(\w+)",\s*(.+?)\);/g; final marbleAttributesRegEx = ~/setMarbleAttributes\("(\w+)",\s*(.+?)\);/g;
final activatePackageRegEx = ~/activatePackage\((.+?)\);/g; final activatePackageRegEx = ~/activatePackage\((.+?)\);/g;
final materialPropertyRegEx = ~/new MaterialProperty *\( *(.+?) *\)\s*{\s*((?:\w+ *= *(\d|\.)+;\s*)*)}/gi; final materialPropertyRegEx = ~/new MaterialProperty *\( *(.+?) *\)\s*{\s*((?:\w+ *= *-?(\d|\.)+;\s*)*)}/gi;
final addMaterialMappingRegEx = ~/addMaterialMapping *\( *"(.+?)" *, *(.+?) *\)/gi; final addMaterialMappingRegEx = ~/addMaterialMapping *\( *"(.+?)" *, *(.+?) *\)/gi;
class MisParser { class MisParser {

View file

@ -96,7 +96,7 @@ class HuntMode extends NullMode {
} }
} else if (element._type == MissionElementType.SimGroup) { } else if (element._type == MissionElementType.SimGroup) {
var scanPls = true; var scanPls = true;
if (Net.connectedServerInfo.oldSpawns) { if (Net.isMP && Net.connectedServerInfo.oldSpawns) {
if (element._name.toLowerCase() == "newversion") { if (element._name.toLowerCase() == "newversion") {
// Remove this // Remove this
elToRemove.push(element); elToRemove.push(element);
@ -121,8 +121,8 @@ class HuntMode extends NullMode {
}; };
override function getSpawnTransform() { override function getSpawnTransform() {
var idx = Net.connectedServerInfo.competitiveMode ? idealSpawnIndex : Math.floor(rng2.randRange(0, playerSpawnPoints.length - 1)); var idx = (Net.isMP && Net.connectedServerInfo.competitiveMode) ? idealSpawnIndex : Math.floor(rng2.randRange(0, playerSpawnPoints.length - 1));
if (!Net.connectedServerInfo.competitiveMode) { if (!(Net.isMP && Net.connectedServerInfo.competitiveMode)) {
var allTaken = true; var allTaken = true;
for (spw in spawnPointTaken) { for (spw in spawnPointTaken) {
if (!spw) { if (!spw) {
@ -370,7 +370,7 @@ class HuntMode extends NullMode {
var gemPos = gemElem.gem.getAbsPos().getPosition(); var gemPos = gemElem.gem.getAbsPos().getPosition();
if (level.mission.missionInfo.game == "PlatinumQuest") { if (level.mission.missionInfo.game == "PlatinumQuest") {
if (Net.connectedServerInfo.oldSpawns) { if (Net.isMP && Net.connectedServerInfo.oldSpawns) {
// Spawn chances! // Spawn chances!
var chance = switch (gemElem.gem.gemColor.toLowerCase()) { var chance = switch (gemElem.gem.gemColor.toLowerCase()) {
case "red.gem": case "red.gem":
@ -836,7 +836,7 @@ class HuntMode extends NullMode {
} }
override function update(t:src.TimeState) { override function update(t:src.TimeState) {
if (Net.connectedServerInfo.competitiveMode) { if (this.level.isMultiplayer && Net.connectedServerInfo.competitiveMode) {
if (competitiveTimerStartTicks != 0) { if (competitiveTimerStartTicks != 0) {
var currentTime = Net.isHost ? t.ticks : @:privateAccess level.marble.serverTicks; var currentTime = Net.isHost ? t.ticks : @:privateAccess level.marble.serverTicks;
var endTime = competitiveTimerStartTicks + (20000 >> 5); var endTime = competitiveTimerStartTicks + (20000 >> 5);