From 038e0ed16e4cba9ac7af30d92de0be9c4b3067ee Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Tue, 9 Apr 2024 23:35:20 +0530 Subject: [PATCH] more net ui and netcode --- src/Marble.hx | 68 ++++++++++++++++++---------- src/MarbleWorld.hx | 15 ++++-- src/gui/CreateMatchGui.hx | 5 +- src/gui/EnterNameDlg.hx | 2 +- src/gui/ExitGameDlg.hx | 4 +- src/gui/MultiplayerLevelSelectGui.hx | 5 +- src/net/ClientConnection.hx | 6 +-- src/net/MasterServerClient.hx | 19 +++++++- src/net/MoveManager.hx | 66 +++++++++++++++++++++------ src/net/Net.hx | 3 +- src/net/NetCommands.hx | 6 +-- src/shaders/NoiseTileMaterial.hx | 3 -- 12 files changed, 144 insertions(+), 58 deletions(-) diff --git a/src/Marble.hx b/src/Marble.hx index 28a0e20e..4f47958f 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -258,6 +258,8 @@ class Marble extends GameObject { var oldPos:Vector; var newPos:Vector; var prevRot:Quat; + var posStore:Vector; + var netCorrected:Bool; public var contacts:Array = []; public var bestContact:CollisionInfo; @@ -324,6 +326,7 @@ class Marble extends GameObject { var isNetUpdate:Bool = false; var netFlags:Int = 0; var serverTicks:Int; + var recvServerTick:Int; public function new() { super(); @@ -368,6 +371,9 @@ class Marble extends GameObject { var isUltra = true; + this.posStore = new Vector(); + this.netCorrected = false; + var marbleDts = new DtsObject(); Console.log("Marble: " + Settings.optionsSettings.marbleModel + " (" + Settings.optionsSettings.marbleSkin + ")"); marbleDts.dtsPath = Settings.optionsSettings.marbleModel; @@ -1748,8 +1754,9 @@ class Marble extends GameObject { // } // } this.serverTicks = p.serverTicks; - this.oldPos = this.newPos; - this.newPos = p.position; + this.recvServerTick = p.serverTicks; + // this.oldPos = this.newPos; + // this.newPos = p.position; this.collider.transform.setPosition(p.position); this.velocity = p.velocity; this.omega = p.omega; @@ -1767,29 +1774,36 @@ class Marble extends GameObject { this.level.pickUpPowerUp(cast this, this.level.powerUps[p.powerUpId]); } - if (this.controllable && Net.isClient) { - // We are client, need to do something about the queue - var mm = Net.clientConnection.moveManager; - // trace('Queue size: ${mm.getQueueSize()}, server: ${p.moveQueueSize}'); - if (mm.getQueueSize() / p.moveQueueSize < 2) { - mm.stall = true; - } else { - mm.stall = false; - } - } + // if (this.controllable && Net.isClient) { + // // We are client, need to do something about the queue + // var mm = Net.clientConnection.moveManager; + // // trace('Queue size: ${mm.getQueueSize()}, server: ${p.moveQueueSize}'); + // if (mm.getQueueSize() / p.moveQueueSize < 2) { + // mm.stall = true; + // } else { + // mm.stall = false; + // } + // } return true; } + function calculateNetSmooth() { + if (this.netCorrected) { + this.netCorrected = false; + this.oldPos.load(this.posStore); + } + } + public function updateServer(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array) { var move:NetMove = null; if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching && !this.level.isReplayingMovement) { if (Net.isClient) { var axis = getMarbleAxis()[1]; - move = Net.clientConnection.recordMove(cast this, axis, timeState); + move = Net.clientConnection.recordMove(cast this, axis, timeState, recvServerTick); } else if (Net.isHost) { var axis = getMarbleAxis()[1]; var innerMove = recordMove(); - move = new NetMove(innerMove, axis, timeState, 65535); + move = new NetMove(innerMove, axis, timeState, recvServerTick, 65535); } } var moveId = 65535; @@ -1800,30 +1814,35 @@ class Marble extends GameObject { var axis = getMarbleAxis()[1]; var innerMove = new Move(); innerMove.d = new Vector(0, 0); - move = new NetMove(innerMove, axis, timeState, 65535); + move = new NetMove(innerMove, axis, timeState, recvServerTick, 65535); } else { move = nextMove; moveMotionDir = nextMove.motionDir; moveId = nextMove.id; } } - if (move == null) { + if (move == null && !this.controllable) { var axis = moveMotionDir != null ? moveMotionDir : new Vector(0, -1, 0); var innerMove = lastMove; if (innerMove == null) { innerMove = new Move(); innerMove.d = new Vector(0, 0); } - move = new NetMove(innerMove, axis, timeState, 65535); + move = new NetMove(innerMove, axis, timeState, recvServerTick, 65535); } - playedSounds = []; - advancePhysics(timeState, move.move, collisionWorld, pathedInteriors); - physicsAccumulator = 0; + if (move != null) { + playedSounds = []; + advancePhysics(timeState, move.move, collisionWorld, pathedInteriors); + physicsAccumulator = 0; - if (move.move.jump && this.outOfBounds) { - this.level.cancel(this.oobSchedule); - this.level.restart(cast this); + if (move.move.jump && this.outOfBounds) { + this.level.cancel(this.oobSchedule); + this.level.restart(cast this); + } + } else { + physicsAccumulator = 0; + newPos.load(oldPos); } return move; @@ -1833,6 +1852,7 @@ class Marble extends GameObject { } public function updateClient(timeState:TimeState, pathedInteriors:Array) { + calculateNetSmooth(); this.level.updateBlast(cast this, timeState); if (oldPos != null && newPos != null) { var deltaT = physicsAccumulator / 0.032; @@ -2318,6 +2338,8 @@ class Marble extends GameObject { this.prevRot = this.getRotationQuat().clone(); this.oldPos = this.getAbsPos().getPosition(); this.newPos = this.getAbsPos().getPosition(); + this.posStore = new Vector(); + this.netCorrected = false; if (this._radius != this._prevRadius) { this._radius = this._prevRadius; this._marbleScale = this._renderScale = this._defaultScale; diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index f2204323..312683b1 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1077,7 +1077,7 @@ class MarbleWorld extends Scheduler { if (!lastMoves.ourMoveApplied) { var ourMove = lastMoves.myMarbleUpdate; if (ourMove != null) { - var ourMoveStruct = Net.clientConnection.acknowledgeMove(ourMove.move.id, timeState); + var ourMoveStruct = Net.clientConnection.acknowledgeMove(ourMove.move, timeState); lastMoves.ourMoveApplied = true; for (client => arr in lastMoves.otherMarbleUpdates) { var lastMove = null; @@ -1203,9 +1203,11 @@ class MarbleWorld extends Scheduler { var marbleToUpdate = clientMarbles[Net.clientIdMap[client]]; // Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius); - var distFromUs = @:privateAccess marbleToUpdate.newPos.distance(this.marble.newPos); + // var distFromUs = @:privateAccess marbleToUpdate.newPos.distance(this.marble.newPos); // if (distFromUs < 5) // { m.calculationTicks = ourQueuedMoves.length; + @:privateAccess marbleToUpdate.posStore.load(marbleToUpdate.newPos); + @:privateAccess marbleToUpdate.netCorrected = true; // } else { // m.calculationTicks = Std.int(Math.max(1, ourQueuedMoves.length - (distFromUs - 5) / 3)); // } @@ -1220,6 +1222,9 @@ class MarbleWorld extends Scheduler { Debug.drawSphere(@:privateAccess this.marble.newPos, this.marble._radius); // var syncTickStates = new Map(); + @:privateAccess this.marble.posStore.load(this.marble.newPos); + @:privateAccess this.marble.netCorrected = true; + for (move in ourQueuedMoves) { var m = move.move; Debug.drawSphere(@:privateAccess this.marble.newPos, this.marble._radius); @@ -1479,9 +1484,11 @@ class MarbleWorld extends Scheduler { for (client => marble in clientMarbles) { otherMoves.push(marble.updateServer(fixedDt, collisionWorld, pathedInteriors)); } - this.predictions.storeState(marble, myMove.timeState.ticks); - for (client => marble in clientMarbles) { + if (myMove != null) { this.predictions.storeState(marble, myMove.timeState.ticks); + for (client => marble in clientMarbles) { + this.predictions.storeState(marble, myMove.timeState.ticks); + } } if (Net.isHost) { for (client => othermarble in clientMarbles) { // Oh no! diff --git a/src/gui/CreateMatchGui.hx b/src/gui/CreateMatchGui.hx index b8f111ed..a7d3aeee 100644 --- a/src/gui/CreateMatchGui.hx +++ b/src/gui/CreateMatchGui.hx @@ -114,8 +114,9 @@ class CreateMatchGui extends GuiImage { nextButton.gamepadAccelerator = ["A"]; nextButton.accelerators = [hxd.Key.ENTER]; nextButton.pressedAction = (e) -> { - Net.hostServer('${Settings.highscoreName}\'s Server', maxPlayers, privateSlots, privateGame); - MarbleGame.canvas.setContent(new MultiplayerLevelSelectGui(true)); + Net.hostServer('${Settings.highscoreName}\'s Server', maxPlayers, privateSlots, privateGame, () -> { + MarbleGame.canvas.setContent(new MultiplayerLevelSelectGui(true)); + }); }; bottomBar.addChild(nextButton); } diff --git a/src/gui/EnterNameDlg.hx b/src/gui/EnterNameDlg.hx index 1375071e..c3624512 100644 --- a/src/gui/EnterNameDlg.hx +++ b/src/gui/EnterNameDlg.hx @@ -55,7 +55,7 @@ class EnterNameDlg extends GuiImage { textInput.text.selectionTile = h2d.Tile.fromColor(0x88BCEE, 0, hxd.Math.ceil(textInput.text.font.lineHeight)); textFrame.addChild(textInput); - textInput.text.text = Settings.highscoreName; + textInput.text.text = Settings.highscoreName == "" ? "Player Name" : Settings.highscoreName; var okButton = new GuiXboxButton("Ok", 120); okButton.position = new Vector(211, 248); diff --git a/src/gui/ExitGameDlg.hx b/src/gui/ExitGameDlg.hx index 63bef34e..ba2af01d 100644 --- a/src/gui/ExitGameDlg.hx +++ b/src/gui/ExitGameDlg.hx @@ -1,5 +1,6 @@ package gui; +import net.Net; import gui.GuiControl.MouseState; import src.AudioManager; import src.MarbleGame; @@ -82,7 +83,8 @@ class ExitGameDlg extends GuiImage { innerCtrl.addChild(btnList); btnList.addButton(0, "Resume", (evt) -> noFunc(btnList)); - btnList.addButton(0, "Restart", (evt) -> restartFunc(btnList)); + if (!Net.isMP) + btnList.addButton(0, "Restart", (evt) -> restartFunc(btnList)); btnList.addButton(4, "Exit Level", (evt) -> { MarbleGame.canvas.pushDialog(new MessageBoxYesNoDlg("Are you sure you want to exit this level? You will lose your current level progress.", () -> yesFunc(btnList), () -> {})); diff --git a/src/gui/MultiplayerLevelSelectGui.hx b/src/gui/MultiplayerLevelSelectGui.hx index 817527a0..0cd8588b 100644 --- a/src/gui/MultiplayerLevelSelectGui.hx +++ b/src/gui/MultiplayerLevelSelectGui.hx @@ -18,7 +18,7 @@ class MultiplayerLevelSelectGui extends GuiImage { static var currentSelectionStatic:Int = 0; static var setLevelFn:Int->Void; - static var playSelectedLevel:Void->Void; + static var playSelectedLevel:Int->Void; var playerList:GuiMLTextListCtrl; var updatePlayerCountFn:(Int, Int, Int, Int) -> Void; @@ -200,7 +200,8 @@ class MultiplayerLevelSelectGui extends GuiImage { }; bottomBar.addChild(nextButton); - playSelectedLevel = () -> { + playSelectedLevel = (index:Int) -> { + curMission = difficultyMissions[index]; MarbleGame.instance.playMission(curMission, true); } diff --git a/src/net/ClientConnection.hx b/src/net/ClientConnection.hx index 3f17aa12..c607f110 100644 --- a/src/net/ClientConnection.hx +++ b/src/net/ClientConnection.hx @@ -69,7 +69,7 @@ abstract class GameConnection { moveManager.queueMove(m); } - public inline function acknowledgeMove(m:Int, timeState:TimeState) { + public inline function acknowledgeMove(m:NetMove, timeState:TimeState) { return moveManager.acknowledgeMove(m, timeState); } @@ -81,8 +81,8 @@ abstract class GameConnection { return moveManager.getQueueSize(); } - public function recordMove(marble:src.Marble, motionDir:h3d.Vector, timeState:TimeState) { - return moveManager.recordMove(marble, motionDir, timeState); + public function recordMove(marble:src.Marble, motionDir:h3d.Vector, timeState:TimeState, serverTicks:Int) { + return moveManager.recordMove(marble, motionDir, timeState, serverTicks); } public function getNextMove() { diff --git a/src/net/MasterServerClient.hx b/src/net/MasterServerClient.hx index cafffe52..09e6baf9 100644 --- a/src/net/MasterServerClient.hx +++ b/src/net/MasterServerClient.hx @@ -26,10 +26,18 @@ class MasterServerClient { var open = false; public function new(onOpenFunc:() -> Void) { + #if sys + var senderThread = sys.thread.Thread.current(); + #end ws = new WebSocket(serverIp); ws.onopen = () -> { open = true; + #if sys + senderThread.events.run(onOpenFunc); + #end + #if js onOpenFunc(); + #end } ws.onmessage = (m) -> { switch (m) { @@ -44,8 +52,15 @@ class MasterServerClient { public static function connectToMasterServer(onConnect:() -> Void) { if (instance == null) instance = new MasterServerClient(onConnect); - else - onConnect(); + else { + if (instance.open) + onConnect(); + else { + instance.ws.close(); + instance = null; + instance = new MasterServerClient(onConnect); + } + } } public static function disconnectFromMasterServer() { diff --git a/src/net/MoveManager.hx b/src/net/MoveManager.hx index 3c390f51..30eb9469 100644 --- a/src/net/MoveManager.hx +++ b/src/net/MoveManager.hx @@ -25,11 +25,13 @@ class NetMove { var move:Move; var id:Int; var timeState:TimeState; + var serverTicks:Int; - public function new(move:Move, motionDir:Vector, timeState:TimeState, id:Int) { + public function new(move:Move, motionDir:Vector, timeState:TimeState, serverTicks:Int, id:Int) { this.move = move; this.motionDir = motionDir; this.id = id; + this.serverTicks = serverTicks; this.timeState = timeState; } } @@ -44,6 +46,12 @@ class MoveManager { var maxMoves = 45; + var serverTargetMoveListSize = 3; + var serverMaxMoveListSize = 5; + var serverAvgMoveListSize = 3.0; + var serverSmoothMoveAvg = 0.15; + var serverMoveListSizeSlack = 1.0; + public var stall = false; public function new(connection:GameConnection) { @@ -54,9 +62,10 @@ class MoveManager { mv.d = new Vector(0, 0); } - public function recordMove(marble:Marble, motionDir:Vector, timeState:TimeState) { - if (queuedMoves.length >= maxMoves || stall) + public function recordMove(marble:Marble, motionDir:Vector, timeState:TimeState, serverTicks:Int) { + if (queuedMoves.length >= maxMoves || stall) { return queuedMoves[queuedMoves.length - 1]; + } var move = new Move(); move.d = new Vector(); move.d.x = Gamepad.getAxis(Settings.gamepadSettings.moveYAxis); @@ -94,7 +103,7 @@ class MoveManager { move.d.x = MarbleGame.instance.touchInput.movementInput.value.y; } - var netMove = new NetMove(move, motionDir, timeState.clone(), nextMoveId++); + var netMove = new NetMove(move, motionDir, timeState.clone(), serverTicks, nextMoveId++); queuedMoves.push(netMove); if (nextMoveId >= 65535) // 65535 is reserved for null move @@ -113,6 +122,11 @@ class MoveManager { return netMove; } + function copyMove(to:Int, from:Int) { + queuedMoves[to].move = queuedMoves[from].move; + queuedMoves[to].motionDir.load(queuedMoves[from].motionDir); + } + public static inline function packMove(m:NetMove, b:OutputBitStream) { b.writeUInt16(m.id); b.writeByte(Std.int((m.move.d.x * 16) + 16)); @@ -139,7 +153,7 @@ class MoveManager { motionDir.x = b.readFloat(); motionDir.y = b.readFloat(); motionDir.z = b.readFloat(); - var netMove = new NetMove(move, motionDir, MarbleGame.instance.world.timeState.clone(), moveId); + var netMove = new NetMove(move, motionDir, MarbleGame.instance.world.timeState.clone(), 0, moveId); return netMove; } @@ -148,9 +162,31 @@ class MoveManager { } public function getNextMove() { - if (queuedMoves.length == 0) + if (Net.isHost) { + serverAvgMoveListSize *= (1 - serverSmoothMoveAvg); + serverAvgMoveListSize += serverSmoothMoveAvg * queuedMoves.length; + if (serverAvgMoveListSize < serverTargetMoveListSize - serverMoveListSizeSlack + && queuedMoves.length < serverTargetMoveListSize + && queuedMoves.length != 0) { + // Send null move + return null; + } + if (queuedMoves.length > serverMaxMoveListSize + || (serverAvgMoveListSize > serverTargetMoveListSize + serverMoveListSizeSlack + && queuedMoves.length > serverTargetMoveListSize)) { + var dropAmt = queuedMoves.length - serverTargetMoveListSize; + while (dropAmt-- > 0) { + queuedMoves.pop(); + } + serverAvgMoveListSize = serverTargetMoveListSize; + } + } + if (queuedMoves.length == 0) { + // if (lastMove != null) { + // lastMove.id++; // So that we force client's move to be overriden by this one + // } return lastMove; - else { + } else { lastMove = queuedMoves[0]; queuedMoves.shift(); return lastMove; @@ -161,25 +197,29 @@ class MoveManager { return queuedMoves.length; } - public function acknowledgeMove(m:Int, timeState:TimeState) { - if (m == 65535 || m == -1) + public function acknowledgeMove(m:NetMove, timeState:TimeState) { + if (m.id == 65535 || m.id == -1) { return null; - if (m <= lastAckMoveId) + } + if (m.id <= lastAckMoveId) return null; // Already acked if (queuedMoves.length == 0) return null; - while (m != queuedMoves[0].id) { + if (m.id >= nextMoveId) { + return queuedMoves[0]; // Input lag + } + while (m.id != queuedMoves[0].id) { queuedMoves.shift(); } var delta = -1; var mv = null; - if (m == queuedMoves[0].id) { + if (m.id == queuedMoves[0].id) { delta = queuedMoves[0].id - lastAckMoveId; mv = queuedMoves.shift(); ackRTT = timeState.ticks - mv.timeState.ticks; maxMoves = ackRTT + 2; } - lastAckMoveId = m; + lastAckMoveId = m.id; return mv; } diff --git a/src/net/Net.hx b/src/net/Net.hx index 6ef35821..18262e71 100644 --- a/src/net/Net.hx +++ b/src/net/Net.hx @@ -79,7 +79,7 @@ class Net { public static var serverInfo:ServerInfo; public static var remoteServerInfo:RemoteServerInfo; - public static function hostServer(name:String, maxPlayers:Int, privateSlots:Int, privateServer:Bool) { + public static function hostServer(name:String, maxPlayers:Int, privateSlots:Int, privateServer:Bool, onHosted:() -> Void) { serverInfo = new ServerInfo(name, 1, maxPlayers, privateSlots, privateServer, Std.int(999999 * Math.random()), "Lobby", getPlatform()); MasterServerClient.connectToMasterServer(() -> { isHost = true; @@ -87,6 +87,7 @@ class Net { clientId = 0; isMP = true; MasterServerClient.instance.sendServerInfo(serverInfo); + onHosted(); }); } diff --git a/src/net/NetCommands.hx b/src/net/NetCommands.hx index 7be2f479..5ea8862e 100644 --- a/src/net/NetCommands.hx +++ b/src/net/NetCommands.hx @@ -13,8 +13,8 @@ class NetCommands { MultiplayerLevelSelectGui.setLevelFn(i); } - @:rpc(server) public static function playLevel() { - MultiplayerLevelSelectGui.playSelectedLevel(); + @:rpc(server) public static function playLevel(levelIndex:Int) { + MultiplayerLevelSelectGui.playSelectedLevel(levelIndex); } @:rpc(server) public static function setNetworkRNG(rng:Float) { @@ -43,7 +43,7 @@ class NetCommands { } } if (allReady && Net.lobbyHostReady) { - NetCommands.playLevel(); + NetCommands.playLevel(MultiplayerLevelSelectGui.currentSelectionStatic); } } } diff --git a/src/shaders/NoiseTileMaterial.hx b/src/shaders/NoiseTileMaterial.hx index fa3ff3c5..971d02db 100644 --- a/src/shaders/NoiseTileMaterial.hx +++ b/src/shaders/NoiseTileMaterial.hx @@ -27,9 +27,6 @@ class NoiseTileMaterial extends hxsl.Shader { }; var calculatedUV:Vec2; var pixelColor:Vec4; - var specColor:Vec3; - var specPower:Float; - var noiseUV:Vec2; @const var useAccurateNoise:Bool; @var var outLightVec:Vec4; @var var outPos:Vec3;