more net ui and netcode

This commit is contained in:
RandomityGuy 2024-04-09 23:35:20 +05:30
parent cabc437ca0
commit 038e0ed16e
12 changed files with 144 additions and 58 deletions

View file

@ -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<CollisionInfo> = [];
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<PathedInterior>) {
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<PathedInterior>) {
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;

View file

@ -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!

View file

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

View file

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

View file

@ -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), () -> {}));

View file

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

View file

@ -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() {

View file

@ -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() {

View file

@ -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;
}

View file

@ -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();
});
}

View file

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

View file

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