Compare commits

...

20 commits

Author SHA1 Message Date
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
20 changed files with 218 additions and 101 deletions

View file

@ -462,10 +462,14 @@ workflows:
filters:
tags:
only: /^\d+.\d+.\d+$/
branches:
ignore: /.*/
build-windows:
jobs:
- build-win:
filters:
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,26 @@
# 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
This update fixes the following bugs:
- Fixed a crash when there are more players than spawnpoints in multiplayer.

View file

@ -2,7 +2,8 @@
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).
[![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
## Web Browser
The browser port supports touch controls, meaning it can be played on mobile devices.
@ -11,15 +12,20 @@ The browser port supports touch controls, meaning it can be played on mobile dev
### Marble Blast Ultra: [Play](https://marbleblastultra.randomityguy.me/)
## Windows and Mac
### Marble Blast Gold: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.1.12)
### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.6.1)
### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.1.3-mbu)
### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.7.1)
### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/tag/1.2.5-mbu)
## Mac Instructions - Important
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.
## Android
### Marble Blast Gold: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.1.12/MBHaxe-Gold.apk)
### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.6.1/MBHaxe-Platinum.apk)
### Marble Blast Ultra: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.1.3-mbu/MBHaxe-Ultra.apk)
### Marble Blast Platinum: [Download](https://github.com/RandomityGuy/MBHaxe/releases/download/1.7.1/MBHaxe-Platinum.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
- 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:
- 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)
- datachannel: obtained from [here](https://github.com/RandomityGuy/hxDatachannel)
- stb_ogg_sound (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 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?
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.
@ -100,11 +104,11 @@ In native version, you can just resize the window if windowed or use the resolut
## How do I change my FOV?
Edit settings.json for native version, edit the MBHaxeSettings key in LocalStorage in browser.
In the platinum version, there is an FOV slider.
In the Platinum and Ultra versions, there is an FOV slider.
## How do I unlock/lock FPS?
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, or edit settings.json and set "vsync" to false to unlock fps.
## 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.

View file

@ -49,7 +49,7 @@ class Leaderboards {
public static function getScores(mission:String, kind:LeaderboardsKind, cb:Array<LBScore>->Void) {
if (!StringTools.startsWith(mission, "data/"))
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 scores:Array<LBScore> = Json.parse(s).scores;
cb(scores);

View file

@ -137,7 +137,10 @@ class Main extends hxd.App {
// });
}
static var updateDT:Float;
override function update(dt:Float) {
updateDT = dt;
super.update(dt);
if (loaded) {
ProfilerUI.begin();
@ -168,6 +171,19 @@ class Main extends hxd.App {
ProfilerUI.end();
}
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() {

View file

@ -394,6 +394,7 @@ class Marble extends GameObject {
this.netSmoothOffset = new Vector();
this.netCorrected = false;
this.currentUp = new Vector(0, 0, 1);
this.lastContactNormal = new Vector(0, 0, 1);
var marbleDts = new DtsObject();
var marbleShader = "";
@ -817,7 +818,7 @@ class Marble extends GameObject {
function computeMoveForces(m:Move, aControl:Vector, desiredOmega:Vector) {
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 axes = this.getMarbleAxis();
// if (!level.isReplayingMovement)
@ -850,15 +851,15 @@ class Marble extends GameObject {
}
var rsq = R.lengthSq();
var crossP = R.cross(motionDir.multiply(desiredYVelocity).add(sideDir.multiply(desiredXVelocity))).multiply(1 / rsq);
desiredOmega.set(crossP.x, crossP.y, crossP.z);
aControl.set(desiredOmega.x - this.omega.x, desiredOmega.y - this.omega.y, desiredOmega.z - this.omega.z);
desiredOmega.load(crossP);
aControl.load(desiredOmega.sub(this.omega));
var aScalar = aControl.length();
if (aScalar > this._angularAcceleration) {
aControl.scale(this._angularAcceleration / aScalar);
}
return false;
}
return return true;
return true;
}
function velocityCancel(timeState:TimeState, surfaceSlide:Bool, noBounce:Bool, stoppedPaths:Bool, pi:Array<PathedInterior>) {
@ -873,7 +874,7 @@ class Marble extends GameObject {
var sVel = this.velocity.sub(contacts[i].velocity);
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 surfaceVel = this.contacts[i].normal.multiply(surfaceDot);
@ -908,7 +909,7 @@ class Marble extends GameObject {
}
contacts[i].velocity.load(otherMarble.velocity);
} 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.normalize();
this.velocity.load(this.velocity.multiply(velLen));
@ -934,7 +935,7 @@ class Marble extends GameObject {
vAtC.load(vAtC.sub(contacts[i].normal.multiply(contacts[i].normal.dot(sVel))));
var vAtCMag = vAtC.length();
if (vAtCMag != 0) {
if (vAtCMag != 0.0) {
var friction = this._bounceKineticFriction * contacts[i].friction;
var angVMagnitude = friction * 5 * normalVel / (2 * this._radius);
@ -970,7 +971,7 @@ class Marble extends GameObject {
}
}
} while (!done && itersIn < 1e4); // Maximum limit pls
if (this.velocity.lengthSq() < 625) {
if (this.velocity.lengthSq() < 625.0) {
var gotOne = false;
var dir = new Vector(0, 0, 0);
for (j in 0...contacts.length) {
@ -994,10 +995,10 @@ class Marble extends GameObject {
soFar += (dist - outVel * timeToSeparate) / timeToSeparate / contacts[k].normal.dot(dir);
}
}
if (soFar < -25)
soFar = -25;
if (soFar > 25)
soFar = 25;
if (soFar < -25.0)
soFar = -25.0;
if (soFar > 25.0)
soFar = 25.0;
this.velocity.load(this.velocity.add(dir.multiply(soFar)));
}
}
@ -1066,7 +1067,7 @@ class Marble extends GameObject {
slipping = false;
}
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));
this._slipAmount = vAtCMag - totalDeltaV;
}
@ -1093,16 +1094,16 @@ class Marble extends GameObject {
friction2 = this._kineticFriction * bestContact.friction;
Aadd.load(Aadd.multiply(friction2 * bestNormalForce / aAtCMag));
}
A.set(A.x + Aadd.x, A.y + Aadd.y, A.z + Aadd.z);
a.set(a.x + aadd.x, a.y + aadd.y, a.z + aadd.z);
A.load(A.add(Aadd));
a.load(a.add(aadd));
}
A.set(A.x + AFriction.x, A.y + AFriction.y, A.z + AFriction.z);
a.set(a.x + aFriction.x, a.y + aFriction.y, a.z + aFriction.z);
A.load(A.add(AFriction));
a.load(a.add(aFriction));
lastContactNormal = bestContact.normal;
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) {
a.set(); // Zero it out
}
@ -1371,7 +1372,7 @@ class Marble extends GameObject {
var surfaceNormal = new Vector(verts.nx, verts.ny,
verts.nz); // surface.normals[surface.indices[i]].transformed3x3(obj.transform).normalized();
if (obj is DtsObject)
surfaceNormal.multiply(-1);
surfaceNormal.load(v.sub(v0).cross(v2.sub(v0)).normalized().multiply(-1));
var surfaceD = -surfaceNormal.dot(v0);
// If we're going the wrong direction or not going to touch the plane, ignore...
@ -2390,6 +2391,8 @@ class Marble extends GameObject {
if (Key.isDown(Settings.controlsSettings.right)) {
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)
|| MarbleGame.instance.touchInput.jumpButton.pressed
|| Gamepad.isDown(Settings.gamepadSettings.jump)) {

View file

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

View file

@ -2524,7 +2524,7 @@ class MarbleWorld extends Scheduler {
} else {
nextLevelCode();
}
}, mission, finishTime);
}, mission, finishTime, this.replay.write());
MarbleGame.canvas.pushDialog(egg);
this.setCursorLock(false);
return 0;

View file

@ -52,6 +52,7 @@ class ReplayFrame {
var t = (time - this.time) / (next.time - this.time);
var dt = time - this.time;
var clockDt = next.clockTime - this.clockTime;
var interpFrame = new ReplayFrame();
@ -59,18 +60,20 @@ class ReplayFrame {
interpFrame.time = time;
interpFrame.bonusTime = this.bonusTime;
interpFrame.clockTime = this.clockTime;
if (interpFrame.bonusTime != 0 && time >= 3.5) {
if (dt <= this.bonusTime) {
interpFrame.bonusTime -= dt;
if (clockDt > 0) {
if (interpFrame.bonusTime != 0 && time >= 3.5) {
if (dt <= this.bonusTime) {
interpFrame.bonusTime -= dt;
} else {
interpFrame.clockTime += dt - this.bonusTime;
interpFrame.bonusTime = 0;
}
} else {
interpFrame.clockTime += dt - this.bonusTime;
interpFrame.bonusTime = 0;
}
} else {
if (this.time >= 3.5)
interpFrame.clockTime += dt;
else if (this.time + dt >= 3.5) {
interpFrame.clockTime += (this.time + dt) - 3.5;
if (this.time >= 3.5)
interpFrame.clockTime += dt;
else if (this.time + dt >= 3.5) {
interpFrame.clockTime += (this.time + dt) - 3.5;
}
}
}
@ -291,7 +294,9 @@ class Replay {
public function endFrame() {
// 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;
return;
}

View file

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

View file

@ -130,14 +130,27 @@ class ManifestEntry extends FileEntry {
if (onReady != null)
onReady();
} else {
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();
});
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();
}).then((buf:js.lib.ArrayBuffer) -> {
loaded = true;
bytes = Bytes.ofData(buf);
if (onReady != null)
onReady();
});
});
}
#else
if (onReady != null)

View file

@ -118,7 +118,7 @@ class ChatCtrl extends GuiControl {
}
public function addChatMessage(text:String) {
var realText = StringTools.htmlUnescape(text);
var realText = StringTools.htmlEscape(text);
this.chats.push({
text: realText,
age: 10.0

View file

@ -19,7 +19,8 @@ class EndGameGui extends GuiControl {
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();
this.horizSizing = Width;
this.vertSizing = Height;
@ -312,12 +313,11 @@ class EndGameGui extends GuiControl {
scoreData.push({name: "Matan W.", time: 5999.999});
}
egFirstLine.text.text = '<p align="left"><font color="#EEC884">1. </font>${scoreData[0].name}</p>';
egSecondLine.text.text = '<p align="left"><font color="#CDCDCD">2. </font>${scoreData[1].name}</p>';
egThirdLine.text.text = '<p align="left"><font color="#C9AFA0">3. </font>${scoreData[2].name}</p>';
egFourthLine.text.text = '<p align="left"><font color="#A4A4A4">4. </font>${scoreData[3].name}</p>';
egFifthLine.text.text = '<p align="left"><font color="#949494">5. </font>${scoreData[4].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>${StringTools.htmlEscape(scoreData[1].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>${StringTools.htmlEscape(scoreData[3].name)}</p>';
egFifthLine.text.text = '<p align="left"><font color="#949494">5. </font>${StringTools.htmlEscape(scoreData[4].name)}</p>';
var lineelems = [
egFirstLineScore,
egSecondLineScore,
@ -393,6 +393,8 @@ class EndGameGui extends GuiControl {
// }
Settings.save();
var rewindUsed = MarbleGame.instance.world.rewindUsed;
if (idx <= 4) {
setButtonStates(false);
var end = new EnterNameDlg(idx, (name) -> {
@ -426,8 +428,7 @@ class EndGameGui extends GuiControl {
var lbPath = mission.path;
if (mission.isClaMission)
lbPath = 'custom/${mission.id}';
var replayData = MarbleGame.instance.world.replay.write();
Leaderboards.submitScore(lbPath, myScore.time, MarbleGame.instance.world.rewindUsed, (sendReplay, rowId) -> {
Leaderboards.submitScore(lbPath, myScore.time, rewindUsed, (sendReplay, rowId) -> {
if (sendReplay && !mission.isClaMission) {
Leaderboards.submitReplay(rowId, replayData);
}
@ -438,20 +439,21 @@ class EndGameGui extends GuiControl {
this.addChild(end);
} else {
// Check if we can submit LB scores
var replayData = MarbleGame.instance.world.replay.write();
var lbPath = mission.path;
if (mission.isClaMission)
lbPath = 'custom/${mission.id}';
Leaderboards.getScores(lbPath, All, (scores) -> {
var hasMyScore = false;
var myTopScoreLB = 0.0;
for (score in scores) {
if (score.name == Settings.highscoreName) {
hasMyScore = true;
myTopScoreLB = score.score;
break;
}
}
if (!hasMyScore) {
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) {
Leaderboards.submitReplay(rowId, replayData);
}

View file

@ -10,6 +10,7 @@ import h2d.Tile;
import h2d.Graphics;
import src.MarbleGame;
import src.Util;
import haxe.Timer;
class GuiScrollCtrl extends GuiControl {
public var scrollY:Float = 0;
@ -40,6 +41,12 @@ class GuiScrollCtrl extends GuiControl {
var dirty:Bool = true;
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 deltaY:Float = 0;
@ -239,34 +246,59 @@ class GuiScrollCtrl extends GuiControl {
}
public override function onMousePress(mouseState:MouseState) {
if (Util.isTouchDevice()) {
this.pressed = true;
this.dirty = true;
this.updateScrollVisual();
this.prevMousePos = mouseState.position;
}
// if (Util.isTouchDevice()) {
this.pressed = true;
this.dirty = true;
this.updateScrollVisual();
this.prevMousePos = mouseState.position;
this.scrollVelocity = 0;
this.momentumActive = false;
this.lastMoveStamp = Timer.stamp();
// }
}
public override function onMouseRelease(mouseState:MouseState) {
if (Util.isTouchDevice()) {
this.pressed = false;
this.dirty = true;
deltaY = 0;
this.updateScrollVisual();
}
// if (Util.isTouchDevice()) {
this.pressed = false;
this.dirty = true;
deltaY = 0;
this.updateScrollVisual();
this.momentumActive = Math.abs(scrollVelocity) > 0.01;
this.lastMoveStamp = 0;
// }
}
public override function onMouseMove(mouseState:MouseState) {
if (Util.isTouchDevice()) {
super.onMouseMove(mouseState);
if (this.pressed) {
var dy = (mouseState.position.y - this.prevMousePos.y) * scrollSpeed / this.maxScrollY;
deltaY = -dy;
this.scrollY -= dy;
this.prevMousePos = mouseState.position;
this.updateScrollVisual();
// if (Util.isTouchDevice()) {
super.onMouseMove(mouseState);
if (this.pressed) {
var renderRect = this.getRenderRectangle();
var scrollExtentY = renderRect.extent.y;
var dy = (mouseState.position.y - this.prevMousePos.y) / ((maxScrollY * Settings.uiScale) / scrollExtentY);
deltaY = -dy;
this.scrollY -= dy;
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();
}
// }
}
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) {
@ -285,6 +317,21 @@ class GuiScrollCtrl extends GuiControl {
this.updateScrollVisual();
}
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) {

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>';
} else {
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);
@ -197,7 +197,7 @@ class JoinServerGui extends GuiImage {
function updateServerListDisplay() {
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);
}

View file

@ -585,11 +585,11 @@ class MPPlayMissionGui extends GuiImage {
currentSelection = -1;
}
pmDesc.text.text = '<font face="MarkerFelt32" color="#E3F3FF"><p align="center">#${currentSelection + 1}: ${currentMission.title}</p></font>'
+ '<font face="MarkerFelt18" color="#CEE0F4">${currentMission.description}</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">${StringTools.htmlEscape(currentMission.description)}</font>';
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;
#if js
@ -718,7 +718,7 @@ class MPPlayMissionGui extends GuiImage {
}
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);
// if (!showingCustoms)
@ -728,7 +728,7 @@ class MPPlayMissionGui extends GuiImage {
}
public static function addChatMessage(s:String) {
var realText = StringTools.htmlUnescape(s);
var realText = StringTools.htmlEscape(s);
allChats.push(realText);
if (allChats.length > 100) {
allChats = allChats.slice(allChats.length - 100);

View file

@ -566,17 +566,17 @@ class PlayGui {
var fpsMeterCtrl = new GuiImage(ResourceLoader.getResource("data/ui/game/transparency-fps.png", ResourceLoader.getImage, this.imageResources)
.toTile());
fpsMeterCtrl.position = new Vector(544, 448);
fpsMeterCtrl.position = new Vector(534, 448);
fpsMeterCtrl.horizSizing = Left;
fpsMeterCtrl.vertSizing = Top;
fpsMeterCtrl.extent = new Vector(96, 32);
fpsMeterCtrl.extent = new Vector(106, 32);
fpsMeter = new GuiText(bfont);
fpsMeter.horizSizing = Width;
fpsMeter.vertSizing = Height;
fpsMeter.position = new Vector(10, 3);
fpsMeter.text.textColor = 0;
fpsMeter.extent = new Vector(96, 32);
fpsMeter.extent = new Vector(106, 32);
fpsMeterCtrl.addChild(fpsMeter);
playGuiCtrl.addChild(fpsMeterCtrl);
@ -738,7 +738,7 @@ class PlayGui {
} else {
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 pingStatus = "unknown";
if (connPing <= 5)
@ -1177,7 +1177,7 @@ class PlayGui {
this.powerupImageScene.setElapsedTime(timeState.dt);
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);
if (Net.isMP) {

View file

@ -1234,7 +1234,7 @@ class PlayMissionGui extends GuiImage {
var i = 1;
for (score in scoreList) {
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="279"><img src="${platformToString(score.platform)}"/></offset>
${score.rewind == 1 ? '<offset value="299"><img src="rewind"/></offset> ' : ""}');

View file

@ -562,7 +562,6 @@ class Net {
serverInfo.players++;
}
serverInfo.players++;
MasterServerClient.instance.sendServerInfo(serverInfo); // notify the server of the new player
if (MarbleGame.canvas.content is MPPlayMissionGui) {