mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2026-06-24 17:23:00 +00:00
Compare commits
15 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59c0842e64 | ||
|
|
3d3f7b816a | ||
|
|
cde152c6a1 | ||
|
|
5698770502 | ||
|
|
ee23828f4d | ||
|
|
6b32f92d4d | ||
|
|
9b4d8a9b5e | ||
|
|
786dcb169e | ||
|
|
717d4eb5c6 | ||
|
|
6fd8d12386 | ||
|
|
6ffd04038c | ||
|
|
7a648a8aeb | ||
|
|
14c7885f58 | ||
|
|
1ec04251e8 | ||
|
|
d7b1e68b21 |
17 changed files with 449 additions and 68 deletions
|
|
@ -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: /.*/
|
||||||
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -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.
|
||||||
|
|
|
||||||
35
README.md
35
README.md
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
BIN
data/ui/discord.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,15 +577,20 @@ class ResourceLoader {
|
||||||
if (zipFilesystem.exists(path.toLowerCase() + ".dds")) {
|
if (zipFilesystem.exists(path.toLowerCase() + ".dds")) {
|
||||||
return [path + ".dds"];
|
return [path + ".dds"];
|
||||||
}
|
}
|
||||||
var files = fileSystem.dir(Path.directory(path)); // FileSystem.readDirectory(Path.directory(path));
|
var dirPath = Path.directory(path);
|
||||||
var names = [];
|
if (fileSystem.exists(dirPath)) {
|
||||||
var fname = Path.withoutDirectory(path).toLowerCase();
|
var files = fileSystem.dir(Path.directory(path)); // FileSystem.readDirectory(Path.directory(path));
|
||||||
for (file in files) {
|
var names = [];
|
||||||
var fname2 = file.name;
|
var fname = Path.withoutDirectory(path).toLowerCase();
|
||||||
if (Path.withoutExtension(fname2).toLowerCase() == fname || fname2.toLowerCase() == fname)
|
for (file in files) {
|
||||||
names.push(file.path);
|
var fname2 = file.name;
|
||||||
|
if (Path.withoutExtension(fname2).toLowerCase() == fname || fname2.toLowerCase() == fname)
|
||||||
|
names.push(file.path);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
return names;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function loadZip(entries:Array<haxe.zip.Entry>, game:String) {
|
public static function loadZip(entries:Array<haxe.zip.Entry>, game:String) {
|
||||||
|
|
|
||||||
|
|
@ -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(() -> {
|
||||||
tasksdone++;
|
if (doneTasks.exists(taskIndex)) {
|
||||||
|
trace('Warning: Task already marked as done!');
|
||||||
|
} else {
|
||||||
|
doneTasks.set(taskIndex, true);
|
||||||
|
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) {
|
||||||
paralleltasks.push(fwd -> ResourceLoader.load(path).entry.load(fwd));
|
// 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));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paralleltasks.push(fwd -> ResourceLoader.load(path).entry.load(fwd));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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());
|
function lookupRealCase(dir:String, name:String):String {
|
||||||
return checkPath(path);
|
var lower = name.toLowerCase();
|
||||||
}
|
var real = listRealCase(dir, false).get(lower);
|
||||||
return false;
|
if (real == null) // maybe added since cached, refresh once
|
||||||
|
real = listRealCase(dir, true).get(lower);
|
||||||
|
return real;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 true;
|
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) {
|
||||||
f = f.split("\\").join("/");
|
var f = sys.FileSystem.fullPath(baseDir + realPath);
|
||||||
if (!check || (sys.FileSystem.exists(f) && checkPath(f))) {
|
if (f != null) {
|
||||||
e = new TorqueFileEntry(this, path.split("/").pop(), path, f);
|
f = f.split("\\").join("/");
|
||||||
convert.run(e);
|
if (!check || sys.FileSystem.exists(f)) {
|
||||||
if (e.file == null)
|
e = new TorqueFileEntry(this, realPath.split("/").pop(), realPath, f);
|
||||||
e = null;
|
convert.run(e);
|
||||||
|
if (e.file == 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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue