Compare commits

...

49 commits

Author SHA1 Message Date
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
RandomityGuy
cbfafdb63f mbg and mbu icons on bottom right 2026-04-01 13:38:17 +01:00
RandomityGuy
6fcbbb6edb update libpng 2026-04-01 00:33:02 +01:00
RandomityGuy
0170bca4c0 lol fix ci for mac 2026-04-01 00:27:08 +01:00
RandomityGuy
359e3c3dc8 fix ci 2026-04-01 00:21:11 +01:00
RandomityGuy
bc7b6f9b9f update ci 2026-03-31 14:53:51 +01:00
RandomityGuy
d20015de69 update ver 2026-03-31 14:53:42 +01:00
RandomityGuy
09344d58a4 reduce allocs 2026-03-31 14:44:11 +01:00
RandomityGuy
30f58ed436 fix touch camera again, bring in mbu camera auto centering 2026-03-31 14:44:03 +01:00
RandomityGuy
ba112425f8 normalize touch camera and do this minor inlining 2026-03-30 17:41:19 +01:00
RandomityGuy
f2a0ba443e update mac ci 2026-03-29 19:07:28 +01:00
RandomityGuy
cc1c8d7950 attempt fix camera 2026-03-29 18:08:55 +01:00
RandomityGuy
ef8c54c195 add proper turn server support 2026-03-29 15:05:16 +01:00
RandomityGuy
bc428260fa try to make touch camera better 2026-03-29 11:05:05 +01:00
RandomityGuy
5391c665ac some mp optimizations 2026-03-28 18:45:08 +00:00
RandomityGuy
c4bbe512df implement progress import-export 2026-03-28 17:57:50 +00:00
RandomityGuy
a67cf3deaa impl console cheats 2026-03-28 15:00:06 +00:00
RandomityGuy
44c15ab011 more array reuse 2026-03-28 14:33:04 +00:00
RandomityGuy
8d35663f3d fix this gravity rewind bug 2026-03-28 13:42:39 +00:00
RandomityGuy
d8cee80266 fix this cursorlock pause issue on chrome and this minor thing 2026-03-28 12:40:49 +00:00
RandomityGuy
724ebbda99 fix this broadphase crash 2026-03-28 01:51:24 +00:00
RandomityGuy
8f08c3b817 reduce array allocations when doing collision 2026-03-27 17:34:32 +00:00
RandomityGuy
4bddb5bd0a fix texture paths for difs and fix endgame softlock for custom missions 2026-03-14 01:42:13 +00:00
RandomityGuy
2ef28aae5f - try to make it case insensitive
- escape all text derived from user input
- fix various race condition issues
- make scrolling smooth for touch controls
- fix leaderboards count
- fix potential crash when joining MP
- clamp input
- fix replay clock when stopped time
- also update ci to only run when tagged
2026-01-27 22:15:10 +00:00
RandomityGuy
51c456e907 update readme 2025-11-19 17:43:36 +00:00
RandomityGuy
afa42fe498 update readme 2025-08-17 21:53:06 +05:30
RandomityGuy
a79f7c8fcc readme update 2025-06-30 20:24:47 +05:30
RandomityGuy
ef9b79f120 Merge branch 'master' of https://github.com/RandomityGuy/MBHaxe 2025-03-21 21:30:35 +05:30
RandomityGuy
553ed365e9 update links 2025-03-21 21:30:23 +05:30
RandomityGuy
d69cb92028
Update README.md 2025-02-15 01:20:16 +05:30
RandomityGuy
e06d871aaf
add discord link 2025-02-15 01:20:06 +05:30
RandomityGuy
8a9866db54 update changelog 2025-02-14 22:50:07 +05:30
RandomityGuy
a2e2b4e211 fix fps not matching tps 2025-02-14 21:56:32 +05:30
RandomityGuy
c5a90673b9 ver increment 2025-02-14 21:43:54 +05:30
RandomityGuy
fbaa766f7e Merge branch 'master' of https://github.com/RandomityGuy/MBHaxe 2025-02-14 21:42:07 +05:30
RandomityGuy
10008f98c3 align this better 2025-02-14 21:41:57 +05:30
RandomityGuy
47b10edbab change fps display to display tps instead when vsync is on 2025-02-14 21:39:39 +05:30
RandomityGuy
6044889270 match drawn fps to tick rate always, except for when vsync 2025-02-14 21:22:53 +05:30
RandomityGuy
e7cac9cd0c uuid things 2025-02-14 20:13:23 +05:30
RandomityGuy
06d0c6d98e score send criteria change and fix replay record oob bug 2025-02-14 20:13:09 +05:30
RandomityGuy
3a21ca2f5c
Create FUNDING.yml 2025-02-11 22:28:47 +05:30
RandomityGuy
ad0e867b48 update links 2025-02-11 21:42:30 +05:30
RandomityGuy
ae0c057b31 update changelog 2025-02-11 21:41:34 +05:30
50 changed files with 1010 additions and 473 deletions

View file

@ -10,8 +10,8 @@ orbs:
jobs: jobs:
build: build:
macos: macos:
xcode: 14.1.0 # Specify the Xcode version to use xcode: 26.2.0 # Specify the Xcode version to use
resource_class: macos.m1.medium.gen1 resource_class: m4pro.medium
environment: environment:
HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_AUTO_UPDATE: 1
NPROC: 4 NPROC: 4
@ -61,10 +61,10 @@ jobs:
command: | command: |
mkdir -p ~/deps mkdir -p ~/deps
cd ~/deps cd ~/deps
curl https://www.zlib.net/zlib-1.3.1.tar.xz | tar xz curl https://www.zlib.net/zlib-1.3.2.tar.xz | tar xz
cd zlib-1.3.1 cd zlib-1.3.2
if [ ! -f /usr/local/lib/libz.1.3.1.dylib ]; then if [ ! -f /usr/local/lib/libz.1.3.2.dylib ]; then
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build --config Release -j$NPROC cmake --build build --config Release -j$NPROC
sudo cmake --install build sudo cmake --install build
fi fi
@ -77,7 +77,7 @@ jobs:
curl https://openal-soft.org/openal-releases/openal-soft-1.22.2.tar.bz2 | tar xz curl https://openal-soft.org/openal-releases/openal-soft-1.22.2.tar.bz2 | tar xz
cd openal-soft-1.22.2 cd openal-soft-1.22.2
if [ ! -f /usr/local/lib/libopenal.1.22.2.dylib ]; then if [ ! -f /usr/local/lib/libopenal.1.22.2.dylib ]; then
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DALSOFT_BACKEND_SNDIO=NO -DALSOFT_BACKEND_PORTAUDIO=NO -DALSOFT_BACKEND_WAVE=NO -DALSOFT_UTILS=NO -DALSOFT_EXAMPLES=NO -DCMAKE_MACOSX_RPATH=TRUE cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DALSOFT_BACKEND_SNDIO=NO -DALSOFT_BACKEND_PORTAUDIO=NO -DALSOFT_BACKEND_WAVE=NO -DALSOFT_UTILS=NO -DALSOFT_EXAMPLES=NO -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build --config Release -j$NPROC cmake --build build --config Release -j$NPROC
sudo cmake --install build sudo cmake --install build
fi fi
@ -89,7 +89,7 @@ jobs:
curl -L https://downloads.sourceforge.net/project/libjpeg-turbo/2.1.4/libjpeg-turbo-2.1.4.tar.gz | tar xz curl -L https://downloads.sourceforge.net/project/libjpeg-turbo/2.1.4/libjpeg-turbo-2.1.4.tar.gz | tar xz
cd libjpeg-turbo-2.1.4 cd libjpeg-turbo-2.1.4
if [ ! -f /usr/local/lib/libturbojpeg.0.2.0.dylib ]; then if [ ! -f /usr/local/lib/libturbojpeg.0.2.0.dylib ]; then
cmake -S. -Bbuild-x86 -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_INSTALL_PREFIX=/usr/local cmake -S. -Bbuild-x86 -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build-x86 --config release -j$NPROC cmake --build build-x86 --config release -j$NPROC
fi fi
@ -98,7 +98,7 @@ jobs:
command: | command: |
cd ~/deps/libjpeg-turbo-2.1.4 cd ~/deps/libjpeg-turbo-2.1.4
if [ ! -f /usr/local/lib/libturbojpeg.0.2.0.dylib ]; then if [ ! -f /usr/local/lib/libturbojpeg.0.2.0.dylib ]; then
cmake -S. -Bbuild-arm64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_INSTALL_PREFIX=/usr/local cmake -S. -Bbuild-arm64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build-arm64 --config release -j$NPROC cmake --build build-arm64 --config release -j$NPROC
fi fi
@ -122,7 +122,7 @@ jobs:
curl -L https://downloads.xiph.org/releases/ogg/libogg-1.3.5.tar.xz | tar xz curl -L https://downloads.xiph.org/releases/ogg/libogg-1.3.5.tar.xz | tar xz
cd libogg-1.3.5 cd libogg-1.3.5
if [ ! -f /usr/local/lib/libogg.0.8.5.dylib ]; then if [ ! -f /usr/local/lib/libogg.0.8.5.dylib ]; then
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DBUILD_SHARED_LIBS=ON -DCMAKE_MACOSX_RPATH=TRUE cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DBUILD_SHARED_LIBS=ON -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build --config Release -j$NPROC cmake --build build --config Release -j$NPROC
sudo cmake --install build sudo cmake --install build
fi fi
@ -135,7 +135,7 @@ jobs:
curl -L https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz | tar xz curl -L https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz | tar xz
cd libvorbis-1.3.7 cd libvorbis-1.3.7
if [ ! -f /usr/local/lib/libvorbis.0.4.9.dylib ]; then if [ ! -f /usr/local/lib/libvorbis.0.4.9.dylib ]; then
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DBUILD_SHARED_LIBS=ON -DCMAKE_MACOSX_RPATH=TRUE cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DBUILD_SHARED_LIBS=ON -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build --config Release -j$NPROC cmake --build build --config Release -j$NPROC
sudo cmake --install build sudo cmake --install build
fi fi
@ -145,10 +145,10 @@ jobs:
command: | command: |
mkdir -p ~/deps mkdir -p ~/deps
cd ~/deps cd ~/deps
curl -L https://download.sourceforge.net/libpng/libpng-1.6.39.tar.xz | tar xz curl -L https://download.sourceforge.net/libpng/libpng-1.6.56.tar.xz | tar xz
cd libpng-1.6.39 cd libpng-1.6.56
if [ ! -f /usr/local/lib/libpng16.16.dylib ]; then if [ ! -f /usr/local/lib/libpng16.16.dylib ]; then
cmake -S. -Bbuild-x86 -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE cmake -S. -Bbuild-x86 -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build-x86 --config release -j$NPROC cmake --build build-x86 --config release -j$NPROC
fi fi
@ -157,9 +157,9 @@ jobs:
command: | command: |
mkdir -p ~/deps mkdir -p ~/deps
cd ~/deps cd ~/deps
cd libpng-1.6.39 cd libpng-1.6.56
if [ ! -f /usr/local/lib/libpng16.16.dylib ]; then if [ ! -f /usr/local/lib/libpng16.16.dylib ]; then
cmake -S. -Bbuild-arm64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE cmake -S. -Bbuild-arm64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build-arm64 --config release -j$NPROC cmake --build build-arm64 --config release -j$NPROC
fi fi
@ -167,8 +167,8 @@ jobs:
name: Install libpng (Universal) name: Install libpng (Universal)
command: | command: |
if [ ! -f /usr/local/lib/libpng16.16.dylib ]; then if [ ! -f /usr/local/lib/libpng16.16.dylib ]; then
cd ~/deps/libpng-1.6.39/build-arm64 cd ~/deps/libpng-1.6.56/build-arm64
for i in libpng16.16.39.0.dylib libpng16.a png-fix-itxt pngfix pngimage pngstest pngtest pngunknown pngvalid for i in libpng16.16.56.0.dylib libpng16.a png-fix-itxt pngfix pngimage pngstest pngtest pngunknown pngvalid
do do
lipo -create -output $i ../build-x86/$i $i lipo -create -output $i ../build-x86/$i $i
done done
@ -183,7 +183,7 @@ jobs:
curl -L https://github.com/libsdl-org/SDL/releases/download/release-2.26.1/SDL2-2.26.1.tar.gz | tar xz curl -L https://github.com/libsdl-org/SDL/releases/download/release-2.26.1/SDL2-2.26.1.tar.gz | tar xz
cd SDL2-2.26.1 cd SDL2-2.26.1
if [ ! -f /usr/local/lib/libSDL2-2.0.0.dylib ]; then if [ ! -f /usr/local/lib/libSDL2-2.0.0.dylib ]; then
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DBUILD_SHARED_LIBS=ON -DCMAKE_MACOSX_RPATH=TRUE cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DBUILD_SHARED_LIBS=ON -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build --config Release -j$NPROC cmake --build build --config Release -j$NPROC
sudo cmake --install build sudo cmake --install build
fi fi
@ -194,7 +194,7 @@ jobs:
cd ~/deps cd ~/deps
curl -fsSL https://github.com/libuv/libuv/archive/refs/tags/v1.44.2.tar.gz | tar xz curl -fsSL https://github.com/libuv/libuv/archive/refs/tags/v1.44.2.tar.gz | tar xz
cd libuv-1.44.2 cd libuv-1.44.2
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_MACOSX_RPATH=TRUE -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build --config Release -j$NPROC cmake --build build --config Release -j$NPROC
sudo cmake --install build sudo cmake --install build
@ -213,7 +213,7 @@ jobs:
curl -L https://raw.githubusercontent.com/RandomityGuy/hashlink/master/libs/ssl/CMakeLists.txt > libs/ssl/CMakeLists.txt curl -L https://raw.githubusercontent.com/RandomityGuy/hashlink/master/libs/ssl/CMakeLists.txt > libs/ssl/CMakeLists.txt
# Fix OpenAL # Fix OpenAL
# curl -L https://github.com/nullobsi/hashlink/commit/a09491918cc4b83c2cb9fcded855fe967857385f.diff | git apply # curl -L https://github.com/nullobsi/hashlink/commit/a09491918cc4b83c2cb9fcded855fe967857385f.diff | git apply
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_FIND_FRAMEWORK=LAST -DWITH_SQLITE=OFF -DBUILD_TESTING=OFF -DCMAKE_MACOSX_RPATH=TRUE -DHASHLINK_INCLUDE_DIR="~/deps/hashlink/src" -DHASHLINK_LIBRARY_DIR="/usr/local/lib/" cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.15" -DCMAKE_FIND_FRAMEWORK=LAST -DWITH_SQLITE=OFF -DBUILD_TESTING=OFF -DCMAKE_MACOSX_RPATH=TRUE -DHASHLINK_INCLUDE_DIR="~/deps/hashlink/src" -DHASHLINK_LIBRARY_DIR="/usr/local/lib/" -DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build --config Release -j$NPROC cmake --build build --config Release -j$NPROC
sudo cmake --install build sudo cmake --install build
@ -246,7 +246,7 @@ jobs:
- /usr/local/lib/libvorbis.0.4.9.dylib - /usr/local/lib/libvorbis.0.4.9.dylib
- /usr/local/lib/libvorbisfile.3.3.8.dylib - /usr/local/lib/libvorbisfile.3.3.8.dylib
- /usr/local/lib/libvorbisenc.2.0.12.dylib - /usr/local/lib/libvorbisenc.2.0.12.dylib
- /usr/local/lib/libz.1.3.1.dylib - /usr/local/lib/libz.1.3.2.dylib
- /usr/local/lib/datachannel.hdll - /usr/local/lib/datachannel.hdll
@ -280,7 +280,7 @@ jobs:
cp /usr/local/lib/libvorbis.0.4.9.dylib libvorbis.0.4.9.dylib cp /usr/local/lib/libvorbis.0.4.9.dylib libvorbis.0.4.9.dylib
cp /usr/local/lib/libvorbisfile.3.3.8.dylib libvorbisfile.3.3.8.dylib cp /usr/local/lib/libvorbisfile.3.3.8.dylib libvorbisfile.3.3.8.dylib
cp /usr/local/lib/libvorbisenc.2.0.12.dylib libvorbisenc.2.0.12.dylib cp /usr/local/lib/libvorbisenc.2.0.12.dylib libvorbisenc.2.0.12.dylib
cp /usr/local/lib/libz.1.3.1.dylib libz.1.dylib cp /usr/local/lib/libz.1.3.2.dylib libz.1.dylib
cp /usr/local/lib/libuv.1.dylib libuv.1.dylib cp /usr/local/lib/libuv.1.dylib libuv.1.dylib
# These libraries have dangling RPATHs # These libraries have dangling RPATHs
install_name_tool -delete_rpath /usr/local/lib libturbojpeg.0.dylib install_name_tool -delete_rpath /usr/local/lib libturbojpeg.0.dylib
@ -387,7 +387,7 @@ jobs:
cd ~/deps cd ~/deps
git clone https://github.com/RandomityGuy/hxDatachannel git clone https://github.com/RandomityGuy/hxDatachannel
cd hxDatachannel/cpp cd hxDatachannel/cpp
"/c/Program Files/CMake/bin/cmake" -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DHASHLINK_LIBRARY_DIR="~/deps/hashlink/x64/Release" -DHASHLINK_INCLUDE_DIR="../../hashlink/src" "/c/Program Files/CMake/bin/cmake" -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DHASHLINK_LIBRARY_DIR="~/deps/hashlink/x64/Release" -DHASHLINK_INCLUDE_DIR="../../hashlink/src" -DCMAKE_POLICY_VERSION_MINIMUM=3.5
"/c/Program Files/CMake/bin/cmake" --build build --config Release -j4 "/c/Program Files/CMake/bin/cmake" --build build --config Release -j4
mv ~/deps/hxDatachannel/cpp/build/Release/datachannel.hdll ~/deps/hashlink/x64/Release mv ~/deps/hxDatachannel/cpp/build/Release/datachannel.hdll ~/deps/hashlink/x64/Release
mv ~/deps/hxDatachannel/cpp/build/Release/datachannel.lib ~/deps/hashlink/x64/Release mv ~/deps/hxDatachannel/cpp/build/Release/datachannel.lib ~/deps/hashlink/x64/Release
@ -419,7 +419,7 @@ jobs:
haxe compile-c.hxml haxe compile-c.hxml
cd native cd native
HASHLINKPATH=~/deps/hashlink HASHLINKPATH=~/deps/hashlink
MSBuild.exe -m -nologo -p:Configuration=Release -p:Platform=x64 -p:PlatformToolset=v142 -p:HASHLINK=$HASHLINKPATH marblegame.sln MSBuild.exe -m -nologo -p:Configuration=Release -p:Platform=x64 -p:PlatformToolset=v142 -p:MultiProcessorCompilation=true -p:HASHLINK=$HASHLINKPATH marblegame.sln
- run: - run:
name: Package app bundle name: Package app bundle
command: | command: |
@ -462,6 +462,8 @@ workflows:
filters: filters:
tags: tags:
only: /^\d+.\d+.\d+$/ only: /^\d+.\d+.\d+$/
branches:
ignore: /.*/
build-windows: build-windows:
jobs: jobs:
@ -469,3 +471,5 @@ workflows:
filters: filters:
tags: tags:
only: /^\d+.\d+.\d+$/ only: /^\d+.\d+.\d+$/
branches:
ignore: /.*/

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
ko_fi: randomityguy

View file

@ -1,3 +1,49 @@
# 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
This update brings the following bugfixes:
- Fixed a crash when the marble goes out of bounds.
- Fixed the FPS limiter not limiting rendered frames per second.
- Fixed scores not being sent in certain cases.
# 1.7.0
It's the fabled Leaderboards update!
Leaderboards have been implemented for all the levels with automatic replay uploading for official levels as well as watching top replays. Additionally, segregation has been made to allow switching between rewind and non-rewind scores on the leaderboards.
Changes:
- Added an FPS limiter in the settings.
- Added custom friction support as well as custom marble attributes. Now levels can modify the marble's physics parameters to their liking.
- Improved level select persistence. Now your last chosen level will be displayed on quitting or finishing a level instead of last level in a category.
- Improved the Gem Hunt algorithm to match closer to PlatinumQuest's.
- Trigger detection now matches with the original game.
- Camera is now smoothened.
- Fixed camera not pointing at gems after respawn in Multiplayer.
- Fixed Superspeed powerup sometimes throwing you in the wrong direction in Multiplayer.
- Fixed the marble being wonky at times in replays.
- Fixed an interaction with Random powerup giving Time Travels.
- Fixed some collision issues with moving platforms.
# 1.6.1 # 1.6.1
This update fixes the following bugs: This update fixes the following bugs:
- Fixed a crash when there are more players than spawnpoints in multiplayer. - Fixed a crash when there are more players than spawnpoints in multiplayer.

View file

@ -3,6 +3,7 @@ A Haxe port of Marble Blast Gold, Ultra and Platinum, name subject to change.
The marble physics code was taken from [OpenMBU](https://github.com/MBU-Team/OpenMBU) along with my own collision detection code, game logic was partially from scratch and taken with permission from [Marble Blast Web Port](https://github.com/Vanilagy/MarbleBlast). The marble physics code was taken from [OpenMBU](https://github.com/MBU-Team/OpenMBU) along with my own collision detection code, game logic was partially from scratch and taken with permission from [Marble Blast Web Port](https://github.com/Vanilagy/MarbleBlast).
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/H2H5FRTTL) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/H2H5FRTTL)
Support Discord: https://discord.gg/GsmTVQQAhG
# Play # Play
## Web Browser ## Web Browser
The browser port supports touch controls, meaning it can be played on mobile devices. The browser port supports touch controls, meaning it can be played on mobile devices.
@ -10,16 +11,21 @@ The browser port supports touch controls, meaning it can be played on mobile dev
### 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 and Mac
### 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.13)
### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.6.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.1.3-mbu) ### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.2.5-mbu)
## 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.13/MBHaxe-Gold.apk)
### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.6.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.1.3-mbu/MBHaxe-Ultra.apk) ### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.2.5-mbu/MBHaxe-Ultra.apk)
## Xbox (NEW!)
### 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).
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, Web, Android.
@ -60,6 +66,7 @@ Requires Haxe 4.3.0 or above
You require the following Haxe libraries: You require the following Haxe libraries:
- heaps: The specific version located [here](https://github.com/RandomityGuy/heaps) - heaps: The specific version located [here](https://github.com/RandomityGuy/heaps)
- hlsdl (Obtain the haxelib version of hlsdl, then patch it with these files [here](https://github.com/RandomityGuy/hashlink/tree/master/libs/sdl)) (Hashlink/C native target) - hlsdl (Obtain the haxelib version of hlsdl, then patch it with these files [here](https://github.com/RandomityGuy/hashlink/tree/master/libs/sdl)) (Hashlink/C native target)
- datachannel: obtained from [here](https://github.com/RandomityGuy/hxDatachannel)
- stb_ogg_sound (JS/Browser target) - stb_ogg_sound (JS/Browser target)
- zip 1.1.0 (JS/Browser target) - zip 1.1.0 (JS/Browser target)
@ -88,9 +95,6 @@ This will build the apk file at Export/android/app/build/outputs/apk/release/app
If you are on browser, please send the browser console log to me If you are on browser, please send the browser console log to me
If you are on native, please run marbleblast-debug.bat and reproduce the crash, send the resulting stacktrace that occurs during the crash to me. If you are on native, please run marbleblast-debug.bat and reproduce the crash, send the resulting stacktrace that occurs during the crash to me.
## Help it shows a black screen when playing a level!
Your PC does not support the game, please upgrade it, there is nothing I can do about it to fix it.
## How accurate are the marble physics? ## How accurate are the marble physics?
Very accurate with up to 1% deviation from the original physics. The deviations are due to traplaunches being slightly different and occassional internal edge collisions, and the lower delta t values for physics simulations. Very accurate with up to 1% deviation from the original physics. The deviations are due to traplaunches being slightly different and occassional internal edge collisions, and the lower delta t values for physics simulations.
@ -99,12 +103,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 version, 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, edit settings.json or the options menu in the platinum. 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.

BIN
data/ui/discord.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
data/ui/icon_mbg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
data/ui/icon_mbu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
data/ui/options/misc_d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
data/ui/options/misc_h.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
data/ui/options/misc_i.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
data/ui/options/misc_n.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -82,6 +82,12 @@ class CameraController extends Object {
var _ignoreCursor:Bool = false; var _ignoreCursor:Bool = false;
var hasXInput:Bool = false;
var hasYInput:Bool = false;
var dt:Float;
var wasLastGamepadInput:Bool = false;
public function new(marble:Marble) { public function new(marble:Marble) {
super(); super();
this.marble = marble; this.marble = marble;
@ -166,19 +172,37 @@ class CameraController extends Object {
deltaposY *= len / max; deltaposY *= len / max;
} }
} }
if (!Settings.controlsSettings.alwaysFreeLook && !Key.isDown(Settings.controlsSettings.freelook)) {
deltaposY = 0;
}
var factor = isTouch ? Util.lerp(1 / 25, 1 / 15, var factor = isTouch ? Util.lerp(1 / 25, 1 / 15,
Settings.controlsSettings.cameraSensitivity) : Util.lerp(1 / 1000, 1 / 200, Settings.controlsSettings.cameraSensitivity); Settings.controlsSettings.cameraSensitivity) : Util.lerp(1 / 1000, 1 / 200, Settings.controlsSettings.cameraSensitivity);
if (!Settings.controlsSettings.alwaysFreeLook && !Key.isDown(Settings.controlsSettings.freelook) && !isTouch) {
deltaposY = 0;
}
// CameraPitch += deltaposY * factor; // CameraPitch += deltaposY * factor;
// CameraYaw += deltaposX * factor; // CameraYaw += deltaposX * factor;
nextCameraPitch += deltaposY * factor; nextCameraPitch += deltaposY * factor;
nextCameraYaw += deltaposX * factor; nextCameraYaw += deltaposX * factor;
if (Math.abs(deltaposX) > 0.001)
hasXInput = true;
else
hasXInput = false;
if (Math.abs(deltaposY) > 0.001)
hasYInput = true;
else
hasYInput = false;
if (MarbleGame.instance.touchInput.cameraInput.pressed) {
hasXInput = true;
hasYInput = true;
}
if (!isTouch)
wasLastGamepadInput = false;
else
wasLastGamepadInput = true;
// var rotX = deltaposX * 0.001 * Settings.controlsSettings.cameraSensitivity * Math.PI * 2; // var rotX = deltaposX * 0.001 * Settings.controlsSettings.cameraSensitivity * Math.PI * 2;
// var rotY = deltaposY * 0.001 * Settings.controlsSettings.cameraSensitivity * Math.PI * 2; // var rotY = deltaposY * 0.001 * Settings.controlsSettings.cameraSensitivity * Math.PI * 2;
// CameraYaw -= rotX; // CameraYaw -= rotX;
@ -231,6 +255,14 @@ class CameraController extends Object {
overview = false; overview = false;
} }
function computePitchSpeedFromDelta(delta:Float) {
return Util.clamp(delta, Math.PI / 10, Math.PI / 2) * 4;
}
function applyNonlinearScale(value:Float) {
return Math.pow(Math.abs(value), 1.6) * (value >= 0 ? 1 : -1);
}
function doOverviewCamera(currentTime:Float, dt:Float) { function doOverviewCamera(currentTime:Float, dt:Float) {
var angle = Util.adjustedMod(2 * currentTime * Math.PI / 100.0, 2 * Math.PI); var angle = Util.adjustedMod(2 * currentTime * Math.PI / 100.0, 2 * Math.PI);
var distance = overviewWidth.multiply(2.0 / 3.0); var distance = overviewWidth.multiply(2.0 / 3.0);
@ -256,19 +288,65 @@ class CameraController extends Object {
function doSpectateCamera(currentTime:Float, dt:Float) { function doSpectateCamera(currentTime:Float, dt:Float) {
var camera = level.scene.camera; var camera = level.scene.camera;
var lerpt = Math.pow(0.5, dt / 0.032); // Math.min(1, 1 - Math.pow(0.6, dt / 0.032)); // hxd.Math.min(1, 1 - Math.pow(0.6, dt * 600)); var lerpt = 1 - Math.pow(0.5, dt / 0.016); // Math.min(1, 1 - Math.pow(0.6, dt / 0.032)); // hxd.Math.min(1, 1 - Math.pow(0.6, dt * 600));
var gamepadX = applyNonlinearScale(rescaleDeadZone(Gamepad.getAxis(Settings.gamepadSettings.cameraXAxis), Settings.gamepadSettings.axisDeadzone));
var gamepadY = rescaleDeadZone(Gamepad.getAxis(Settings.gamepadSettings.cameraYAxis), Settings.gamepadSettings.axisDeadzone);
if (gamepadX != 0.0 || gamepadY != 0.0) {
wasLastGamepadInput = true;
}
var cameraPitchDelta = (Key.isDown(Settings.controlsSettings.camBackward) ? 1 : 0) var cameraPitchDelta = (Key.isDown(Settings.controlsSettings.camBackward) ? 1 : 0)
- (Key.isDown(Settings.controlsSettings.camForward) ? 1 : 0) - (Key.isDown(Settings.controlsSettings.camForward) ? 1 : 0)
+ Gamepad.getAxis(Settings.gamepadSettings.cameraYAxis); + gamepadY;
if (Settings.gamepadSettings.invertYAxis) if (Settings.gamepadSettings.invertYAxis || Settings.controlsSettings.invertYAxis)
cameraPitchDelta = -cameraPitchDelta; cameraPitchDelta = -cameraPitchDelta;
nextCameraPitch += 0.75 * 5 * cameraPitchDelta * dt * Settings.gamepadSettings.cameraSensitivity; var cameraYawDelta = (Key.isDown(Settings.controlsSettings.camRight) ? 1 : 0) - (Key.isDown(Settings.controlsSettings.camLeft) ? 1 : 0) + gamepadX;
var cameraYawDelta = (Key.isDown(Settings.controlsSettings.camRight) ? 1 : 0) - (Key.isDown(Settings.controlsSettings.camLeft) ? 1 : 0)
+ Gamepad.getAxis(Settings.gamepadSettings.cameraXAxis);
if (Settings.gamepadSettings.invertXAxis) if (Settings.gamepadSettings.invertXAxis)
cameraYawDelta = -cameraYawDelta; cameraYawDelta = -cameraYawDelta;
nextCameraYaw += 0.75 * 5 * cameraYawDelta * dt * Settings.gamepadSettings.cameraSensitivity;
if (MarbleGame.instance.paused) {
cameraYawDelta = 0;
cameraPitchDelta = 0;
}
var gamePadSensitivity = 1.0;
if (wasLastGamepadInput) {
gamePadSensitivity = Settings.gamepadSettings.cameraSensitivity; // It defaults to 0.6
}
var deltaX = 0.75 * 5 * cameraYawDelta * dt * gamePadSensitivity;
var deltaY = 0.75 * 5 * cameraPitchDelta * dt * gamePadSensitivity;
if (spectateMarbleIndex != -1) {
// Center the pitch
if (Util.isTouchDevice()) { // Do this only on touch devices
if (!Settings.controlsSettings.alwaysFreeLook
&& !Key.isDown(Settings.controlsSettings.freelook)
&& !MarbleGame.instance.touchInput.cameraInput.pressed
&& deltaY == 0.0) {
var rescaledY = deltaY;
if (rescaledY <= 0.0)
rescaledY = 0.4 - rescaledY * -0.75;
else
rescaledY = rescaledY * 1.1 + 0.4;
var movePitchDelta = (rescaledY - CameraPitch);
var movePitchSpeed = computePitchSpeedFromDelta(Math.abs(movePitchDelta)) * dt * 0.8;
if (movePitchDelta <= 0.0) {
movePitchDelta = -movePitchDelta;
if (movePitchDelta < movePitchSpeed)
movePitchSpeed = movePitchDelta;
movePitchDelta = -movePitchSpeed;
movePitchSpeed = movePitchDelta;
} else if (movePitchSpeed > movePitchDelta) {
movePitchSpeed = movePitchDelta;
}
deltaY = movePitchSpeed;
}
}
}
nextCameraYaw += deltaX;
nextCameraPitch += deltaY;
var limits = spectateMarbleIndex == -1 ? 0.0001 : Math.PI / 4; var limits = spectateMarbleIndex == -1 ? 0.0001 : Math.PI / 4;
@ -458,9 +536,10 @@ class CameraController extends Object {
&& (firstHit == null || (rayCastOrigin.distance(result.point) < firstHitDistance))) { && (firstHit == null || (rayCastOrigin.distance(result.point) < firstHitDistance))) {
firstHit = result; firstHit = result;
firstHitDistance = rayCastOrigin.distance(result.point); firstHitDistance = rayCastOrigin.distance(result.point);
processedShapes.push(result.object);
} }
} }
if (firstHit != null)
processedShapes.push(firstHit.object);
if (firstHit != null) { if (firstHit != null) {
if (firstHitDistance < CameraDistance) { if (firstHitDistance < CameraDistance) {
@ -473,7 +552,7 @@ class CameraController extends Object {
var dist = plane.distance(camera.pos.toPoint()); var dist = plane.distance(camera.pos.toPoint());
if (dist >= closeness) if (dist >= closeness)
break; continue;
camera.pos = projected.toVector().add(normal.multiply(-closeness)); camera.pos = projected.toVector().add(normal.multiply(-closeness));
@ -493,10 +572,6 @@ class CameraController extends Object {
this.setPosition(camera.pos.x, camera.pos.y, camera.pos.z); this.setPosition(camera.pos.x, camera.pos.y, camera.pos.z);
} }
function applyNonlinearScale(value:Float) {
return Math.pow(Math.abs(value), 3.2) * (value >= 0 ? 1 : -1);
}
function rescaleDeadZone(value:Float, deadZone:Float) { function rescaleDeadZone(value:Float, deadZone:Float) {
if (deadZone >= value) { if (deadZone >= value) {
if (-deadZone <= value) if (-deadZone <= value)
@ -526,8 +601,8 @@ class CameraController extends Object {
var lerpt = 1 - Math.pow(0.5, dt / 0.016); // Math.min(1, 1 - Math.pow(0.6, dt / 0.032)); // hxd.Math.min(1, 1 - Math.pow(0.6, dt * 600)); var lerpt = 1 - Math.pow(0.5, dt / 0.016); // Math.min(1, 1 - Math.pow(0.6, dt / 0.032)); // hxd.Math.min(1, 1 - Math.pow(0.6, dt * 600));
var gamepadX = applyNonlinearScale(rescaleDeadZone(Gamepad.getAxis(Settings.gamepadSettings.cameraXAxis), 0.25)); var gamepadX = applyNonlinearScale(rescaleDeadZone(Gamepad.getAxis(Settings.gamepadSettings.cameraXAxis), Settings.gamepadSettings.axisDeadzone));
var gamepadY = applyNonlinearScale(rescaleDeadZone(Gamepad.getAxis(Settings.gamepadSettings.cameraYAxis), 0.25)); var gamepadY = rescaleDeadZone(Gamepad.getAxis(Settings.gamepadSettings.cameraYAxis), Settings.gamepadSettings.axisDeadzone);
var cameraPitchDelta = (Key.isDown(Settings.controlsSettings.camBackward) ? 1 : 0) var cameraPitchDelta = (Key.isDown(Settings.controlsSettings.camBackward) ? 1 : 0)
- (Key.isDown(Settings.controlsSettings.camForward) ? 1 : 0) - (Key.isDown(Settings.controlsSettings.camForward) ? 1 : 0)
@ -538,8 +613,53 @@ class CameraController extends Object {
var cameraYawDelta = (Key.isDown(Settings.controlsSettings.camRight) ? 1 : 0) - (Key.isDown(Settings.controlsSettings.camLeft) ? 1 : 0) + gamepadX; var cameraYawDelta = (Key.isDown(Settings.controlsSettings.camRight) ? 1 : 0) - (Key.isDown(Settings.controlsSettings.camLeft) ? 1 : 0) + gamepadX;
if (Settings.gamepadSettings.invertXAxis) if (Settings.gamepadSettings.invertXAxis)
cameraYawDelta = -cameraYawDelta; cameraYawDelta = -cameraYawDelta;
nextCameraYaw += 0.75 * 5 * cameraYawDelta * dt * Settings.gamepadSettings.cameraSensitivity;
if (MarbleGame.instance.paused) {
cameraYawDelta = 0;
cameraPitchDelta = 0;
}
var gamePadSensitivity = 1.0;
if (wasLastGamepadInput) {
gamePadSensitivity = (1.6 - Settings.controlsSettings.cameraSensitivity); // It defaults to 0.6
}
var deltaX = 0.75 * 5 * cameraYawDelta * dt * gamePadSensitivity;
var deltaY = 0.75 * 5 * cameraPitchDelta * dt * gamePadSensitivity;
// Center the pitch
if (Util.isTouchDevice()) { // Do this only on touch devices
if (!Settings.controlsSettings.alwaysFreeLook
&& !Key.isDown(Settings.controlsSettings.freelook)
&& !MarbleGame.instance.touchInput.cameraInput.pressed
&& deltaY == 0.0) {
var rescaledY = deltaY;
if (rescaledY <= 0.0)
rescaledY = 0.4 - rescaledY * -0.75;
else
rescaledY = rescaledY * 1.1 + 0.4;
var movePitchDelta = (rescaledY - CameraPitch);
var movePitchSpeed = computePitchSpeedFromDelta(Math.abs(movePitchDelta)) * dt * 0.8;
if (movePitchDelta <= 0.0) {
movePitchDelta = -movePitchDelta;
if (movePitchDelta < movePitchSpeed)
movePitchSpeed = movePitchDelta;
movePitchDelta = -movePitchSpeed;
movePitchSpeed = movePitchDelta;
} else if (movePitchSpeed > movePitchDelta) {
movePitchSpeed = movePitchDelta;
}
deltaY = movePitchSpeed;
}
}
if (!MarbleGame.instance.touchInput.cameraInput.pressed) {
hasXInput = false;
hasYInput = false;
}
nextCameraYaw += deltaX;
nextCameraPitch += deltaY;
nextCameraPitch = Math.max(-Math.PI / 2 + Math.PI / 4, Math.min(Math.PI / 2 - 0.0001, nextCameraPitch)); nextCameraPitch = Math.max(-Math.PI / 2 + Math.PI / 4, Math.min(Math.PI / 2 - 0.0001, nextCameraPitch));
CameraYaw = Util.lerp(CameraYaw, nextCameraYaw, lerpt); CameraYaw = Util.lerp(CameraYaw, nextCameraYaw, lerpt);
@ -632,9 +752,10 @@ class CameraController extends Object {
&& (firstHit == null || (rayCastOrigin.distance(result.point) < firstHitDistance))) { && (firstHit == null || (rayCastOrigin.distance(result.point) < firstHitDistance))) {
firstHit = result; firstHit = result;
firstHitDistance = rayCastOrigin.distance(result.point); firstHitDistance = rayCastOrigin.distance(result.point);
processedShapes.push(result.object);
} }
} }
if (firstHit != null)
processedShapes.push(firstHit.object);
if (firstHit != null) { if (firstHit != null) {
if (firstHitDistance < CameraDistance) { if (firstHitDistance < CameraDistance) {
@ -647,7 +768,7 @@ class CameraController extends Object {
var dist = plane.distance(camera.pos.toPoint()); var dist = plane.distance(camera.pos.toPoint());
if (dist >= closeness) if (dist >= closeness)
break; continue;
camera.pos = projected.toVector().add(normal.multiply(-closeness)); camera.pos = projected.toVector().add(normal.multiply(-closeness));

View file

@ -1,5 +1,6 @@
package src; package src;
import net.Net;
#if !js #if !js
import sys.FileSystem; import sys.FileSystem;
#end #end
@ -113,6 +114,29 @@ class Console {
} }
public static function eval(cmd:String) { public static function eval(cmd:String) {
var cmdLower = cmd.toLowerCase();
if (StringTools.startsWith(cmdLower, "defaultmarble")) {
// parse regex DefaultMarble.<attribName> = <value>
var regex = ~/defaultmarble\.(\w+)\s*=\s*(.+)/;
var matched = regex.match(cmdLower);
if (matched) {
var attribName = regex.matched(1);
var valueStr = regex.matched(2);
var numValue = Std.parseFloat(valueStr);
if (Math.isNaN(numValue))
numValue = 0;
if (MarbleGame.instance.world != null && !Net.isMP && MarbleGame.instance.world.marble != null) {
MarbleGame.instance.world.marble.setMarbleAttribute(attribName, numValue);
MarbleGame.instance.world.cheatsUsed = true;
log("Set DefaultMarble." + attribName + " to " + numValue);
return;
}
} else {
error("Invalid command format. Expected: DefaultMarble.<attribName> = <value>");
return;
}
}
var cmdSplit = cmd.split(" "); var cmdSplit = cmd.split(" ");
if (cmdSplit.length != 0) { if (cmdSplit.length != 0) {
var cmdType = cmdSplit[0]; var cmdType = cmdSplit[0];

View file

@ -714,17 +714,40 @@ class DifBuilder {
tex = spl[spl.length - 1]; tex = spl[spl.length - 1];
} }
// search with extension first
if (ResourceLoader.exists(Path.directory(path) + "/" + tex)) {
return true;
}
var prevDir = Path.directory(Path.directory(path));
if (ResourceLoader.exists(prevDir + "/" + tex)) {
return true;
}
prevDir = Path.directory(prevDir);
if (ResourceLoader.exists(prevDir + "/" + tex))
return true;
// remove extension from it
if (tex.lastIndexOf(".") != -1) {
tex = tex.substring(0, tex.lastIndexOf("."));
}
#if (js || android) #if (js || android)
path = StringTools.replace(path, "data/", ""); path = StringTools.replace(path, "data/", "");
#end #end
// search jpg, png and bmp
if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".jpg")) { if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".jpg")) {
return true; return true;
} }
if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".png")) { if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".png")) {
return true; return true;
} }
var prevDir = Path.directory(Path.directory(path)); if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".bmp")) {
return true;
}
prevDir = Path.directory(Path.directory(path));
if (ResourceLoader.exists(prevDir + "/" + tex + ".jpg")) { if (ResourceLoader.exists(prevDir + "/" + tex + ".jpg")) {
return true; return true;
@ -732,6 +755,9 @@ class DifBuilder {
if (ResourceLoader.exists(prevDir + "/" + tex + ".png")) { if (ResourceLoader.exists(prevDir + "/" + tex + ".png")) {
return true; return true;
} }
if (ResourceLoader.exists(prevDir + "/" + tex + ".bmp")) {
return true;
}
prevDir = Path.directory(prevDir); prevDir = Path.directory(prevDir);
@ -741,6 +767,9 @@ class DifBuilder {
if (ResourceLoader.exists(prevDir + "/" + tex + ".png")) { if (ResourceLoader.exists(prevDir + "/" + tex + ".png")) {
return true; return true;
} }
if (ResourceLoader.exists(prevDir + "/" + tex + ".bmp")) {
return true;
}
return false; return false;
} }
@ -750,12 +779,36 @@ class DifBuilder {
tex = spl[spl.length - 1]; tex = spl[spl.length - 1];
} }
// search with extension first
if (ResourceLoader.exists(Path.directory(path) + "/" + tex)) {
return Path.directory(path) + "/" + tex;
}
var prevDir = Path.directory(Path.directory(path));
if (ResourceLoader.exists(prevDir + "/" + tex)) {
return prevDir + "/" + tex;
}
prevDir = Path.directory(prevDir);
if (ResourceLoader.exists(prevDir + "/" + tex)) {
return prevDir + "/" + tex;
}
// remove extension from it
if (tex.lastIndexOf(".") != -1) {
tex = tex.substring(0, tex.lastIndexOf("."));
}
if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".jpg")) { if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".jpg")) {
return Path.directory(path) + "/" + tex + ".jpg"; return Path.directory(path) + "/" + tex + ".jpg";
} }
if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".png")) { if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".png")) {
return Path.directory(path) + "/" + tex + ".png"; return Path.directory(path) + "/" + tex + ".png";
} }
if (ResourceLoader.exists(Path.directory(path) + "/" + tex + ".bmp")) {
return Path.directory(path) + "/" + tex + ".bmp";
}
var prevDir = Path.directory(Path.directory(path)); var prevDir = Path.directory(Path.directory(path));
@ -765,6 +818,9 @@ class DifBuilder {
if (ResourceLoader.exists(prevDir + "/" + tex + ".png")) { if (ResourceLoader.exists(prevDir + "/" + tex + ".png")) {
return prevDir + "/" + tex + ".png"; return prevDir + "/" + tex + ".png";
} }
if (ResourceLoader.exists(prevDir + "/" + tex + ".bmp")) {
return prevDir + "/" + tex + ".bmp";
}
var prevDir = Path.directory(prevDir); var prevDir = Path.directory(prevDir);
@ -774,6 +830,9 @@ class DifBuilder {
if (ResourceLoader.exists(prevDir + "/" + tex + ".png")) { if (ResourceLoader.exists(prevDir + "/" + tex + ".png")) {
return prevDir + "/" + tex + ".png"; return prevDir + "/" + tex + ".png";
} }
if (ResourceLoader.exists(prevDir + "/" + tex + ".bmp")) {
return prevDir + "/" + tex + ".bmp";
}
return null; return null;
} }

View file

@ -158,51 +158,37 @@ class InstanceManager {
inst.visibleTicks = inst.visibleTicks - 1; inst.visibleTicks = inst.visibleTicks - 1;
} }
if (inst.gameObject.currentOpacity == 1) var opacity = inst.gameObject.currentOpacity;
if (opacity == 1)
opaqueinstances.push(inst); opaqueinstances.push(inst);
else if (inst.gameObject.currentOpacity != 0) else if (opacity != 0)
transparentinstances.push(inst); transparentinstances.push(inst);
// break; // break;
// } // }
// } // }
} }
} }
var dtsShader = minfo.dtsShader;
// Emit non culled primitives // Emit non culled primitives
if (minfo.meshbatch != null) { if (minfo.meshbatch != null) {
minfo.meshbatch.begin(opaqueinstances.length); minfo.meshbatch.begin(opaqueinstances.length);
for (instance in opaqueinstances) { // Draw the opaque shit first var i = 0;
var dtsShader = minfo.dtsShader; while (i < opaqueinstances.length) {
if (dtsShader != null) { var instance = @:privateAccess opaqueinstances.array[i++];
if (dtsShader != null)
dtsShader.currentOpacity = instance.gameObject.currentOpacity; dtsShader.currentOpacity = instance.gameObject.currentOpacity;
} minfo.meshbatch.worldPosition = instance.emptyObj.getAbsPos();
var transform = instance.emptyObj.getAbsPos();
// minfo.meshbatch.shadersChanged = true;
// minfo.meshbatch.material.mainPass.setPassName(minfo.mesh.material.mainPass.name);
// minfo.meshbatch.material.mainPass.enableLights = minfo.mesh.material.mainPass.enableLights;
minfo.meshbatch.worldPosition = transform;
minfo.meshbatch.emitInstance(); minfo.meshbatch.emitInstance();
} }
} }
if (minfo.transparencymeshbatch != null) { if (minfo.transparencymeshbatch != null) {
minfo.transparencymeshbatch.begin(transparentinstances.length); minfo.transparencymeshbatch.begin(transparentinstances.length);
for (instance in transparentinstances) { // Non opaque shit var i = 0;
var dtsShader = minfo.dtsShader; while (i < transparentinstances.length) {
if (dtsShader != null) { var instance = @:privateAccess transparentinstances.array[i++];
if (dtsShader != null)
dtsShader.currentOpacity = instance.gameObject.currentOpacity; dtsShader.currentOpacity = instance.gameObject.currentOpacity;
} minfo.transparencymeshbatch.worldPosition = instance.emptyObj.getAbsPos();
// minfo.transparencymeshbatch.material.blendMode = Alpha;
// minfo.transparencymeshbatch.material.color.a = instance.gameObject.currentOpacity;
// minfo.transparencymeshbatch.material.mainPass.setPassName(minfo.mesh.material.mainPass.name);
// minfo.transparencymeshbatch.shadersChanged = true;
// minfo.transparencymeshbatch.material.mainPass.enableLights = minfo.mesh.material.mainPass.enableLights;
// minfo.transparencymeshbatch.material.mainPass.depthWrite = false;
// if (dtsShader != null) {
// dtsShader.currentOpacity = instance.gameObject.currentOpacity;
// minfo.transparencymeshbatch.shadersChanged = true;
// }
var transform = instance.emptyObj.getAbsPos();
minfo.transparencymeshbatch.worldPosition = transform;
minfo.transparencymeshbatch.emitInstance(); minfo.transparencymeshbatch.emitInstance();
} }
} }

View file

@ -49,7 +49,7 @@ class Leaderboards {
public static function getScores(mission:String, kind:LeaderboardsKind, cb:Array<LBScore>->Void) { public static function getScores(mission:String, kind:LeaderboardsKind, cb:Array<LBScore>->Void) {
if (!StringTools.startsWith(mission, "data/")) if (!StringTools.startsWith(mission, "data/"))
mission = "data/" + mission; mission = "data/" + mission;
return Http.get('${host}/api/scores?mission=${StringTools.urlEncode(mission)}&game=${game}&view=${kind}&count=10', (b) -> { return Http.get('${host}/api/scores?mission=${StringTools.urlEncode(mission)}&game=${game}&view=${kind}&count=5', (b) -> {
var s = b.toString(); var s = b.toString();
var scores:Array<LBScore> = Json.parse(s).scores; var scores:Array<LBScore> = Json.parse(s).scores;
cb(scores); cb(scores);

View file

@ -137,7 +137,10 @@ class Main extends hxd.App {
// }); // });
} }
static var updateDT:Float;
override function update(dt:Float) { override function update(dt:Float) {
updateDT = dt;
super.update(dt); super.update(dt);
if (loaded) { if (loaded) {
ProfilerUI.begin(); ProfilerUI.begin();
@ -168,6 +171,19 @@ class Main extends hxd.App {
ProfilerUI.end(); ProfilerUI.end();
} }
super.render(e); super.render(e);
#if hl
static var dtAccumulator;
dtAccumulator += updateDT;
if (Settings.optionsSettings.fpsLimit <= 0 || Settings.optionsSettings.vsync) {
e.driver.present();
} else {
if (dtAccumulator >= 1.0 / Settings.optionsSettings.fpsLimit) {
e.driver.present();
dtAccumulator = 0.0;
}
}
#end
} }
static function main() { static function main() {

View file

@ -260,7 +260,9 @@ class Marble extends GameObject {
public var contacts:Array<CollisionInfo> = []; public var contacts:Array<CollisionInfo> = [];
public var bestContact:CollisionInfo; public var bestContact:CollisionInfo;
public var contactEntities:Array<CollisionEntity> = [];
static var contactScratch:Array<CollisionEntity> = [];
static var surfaceScratch:Array<CollisionSurface> = [];
var queuedContacts:Array<CollisionInfo> = []; var queuedContacts:Array<CollisionInfo> = [];
var appliedImpulses:Array<{impulse:Vector, contactImpulse:Bool}> = []; var appliedImpulses:Array<{impulse:Vector, contactImpulse:Bool}> = [];
@ -394,6 +396,7 @@ class Marble extends GameObject {
this.netSmoothOffset = new Vector(); this.netSmoothOffset = new Vector();
this.netCorrected = false; this.netCorrected = false;
this.currentUp = new Vector(0, 0, 1); this.currentUp = new Vector(0, 0, 1);
this.lastContactNormal = new Vector(0, 0, 1);
var marbleDts = new DtsObject(); var marbleDts = new DtsObject();
var marbleShader = ""; var marbleShader = "";
@ -697,12 +700,41 @@ class Marble extends GameObject {
this._bounceKineticFriction = MisParser.parseNumber(attribs.get("bouncekineticfriction")); this._bounceKineticFriction = MisParser.parseNumber(attribs.get("bouncekineticfriction"));
} }
public function setMarbleAttribute(attr:String, value:Float) {
switch (attr.toLowerCase()) {
case "maxrollvelocity":
this._maxRollVelocity = value;
case "angularacceleration":
this._angularAcceleration = value;
case "jumpimpulse":
this._jumpImpulse = value;
case "kineticfriction":
this._kineticFriction = value;
case "staticfriction":
this._staticFriction = value;
case "brakingacceleration":
this._brakingAcceleration = value;
case "gravity":
this._gravity = value;
case "airaccel":
this._airAccel = value;
case "maxdotslide":
this._maxDotSlide = value;
case "minbouncevel":
this._minBounceVel = value;
case "minbouncespeed":
this._minBounceSpeed = value;
case "mintrailvel":
this._minTrailVel = value;
case "bouncekineticfriction":
this._bounceKineticFriction = value;
}
}
function findContacts(collisiomWorld:CollisionWorld, timeState:TimeState) { function findContacts(collisiomWorld:CollisionWorld, timeState:TimeState) {
this.contacts = queuedContacts; this.contacts = queuedContacts;
CollisionPool.clear(); CollisionPool.clear();
var c = collisiomWorld.sphereIntersection(this.collider, timeState); collisiomWorld.sphereIntersection(this.collider, timeState, this.contacts);
this.contactEntities = c.foundEntities;
contacts = contacts.concat(c.contacts);
} }
public function queueCollision(collisionInfo:CollisionInfo) { public function queueCollision(collisionInfo:CollisionInfo) {
@ -817,7 +849,7 @@ class Marble extends GameObject {
function computeMoveForces(m:Move, aControl:Vector, desiredOmega:Vector) { function computeMoveForces(m:Move, aControl:Vector, desiredOmega:Vector) {
var currentGravityDir = this.currentUp.multiply(-1); var currentGravityDir = this.currentUp.multiply(-1);
var R = currentGravityDir.multiply(-this._radius); var R = this.currentUp.multiply(this._radius);
var rollVelocity = this.omega.cross(R); var rollVelocity = this.omega.cross(R);
var axes = this.getMarbleAxis(); var axes = this.getMarbleAxis();
// if (!level.isReplayingMovement) // if (!level.isReplayingMovement)
@ -850,15 +882,15 @@ class Marble extends GameObject {
} }
var rsq = R.lengthSq(); var rsq = R.lengthSq();
var crossP = R.cross(motionDir.multiply(desiredYVelocity).add(sideDir.multiply(desiredXVelocity))).multiply(1 / rsq); var crossP = R.cross(motionDir.multiply(desiredYVelocity).add(sideDir.multiply(desiredXVelocity))).multiply(1 / rsq);
desiredOmega.set(crossP.x, crossP.y, crossP.z); desiredOmega.load(crossP);
aControl.set(desiredOmega.x - this.omega.x, desiredOmega.y - this.omega.y, desiredOmega.z - this.omega.z); aControl.load(desiredOmega.sub(this.omega));
var aScalar = aControl.length(); var aScalar = aControl.length();
if (aScalar > this._angularAcceleration) { if (aScalar > this._angularAcceleration) {
aControl.scale(this._angularAcceleration / aScalar); aControl.scale(this._angularAcceleration / aScalar);
} }
return false; return false;
} }
return return true; return true;
} }
function velocityCancel(timeState:TimeState, surfaceSlide:Bool, noBounce:Bool, stoppedPaths:Bool, pi:Array<PathedInterior>) { function velocityCancel(timeState:TimeState, surfaceSlide:Bool, noBounce:Bool, stoppedPaths:Bool, pi:Array<PathedInterior>) {
@ -873,7 +905,7 @@ class Marble extends GameObject {
var sVel = this.velocity.sub(contacts[i].velocity); var sVel = this.velocity.sub(contacts[i].velocity);
var surfaceDot = contacts[i].normal.dot(sVel); var surfaceDot = contacts[i].normal.dot(sVel);
if ((!looped && surfaceDot < 0) || surfaceDot < -SurfaceDotThreshold) { if ((!looped && surfaceDot < 0.0) || surfaceDot < -SurfaceDotThreshold) {
var velLen = this.velocity.length(); var velLen = this.velocity.length();
var surfaceVel = this.contacts[i].normal.multiply(surfaceDot); var surfaceVel = this.contacts[i].normal.multiply(surfaceDot);
@ -908,7 +940,7 @@ class Marble extends GameObject {
} }
contacts[i].velocity.load(otherMarble.velocity); contacts[i].velocity.load(otherMarble.velocity);
} else { } else {
if (contacts[i].velocity.length() == 0 && !surfaceSlide && surfaceDot > -this._maxDotSlide * velLen) { if (contacts[i].velocity.length() == 0.0 && !surfaceSlide && surfaceDot > -this._maxDotSlide * velLen) {
this.velocity.load(this.velocity.sub(surfaceVel)); this.velocity.load(this.velocity.sub(surfaceVel));
this.velocity.normalize(); this.velocity.normalize();
this.velocity.load(this.velocity.multiply(velLen)); this.velocity.load(this.velocity.multiply(velLen));
@ -934,7 +966,7 @@ class Marble extends GameObject {
vAtC.load(vAtC.sub(contacts[i].normal.multiply(contacts[i].normal.dot(sVel)))); vAtC.load(vAtC.sub(contacts[i].normal.multiply(contacts[i].normal.dot(sVel))));
var vAtCMag = vAtC.length(); var vAtCMag = vAtC.length();
if (vAtCMag != 0) { if (vAtCMag != 0.0) {
var friction = this._bounceKineticFriction * contacts[i].friction; var friction = this._bounceKineticFriction * contacts[i].friction;
var angVMagnitude = friction * 5 * normalVel / (2 * this._radius); var angVMagnitude = friction * 5 * normalVel / (2 * this._radius);
@ -970,7 +1002,7 @@ class Marble extends GameObject {
} }
} }
} while (!done && itersIn < 1e4); // Maximum limit pls } while (!done && itersIn < 1e4); // Maximum limit pls
if (this.velocity.lengthSq() < 625) { if (this.velocity.lengthSq() < 625.0) {
var gotOne = false; var gotOne = false;
var dir = new Vector(0, 0, 0); var dir = new Vector(0, 0, 0);
for (j in 0...contacts.length) { for (j in 0...contacts.length) {
@ -994,10 +1026,10 @@ class Marble extends GameObject {
soFar += (dist - outVel * timeToSeparate) / timeToSeparate / contacts[k].normal.dot(dir); soFar += (dist - outVel * timeToSeparate) / timeToSeparate / contacts[k].normal.dot(dir);
} }
} }
if (soFar < -25) if (soFar < -25.0)
soFar = -25; soFar = -25.0;
if (soFar > 25) if (soFar > 25.0)
soFar = 25; soFar = 25.0;
this.velocity.load(this.velocity.add(dir.multiply(soFar))); this.velocity.load(this.velocity.add(dir.multiply(soFar)));
} }
} }
@ -1066,7 +1098,7 @@ class Marble extends GameObject {
slipping = false; slipping = false;
} }
var vAtCDir = vAtC.multiply(1 / vAtCMag); var vAtCDir = vAtC.multiply(1 / vAtCMag);
aFriction.load(bestContact.normal.multiply(-1).cross(vAtCDir.multiply(-1)).multiply(angAMagnitude)); aFriction.load(bestContact.normal.cross(vAtCDir).multiply(angAMagnitude));
AFriction.load(vAtCDir.multiply(-AMagnitude)); AFriction.load(vAtCDir.multiply(-AMagnitude));
this._slipAmount = vAtCMag - totalDeltaV; this._slipAmount = vAtCMag - totalDeltaV;
} }
@ -1093,16 +1125,16 @@ class Marble extends GameObject {
friction2 = this._kineticFriction * bestContact.friction; friction2 = this._kineticFriction * bestContact.friction;
Aadd.load(Aadd.multiply(friction2 * bestNormalForce / aAtCMag)); Aadd.load(Aadd.multiply(friction2 * bestNormalForce / aAtCMag));
} }
A.set(A.x + Aadd.x, A.y + Aadd.y, A.z + Aadd.z); A.load(A.add(Aadd));
a.set(a.x + aadd.x, a.y + aadd.y, a.z + aadd.z); a.load(a.add(aadd));
} }
A.set(A.x + AFriction.x, A.y + AFriction.y, A.z + AFriction.z); A.load(A.add(AFriction));
a.set(a.x + aFriction.x, a.y + aFriction.y, a.z + aFriction.z); a.load(a.add(aFriction));
lastContactNormal = bestContact.normal; lastContactNormal = bestContact.normal;
lastContactPosition = this.getAbsPos().getPosition(); lastContactPosition = this.getAbsPos().getPosition();
} }
a.set(a.x + aControl.x, a.y + aControl.y, a.z + aControl.z); a.load(a.add(aControl));
if (this.mode == Finish) { if (this.mode == Finish) {
a.set(); // Zero it out a.set(); // Zero it out
} }
@ -1274,7 +1306,10 @@ class Marble extends GameObject {
searchbox.addSpherePos(position.x, position.y, position.z, _radius); searchbox.addSpherePos(position.x, position.y, position.z, _radius);
searchbox.addSpherePos(position.x + velocity.x * deltaT, position.y + velocity.y * deltaT, position.z + velocity.z * deltaT, _radius); searchbox.addSpherePos(position.x + velocity.x * deltaT, position.y + velocity.y * deltaT, position.z + velocity.z * deltaT, _radius);
var foundObjs = this.collisionWorld.boundingSearch(searchbox); contactScratch.resize(0);
this.collisionWorld.boundingSearch(searchbox, contactScratch);
var foundObjs = contactScratch;
var finalT = deltaT; var finalT = deltaT;
var found = false; var found = false;
@ -1317,7 +1352,7 @@ class Marble extends GameObject {
// var iterationFound = false; // var iterationFound = false;
for (obj in foundObjs) { for (obj in foundObjs) {
// Its an MP so bruh // Its an MP so bruh
if (obj.go != null && !obj.go.isCollideable) if (obj.go == this || (obj.go != null && !obj.go.isCollideable))
continue; continue;
var isDts = obj.go is DtsObject; var isDts = obj.go is DtsObject;
@ -1345,11 +1380,13 @@ class Marble extends GameObject {
Math.max(Math.max(sphereRadius.x, sphereRadius.y), sphereRadius.z) * 2); Math.max(Math.max(sphereRadius.x, sphereRadius.y), sphereRadius.z) * 2);
var currentFinalPos = position.add(relVel.multiply(finalT)); // localpos.add(relLocalVel.multiply(finalT)); var currentFinalPos = position.add(relVel.multiply(finalT)); // localpos.add(relLocalVel.multiply(finalT));
var surfaces = @:privateAccess obj.grid != null ? @:privateAccess obj.grid.boundingSearch(boundThing) : (obj.bvh == null ? obj.octree.boundingSearch(boundThing) surfaceScratch.resize(0);
.map(x -> cast x) : obj.bvh.boundingSearch(boundThing)); if (@:privateAccess obj.grid != null)
@:privateAccess obj.grid.boundingSearch(boundThing, surfaceScratch);
var surfaces = surfaceScratch;
for (surf in surfaces) { for (surf in surfaces) {
var surface:CollisionSurface = cast surf; var surface:CollisionSurface = surf;
currentFinalPos = position.add(relVel.multiply(finalT)); currentFinalPos = position.add(relVel.multiply(finalT));
@ -1371,7 +1408,7 @@ class Marble extends GameObject {
var surfaceNormal = new Vector(verts.nx, verts.ny, var surfaceNormal = new Vector(verts.nx, verts.ny,
verts.nz); // surface.normals[surface.indices[i]].transformed3x3(obj.transform).normalized(); verts.nz); // surface.normals[surface.indices[i]].transformed3x3(obj.transform).normalized();
if (obj is DtsObject) if (obj is DtsObject)
surfaceNormal.multiply(-1); surfaceNormal.load(v.sub(v0).cross(v2.sub(v0)).normalized().multiply(-1));
var surfaceD = -surfaceNormal.dot(v0); var surfaceD = -surfaceNormal.dot(v0);
// If we're going the wrong direction or not going to touch the plane, ignore... // If we're going the wrong direction or not going to touch the plane, ignore...
@ -1904,7 +1941,7 @@ class Marble extends GameObject {
} }
// } // }
} }
this.queuedContacts = []; this.queuedContacts.resize(0);
newPos = this.collider.transform.getPosition(); // this.getAbsPos().getPosition().clone(); newPos = this.collider.transform.getPosition(); // this.getAbsPos().getPosition().clone();
@ -1983,7 +2020,9 @@ class Marble extends GameObject {
// marbleHitbox.offset(end.x, end.y, end.z); // marbleHitbox.offset(end.x, end.y, end.z);
// spherebounds.addSpherePos(gjkCapsule.p2.x, gjkCapsule.p2.y, gjkCapsule.p2.z, gjkCapsule.radius); // spherebounds.addSpherePos(gjkCapsule.p2.x, gjkCapsule.p2.y, gjkCapsule.p2.z, gjkCapsule.radius);
var contacts = this.collisionWorld.boundingSearch(box); contactScratch.resize(0);
this.collisionWorld.boundingSearch(box, contactScratch);
var contacts = contactScratch;
// var contacts = marble.contactEntities; // var contacts = marble.contactEntities;
var inside = []; var inside = [];
@ -2036,7 +2075,9 @@ class Marble extends GameObject {
var checkSphereRadius = checkBounds.getMax().sub(checkBoundsCenter).length(); var checkSphereRadius = checkBounds.getMax().sub(checkBoundsCenter).length();
var checkSphere = new Bounds(); var checkSphere = new Bounds();
checkSphere.addSpherePos(checkBoundsCenter.x, checkBoundsCenter.y, checkBoundsCenter.z, checkSphereRadius); checkSphere.addSpherePos(checkBoundsCenter.x, checkBoundsCenter.y, checkBoundsCenter.z, checkSphereRadius);
var endpadBB = this.collisionWorld.boundingSearch(checkSphere, false); contactScratch.resize(0);
this.collisionWorld.boundingSearch(checkSphere, contactScratch, false);
var endpadBB = contactScratch;
var found = false; var found = false;
for (collider in endpadBB) { for (collider in endpadBB) {
if (collider.go == @:privateAccess this.level.endPad) { if (collider.go == @:privateAccess this.level.endPad) {
@ -2390,6 +2431,8 @@ class Marble extends GameObject {
if (Key.isDown(Settings.controlsSettings.right)) { if (Key.isDown(Settings.controlsSettings.right)) {
move.d.y -= 1; move.d.y -= 1;
} }
move.d.x = Util.clamp(move.d.x, -1, 1);
move.d.y = Util.clamp(move.d.y, -1, 1);
if (Key.isDown(Settings.controlsSettings.jump) if (Key.isDown(Settings.controlsSettings.jump)
|| MarbleGame.instance.touchInput.jumpButton.pressed || MarbleGame.instance.touchInput.jumpButton.pressed
|| Gamepad.isDown(Settings.gamepadSettings.jump)) { || Gamepad.isDown(Settings.gamepadSettings.jump)) {
@ -2818,7 +2861,6 @@ class Marble extends GameObject {
this.megaMarbleUseTick = 0; this.megaMarbleUseTick = 0;
this.netFlags = MarbleNetFlags.DoBlast | MarbleNetFlags.DoMega | MarbleNetFlags.DoHelicopter | MarbleNetFlags.DoShockAbsorber | MarbleNetFlags.DoSuperBounce | MarbleNetFlags.PickupPowerup | MarbleNetFlags.GravityChange | MarbleNetFlags.UsePowerup; this.netFlags = MarbleNetFlags.DoBlast | MarbleNetFlags.DoMega | MarbleNetFlags.DoHelicopter | MarbleNetFlags.DoShockAbsorber | MarbleNetFlags.DoSuperBounce | MarbleNetFlags.PickupPowerup | MarbleNetFlags.GravityChange | MarbleNetFlags.UsePowerup;
this.lastContactNormal = new Vector(0, 0, 1); this.lastContactNormal = new Vector(0, 0, 1);
this.contactEntities = [];
this.cloak = false; this.cloak = false;
this._firstTick = true; this._firstTick = true;
this.lastRespawnTick = -100000; this.lastRespawnTick = -100000;

View file

@ -42,7 +42,7 @@ class MarbleGame {
static var instance:MarbleGame; static var instance:MarbleGame;
static var currentVersion = "1.7.0"; static var currentVersion = "1.7.3";
var world:MarbleWorld; var world:MarbleWorld;
@ -88,7 +88,6 @@ class MarbleGame {
return; // don't pause return; // don't pause
} }
paused = true;
handlePauseGame(); handlePauseGame();
// Focus the shit again // Focus the shit again
var jsCanvas = @:privateAccess Window.getInstance().canvas; var jsCanvas = @:privateAccess Window.getInstance().canvas;
@ -260,7 +259,6 @@ class MarbleGame {
|| (Net.isMP && paused && !(MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1] is MPExitGameDlg))) { || (Net.isMP && paused && !(MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1] is MPExitGameDlg))) {
return; // don't pause return; // don't pause
} }
paused = !paused;
handlePauseGame(); handlePauseGame();
} }
} }
@ -288,13 +286,7 @@ class MarbleGame {
} }
} }
public function handlePauseGame() { public function showPauseUI() {
if (paused && world._ready) {
Console.log("Game paused");
world.setCursorLock(false);
if (Util.isTouchDevice()) {
this.touchInput.movementInput.forceRelease();
}
if (world.isMultiplayer) { if (world.isMultiplayer) {
exitGameDlg = new MPExitGameDlg(() -> { exitGameDlg = new MPExitGameDlg(() -> {
canvas.popDialog(exitGameDlg); canvas.popDialog(exitGameDlg);
@ -337,8 +329,20 @@ class MarbleGame {
}); });
} }
canvas.pushDialog(exitGameDlg); canvas.pushDialog(exitGameDlg);
}
public function handlePauseGame() {
if (!paused && world._ready) {
paused = true;
Console.log("Game paused");
world.setCursorLock(false);
if (Util.isTouchDevice()) {
this.touchInput.movementInput.forceRelease();
}
showPauseUI();
} else { } else {
if (world._ready) { if (world._ready) {
paused = false;
Console.log("Game unpaused"); Console.log("Game unpaused");
if (exitGameDlg != null) if (exitGameDlg != null)
canvas.popDialog(exitGameDlg); canvas.popDialog(exitGameDlg);

View file

@ -218,6 +218,8 @@ class MarbleWorld extends Scheduler {
public var rewinding:Bool = false; public var rewinding:Bool = false;
public var rewindUsed:Bool = false; public var rewindUsed:Bool = false;
public var cheatsUsed:Bool = false;
public var inputRecorder:InputRecorder; public var inputRecorder:InputRecorder;
public var isReplayingMovement:Bool = false; public var isReplayingMovement:Bool = false;
public var currentInputMoves:Array<InputRecorderFrame>; public var currentInputMoves:Array<InputRecorderFrame>;
@ -1489,13 +1491,31 @@ class MarbleWorld extends Scheduler {
return packets; return packets;
} }
inline function hasPredictionFlag(mask:Int, clientId:Int):Bool {
return (mask & (1 << clientId)) != 0;
}
public function applyReceivedMoves() { public function applyReceivedMoves() {
var needsPrediction = 0; var needsPrediction = 0;
inline function correctPrediction(target:Marble, packet:MarbleUpdatePacket, tick:Int, bitMask:Int, ?pending:Array<MarbleUpdatePacket>) {
target.unpackUpdate(packet);
needsPrediction |= bitMask;
if (pending != null)
pending.insert(0, packet);
if (tick >= 0)
predictions.clearStatesAfterTick(target, tick);
}
if (!lastMoves.ourMoveApplied) { if (!lastMoves.ourMoveApplied) {
var ourMove = lastMoves.myMarbleUpdate; var ourMove = lastMoves.myMarbleUpdate;
if (ourMove != null) { if (ourMove != null) {
var ourMoveStruct = Net.clientConnection.acknowledgeMove(ourMove.move, timeState); var ourMoveStruct = Net.clientConnection.acknowledgeMove(ourMove.move, timeState);
lastMoves.ourMoveApplied = true; lastMoves.ourMoveApplied = true;
var hasStruct = ourMoveStruct != null;
var ourTick = hasStruct ? ourMoveStruct.timeState.ticks : -1;
for (client => arr in lastMoves.otherMarbleUpdates) { for (client => arr in lastMoves.otherMarbleUpdates) {
var lastMove = null; var lastMove = null;
while (arr.packets.length > 0) { while (arr.packets.length > 0) {
@ -1506,68 +1526,35 @@ class MarbleWorld extends Scheduler {
break; break;
} }
} }
if (lastMove != null) {
// clientMarbles[Net.clientIdMap[client]].unpackUpdate(lastMove); if (lastMove == null || ourMove.serverTicks != lastMove.serverTicks)
// needsPrediction |= 1 << client; continue;
// arr.insert(0, lastMove);
var clientMarble = clientMarbles[Net.clientIdMap[client]]; var connection = Net.clientIdMap[client];
if (clientMarble != null) { if (connection == null)
if (ourMove.serverTicks == lastMove.serverTicks) { continue;
if (ourMoveStruct != null) { var clientMarble = clientMarbles[connection];
if (clientMarble == null)
continue;
var mask = 1 << client;
if (hasStruct) {
var otherPred = predictions.retrieveState(clientMarble, ourMoveStruct.timeState.ticks); var otherPred = predictions.retrieveState(clientMarble, ourMoveStruct.timeState.ticks);
if (otherPred != null) { if (otherPred == null || otherPred.getError(lastMove) > 0.01) {
if (otherPred.getError(lastMove) > 0.01) { correctPrediction(clientMarble, lastMove, ourTick, mask, arr.packets);
// Debug.drawSphere(@:privateAccess clientMarbles[Net.clientIdMap[client]].newPos, 0.2, 0.5);
// trace('Prediction error: ${otherPred.getError(lastMove)}');
// trace('Desync for tick ${ourMoveStruct.timeState.ticks}');
clientMarble.unpackUpdate(lastMove);
needsPrediction |= 1 << client;
arr.packets.insert(0, lastMove);
predictions.clearStatesAfterTick(clientMarbles[Net.clientIdMap[client]], ourMoveStruct.timeState.ticks);
} }
} else { } else {
// Debug.drawSphere(@:privateAccess clientMarbles[Net.clientIdMap[client]].newPos, 0.2, 0.5); correctPrediction(clientMarble, lastMove, -1, mask, arr.packets);
// trace('Desync for tick ${ourMoveStruct.timeState.ticks}');
clientMarble.unpackUpdate(lastMove);
needsPrediction |= 1 << client;
arr.packets.insert(0, lastMove);
predictions.clearStatesAfterTick(clientMarble, ourMoveStruct.timeState.ticks);
}
} else {
// Debug.drawSphere(@:privateAccess clientMarbles[Net.clientIdMap[client]].newPos, 0.2, 0.5);
// trace('Desync in General');
clientMarble.unpackUpdate(lastMove);
needsPrediction |= 1 << client;
arr.packets.insert(0, lastMove);
// predictions.clearStatesAfterTick(clientMarbles[Net.clientIdMap[client]], ourMoveStruct.timeState.ticks);
} }
} }
}
}
}
// marble.unpackUpdate(ourMove);
// needsPrediction |= 1 << Net.clientId;
if (!Net.clientSpectate) { if (!Net.clientSpectate) {
if (ourMoveStruct != null) { if (hasStruct) {
var ourPred = predictions.retrieveState(marble, ourMoveStruct.timeState.ticks); var ourPred = predictions.retrieveState(marble, ourTick);
if (ourPred != null) { if (ourPred == null || ourPred.getError(ourMove) > 0.01) {
if (ourPred.getError(ourMove) > 0.01) { correctPrediction(marble, ourMove, ourTick, 1 << Net.clientId);
// trace('Desync for tick ${ourMoveStruct.timeState.ticks}');
marble.unpackUpdate(ourMove);
needsPrediction |= 1 << Net.clientId;
predictions.clearStatesAfterTick(marble, ourMoveStruct.timeState.ticks);
} }
} else { } else {
// trace('Desync for tick ${ourMoveStruct.timeState.ticks}'); correctPrediction(marble, ourMove, -1, 1 << Net.clientId);
marble.unpackUpdate(ourMove);
needsPrediction |= 1 << Net.clientId;
predictions.clearStatesAfterTick(marble, ourMoveStruct.timeState.ticks);
}
} else {
// trace('Desync in General');
marble.unpackUpdate(ourMove);
needsPrediction |= 1 << Net.clientId;
// predictions.clearStatesAfterTick(marble, ourMoveStruct.timeState.ticks);
} }
} }
} }
@ -2524,7 +2511,7 @@ class MarbleWorld extends Scheduler {
} else { } else {
nextLevelCode(); nextLevelCode();
} }
}, mission, finishTime); }, mission, finishTime, this.replay.write());
MarbleGame.canvas.pushDialog(egg); MarbleGame.canvas.pushDialog(egg);
this.setCursorLock(false); this.setCursorLock(false);
return 0; return 0;

View file

@ -52,6 +52,7 @@ class ReplayFrame {
var t = (time - this.time) / (next.time - this.time); var t = (time - this.time) / (next.time - this.time);
var dt = time - this.time; var dt = time - this.time;
var clockDt = next.clockTime - this.clockTime;
var interpFrame = new ReplayFrame(); var interpFrame = new ReplayFrame();
@ -59,6 +60,7 @@ class ReplayFrame {
interpFrame.time = time; interpFrame.time = time;
interpFrame.bonusTime = this.bonusTime; interpFrame.bonusTime = this.bonusTime;
interpFrame.clockTime = this.clockTime; interpFrame.clockTime = this.clockTime;
if (clockDt > 0) {
if (interpFrame.bonusTime != 0 && time >= 3.5) { if (interpFrame.bonusTime != 0 && time >= 3.5) {
if (dt <= this.bonusTime) { if (dt <= this.bonusTime) {
interpFrame.bonusTime -= dt; interpFrame.bonusTime -= dt;
@ -73,6 +75,7 @@ class ReplayFrame {
interpFrame.clockTime += (this.time + dt) - 3.5; interpFrame.clockTime += (this.time + dt) - 3.5;
} }
} }
}
// Interpolate marble // Interpolate marble
if (this.marbleStateFlags.has(InstantTeleport)) { if (this.marbleStateFlags.has(InstantTeleport)) {
@ -291,7 +294,9 @@ class Replay {
public function endFrame() { public function endFrame() {
// Do not record frames beyond par time/5 minutes to limit file size, if we aren't explicitly recording // Do not record frames beyond par time/5 minutes to limit file size, if we aren't explicitly recording
if (!MarbleGame.instance.toRecord && currentRecordFrame.clockTime > Math.min(300, MarbleGame.instance.world.mission.qualifyTime)) { if (!MarbleGame.instance.toRecord
&& currentRecordFrame != null
&& currentRecordFrame.clockTime > Math.min(300, MarbleGame.instance.world.mission.qualifyTime)) {
currentRecordFrame = null; currentRecordFrame = null;
return; return;
} }

View file

@ -466,7 +466,7 @@ class Settings {
highscoreName = ""; highscoreName = "";
} }
userId = json.userId; userId = json.userId;
if (userId == null) { if (userId == null || userId == "") {
userId = Uuid.v4(); userId = Uuid.v4();
} }
} else { } else {

View file

@ -30,6 +30,14 @@ class Util {
return value; return value;
} }
public static inline function imin(a:Int, b:Int) {
return a < b ? a : b;
}
public static inline function imax(a:Int, b:Int) {
return a > b ? a : b;
}
public static inline function lerp(a:Float, b:Float, t:Float) { public static inline function lerp(a:Float, b:Float, t:Float) {
return a + (b - a) * t; return a + (b - a) * t;
} }

View file

@ -56,7 +56,5 @@ class BoxCollisionEntity extends CollisionEntity implements IBVHObject {
return Math.POSITIVE_INFINITY; return Math.POSITIVE_INFINITY;
} }
public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState) { public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState, contacts:Array<CollisionInfo>) {}
return [];
}
} }

View file

@ -23,8 +23,7 @@ class CollisionEntity implements IOctreeObject implements IBVHObject {
public var octree:Octree; public var octree:Octree;
public var bvh:BVHTree<CollisionSurface>; // public var bvh:BVHTree<CollisionSurface>;
var grid:Grid; var grid:Grid;
public var surfaces:Array<CollisionSurface>; public var surfaces:Array<CollisionSurface>;
@ -67,12 +66,12 @@ class CollisionEntity implements IOctreeObject implements IBVHObject {
// Generates the bvh // Generates the bvh
public function finalize() { public function finalize() {
this.generateBoundingBox(); this.generateBoundingBox();
#if hl // #if hl
this.bvh = new BVHTree(); // this.bvh = new BVHTree();
for (surface in this.surfaces) { // for (surface in this.surfaces) {
this.bvh.add(surface); // this.bvh.add(surface);
} // }
#end // #end
var bbox = new Bounds(); var bbox = new Bounds();
for (surface in this.surfaces) for (surface in this.surfaces)
bbox.add(surface.boundingBox); bbox.add(surface.boundingBox);
@ -90,7 +89,8 @@ class CollisionEntity implements IOctreeObject implements IBVHObject {
} }
go = null; go = null;
surfaces = null; surfaces = null;
bvh = null; grid = null;
// bvh = null;
octree = null; octree = null;
} }
@ -200,7 +200,9 @@ class CollisionEntity implements IOctreeObject implements IBVHObject {
this.priority = priority; this.priority = priority;
} }
public function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState) { static var surfaceSearchPool:Array<CollisionSurface> = [];
public function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState, contacts:Array<CollisionInfo>) {
var position = collisionEntity.transform.getPosition(); var position = collisionEntity.transform.getPosition();
var radius = collisionEntity.radius + 0.001; var radius = collisionEntity.radius + 0.001;
@ -215,7 +217,9 @@ class CollisionEntity implements IOctreeObject implements IBVHObject {
var invScale = invMatrix.getScale(); var invScale = invMatrix.getScale();
var sphereRadius = new Vector(radius * invScale.x, radius * invScale.y, radius * invScale.z); var sphereRadius = new Vector(radius * invScale.x, radius * invScale.y, radius * invScale.z);
sphereBounds.addSpherePos(localPos.x, localPos.y, localPos.z, Math.max(Math.max(sphereRadius.x, sphereRadius.y), sphereRadius.z) * 1.1); sphereBounds.addSpherePos(localPos.x, localPos.y, localPos.z, Math.max(Math.max(sphereRadius.x, sphereRadius.y), sphereRadius.z) * 1.1);
var surfaces = grid.boundingSearch(sphereBounds); // bvh == null ? octree.boundingSearch(sphereBounds).map(x -> cast x) : bvh.boundingSearch(sphereBounds); surfaceSearchPool.resize(0);
grid.boundingSearch(sphereBounds, surfaceSearchPool);
var surfaces = surfaceSearchPool;
var invtform = invMatrix.clone(); var invtform = invMatrix.clone();
invtform.transpose(); invtform.transpose();
@ -227,8 +231,6 @@ class CollisionEntity implements IOctreeObject implements IBVHObject {
invtform.load(Matrix.I()); invtform.load(Matrix.I());
} }
var contacts = [];
for (obj in surfaces) { for (obj in surfaces) {
var surface:CollisionSurface = cast obj; var surface:CollisionSurface = cast obj;
@ -301,7 +303,5 @@ class CollisionEntity implements IOctreeObject implements IBVHObject {
// if (surfaceBestContact != null) // if (surfaceBestContact != null)
// contacts.push(surfaceBestContact); // contacts.push(surfaceBestContact);
} }
return contacts;
} }
} }

View file

@ -18,7 +18,7 @@ class CollisionHull extends CollisionEntity {
super(go); super(go);
} }
public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState):Array<CollisionInfo> { public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState, contacts:Array<CollisionInfo>) {
var bbox = this.boundingBox; var bbox = this.boundingBox;
var box = new Bounds(); var box = new Bounds();
var pos = collisionEntity.transform.getPosition(); var pos = collisionEntity.transform.getPosition();
@ -51,10 +51,9 @@ class CollisionHull extends CollisionEntity {
cinfo.friction = friction; cinfo.friction = friction;
cinfo.force = force; cinfo.force = force;
this.go.onMarbleContact(collisionEntity.marble, timeState, cinfo); this.go.onMarbleContact(collisionEntity.marble, timeState, cinfo);
return [cinfo]; contacts.push(cinfo);
} }
} }
return [];
} }
public override function addSurface(surface:CollisionSurface) { public override function addSurface(surface:CollisionSurface) {

View file

@ -38,11 +38,12 @@ class CollisionWorld {
this.dynamicGrid.build(); this.dynamicGrid.build();
} }
public function sphereIntersection(spherecollision:SphereCollisionEntity, timeState:TimeState):SphereIntersectionResult { var contactList:Array<CollisionInfo> = [];
var intersectionList:Array<CollisionEntity> = [];
public function sphereIntersection(spherecollision:SphereCollisionEntity, timeState:TimeState, contacts:Array<CollisionInfo>) {
var position = spherecollision.transform.getPosition(); var position = spherecollision.transform.getPosition();
var radius = spherecollision.radius; var radius = spherecollision.radius;
// var velocity = spherecollision.velocity;
// var intersections = this.octree.radiusSearch(position, searchdist);
var box = new Bounds(); var box = new Bounds();
box.addSpherePos(0, 0, 0, radius); box.addSpherePos(0, 0, 0, radius);
@ -50,60 +51,24 @@ class CollisionWorld {
box.transform(rotQuat.toMatrix()); box.transform(rotQuat.toMatrix());
box.offset(position.x, position.y, position.z); box.offset(position.x, position.y, position.z);
// box.addSpherePos(position.x + velocity.x * timeState.dt, position.y + velocity.y * timeState.dt, position.z + velocity.z * timeState.dt, radius); // box.addSpherePos(position.x + velocity.x * timeState.dt, position.y + velocity.y * timeState.dt, position.z + velocity.z * timeState.dt, radius);
var intersections = this.grid.boundingSearch(box); this.intersectionList.resize(0);
this.grid.boundingSearch(box, this.intersectionList);
dynamicGrid.boundingSearch(box, this.intersectionList);
// var intersections = this.rtree.search([box.xMin, box.yMax, box.zMin], [box.xSize, box.ySize, box.zSize]); for (obj in this.intersectionList) {
var contacts = [];
var foundEntities = [];
for (obj in intersections) {
var entity:CollisionEntity = cast obj;
foundEntities.push(entity);
if (entity.go.isCollideable) {
contacts = contacts.concat(entity.sphereIntersection(spherecollision, timeState));
}
}
// if (marbleEntities.length > 1) {
// marbleSap.recompute();
// var sapCollisions = marbleSap.getIntersections(spherecollision);
// for (obj in sapCollisions) {
// if (obj.go.isCollideable) {
// contacts = contacts.concat(obj.sphereIntersection(spherecollision, timeState));
// }
// }
// }
// contacts = contacts.concat(this.staticWorld.sphereIntersection(spherecollision, timeState));
var dynSearch = dynamicGrid.boundingSearch(box);
for (obj in dynSearch) {
if (obj != spherecollision) { if (obj != spherecollision) {
var col = cast(obj, CollisionEntity); var entity = obj;
if (col.boundingBox.collide(box) && col.go.isCollideable)
contacts = contacts.concat(col.sphereIntersection(spherecollision, timeState)); if (obj.boundingBox.collide(box) && entity.go.isCollideable) {
entity.sphereIntersection(spherecollision, timeState, contacts);
}
}
} }
} }
// for (marb in marbleEntities) { public function boundingSearch(bounds:Bounds, contacts:Array<CollisionEntity>, useCache:Bool = true) {
// if (marb != spherecollision) { this.grid.boundingSearch(bounds, contacts);
// if (spherecollision.go.isCollideable) { dynamicGrid.boundingSearch(bounds, contacts);
// var isecs = marb.sphereIntersection(spherecollision, timeState);
// if (isecs.length > 0)
// foundEntities.push(marb);
// contacts = contacts.concat(isecs);
// }
// }
// }
return {foundEntities: foundEntities, contacts: contacts};
}
public function boundingSearch(bounds:Bounds, useCache:Bool = true) {
var contacts = this.grid.boundingSearch(bounds).map(x -> cast(x, CollisionEntity));
contacts = contacts.concat(dynamicGrid.boundingSearch(bounds).map(x -> cast(x, CollisionEntity)));
return contacts;
} }
public function rayCast(rayStart:Vector, rayDirection:Vector, rayLength:Float) { public function rayCast(rayStart:Vector, rayDirection:Vector, rayLength:Float) {
@ -116,19 +81,17 @@ class CollisionWorld {
+ rayDirection.x * rayLength, rayStart.y + rayDirection.x * rayLength, rayStart.y
+ rayDirection.y * rayLength, rayStart.z + rayDirection.y * rayLength, rayStart.z
+ rayDirection.z * rayLength); + rayDirection.z * rayLength);
var objs = this.grid.boundingSearch(bounds); this.intersectionList.resize(0);
var dynObjs = dynamicGrid.boundingSearch(bounds);
this.grid.boundingSearch(bounds, this.intersectionList);
dynamicGrid.boundingSearch(bounds, this.intersectionList);
var results = []; var results = [];
for (obj in objs) { for (obj in this.intersectionList) {
var oo = cast(obj, CollisionEntity); var oo = obj;
oo.rayCast(rayStart, rayDirection, results, rayLength); oo.rayCast(rayStart, rayDirection, results, rayLength);
} }
for (obj in dynObjs) {
var oo = cast(obj, CollisionEntity);
oo.rayCast(rayStart, rayDirection, results, rayLength);
}
// results = results.concat(this.staticWorld.rayCast(rayStart, rayDirection));
return results; return results;
} }

View file

@ -75,7 +75,7 @@ class Grid {
} }
// searchbox should be in LOCAL coordinates // searchbox should be in LOCAL coordinates
public function boundingSearch(searchbox:Bounds) { public function boundingSearch(searchbox:Bounds, foundSurfaces:Array<CollisionSurface>) {
var queryMinX = Math.max(searchbox.xMin, bounds.xMin); var queryMinX = Math.max(searchbox.xMin, bounds.xMin);
var queryMinY = Math.max(searchbox.yMin, bounds.yMin); var queryMinY = Math.max(searchbox.yMin, bounds.yMin);
var queryMaxX = Math.min(searchbox.xMax, bounds.xMax); var queryMaxX = Math.min(searchbox.xMax, bounds.xMax);
@ -94,8 +94,6 @@ class Grid {
if (yEnd > CELL_SIZE) if (yEnd > CELL_SIZE)
yEnd = CELL_SIZE; yEnd = CELL_SIZE;
var foundSurfaces = [];
searchKey++; searchKey++;
// Insert the surface references from [xStart, yStart, zStart] to [xEnd, yEnd, zEnd] into the map // Insert the surface references from [xStart, yStart, zStart] to [xEnd, yEnd, zEnd] into the map
@ -113,8 +111,6 @@ class Grid {
} }
} }
} }
return foundSurfaces;
} }
function elegantPair(x:Int, y:Int) { function elegantPair(x:Int, y:Int) {

View file

@ -111,10 +111,10 @@ class GridBroadphase {
var queryMinY = Math.max(object.boundingBox.yMin, bounds.yMin); var queryMinY = Math.max(object.boundingBox.yMin, bounds.yMin);
var queryMaxX = Math.min(object.boundingBox.xMax, bounds.xMax); var queryMaxX = Math.min(object.boundingBox.xMax, bounds.xMax);
var queryMaxY = Math.min(object.boundingBox.yMax, bounds.yMax); var queryMaxY = Math.min(object.boundingBox.yMax, bounds.yMax);
var xStart = Math.floor((queryMinX - bounds.xMin) / this.cellSize.x); var xStart = Util.imax(0, Math.floor((queryMinX - bounds.xMin) / this.cellSize.x));
var yStart = Math.floor((queryMinY - bounds.yMin) / this.cellSize.y); var yStart = Util.imax(0, Math.floor((queryMinY - bounds.yMin) / this.cellSize.y));
var xEnd = Math.floor((queryMaxX - bounds.xMin) / this.cellSize.x); var xEnd = Util.imin(CELL_SIZE - 1, Math.floor((queryMaxX - bounds.xMin) / this.cellSize.x));
var yEnd = Math.floor((queryMaxY - bounds.yMin) / this.cellSize.y); var yEnd = Util.imin(CELL_SIZE - 1, Math.floor((queryMaxY - bounds.yMin) / this.cellSize.y));
var proxy = objectToProxy.get(object); var proxy = objectToProxy.get(object);
if (proxy == null) { if (proxy == null) {
insert(object); insert(object);
@ -221,7 +221,7 @@ class GridBroadphase {
} }
// searchbox should be in LOCAL coordinates // searchbox should be in LOCAL coordinates
public function boundingSearch(searchbox:Bounds) { public function boundingSearch(searchbox:Bounds, foundSurfaces:Array<CollisionEntity>) {
var queryMinX = Math.max(searchbox.xMin, bounds.xMin); var queryMinX = Math.max(searchbox.xMin, bounds.xMin);
var queryMinY = Math.max(searchbox.yMin, bounds.yMin); var queryMinY = Math.max(searchbox.yMin, bounds.yMin);
var queryMaxX = Math.min(searchbox.xMax, bounds.xMax); var queryMaxX = Math.min(searchbox.xMax, bounds.xMax);
@ -240,8 +240,6 @@ class GridBroadphase {
if (yEnd > CELL_SIZE) if (yEnd > CELL_SIZE)
yEnd = CELL_SIZE; yEnd = CELL_SIZE;
var foundSurfaces = [];
searchKey++; searchKey++;
// Insert the surface references from [xStart, yStart, zStart] to [xEnd, yEnd, zEnd] into the map // Insert the surface references from [xStart, yStart, zStart] to [xEnd, yEnd, zEnd] into the map

View file

@ -75,10 +75,9 @@ class SphereCollisionEntity extends CollisionEntity {
return Math.POSITIVE_INFINITY; return Math.POSITIVE_INFINITY;
} }
public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState) { public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState, contacts:Array<CollisionInfo>) {
if (ignore) if (ignore)
return []; return;
var contacts = [];
var thispos = transform.getPosition(); var thispos = transform.getPosition();
var position = collisionEntity.transform.getPosition(); var position = collisionEntity.transform.getPosition();
var velocity = collisionEntity.velocity; var velocity = collisionEntity.velocity;
@ -113,6 +112,5 @@ class SphereCollisionEntity extends CollisionEntity {
// othercontact.penetration = this.radius - (thispos.sub(othercontact.point).dot(othercontact.normal)); // othercontact.penetration = this.radius - (thispos.sub(othercontact.point).dot(othercontact.normal));
// this.marble.queueCollision(othercontact); // this.marble.queueCollision(othercontact);
} }
return contacts;
} }
} }

View file

@ -130,7 +130,19 @@ class ManifestEntry extends FileEntry {
if (onReady != null) if (onReady != null)
onReady(); onReady();
} else { } else {
js.Browser.window.fetch(file).then((res:js.html.Response) -> { js.Browser.window.fetch(file)
.then((res:js.html.Response) -> {
return res.arrayBuffer();
})
.then((buf:js.lib.ArrayBuffer) -> {
loaded = true;
bytes = Bytes.ofData(buf);
if (onReady != null)
onReady();
})
.catchError((e) -> {
// Try the original file path
js.Browser.window.fetch('data/' + originalFile).then((res:js.html.Response) -> {
return res.arrayBuffer(); return res.arrayBuffer();
}).then((buf:js.lib.ArrayBuffer) -> { }).then((buf:js.lib.ArrayBuffer) -> {
loaded = true; loaded = true;
@ -138,6 +150,7 @@ class ManifestEntry extends FileEntry {
if (onReady != null) if (onReady != null)
onReady(); onReady();
}); });
});
} }
#else #else
if (onReady != null) if (onReady != null)

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.htmlUnescape(text); var realText = text;
this.chats.push({ this.chats.push({
text: realText, text: realText,
age: 10.0 age: 10.0

View file

@ -19,7 +19,8 @@ class EndGameGui extends GuiControl {
var scoreSubmitted:Bool = false; var scoreSubmitted:Bool = false;
public function new(continueFunc:GuiControl->Void, restartFunc:GuiControl->Void, nextLevelFunc:GuiControl->Void, mission:Mission, timeState:TimeState) { public function new(continueFunc:GuiControl->Void, restartFunc:GuiControl->Void, nextLevelFunc:GuiControl->Void, mission:Mission, timeState:TimeState,
replayData:haxe.io.Bytes) {
super(); super();
this.horizSizing = Width; this.horizSizing = Width;
this.vertSizing = Height; this.vertSizing = Height;
@ -70,7 +71,7 @@ class EndGameGui extends GuiControl {
nextLevelPreview.extent = new Vector(160, 110); nextLevelPreview.extent = new Vector(160, 110);
nextLevel.addChild(nextLevelPreview); nextLevel.addChild(nextLevelPreview);
mission.getNextMission().getPreviewImage(t -> { mission.getNextMission()?.getPreviewImage(t -> {
nextLevelPreview.bmp.tile = t; nextLevelPreview.bmp.tile = t;
}); });
@ -312,12 +313,11 @@ class EndGameGui extends GuiControl {
scoreData.push({name: "Matan W.", time: 5999.999}); scoreData.push({name: "Matan W.", time: 5999.999});
} }
egFirstLine.text.text = '<p align="left"><font color="#EEC884">1. </font>${scoreData[0].name}</p>'; egFirstLine.text.text = '<p align="left"><font color="#EEC884">1. </font>${StringTools.htmlEscape(scoreData[0].name)}</p>';
egSecondLine.text.text = '<p align="left"><font color="#CDCDCD">2. </font>${scoreData[1].name}</p>'; egSecondLine.text.text = '<p align="left"><font color="#CDCDCD">2. </font>${StringTools.htmlEscape(scoreData[1].name)}</p>';
egThirdLine.text.text = '<p align="left"><font color="#C9AFA0">3. </font>${scoreData[2].name}</p>'; egThirdLine.text.text = '<p align="left"><font color="#C9AFA0">3. </font>${StringTools.htmlEscape(scoreData[2].name)}</p>';
egFourthLine.text.text = '<p align="left"><font color="#A4A4A4">4. </font>${scoreData[3].name}</p>'; egFourthLine.text.text = '<p align="left"><font color="#A4A4A4">4. </font>${StringTools.htmlEscape(scoreData[3].name)}</p>';
egFifthLine.text.text = '<p align="left"><font color="#949494">5. </font>${scoreData[4].name}</p>'; egFifthLine.text.text = '<p align="left"><font color="#949494">5. </font>${StringTools.htmlEscape(scoreData[4].name)}</p>';
var lineelems = [ var lineelems = [
egFirstLineScore, egFirstLineScore,
egSecondLineScore, egSecondLineScore,
@ -393,6 +393,9 @@ class EndGameGui extends GuiControl {
// } // }
Settings.save(); Settings.save();
var rewindUsed = MarbleGame.instance.world.rewindUsed;
var cheatsUsed = MarbleGame.instance.world.cheatsUsed;
if (idx <= 4) { if (idx <= 4) {
setButtonStates(false); setButtonStates(false);
var end = new EnterNameDlg(idx, (name) -> { var end = new EnterNameDlg(idx, (name) -> {
@ -422,41 +425,45 @@ class EndGameGui extends GuiControl {
} }
} }
if (!cheatsUsed) { // dont submit or save if we have cheated
Settings.saveScore(mission.path, myScore); Settings.saveScore(mission.path, myScore);
var lbPath = mission.path; var lbPath = mission.path;
if (mission.isClaMission) if (mission.isClaMission)
lbPath = 'custom/${mission.id}'; lbPath = 'custom/${mission.id}';
var replayData = MarbleGame.instance.world.replay.write(); Leaderboards.submitScore(lbPath, myScore.time, rewindUsed, (sendReplay, rowId) -> {
Leaderboards.submitScore(lbPath, myScore.time, MarbleGame.instance.world.rewindUsed, (sendReplay, rowId) -> {
if (sendReplay && !mission.isClaMission) { if (sendReplay && !mission.isClaMission) {
Leaderboards.submitReplay(rowId, replayData); Leaderboards.submitReplay(rowId, replayData);
} }
}); });
}
scoreSubmitted = true; scoreSubmitted = true;
}); });
this.addChild(end); this.addChild(end);
} else { } else {
// Check if we can submit LB scores // Check if we can submit LB scores
var replayData = MarbleGame.instance.world.replay.write();
var lbPath = mission.path; var lbPath = mission.path;
if (mission.isClaMission) if (mission.isClaMission)
lbPath = 'custom/${mission.id}'; lbPath = 'custom/${mission.id}';
Leaderboards.getScores(lbPath, All, (scores) -> { Leaderboards.getScores(lbPath, All, (scores) -> {
var hasMyScore = false; var hasMyScore = false;
var myTopScoreLB = 0.0;
for (score in scores) { for (score in scores) {
if (score.name == Settings.highscoreName) { if (score.name == Settings.highscoreName) {
hasMyScore = true; hasMyScore = true;
myTopScoreLB = score.score;
break; break;
} }
} }
if (!hasMyScore) { if (!cheatsUsed) {
Leaderboards.submitScore(lbPath, timeState.gameplayClock, MarbleGame.instance.world.rewindUsed, (sendReplay, rowId) -> { if (!hasMyScore || (hasMyScore && myTopScoreLB > timeState.gameplayClock)) {
Leaderboards.submitScore(lbPath, timeState.gameplayClock, rewindUsed, (sendReplay, rowId) -> {
if (sendReplay && !mission.isClaMission) { if (sendReplay && !mission.isClaMission) {
Leaderboards.submitReplay(rowId, replayData); Leaderboards.submitReplay(rowId, replayData);
} }
}); });
} }
}
}); });
} }
} }

View file

@ -10,6 +10,7 @@ import h2d.Tile;
import h2d.Graphics; import h2d.Graphics;
import src.MarbleGame; import src.MarbleGame;
import src.Util; import src.Util;
import haxe.Timer;
class GuiScrollCtrl extends GuiControl { class GuiScrollCtrl extends GuiControl {
public var scrollY:Float = 0; public var scrollY:Float = 0;
@ -40,6 +41,12 @@ class GuiScrollCtrl extends GuiControl {
var dirty:Bool = true; var dirty:Bool = true;
var prevMousePos:Vector; var prevMousePos:Vector;
var scrollVelocity:Float = 0;
var lastMoveStamp:Float = 0;
var momentumActive:Bool = false;
static inline var MOMENTUM_DAMPING:Float = 8;
var _contentYPositions:Map<h2d.Object, Float> = []; var _contentYPositions:Map<h2d.Object, Float> = [];
var deltaY:Float = 0; var deltaY:Float = 0;
@ -244,6 +251,9 @@ class GuiScrollCtrl extends GuiControl {
this.dirty = true; this.dirty = true;
this.updateScrollVisual(); this.updateScrollVisual();
this.prevMousePos = mouseState.position; this.prevMousePos = mouseState.position;
this.scrollVelocity = 0;
this.momentumActive = false;
this.lastMoveStamp = Timer.stamp();
} }
} }
@ -253,6 +263,8 @@ class GuiScrollCtrl extends GuiControl {
this.dirty = true; this.dirty = true;
deltaY = 0; deltaY = 0;
this.updateScrollVisual(); this.updateScrollVisual();
this.momentumActive = Math.abs(scrollVelocity) > 0.01;
this.lastMoveStamp = 0;
} }
} }
@ -260,15 +272,35 @@ class GuiScrollCtrl extends GuiControl {
if (Util.isTouchDevice()) { if (Util.isTouchDevice()) {
super.onMouseMove(mouseState); super.onMouseMove(mouseState);
if (this.pressed) { if (this.pressed) {
var dy = (mouseState.position.y - this.prevMousePos.y) * scrollSpeed / this.maxScrollY; var renderRect = this.getRenderRectangle();
var scrollExtentY = renderRect.extent.y;
var dy = (mouseState.position.y - this.prevMousePos.y) / ((maxScrollY * Settings.uiScale) / scrollExtentY);
deltaY = -dy; deltaY = -dy;
this.scrollY -= dy; this.scrollY -= dy;
this.prevMousePos = mouseState.position; this.prevMousePos = mouseState.position;
var now = Timer.stamp();
if (lastMoveStamp > 0) {
var dt = now - lastMoveStamp;
if (dt > 0)
scrollVelocity = -dy / dt;
}
lastMoveStamp = now;
momentumActive = false;
this.updateScrollVisual(); this.updateScrollVisual();
} }
} }
} }
public override function onMouseLeave(mouseState:MouseState) {
if (Util.isTouchDevice()) {
this.pressed = false;
this.dirty = true;
this.updateScrollVisual();
this.momentumActive = Math.abs(scrollVelocity) > 0.01;
this.lastMoveStamp = 0;
}
}
public override function update(dt:Float, mouseState:MouseState) { public override function update(dt:Float, mouseState:MouseState) {
if (Key.isPressed(Key.MOUSE_WHEEL_DOWN) && Math.abs(mouseState.wheel) >= 1) { if (Key.isPressed(Key.MOUSE_WHEEL_DOWN) && Math.abs(mouseState.wheel) >= 1) {
var renderRect = this.getRenderRectangle(); var renderRect = this.getRenderRectangle();
@ -285,6 +317,21 @@ class GuiScrollCtrl extends GuiControl {
this.updateScrollVisual(); this.updateScrollVisual();
} }
super.update(dt, mouseState); super.update(dt, mouseState);
if (!pressed && momentumActive) {
var damping = Math.exp(-MOMENTUM_DAMPING * dt);
scrollVelocity *= damping;
if (Math.abs(scrollVelocity) < 0.01) {
scrollVelocity = 0;
momentumActive = false;
return;
}
var before = scrollY;
scrollY += scrollVelocity * dt;
updateScrollVisual();
if (scrollY == 0 || scrollY == before)
momentumActive = false;
}
} }
// public override function onMouseDown(mouseState:MouseState) { // public override function onMouseDown(mouseState:MouseState) {

View file

@ -186,7 +186,7 @@ class JoinServerGui extends GuiImage {
serverInfo.text.text = '<p align="center">Select a Server</p><p align="center">or Host your own</p>'; serverInfo.text.text = '<p align="center">Select a Server</p><p align="center">or Host your own</p>';
} else { } else {
var server = ourServerList[curSelection]; var server = ourServerList[curSelection];
serverInfo.text.text = '<p align="center">${server.name}</p><p align="center"><font face="MarkerFelt18" color="#DDDDEE">Hosted by ${server.host}</font></p><p align="left">${server.description}</p>'; serverInfo.text.text = '<p align="center">${StringTools.htmlEscape(server.name)}</p><p align="center"><font face="MarkerFelt18" color="#DDDDEE">Hosted by ${StringTools.htmlEscape(server.host)}</font></p><p align="left">${StringTools.htmlEscape(server.description)}</p>';
} }
} }
serverListContainer.addChild(serverList); serverListContainer.addChild(serverList);
@ -197,7 +197,7 @@ class JoinServerGui extends GuiImage {
function updateServerListDisplay() { function updateServerListDisplay() {
serverDisplays = ourServerList.map(x -> serverDisplays = ourServerList.map(x ->
'<img src="${platformToString[x.platform]}"></img><font color="#FFFFFF">${x.name} <offset value="${400 * Settings.uiScale}">${x.players}/${x.maxPlayers}</offset></font>'); '<img src="${platformToString[x.platform]}"></img><font color="#FFFFFF">${StringTools.htmlEscape(x.name)} <offset value="${400 * Settings.uiScale}">${x.players}/${x.maxPlayers}</offset></font>');
serverList.setTexts(serverDisplays); serverList.setTexts(serverDisplays);
} }

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 = "";
@ -585,11 +585,11 @@ class MPPlayMissionGui extends GuiImage {
currentSelection = -1; currentSelection = -1;
} }
pmDesc.text.text = '<font face="MarkerFelt32" color="#E3F3FF"><p align="center">#${currentSelection + 1}: ${currentMission.title}</p></font>' pmDesc.text.text = '<font face="MarkerFelt32" color="#E3F3FF"><p align="center">#${currentSelection + 1}: ${StringTools.htmlEscape(currentMission.title)}</p></font>'
+ '<font face="MarkerFelt18" color="#CEE0F4">${currentMission.description}</font>'; + '<font face="MarkerFelt18" color="#CEE0F4">${StringTools.htmlEscape(currentMission.description)}</font>';
parTime.text.text = '<font face="MarkerFelt24" color="#E3F3FF">Duration: <font color="#FFFFFF">${Util.formatTime(currentMission.qualifyTime)}</font></font><br/>' parTime.text.text = '<font face="MarkerFelt24" color="#E3F3FF">Duration: <font color="#FFFFFF">${Util.formatTime(currentMission.qualifyTime)}</font></font><br/>'
+ '<font face="MarkerFelt24" color="#E3F3FF">Author: <font color="#FFFFFF">${currentMission.artist}</font></font>'; + '<font face="MarkerFelt24" color="#E3F3FF">Author: <font color="#FFFFFF">${StringTools.htmlEscape(currentMission.artist)}</font></font>';
// pmPreview.bmp.tile = tmpprevtile; // pmPreview.bmp.tile = tmpprevtile;
#if js #if js
@ -718,7 +718,7 @@ class MPPlayMissionGui extends GuiImage {
} }
var playerListCompiled = playerListArr.map(player -> var playerListCompiled = playerListArr.map(player ->
'<img src="${platformToString(player.platform)}"></img><font color="#FFFFFF">${player.name}<offset value="${220 * Settings.uiScale}">${player.ready ? "Ready" : ""}</offset></font>'); '<img src="${platformToString(player.platform)}"></img><font color="#FFFFFF">${StringTools.htmlEscape(player.name)}<offset value="${220 * Settings.uiScale}">${player.ready ? "Ready" : ""}</offset></font>');
playerListCtrl.setTexts(playerListCompiled); playerListCtrl.setTexts(playerListCompiled);
// if (!showingCustoms) // if (!showingCustoms)
@ -728,7 +728,7 @@ class MPPlayMissionGui extends GuiImage {
} }
public static function addChatMessage(s:String) { public static function addChatMessage(s:String) {
var realText = StringTools.htmlUnescape(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

@ -47,6 +47,13 @@ class MainMenuGui extends GuiImage {
return [normal, hover, pressed]; return [normal, hover, pressed];
} }
function loadStaticButtonImages(path:String) {
var normal = ResourceLoader.getResource('${path}.png', ResourceLoader.getImage, this.imageResources).toTile();
var hover = ResourceLoader.getResource('${path}.png', ResourceLoader.getImage, this.imageResources).toTile();
var pressed = ResourceLoader.getResource('${path}.png', ResourceLoader.getImage, this.imageResources).toTile();
return [normal, hover, pressed];
}
var siteButton = new GuiButton(loadButtonImages('data/ui/menu/site')); var siteButton = new GuiButton(loadButtonImages('data/ui/menu/site'));
siteButton.horizSizing = Right; siteButton.horizSizing = Right;
siteButton.vertSizing = Top; siteButton.vertSizing = Top;
@ -232,6 +239,38 @@ class MainMenuGui extends GuiImage {
} }
this.addChild(github); this.addChild(github);
#if js
var mbg = new GuiButton(loadStaticButtonImages("data/ui/icon_mbg"));
mbg.horizSizing = Right;
mbg.vertSizing = Top;
mbg.position = new Vector(0, 380);
mbg.extent = new Vector(76, 76);
mbg.pressedAction = (sender) -> {
js.Browser.window.open("https://marbleblastgold.randomityguy.me");
}
this.addChild(mbg);
var mbu = new GuiButton(loadStaticButtonImages("data/ui/icon_mbu"));
mbu.horizSizing = Right;
mbu.vertSizing = Top;
mbu.position = new Vector(76, 380);
mbu.extent = new Vector(76, 76);
mbu.pressedAction = (sender) -> {
js.Browser.window.open("https://marbleblastultra.randomityguy.me");
}
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
#if js #if js
var urlParams = new js.html.URLSearchParams(js.Browser.window.location.search); var urlParams = new js.html.URLSearchParams(js.Browser.window.location.search);
var playParam = urlParams.get("app"); var playParam = urlParams.get("app");

View file

@ -1,5 +1,6 @@
package gui; package gui;
import haxe.DynamicAccess;
import hxd.BitmapData; import hxd.BitmapData;
import h2d.filter.DropShadow; import h2d.filter.DropShadow;
import h2d.Text; import h2d.Text;
@ -70,10 +71,10 @@ class OptionsDlg extends GuiImage {
hotkeysBtn.extent = new Vector(134, 65); hotkeysBtn.extent = new Vector(134, 65);
window.addChild(hotkeysBtn); window.addChild(hotkeysBtn);
var onlineBtn = new GuiImage(ResourceLoader.getResource("data/ui/options/online_i.png", ResourceLoader.getImage, this.imageResources).toTile()); var miscBtn = new GuiButton(loadButtonImages('data/ui/options/misc'));
onlineBtn.position = new Vector(548, 19); miscBtn.position = new Vector(548, 19);
onlineBtn.extent = new Vector(134, 65); miscBtn.extent = new Vector(134, 65);
window.addChild(onlineBtn); window.addChild(miscBtn);
var generalPanel:GuiScrollCtrl = null; var generalPanel:GuiScrollCtrl = null;
@ -114,6 +115,10 @@ class OptionsDlg extends GuiImage {
hotkeysPanel.position = new Vector(30, 88); hotkeysPanel.position = new Vector(30, 88);
hotkeysPanel.extent = new Vector(726, 394); hotkeysPanel.extent = new Vector(726, 394);
var miscPanel = new GuiControl();
miscPanel.position = new Vector(30, 88);
miscPanel.extent = new Vector(726, 394);
var markerFelt32fontdata = ResourceLoader.getFileEntry("data/font/MarkerFelt.fnt"); var markerFelt32fontdata = ResourceLoader.getFileEntry("data/font/MarkerFelt.fnt");
var markerFelt32b = new BitmapFont(markerFelt32fontdata.entry); var markerFelt32b = new BitmapFont(markerFelt32fontdata.entry);
@:privateAccess markerFelt32b.loader = ResourceLoader.loader; @:privateAccess markerFelt32b.loader = ResourceLoader.loader;
@ -470,6 +475,31 @@ class OptionsDlg extends GuiImage {
parent.addChild(remapBtn); parent.addChild(remapBtn);
} }
function makeButton(text:String, yPos:Int, buttonText:String, pressedAction:() -> Void, parent:GuiControl, right:Bool = false) {
var textObj = new GuiText(markerFelt32);
textObj.position = new Vector(right ? 368 : 5, yPos);
textObj.extent = new Vector(212, 14);
textObj.text.text = text;
textObj.text.textColor = 0xFFFFFF;
textObj.text.dropShadow = {
dx: 1 * Settings.uiScale,
dy: 1 * Settings.uiScale,
alpha: 0.5,
color: 0
};
parent.addChild(textObj);
var btn = new GuiButtonText(loadButtonImages("data/ui/options/bind"), markerFelt24);
btn.position = new Vector(right ? 363 + 203 : 203, yPos - 3);
btn.txtCtrl.text.text = buttonText;
btn.setExtent(new Vector(152, 49));
btn.pressedAction = (sender) -> {
pressedAction();
}
parent.addChild(btn);
}
if (Util.isTouchDevice()) { if (Util.isTouchDevice()) {
var textObj = new GuiText(markerFelt32); var textObj = new GuiText(markerFelt32);
textObj.position = new Vector(5, 38); textObj.position = new Vector(5, 38);
@ -540,10 +570,85 @@ class OptionsDlg extends GuiImage {
hotkeysPanel, true); hotkeysPanel, true);
} }
// MISC PANEL
makeButton("Import Progress:", 38, "Import", () -> {
hxd.File.browse((sel) -> {
sel.load((data) -> {
try {
// convert to string
var jsonStr = data.toString();
// parse JSON
var json = haxe.Json.parse(jsonStr);
var highScoreData:DynamicAccess<Array<Score>> = json.highScores;
for (key => value in highScoreData) {
Settings.highScores.set(key, value);
}
var easterEggData:DynamicAccess<Float> = json.easterEggs;
if (easterEggData != null) {
for (key => value in easterEggData) {
Settings.easterEggs.set(key, value);
}
}
MarbleGame.canvas.pushDialog(new MessageBoxOkDlg("Progress data imported successfully!"));
Settings.save();
} catch (e) {
MarbleGame.canvas.pushDialog(new MessageBoxOkDlg("Failed to import progress data: " + e.message));
}
});
}, {
title: "Select a progress file to import",
fileTypes: [
{name: "JSON files", extensions: ["json"]},
{name: "All files", extensions: ["*"]}
],
});
}, miscPanel);
makeButton("Export Progress:", 38, "Export", () -> {
#if sys
#if MACOS_BUNDLE
// open the finder to that folder
Sys.command('open "${Settings.settingsDir}"');
#else
// Just open the folder in the explorer.exe
Sys.command('explorer.exe "${Settings.settingsDir}"');
#end
MarbleGame.canvas.pushDialog(new MessageBoxOkDlg("The settings.json file contains your progress data. You can copy it to another device or share it with others."));
#end
#if js
// Serialize Settings to JSON
var localStorage = js.Browser.getLocalStorage();
if (localStorage != null) {
var settingsData = localStorage.getItem("MBHaxeSettings");
if (settingsData != null) {
// Download this
var replayBytes = settingsData;
var blob = new js.html.Blob([haxe.io.Bytes.ofString(replayBytes).getData()], {
type: 'application/octet-stream'
});
var url = js.html.URL.createObjectURL(blob);
var fname = 'settings.json';
var element = js.Browser.document.createElement('a');
element.setAttribute('href', url);
element.setAttribute('download', fname);
element.style.display = 'none';
js.Browser.document.body.appendChild(element);
element.click();
js.Browser.document.body.removeChild(element);
js.html.URL.revokeObjectURL(url);
}
}
#end
}, miscPanel, true);
generalBtn.pressedAction = (e) -> { generalBtn.pressedAction = (e) -> {
if (currentTab != "general") { if (currentTab != "general") {
currentTab = "general"; currentTab = "general";
hotkeysPanel.parent.removeChild(hotkeysPanel); hotkeysPanel.parent?.removeChild(hotkeysPanel);
miscPanel.parent?.removeChild(miscPanel);
generalPanel.scrollY = 0; generalPanel.scrollY = 0;
window.addChild(generalPanel); window.addChild(generalPanel);
MarbleGame.canvas.render(MarbleGame.canvas.scene2d); // Force refresh MarbleGame.canvas.render(MarbleGame.canvas.scene2d); // Force refresh
@ -553,12 +658,23 @@ class OptionsDlg extends GuiImage {
hotkeysBtn.pressedAction = (e) -> { hotkeysBtn.pressedAction = (e) -> {
if (currentTab != "hotkeys") { if (currentTab != "hotkeys") {
currentTab = "hotkeys"; currentTab = "hotkeys";
generalPanel.parent.removeChild(generalPanel); generalPanel.parent?.removeChild(generalPanel);
miscPanel.parent?.removeChild(miscPanel);
window.addChild(hotkeysPanel); window.addChild(hotkeysPanel);
MarbleGame.canvas.render(MarbleGame.canvas.scene2d); // Force refresh MarbleGame.canvas.render(MarbleGame.canvas.scene2d); // Force refresh
} }
}; };
miscBtn.pressedAction = (e) -> {
if (currentTab != "misc") {
currentTab = "misc";
generalPanel.parent?.removeChild(generalPanel);
hotkeysPanel.parent?.removeChild(hotkeysPanel);
window.addChild(miscPanel);
MarbleGame.canvas.render(MarbleGame.canvas.scene2d); // Force refresh
}
};
// // Touch Controls buttons??? // // Touch Controls buttons???
// if (Util.isTouchDevice()) { // if (Util.isTouchDevice()) {
// var touchControlsTxt = new GuiText(domcasual24); // var touchControlsTxt = new GuiText(domcasual24);

View file

@ -566,17 +566,17 @@ class PlayGui {
var fpsMeterCtrl = new GuiImage(ResourceLoader.getResource("data/ui/game/transparency-fps.png", ResourceLoader.getImage, this.imageResources) var fpsMeterCtrl = new GuiImage(ResourceLoader.getResource("data/ui/game/transparency-fps.png", ResourceLoader.getImage, this.imageResources)
.toTile()); .toTile());
fpsMeterCtrl.position = new Vector(544, 448); fpsMeterCtrl.position = new Vector(534, 448);
fpsMeterCtrl.horizSizing = Left; fpsMeterCtrl.horizSizing = Left;
fpsMeterCtrl.vertSizing = Top; fpsMeterCtrl.vertSizing = Top;
fpsMeterCtrl.extent = new Vector(96, 32); fpsMeterCtrl.extent = new Vector(106, 32);
fpsMeter = new GuiText(bfont); fpsMeter = new GuiText(bfont);
fpsMeter.horizSizing = Width; fpsMeter.horizSizing = Width;
fpsMeter.vertSizing = Height; fpsMeter.vertSizing = Height;
fpsMeter.position = new Vector(10, 3); fpsMeter.position = new Vector(10, 3);
fpsMeter.text.textColor = 0; fpsMeter.text.textColor = 0;
fpsMeter.extent = new Vector(96, 32); fpsMeter.extent = new Vector(106, 32);
fpsMeterCtrl.addChild(fpsMeter); fpsMeterCtrl.addChild(fpsMeter);
playGuiCtrl.addChild(fpsMeterCtrl); playGuiCtrl.addChild(fpsMeterCtrl);
@ -738,7 +738,7 @@ class PlayGui {
} else { } else {
isSpectating = Net.clientIdMap[item.id].spectator; isSpectating = Net.clientIdMap[item.id].spectator;
} }
pl.push('<font color="${color}">${i + 1}. ${isSpectating ? "[S] " : ""}${Util.rightPad(item.name, 25, 3)}</font>'); pl.push('<font color="${color}">${i + 1}. ${isSpectating ? "[S] " : ""}${Util.rightPad(StringTools.htmlEscape(item.name), 25, 3)}</font>');
var connPing = item.us ? (Net.isHost ? 0 : Net.clientConnection.pingTicks) : (item.id == 0 ? 0 : Net.clientIdMap[item.id].pingTicks); var connPing = item.us ? (Net.isHost ? 0 : Net.clientConnection.pingTicks) : (item.id == 0 ? 0 : Net.clientIdMap[item.id].pingTicks);
var pingStatus = "unknown"; var pingStatus = "unknown";
if (connPing <= 5) if (connPing <= 5)
@ -1177,7 +1177,7 @@ class PlayGui {
this.powerupImageScene.setElapsedTime(timeState.dt); this.powerupImageScene.setElapsedTime(timeState.dt);
if (this.fpsMeter != null) { if (this.fpsMeter != null) {
this.fpsMeter.text.text = '${Math.floor(ProfilerUI.instance.fps)} fps'; this.fpsMeter.text.text = '${Math.floor(ProfilerUI.instance.fps)} FPS';
} }
this.updateMiddleMessages(timeState.dt); this.updateMiddleMessages(timeState.dt);
if (Net.isMP) { if (Net.isMP) {

View file

@ -1234,7 +1234,7 @@ class PlayMissionGui extends GuiImage {
var i = 1; var i = 1;
for (score in scoreList) { for (score in scoreList) {
sFmt.push('${i}. sFmt.push('${i}.
<offset value="15">${score.name.substr(0, 30)}</offset> <offset value="15">${StringTools.htmlEscape(score.name.substr(0, 30))}</offset>
<offset value="215">${Util.formatTime(score.score)}</offset> <offset value="215">${Util.formatTime(score.score)}</offset>
<offset value="279"><img src="${platformToString(score.platform)}"/></offset> <offset value="279"><img src="${platformToString(score.platform)}"/></offset>
${score.rewind == 1 ? '<offset value="299"><img src="rewind"/></offset> ' : ""}'); ${score.rewind == 1 ? '<offset value="299"><img src="rewind"/></offset> ' : ""}');

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

View file

@ -16,7 +16,7 @@ class InputBitStream {
this.shift = 0; this.shift = 0;
} }
function readBits(bits:Int = 8) { inline function readBits(bits:Int = 8) {
if (this.shift + bits >= 8) { if (this.shift + bits >= 8) {
var extra = (this.shift + bits) % 8; var extra = (this.shift + bits) % 8;
var remain = bits - extra; var remain = bits - extra;
@ -37,7 +37,7 @@ class InputBitStream {
} }
} }
public function readInt(bits:Int = 32) { public inline function readInt(bits:Int = 32) {
var value = 0; var value = 0;
var shift = 0; var shift = 0;
while (bits > 0) { while (bits > 0) {
@ -48,39 +48,40 @@ class InputBitStream {
return value; return value;
} }
public function readFlag() { public inline function readFlag() {
return readInt(1) != 0; return readInt(1) != 0;
} }
public function readByte() { public inline function readByte() {
return readInt(8); return readInt(8);
} }
public function readUInt16() { public inline function readUInt16() {
return readInt(16); return readInt(16);
} }
public function readInt32() { public inline function readInt32() {
return readInt(32); return readInt(32);
} }
public function readFloat() { public inline function readFloat() {
return FPHelper.i32ToFloat(readInt32()); return FPHelper.i32ToFloat(readInt32());
} }
public function readDouble() { public inline function readDouble() {
var lo = readInt32(); var lo = readInt32();
var hi = readInt32(); var hi = readInt32();
return FPHelper.i64ToDouble(lo, hi); return FPHelper.i64ToDouble(lo, hi);
} }
public function readString() { public inline function readString() {
var length = readUInt16(); var length = readUInt16();
var str = ""; var str = "";
var buf = new StringBuf();
for (i in 0...length) { for (i in 0...length) {
str += String.fromCharCode(readByte()); buf.addChar(readByte());
} }
return str; return buf.toString();
} }
} }
@ -99,7 +100,7 @@ class OutputBitStream {
this.lastByte = 0; this.lastByte = 0;
} }
function writeBits(value:Int, bits:Int) { inline function writeBits(value:Int, bits:Int) {
value = value & (0xFF >> (8 - bits)); value = value & (0xFF >> (8 - bits));
if (this.shift + bits >= 8) { if (this.shift + bits >= 8) {
var extra = (shift + bits) % 8; var extra = (shift + bits) % 8;
@ -118,7 +119,7 @@ class OutputBitStream {
} }
} }
public function writeInt(value:Int, bits:Int = 32) { public inline function writeInt(value:Int, bits:Int = 32) {
while (bits > 0) { while (bits > 0) {
this.writeBits(value & 0xFF, bits < 8 ? bits : 8); this.writeBits(value & 0xFF, bits < 8 ? bits : 8);
value >>= 8; value >>= 8;
@ -126,39 +127,39 @@ class OutputBitStream {
} }
} }
public function writeFlag(value:Bool) { public inline function writeFlag(value:Bool) {
writeInt(value ? 1 : 0, 1); writeInt(value ? 1 : 0, 1);
} }
public function writeByte(value:Int) { public inline function writeByte(value:Int) {
writeInt(value, 8); writeInt(value, 8);
} }
public function writeUInt16(value:Int) { public inline function writeUInt16(value:Int) {
writeInt(value, 16); writeInt(value, 16);
} }
public function writeInt32(value:Int) { public inline function writeInt32(value:Int) {
writeInt(value, 32); writeInt(value, 32);
} }
public function getBytes() { public inline function getBytes() {
this.data.writeByte(this.lastByte); this.data.writeByte(this.lastByte);
return this.data.getBytes(); return this.data.getBytes();
} }
public function writeFloat(value:Float) { public inline function writeFloat(value:Float) {
writeInt(FPHelper.floatToI32(value), 32); writeInt(FPHelper.floatToI32(value), 32);
} }
public function writeString(value:String) { public inline function writeString(value:String) {
writeUInt16(value.length); writeUInt16(value.length);
for (i in 0...value.length) { for (i in 0...value.length) {
writeByte(StringTools.fastCodeAt(value, i)); writeByte(StringTools.fastCodeAt(value, i));
} }
} }
public function writeDouble(value:Float) { public inline function writeDouble(value:Float) {
var i64 = FPHelper.doubleToI64(value); var i64 = FPHelper.doubleToI64(value);
writeInt32(i64.low); writeInt32(i64.low);
writeInt32(i64.high); writeInt32(i64.high);

View file

@ -46,41 +46,61 @@ class MarblePredictionStore {
} }
public function storeState(marble:Marble, tick:Int) { public function storeState(marble:Marble, tick:Int) {
var state = new MarblePrediction(marble, tick); var arr = ensureHistory(marble);
if (predictions.exists(marble)) { truncateFromTick(arr, tick);
var arr = predictions[marble]; arr.push(new MarblePrediction(marble, tick));
while (arr.length != 0 && arr[0].tick >= tick)
arr.shift();
arr.push(state);
} else {
predictions.set(marble, [state]);
}
} }
public function retrieveState(marble:Marble, tick:Int) { public function retrieveState(marble:Marble, tick:Int) {
if (predictions.exists(marble)) { var arr = predictions.get(marble);
var arr = predictions[marble]; if (arr == null)
while (arr.length != 0 && arr[0].tick < tick)
arr.shift();
if (arr.length == 0)
return null;
var p = arr[0];
if (p.tick == tick)
return p;
return null;
}
return null; return null;
dropBeforeTick(arr, tick);
return (arr.length != 0 && arr[0].tick == tick) ? arr[0] : null;
} }
public function clearStatesAfterTick(marble:Marble, tick:Int) { public function clearStatesAfterTick(marble:Marble, tick:Int) {
if (predictions.exists(marble)) { var arr = predictions.get(marble);
var arr = predictions[marble]; if (arr != null)
while (arr.length != 0 && arr[arr.length - 1].tick >= tick) truncateFromTick(arr, tick);
arr.pop();
}
} }
public function removeMarbleFromPrediction(marble:Marble) { public function removeMarbleFromPrediction(marble:Marble) {
this.predictions.remove(marble); this.predictions.remove(marble);
} }
inline function ensureHistory(marble:Marble) {
var arr = predictions.get(marble);
if (arr == null) {
arr = [];
predictions.set(marble, arr);
}
return arr;
}
inline function dropBeforeTick(arr:Array<MarblePrediction>, tick:Int) {
var idx = lowerBound(arr, tick);
if (idx > 0)
arr.splice(0, idx);
}
inline function truncateFromTick(arr:Array<MarblePrediction>, tick:Int) {
var idx = lowerBound(arr, tick);
if (idx < arr.length)
arr.splice(idx, arr.length - idx);
}
static inline function lowerBound(arr:Array<MarblePrediction>, tick:Int) {
var lo = 0;
var hi = arr.length;
while (lo < hi) {
var mid = (lo + hi) >> 1;
if (arr[mid].tick < tick)
lo = mid + 1;
else
hi = mid;
}
return lo;
}
} }

View file

@ -25,7 +25,7 @@ class MasterServerClient {
#if js #if js
static var serverIp = "wss://mbpmaster.randomityguy.me:8443"; static var serverIp = "wss://mbpmaster.randomityguy.me:8443";
#else #else
static var serverIp = "ws://89.58.58.191:8084"; static var serverIp = "ws://51.75.65.148:8084";
#end #end
public static var instance:MasterServerClient; public static var instance:MasterServerClient;
@ -102,7 +102,6 @@ class MasterServerClient {
instance = null; instance = null;
} }
#if hl #if hl
stopMutex.acquire();
stopping = true; stopping = true;
stopMutex.release(); stopMutex.release();
if (myToken == wsToken) { if (myToken == wsToken) {
@ -195,6 +194,14 @@ class MasterServerClient {
} }
} }
public static function requestTurnCredentials() {
if (instance != null && instance.open) {
instance.queueMessage(Json.stringify({
type: "turn_credentials"
}));
}
}
function queueMessage(m:String) { function queueMessage(m:String) {
#if hl #if hl
toSend.add(m); toSend.add(m);
@ -307,8 +314,11 @@ class MasterServerClient {
} }
} }
} }
if (conts.type == "turnserver") { if (conts.type == "turn_credentials") {
Net.turnServer = conts.server; // Turn server! Net.turnServers = conts.turn_servers;
if (@:privateAccess Net.onTurnServersReceived != null) {
@:privateAccess Net.onTurnServersReceived();
}
} }
} }
} }

View file

@ -147,7 +147,7 @@ class MoveManager {
return netMove; return netMove;
} }
function copyMove(to:Int, from:Int) { inline function copyMove(to:Int, from:Int) {
queuedMoves[to].move = queuedMoves[from].move; queuedMoves[to].move = queuedMoves[from].move;
queuedMoves[to].motionDir.load(queuedMoves[from].motionDir); queuedMoves[to].motionDir.load(queuedMoves[from].motionDir);
} }

View file

@ -106,7 +106,9 @@ class Net {
static var stunServers = ["stun:stun.l.google.com:19302"]; static var stunServers = ["stun:stun.l.google.com:19302"];
public static var turnServer:String = ""; public static var turnServers:Array<String> = [];
static var onTurnServersReceived:Null<() -> Void> = null;
public static function hostServer(name:String, description:String, maxPlayers:Int, password:String, onHosted:() -> Void) { public static function hostServer(name:String, description:String, maxPlayers:Int, password:String, onHosted:() -> Void) {
serverInfo = new ServerInfo(name, Settings.highscoreName, description, 1, maxPlayers, password, "LOBBY", getPlatform()); serverInfo = new ServerInfo(name, Settings.highscoreName, description, 1, maxPlayers, password, "LOBBY", getPlatform());
@ -128,8 +130,16 @@ class Net {
}); });
} }
public static function addClientFromSdp(sdpString:String, onFinishSdp:String->Void) { public static function addClientFromSdp(sdpString:String, onFinishSdp:String->Void, turnTried:Bool = false) {
var peer = new RTCPeerConnection(stunServers, "0.0.0.0"); if (Net.turnServers.length == 0 && !turnTried) {
MasterServerClient.requestTurnCredentials();
Net.onTurnServersReceived = () -> {
Net.onTurnServersReceived = null;
addClientFromSdp(sdpString, onFinishSdp, true);
};
return;
}
var peer = new RTCPeerConnection(stunServers.concat(Net.turnServers), "0.0.0.0");
var sdpObj = Json.parse(sdpString); var sdpObj = Json.parse(sdpString);
peer.setRemoteDescription(sdpObj.sdp, sdpObj.type); peer.setRemoteDescription(sdpObj.sdp, sdpObj.type);
addClient(peer, onFinishSdp); addClient(peer, onFinishSdp);
@ -212,9 +222,18 @@ class Net {
clientIdMap[id] = ghost; clientIdMap[id] = ghost;
} }
public static function joinServer(serverName:String, password:String, connectedCb:() -> Void) { public static function joinServer(serverName:String, password:String, connectedCb:() -> Void, turnTried:Bool = false) {
MasterServerClient.connectToMasterServer(() -> { MasterServerClient.connectToMasterServer(() -> {
client = new RTCPeerConnection(stunServers, "0.0.0.0"); if (Net.turnServers.length == 0 && !turnTried) {
MasterServerClient.requestTurnCredentials();
Net.onTurnServersReceived = () -> {
Net.onTurnServersReceived = null;
joinServer(serverName, password, connectedCb, true);
};
return;
}
client = new RTCPeerConnection(stunServers.concat(Net.turnServers), "0.0.0.0");
var candidates = []; var candidates = [];
var closing = false; var closing = false;
@ -562,7 +581,6 @@ class Net {
serverInfo.players++; serverInfo.players++;
} }
serverInfo.players++;
MasterServerClient.instance.sendServerInfo(serverInfo); // notify the server of the new player MasterServerClient.instance.sendServerInfo(serverInfo); // notify the server of the new player
if (MarbleGame.canvas.content is MPPlayMissionGui) { if (MarbleGame.canvas.content is MPPlayMissionGui) {

View file

@ -154,13 +154,13 @@ class RewindManager {
|| level.marble.currentUp.z != rf.currentUp.z) { || level.marble.currentUp.z != rf.currentUp.z) {
level.setUp(level.marble, rf.currentUp, level.timeState); level.setUp(level.marble, rf.currentUp, level.timeState);
// Hacky things // Hacky things
@:privateAccess level.orientationChangeTime = level.timeState.currentAttemptTime - 300; @:privateAccess level.orientationChangeTime = level.timeState.currentAttemptTime - 0.3;
var oldorient = level.newOrientationQuat; var oldorient = level.newOrientationQuat;
level.newOrientationQuat = @:privateAccess level.oldOrientationQuat; level.newOrientationQuat = @:privateAccess level.oldOrientationQuat;
@:privateAccess level.oldOrientationQuat = oldorient; @:privateAccess level.oldOrientationQuat = oldorient;
} }
var gravitycompletion = Util.clamp((level.timeState.currentAttemptTime - @:privateAccess level.orientationChangeTime) / 300, 0, 1); var gravitycompletion = Util.clamp((level.timeState.currentAttemptTime - @:privateAccess level.orientationChangeTime) / 0.3, 0, 1);
if (gravitycompletion == 0) { if (gravitycompletion == 0) {
level.newOrientationQuat = @:privateAccess level.oldOrientationQuat; level.newOrientationQuat = @:privateAccess level.oldOrientationQuat;
@:privateAccess level.orientationChangeTime = -1e8; @:privateAccess level.orientationChangeTime = -1e8;

View file

@ -14,6 +14,7 @@ class CameraInput {
var identifier:Int = -1; var identifier:Int = -1;
public var enabled = false; public var enabled = false;
public var pressed = false;
var added = false; var added = false;
@ -36,8 +37,6 @@ class CameraInput {
this.collider.horizSizing = Width; this.collider.horizSizing = Width;
this.collider.vertSizing = Height; this.collider.vertSizing = Height;
var pressed = false;
var prevMouse = new Vector(0, 0); var prevMouse = new Vector(0, 0);
interactive.onPush = (e) -> { interactive.onPush = (e) -> {
e.propagate = true; e.propagate = true;
@ -87,14 +86,24 @@ class CameraInput {
if (jumpcam) { if (jumpcam) {
scaleFactor /= Settings.touchSettings.buttonJoystickMultiplier; scaleFactor /= Settings.touchSettings.buttonJoystickMultiplier;
} }
if (Math.abs(delta.x) < 0.05) var inpX = delta.x / scaleFactor;
delta.x = 0; var inpY = delta.y / scaleFactor;
if (Math.abs(delta.y) < 0.05)
delta.y = 0; if (jumpcam) {
MarbleGame.instance.world.marble.camera.orbit(applyNonlinearScale(delta.x / scaleFactor), applyNonlinearScale(delta.y / scaleFactor), true); if (Math.abs(inpX) < 1.3)
if (delta.x != 0) inpX = 0;
if (Math.abs(inpY) < 1.3)
inpY = 0;
}
var dt = MarbleGame.instance.world.timeState.dt;
MarbleGame.instance.world.marble.camera.orbit(applyNonlinearScale((inpX / dt) * (1 / 60.0)) * (1 / 60.0) * 35,
applyNonlinearScale((inpY / dt) * (1 / 60.0)) * (1 / 60.0) * 35, true);
if (inpX != 0)
prevMouse.x = e.relX; prevMouse.x = e.relX;
if (delta.y != 0) if (inpY != 0)
prevMouse.y = e.relY; prevMouse.y = e.relY;
} }
} }

View file

@ -13,7 +13,6 @@ class PauseButton extends TouchButton {
this.onClick = () -> { this.onClick = () -> {
if (MarbleGame.instance.world != null && @:privateAccess !MarbleGame.instance.paused) { if (MarbleGame.instance.world != null && @:privateAccess !MarbleGame.instance.paused) {
@:privateAccess MarbleGame.instance.paused = true;
MarbleGame.instance.handlePauseGame(); MarbleGame.instance.handlePauseGame();
} }
} }

View file

@ -36,7 +36,7 @@ class TouchEventState {
} }
class TouchInput { class TouchInput {
var cameraInput:CameraInput; public var cameraInput:CameraInput;
public var movementInput:MovementInput; public var movementInput:MovementInput;