change ws library, start "restart" netcode, fix some marble move bugs

This commit is contained in:
RandomityGuy 2024-04-20 22:36:27 +05:30
parent 5d5cb2b892
commit 604f858573
13 changed files with 164 additions and 76 deletions

View file

@ -1,5 +1,5 @@
-cp src
-lib hxWebSockets
-lib colyseus-websocket
-lib datachannel
-lib heaps
-lib stb_ogg_sound
@ -11,5 +11,6 @@
-D highDPI
-D flow_border
-D analyzer-optimize
--dce full
--main Main
-debug

View file

@ -1,7 +1,7 @@
-cp src
-lib heaps
-lib hlsdl
-lib hxWebSockets
-lib colyseus-websocket
-lib datachannel
-hl marblegame.hl
-D windowSize=1280x720

View file

@ -1796,17 +1796,23 @@ class Marble extends GameObject {
public function updateServer(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array<PathedInterior>) {
var move:NetMove = null;
if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching && !this.level.isReplayingMovement) {
if (this.controllable && this.mode != Finish) {
if (Net.isClient) {
var axis = getMarbleAxis()[1];
move = Net.clientConnection.recordMove(cast this, axis, timeState, recvServerTick);
} else if (Net.isHost) {
var axis = getMarbleAxis()[1];
var innerMove = recordMove();
var qx = Std.int((innerMove.d.x * 16) + 16);
var qy = Std.int((innerMove.d.y * 16) + 16);
innerMove.d.x = (qx - 16) / 16.0;
innerMove.d.y = (qy - 16) / 16.0;
if (MarbleGame.instance.paused) {
innerMove.d.x = 0;
innerMove.d.y = 0;
innerMove.blast = innerMove.jump = innerMove.powerup = false;
} else {
var qx = Std.int((innerMove.d.x * 16) + 16);
var qy = Std.int((innerMove.d.y * 16) + 16);
innerMove.d.x = (qx - 16) / 16.0;
innerMove.d.y = (qy - 16) / 16.0;
}
move = new NetMove(innerMove, axis, timeState, recvServerTick, 65535);
}
}
@ -1844,7 +1850,7 @@ class Marble extends GameObject {
advancePhysics(timeState, move.move, collisionWorld, pathedInteriors);
physicsAccumulator = 0;
if (move.move.jump && this.outOfBounds) {
if (move.move.jump && this.outOfBounds && Net.isHost) {
this.level.cancel(this.oobSchedule);
this.level.restart(cast this);
}

View file

@ -1,5 +1,6 @@
package src;
import net.MasterServerClient;
import gui.MultiplayerLevelSelectGui;
import net.NetCommands;
import net.Net;
@ -187,6 +188,7 @@ class MarbleGame {
}
public function update(dt:Float) {
MasterServerClient.process();
if (world != null) {
if (world._disposed) {
world = null;

View file

@ -204,7 +204,8 @@ class MarbleWorld extends Scheduler {
// Multiplayer
public var isMultiplayer:Bool;
public var startRealTime:Float = 0;
public var serverStartTicks:Int;
public var startTime:Float = 1e8;
public var multiplayerStarted:Bool = false;
var tickAccumulator:Float = 0.0;
@ -511,6 +512,16 @@ class MarbleWorld extends Scheduler {
NetCommands.clientIsReady(Net.clientId);
}
public function restartMultiplayerState() {
if (this.isMultiplayer) {
serverStartTicks = 0;
lastMoves = new MarbleUpdateQueue();
predictions = new MarblePredictionStore();
powerupPredictions = new PowerupPredictionStore();
gemPredictions = new GemPredictionStore();
}
}
public function restart(marble:Marble, full:Bool = false) {
Console.log("LEVEL RESTART");
if (!full && this.currentCheckpoint != null) {
@ -535,6 +546,7 @@ class MarbleWorld extends Scheduler {
this.timeState.currentAttemptTime = 0;
this.timeState.gameplayClock = this.gameMode.getStartTime();
this.timeState.ticks = 0;
this.bonusTime = 0;
this.marble.outOfBounds = false;
this.marble.blastAmount = 0;
@ -678,7 +690,7 @@ class MarbleWorld extends Scheduler {
}
public function allClientsReady() {
NetCommands.setStartTime(3); // Start after 3 seconds
NetCommands.setStartTicks(this.timeState.ticks);
}
public function updateGameState() {
@ -695,8 +707,9 @@ class MarbleWorld extends Scheduler {
this.marble.setMode(Play);
}
} else {
if (!this.multiplayerStarted) {
if (this.startRealTime != 0 && this.timeState.timeSinceLoad > this.startRealTime) {
if (!this.multiplayerStarted && this.finishTime == null) {
if ((Net.isHost && (this.timeState.timeSinceLoad >= startTime)) // 3.5 == 109 ticks
|| (Net.isClient && this.serverStartTicks != 0 && @:privateAccess this.marble.serverTicks >= this.serverStartTicks + 109)) {
this.multiplayerStarted = true;
this.marble.setMode(Play);
for (client => marble in this.clientMarbles)
@ -1636,10 +1649,15 @@ class MarbleWorld extends Scheduler {
timeTravelSound.stop();
timeTravelSound = null;
}
if (this.timeState.currentAttemptTime + skipStartBugPauseTime >= 3.5) {
if (!this.isMultiplayer) {
if (this.timeState.currentAttemptTime + skipStartBugPauseTime >= 3.5) {
this.timeState.gameplayClock += dt * timeMultiplier;
} else if (this.timeState.currentAttemptTime + dt >= 3.5) {
this.timeState.gameplayClock += ((this.timeState.currentAttemptTime + dt) - 3.5) * timeMultiplier;
}
} else if (this.multiplayerStarted) {
this.timeState.gameplayClock += dt * timeMultiplier;
} else if (this.timeState.currentAttemptTime + dt >= 3.5) {
this.timeState.gameplayClock += ((this.timeState.currentAttemptTime + dt) - 3.5) * timeMultiplier;
}
if (this.timeState.gameplayClock < 0)
this.gameMode.onTimeExpire();
@ -1989,6 +2007,9 @@ class MarbleWorld extends Scheduler {
if (Util.isTouchDevice()) {
MarbleGame.instance.touchInput.setControlsEnabled(true);
}
if (this.isMultiplayer) {
NetCommands.restartGame();
}
// @:privateAccess playGui.playGuiCtrl.render(scene2d);
}
if (MarbleGame.instance.toRecord) {
@ -2165,7 +2186,7 @@ class MarbleWorld extends Scheduler {
return null;
});
}
if (Net.isHost) {
if (!this.isMultiplayer || Net.isHost) {
marble.oobSchedule = this.schedule(this.timeState.currentAttemptTime + 2.5, () -> {
this.restart(marble);
return null;

View file

@ -1,5 +1,6 @@
package src;
import hxd.Key;
import shaders.RendererDefaultPass;
import h3d.pass.PassList;
import hxd.Window;
@ -207,6 +208,18 @@ class Renderer extends h3d.scene.Renderer {
copyPass.render();
}
if (!cubemapPass) {
#if sys
if (Key.isDown(Key.CTRL) && Key.isPressed(Key.P)) {
var pixels = backBuffer.capturePixels();
var filename = StringTools.replace('Screenshot ${Date.now().toString()}.png', ":", ".");
var pixdata = pixels.toPNG();
hxd.File.createDirectory("data/screenshots");
hxd.File.saveBytes("data/screenshots/" + filename, pixdata);
}
#end
}
// h3d.pass.Copy.run(backBuffers[0], backBuffers[1]);
// renderPass(defaultPass, get("refract"));
// ctx.engine.popTarget();

View file

@ -19,6 +19,9 @@ class EndGameGui extends GuiImage {
var mission:Mission;
var innerCtrl:GuiControl;
var endGameWnd:GuiImage;
var retryFunc:GuiControl->Void;
var nextFunc:GuiControl->Void;
var continueFunc:GuiControl->Void;
var scoreSubmitted:Bool = false;
@ -31,6 +34,9 @@ class EndGameGui extends GuiImage {
this.position = new Vector(0, 0);
this.extent = new Vector(640, 480);
this.mission = mission;
this.retryFunc = restartFunc;
this.nextFunc = nextLevelFunc;
this.continueFunc = continueFunc;
function loadButtonImages(path:String) {
var normal = ResourceLoader.getResource('${path}_n.png', ResourceLoader.getImage, this.imageResources).toTile();

View file

@ -121,7 +121,18 @@ class MPServerListGui extends GuiImage {
nextButton.gamepadAccelerator = ["X"];
nextButton.pressedAction = (e) -> {
MarbleGame.canvas.setContent(new MultiplayerLoadingGui("Connecting"));
var failed = true;
haxe.Timer.delay(() -> {
if (failed) {
var loadGui:MultiplayerLoadingGui = cast MarbleGame.canvas.content;
if (loadGui != null) {
loadGui.setErrorStatus("Failed to connect to server");
Net.disconnect();
}
}
}, 15000);
Net.joinServer(ourServerList[curSelection].name, () -> {
failed = false;
MarbleGame.canvas.setContent(new MultiplayerLevelSelectGui(false));
Net.remoteServerInfo = ourServerList[curSelection];
});

View file

@ -726,6 +726,30 @@ class PlayGui {
playerListScoresCtrl.setTexts(plScores);
}
public function doMPEndGameMessage() {
playerList.sort((a, b) -> a.score > b.score ? -1 : (a.score < b.score ? 1 : 0));
var p1 = playerList[0];
var p2 = playerList.length > 1 ? playerList[1] : null;
if (p2 == null) {
var onePt = p1.score == 1;
if (onePt)
MarbleGame.instance.world.displayAlert('${p1.name} won with 1 point!');
else
MarbleGame.instance.world.displayAlert('${p1.name} won with ${p1.score} points!');
} else {
var tie = p1.score == p2.score;
if (tie) {
MarbleGame.instance.world.displayAlert('Game tied!');
} else {
var onePt = p1.score == 1;
if (onePt)
MarbleGame.instance.world.displayAlert('${p1.name} won with 1 point!');
else
MarbleGame.instance.world.displayAlert('${p1.name} won with ${p1.score} points!');
}
}
}
public function addPlayer(id:Int, name:String, us:Bool) {
playerList.push({
id: id,

View file

@ -287,7 +287,7 @@ class HuntMode extends NullMode {
if (!this.level.isMultiplayer || Net.isHost) {
rng.setSeed(100);
rng2.setSeed(100);
if (Settings.optionsSettings.huntRandom) {
if (Settings.optionsSettings.huntRandom || Net.isMP) {
rng.setSeed(cast Math.random() * 10000);
rng2.setSeed(cast Math.random() * 10000);
}
@ -592,7 +592,11 @@ class HuntMode extends NullMode {
level.marble.camera.finish = true;
level.finishYaw = level.marble.camera.CameraYaw;
level.finishPitch = level.marble.camera.CameraPitch;
level.displayAlert("Congratulations! You've finished!");
if (level.isMultiplayer) {
@:privateAccess level.playGui.doMPEndGameMessage();
} else {
level.displayAlert("Congratulations! You've finished!");
}
level.cancel(@:privateAccess level.oobSchedule);
level.cancel(@:privateAccess level.marble.oobSchedule);
if (!level.isWatching) {

View file

@ -4,9 +4,8 @@ import gui.MessageBoxOkDlg;
import src.MarbleGame;
import haxe.Json;
import net.Net.ServerInfo;
import hx.ws.WebSocket;
import haxe.net.WebSocket;
import src.Console;
import hx.ws.Types.MessageType;
import gui.MultiplayerLoadingGui;
typedef RemoteServerInfo = {
@ -26,29 +25,23 @@ class MasterServerClient {
var open = false;
public function new(onOpenFunc:() -> Void) {
#if sys
var senderThread = sys.thread.Thread.current();
#end
ws = new WebSocket(serverIp);
ws = WebSocket.create(serverIp);
ws.onopen = () -> {
open = true;
#if sys
senderThread.events.run(onOpenFunc);
#end
#if js
onOpenFunc();
#end
}
ws.onmessage = (m) -> {
switch (m) {
case StrMessage(content):
handleMessage(content);
case _:
return;
}
ws.onmessageString = (m) -> {
handleMessage(m);
}
}
public static function process() {
#if sys
if (instance != null)
instance.ws.process();
#end
}
public static function connectToMasterServer(onConnect:() -> Void) {
if (instance == null)
instance = new MasterServerClient(onConnect);
@ -71,7 +64,7 @@ class MasterServerClient {
}
public function sendServerInfo(serverInfo:ServerInfo) {
ws.send(Json.stringify({
ws.sendString(Json.stringify({
type: "serverInfo",
name: serverInfo.name,
players: serverInfo.players,
@ -84,7 +77,7 @@ class MasterServerClient {
}
public function sendConnectToServer(serverName:String, sdp:String) {
ws.send(Json.stringify({
ws.sendString(Json.stringify({
type: "connect",
serverName: serverName,
sdp: sdp
@ -93,7 +86,7 @@ class MasterServerClient {
public function getServerList(serverListCb:Array<RemoteServerInfo>->Void) {
this.serverListCb = serverListCb;
ws.send(Json.stringify({
ws.sendString(Json.stringify({
type: "serverList"
}));
}
@ -108,7 +101,7 @@ class MasterServerClient {
}
if (conts.type == "connect") {
if (!Net.isHost) {
ws.send(Json.stringify({
ws.sendString(Json.stringify({
type: "connectFailed",
success: false,
reason: "The server has shut down"
@ -116,7 +109,7 @@ class MasterServerClient {
return;
}
if (Net.serverInfo.players >= Net.serverInfo.maxPlayers) {
ws.send(Json.stringify({
ws.sendString(Json.stringify({
type: "connectFailed",
success: false,
reason: "The server is full"
@ -124,7 +117,7 @@ class MasterServerClient {
return;
}
Net.addClientFromSdp(conts.sdp, (sdpReply) -> {
ws.send(Json.stringify({
ws.sendString(Json.stringify({
success: true,
type: "connectResponse",
sdp: sdpReply,

View file

@ -70,39 +70,47 @@ class MoveManager {
}
var move = new Move();
move.d = new Vector();
move.d.x = Gamepad.getAxis(Settings.gamepadSettings.moveYAxis);
move.d.y = -Gamepad.getAxis(Settings.gamepadSettings.moveXAxis);
if (Key.isDown(Settings.controlsSettings.forward)) {
move.d.x -= 1;
}
if (Key.isDown(Settings.controlsSettings.backward)) {
move.d.x += 1;
}
if (Key.isDown(Settings.controlsSettings.left)) {
move.d.y += 1;
}
if (Key.isDown(Settings.controlsSettings.right)) {
move.d.y -= 1;
}
if (Key.isDown(Settings.controlsSettings.jump)
|| MarbleGame.instance.touchInput.jumpButton.pressed
|| Gamepad.isDown(Settings.gamepadSettings.jump)) {
move.jump = true;
}
if ((!Util.isTouchDevice() && Key.isDown(Settings.controlsSettings.powerup))
|| (Util.isTouchDevice() && MarbleGame.instance.touchInput.powerupButton.pressed)
|| Gamepad.isDown(Settings.gamepadSettings.powerup)) {
move.powerup = true;
}
if (!MarbleGame.instance.paused) {
move.d.x = Gamepad.getAxis(Settings.gamepadSettings.moveYAxis);
move.d.y = -Gamepad.getAxis(Settings.gamepadSettings.moveXAxis);
if (Key.isDown(Settings.controlsSettings.forward)) {
move.d.x -= 1;
}
if (Key.isDown(Settings.controlsSettings.backward)) {
move.d.x += 1;
}
if (Key.isDown(Settings.controlsSettings.left)) {
move.d.y += 1;
}
if (Key.isDown(Settings.controlsSettings.right)) {
move.d.y -= 1;
}
if (Key.isDown(Settings.controlsSettings.jump)
|| MarbleGame.instance.touchInput.jumpButton.pressed
|| Gamepad.isDown(Settings.gamepadSettings.jump)) {
move.jump = true;
}
if ((!Util.isTouchDevice() && Key.isDown(Settings.controlsSettings.powerup))
|| (Util.isTouchDevice() && MarbleGame.instance.touchInput.powerupButton.pressed)
|| Gamepad.isDown(Settings.gamepadSettings.powerup)) {
move.powerup = true;
}
if (Key.isDown(Settings.controlsSettings.blast)
|| (MarbleGame.instance.touchInput.blastbutton.pressed)
|| Gamepad.isDown(Settings.gamepadSettings.blast))
move.blast = true;
if (Key.isDown(Settings.controlsSettings.blast)
|| (MarbleGame.instance.touchInput.blastbutton.pressed)
|| Gamepad.isDown(Settings.gamepadSettings.blast))
move.blast = true;
if (MarbleGame.instance.touchInput.movementInput.pressed) {
move.d.y = -MarbleGame.instance.touchInput.movementInput.value.x;
move.d.x = MarbleGame.instance.touchInput.movementInput.value.y;
if (MarbleGame.instance.touchInput.movementInput.pressed) {
move.d.y = -MarbleGame.instance.touchInput.movementInput.value.x;
move.d.x = MarbleGame.instance.touchInput.movementInput.value.y;
}
// quantize moves for client
var qx = Std.int((move.d.x * 16) + 16);
var qy = Std.int((move.d.y * 16) + 16);
move.d.x = (qx - 16) / 16.0;
move.d.y = (qy - 16) / 16.0;
}
var netMove = new NetMove(move, motionDir, timeState.clone(), serverTicks, nextMoveId++);

View file

@ -17,11 +17,9 @@ import net.NetPacket.MarbleMovePacket;
import haxe.Json;
import datachannel.RTCPeerConnection;
import datachannel.RTCDataChannel;
import hx.ws.WebSocket;
import src.Console;
import net.NetCommands;
import src.MarbleGame;
import hx.ws.Types.MessageType;
import src.Settings;
enum abstract NetPacketType(Int) from Int to Int {
@ -371,7 +369,8 @@ class Net {
var movePacket = new MarbleMovePacket();
movePacket.deserialize(input);
var cc = clientIdMap[movePacket.clientId];
cc.queueMove(movePacket.move);
if (cc.state == GAME)
cc.queueMove(movePacket.move);
case PowerupPickup:
var powerupPickupPacket = new PowerupPickupPacket();