mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2025-10-30 08:11:25 +00:00
begin netcode
This commit is contained in:
parent
73a7366307
commit
027f65f85b
19 changed files with 1314 additions and 161 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
-cp src
|
-cp src
|
||||||
-lib heaps
|
-lib heaps
|
||||||
-lib hlsdl
|
-lib hlsdl
|
||||||
|
-lib hxWebSockets
|
||||||
|
-lib datachannel
|
||||||
-hl marblegame.hl
|
-hl marblegame.hl
|
||||||
-D windowSize=1280x720
|
-D windowSize=1280x720
|
||||||
-D keep-inline-positions
|
-D keep-inline-positions
|
||||||
|
|
|
||||||
38
server/Signalling.hx
Normal file
38
server/Signalling.hx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import haxe.Json;
|
||||||
|
import hx.ws.SocketImpl;
|
||||||
|
import hx.ws.WebSocketHandler;
|
||||||
|
import hx.ws.WebSocketServer;
|
||||||
|
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
class SignallingHandler extends WebSocketHandler {
|
||||||
|
static var clients:Array<SignallingHandler> = [];
|
||||||
|
|
||||||
|
public function new(s:SocketImpl) {
|
||||||
|
super(s);
|
||||||
|
onopen = () -> {
|
||||||
|
clients.push(this);
|
||||||
|
}
|
||||||
|
onclose = () -> {
|
||||||
|
clients.remove(this);
|
||||||
|
}
|
||||||
|
onmessage = (m) -> {
|
||||||
|
switch (m) {
|
||||||
|
case StrMessage(content):
|
||||||
|
var conts = Json.parse(content);
|
||||||
|
if (conts.type == "connect") {
|
||||||
|
var other = clients.find(x -> x != this);
|
||||||
|
other.send(Json.stringify(conts.sdpObj));
|
||||||
|
}
|
||||||
|
case _: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Signalling {
|
||||||
|
static function main() {
|
||||||
|
var ws = new WebSocketServer<SignallingHandler>("0.0.0.0", 8080, 2);
|
||||||
|
ws.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
5
server/build_signalling.hxml
Normal file
5
server/build_signalling.hxml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
--library hxWebSockets
|
||||||
|
--library datachannel
|
||||||
|
--main Signalling
|
||||||
|
-cp .
|
||||||
|
--hl bin/signalling.hl
|
||||||
|
|
@ -184,6 +184,9 @@ class Console {
|
||||||
log('Allocation Count: ${gc.allocationCount}');
|
log('Allocation Count: ${gc.allocationCount}');
|
||||||
log('Memory usage: ${gc.currentMemory}');
|
log('Memory usage: ${gc.currentMemory}');
|
||||||
#end
|
#end
|
||||||
|
} else if (cmdType == 'rollback') {
|
||||||
|
var t = Std.parseFloat(cmdSplit[1]);
|
||||||
|
MarbleGame.instance.world.rollback(t);
|
||||||
} else {
|
} else {
|
||||||
error("Unknown command");
|
error("Unknown command");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -298,94 +298,4 @@ class DynamicPolygon extends MeshPrimitive {
|
||||||
else
|
else
|
||||||
engine.renderMultiBuffers(bufs, engine.mem.triIndexes, 0, triCount());
|
engine.renderMultiBuffers(bufs, engine.mem.triIndexes, 0, triCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
#if hxbit
|
|
||||||
override function customSerialize(ctx:hxbit.Serializer) {
|
|
||||||
ctx.addInt(points.length);
|
|
||||||
for (p in points) {
|
|
||||||
ctx.addDouble(p.x);
|
|
||||||
ctx.addDouble(p.y);
|
|
||||||
ctx.addDouble(p.z);
|
|
||||||
}
|
|
||||||
if (normals == null)
|
|
||||||
ctx.addInt(0);
|
|
||||||
else {
|
|
||||||
ctx.addInt(normals.length);
|
|
||||||
for (p in normals) {
|
|
||||||
ctx.addDouble(p.x);
|
|
||||||
ctx.addDouble(p.y);
|
|
||||||
ctx.addDouble(p.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tangents == null)
|
|
||||||
ctx.addInt(0);
|
|
||||||
else {
|
|
||||||
ctx.addInt(tangents.length);
|
|
||||||
for (p in tangents) {
|
|
||||||
ctx.addDouble(p.x);
|
|
||||||
ctx.addDouble(p.y);
|
|
||||||
ctx.addDouble(p.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (uvs == null)
|
|
||||||
ctx.addInt(0);
|
|
||||||
else {
|
|
||||||
ctx.addInt(uvs.length);
|
|
||||||
for (uv in uvs) {
|
|
||||||
ctx.addDouble(uv.u);
|
|
||||||
ctx.addDouble(uv.v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idx == null)
|
|
||||||
ctx.addInt(0);
|
|
||||||
else {
|
|
||||||
ctx.addInt(idx.length);
|
|
||||||
for (i in idx)
|
|
||||||
ctx.addInt(i);
|
|
||||||
}
|
|
||||||
if (colors == null)
|
|
||||||
ctx.addInt(0);
|
|
||||||
else {
|
|
||||||
ctx.addInt(colors.length);
|
|
||||||
for (c in colors) {
|
|
||||||
ctx.addDouble(c.x);
|
|
||||||
ctx.addDouble(c.y);
|
|
||||||
ctx.addDouble(c.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override function customUnserialize(ctx:hxbit.Serializer) {
|
|
||||||
points = [
|
|
||||||
for (i in 0...ctx.getInt())
|
|
||||||
new h3d.col.Point(ctx.getDouble(), ctx.getDouble(), ctx.getDouble())
|
|
||||||
];
|
|
||||||
normals = [
|
|
||||||
for (i in 0...ctx.getInt())
|
|
||||||
new h3d.col.Point(ctx.getDouble(), ctx.getDouble(), ctx.getDouble())
|
|
||||||
];
|
|
||||||
tangents = [
|
|
||||||
for (i in 0...ctx.getInt())
|
|
||||||
new h3d.col.Point(ctx.getDouble(), ctx.getDouble(), ctx.getDouble())
|
|
||||||
];
|
|
||||||
uvs = [for (i in 0...ctx.getInt()) new UV(ctx.getDouble(), ctx.getDouble())];
|
|
||||||
if (normals.length == 0)
|
|
||||||
normals = null;
|
|
||||||
if (uvs.length == 0)
|
|
||||||
uvs = null;
|
|
||||||
var nindex = ctx.getInt();
|
|
||||||
if (nindex > 0) {
|
|
||||||
idx = new hxd.IndexBuffer();
|
|
||||||
idx.grow(nindex);
|
|
||||||
for (i in 0...nindex)
|
|
||||||
idx[i] = ctx.getInt();
|
|
||||||
}
|
|
||||||
colors = [
|
|
||||||
for (i in 0...ctx.getInt())
|
|
||||||
new h3d.col.Point(ctx.getDouble(), ctx.getDouble(), ctx.getDouble())
|
|
||||||
];
|
|
||||||
if (colors.length == 0)
|
|
||||||
colors = null;
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package;
|
package;
|
||||||
|
|
||||||
|
import datachannel.RTC;
|
||||||
import gui.VersionGui;
|
import gui.VersionGui;
|
||||||
import gui.PresentsGui;
|
import gui.PresentsGui;
|
||||||
import src.Debug;
|
import src.Debug;
|
||||||
|
|
@ -81,6 +82,7 @@ class Main extends hxd.App {
|
||||||
#end
|
#end
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
|
RTC.init();
|
||||||
Http.init();
|
Http.init();
|
||||||
haxe.MainLoop.add(() -> Http.loop());
|
haxe.MainLoop.add(() -> Http.loop());
|
||||||
Settings.init();
|
Settings.init();
|
||||||
|
|
@ -143,6 +145,7 @@ class Main extends hxd.App {
|
||||||
// marbleGame.update(1 / 60);
|
// marbleGame.update(1 / 60);
|
||||||
// timeAccumulator -= 1 / 60;
|
// timeAccumulator -= 1 / 60;
|
||||||
// }
|
// }
|
||||||
|
RTC.processEvents();
|
||||||
marbleGame.update(dt);
|
marbleGame.update(dt);
|
||||||
// } catch (e) {
|
// } catch (e) {
|
||||||
// Console.error(e.message);
|
// Console.error(e.message);
|
||||||
|
|
|
||||||
184
src/Marble.hx
184
src/Marble.hx
|
|
@ -66,6 +66,7 @@ import src.ResourceLoaderWorker;
|
||||||
import src.InteriorObject;
|
import src.InteriorObject;
|
||||||
import src.Console;
|
import src.Console;
|
||||||
import src.Gamepad;
|
import src.Gamepad;
|
||||||
|
import net.Net;
|
||||||
|
|
||||||
class Move {
|
class Move {
|
||||||
public var d:Vector;
|
public var d:Vector;
|
||||||
|
|
@ -284,12 +285,15 @@ class Marble extends GameObject {
|
||||||
|
|
||||||
public var cubemapRenderer:CubemapRenderer;
|
public var cubemapRenderer:CubemapRenderer;
|
||||||
|
|
||||||
|
var connection:net.Net.ClientConnection;
|
||||||
|
|
||||||
public function new() {
|
public function new() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.velocity = new Vector();
|
this.velocity = new Vector();
|
||||||
this.omega = new Vector();
|
this.omega = new Vector();
|
||||||
this.camera = new CameraController(cast this);
|
this.camera = new CameraController(cast this);
|
||||||
|
this.isCollideable = true;
|
||||||
|
|
||||||
this.bounceEmitterData = new ParticleData();
|
this.bounceEmitterData = new ParticleData();
|
||||||
this.bounceEmitterData.identifier = "MarbleBounceParticle";
|
this.bounceEmitterData.identifier = "MarbleBounceParticle";
|
||||||
|
|
@ -318,8 +322,9 @@ class Marble extends GameObject {
|
||||||
this.helicopterSound.pause = true;
|
this.helicopterSound.pause = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function init(level:MarbleWorld, onFinish:Void->Void) {
|
public function init(level:MarbleWorld, connection:ClientConnection, onFinish:Void->Void) {
|
||||||
this.level = level;
|
this.level = level;
|
||||||
|
this.connection = connection;
|
||||||
if (this.level != null)
|
if (this.level != null)
|
||||||
this.collisionWorld = this.level.collisionWorld;
|
this.collisionWorld = this.level.collisionWorld;
|
||||||
|
|
||||||
|
|
@ -522,6 +527,8 @@ class Marble extends GameObject {
|
||||||
|
|
||||||
public function getMarbleAxis() {
|
public function getMarbleAxis() {
|
||||||
var motiondir = new Vector(0, -1, 0);
|
var motiondir = new Vector(0, -1, 0);
|
||||||
|
if (level.isReplayingMovement)
|
||||||
|
return level.currentInputMoves[1].marbleAxes;
|
||||||
if (this.controllable) {
|
if (this.controllable) {
|
||||||
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());
|
||||||
|
|
@ -561,7 +568,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 (!playedSounds.contains("data/sound/bumperding1.wav")) {
|
if (!level.isReplayingMovement && !playedSounds.contains("data/sound/bumperding1.wav")) {
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|
@ -605,6 +612,8 @@ class Marble extends GameObject {
|
||||||
var R = currentGravityDir.multiply(-this._radius);
|
var R = currentGravityDir.multiply(-this._radius);
|
||||||
var rollVelocity = this.omega.cross(R);
|
var rollVelocity = this.omega.cross(R);
|
||||||
var axes = this.getMarbleAxis();
|
var axes = this.getMarbleAxis();
|
||||||
|
if (!level.isReplayingMovement)
|
||||||
|
level.inputRecorder.recordAxis(axes);
|
||||||
var sideDir = axes[0];
|
var sideDir = axes[0];
|
||||||
var motionDir = axes[1];
|
var motionDir = axes[1];
|
||||||
var upDir = axes[2];
|
var upDir = axes[2];
|
||||||
|
|
@ -799,7 +808,7 @@ 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 (!playedSounds.contains("data/sound/jump.wav")) {
|
if (!level.isReplayingMovement && !playedSounds.contains("data/sound/jump.wav")) {
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|
@ -879,7 +888,7 @@ class Marble extends GameObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
function bounceEmitter(speed:Float, normal:Vector) {
|
function bounceEmitter(speed:Float, normal:Vector) {
|
||||||
if (!this.controllable)
|
if (!this.controllable || level.isReplayingMovement)
|
||||||
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,
|
||||||
|
|
@ -914,6 +923,8 @@ class Marble extends GameObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
function playBoundSound(time:Float, contactVel:Float) {
|
function playBoundSound(time:Float, contactVel:Float) {
|
||||||
|
if (level.isReplayingMovement)
|
||||||
|
return;
|
||||||
if (minVelocityBounceSoft <= contactVel) {
|
if (minVelocityBounceSoft <= contactVel) {
|
||||||
var hardBounceSpeed = minVelocityBounceHard;
|
var hardBounceSpeed = minVelocityBounceHard;
|
||||||
var bounceSoundNum = Math.floor(Math.random() * 4);
|
var bounceSoundNum = Math.floor(Math.random() * 4);
|
||||||
|
|
@ -943,6 +954,8 @@ class Marble extends GameObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRollSound(time:TimeState, contactPct:Float, slipAmount:Float) {
|
function updateRollSound(time:TimeState, contactPct:Float, slipAmount:Float) {
|
||||||
|
if (level.isReplayingMovement)
|
||||||
|
return;
|
||||||
var rSpat = rollSound.getEffect(Spatialization);
|
var rSpat = rollSound.getEffect(Spatialization);
|
||||||
rSpat.position = this.collider.transform.getPosition();
|
rSpat.position = this.collider.transform.getPosition();
|
||||||
|
|
||||||
|
|
@ -1542,6 +1555,7 @@ class Marble extends GameObject {
|
||||||
// this.setPosition(newPos.x, newPos.y, newPos.z);
|
// this.setPosition(newPos.x, newPos.y, newPos.z);
|
||||||
|
|
||||||
this.collider.setTransform(totMatrix);
|
this.collider.setTransform(totMatrix);
|
||||||
|
this.collisionWorld.updateTransform(this.collider);
|
||||||
this.collider.velocity = this.velocity;
|
this.collider.velocity = this.velocity;
|
||||||
|
|
||||||
if (this.heldPowerup != null && m.powerup && !this.level.outOfBounds) {
|
if (this.heldPowerup != null && m.powerup && !this.level.outOfBounds) {
|
||||||
|
|
@ -1592,41 +1606,139 @@ class Marble extends GameObject {
|
||||||
this.updateRollSound(timeState, contactTime / timeState.dt, this._slipAmount);
|
this.updateRollSound(timeState, contactTime / timeState.dt, this._slipAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array<PathedInterior>) {
|
// MP Only Functions
|
||||||
var move = new Move();
|
public function updateServer(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array<PathedInterior>) {
|
||||||
move.d = new Vector();
|
var move:Move = null;
|
||||||
if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching) {
|
if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching && !this.level.isReplayingMovement) {
|
||||||
move.d.x = Gamepad.getAxis(Settings.gamepadSettings.moveYAxis);
|
move = recordMove();
|
||||||
move.d.y = -Gamepad.getAxis(Settings.gamepadSettings.moveXAxis);
|
}
|
||||||
if (Key.isDown(Settings.controlsSettings.forward)) {
|
if (!this.controllable && this.connection != null) {
|
||||||
move.d.x -= 1;
|
move = new Move();
|
||||||
|
move.d = new Vector(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
playedSounds = [];
|
||||||
|
advancePhysics(timeState, move, collisionWorld, pathedInteriors);
|
||||||
|
|
||||||
|
physicsAccumulator = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateClient(timeState:TimeState, pathedInteriors:Array<PathedInterior>) {
|
||||||
|
if (oldPos != null && newPos != null) {
|
||||||
|
var deltaT = physicsAccumulator / 0.032;
|
||||||
|
var renderPos = Util.lerpThreeVectors(this.oldPos, this.newPos, deltaT);
|
||||||
|
this.setPosition(renderPos.x, renderPos.y, renderPos.z);
|
||||||
|
|
||||||
|
var rot = this.prevRot;
|
||||||
|
var quat = new Quat();
|
||||||
|
quat.initRotation(omega.x * physicsAccumulator, omega.y * physicsAccumulator, omega.z * physicsAccumulator);
|
||||||
|
quat.multiply(quat, rot);
|
||||||
|
this.setRotationQuat(quat);
|
||||||
|
|
||||||
|
var adt = timeState.clone();
|
||||||
|
adt.dt = physicsAccumulator;
|
||||||
|
for (pi in pathedInteriors) {
|
||||||
|
pi.update(adt);
|
||||||
}
|
}
|
||||||
if (Key.isDown(Settings.controlsSettings.backward)) {
|
}
|
||||||
move.d.x += 1;
|
physicsAccumulator += timeState.dt;
|
||||||
}
|
|
||||||
if (Key.isDown(Settings.controlsSettings.left)) {
|
if (this.controllable && this.level != null && !this.level.rewinding) {
|
||||||
move.d.y += 1;
|
// this.camera.startCenterCamera();
|
||||||
}
|
this.camera.update(timeState.currentAttemptTime, timeState.dt);
|
||||||
if (Key.isDown(Settings.controlsSettings.right)) {
|
}
|
||||||
move.d.y -= 1;
|
|
||||||
}
|
updatePowerupStates(timeState.currentAttemptTime, timeState.dt);
|
||||||
if (Key.isDown(Settings.controlsSettings.jump)
|
|
||||||
|| MarbleGame.instance.touchInput.jumpButton.pressed
|
var s = this._renderScale * this._renderScale;
|
||||||
|| Gamepad.isDown(Settings.gamepadSettings.jump)) {
|
if (s <= this._marbleScale * this._marbleScale)
|
||||||
move.jump = true;
|
s = 0.1;
|
||||||
}
|
else
|
||||||
if ((!Util.isTouchDevice() && Key.isDown(Settings.controlsSettings.powerup))
|
s = 0.4;
|
||||||
|| (Util.isTouchDevice() && MarbleGame.instance.touchInput.powerupButton.pressed)
|
|
||||||
|| Gamepad.isDown(Settings.gamepadSettings.powerup)) {
|
s = timeState.dt / s * 2.302585124969482;
|
||||||
move.powerup = true;
|
s = 1.0 / (s * (s * 0.2349999994039536 * s) + s + 1.0 + 0.4799999892711639 * s * s);
|
||||||
}
|
this._renderScale *= s;
|
||||||
if (MarbleGame.instance.touchInput.movementInput.pressed) {
|
s = 1 - s;
|
||||||
move.d.y = -MarbleGame.instance.touchInput.movementInput.value.x;
|
this._renderScale += s * this._marbleScale;
|
||||||
move.d.x = MarbleGame.instance.touchInput.movementInput.value.y;
|
var marbledts = cast(this.getChildAt(0), DtsObject);
|
||||||
|
marbledts.setScale(this._renderScale);
|
||||||
|
|
||||||
|
if (this._radius != 0.675 && timeState.currentAttemptTime - this.megaMarbleEnableTime < 10) {
|
||||||
|
this._prevRadius = this._radius;
|
||||||
|
this._radius = 0.675;
|
||||||
|
this.collider.radius = 0.675;
|
||||||
|
this._marbleScale *= 2.25;
|
||||||
|
var boost = this.level.currentUp.multiply(5);
|
||||||
|
this.velocity = this.velocity.add(boost);
|
||||||
|
} else if (timeState.currentAttemptTime - this.megaMarbleEnableTime > 10) {
|
||||||
|
if (this._radius != this._prevRadius) {
|
||||||
|
this._radius = this._prevRadius;
|
||||||
|
this.collider.radius = this._radius;
|
||||||
|
this._marbleScale = this._defaultScale;
|
||||||
|
AudioManager.playSound(ResourceLoader.getResource("data/sound/MegaShrink.wav", ResourceLoader.getAudio, this.soundResources), null, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateFinishAnimation(timeState.dt);
|
||||||
|
if (this.mode == Finish) {
|
||||||
|
this.setPosition(this.finishAnimPosition.x, this.finishAnimPosition.y, this.finishAnimPosition.z);
|
||||||
|
updatePowerupStates(timeState.currentAttemptTime, timeState.dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.trailEmitter();
|
||||||
|
if (bounceEmitDelay > 0)
|
||||||
|
bounceEmitDelay -= timeState.dt;
|
||||||
|
if (bounceEmitDelay < 0)
|
||||||
|
bounceEmitDelay = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function recordMove() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return move;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SP only function
|
||||||
|
public function update(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array<PathedInterior>) {
|
||||||
|
var move:Move = null;
|
||||||
|
if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching && !this.level.isReplayingMovement) {
|
||||||
|
move = recordMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level.isReplayingMovement)
|
||||||
|
move = level.currentInputMoves[1].move;
|
||||||
|
|
||||||
if (this.controllable && this.level.isWatching) {
|
if (this.controllable && this.level.isWatching) {
|
||||||
|
move = new Move();
|
||||||
if (this.level.replay.currentPlaybackFrame.marbleStateFlags.has(Jumped))
|
if (this.level.replay.currentPlaybackFrame.marbleStateFlags.has(Jumped))
|
||||||
move.jump = true;
|
move.jump = true;
|
||||||
if (this.level.replay.currentPlaybackFrame.marbleStateFlags.has(UsedPowerup))
|
if (this.level.replay.currentPlaybackFrame.marbleStateFlags.has(UsedPowerup))
|
||||||
|
|
@ -1638,6 +1750,10 @@ class Marble extends GameObject {
|
||||||
this.level.replay.recordMarbleInput(move.d.x, move.d.y);
|
this.level.replay.recordMarbleInput(move.d.x, move.d.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!this.controllable && this.connection != null) {
|
||||||
|
move = new Move();
|
||||||
|
move.d = new Vector(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
physicsAccumulator += timeState.dt;
|
physicsAccumulator += timeState.dt;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -318,14 +318,14 @@ class MarbleGame {
|
||||||
Settings.save();
|
Settings.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function playMission(mission:Mission) {
|
public function playMission(mission:Mission, multiplayer:Bool = false) {
|
||||||
canvas.clearContent();
|
canvas.clearContent();
|
||||||
destroyPreviewWorld();
|
destroyPreviewWorld();
|
||||||
if (world != null) {
|
if (world != null) {
|
||||||
world.dispose();
|
world.dispose();
|
||||||
}
|
}
|
||||||
Analytics.trackLevelPlay(mission.title, mission.path);
|
Analytics.trackLevelPlay(mission.title, mission.path);
|
||||||
world = new MarbleWorld(scene, scene2d, mission, toRecord);
|
world = new MarbleWorld(scene, scene2d, mission, toRecord, multiplayer);
|
||||||
world.init();
|
world.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
package src;
|
package src;
|
||||||
|
|
||||||
|
import net.NetCommands;
|
||||||
|
import net.Net;
|
||||||
|
import net.Net.ClientConnection;
|
||||||
|
import rewind.InputRecorder;
|
||||||
import gui.AchievementsGui;
|
import gui.AchievementsGui;
|
||||||
import src.Radar;
|
import src.Radar;
|
||||||
import gui.LevelSelectGui;
|
import gui.LevelSelectGui;
|
||||||
|
|
@ -106,7 +110,6 @@ class MarbleWorld extends Scheduler {
|
||||||
|
|
||||||
public var interiors:Array<InteriorObject> = [];
|
public var interiors:Array<InteriorObject> = [];
|
||||||
public var pathedInteriors:Array<PathedInterior> = [];
|
public var pathedInteriors:Array<PathedInterior> = [];
|
||||||
public var marbles:Array<Marble> = [];
|
|
||||||
public var dtsObjects:Array<DtsObject> = [];
|
public var dtsObjects:Array<DtsObject> = [];
|
||||||
public var forceObjects:Array<ForceObject> = [];
|
public var forceObjects:Array<ForceObject> = [];
|
||||||
public var triggers:Array<Trigger> = [];
|
public var triggers:Array<Trigger> = [];
|
||||||
|
|
@ -184,6 +187,18 @@ class MarbleWorld extends Scheduler {
|
||||||
public var rewindManager:RewindManager;
|
public var rewindManager:RewindManager;
|
||||||
public var rewinding:Bool = false;
|
public var rewinding:Bool = false;
|
||||||
|
|
||||||
|
public var inputRecorder:InputRecorder;
|
||||||
|
public var isReplayingMovement:Bool = false;
|
||||||
|
public var currentInputMoves:Array<InputRecorderFrame>;
|
||||||
|
|
||||||
|
// Multiplayer
|
||||||
|
public var isMultiplayer:Bool;
|
||||||
|
|
||||||
|
public var startRealTime:Float = 0;
|
||||||
|
public var multiplayerStarted:Bool = false;
|
||||||
|
|
||||||
|
var clientMarbles:Map<ClientConnection, Marble> = [];
|
||||||
|
|
||||||
// Loading
|
// Loading
|
||||||
var resourceLoadFuncs:Array<(() -> Void)->Void> = [];
|
var resourceLoadFuncs:Array<(() -> Void)->Void> = [];
|
||||||
|
|
||||||
|
|
@ -208,7 +223,7 @@ class MarbleWorld extends Scheduler {
|
||||||
|
|
||||||
var lock:Bool = false;
|
var lock:Bool = false;
|
||||||
|
|
||||||
public function new(scene:Scene, scene2d:h2d.Scene, mission:Mission, record:Bool = false) {
|
public function new(scene:Scene, scene2d:h2d.Scene, mission:Mission, record:Bool = false, multiplayer:Bool = false) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.scene2d = scene2d;
|
this.scene2d = scene2d;
|
||||||
this.mission = mission;
|
this.mission = mission;
|
||||||
|
|
@ -217,6 +232,17 @@ class MarbleWorld extends Scheduler {
|
||||||
this.replay = new Replay(mission.path, mission.isClaMission ? mission.id : 0);
|
this.replay = new Replay(mission.path, mission.isClaMission ? mission.id : 0);
|
||||||
this.isRecording = record;
|
this.isRecording = record;
|
||||||
this.rewindManager = new RewindManager(this);
|
this.rewindManager = new RewindManager(this);
|
||||||
|
this.inputRecorder = new InputRecorder(this);
|
||||||
|
this.isMultiplayer = multiplayer;
|
||||||
|
|
||||||
|
// Set the network RNG for hunt
|
||||||
|
if (isMultiplayer && gameMode is modes.HuntMode && Net.isHost) {
|
||||||
|
var hunt:modes.HuntMode = cast gameMode;
|
||||||
|
var rng = Math.random() * 10000;
|
||||||
|
NetCommands.setNetworkRNG(rng);
|
||||||
|
@:privateAccess hunt.rng.setSeed(cast rng);
|
||||||
|
@:privateAccess hunt.rng2.setSeed(cast rng);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function init() {
|
public function init() {
|
||||||
|
|
@ -267,7 +293,12 @@ class MarbleWorld extends Scheduler {
|
||||||
scanMission(this.mission.root);
|
scanMission(this.mission.root);
|
||||||
this.gameMode.missionScan(this.mission);
|
this.gameMode.missionScan(this.mission);
|
||||||
this.resourceLoadFuncs.push(fwd -> this.initScene(fwd));
|
this.resourceLoadFuncs.push(fwd -> this.initScene(fwd));
|
||||||
this.resourceLoadFuncs.push(fwd -> this.initMarble(fwd));
|
if (this.isMultiplayer) {
|
||||||
|
for (client in Net.clients) {
|
||||||
|
this.resourceLoadFuncs.push(fwd -> this.initMarble(client, fwd)); // Others
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.resourceLoadFuncs.push(fwd -> this.initMarble(null, fwd)); // Self
|
||||||
this.resourceLoadFuncs.push(fwd -> {
|
this.resourceLoadFuncs.push(fwd -> {
|
||||||
this.addSimGroup(this.mission.root);
|
this.addSimGroup(this.mission.root);
|
||||||
this._loadingLength = resourceLoadFuncs.length;
|
this._loadingLength = resourceLoadFuncs.length;
|
||||||
|
|
@ -371,7 +402,7 @@ class MarbleWorld extends Scheduler {
|
||||||
worker.run();
|
worker.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function initMarble(onFinish:Void->Void) {
|
public function initMarble(client:ClientConnection, onFinish:Void->Void) {
|
||||||
Console.log("Initializing marble");
|
Console.log("Initializing marble");
|
||||||
var worker = new ResourceLoaderWorker(onFinish);
|
var worker = new ResourceLoaderWorker(onFinish);
|
||||||
var marblefiles = [
|
var marblefiles = [
|
||||||
|
|
@ -422,8 +453,9 @@ class MarbleWorld extends Scheduler {
|
||||||
}
|
}
|
||||||
worker.addTask(fwd -> {
|
worker.addTask(fwd -> {
|
||||||
var marble = new Marble();
|
var marble = new Marble();
|
||||||
marble.controllable = true;
|
if (client == null)
|
||||||
this.addMarble(marble, fwd);
|
marble.controllable = true;
|
||||||
|
this.addMarble(marble, client, fwd);
|
||||||
});
|
});
|
||||||
worker.run();
|
worker.run();
|
||||||
}
|
}
|
||||||
|
|
@ -435,6 +467,8 @@ class MarbleWorld extends Scheduler {
|
||||||
interior.onLevelStart();
|
interior.onLevelStart();
|
||||||
for (shape in this.dtsObjects)
|
for (shape in this.dtsObjects)
|
||||||
shape.onLevelStart();
|
shape.onLevelStart();
|
||||||
|
if (this.isMultiplayer && Net.isClient)
|
||||||
|
NetCommands.clientIsReady(Net.clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function restart(full:Bool = false) {
|
public function restart(full:Bool = false) {
|
||||||
|
|
@ -527,6 +561,15 @@ class MarbleWorld extends Scheduler {
|
||||||
this.marble.setMode(Start);
|
this.marble.setMode(Start);
|
||||||
sky.follow = marble.camera;
|
sky.follow = marble.camera;
|
||||||
|
|
||||||
|
if (isMultiplayer) {
|
||||||
|
for (client => marble in clientMarbles) {
|
||||||
|
var marbleStartQuat = this.gameMode.getSpawnTransform();
|
||||||
|
marble.setMarblePosition(marbleStartQuat.position.x, marbleStartQuat.position.y, marbleStartQuat.position.z);
|
||||||
|
marble.reset();
|
||||||
|
marble.setMode(Start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var missionInfo:MissionElementScriptObject = cast this.mission.root.elements.filter((element) -> element._type == MissionElementType.ScriptObject
|
var missionInfo:MissionElementScriptObject = cast this.mission.root.elements.filter((element) -> element._type == MissionElementType.ScriptObject
|
||||||
&& element._name == "MissionInfo")[0];
|
&& element._name == "MissionInfo")[0];
|
||||||
if (missionInfo.starthelptext != null)
|
if (missionInfo.starthelptext != null)
|
||||||
|
|
@ -586,17 +629,32 @@ class MarbleWorld extends Scheduler {
|
||||||
AudioManager.playSound(ResourceLoader.getResource('data/sound/spawn_alternate.wav', ResourceLoader.getAudio, this.soundResources));
|
AudioManager.playSound(ResourceLoader.getResource('data/sound/spawn_alternate.wav', ResourceLoader.getAudio, this.soundResources));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function allClientsReady() {
|
||||||
|
NetCommands.setStartTime(3); // Start after 3 seconds
|
||||||
|
}
|
||||||
|
|
||||||
public function updateGameState() {
|
public function updateGameState() {
|
||||||
if (this.outOfBounds)
|
if (this.outOfBounds)
|
||||||
return; // We will update state manually
|
return; // We will update state manually
|
||||||
if (this.timeState.currentAttemptTime < 0.5) {
|
if (!this.isMultiplayer) {
|
||||||
this.marble.setMode(Start);
|
if (this.timeState.currentAttemptTime < 0.5) {
|
||||||
}
|
this.marble.setMode(Start);
|
||||||
if ((this.timeState.currentAttemptTime >= 0.5) && (this.timeState.currentAttemptTime < 3.5)) {
|
}
|
||||||
this.marble.setMode(Start);
|
if ((this.timeState.currentAttemptTime >= 0.5) && (this.timeState.currentAttemptTime < 3.5)) {
|
||||||
}
|
this.marble.setMode(Start);
|
||||||
if (this.timeState.currentAttemptTime + skipStartBugPauseTime >= 3.5 && this.finishTime == null) {
|
}
|
||||||
this.marble.setMode(Play);
|
if (this.timeState.currentAttemptTime + skipStartBugPauseTime >= 3.5 && this.finishTime == null) {
|
||||||
|
this.marble.setMode(Play);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this.multiplayerStarted) {
|
||||||
|
if (this.startRealTime != 0 && this.timeState.timeSinceLoad > this.startRealTime) {
|
||||||
|
this.multiplayerStarted = true;
|
||||||
|
this.marble.setMode(Play);
|
||||||
|
for (client => marble in this.clientMarbles)
|
||||||
|
marble.setMode(Play);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -897,26 +955,46 @@ class MarbleWorld extends Scheduler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addMarble(marble:Marble, onFinish:Void->Void) {
|
public function addMarble(marble:Marble, client:ClientConnection, onFinish:Void->Void) {
|
||||||
this.marbles.push(marble);
|
|
||||||
marble.level = cast this;
|
marble.level = cast this;
|
||||||
if (marble.controllable) {
|
if (marble.controllable) {
|
||||||
marble.init(cast this, () -> {
|
marble.init(cast this, client, () -> {
|
||||||
this.scene.addChild(marble.camera);
|
this.scene.addChild(marble.camera);
|
||||||
this.marble = marble;
|
this.marble = marble;
|
||||||
// Ugly hack
|
// Ugly hack
|
||||||
// sky.follow = marble;
|
// sky.follow = marble;
|
||||||
sky.follow = marble.camera;
|
sky.follow = marble.camera;
|
||||||
this.collisionWorld.addMovingEntity(marble.collider);
|
this.collisionWorld.addMovingEntity(marble.collider);
|
||||||
|
this.collisionWorld.addMarbleEntity(marble.collider);
|
||||||
this.scene.addChild(marble);
|
this.scene.addChild(marble);
|
||||||
onFinish();
|
onFinish();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.collisionWorld.addMovingEntity(marble.collider);
|
marble.init(cast this, client, () -> {
|
||||||
this.scene.addChild(marble);
|
marble.collisionWorld = this.collisionWorld;
|
||||||
|
this.collisionWorld.addMovingEntity(marble.collider);
|
||||||
|
this.collisionWorld.addMarbleEntity(marble.collider);
|
||||||
|
this.scene.addChild(marble);
|
||||||
|
if (client != null)
|
||||||
|
clientMarbles.set(client, marble);
|
||||||
|
onFinish();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addGhostMarble(onFinish:Marble->Void) {
|
||||||
|
var marb = new Marble();
|
||||||
|
marb.controllable = false;
|
||||||
|
marb.init(null, null, () -> {
|
||||||
|
marb.collisionWorld = this.collisionWorld;
|
||||||
|
this.collisionWorld.addMovingEntity(marb.collider);
|
||||||
|
this.collisionWorld.addMarbleEntity(marb.collider);
|
||||||
|
this.scene.addChild(marb);
|
||||||
|
onFinish(marb);
|
||||||
|
});
|
||||||
|
return marb;
|
||||||
|
}
|
||||||
|
|
||||||
public function performRestart() {
|
public function performRestart() {
|
||||||
this.respawnPressedTime = timeState.timeSinceLoad;
|
this.respawnPressedTime = timeState.timeSinceLoad;
|
||||||
this.restart();
|
this.restart();
|
||||||
|
|
@ -935,11 +1013,53 @@ class MarbleWorld extends Scheduler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function rollback(t:Float) {
|
||||||
|
var newT = timeState.currentAttemptTime - t;
|
||||||
|
var rewindFrame = rewindManager.getNextRewindFrame(timeState.currentAttemptTime - t);
|
||||||
|
rewindManager.applyFrame(rewindFrame);
|
||||||
|
this.isReplayingMovement = true;
|
||||||
|
this.currentInputMoves = this.inputRecorder.getMovesFrom(timeState.currentAttemptTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function advanceWorld(dt:Float) {
|
||||||
|
ProfilerUI.measure("updateTimer");
|
||||||
|
this.updateTimer(dt);
|
||||||
|
this.tickSchedule(timeState.currentAttemptTime);
|
||||||
|
|
||||||
|
if (Key.isDown(Settings.controlsSettings.blast)
|
||||||
|
|| (MarbleGame.instance.touchInput.blastbutton.pressed)
|
||||||
|
|| Gamepad.isDown(Settings.gamepadSettings.blast)
|
||||||
|
&& !this.isWatching
|
||||||
|
&& this.game == "ultra") {
|
||||||
|
this.marble.useBlast();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateGameState();
|
||||||
|
this.updateBlast(timeState);
|
||||||
|
ProfilerUI.measure("updateDTS");
|
||||||
|
for (obj in dtsObjects) {
|
||||||
|
obj.update(timeState);
|
||||||
|
}
|
||||||
|
for (obj in triggers) {
|
||||||
|
obj.update(timeState);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerUI.measure("updateMarbles");
|
||||||
|
marble.update(timeState, collisionWorld, this.pathedInteriors);
|
||||||
|
for (client => marble in clientMarbles) {
|
||||||
|
marble.update(timeState, collisionWorld, this.pathedInteriors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function update(dt:Float) {
|
public function update(dt:Float) {
|
||||||
if (!_ready) {
|
if (!_ready) {
|
||||||
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)
|
||||||
|
|
@ -996,9 +1116,34 @@ class MarbleWorld extends Scheduler {
|
||||||
rewindManager.applyFrame(rframe);
|
rewindManager.applyFrame(rframe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dt < 0)
|
if (dt < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (this.isReplayingMovement) {
|
||||||
|
while (this.currentInputMoves.length > 1) {
|
||||||
|
while (this.currentInputMoves[1].time <= timeState.currentAttemptTime) {
|
||||||
|
this.currentInputMoves = this.currentInputMoves.slice(1);
|
||||||
|
if (this.currentInputMoves.length == 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (this.currentInputMoves.length > 1) {
|
||||||
|
dt = this.currentInputMoves[1].time - this.currentInputMoves[0].time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isReplayingMovement) {
|
||||||
|
if (this.timeState.currentAttemptTime != this.currentInputMoves[0].time)
|
||||||
|
trace("fucked");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentInputMoves.length > 1) {
|
||||||
|
advanceWorld(dt);
|
||||||
|
// trace('Position: ${@:privateAccess marble.newPos.sub(currentInputMoves[1].pos).length()}. Vel: ${marble.velocity.sub(currentInputMoves[1].velocity).length()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isReplayingMovement = false;
|
||||||
|
}
|
||||||
|
|
||||||
ProfilerUI.measure("updateTimer");
|
ProfilerUI.measure("updateTimer");
|
||||||
this.updateTimer(dt);
|
this.updateTimer(dt);
|
||||||
|
|
||||||
|
|
@ -1057,8 +1202,14 @@ class MarbleWorld extends Scheduler {
|
||||||
for (obj in triggers) {
|
for (obj in triggers) {
|
||||||
obj.update(timeState);
|
obj.update(timeState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isReplayingMovement) {
|
||||||
|
inputRecorder.recordInput(timeState.currentAttemptTime);
|
||||||
|
}
|
||||||
|
|
||||||
ProfilerUI.measure("updateMarbles");
|
ProfilerUI.measure("updateMarbles");
|
||||||
for (marble in marbles) {
|
marble.update(timeState, collisionWorld, this.pathedInteriors);
|
||||||
|
for (client => marble in clientMarbles) {
|
||||||
marble.update(timeState, collisionWorld, this.pathedInteriors);
|
marble.update(timeState, collisionWorld, this.pathedInteriors);
|
||||||
}
|
}
|
||||||
_cubemapNeedsUpdate = true;
|
_cubemapNeedsUpdate = true;
|
||||||
|
|
@ -1096,6 +1247,10 @@ class MarbleWorld extends Scheduler {
|
||||||
if (!this.rewinding && Settings.optionsSettings.rewindEnabled)
|
if (!this.rewinding && Settings.optionsSettings.rewindEnabled)
|
||||||
this.rewindManager.recordFrame();
|
this.rewindManager.recordFrame();
|
||||||
|
|
||||||
|
if (!this.isReplayingMovement) {
|
||||||
|
inputRecorder.recordMarble();
|
||||||
|
}
|
||||||
|
|
||||||
this.updateTexts();
|
this.updateTexts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1177,7 +1332,8 @@ class MarbleWorld extends Scheduler {
|
||||||
if (this.timeState.gameplayClock < 0)
|
if (this.timeState.gameplayClock < 0)
|
||||||
this.gameMode.onTimeExpire();
|
this.gameMode.onTimeExpire();
|
||||||
}
|
}
|
||||||
this.timeState.currentAttemptTime += dt;
|
if (!this.isMultiplayer || this.multiplayerStarted)
|
||||||
|
this.timeState.currentAttemptTime += dt;
|
||||||
} else {
|
} else {
|
||||||
this.timeState.currentAttemptTime = this.replay.currentPlaybackFrame.time;
|
this.timeState.currentAttemptTime = this.replay.currentPlaybackFrame.time;
|
||||||
this.timeState.gameplayClock = this.replay.currentPlaybackFrame.clockTime;
|
this.timeState.gameplayClock = this.replay.currentPlaybackFrame.clockTime;
|
||||||
|
|
@ -1834,10 +1990,10 @@ class MarbleWorld extends Scheduler {
|
||||||
pathedInteriors.dispose();
|
pathedInteriors.dispose();
|
||||||
}
|
}
|
||||||
pathedInteriors = null;
|
pathedInteriors = null;
|
||||||
for (marble in this.marbles) {
|
for (client => marble in clientMarbles) {
|
||||||
marble.dispose();
|
marble.dispose();
|
||||||
}
|
}
|
||||||
marbles = null;
|
clientMarbles = null;
|
||||||
for (dtsObject in this.dtsObjects) {
|
for (dtsObject in this.dtsObjects) {
|
||||||
dtsObject.dispose();
|
dtsObject.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -582,7 +582,7 @@ class PreviewWorld extends Scheduler {
|
||||||
public function spawnMarble(onFinish:Marble->Void) {
|
public function spawnMarble(onFinish:Marble->Void) {
|
||||||
var marb = new Marble();
|
var marb = new Marble();
|
||||||
marb.controllable = false;
|
marb.controllable = false;
|
||||||
marb.init(null, () -> {
|
marb.init(null, null, () -> {
|
||||||
marb.collisionWorld = this.collisionWorld;
|
marb.collisionWorld = this.collisionWorld;
|
||||||
this.collisionWorld.addMovingEntity(marb.collider);
|
this.collisionWorld.addMovingEntity(marb.collider);
|
||||||
this.scene.addChild(marb);
|
this.scene.addChild(marb);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ class CollisionWorld {
|
||||||
public var dynamicEntities:Array<CollisionEntity> = [];
|
public var dynamicEntities:Array<CollisionEntity> = [];
|
||||||
public var dynamicOctree:Octree;
|
public var dynamicOctree:Octree;
|
||||||
|
|
||||||
|
var marbleEntities:Array<SphereCollisionEntity> = [];
|
||||||
|
|
||||||
var dynamicEntitySet:Map<CollisionEntity, Bool> = [];
|
var dynamicEntitySet:Map<CollisionEntity, Bool> = [];
|
||||||
|
|
||||||
public function new() {
|
public function new() {
|
||||||
|
|
@ -55,6 +57,13 @@ class CollisionWorld {
|
||||||
contacts = contacts.concat(obj.sphereIntersection(spherecollision, timeState));
|
contacts = contacts.concat(obj.sphereIntersection(spherecollision, timeState));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (marb in marbleEntities) {
|
||||||
|
if (marb != spherecollision) {
|
||||||
|
if (spherecollision.go.isCollideable)
|
||||||
|
contacts = contacts.concat(marb.sphereIntersection(spherecollision, timeState));
|
||||||
|
}
|
||||||
|
}
|
||||||
return {foundEntities: foundEntities, contacts: contacts};
|
return {foundEntities: foundEntities, contacts: contacts};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,6 +123,14 @@ class CollisionWorld {
|
||||||
// [entity.boundingBox.xSize, entity.boundingBox.ySize, entity.boundingBox.zSize], entity);
|
// [entity.boundingBox.xSize, entity.boundingBox.ySize, entity.boundingBox.zSize], entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addMarbleEntity(entity:SphereCollisionEntity) {
|
||||||
|
this.marbleEntities.push(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeMarbleEntity(entity:SphereCollisionEntity) {
|
||||||
|
this.marbleEntities.remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
public function addMovingEntity(entity:CollisionEntity) {
|
public function addMovingEntity(entity:CollisionEntity) {
|
||||||
this.dynamicEntities.push(entity);
|
this.dynamicEntities.push(entity);
|
||||||
this.dynamicOctree.insert(entity);
|
this.dynamicOctree.insert(entity);
|
||||||
|
|
|
||||||
|
|
@ -98,17 +98,17 @@ class SphereCollisionEntity extends CollisionEntity {
|
||||||
contact.penetration = radius - (position.sub(contact.point).dot(contact.normal));
|
contact.penetration = radius - (position.sub(contact.point).dot(contact.normal));
|
||||||
contacts.push(contact);
|
contacts.push(contact);
|
||||||
|
|
||||||
// var othercontact = new CollisionInfo();
|
var othercontact = new CollisionInfo();
|
||||||
// othercontact.collider = collisionEntity;
|
othercontact.collider = collisionEntity;
|
||||||
// othercontact.friction = 1;
|
othercontact.friction = 1;
|
||||||
// othercontact.restitution = 1;
|
othercontact.restitution = 1;
|
||||||
// othercontact.velocity = this.velocity;
|
othercontact.velocity = this.velocity;
|
||||||
// othercontact.point = thispos.add(position).multiply(0.5);
|
othercontact.point = thispos.add(position).multiply(0.5);
|
||||||
// othercontact.normal = contact.point.sub(position).normalized();
|
othercontact.normal = contact.point.sub(position).normalized();
|
||||||
// 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));
|
||||||
// this.marble.queueCollision(othercontact);
|
this.marble.queueCollision(othercontact);
|
||||||
}
|
}
|
||||||
return contacts;
|
return contacts;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,9 @@ class MainMenuGui extends GuiImage {
|
||||||
btnList.addButton(0, "Single Player Game", (sender) -> {
|
btnList.addButton(0, "Single Player Game", (sender) -> {
|
||||||
cast(this.parent, Canvas).setContent(new DifficultySelectGui());
|
cast(this.parent, Canvas).setContent(new DifficultySelectGui());
|
||||||
});
|
});
|
||||||
|
btnList.addButton(0, "Multiplayer Game", (sender) -> {
|
||||||
|
cast(this.parent, Canvas).setContent(new MultiplayerGui());
|
||||||
|
});
|
||||||
// btnList.addButton(2, "Leaderboards", (e) -> {}, 20);
|
// btnList.addButton(2, "Leaderboards", (e) -> {}, 20);
|
||||||
btnList.addButton(2, "Achievements", (e) -> {
|
btnList.addButton(2, "Achievements", (e) -> {
|
||||||
cast(this.parent, Canvas).setContent(new AchievementsGui());
|
cast(this.parent, Canvas).setContent(new AchievementsGui());
|
||||||
|
|
|
||||||
106
src/gui/MultiplayerGui.hx
Normal file
106
src/gui/MultiplayerGui.hx
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
package gui;
|
||||||
|
|
||||||
|
import net.Net;
|
||||||
|
import src.MarbleGame;
|
||||||
|
import hxd.res.BitmapFont;
|
||||||
|
import h3d.Vector;
|
||||||
|
import src.ResourceLoader;
|
||||||
|
import src.Settings;
|
||||||
|
import src.Util;
|
||||||
|
|
||||||
|
class MultiplayerGui extends GuiImage {
|
||||||
|
var innerCtrl:GuiControl;
|
||||||
|
var btnList:GuiXboxList;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
var res = ResourceLoader.getImage("data/ui/xbox/BG_fadeOutSoftEdge.png").resource.toTile();
|
||||||
|
super(res);
|
||||||
|
var domcasual32fontdata = ResourceLoader.getFileEntry("data/font/DomCasualD.fnt");
|
||||||
|
var domcasual32b = new BitmapFont(domcasual32fontdata.entry);
|
||||||
|
@:privateAccess domcasual32b.loader = ResourceLoader.loader;
|
||||||
|
var domcasual32 = domcasual32b.toSdfFont(cast 42 * Settings.uiScale, MultiChannel);
|
||||||
|
|
||||||
|
this.horizSizing = Width;
|
||||||
|
this.vertSizing = Height;
|
||||||
|
this.position = new Vector();
|
||||||
|
this.extent = new Vector(640, 480);
|
||||||
|
|
||||||
|
#if hl
|
||||||
|
var scene2d = hxd.Window.getInstance();
|
||||||
|
#end
|
||||||
|
#if js
|
||||||
|
var scene2d = MarbleGame.instance.scene2d;
|
||||||
|
#end
|
||||||
|
|
||||||
|
var offsetX = (scene2d.width - 1280) / 2;
|
||||||
|
var offsetY = (scene2d.height - 720) / 2;
|
||||||
|
|
||||||
|
var subX = 640 - (scene2d.width - offsetX) * 640 / scene2d.width;
|
||||||
|
var subY = 480 - (scene2d.height - offsetY) * 480 / scene2d.height;
|
||||||
|
|
||||||
|
innerCtrl = new GuiControl();
|
||||||
|
innerCtrl.position = new Vector(offsetX, offsetY);
|
||||||
|
innerCtrl.extent = new Vector(640 - subX, 480 - subY);
|
||||||
|
innerCtrl.horizSizing = Width;
|
||||||
|
innerCtrl.vertSizing = Height;
|
||||||
|
this.addChild(innerCtrl);
|
||||||
|
|
||||||
|
var coliseumfontdata = ResourceLoader.getFileEntry("data/font/ColiseumRR.fnt");
|
||||||
|
var coliseumb = new BitmapFont(coliseumfontdata.entry);
|
||||||
|
@:privateAccess coliseumb.loader = ResourceLoader.loader;
|
||||||
|
var coliseum = coliseumb.toSdfFont(cast 44 * Settings.uiScale, MultiChannel);
|
||||||
|
|
||||||
|
var rootTitle = new GuiText(coliseum);
|
||||||
|
rootTitle.position = new Vector(100, 30);
|
||||||
|
rootTitle.extent = new Vector(1120, 80);
|
||||||
|
rootTitle.text.textColor = 0xFFFFFF;
|
||||||
|
rootTitle.text.text = "MULTIPLAYER";
|
||||||
|
rootTitle.text.alpha = 0.5;
|
||||||
|
innerCtrl.addChild(rootTitle);
|
||||||
|
|
||||||
|
var btnList = new GuiXboxList();
|
||||||
|
btnList.position = new Vector(70 - offsetX, 165);
|
||||||
|
btnList.horizSizing = Left;
|
||||||
|
btnList.extent = new Vector(502, 500);
|
||||||
|
innerCtrl.addChild(btnList);
|
||||||
|
|
||||||
|
btnList.addButton(3, 'Create Game', (e) -> {
|
||||||
|
MarbleGame.canvas.setContent(new MultiplayerLevelSelectGui(true));
|
||||||
|
Net.hostServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
btnList.addButton(3, 'Join Game', (e) -> {
|
||||||
|
Net.joinServer(() -> {
|
||||||
|
MarbleGame.canvas.setContent(new MultiplayerLevelSelectGui(false));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var bottomBar = new GuiControl();
|
||||||
|
bottomBar.position = new Vector(0, 590);
|
||||||
|
bottomBar.extent = new Vector(640, 200);
|
||||||
|
bottomBar.horizSizing = Width;
|
||||||
|
bottomBar.vertSizing = Bottom;
|
||||||
|
innerCtrl.addChild(bottomBar);
|
||||||
|
|
||||||
|
var backButton = new GuiXboxButton("Back", 160);
|
||||||
|
backButton.position = new Vector(400, 0);
|
||||||
|
backButton.vertSizing = Bottom;
|
||||||
|
backButton.horizSizing = Right;
|
||||||
|
backButton.gamepadAccelerator = ["B"];
|
||||||
|
backButton.accelerators = [hxd.Key.ESCAPE, hxd.Key.BACKSPACE];
|
||||||
|
backButton.pressedAction = (e) -> MarbleGame.canvas.setContent(new MainMenuGui());
|
||||||
|
bottomBar.addChild(backButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function onResize(width:Int, height:Int) {
|
||||||
|
var offsetX = (width - 1280) / 2;
|
||||||
|
var offsetY = (height - 720) / 2;
|
||||||
|
|
||||||
|
var subX = 640 - (width - offsetX) * 640 / width;
|
||||||
|
var subY = 480 - (height - offsetY) * 480 / height;
|
||||||
|
innerCtrl.position = new Vector(offsetX, offsetY);
|
||||||
|
innerCtrl.extent = new Vector(640 - subX, 480 - subY);
|
||||||
|
|
||||||
|
super.onResize(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
298
src/gui/MultiplayerLevelSelectGui.hx
Normal file
298
src/gui/MultiplayerLevelSelectGui.hx
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
package gui;
|
||||||
|
|
||||||
|
import net.NetCommands;
|
||||||
|
import modes.GameMode.ScoreType;
|
||||||
|
import src.Util;
|
||||||
|
import haxe.io.Path;
|
||||||
|
import h2d.filter.DropShadow;
|
||||||
|
import src.MarbleGame;
|
||||||
|
import gui.GuiControl.MouseState;
|
||||||
|
import hxd.res.BitmapFont;
|
||||||
|
import h3d.Vector;
|
||||||
|
import src.ResourceLoader;
|
||||||
|
import src.Settings;
|
||||||
|
import src.MissionList;
|
||||||
|
|
||||||
|
class MultiplayerLevelSelectGui extends GuiImage {
|
||||||
|
static var currentSelectionStatic:Int = 0;
|
||||||
|
|
||||||
|
static var setLevelFn:Int->Void;
|
||||||
|
static var playSelectedLevel:Void->Void;
|
||||||
|
|
||||||
|
var innerCtrl:GuiControl;
|
||||||
|
|
||||||
|
public function new(isHost:Bool) {
|
||||||
|
var res = ResourceLoader.getImage("data/ui/game/CloudBG.jpg").resource.toTile();
|
||||||
|
super(res);
|
||||||
|
|
||||||
|
var arial14fontdata = ResourceLoader.getFileEntry("data/font/Arial Bold.fnt");
|
||||||
|
var arial14b = new BitmapFont(arial14fontdata.entry);
|
||||||
|
@:privateAccess arial14b.loader = ResourceLoader.loader;
|
||||||
|
var arial14 = arial14b.toSdfFont(cast 21 * Settings.uiScale, h2d.Font.SDFChannel.MultiChannel);
|
||||||
|
function mlFontLoader(text:String) {
|
||||||
|
return arial14;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarbleGame.instance.toRecord = false;
|
||||||
|
|
||||||
|
var fadeEdge = new GuiImage(ResourceLoader.getResource("data/ui/xbox/BG_fadeOutSoftEdge.png", ResourceLoader.getImage, this.imageResources).toTile());
|
||||||
|
fadeEdge.position = new Vector(0, 0);
|
||||||
|
fadeEdge.extent = new Vector(640, 480);
|
||||||
|
fadeEdge.vertSizing = Height;
|
||||||
|
fadeEdge.horizSizing = Width;
|
||||||
|
this.addChild(fadeEdge);
|
||||||
|
|
||||||
|
var loadAnim = new GuiLoadAnim();
|
||||||
|
loadAnim.position = new Vector(610, 253);
|
||||||
|
loadAnim.extent = new Vector(63, 63);
|
||||||
|
loadAnim.horizSizing = Center;
|
||||||
|
loadAnim.vertSizing = Bottom;
|
||||||
|
this.addChild(loadAnim);
|
||||||
|
|
||||||
|
var loadTextBg = new GuiText(arial14);
|
||||||
|
loadTextBg.position = new Vector(608, 335);
|
||||||
|
loadTextBg.extent = new Vector(63, 40);
|
||||||
|
loadTextBg.horizSizing = Center;
|
||||||
|
loadTextBg.vertSizing = Bottom;
|
||||||
|
loadTextBg.justify = Center;
|
||||||
|
loadTextBg.text.text = "Loading";
|
||||||
|
loadTextBg.text.textColor = 0;
|
||||||
|
this.addChild(loadTextBg);
|
||||||
|
|
||||||
|
var loadText = new GuiText(arial14);
|
||||||
|
loadText.position = new Vector(610, 334);
|
||||||
|
loadText.extent = new Vector(63, 40);
|
||||||
|
loadText.horizSizing = Center;
|
||||||
|
loadText.vertSizing = Bottom;
|
||||||
|
loadText.justify = Center;
|
||||||
|
loadText.text.text = "Loading";
|
||||||
|
this.addChild(loadText);
|
||||||
|
|
||||||
|
var difficultyMissions = MissionList.missionList['ultra']["multiplayer"];
|
||||||
|
if (currentSelectionStatic >= difficultyMissions.length)
|
||||||
|
currentSelectionStatic = 0;
|
||||||
|
var curMission = difficultyMissions[currentSelectionStatic];
|
||||||
|
|
||||||
|
var lock = true;
|
||||||
|
var currentToken = 0;
|
||||||
|
var requestToken = 0;
|
||||||
|
|
||||||
|
// var misFile = Path.withoutExtension(Path.withoutDirectory(curMission.path));
|
||||||
|
// MarbleGame.instance.setPreviewMission(misFile, () -> {
|
||||||
|
// lock = false;
|
||||||
|
// if (currentToken != requestToken)
|
||||||
|
// return;
|
||||||
|
// this.bmp.visible = false;
|
||||||
|
// loadAnim.anim.visible = false;
|
||||||
|
// loadText.text.visible = false;
|
||||||
|
// loadTextBg.text.visible = false;
|
||||||
|
// });
|
||||||
|
|
||||||
|
var domcasual32fontdata = ResourceLoader.getFileEntry("data/font/DomCasualD.fnt");
|
||||||
|
var domcasual32b = new BitmapFont(domcasual32fontdata.entry);
|
||||||
|
@:privateAccess domcasual32b.loader = ResourceLoader.loader;
|
||||||
|
var domcasual32 = domcasual32b.toSdfFont(cast 42 * Settings.uiScale, MultiChannel);
|
||||||
|
|
||||||
|
this.horizSizing = Width;
|
||||||
|
this.vertSizing = Height;
|
||||||
|
this.position = new Vector();
|
||||||
|
this.extent = new Vector(640, 480);
|
||||||
|
#if hl
|
||||||
|
var scene2d = hxd.Window.getInstance();
|
||||||
|
#end
|
||||||
|
#if js
|
||||||
|
var scene2d = MarbleGame.instance.scene2d;
|
||||||
|
#end
|
||||||
|
|
||||||
|
var offsetX = (scene2d.width - 1280) / 2;
|
||||||
|
var offsetY = (scene2d.height - 720) / 2;
|
||||||
|
|
||||||
|
var subX = 640 - (scene2d.width - offsetX) * 640 / scene2d.width;
|
||||||
|
var subY = 480 - (scene2d.height - offsetY) * 480 / scene2d.height;
|
||||||
|
|
||||||
|
innerCtrl = new GuiControl();
|
||||||
|
|
||||||
|
innerCtrl.position = new Vector(offsetX, offsetY);
|
||||||
|
innerCtrl.extent = new Vector(640 - subX, 480 - subY);
|
||||||
|
innerCtrl.horizSizing = Width;
|
||||||
|
innerCtrl.vertSizing = Height;
|
||||||
|
this.addChild(innerCtrl);
|
||||||
|
var coliseumfontdata = ResourceLoader.getFileEntry("data/font/ColiseumRR.fnt");
|
||||||
|
var coliseumb = new BitmapFont(coliseumfontdata.entry);
|
||||||
|
@:privateAccess coliseumb.loader = ResourceLoader.loader;
|
||||||
|
var coliseum = coliseumb.toSdfFont(cast 44 * Settings.uiScale, MultiChannel);
|
||||||
|
|
||||||
|
var rootTitle = new GuiText(coliseum);
|
||||||
|
|
||||||
|
rootTitle.position = new Vector(100, 30);
|
||||||
|
rootTitle.extent = new Vector(1120, 80);
|
||||||
|
rootTitle.text.textColor = 0xFFFFFF;
|
||||||
|
rootTitle.text.text = "SELECT LEVEL";
|
||||||
|
rootTitle.text.alpha = 0.5;
|
||||||
|
innerCtrl.addChild(rootTitle);
|
||||||
|
var bottomBar = new GuiControl();
|
||||||
|
|
||||||
|
bottomBar.position = new Vector(0, 590);
|
||||||
|
bottomBar.extent = new Vector(640, 200);
|
||||||
|
bottomBar.horizSizing = Width;
|
||||||
|
bottomBar.vertSizing = Bottom;
|
||||||
|
innerCtrl.addChild(bottomBar);
|
||||||
|
|
||||||
|
var backButton = new GuiXboxButton("Back", 160);
|
||||||
|
backButton.position = new Vector(400, 0);
|
||||||
|
backButton.vertSizing = Bottom;
|
||||||
|
backButton.horizSizing = Right;
|
||||||
|
backButton.gamepadAccelerator = ["B"];
|
||||||
|
backButton.accelerators = [hxd.Key.ESCAPE, hxd.Key.BACKSPACE];
|
||||||
|
backButton.pressedAction = (e) -> MarbleGame.canvas.setContent(new DifficultySelectGui());
|
||||||
|
bottomBar.addChild(backButton);
|
||||||
|
|
||||||
|
// var lbButton = new GuiXboxButton("Leaderboard", 220);
|
||||||
|
// lbButton.position = new Vector(750, 0);
|
||||||
|
// lbButton.vertSizing = Bottom;
|
||||||
|
// lbButton.horizSizing = Right;
|
||||||
|
// bottomBar.addChild(lbButton);
|
||||||
|
|
||||||
|
if (isHost) {
|
||||||
|
var nextButton = new GuiXboxButton("Play", 160);
|
||||||
|
nextButton.position = new Vector(960, 0);
|
||||||
|
nextButton.vertSizing = Bottom;
|
||||||
|
nextButton.horizSizing = Right;
|
||||||
|
nextButton.gamepadAccelerator = ["A"];
|
||||||
|
nextButton.accelerators = [hxd.Key.ENTER];
|
||||||
|
nextButton.pressedAction = (e) -> {
|
||||||
|
NetCommands.playLevel();
|
||||||
|
};
|
||||||
|
bottomBar.addChild(nextButton);
|
||||||
|
}
|
||||||
|
playSelectedLevel = () -> {
|
||||||
|
MarbleGame.instance.playMission(curMission, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelWnd = new GuiImage(ResourceLoader.getResource("data/ui/xbox/levelPreviewWindow.png", ResourceLoader.getImage, this.imageResources).toTile());
|
||||||
|
levelWnd.position = new Vector(555, 469);
|
||||||
|
levelWnd.extent = new Vector(535, 137);
|
||||||
|
levelWnd.vertSizing = Bottom;
|
||||||
|
levelWnd.horizSizing = Right;
|
||||||
|
innerCtrl.addChild(levelWnd);
|
||||||
|
|
||||||
|
var statIcon = new GuiImage(ResourceLoader.getResource("data/ui/xbox/statIcon.png", ResourceLoader.getImage, this.imageResources).toTile());
|
||||||
|
statIcon.position = new Vector(29, 54);
|
||||||
|
statIcon.extent = new Vector(20, 20);
|
||||||
|
levelWnd.addChild(statIcon);
|
||||||
|
|
||||||
|
var eggIcon = new GuiImage(ResourceLoader.getResource("data/ui/xbox/eggIcon.png", ResourceLoader.getImage, this.imageResources).toTile());
|
||||||
|
eggIcon.position = new Vector(29, 79);
|
||||||
|
eggIcon.extent = new Vector(20, 20);
|
||||||
|
levelWnd.addChild(eggIcon);
|
||||||
|
|
||||||
|
var c0 = 0xEBEBEB;
|
||||||
|
var c1 = 0x8DFF8D;
|
||||||
|
var c2 = 0x88BCEE;
|
||||||
|
var c3 = 0xFF7575;
|
||||||
|
|
||||||
|
var levelInfoLeft = new GuiMLText(arial14, mlFontLoader);
|
||||||
|
levelInfoLeft.position = new Vector(69, 54);
|
||||||
|
levelInfoLeft.extent = new Vector(180, 100);
|
||||||
|
levelInfoLeft.text.text = '<p align="right"><font color="#EBEBEB">My Best Time:</font><br/><font color="#EBEBEB">Par Time:</font></p>';
|
||||||
|
levelInfoLeft.text.lineSpacing = 6;
|
||||||
|
levelWnd.addChild(levelInfoLeft);
|
||||||
|
|
||||||
|
var levelInfoMid = new GuiMLText(arial14, mlFontLoader);
|
||||||
|
levelInfoMid.position = new Vector(269, 54);
|
||||||
|
levelInfoMid.extent = new Vector(180, 100);
|
||||||
|
levelInfoMid.text.text = '<p align="left"><font color="#EBEBEB">None</font><br/><font color="#88BCEE">99:59:99</font></p>';
|
||||||
|
levelInfoMid.text.lineSpacing = 6;
|
||||||
|
levelWnd.addChild(levelInfoMid);
|
||||||
|
|
||||||
|
var levelInfoRight = new GuiMLText(arial14, mlFontLoader);
|
||||||
|
levelInfoRight.position = new Vector(379, 54);
|
||||||
|
levelInfoRight.extent = new Vector(180, 100);
|
||||||
|
levelInfoRight.text.text = '<p align="left"><font color="#EBEBEB">Level 1<br/>Difficulty 1</font></p>';
|
||||||
|
levelInfoRight.text.lineSpacing = 6;
|
||||||
|
levelWnd.addChild(levelInfoRight);
|
||||||
|
|
||||||
|
var levelNames = difficultyMissions.map(x -> x.title);
|
||||||
|
var levelSelectOpts = new GuiXboxOptionsList(6, "Level", levelNames);
|
||||||
|
|
||||||
|
function setLevel(idx:Int) {
|
||||||
|
// if (lock)
|
||||||
|
// return false;
|
||||||
|
levelSelectOpts.currentOption = idx;
|
||||||
|
this.bmp.visible = true;
|
||||||
|
loadAnim.anim.visible = true;
|
||||||
|
loadText.text.visible = true;
|
||||||
|
loadTextBg.text.visible = true;
|
||||||
|
lock = true;
|
||||||
|
curMission = difficultyMissions[idx];
|
||||||
|
currentSelectionStatic = idx;
|
||||||
|
currentToken++;
|
||||||
|
var misFile = Path.withoutExtension(Path.withoutDirectory(curMission.path));
|
||||||
|
var mis = difficultyMissions[idx];
|
||||||
|
var requestToken = currentToken;
|
||||||
|
if (Settings.easterEggs.exists(mis.path))
|
||||||
|
eggIcon.bmp.visible = true;
|
||||||
|
else
|
||||||
|
eggIcon.bmp.visible = false;
|
||||||
|
MarbleGame.instance.setPreviewMission(misFile, () -> {
|
||||||
|
lock = false;
|
||||||
|
if (requestToken != currentToken)
|
||||||
|
return;
|
||||||
|
this.bmp.visible = false;
|
||||||
|
loadAnim.anim.visible = false;
|
||||||
|
loadText.text.visible = false;
|
||||||
|
loadTextBg.text.visible = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
var scoreType = mis.missionInfo.gamemode != null
|
||||||
|
&& mis.missionInfo.gamemode.toLowerCase() == 'scrum' ? ScoreType.Score : ScoreType.Time;
|
||||||
|
|
||||||
|
var myScore = Settings.getScores(mis.path);
|
||||||
|
var scoreDisp = "None";
|
||||||
|
if (myScore.length != 0)
|
||||||
|
scoreDisp = scoreType == Time ? Util.formatTime(myScore[0].time) : Util.formatScore(myScore[0].time);
|
||||||
|
var isPar = myScore.length != 0 && myScore[0].time < mis.qualifyTime;
|
||||||
|
var scoreColor = "#EBEBEB";
|
||||||
|
if (isPar)
|
||||||
|
scoreColor = "#8DFF8D";
|
||||||
|
if (scoreType == Score && myScore.length == 0)
|
||||||
|
scoreColor = "#EBEBEB";
|
||||||
|
if (scoreType == Time) {
|
||||||
|
levelInfoLeft.text.text = '<p align="right"><font color="#EBEBEB">My Best Time:</font><br/><font color="#EBEBEB">Par Time:</font></p>';
|
||||||
|
levelInfoMid.text.text = '<p align="left"><font color="${scoreColor}">${scoreDisp}</font><br/><font color="#88BCEE">${Util.formatTime(mis.qualifyTime)}</font></p>';
|
||||||
|
}
|
||||||
|
if (scoreType == Score) {
|
||||||
|
levelInfoLeft.text.text = '<p align="right"><font color="#EBEBEB">My Best Score:</font></p>';
|
||||||
|
levelInfoMid.text.text = '<p align="left"><font color="${scoreColor}">${scoreDisp}</font></p>';
|
||||||
|
}
|
||||||
|
levelInfoRight.text.text = '<p align="left"><font color="#EBEBEB">Level ${mis.missionInfo.level}<br/>Difficulty ${mis.missionInfo.difficulty == null ? "" : mis.missionInfo.difficulty}</font></p>';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
setLevelFn = setLevel;
|
||||||
|
|
||||||
|
levelSelectOpts.position = new Vector(380, 435);
|
||||||
|
levelSelectOpts.extent = new Vector(815, 94);
|
||||||
|
levelSelectOpts.vertSizing = Bottom;
|
||||||
|
levelSelectOpts.horizSizing = Right;
|
||||||
|
levelSelectOpts.alwaysActive = true;
|
||||||
|
levelSelectOpts.onChangeFunc = (i) -> {
|
||||||
|
NetCommands.setLobbyLevelIndex(i);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
levelSelectOpts.setCurrentOption(currentSelectionStatic);
|
||||||
|
setLevel(currentSelectionStatic);
|
||||||
|
innerCtrl.addChild(levelSelectOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function onResize(width:Int, height:Int) {
|
||||||
|
var offsetX = (width - 1280) / 2;
|
||||||
|
var offsetY = (height - 720) / 2;
|
||||||
|
|
||||||
|
var subX = 640 - (width - offsetX) * 640 / width;
|
||||||
|
var subY = 480 - (height - offsetY) * 480 / height;
|
||||||
|
innerCtrl.position = new Vector(offsetX, offsetY);
|
||||||
|
innerCtrl.extent = new Vector(640 - subX, 480 - subY);
|
||||||
|
|
||||||
|
super.onResize(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
244
src/net/Net.hx
Normal file
244
src/net/Net.hx
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
package net;
|
||||||
|
|
||||||
|
import haxe.Json;
|
||||||
|
import datachannel.RTCPeerConnection;
|
||||||
|
import datachannel.RTCDataChannel;
|
||||||
|
import hx.ws.WebSocket;
|
||||||
|
import src.Console;
|
||||||
|
import net.NetCommands;
|
||||||
|
|
||||||
|
enum abstract GameplayState(Int) from Int to Int {
|
||||||
|
var UNKNOWN;
|
||||||
|
var LOBBY;
|
||||||
|
var GAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum abstract NetPacketType(Int) from Int to Int {
|
||||||
|
var NullPacket;
|
||||||
|
var ClientIdAssign;
|
||||||
|
var NetCommand;
|
||||||
|
var Ping;
|
||||||
|
var PingBack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
class ClientConnection {
|
||||||
|
var id:Int;
|
||||||
|
var socket:RTCPeerConnection;
|
||||||
|
var datachannel:RTCDataChannel;
|
||||||
|
var state:GameplayState;
|
||||||
|
var rtt:Float;
|
||||||
|
var pingSendTime:Float;
|
||||||
|
var _rttRecords:Array<Float> = [];
|
||||||
|
|
||||||
|
public function new(id:Int, socket:RTCPeerConnection, datachannel:RTCDataChannel) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.datachannel = datachannel;
|
||||||
|
this.id = id;
|
||||||
|
this.state = GameplayState.LOBBY;
|
||||||
|
this.rtt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ready() {
|
||||||
|
state = GameplayState.GAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Net {
|
||||||
|
static var client:RTCPeerConnection;
|
||||||
|
static var clientDatachannel:RTCDataChannel;
|
||||||
|
|
||||||
|
static var masterWs:WebSocket;
|
||||||
|
|
||||||
|
public static var isMP:Bool;
|
||||||
|
public static var isHost:Bool;
|
||||||
|
public static var isClient:Bool;
|
||||||
|
|
||||||
|
public static var startMP:Bool;
|
||||||
|
|
||||||
|
public static var clientId:Int;
|
||||||
|
public static var networkRNG:Float;
|
||||||
|
public static var clients:Map<RTCPeerConnection, ClientConnection> = [];
|
||||||
|
public static var clientIdMap:Map<Int, ClientConnection> = [];
|
||||||
|
|
||||||
|
public static function hostServer() {
|
||||||
|
// host = new RTCPeerConnection(["stun.l.google.com:19302"], "0.0.0.0");
|
||||||
|
// host.bind("127.0.0.1", 28000, (c) -> {
|
||||||
|
// onClientConnect(c);
|
||||||
|
// isMP = true;
|
||||||
|
// });
|
||||||
|
isHost = true;
|
||||||
|
isClient = false;
|
||||||
|
clientId = 0;
|
||||||
|
masterWs = new WebSocket("ws://localhost:8080");
|
||||||
|
|
||||||
|
masterWs.onmessage = (m) -> {
|
||||||
|
switch (m) {
|
||||||
|
case StrMessage(content):
|
||||||
|
var conts = Json.parse(content);
|
||||||
|
var peer = new RTCPeerConnection(["stun.l.google.com:19302"], "0.0.0.0");
|
||||||
|
peer.setRemoteDescription(conts.sdp, conts.type);
|
||||||
|
|
||||||
|
var candidates = [];
|
||||||
|
peer.onLocalCandidate = (c) -> {
|
||||||
|
if (c != "")
|
||||||
|
candidates.push('a=${c}');
|
||||||
|
}
|
||||||
|
peer.onGatheringStateChange = (s) -> {
|
||||||
|
if (s == RTC_GATHERING_COMPLETE) {
|
||||||
|
var sdpObj = StringTools.trim(peer.localDescription);
|
||||||
|
sdpObj = sdpObj + '\r\n' + candidates.join('\r\n');
|
||||||
|
masterWs.send(Json.stringify({
|
||||||
|
type: "connect",
|
||||||
|
sdpObj: {
|
||||||
|
sdp: sdpObj,
|
||||||
|
type: "offer"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peer.onDataChannel = (dc) -> {
|
||||||
|
onClientConnect(peer, dc);
|
||||||
|
};
|
||||||
|
case _: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isMP = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function joinServer(connectedCb:() -> Void) {
|
||||||
|
masterWs = new WebSocket("ws://localhost:8080");
|
||||||
|
|
||||||
|
client = new RTCPeerConnection(["stun.l.google.com:19302"], "0.0.0.0");
|
||||||
|
var candidates = [];
|
||||||
|
|
||||||
|
client.onLocalCandidate = (c) -> {
|
||||||
|
if (c != "")
|
||||||
|
candidates.push('a=${c}');
|
||||||
|
}
|
||||||
|
client.onGatheringStateChange = (s) -> {
|
||||||
|
if (s == RTC_GATHERING_COMPLETE) {
|
||||||
|
var sdpObj = StringTools.trim(client.localDescription);
|
||||||
|
sdpObj = sdpObj + '\r\n' + candidates.join('\r\n');
|
||||||
|
masterWs.send(Json.stringify({
|
||||||
|
type: "connect",
|
||||||
|
sdpObj: {
|
||||||
|
sdp: sdpObj,
|
||||||
|
type: "offer"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
masterWs.onmessage = (m) -> {
|
||||||
|
switch (m) {
|
||||||
|
case StrMessage(content):
|
||||||
|
var conts = Json.parse(content);
|
||||||
|
client.setRemoteDescription(conts.sdp, conts.type);
|
||||||
|
case _: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clientDatachannel = client.createDatachannel("mp");
|
||||||
|
clientDatachannel.onOpen = (n) -> {
|
||||||
|
clients.set(client, new ClientConnection(0, client, clientDatachannel)); // host is always 0
|
||||||
|
clientIdMap[0] = clients[client];
|
||||||
|
onConnectedToServer();
|
||||||
|
haxe.Timer.delay(() -> connectedCb(), 1500); // 1.5 second delay to do the RTT calculation
|
||||||
|
}
|
||||||
|
clientDatachannel.onMessage = (b) -> {
|
||||||
|
onPacketReceived(client, clientDatachannel, new haxe.io.BytesInput(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
isMP = true;
|
||||||
|
isHost = false;
|
||||||
|
isClient = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function onClientConnect(c:RTCPeerConnection, dc:RTCDataChannel) {
|
||||||
|
clientId += 1;
|
||||||
|
clients.set(c, new ClientConnection(clientId, c, dc));
|
||||||
|
clientIdMap[clientId] = clients[c];
|
||||||
|
dc.onMessage = (msgBytes) -> {
|
||||||
|
onPacketReceived(c, dc, new haxe.io.BytesInput(msgBytes));
|
||||||
|
}
|
||||||
|
var b = haxe.io.Bytes.alloc(3);
|
||||||
|
b.set(0, ClientIdAssign);
|
||||||
|
b.setUInt16(1, clientId);
|
||||||
|
dc.sendBytes(b);
|
||||||
|
Console.log("Client has connected!");
|
||||||
|
// Send the ping packet to calculcate the RTT
|
||||||
|
var b = haxe.io.Bytes.alloc(2);
|
||||||
|
b.set(0, Ping);
|
||||||
|
b.set(1, 3); // Count
|
||||||
|
clients[c].pingSendTime = Sys.time();
|
||||||
|
dc.sendBytes(b);
|
||||||
|
Console.log("Sending ping packet!");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function onConnectedToServer() {
|
||||||
|
Console.log("Connected to the server!");
|
||||||
|
// Send the ping packet to calculate the RTT
|
||||||
|
var b = haxe.io.Bytes.alloc(2);
|
||||||
|
b.set(0, Ping);
|
||||||
|
b.set(1, 3); // Count
|
||||||
|
clients[client].pingSendTime = Sys.time();
|
||||||
|
clientDatachannel.sendBytes(b);
|
||||||
|
Console.log("Sending ping packet!");
|
||||||
|
}
|
||||||
|
|
||||||
|
static function onPacketReceived(c:RTCPeerConnection, dc:RTCDataChannel, input:haxe.io.BytesInput) {
|
||||||
|
var packetType = input.readByte();
|
||||||
|
switch (packetType) {
|
||||||
|
case NetCommand:
|
||||||
|
NetCommands.readPacket(input);
|
||||||
|
|
||||||
|
case ClientIdAssign:
|
||||||
|
clientId = input.readUInt16();
|
||||||
|
Console.log('Client ID set to ${clientId}');
|
||||||
|
|
||||||
|
case Ping:
|
||||||
|
var pingLeft = input.readByte();
|
||||||
|
Console.log("Got ping packet!");
|
||||||
|
var b = haxe.io.Bytes.alloc(2);
|
||||||
|
b.set(0, PingBack);
|
||||||
|
b.set(1, pingLeft);
|
||||||
|
dc.sendBytes(b);
|
||||||
|
|
||||||
|
case PingBack:
|
||||||
|
var pingLeft = input.readByte();
|
||||||
|
Console.log("Got pingback packet!");
|
||||||
|
var conn = clients[c];
|
||||||
|
var now = Sys.time();
|
||||||
|
conn._rttRecords.push((now - conn.pingSendTime));
|
||||||
|
if (pingLeft > 0) {
|
||||||
|
conn.pingSendTime = now;
|
||||||
|
var b = haxe.io.Bytes.alloc(2);
|
||||||
|
b.set(0, Ping);
|
||||||
|
b.set(1, pingLeft - 1);
|
||||||
|
dc.sendBytes(b);
|
||||||
|
} else {
|
||||||
|
for (r in conn._rttRecords)
|
||||||
|
conn.rtt += r;
|
||||||
|
conn.rtt /= conn._rttRecords.length;
|
||||||
|
Console.log('Got RTT ${conn.rtt} for client ${conn.id}');
|
||||||
|
}
|
||||||
|
|
||||||
|
case _:
|
||||||
|
trace("unknown command: " + packetType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function sendPacketToAll(packetData:haxe.io.BytesOutput) {
|
||||||
|
var bytes = packetData.getBytes();
|
||||||
|
for (c => v in clients) {
|
||||||
|
v.datachannel.sendBytes(packetData.getBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function sendPacketToHost(packetData:haxe.io.BytesOutput) {
|
||||||
|
var bytes = packetData.getBytes();
|
||||||
|
clientDatachannel.sendBytes(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/net/NetCommands.hx
Normal file
54
src/net/NetCommands.hx
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
package net;
|
||||||
|
|
||||||
|
import net.Net.GameplayState;
|
||||||
|
import net.Net.NetPacketType;
|
||||||
|
import gui.MultiplayerLevelSelectGui;
|
||||||
|
import src.MarbleGame;
|
||||||
|
|
||||||
|
@:build(net.RPCMacro.build())
|
||||||
|
class NetCommands {
|
||||||
|
@:rpc(server) public static function setLobbyLevelIndex(i:Int) {
|
||||||
|
MultiplayerLevelSelectGui.setLevelFn(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@:rpc(server) public static function playLevel() {
|
||||||
|
MultiplayerLevelSelectGui.playSelectedLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@:rpc(server) public static function setNetworkRNG(rng:Float) {
|
||||||
|
Net.networkRNG = rng;
|
||||||
|
if (MarbleGame.instance.world != null) {
|
||||||
|
var gameMode = MarbleGame.instance.world.gameMode;
|
||||||
|
if (gameMode is modes.HuntMode) {
|
||||||
|
var hunt:modes.HuntMode = cast gameMode;
|
||||||
|
@:privateAccess hunt.rng.setSeed(cast rng);
|
||||||
|
@:privateAccess hunt.rng2.setSeed(cast rng);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:rpc(client) public static function clientIsReady(clientId:Int) {
|
||||||
|
if (Net.isHost) {
|
||||||
|
Net.clientIdMap[clientId].ready();
|
||||||
|
var allReady = true;
|
||||||
|
for (id => client in Net.clientIdMap) {
|
||||||
|
if (client.state != GameplayState.GAME)
|
||||||
|
allReady = false;
|
||||||
|
}
|
||||||
|
if (allReady) {
|
||||||
|
if (MarbleGame.instance.world != null) {
|
||||||
|
MarbleGame.instance.world.allClientsReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:rpc(server) public static function setStartTime(t:Float) {
|
||||||
|
if (MarbleGame.instance.world != null) {
|
||||||
|
if (Net.isClient) {
|
||||||
|
t -= Net.clientIdMap[0].rtt / 2; // Subtract receving time
|
||||||
|
}
|
||||||
|
MarbleGame.instance.world.startRealTime = MarbleGame.instance.world.timeState.timeSinceLoad + t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
src/net/RPCMacro.hx
Normal file
136
src/net/RPCMacro.hx
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
package net;
|
||||||
|
|
||||||
|
import haxe.macro.Context;
|
||||||
|
import haxe.macro.Expr;
|
||||||
|
|
||||||
|
class RPCMacro {
|
||||||
|
macro static public function build():Array<Field> {
|
||||||
|
var fields = Context.getBuildFields();
|
||||||
|
|
||||||
|
var rpcFnId = 1;
|
||||||
|
|
||||||
|
var idtoFn:Map<Int, {
|
||||||
|
name:String,
|
||||||
|
serialize:Array<Expr>,
|
||||||
|
deserialize:Array<Expr>
|
||||||
|
}> = new Map();
|
||||||
|
|
||||||
|
for (field in fields) {
|
||||||
|
if (field.meta.length > 0 && field.meta[0].name == ':rpc') {
|
||||||
|
switch (field.kind) {
|
||||||
|
case FFun(f):
|
||||||
|
{
|
||||||
|
var serializeFns = [];
|
||||||
|
var deserializeFns = [];
|
||||||
|
var callExprs = [];
|
||||||
|
for (arg in f.args) {
|
||||||
|
var argName = arg.name;
|
||||||
|
switch (arg.type) {
|
||||||
|
case TPath({
|
||||||
|
name: 'Int'
|
||||||
|
}): {
|
||||||
|
deserializeFns.push(macro var $argName = stream.readInt32());
|
||||||
|
callExprs.push(macro $i{argName});
|
||||||
|
serializeFns.push(macro stream.writeInt32($i{argName}));
|
||||||
|
}
|
||||||
|
|
||||||
|
case TPath({
|
||||||
|
name: 'Float'
|
||||||
|
}): {
|
||||||
|
deserializeFns.push(macro var $argName = stream.readFloat());
|
||||||
|
callExprs.push(macro $i{argName});
|
||||||
|
serializeFns.push(macro stream.writeFloat($i{argName}));
|
||||||
|
}
|
||||||
|
|
||||||
|
case _: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deserializeFns.push(macro {
|
||||||
|
$i{field.name}($a{callExprs});
|
||||||
|
});
|
||||||
|
idtoFn.set(rpcFnId, {
|
||||||
|
name: field.name,
|
||||||
|
serialize: serializeFns,
|
||||||
|
deserialize: deserializeFns
|
||||||
|
});
|
||||||
|
|
||||||
|
var directionParam = field.meta[0].params[0].expr;
|
||||||
|
switch (directionParam) {
|
||||||
|
case EConst(CIdent("server")):
|
||||||
|
var lastExpr = macro {
|
||||||
|
if (Net.isHost) {
|
||||||
|
var stream = new haxe.io.BytesOutput();
|
||||||
|
stream.writeByte(NetPacketType.NetCommand);
|
||||||
|
stream.writeByte($v{rpcFnId});
|
||||||
|
$b{serializeFns};
|
||||||
|
Net.sendPacketToAll(stream);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
f.expr = macro $b{[f.expr, lastExpr]};
|
||||||
|
|
||||||
|
case EConst(CIdent("client")):
|
||||||
|
var lastExpr = macro {
|
||||||
|
if (!Net.isHost) {
|
||||||
|
var stream = new haxe.io.BytesOutput();
|
||||||
|
stream.writeByte(NetPacketType.NetCommand);
|
||||||
|
stream.writeByte($v{rpcFnId});
|
||||||
|
$b{serializeFns};
|
||||||
|
Net.sendPacketToHost(stream);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
f.expr = macro $b{[f.expr, lastExpr]};
|
||||||
|
|
||||||
|
case _:
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcFnId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
case _:
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cases:Array<Case> = [];
|
||||||
|
for (k => v in idtoFn) {
|
||||||
|
cases.push({
|
||||||
|
values: [macro $v{k}],
|
||||||
|
expr: macro {
|
||||||
|
$b{v.deserialize}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var deserializeField:Field = {
|
||||||
|
name: "readPacket",
|
||||||
|
pos: Context.currentPos(),
|
||||||
|
access: [APublic, AStatic],
|
||||||
|
kind: FFun({
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "stream",
|
||||||
|
type: haxe.macro.TypeTools.toComplexType(Context.getType('haxe.io.Input'))
|
||||||
|
}
|
||||||
|
],
|
||||||
|
expr: macro {
|
||||||
|
var fnId = stream.readByte();
|
||||||
|
|
||||||
|
$e{
|
||||||
|
{
|
||||||
|
expr: ESwitch(macro fnId, cases, null),
|
||||||
|
pos: Context.currentPos()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
fields.push(deserializeField);
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/rewind/InputRecorder.hx
Normal file
62
src/rewind/InputRecorder.hx
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
package rewind;
|
||||||
|
|
||||||
|
import src.MarbleWorld;
|
||||||
|
import h3d.Vector;
|
||||||
|
import src.Marble.Move;
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
class InputRecorderFrame {
|
||||||
|
var time:Float;
|
||||||
|
var move:Move;
|
||||||
|
var marbleAxes:Array<Vector>;
|
||||||
|
var pos:Vector;
|
||||||
|
var velocity:Vector;
|
||||||
|
|
||||||
|
public function new() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InputRecorder {
|
||||||
|
var frames:Array<InputRecorderFrame>;
|
||||||
|
var level:MarbleWorld;
|
||||||
|
|
||||||
|
public function new(level:MarbleWorld) {
|
||||||
|
frames = [];
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function recordInput(t:Float) {
|
||||||
|
var frame = new InputRecorderFrame();
|
||||||
|
frame.time = t;
|
||||||
|
frame.move = level.marble.recordMove();
|
||||||
|
frames.push(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function recordMarble() {
|
||||||
|
frames[frames.length - 1].pos = @:privateAccess level.marble.newPos?.clone();
|
||||||
|
frames[frames.length - 1].velocity = level.marble.velocity.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function recordAxis(axis:Array<Vector>) {
|
||||||
|
frames[frames.length - 1].marbleAxes = axis.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMovesFrom(t:Float) {
|
||||||
|
if (frames.length == 0)
|
||||||
|
return [];
|
||||||
|
var start = 0;
|
||||||
|
var end = frames.length - 1;
|
||||||
|
var mid = Std.int(frames.length / 2);
|
||||||
|
while (end - start > 1) {
|
||||||
|
mid = Std.int((start / 2) + (end / 2));
|
||||||
|
if (frames[mid].time < t) {
|
||||||
|
start = mid + 1;
|
||||||
|
} else if (frames[mid].time > t) {
|
||||||
|
end = mid - 1;
|
||||||
|
} else {
|
||||||
|
start = end = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames.slice(start - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue