mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2025-10-30 08:11:25 +00:00
make client predict other marbles too
This commit is contained in:
parent
5ab1bb7a65
commit
1fef9a5bdc
6 changed files with 368 additions and 53 deletions
|
|
@ -21,6 +21,7 @@ class SignallingHandler extends WebSocketHandler {
|
||||||
case StrMessage(content):
|
case StrMessage(content):
|
||||||
var conts = Json.parse(content);
|
var conts = Json.parse(content);
|
||||||
if (conts.type == "connect") {
|
if (conts.type == "connect") {
|
||||||
|
trace('Connect received');
|
||||||
var other = clients.find(x -> x != this);
|
var other = clients.find(x -> x != this);
|
||||||
other.send(Json.stringify(conts.sdpObj));
|
other.send(Json.stringify(conts.sdpObj));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
121
src/Marble.hx
121
src/Marble.hx
|
|
@ -1,5 +1,7 @@
|
||||||
package src;
|
package src;
|
||||||
|
|
||||||
|
import net.MoveManager;
|
||||||
|
import net.MoveManager.NetMove;
|
||||||
import shaders.marble.CrystalMarb;
|
import shaders.marble.CrystalMarb;
|
||||||
import shaders.marble.ClassicMarb;
|
import shaders.marble.ClassicMarb;
|
||||||
import shapes.HelicopterImage;
|
import shapes.HelicopterImage;
|
||||||
|
|
@ -286,6 +288,8 @@ class Marble extends GameObject {
|
||||||
public var cubemapRenderer:CubemapRenderer;
|
public var cubemapRenderer:CubemapRenderer;
|
||||||
|
|
||||||
var connection:net.Net.ClientConnection;
|
var connection:net.Net.ClientConnection;
|
||||||
|
var moveMotionDir:Vector;
|
||||||
|
var isNetUpdate:Bool = false;
|
||||||
|
|
||||||
public function new() {
|
public function new() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -529,7 +533,7 @@ class Marble extends GameObject {
|
||||||
var motiondir = new Vector(0, -1, 0);
|
var motiondir = new Vector(0, -1, 0);
|
||||||
if (level.isReplayingMovement)
|
if (level.isReplayingMovement)
|
||||||
return level.currentInputMoves[1].marbleAxes;
|
return level.currentInputMoves[1].marbleAxes;
|
||||||
if (this.controllable) {
|
if (this.controllable && !this.isNetUpdate) {
|
||||||
motiondir.transform(Matrix.R(0, 0, camera.CameraYaw));
|
motiondir.transform(Matrix.R(0, 0, camera.CameraYaw));
|
||||||
motiondir.transform(level.newOrientationQuat.toMatrix());
|
motiondir.transform(level.newOrientationQuat.toMatrix());
|
||||||
var updir = this.level.currentUp;
|
var updir = this.level.currentUp;
|
||||||
|
|
@ -539,7 +543,11 @@ class Marble extends GameObject {
|
||||||
motiondir = updir.cross(sidedir);
|
motiondir = updir.cross(sidedir);
|
||||||
return [sidedir, motiondir, updir];
|
return [sidedir, motiondir, updir];
|
||||||
} else {
|
} else {
|
||||||
return [new Vector(1, 0, 0), motiondir, new Vector(0, 0, 1)];
|
if (moveMotionDir != null)
|
||||||
|
motiondir = moveMotionDir;
|
||||||
|
var updir = this.level.currentUp;
|
||||||
|
var sidedir = motiondir.cross(updir);
|
||||||
|
return [sidedir, motiondir, updir];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -568,7 +576,7 @@ class Marble extends GameObject {
|
||||||
for (contact in contacts) {
|
for (contact in contacts) {
|
||||||
if (contact.force != 0 && !forceObjects.contains(contact.otherObject)) {
|
if (contact.force != 0 && !forceObjects.contains(contact.otherObject)) {
|
||||||
if (contact.otherObject is RoundBumper) {
|
if (contact.otherObject is RoundBumper) {
|
||||||
if (!level.isReplayingMovement && !playedSounds.contains("data/sound/bumperding1.wav")) {
|
if (!level.isReplayingMovement && !playedSounds.contains("data/sound/bumperding1.wav") && !this.isNetUpdate) {
|
||||||
AudioManager.playSound(ResourceLoader.getResource("data/sound/bumperding1.wav", ResourceLoader.getAudio, this.soundResources));
|
AudioManager.playSound(ResourceLoader.getResource("data/sound/bumperding1.wav", ResourceLoader.getAudio, this.soundResources));
|
||||||
playedSounds.push("data/sound/bumperding1.wav");
|
playedSounds.push("data/sound/bumperding1.wav");
|
||||||
}
|
}
|
||||||
|
|
@ -808,7 +816,10 @@ class Marble extends GameObject {
|
||||||
}
|
}
|
||||||
if (sv < this._jumpImpulse) {
|
if (sv < this._jumpImpulse) {
|
||||||
this.velocity.load(this.velocity.add(bestContact.normal.multiply((this._jumpImpulse - sv))));
|
this.velocity.load(this.velocity.add(bestContact.normal.multiply((this._jumpImpulse - sv))));
|
||||||
if (!level.isReplayingMovement && !playedSounds.contains("data/sound/jump.wav")) {
|
if (!level.isReplayingMovement
|
||||||
|
&& !playedSounds.contains("data/sound/jump.wav")
|
||||||
|
&& !this.isNetUpdate
|
||||||
|
&& this.controllable) {
|
||||||
AudioManager.playSound(ResourceLoader.getResource("data/sound/jump.wav", ResourceLoader.getAudio, this.soundResources));
|
AudioManager.playSound(ResourceLoader.getResource("data/sound/jump.wav", ResourceLoader.getAudio, this.soundResources));
|
||||||
playedSounds.push("data/sound/jump.wav");
|
playedSounds.push("data/sound/jump.wav");
|
||||||
}
|
}
|
||||||
|
|
@ -888,7 +899,7 @@ class Marble extends GameObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
function bounceEmitter(speed:Float, normal:Vector) {
|
function bounceEmitter(speed:Float, normal:Vector) {
|
||||||
if (!this.controllable || level.isReplayingMovement)
|
if (!this.controllable || level.isReplayingMovement || this.isNetUpdate)
|
||||||
return;
|
return;
|
||||||
if (this.bounceEmitDelay == 0 && this._minBounceSpeed <= speed) {
|
if (this.bounceEmitDelay == 0 && this._minBounceSpeed <= speed) {
|
||||||
this.level.particleManager.createEmitter(bounceParticleOptions, this.bounceEmitterData,
|
this.level.particleManager.createEmitter(bounceParticleOptions, this.bounceEmitterData,
|
||||||
|
|
@ -923,7 +934,7 @@ class Marble extends GameObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
function playBoundSound(time:Float, contactVel:Float) {
|
function playBoundSound(time:Float, contactVel:Float) {
|
||||||
if (level.isReplayingMovement)
|
if (level.isReplayingMovement || this.isNetUpdate)
|
||||||
return;
|
return;
|
||||||
if (minVelocityBounceSoft <= contactVel) {
|
if (minVelocityBounceSoft <= contactVel) {
|
||||||
var hardBounceSpeed = minVelocityBounceHard;
|
var hardBounceSpeed = minVelocityBounceHard;
|
||||||
|
|
@ -1607,20 +1618,102 @@ class Marble extends GameObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MP Only Functions
|
// MP Only Functions
|
||||||
public function updateServer(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array<PathedInterior>) {
|
|
||||||
var move:Move = null;
|
public function packUpdate(move:NetMove) {
|
||||||
if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching && !this.level.isReplayingMovement) {
|
var b = new haxe.io.BytesOutput();
|
||||||
move = recordMove();
|
b.writeByte(NetPacketType.MarbleUpdate);
|
||||||
|
b.writeUInt16(connection != null ? connection.id : 0);
|
||||||
|
MoveManager.packMove(move, b);
|
||||||
|
b.writeUInt16(this.level.ticks); // So we can get the clients to do stuff about it
|
||||||
|
b.writeFloat(this.newPos.x);
|
||||||
|
b.writeFloat(this.newPos.y);
|
||||||
|
b.writeFloat(this.newPos.z);
|
||||||
|
b.writeFloat(this.velocity.x);
|
||||||
|
b.writeFloat(this.velocity.y);
|
||||||
|
b.writeFloat(this.velocity.z);
|
||||||
|
b.writeFloat(this.omega.x);
|
||||||
|
b.writeFloat(this.omega.y);
|
||||||
|
b.writeFloat(this.omega.z);
|
||||||
|
return b.getBytes();
|
||||||
}
|
}
|
||||||
if (!this.controllable && this.connection != null) {
|
|
||||||
move = new Move();
|
public function unpackUpdate(b:haxe.io.BytesInput) {
|
||||||
move.d = new Vector(0, 0);
|
// Assume packet header is already read
|
||||||
|
var serverMove = MoveManager.unpackMove(b);
|
||||||
|
if (Net.isClient)
|
||||||
|
Net.clientConnection.moveManager.acknowledgeMove(serverMove.id);
|
||||||
|
var serverTicks = b.readUInt16();
|
||||||
|
this.oldPos = this.newPos;
|
||||||
|
this.newPos = new Vector(b.readFloat(), b.readFloat(), b.readFloat());
|
||||||
|
this.collider.transform.setPosition(this.newPos);
|
||||||
|
this.velocity = new Vector(b.readFloat(), b.readFloat(), b.readFloat());
|
||||||
|
this.omega = new Vector(b.readFloat(), b.readFloat(), b.readFloat());
|
||||||
|
|
||||||
|
// Apply the moves we have queued
|
||||||
|
if (Net.isClient) {
|
||||||
|
this.isNetUpdate = true;
|
||||||
|
if (this.controllable) {
|
||||||
|
for (move in @:privateAccess Net.clientConnection.moveManager.queuedMoves) {
|
||||||
|
moveMotionDir = move.motionDir;
|
||||||
|
advancePhysics(move.timeState, move.move, this.level.collisionWorld, this.level.pathedInteriors);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var tickDiff = this.level.ticks - serverTicks;
|
||||||
|
if (tickDiff > 0) {
|
||||||
|
var timeState = this.level.timeState.clone();
|
||||||
|
timeState.dt = 0.032;
|
||||||
|
var m = serverMove.move;
|
||||||
|
moveMotionDir = serverMove.motionDir;
|
||||||
|
for (o in 0...tickDiff) {
|
||||||
|
advancePhysics(timeState, m, this.level.collisionWorld, this.level.pathedInteriors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isNetUpdate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateServer(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array<PathedInterior>, packets:Array<haxe.io.Bytes>) {
|
||||||
|
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.moveManager.recordMove(axis, timeState);
|
||||||
|
} else if (Net.isHost) {
|
||||||
|
var axis = getMarbleAxis()[1];
|
||||||
|
var innerMove = recordMove();
|
||||||
|
move = new NetMove(innerMove, axis, timeState, 65535);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var moveId = 65535;
|
||||||
|
if (!this.controllable && this.connection != null && Net.isHost) {
|
||||||
|
var nextMove = this.connection.moveManager.getNextMove();
|
||||||
|
if (nextMove == null) {
|
||||||
|
var axis = getMarbleAxis()[1];
|
||||||
|
var innerMove = new Move();
|
||||||
|
innerMove.d = new Vector(0, 0);
|
||||||
|
move = new NetMove(innerMove, axis, timeState, 65535);
|
||||||
|
} else {
|
||||||
|
move = nextMove;
|
||||||
|
moveMotionDir = nextMove.motionDir;
|
||||||
|
moveId = nextMove.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (move == null) {
|
||||||
|
var axis = getMarbleAxis()[1];
|
||||||
|
var innerMove = new Move();
|
||||||
|
innerMove.d = new Vector(0, 0);
|
||||||
|
move = new NetMove(innerMove, axis, timeState, 65535);
|
||||||
}
|
}
|
||||||
|
|
||||||
playedSounds = [];
|
playedSounds = [];
|
||||||
advancePhysics(timeState, move, collisionWorld, pathedInteriors);
|
advancePhysics(timeState, move.move, collisionWorld, pathedInteriors);
|
||||||
|
|
||||||
physicsAccumulator = 0;
|
physicsAccumulator = 0;
|
||||||
|
|
||||||
|
if (Net.isHost) {
|
||||||
|
packets.push(packUpdate(move));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateClient(timeState:TimeState, pathedInteriors:Array<PathedInterior>) {
|
public function updateClient(timeState:TimeState, pathedInteriors:Array<PathedInterior>) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package src;
|
package src;
|
||||||
|
|
||||||
|
import net.MoveManager;
|
||||||
import net.NetCommands;
|
import net.NetCommands;
|
||||||
import net.Net;
|
import net.Net;
|
||||||
import net.Net.ClientConnection;
|
import net.Net.ClientConnection;
|
||||||
|
|
@ -195,6 +196,9 @@ class MarbleWorld extends Scheduler {
|
||||||
|
|
||||||
public var startRealTime:Float = 0;
|
public var startRealTime:Float = 0;
|
||||||
public var multiplayerStarted:Bool = false;
|
public var multiplayerStarted:Bool = false;
|
||||||
|
public var ticks:Int = 0; // How many 32ms ticks have happened
|
||||||
|
|
||||||
|
var tickAccumulator:Float = 0.0;
|
||||||
|
|
||||||
var clientMarbles:Map<ClientConnection, Marble> = [];
|
var clientMarbles:Map<ClientConnection, Marble> = [];
|
||||||
|
|
||||||
|
|
@ -233,6 +237,10 @@ class MarbleWorld extends Scheduler {
|
||||||
this.rewindManager = new RewindManager(this);
|
this.rewindManager = new RewindManager(this);
|
||||||
this.inputRecorder = new InputRecorder(this);
|
this.inputRecorder = new InputRecorder(this);
|
||||||
this.isMultiplayer = multiplayer;
|
this.isMultiplayer = multiplayer;
|
||||||
|
if (this.isMultiplayer) {
|
||||||
|
isRecording = false;
|
||||||
|
isWatching = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the network RNG for hunt
|
// Set the network RNG for hunt
|
||||||
if (isMultiplayer && gameMode is modes.HuntMode && Net.isHost) {
|
if (isMultiplayer && gameMode is modes.HuntMode && Net.isHost) {
|
||||||
|
|
@ -1055,16 +1063,13 @@ class MarbleWorld extends Scheduler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Key.isPressed(Key.T)) {
|
|
||||||
rollback(0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
var realDt = dt;
|
var realDt = dt;
|
||||||
|
|
||||||
if ((Key.isDown(Settings.controlsSettings.rewind)
|
if ((Key.isDown(Settings.controlsSettings.rewind)
|
||||||
|| MarbleGame.instance.touchInput.rewindButton.pressed
|
|| MarbleGame.instance.touchInput.rewindButton.pressed
|
||||||
|| Gamepad.isDown(Settings.gamepadSettings.rewind))
|
|| Gamepad.isDown(Settings.gamepadSettings.rewind))
|
||||||
&& Settings.optionsSettings.rewindEnabled
|
&& Settings.optionsSettings.rewindEnabled
|
||||||
|
&& !this.isMultiplayer
|
||||||
&& !this.isWatching
|
&& !this.isWatching
|
||||||
&& this.finishTime == null) {
|
&& this.finishTime == null) {
|
||||||
this.rewinding = true;
|
this.rewinding = true;
|
||||||
|
|
@ -1072,7 +1077,8 @@ class MarbleWorld extends Scheduler {
|
||||||
if ((Key.isReleased(Settings.controlsSettings.rewind)
|
if ((Key.isReleased(Settings.controlsSettings.rewind)
|
||||||
|| !MarbleGame.instance.touchInput.rewindButton.pressed
|
|| !MarbleGame.instance.touchInput.rewindButton.pressed
|
||||||
|| Gamepad.isReleased(Settings.gamepadSettings.rewind))
|
|| Gamepad.isReleased(Settings.gamepadSettings.rewind))
|
||||||
&& this.rewinding) {
|
&& this.rewinding
|
||||||
|
&& !this.isMultiplayer) {
|
||||||
if (this.isRecording) {
|
if (this.isRecording) {
|
||||||
this.replay.spliceReplay(timeState.currentAttemptTime);
|
this.replay.spliceReplay(timeState.currentAttemptTime);
|
||||||
}
|
}
|
||||||
|
|
@ -1207,10 +1213,36 @@ class MarbleWorld extends Scheduler {
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfilerUI.measure("updateMarbles");
|
ProfilerUI.measure("updateMarbles");
|
||||||
|
if (this.isMultiplayer) {
|
||||||
|
tickAccumulator += timeState.dt;
|
||||||
|
while (tickAccumulator >= 0.032) {
|
||||||
|
var fixedDt = timeState.clone();
|
||||||
|
fixedDt.dt = 0.032;
|
||||||
|
tickAccumulator -= 0.032;
|
||||||
|
var packets = [];
|
||||||
|
marble.updateServer(fixedDt, collisionWorld, pathedInteriors, packets);
|
||||||
|
for (client => marble in clientMarbles) {
|
||||||
|
marble.updateServer(fixedDt, collisionWorld, pathedInteriors, packets);
|
||||||
|
}
|
||||||
|
if (Net.isHost) {
|
||||||
|
for (client => marble in clientMarbles) { // Oh no!
|
||||||
|
for (packet in packets) {
|
||||||
|
client.datachannel.sendBytes(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ticks++;
|
||||||
|
}
|
||||||
|
marble.updateClient(timeState, this.pathedInteriors);
|
||||||
|
for (client => marble in clientMarbles) {
|
||||||
|
marble.updateClient(timeState, this.pathedInteriors);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
marble.update(timeState, collisionWorld, this.pathedInteriors);
|
marble.update(timeState, collisionWorld, this.pathedInteriors);
|
||||||
for (client => marble in clientMarbles) {
|
for (client => marble in clientMarbles) {
|
||||||
marble.update(timeState, collisionWorld, this.pathedInteriors);
|
marble.update(timeState, collisionWorld, this.pathedInteriors);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_cubemapNeedsUpdate = true;
|
_cubemapNeedsUpdate = true;
|
||||||
Renderer.dirtyBuffers = true;
|
Renderer.dirtyBuffers = true;
|
||||||
if (this.rewinding) {
|
if (this.rewinding) {
|
||||||
|
|
@ -1243,7 +1275,7 @@ class MarbleWorld extends Scheduler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.rewinding && Settings.optionsSettings.rewindEnabled)
|
if (!this.rewinding && Settings.optionsSettings.rewindEnabled && !this.isMultiplayer)
|
||||||
this.rewindManager.recordFrame();
|
this.rewindManager.recordFrame();
|
||||||
|
|
||||||
if (!this.isReplayingMovement) {
|
if (!this.isReplayingMovement) {
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ class SphereCollisionEntity extends CollisionEntity {
|
||||||
contact.collider = this;
|
contact.collider = this;
|
||||||
contact.friction = 1;
|
contact.friction = 1;
|
||||||
contact.restitution = 1;
|
contact.restitution = 1;
|
||||||
contact.velocity = this.velocity;
|
contact.velocity = this.velocity.clone();
|
||||||
contact.otherObject = this.go;
|
contact.otherObject = this.go;
|
||||||
contact.point = position.add(normDist);
|
contact.point = position.add(normDist);
|
||||||
contact.normal = normDist.multiply(-1);
|
contact.normal = normDist.multiply(-1);
|
||||||
|
|
@ -102,9 +102,9 @@ class SphereCollisionEntity extends CollisionEntity {
|
||||||
othercontact.collider = collisionEntity;
|
othercontact.collider = collisionEntity;
|
||||||
othercontact.friction = 1;
|
othercontact.friction = 1;
|
||||||
othercontact.restitution = 1;
|
othercontact.restitution = 1;
|
||||||
othercontact.velocity = this.velocity;
|
othercontact.velocity = collisionEntity.velocity.clone();
|
||||||
othercontact.point = thispos.add(position).multiply(0.5);
|
othercontact.point = thispos.sub(normDist);
|
||||||
othercontact.normal = contact.point.sub(position).normalized();
|
othercontact.normal = normDist.clone();
|
||||||
othercontact.contactDistance = contact.point.distance(position);
|
othercontact.contactDistance = contact.point.distance(position);
|
||||||
othercontact.force = 0;
|
othercontact.force = 0;
|
||||||
othercontact.penetration = this.radius - (thispos.sub(othercontact.point).dot(othercontact.normal));
|
othercontact.penetration = this.radius - (thispos.sub(othercontact.point).dot(othercontact.normal));
|
||||||
|
|
|
||||||
157
src/net/MoveManager.hx
Normal file
157
src/net/MoveManager.hx
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
package net;
|
||||||
|
|
||||||
|
import src.TimeState;
|
||||||
|
import src.Console;
|
||||||
|
import net.Net.ClientConnection;
|
||||||
|
import net.Net.NetPacketType;
|
||||||
|
import src.MarbleWorld;
|
||||||
|
import src.Marble.Move;
|
||||||
|
import h3d.Vector;
|
||||||
|
import src.Gamepad;
|
||||||
|
import src.Settings;
|
||||||
|
import hxd.Key;
|
||||||
|
import src.MarbleGame;
|
||||||
|
import src.Util;
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
class NetMove {
|
||||||
|
var motionDir:Vector;
|
||||||
|
var move:Move;
|
||||||
|
var id:Int;
|
||||||
|
var timeState:TimeState;
|
||||||
|
|
||||||
|
public function new(move:Move, motionDir:Vector, timeState:TimeState, id:Int) {
|
||||||
|
this.move = move;
|
||||||
|
this.motionDir = motionDir;
|
||||||
|
this.id = id;
|
||||||
|
this.timeState = timeState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoveManager {
|
||||||
|
var connection:ClientConnection;
|
||||||
|
var queuedMoves:Array<NetMove>;
|
||||||
|
var nextMoveId:Int;
|
||||||
|
var lastMove:NetMove;
|
||||||
|
var lastAckMoveId:Int = -1;
|
||||||
|
|
||||||
|
static var maxMoves = 45; // Taken from Torque
|
||||||
|
|
||||||
|
public function new(connection:ClientConnection) {
|
||||||
|
queuedMoves = [];
|
||||||
|
nextMoveId = 0;
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function recordMove(motionDir:Vector, timeState:TimeState) {
|
||||||
|
if (queuedMoves.length >= maxMoves)
|
||||||
|
return queuedMoves[queuedMoves.length - 1];
|
||||||
|
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.touchInput.movementInput.pressed) {
|
||||||
|
move.d.y = -MarbleGame.instance.touchInput.movementInput.value.x;
|
||||||
|
move.d.x = MarbleGame.instance.touchInput.movementInput.value.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
var netMove = new NetMove(move, motionDir, timeState.clone(), nextMoveId++);
|
||||||
|
queuedMoves.push(netMove);
|
||||||
|
|
||||||
|
if (nextMoveId >= 65535) // 65535 is reserved for null move
|
||||||
|
nextMoveId = 0;
|
||||||
|
|
||||||
|
var b = new haxe.io.BytesOutput();
|
||||||
|
b.writeByte(NetPacketType.MarbleMove);
|
||||||
|
b.writeUInt16(Net.clientId);
|
||||||
|
|
||||||
|
Net.sendPacketToHost(packMove(netMove, b));
|
||||||
|
|
||||||
|
return netMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function packMove(m:NetMove, b:haxe.io.BytesOutput) {
|
||||||
|
b.writeUInt16(m.id);
|
||||||
|
b.writeFloat(m.move.d.x);
|
||||||
|
b.writeFloat(m.move.d.y);
|
||||||
|
var flags = 0;
|
||||||
|
if (m.move.jump)
|
||||||
|
flags |= 1;
|
||||||
|
if (m.move.powerup)
|
||||||
|
flags |= 2;
|
||||||
|
b.writeByte(flags);
|
||||||
|
b.writeFloat(m.motionDir.x);
|
||||||
|
b.writeFloat(m.motionDir.y);
|
||||||
|
b.writeFloat(m.motionDir.z);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unpackMove(b:haxe.io.BytesInput) {
|
||||||
|
var moveId = b.readUInt16();
|
||||||
|
var move = new Move();
|
||||||
|
move.d = new Vector();
|
||||||
|
move.d.x = b.readFloat();
|
||||||
|
move.d.y = b.readFloat();
|
||||||
|
var flags = b.readByte();
|
||||||
|
move.jump = (flags & 1) != 0;
|
||||||
|
move.powerup = (flags & 2) != 0;
|
||||||
|
var motionDir = new Vector();
|
||||||
|
motionDir.x = b.readFloat();
|
||||||
|
motionDir.y = b.readFloat();
|
||||||
|
motionDir.z = b.readFloat();
|
||||||
|
var netMove = new NetMove(move, motionDir, MarbleGame.instance.world.timeState.clone(), moveId);
|
||||||
|
return netMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function queueMove(m:NetMove) {
|
||||||
|
queuedMoves.push(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNextMove() {
|
||||||
|
if (queuedMoves.length == 0)
|
||||||
|
return lastMove;
|
||||||
|
else {
|
||||||
|
lastMove = queuedMoves[0];
|
||||||
|
queuedMoves.shift();
|
||||||
|
return lastMove;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function acknowledgeMove(m:Int) {
|
||||||
|
if (m == 65535 || m == -1)
|
||||||
|
return;
|
||||||
|
if (m <= lastAckMoveId)
|
||||||
|
return; // Already acked
|
||||||
|
if (queuedMoves.length == 0)
|
||||||
|
return;
|
||||||
|
while (m != queuedMoves[0].id) {
|
||||||
|
trace('Ignoring move ${queuedMoves[0].id}, need ${m}');
|
||||||
|
queuedMoves.shift();
|
||||||
|
}
|
||||||
|
if (m == queuedMoves[0].id)
|
||||||
|
queuedMoves.shift();
|
||||||
|
lastAckMoveId = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,8 @@ import datachannel.RTCDataChannel;
|
||||||
import hx.ws.WebSocket;
|
import hx.ws.WebSocket;
|
||||||
import src.Console;
|
import src.Console;
|
||||||
import net.NetCommands;
|
import net.NetCommands;
|
||||||
|
import src.MarbleGame;
|
||||||
|
import hx.ws.Types.MessageType;
|
||||||
|
|
||||||
enum abstract GameplayState(Int) from Int to Int {
|
enum abstract GameplayState(Int) from Int to Int {
|
||||||
var UNKNOWN;
|
var UNKNOWN;
|
||||||
|
|
@ -19,6 +21,8 @@ enum abstract NetPacketType(Int) from Int to Int {
|
||||||
var NetCommand;
|
var NetCommand;
|
||||||
var Ping;
|
var Ping;
|
||||||
var PingBack;
|
var PingBack;
|
||||||
|
var MarbleUpdate;
|
||||||
|
var MarbleMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:publicFields
|
@:publicFields
|
||||||
|
|
@ -27,6 +31,7 @@ class ClientConnection {
|
||||||
var socket:RTCPeerConnection;
|
var socket:RTCPeerConnection;
|
||||||
var datachannel:RTCDataChannel;
|
var datachannel:RTCDataChannel;
|
||||||
var state:GameplayState;
|
var state:GameplayState;
|
||||||
|
var moveManager:MoveManager;
|
||||||
var rtt:Float;
|
var rtt:Float;
|
||||||
var pingSendTime:Float;
|
var pingSendTime:Float;
|
||||||
var _rttRecords:Array<Float> = [];
|
var _rttRecords:Array<Float> = [];
|
||||||
|
|
@ -37,6 +42,7 @@ class ClientConnection {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.state = GameplayState.LOBBY;
|
this.state = GameplayState.LOBBY;
|
||||||
this.rtt = 0;
|
this.rtt = 0;
|
||||||
|
this.moveManager = new MoveManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ready() {
|
public function ready() {
|
||||||
|
|
@ -60,6 +66,7 @@ class Net {
|
||||||
public static var networkRNG:Float;
|
public static var networkRNG:Float;
|
||||||
public static var clients:Map<RTCPeerConnection, ClientConnection> = [];
|
public static var clients:Map<RTCPeerConnection, ClientConnection> = [];
|
||||||
public static var clientIdMap:Map<Int, ClientConnection> = [];
|
public static var clientIdMap:Map<Int, ClientConnection> = [];
|
||||||
|
public static var clientConnection:ClientConnection;
|
||||||
|
|
||||||
public static function hostServer() {
|
public static function hostServer() {
|
||||||
// host = new RTCPeerConnection(["stun.l.google.com:19302"], "0.0.0.0");
|
// host = new RTCPeerConnection(["stun.l.google.com:19302"], "0.0.0.0");
|
||||||
|
|
@ -76,9 +83,18 @@ class Net {
|
||||||
switch (m) {
|
switch (m) {
|
||||||
case StrMessage(content):
|
case StrMessage(content):
|
||||||
var conts = Json.parse(content);
|
var conts = Json.parse(content);
|
||||||
var peer = new RTCPeerConnection(["stun.l.google.com:19302"], "0.0.0.0");
|
var peer = new RTCPeerConnection(["stun:stun.l.google.com:19302"], "0.0.0.0");
|
||||||
peer.setRemoteDescription(conts.sdp, conts.type);
|
peer.setRemoteDescription(conts.sdp, conts.type);
|
||||||
|
addClient(peer);
|
||||||
|
|
||||||
|
case BytesMessage(content): {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isMP = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function addClient(peer:RTCPeerConnection) {
|
||||||
var candidates = [];
|
var candidates = [];
|
||||||
peer.onLocalCandidate = (c) -> {
|
peer.onLocalCandidate = (c) -> {
|
||||||
if (c != "")
|
if (c != "")
|
||||||
|
|
@ -92,25 +108,20 @@ class Net {
|
||||||
type: "connect",
|
type: "connect",
|
||||||
sdpObj: {
|
sdpObj: {
|
||||||
sdp: sdpObj,
|
sdp: sdpObj,
|
||||||
type: "offer"
|
type: "answer"
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
peer.onDataChannel = (dc) -> {
|
peer.onDataChannel = (dc:datachannel.RTCDataChannel) -> {
|
||||||
onClientConnect(peer, dc);
|
onClientConnect(peer, dc);
|
||||||
};
|
|
||||||
case _: {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isMP = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function joinServer(connectedCb:() -> Void) {
|
public static function joinServer(connectedCb:() -> Void) {
|
||||||
masterWs = new WebSocket("ws://localhost:8080");
|
masterWs = new WebSocket("ws://localhost:8080");
|
||||||
|
|
||||||
client = new RTCPeerConnection(["stun.l.google.com:19302"], "0.0.0.0");
|
client = new RTCPeerConnection(["stun:stun.l.google.com:19302"], "0.0.0.0");
|
||||||
var candidates = [];
|
var candidates = [];
|
||||||
|
|
||||||
client.onLocalCandidate = (c) -> {
|
client.onLocalCandidate = (c) -> {
|
||||||
|
|
@ -119,6 +130,7 @@ class Net {
|
||||||
}
|
}
|
||||||
client.onGatheringStateChange = (s) -> {
|
client.onGatheringStateChange = (s) -> {
|
||||||
if (s == RTC_GATHERING_COMPLETE) {
|
if (s == RTC_GATHERING_COMPLETE) {
|
||||||
|
Console.log("Local Description Set!");
|
||||||
var sdpObj = StringTools.trim(client.localDescription);
|
var sdpObj = StringTools.trim(client.localDescription);
|
||||||
sdpObj = sdpObj + '\r\n' + candidates.join('\r\n');
|
sdpObj = sdpObj + '\r\n' + candidates.join('\r\n');
|
||||||
masterWs.send(Json.stringify({
|
masterWs.send(Json.stringify({
|
||||||
|
|
@ -134,6 +146,7 @@ class Net {
|
||||||
masterWs.onmessage = (m) -> {
|
masterWs.onmessage = (m) -> {
|
||||||
switch (m) {
|
switch (m) {
|
||||||
case StrMessage(content):
|
case StrMessage(content):
|
||||||
|
Console.log("Remote Description Received!");
|
||||||
var conts = Json.parse(content);
|
var conts = Json.parse(content);
|
||||||
client.setRemoteDescription(conts.sdp, conts.type);
|
client.setRemoteDescription(conts.sdp, conts.type);
|
||||||
case _: {}
|
case _: {}
|
||||||
|
|
@ -142,8 +155,10 @@ class Net {
|
||||||
|
|
||||||
clientDatachannel = client.createDatachannel("mp");
|
clientDatachannel = client.createDatachannel("mp");
|
||||||
clientDatachannel.onOpen = (n) -> {
|
clientDatachannel.onOpen = (n) -> {
|
||||||
|
Console.log("Successfully connected!");
|
||||||
clients.set(client, new ClientConnection(0, client, clientDatachannel)); // host is always 0
|
clients.set(client, new ClientConnection(0, client, clientDatachannel)); // host is always 0
|
||||||
clientIdMap[0] = clients[client];
|
clientIdMap[0] = clients[client];
|
||||||
|
clientConnection = clients[client];
|
||||||
onConnectedToServer();
|
onConnectedToServer();
|
||||||
haxe.Timer.delay(() -> connectedCb(), 1500); // 1.5 second delay to do the RTT calculation
|
haxe.Timer.delay(() -> connectedCb(), 1500); // 1.5 second delay to do the RTT calculation
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +187,7 @@ class Net {
|
||||||
var b = haxe.io.Bytes.alloc(2);
|
var b = haxe.io.Bytes.alloc(2);
|
||||||
b.set(0, Ping);
|
b.set(0, Ping);
|
||||||
b.set(1, 3); // Count
|
b.set(1, 3); // Count
|
||||||
clients[c].pingSendTime = Sys.time();
|
clients[c].pingSendTime = Console.time();
|
||||||
dc.sendBytes(b);
|
dc.sendBytes(b);
|
||||||
Console.log("Sending ping packet!");
|
Console.log("Sending ping packet!");
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +198,7 @@ class Net {
|
||||||
var b = haxe.io.Bytes.alloc(2);
|
var b = haxe.io.Bytes.alloc(2);
|
||||||
b.set(0, Ping);
|
b.set(0, Ping);
|
||||||
b.set(1, 3); // Count
|
b.set(1, 3); // Count
|
||||||
clients[client].pingSendTime = Sys.time();
|
clients[client].pingSendTime = Console.time();
|
||||||
clientDatachannel.sendBytes(b);
|
clientDatachannel.sendBytes(b);
|
||||||
Console.log("Sending ping packet!");
|
Console.log("Sending ping packet!");
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +225,7 @@ class Net {
|
||||||
var pingLeft = input.readByte();
|
var pingLeft = input.readByte();
|
||||||
Console.log("Got pingback packet!");
|
Console.log("Got pingback packet!");
|
||||||
var conn = clients[c];
|
var conn = clients[c];
|
||||||
var now = Sys.time();
|
var now = Console.time();
|
||||||
conn._rttRecords.push((now - conn.pingSendTime));
|
conn._rttRecords.push((now - conn.pingSendTime));
|
||||||
if (pingLeft > 0) {
|
if (pingLeft > 0) {
|
||||||
conn.pingSendTime = now;
|
conn.pingSendTime = now;
|
||||||
|
|
@ -225,6 +240,23 @@ class Net {
|
||||||
Console.log('Got RTT ${conn.rtt} for client ${conn.id}');
|
Console.log('Got RTT ${conn.rtt} for client ${conn.id}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case MarbleUpdate:
|
||||||
|
var marbleClientId = input.readUInt16();
|
||||||
|
if (marbleClientId == clientId) {
|
||||||
|
if (MarbleGame.instance.world != null)
|
||||||
|
MarbleGame.instance.world.marble.unpackUpdate(input);
|
||||||
|
} else {
|
||||||
|
var cc = clientIdMap[marbleClientId];
|
||||||
|
if (MarbleGame.instance.world != null)
|
||||||
|
@:privateAccess MarbleGame.instance.world.clientMarbles[cc].unpackUpdate(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MarbleMove:
|
||||||
|
var marbleClientId = input.readUInt16();
|
||||||
|
var cc = clientIdMap[marbleClientId];
|
||||||
|
var m = MoveManager.unpackMove(input);
|
||||||
|
cc.moveManager.queueMove(m);
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
trace("unknown command: " + packetType);
|
trace("unknown command: " + packetType);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue