mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2026-04-27 21:21:41 +00:00
add initial netcode
This commit is contained in:
parent
2e9917d9f1
commit
3120674b66
40 changed files with 5957 additions and 706 deletions
|
|
@ -56,6 +56,10 @@ class Console {
|
|||
return Std.int((haxe.Timer.stamp() - timeSinceStart) * 1000) / 1000;
|
||||
}
|
||||
|
||||
public static inline function time() {
|
||||
return haxe.Timer.stamp();
|
||||
}
|
||||
|
||||
function addEntry(type:String, msg:String) {
|
||||
var e = new ConsoleEntry(getTime(), type, msg);
|
||||
entries.push(e);
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ class DifBuilder {
|
|||
var difresource = ResourceLoader.loadInterior(path);
|
||||
difresource.acquire();
|
||||
var dif = difresource.resource;
|
||||
dumbDownDif(dif);
|
||||
var geo = so == -1 ? dif.interiors[0] : dif.subObjects[so];
|
||||
var triangles = [];
|
||||
var textures = [];
|
||||
|
|
@ -748,4 +749,55 @@ class DifBuilder {
|
|||
worker.run();
|
||||
});
|
||||
}
|
||||
|
||||
// Keeps only relevant parts of the dif to reduce memory footprint
|
||||
static function dumbDownDif(dif:Dif) {
|
||||
dif.aiSpecialNodes = null;
|
||||
dif.forceFields = null;
|
||||
dif.triggers = null;
|
||||
dif.gameEntities = null;
|
||||
dif.interiorPathfollowers = null;
|
||||
dif.triggers = null;
|
||||
dif.vehicleCollision = null;
|
||||
for (itr in dif.interiors.concat(dif.subObjects)) {
|
||||
itr.alarmAmbientColor = null;
|
||||
itr.alarmLMapIndices = null;
|
||||
itr.animatedLights = null;
|
||||
itr.baseAmbientColor = null;
|
||||
itr.bspNodes = null;
|
||||
itr.bspSolidLeaves = null;
|
||||
itr.convexHullEmitStrings = null;
|
||||
itr.convexHulls = null;
|
||||
itr.coordBinIndices = null;
|
||||
itr.boundingSphere = null;
|
||||
itr.coordBins = null;
|
||||
itr.edges = null;
|
||||
itr.edges2 = null;
|
||||
itr.hullEmitStringIndices = null;
|
||||
itr.hullIndices = null;
|
||||
itr.hullPlaneIndices = null;
|
||||
itr.hullSurfaceIndices = null;
|
||||
itr.lightMaps = null;
|
||||
itr.lightStates = null;
|
||||
itr.nameBuffer = null;
|
||||
itr.normalIndices = null;
|
||||
itr.normalLMapIndices = null;
|
||||
itr.nullSurfaces = null;
|
||||
itr.pointVisibilities = null;
|
||||
itr.polyListPlanes = null;
|
||||
itr.polyListPoints = null;
|
||||
itr.polyListStrings = null;
|
||||
itr.portals = null;
|
||||
itr.solidLeafSurfaces = null;
|
||||
itr.stateDataBuffers = null;
|
||||
itr.zones = null;
|
||||
itr.zoneSurfaces = null;
|
||||
itr.zoneStaticMeshes = null;
|
||||
itr.windingIndices = null;
|
||||
itr.texNormals = null;
|
||||
itr.texMatrices = null;
|
||||
itr.texMatIndices = null;
|
||||
itr.stateDatas = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import h3d.Vector;
|
|||
import src.ProfilerUI;
|
||||
import src.Gamepad;
|
||||
import src.Http;
|
||||
import datachannel.RTC;
|
||||
|
||||
class Main extends hxd.App {
|
||||
var marbleGame:MarbleGame;
|
||||
|
|
@ -72,6 +73,7 @@ class Main extends hxd.App {
|
|||
#end
|
||||
|
||||
try {
|
||||
RTC.init();
|
||||
Http.init();
|
||||
haxe.MainLoop.add(() -> Http.loop());
|
||||
Settings.init();
|
||||
|
|
@ -126,6 +128,7 @@ class Main extends hxd.App {
|
|||
// marbleGame.update(1 / 60);
|
||||
// timeAccumulator -= 1 / 60;
|
||||
// }
|
||||
RTC.processEvents();
|
||||
marbleGame.update(dt);
|
||||
// } catch (e) {
|
||||
// Console.error(e.message);
|
||||
|
|
|
|||
506
src/Marble.hx
506
src/Marble.hx
|
|
@ -1,5 +1,14 @@
|
|||
package src;
|
||||
|
||||
import net.Net;
|
||||
import gui.MarbleSelectGui;
|
||||
import net.NetPacket.MarbleNetFlags;
|
||||
import net.BitStream.OutputBitStream;
|
||||
import net.ClientConnection;
|
||||
import net.ClientConnection.GameConnection;
|
||||
import net.NetPacket.MarbleUpdatePacket;
|
||||
import net.MoveManager;
|
||||
import net.MoveManager.NetMove;
|
||||
import collision.CollisionPool;
|
||||
import collision.CollisionHull;
|
||||
import dif.Plane;
|
||||
|
|
@ -63,14 +72,7 @@ import src.ResourceLoaderWorker;
|
|||
import src.InteriorObject;
|
||||
import src.Console;
|
||||
import src.Gamepad;
|
||||
|
||||
class Move {
|
||||
public var d:Vector;
|
||||
public var jump:Bool;
|
||||
public var powerup:Bool;
|
||||
|
||||
public function new() {}
|
||||
}
|
||||
import net.Move;
|
||||
|
||||
enum Mode {
|
||||
Start;
|
||||
|
|
@ -314,6 +316,7 @@ class Marble extends GameObject {
|
|||
|
||||
public var cubemapRenderer:CubemapRenderer;
|
||||
|
||||
var connection:GameConnection;
|
||||
var moveMotionDir:Vector;
|
||||
var lastMove:Move;
|
||||
var isNetUpdate:Bool = false;
|
||||
|
|
@ -328,6 +331,7 @@ class Marble extends GameObject {
|
|||
this.velocity = new Vector();
|
||||
this.omega = new Vector();
|
||||
this.camera = new CameraController(cast this);
|
||||
this.isCollideable = true;
|
||||
|
||||
this.bounceEmitterData = new ParticleData();
|
||||
this.bounceEmitterData.identifier = "MarbleBounceParticle";
|
||||
|
|
@ -362,11 +366,13 @@ class Marble extends GameObject {
|
|||
this.helicopterSound.pause = true;
|
||||
}
|
||||
|
||||
public function init(level:MarbleWorld, onFinish:Void->Void) {
|
||||
public function init(level:MarbleWorld, connection:GameConnection, onFinish:Void->Void) {
|
||||
this.level = level;
|
||||
if (this.level != null)
|
||||
this.collisionWorld = this.level.collisionWorld;
|
||||
|
||||
this.connection = connection;
|
||||
|
||||
var isUltra = level.mission.game.toLowerCase() == "ultra";
|
||||
|
||||
this.posStore = new Vector();
|
||||
|
|
@ -375,9 +381,16 @@ class Marble extends GameObject {
|
|||
this.netCorrected = false;
|
||||
|
||||
var marbleDts = new DtsObject();
|
||||
Console.log("Marble: " + Settings.optionsSettings.marbleModel + " (" + Settings.optionsSettings.marbleSkin + ")");
|
||||
marbleDts.dtsPath = Settings.optionsSettings.marbleModel;
|
||||
marbleDts.matNameOverride.set("base.marble", Settings.optionsSettings.marbleSkin + ".marble");
|
||||
if (connection == null) {
|
||||
Console.log("Marble: " + Settings.optionsSettings.marbleModel + " (" + Settings.optionsSettings.marbleSkin + ")");
|
||||
marbleDts.dtsPath = Settings.optionsSettings.marbleModel;
|
||||
marbleDts.matNameOverride.set("base.marble", Settings.optionsSettings.marbleSkin + ".marble");
|
||||
} else {
|
||||
var marbleData = MarbleSelectGui.marbleData[0][connection.getMarbleId()]; // FIXME category support
|
||||
Console.log("Marble: " + marbleData.dts + " (" + marbleData.skin + ")");
|
||||
marbleDts.dtsPath = marbleData.dts;
|
||||
marbleDts.matNameOverride.set("base.marble", marbleData.skin + ".marble");
|
||||
}
|
||||
marbleDts.showSequences = false;
|
||||
marbleDts.useInstancing = false;
|
||||
marbleDts.init(null, () -> {}); // SYNC
|
||||
|
|
@ -858,7 +871,7 @@ class Marble extends GameObject {
|
|||
}
|
||||
if (sv < this._jumpImpulse) {
|
||||
this.velocity.load(this.velocity.add(bestContact.normal.multiply((this._jumpImpulse - sv))));
|
||||
if (!playedSounds.contains("data/sound/jump.wav")) {
|
||||
if (!playedSounds.contains("data/sound/jump.wav") && !this.isNetUpdate && this.controllable) {
|
||||
AudioManager.playSound(ResourceLoader.getResource("data/sound/jump.wav", ResourceLoader.getAudio, this.soundResources));
|
||||
playedSounds.push("data/sound/jump.wav");
|
||||
}
|
||||
|
|
@ -937,6 +950,8 @@ class Marble extends GameObject {
|
|||
}
|
||||
|
||||
function bounceEmitter(speed:Float, normal:Vector) {
|
||||
if (!this.controllable || this.isNetUpdate)
|
||||
return;
|
||||
if (this.bounceEmitDelay == 0 && this._minBounceSpeed <= speed) {
|
||||
this.level.particleManager.createEmitter(bounceParticleOptions, this.bounceEmitterData, this.getAbsPos().getPosition());
|
||||
this.bounceEmitDelay = 0.3;
|
||||
|
|
@ -969,6 +984,8 @@ class Marble extends GameObject {
|
|||
}
|
||||
|
||||
function playBoundSound(time:Float, contactVel:Float) {
|
||||
if (this.isNetUpdate)
|
||||
return;
|
||||
if (minVelocityBounceSoft <= contactVel) {
|
||||
var hardBounceSpeed = minVelocityBounceHard;
|
||||
var bounceSoundNum = Math.floor(Math.random() * 4);
|
||||
|
|
@ -1486,6 +1503,21 @@ class Marble extends GameObject {
|
|||
|
||||
var piTime = timeRemaining;
|
||||
|
||||
if (this.isNetUpdate) {
|
||||
lastMove = m;
|
||||
}
|
||||
|
||||
if (m == null) {
|
||||
m = new Move();
|
||||
m.d = new Vector();
|
||||
}
|
||||
|
||||
if (this.blastTicks < (30000 >> 5))
|
||||
this.blastTicks += 1;
|
||||
|
||||
if (Net.isClient)
|
||||
this.serverTicks++;
|
||||
|
||||
_bounceYet = false;
|
||||
|
||||
var contactTime = 0.0;
|
||||
|
|
@ -1493,7 +1525,8 @@ class Marble extends GameObject {
|
|||
|
||||
var passedTime = timeState.currentAttemptTime;
|
||||
|
||||
var oldPos = this.getAbsPos().getPosition().clone();
|
||||
oldPos = this.collider.transform.getPosition();
|
||||
prevRot = this.getRotationQuat().clone();
|
||||
|
||||
if (this.controllable) {
|
||||
for (interior in pathedInteriors) {
|
||||
|
|
@ -1502,6 +1535,14 @@ class Marble extends GameObject {
|
|||
}
|
||||
}
|
||||
|
||||
// Blast
|
||||
if (m != null && m.blast) {
|
||||
this.useBlast(timeState);
|
||||
if (level.isRecording) {
|
||||
level.replay.recordMarbleStateFlags(false, false, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
if (timeRemaining <= 0)
|
||||
break;
|
||||
|
|
@ -1566,7 +1607,7 @@ class Marble extends GameObject {
|
|||
|
||||
velocity.w = 0;
|
||||
|
||||
var pos = this.getAbsPos().getPosition();
|
||||
var pos = this.collider.transform.getPosition();
|
||||
this.prevPos = pos.clone();
|
||||
|
||||
var tdiff = timeStep;
|
||||
|
|
@ -1605,24 +1646,36 @@ class Marble extends GameObject {
|
|||
var quat = new Quat();
|
||||
quat.initRotation(omega.x * timeStep, omega.y * timeStep, omega.z * timeStep);
|
||||
quat.multiply(quat, rot);
|
||||
this.setRotationQuat(quat);
|
||||
if (!Net.isMP)
|
||||
this.setRotationQuat(quat);
|
||||
|
||||
var totMatrix = quat.toMatrix();
|
||||
newPos.w = 1; // Fix shit blowing up
|
||||
totMatrix.setPosition(newPos);
|
||||
|
||||
this.setPosition(newPos.x, newPos.y, newPos.z);
|
||||
if (!Net.isMP)
|
||||
this.setPosition(newPos.x, newPos.y, newPos.z);
|
||||
|
||||
this.collider.setTransform(totMatrix);
|
||||
this.collisionWorld.updateTransform(this.collider);
|
||||
this.collider.velocity = this.velocity;
|
||||
|
||||
if (this.heldPowerup != null && m.powerup && !this.outOfBounds) {
|
||||
if (this.heldPowerup != null
|
||||
&& (m.powerup || (Net.isClient && this.serverUsePowerup && !this.controllable))
|
||||
&& !this.outOfBounds) {
|
||||
var pTime = timeState.clone();
|
||||
pTime.dt = timeStep;
|
||||
pTime.currentAttemptTime = passedTime;
|
||||
var netUpdate = this.isNetUpdate;
|
||||
if (this.serverUsePowerup)
|
||||
this.isNetUpdate = false;
|
||||
this.heldPowerup.use(this, pTime);
|
||||
this.isNetUpdate = netUpdate;
|
||||
this.heldPowerup = null;
|
||||
this.serverUsePowerup = false;
|
||||
if (!this.isNetUpdate) {
|
||||
this.netFlags |= MarbleNetFlags.PickupPowerup | MarbleNetFlags.UsePowerup;
|
||||
}
|
||||
if (this.level.isRecording) {
|
||||
this.level.replay.recordPowerupPickup(null);
|
||||
}
|
||||
|
|
@ -1654,49 +1707,333 @@ class Marble extends GameObject {
|
|||
}
|
||||
this.queuedContacts = [];
|
||||
|
||||
var newPos = this.getAbsPos().getPosition().clone();
|
||||
newPos = this.collider.transform.getPosition(); // this.getAbsPos().getPosition().clone();
|
||||
|
||||
if (this.controllable && this.prevPos != null) {
|
||||
if (this.prevPos != null && this.level != null) {
|
||||
var tempTimeState = timeState.clone();
|
||||
tempTimeState.currentAttemptTime = passedTime;
|
||||
this.level.callCollisionHandlers(cast this, tempTimeState, oldPos, newPos);
|
||||
}
|
||||
|
||||
this.updateRollSound(timeState, contactTime / timeState.dt, this._slipAmount);
|
||||
|
||||
// if (this.megaMarbleUseTick > 0) {
|
||||
// if (Net.isHost) {
|
||||
// if ((timeState.ticks - this.megaMarbleUseTick) <= 312 && this.megaMarbleUseTick > 0) {
|
||||
// this._radius = 0.675;
|
||||
// this.collider.radius = 0.675;
|
||||
// } else if ((timeState.ticks - this.megaMarbleUseTick) > 312) {
|
||||
// this.collider.radius = this._radius = 0.3;
|
||||
// if (!this.isNetUpdate && this.controllable)
|
||||
// AudioManager.playSound(ResourceLoader.getResource("data/sound/MegaShrink.wav", ResourceLoader.getAudio, this.soundResources), null,
|
||||
// false);
|
||||
// this.megaMarbleUseTick = 0;
|
||||
// this.netFlags |= MarbleNetFlags.DoMega;
|
||||
// }
|
||||
// }
|
||||
// if (Net.isClient) {
|
||||
// if (this.serverTicks - this.megaMarbleUseTick <= 312 && this.megaMarbleUseTick > 0) {
|
||||
// this._radius = 0.675;
|
||||
// this.collider.radius = 0.675;
|
||||
// } else {
|
||||
// this.collider.radius = this._radius = 0.3;
|
||||
// if (!this.isNetUpdate && this.controllable)
|
||||
// AudioManager.playSound(ResourceLoader.getResource("data/sound/MegaShrink.wav", ResourceLoader.getAudio, this.soundResources), null,
|
||||
// false);
|
||||
// this.megaMarbleUseTick = 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (Net.isClient && this.megaMarbleUseTick == 0) {
|
||||
// this.collider.radius = this._radius = 0.3;
|
||||
// }
|
||||
|
||||
if (Net.isMP) {
|
||||
if (m.jump && this.outOfBounds) {
|
||||
this.level.cancel(this.oobSchedule);
|
||||
this.level.restart(cast this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MP Only Functions
|
||||
public inline function clearNetFlags() {
|
||||
this.netFlags = 0;
|
||||
}
|
||||
|
||||
public function packUpdate(move:NetMove, timeState:TimeState) {
|
||||
var b = new OutputBitStream();
|
||||
b.writeByte(NetPacketType.MarbleUpdate);
|
||||
var marbleUpdate = new MarbleUpdatePacket();
|
||||
marbleUpdate.clientId = connection != null ? connection.id : 0;
|
||||
marbleUpdate.serverTicks = timeState.ticks;
|
||||
marbleUpdate.position = this.newPos;
|
||||
marbleUpdate.velocity = this.velocity;
|
||||
marbleUpdate.omega = this.omega;
|
||||
marbleUpdate.move = move;
|
||||
marbleUpdate.moveQueueSize = this.connection != null ? this.connection.moveManager.getQueueSize() : 255;
|
||||
marbleUpdate.blastAmount = this.blastTicks;
|
||||
marbleUpdate.blastTick = this.blastUseTick;
|
||||
marbleUpdate.heliTick = this.helicopterUseTick;
|
||||
marbleUpdate.megaTick = this.megaMarbleUseTick;
|
||||
marbleUpdate.oob = this.outOfBounds;
|
||||
marbleUpdate.powerUpId = this.heldPowerup != null ? this.heldPowerup.netIndex : 0x1FF;
|
||||
marbleUpdate.netFlags = this.netFlags;
|
||||
marbleUpdate.gravityDirection = this.currentUp;
|
||||
marbleUpdate.serialize(b);
|
||||
return b.getBytes();
|
||||
}
|
||||
|
||||
public function unpackUpdate(p:MarbleUpdatePacket) {
|
||||
// Assume packet header is already read
|
||||
// Check if we aren't colliding with a marble
|
||||
// for (marble in this.level.collisionWorld.marbleEntities) {
|
||||
// if (marble != this.collider && marble.transform.getPosition().distance(p.position) < marble.radius + this._radius) {
|
||||
// Console.log("Marble updated inside another one!");
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
this.serverTicks = p.serverTicks;
|
||||
this.recvServerTick = p.serverTicks;
|
||||
// this.oldPos = this.newPos;
|
||||
// this.newPos = p.position;
|
||||
this.collider.transform.setPosition(p.position);
|
||||
this.velocity = p.velocity;
|
||||
this.omega = p.omega;
|
||||
this.blastTicks = p.blastAmount;
|
||||
this.blastUseTick = p.blastTick;
|
||||
this.helicopterUseTick = p.heliTick;
|
||||
this.megaMarbleUseTick = p.megaTick;
|
||||
this.serverUsePowerup = p.netFlags & MarbleNetFlags.UsePowerup > 0;
|
||||
// this.currentUp = p.gravityDirection;
|
||||
this.level.setUp(cast this, p.gravityDirection, this.level.timeState);
|
||||
if (this.outOfBounds && !p.oob && this.controllable)
|
||||
@:privateAccess this.level.playGui.setCenterText('');
|
||||
this.outOfBounds = p.oob;
|
||||
this.camera.oob = p.oob;
|
||||
if (p.powerUpId == 0x1FF) {
|
||||
if (!this.serverUsePowerup)
|
||||
this.level.deselectPowerUp(cast this);
|
||||
else
|
||||
Console.log("Using powerup");
|
||||
} else {
|
||||
this.level.pickUpPowerUp(cast this, this.level.powerUps[p.powerUpId]);
|
||||
}
|
||||
if (p.moveQueueSize == 0 && this.connection != null) {
|
||||
// Pad null move on client
|
||||
this.connection.moveManager.duplicateLastMove();
|
||||
}
|
||||
// if (Net.isClient && !this.controllable && (this.serverTicks - this.blastUseTick) < 12) {
|
||||
// var ticksSince = (this.serverTicks - this.blastUseTick);
|
||||
// if (ticksSince >= 0) {
|
||||
// this.blastWave.doSequenceOnceBeginTime = this.level.timeState.timeSinceLoad - ticksSince * 0.032;
|
||||
// this.blastUseTime = this.level.timeState.currentAttemptTime - ticksSince * 0.032;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (this.controllable && Net.isClient) {
|
||||
// // We are client, need to do something about the queue
|
||||
// var mm = Net.clientConnection.moveManager;
|
||||
// // trace('Queue size: ${mm.getQueueSize()}, server: ${p.moveQueueSize}');
|
||||
// if (mm.getQueueSize() / p.moveQueueSize < 2) {
|
||||
// mm.stall = true;
|
||||
// } else {
|
||||
// mm.stall = false;
|
||||
// }
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
function calculateNetSmooth() {
|
||||
if (this.netCorrected) {
|
||||
this.netCorrected = false;
|
||||
this.netSmoothOffset.load(this.lastRenderPos.sub(this.oldPos));
|
||||
// this.oldPos.load(this.posStore);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateServer(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array<PathedInterior>) {
|
||||
var move:NetMove = null;
|
||||
if (this.controllable && this.mode != Finish) {
|
||||
if (Net.isClient) {
|
||||
var axis = getMarbleAxis()[1];
|
||||
move = Net.clientConnection.recordMove(cast this, axis, timeState, recvServerTick);
|
||||
} else if (Net.isHost) {
|
||||
var axis = getMarbleAxis()[1];
|
||||
var innerMove = recordMove();
|
||||
if (MarbleGame.instance.paused) {
|
||||
innerMove.d.x = 0;
|
||||
innerMove.d.y = 0;
|
||||
innerMove.blast = innerMove.jump = innerMove.powerup = false;
|
||||
} else {
|
||||
var qx = Std.int((innerMove.d.x * 16) + 16);
|
||||
var qy = Std.int((innerMove.d.y * 16) + 16);
|
||||
innerMove.d.x = (qx - 16) / 16.0;
|
||||
innerMove.d.y = (qy - 16) / 16.0;
|
||||
}
|
||||
move = new NetMove(innerMove, axis, timeState, recvServerTick, 65535);
|
||||
}
|
||||
}
|
||||
var moveId = 65535;
|
||||
if (!this.controllable && this.connection != null && Net.isHost) {
|
||||
var nextMove = this.connection.getNextMove();
|
||||
// trace('Moves left: ${@:privateAccess this.connection.moveManager.queuedMoves.length}');
|
||||
if (nextMove == null) {
|
||||
var axis = moveMotionDir != null ? moveMotionDir : getMarbleAxis()[1];
|
||||
var innerMove = lastMove;
|
||||
if (innerMove == null) {
|
||||
innerMove = new Move();
|
||||
innerMove.d = new Vector(0, 0);
|
||||
}
|
||||
move = new NetMove(innerMove, axis, timeState, recvServerTick, 65535);
|
||||
} else {
|
||||
move = nextMove;
|
||||
moveMotionDir = nextMove.motionDir;
|
||||
moveId = nextMove.id;
|
||||
lastMove = move.move;
|
||||
}
|
||||
}
|
||||
if (move == null && !this.controllable) {
|
||||
var axis = moveMotionDir != null ? moveMotionDir : new Vector(0, -1, 0);
|
||||
var innerMove = lastMove;
|
||||
if (innerMove == null) {
|
||||
innerMove = new Move();
|
||||
innerMove.d = new Vector(0, 0);
|
||||
}
|
||||
move = new NetMove(innerMove, axis, timeState, recvServerTick, 65535);
|
||||
}
|
||||
|
||||
if (move != null) {
|
||||
playedSounds = [];
|
||||
advancePhysics(timeState, move.move, collisionWorld, pathedInteriors);
|
||||
physicsAccumulator = 0;
|
||||
} else {
|
||||
physicsAccumulator = 0;
|
||||
newPos.load(oldPos);
|
||||
}
|
||||
|
||||
return move;
|
||||
// if (Net.isHost) {
|
||||
// packets.push({b: packUpdate(move, timeState), c: this.connection != null ? this.connection.id : 0});
|
||||
// }
|
||||
}
|
||||
|
||||
public function updateClient(timeState:TimeState, pathedInteriors:Array<PathedInterior>) {
|
||||
calculateNetSmooth();
|
||||
this.level.updateBlast(cast this, timeState);
|
||||
|
||||
var newDt = 2.3 * (timeState.dt / 0.4);
|
||||
var smooth = 1.0 / (newDt * (newDt * 0.235 * newDt) + newDt + 1.0 + 0.48 * newDt * newDt);
|
||||
this.netSmoothOffset.scale(smooth);
|
||||
var smoothScale = this.netSmoothOffset.lengthSq();
|
||||
if (smoothScale < 0.1 || smoothScale > 10.0)
|
||||
this.netSmoothOffset.set(0, 0, 0);
|
||||
|
||||
if (oldPos != null && newPos != null) {
|
||||
var deltaT = physicsAccumulator / 0.032;
|
||||
if (Net.isClient && !this.controllable)
|
||||
deltaT *= 0.75; // Don't overshoot
|
||||
var renderPos = Util.lerpThreeVectors(this.oldPos, this.newPos, deltaT);
|
||||
if (Net.isClient) {
|
||||
renderPos.load(renderPos.add(this.netSmoothOffset));
|
||||
}
|
||||
this.setPosition(renderPos.x, renderPos.y, renderPos.z);
|
||||
this.lastRenderPos.load(renderPos);
|
||||
|
||||
var rot = this.getRotationQuat();
|
||||
var quat = new Quat();
|
||||
quat.initRotation(omega.x * timeState.dt, omega.y * timeState.dt, omega.z * timeState.dt);
|
||||
quat.multiply(quat, rot);
|
||||
this.setRotationQuat(quat);
|
||||
|
||||
var adt = timeState.clone();
|
||||
adt.dt = physicsAccumulator;
|
||||
for (pi in pathedInteriors) {
|
||||
pi.update(adt);
|
||||
}
|
||||
}
|
||||
physicsAccumulator += timeState.dt;
|
||||
|
||||
if (this.controllable && this.level != null && !this.level.rewinding) {
|
||||
// this.camera.startCenterCamera();
|
||||
this.camera.update(timeState.currentAttemptTime, timeState.dt);
|
||||
}
|
||||
|
||||
updatePowerupStates(timeState);
|
||||
|
||||
// if (isMegaMarbleEnabled(timeState)) {
|
||||
// this._marbleScale = this._defaultScale * 2.25;
|
||||
// } else {
|
||||
// this._marbleScale = this._defaultScale;
|
||||
// }
|
||||
|
||||
// var s = this._renderScale * this._renderScale;
|
||||
// if (s <= this._marbleScale * this._marbleScale)
|
||||
// s = 0.1;
|
||||
// else
|
||||
// s = 0.4;
|
||||
|
||||
// s = timeState.dt / s * 2.302585124969482;
|
||||
// s = 1.0 / (s * (s * 0.235 * s) + s + 1.0 + 0.48 * s * s);
|
||||
// this._renderScale *= s;
|
||||
// s = 1 - s;
|
||||
// this._renderScale += s * this._marbleScale;
|
||||
// var marbledts = cast(this.getChildAt(0), DtsObject);
|
||||
// marbledts.setScale(this._renderScale);
|
||||
|
||||
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 (@:privateAccess !MarbleGame.instance.world.playGui.isChatFocused()) {
|
||||
if (Key.isDown(Settings.controlsSettings.forward)) {
|
||||
move.d.x -= 1;
|
||||
}
|
||||
if (Key.isDown(Settings.controlsSettings.backward)) {
|
||||
move.d.x += 1;
|
||||
}
|
||||
if (Key.isDown(Settings.controlsSettings.left)) {
|
||||
move.d.y += 1;
|
||||
}
|
||||
if (Key.isDown(Settings.controlsSettings.right)) {
|
||||
move.d.y -= 1;
|
||||
}
|
||||
if (Key.isDown(Settings.controlsSettings.jump)
|
||||
|| MarbleGame.instance.touchInput.jumpButton.pressed
|
||||
|| Gamepad.isDown(Settings.gamepadSettings.jump)) {
|
||||
move.jump = true;
|
||||
}
|
||||
if ((!Util.isTouchDevice() && Key.isDown(Settings.controlsSettings.powerup))
|
||||
|| (Util.isTouchDevice() && MarbleGame.instance.touchInput.powerupButton.pressed)
|
||||
|| Gamepad.isDown(Settings.gamepadSettings.powerup)) {
|
||||
move.powerup = true;
|
||||
}
|
||||
|
||||
if (Key.isDown(Settings.controlsSettings.blast)
|
||||
|| (MarbleGame.instance.touchInput.blastbutton.pressed)
|
||||
|| Gamepad.isDown(Settings.gamepadSettings.blast))
|
||||
move.blast = true;
|
||||
|
||||
if (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;
|
||||
}
|
||||
|
||||
public function update(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array<PathedInterior>) {
|
||||
var move = new Move();
|
||||
move.d = new Vector();
|
||||
if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching) {
|
||||
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 move:Move = null;
|
||||
if (this.controllable && !this.level.isWatching) {
|
||||
move = recordMove();
|
||||
}
|
||||
|
||||
if (this.level.isWatching) {
|
||||
|
|
@ -1711,6 +2048,10 @@ class Marble extends GameObject {
|
|||
this.level.replay.recordMarbleInput(move.d.x, move.d.y);
|
||||
}
|
||||
}
|
||||
if (!this.controllable && (this.connection != null || this.level == null)) {
|
||||
move = new Move();
|
||||
move.d = new Vector(0, 0);
|
||||
}
|
||||
|
||||
playedSounds = [];
|
||||
advancePhysics(timeState, move, collisionWorld, pathedInteriors);
|
||||
|
|
@ -1738,7 +2079,7 @@ class Marble extends GameObject {
|
|||
this.camera.update(timeState.currentAttemptTime, timeState.dt);
|
||||
}
|
||||
|
||||
updatePowerupStates(timeState.currentAttemptTime, timeState.dt);
|
||||
updatePowerupStates(timeState);
|
||||
|
||||
if (this._radius != 0.6666 && timeState.currentAttemptTime - this.megaMarbleEnableTime < 10) {
|
||||
this._prevRadius = this._radius;
|
||||
|
|
@ -1766,30 +2107,30 @@ class Marble extends GameObject {
|
|||
// this.camera.target.load(this.getAbsPos().getPosition().toPoint());
|
||||
}
|
||||
|
||||
public function updatePowerupStates(currentTime:Float, dt:Float) {
|
||||
if (currentTime - this.shockAbsorberEnableTime < 5) {
|
||||
public function updatePowerupStates(timeState:TimeState) {
|
||||
if (timeState.currentAttemptTime - this.shockAbsorberEnableTime < 5) {
|
||||
this.shockabsorberSound.pause = false;
|
||||
} else {
|
||||
this.shockabsorberSound.pause = true;
|
||||
}
|
||||
if (currentTime - this.superBounceEnableTime < 5) {
|
||||
if (timeState.currentAttemptTime - this.superBounceEnableTime < 5) {
|
||||
this.superbounceSound.pause = false;
|
||||
} else {
|
||||
this.superbounceSound.pause = true;
|
||||
}
|
||||
|
||||
if (currentTime - this.shockAbsorberEnableTime < 5) {
|
||||
if (timeState.currentAttemptTime - this.shockAbsorberEnableTime < 5) {
|
||||
this.forcefield.setPosition(0, 0, 0);
|
||||
} else if (currentTime - this.superBounceEnableTime < 5) {
|
||||
} else if (timeState.currentAttemptTime - this.superBounceEnableTime < 5) {
|
||||
this.forcefield.setPosition(0, 0, 0);
|
||||
} else {
|
||||
this.forcefield.x = 1e8;
|
||||
this.forcefield.y = 1e8;
|
||||
this.forcefield.z = 1e8;
|
||||
}
|
||||
if (currentTime - this.helicopterEnableTime < 5) {
|
||||
if (timeState.currentAttemptTime - this.helicopterEnableTime < 5) {
|
||||
this.helicopter.setPosition(x, y, z);
|
||||
this.helicopter.setRotationQuat(this.level.getOrientationQuat(currentTime));
|
||||
this.helicopter.setRotationQuat(this.level.getOrientationQuat(timeState.currentAttemptTime));
|
||||
this.helicopterSound.pause = false;
|
||||
} else {
|
||||
this.helicopter.setPosition(1e8, 1e8, 1e8);
|
||||
|
|
@ -1807,18 +2148,18 @@ class Marble extends GameObject {
|
|||
}
|
||||
}
|
||||
|
||||
public function useBlast() {
|
||||
if (this.level.blastAmount < 0.2 || this.level.game != "ultra")
|
||||
public function useBlast(timeState:TimeState) {
|
||||
if (this.blastAmount < 0.2 || this.level.game != "ultra")
|
||||
return;
|
||||
var impulse = this.currentUp.multiply(Math.max(Math.sqrt(this.level.blastAmount), this.level.blastAmount) * 10);
|
||||
var impulse = this.currentUp.multiply(Math.max(Math.sqrt(this.blastAmount), this.blastAmount) * 10);
|
||||
this.applyImpulse(impulse);
|
||||
AudioManager.playSound(ResourceLoader.getResource('data/sound/blast.wav', ResourceLoader.getAudio, this.soundResources));
|
||||
this.level.particleManager.createEmitter(this.level.blastAmount > 1 ? blastMaxParticleOptions : blastParticleOptions,
|
||||
this.level.blastAmount > 1 ? blastMaxEmitterData : blastEmitterData, this.getAbsPos().getPosition(), () -> {
|
||||
this.level.particleManager.createEmitter(this.blastAmount > 1 ? blastMaxParticleOptions : blastParticleOptions,
|
||||
this.blastAmount > 1 ? blastMaxEmitterData : blastEmitterData, this.getAbsPos().getPosition(), () -> {
|
||||
this.getAbsPos().getPosition().add(this.currentUp.multiply(-this._radius * 0.4));
|
||||
},
|
||||
new Vector(1, 1, 1).add(new Vector(Math.abs(this.currentUp.x), Math.abs(this.currentUp.y), Math.abs(this.currentUp.z)).multiply(-0.8)));
|
||||
this.level.blastAmount = 0;
|
||||
this.blastAmount = 0;
|
||||
}
|
||||
|
||||
public function getForce(position:Vector, tick:Int) {
|
||||
|
|
@ -1909,11 +2250,23 @@ class Marble extends GameObject {
|
|||
}
|
||||
}
|
||||
|
||||
public inline function setMode(mode:Mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public function setMarblePosition(x:Float, y:Float, z:Float) {
|
||||
this.collider.transform.setPosition(new Vector(x, y, z));
|
||||
this.setPosition(x, y, z);
|
||||
}
|
||||
|
||||
public inline function getConnectionId() {
|
||||
if (this.connection == null) {
|
||||
return Net.isHost ? 0 : Net.clientId;
|
||||
} else {
|
||||
return this.connection.id;
|
||||
}
|
||||
}
|
||||
|
||||
public override function reset() {
|
||||
this.velocity = new Vector();
|
||||
this.collider.velocity = new Vector();
|
||||
|
|
@ -1922,6 +2275,11 @@ class Marble extends GameObject {
|
|||
this.shockAbsorberEnableTime = Math.NEGATIVE_INFINITY;
|
||||
this.helicopterEnableTime = Math.NEGATIVE_INFINITY;
|
||||
this.megaMarbleEnableTime = Math.NEGATIVE_INFINITY;
|
||||
this.blastUseTick = 0;
|
||||
this.blastTicks = 0;
|
||||
this.helicopterUseTick = 0;
|
||||
this.megaMarbleUseTick = 0;
|
||||
this.netFlags = MarbleNetFlags.DoBlast | MarbleNetFlags.DoMega | MarbleNetFlags.DoHelicopter | MarbleNetFlags.PickupPowerup | MarbleNetFlags.GravityChange | MarbleNetFlags.UsePowerup;
|
||||
this.lastContactNormal = new Vector(0, 0, 1);
|
||||
this.contactEntities = [];
|
||||
this.cloak = false;
|
||||
|
|
@ -1933,6 +2291,15 @@ class Marble extends GameObject {
|
|||
this.teleporting = false;
|
||||
this.teleportDisableTime = null;
|
||||
this.teleportEnableTime = null;
|
||||
this.physicsAccumulator = 0;
|
||||
this.prevRot = this.getRotationQuat().clone();
|
||||
this.oldPos = this.getAbsPos().getPosition();
|
||||
this.newPos = this.getAbsPos().getPosition();
|
||||
this.posStore = new Vector();
|
||||
this.netSmoothOffset = new Vector();
|
||||
this.lastRenderPos = new Vector();
|
||||
this.netCorrected = false;
|
||||
this.serverUsePowerup = false;
|
||||
if (this._radius != this._prevRadius) {
|
||||
this._radius = this._prevRadius;
|
||||
this.collider.radius = this._radius;
|
||||
|
|
@ -1942,6 +2309,15 @@ class Marble extends GameObject {
|
|||
}
|
||||
|
||||
public override function dispose() {
|
||||
if (this.rollSound != null)
|
||||
this.rollSound.stop();
|
||||
if (this.rollMegaSound != null)
|
||||
this.rollMegaSound.stop();
|
||||
if (this.slipSound != null)
|
||||
this.slipSound.stop();
|
||||
if (this.helicopterSound != null)
|
||||
this.helicopterSound.stop();
|
||||
this.helicopter.remove();
|
||||
super.dispose();
|
||||
removeChildren();
|
||||
camera = null;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package src;
|
||||
|
||||
import gui.MPPlayMissionGui;
|
||||
import gui.MainMenuGui;
|
||||
#if !js
|
||||
import gui.ReplayCenterGui;
|
||||
|
|
@ -27,6 +28,9 @@ import src.Console;
|
|||
import src.Debug;
|
||||
import src.Gamepad;
|
||||
import src.Analytics;
|
||||
import net.Net;
|
||||
import net.MasterServerClient;
|
||||
import net.NetCommands;
|
||||
|
||||
@:publicFields
|
||||
class MarbleGame {
|
||||
|
|
@ -181,6 +185,8 @@ class MarbleGame {
|
|||
}
|
||||
|
||||
public function update(dt:Float) {
|
||||
MasterServerClient.process();
|
||||
Net.checkPacketTimeout(dt);
|
||||
if (world != null) {
|
||||
if (world._disposed) {
|
||||
world = null;
|
||||
|
|
@ -190,7 +196,7 @@ class MarbleGame {
|
|||
if (Util.isTouchDevice()) {
|
||||
touchInput.update();
|
||||
}
|
||||
if (!paused) {
|
||||
if (!paused || world.isMultiplayer) {
|
||||
world.update(dt * Debug.timeScale);
|
||||
}
|
||||
if (((Key.isPressed(Key.ESCAPE) #if js && paused #end) || Gamepad.isPressed(["start"]))
|
||||
|
|
@ -236,7 +242,10 @@ class MarbleGame {
|
|||
quitMission();
|
||||
}));
|
||||
} else {
|
||||
quitMission();
|
||||
quitMission(Net.isClient);
|
||||
if (Net.isMP && Net.isClient) {
|
||||
Net.disconnect();
|
||||
}
|
||||
}
|
||||
}, (sender) -> {
|
||||
canvas.popDialog(exitGameDlg);
|
||||
|
|
@ -266,8 +275,13 @@ class MarbleGame {
|
|||
return world;
|
||||
}
|
||||
|
||||
public function quitMission() {
|
||||
public function quitMission(weDisconnecting:Bool = false) {
|
||||
Console.log("Quitting mission");
|
||||
if (Net.isMP) {
|
||||
if (Net.isHost) {
|
||||
NetCommands.endGame();
|
||||
}
|
||||
}
|
||||
world.setCursorLock(false);
|
||||
if (!Settings.levelStatistics.exists(world.mission.path)) {
|
||||
Settings.levelStatistics.set(world.mission.path, {
|
||||
|
|
@ -287,13 +301,18 @@ class MarbleGame {
|
|||
canvas.setContent(new MainMenuGui());
|
||||
#end
|
||||
} else {
|
||||
if (!world.mission.isClaMission && !world.mission.isCustom) {
|
||||
PlayMissionGui.currentCategoryStatic = world.mission.type;
|
||||
if (Net.isMP) {
|
||||
var lobby = new MPPlayMissionGui(Net.isHost);
|
||||
canvas.setContent(lobby);
|
||||
} else {
|
||||
if (!world.mission.isClaMission && !world.mission.isCustom) {
|
||||
PlayMissionGui.currentCategoryStatic = world.mission.type;
|
||||
}
|
||||
var pmg = new PlayMissionGui();
|
||||
PlayMissionGui.currentSelectionStatic = world.mission.index;
|
||||
PlayMissionGui.currentGameStatic = world.mission.game;
|
||||
canvas.setContent(pmg);
|
||||
}
|
||||
var pmg = new PlayMissionGui();
|
||||
PlayMissionGui.currentSelectionStatic = world.mission.index;
|
||||
PlayMissionGui.currentGameStatic = world.mission.game;
|
||||
canvas.setContent(pmg);
|
||||
}
|
||||
world.dispose();
|
||||
world = null;
|
||||
|
|
@ -301,13 +320,13 @@ class MarbleGame {
|
|||
Settings.save();
|
||||
}
|
||||
|
||||
public function playMission(mission:Mission) {
|
||||
public function playMission(mission:Mission, multiplayer:Bool = false) {
|
||||
canvas.clearContent();
|
||||
if (world != null) {
|
||||
world.dispose();
|
||||
}
|
||||
Analytics.trackLevelPlay(mission.title, mission.path);
|
||||
world = new MarbleWorld(scene, scene2d, mission, toRecord);
|
||||
world = new MarbleWorld(scene, scene2d, mission, toRecord, multiplayer);
|
||||
world.init();
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,11 @@
|
|||
package src;
|
||||
|
||||
import haxe.Json;
|
||||
import mis.MisParser;
|
||||
import src.ResourceLoader;
|
||||
import src.Mission;
|
||||
import src.Console;
|
||||
import src.MissionList;
|
||||
|
||||
@:publicFields
|
||||
class MissionList {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import src.ResourceLoader;
|
|||
import src.MarbleGame;
|
||||
import src.Settings;
|
||||
import src.Mission;
|
||||
import src.MissionList;
|
||||
|
||||
class AchievementsGui extends GuiImage {
|
||||
public function new() {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import gui.GuiControl.MouseState;
|
|||
class Canvas extends GuiControl {
|
||||
var scene2d:Scene;
|
||||
var marbleGame:MarbleGame;
|
||||
var content:GuiControl;
|
||||
|
||||
public function new(scene, marbleGame:MarbleGame) {
|
||||
super();
|
||||
|
|
@ -25,6 +26,7 @@ class Canvas extends GuiControl {
|
|||
|
||||
public function setContent(content:GuiControl) {
|
||||
this.dispose();
|
||||
this.content = content;
|
||||
this.addChild(content);
|
||||
this.render(scene2d);
|
||||
}
|
||||
|
|
|
|||
904
src/gui/Graphics.hx
Normal file
904
src/gui/Graphics.hx
Normal file
|
|
@ -0,0 +1,904 @@
|
|||
package gui;
|
||||
|
||||
import h2d.RenderContext;
|
||||
import h2d.impl.BatchDrawState;
|
||||
import hxd.Math;
|
||||
import hxd.impl.Allocator;
|
||||
import h2d.Drawable;
|
||||
|
||||
private typedef GraphicsPoint = hxd.poly2tri.Point;
|
||||
|
||||
@:dox(hide)
|
||||
class GPoint {
|
||||
public var x:Float;
|
||||
public var y:Float;
|
||||
public var r:Float;
|
||||
public var g:Float;
|
||||
public var b:Float;
|
||||
public var a:Float;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function load(x, y, r, g, b, a) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
}
|
||||
|
||||
private class GraphicsContent extends h3d.prim.Primitive {
|
||||
var tmp:hxd.FloatBuffer;
|
||||
var index:hxd.IndexBuffer;
|
||||
var state:BatchDrawState;
|
||||
|
||||
var bufferDirty:Bool;
|
||||
var indexDirty:Bool;
|
||||
#if track_alloc
|
||||
var allocPos:hxd.impl.AllocPos;
|
||||
#end
|
||||
|
||||
var bufferSize:Int;
|
||||
var ibufferSize:Int;
|
||||
|
||||
public function new() {
|
||||
state = new BatchDrawState();
|
||||
#if track_alloc
|
||||
this.allocPos = new hxd.impl.AllocPos();
|
||||
#end
|
||||
}
|
||||
|
||||
public inline function addIndex(i) {
|
||||
index.push(i);
|
||||
state.add(1);
|
||||
indexDirty = true;
|
||||
}
|
||||
|
||||
public inline function add(x:Float, y:Float, u:Float, v:Float, r:Float, g:Float, b:Float, a:Float) {
|
||||
tmp.push(x);
|
||||
tmp.push(y);
|
||||
tmp.push(u);
|
||||
tmp.push(v);
|
||||
tmp.push(r);
|
||||
tmp.push(g);
|
||||
tmp.push(b);
|
||||
tmp.push(a);
|
||||
bufferDirty = true;
|
||||
}
|
||||
|
||||
public function setTile(tile:h2d.Tile) {
|
||||
state.setTile(tile);
|
||||
}
|
||||
|
||||
public function next() {
|
||||
var nvect = tmp.length >> 3;
|
||||
if (nvect < 1 << 15)
|
||||
return false;
|
||||
tmp = new hxd.FloatBuffer();
|
||||
index = new hxd.IndexBuffer();
|
||||
var tex = state.currentTexture;
|
||||
state = new BatchDrawState();
|
||||
state.setTexture(tex);
|
||||
super.dispose();
|
||||
return true;
|
||||
}
|
||||
|
||||
override function alloc(engine:h3d.Engine) {
|
||||
if (index.length <= 0)
|
||||
return;
|
||||
var alloc = Allocator.get();
|
||||
buffer = alloc.ofFloats(tmp, 8, RawFormat);
|
||||
bufferSize = tmp.length;
|
||||
#if track_alloc
|
||||
@:privateAccess buffer.allocPos = allocPos;
|
||||
#end
|
||||
indexes = alloc.ofIndexes(index);
|
||||
ibufferSize = index.length;
|
||||
bufferDirty = false;
|
||||
indexDirty = false;
|
||||
}
|
||||
|
||||
public function doRender(ctx:h2d.RenderContext) {
|
||||
if (index.length == 0)
|
||||
return;
|
||||
flush();
|
||||
state.drawIndexed(ctx, buffer, indexes, 0, tmp.length >> 3);
|
||||
}
|
||||
|
||||
public function flush() {
|
||||
if (buffer == null || buffer.isDisposed()) {
|
||||
alloc(h3d.Engine.getCurrent());
|
||||
} else {
|
||||
var allocator = Allocator.get();
|
||||
if (bufferDirty) {
|
||||
if (tmp.length > bufferSize) {
|
||||
allocator.disposeBuffer(buffer);
|
||||
|
||||
buffer = new h3d.Buffer(tmp.length >> 3, 8, [RawFormat, Dynamic]);
|
||||
buffer.uploadVector(tmp, 0, tmp.length >> 3);
|
||||
bufferSize = tmp.length;
|
||||
} else {
|
||||
buffer.uploadVector(tmp, 0, tmp.length >> 3);
|
||||
}
|
||||
bufferDirty = false;
|
||||
}
|
||||
if (indexDirty) {
|
||||
if (index.length > ibufferSize) {
|
||||
allocator.disposeIndexBuffer(indexes);
|
||||
indexes = allocator.ofIndexes(index);
|
||||
ibufferSize = index.length;
|
||||
} else {
|
||||
indexes.upload(index, 0, index.length);
|
||||
}
|
||||
indexDirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override function dispose() {
|
||||
state.clear();
|
||||
// disposeBuffers();
|
||||
|
||||
// super.dispose();
|
||||
}
|
||||
|
||||
function disposeBuffers() {
|
||||
if (buffer != null) {
|
||||
Allocator.get().disposeBuffer(buffer);
|
||||
buffer = null;
|
||||
}
|
||||
if (indexes != null) {
|
||||
Allocator.get().disposeIndexBuffer(indexes);
|
||||
indexes = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function clear() {
|
||||
dispose();
|
||||
tmp = new hxd.FloatBuffer();
|
||||
index = new hxd.IndexBuffer();
|
||||
}
|
||||
|
||||
public function disposeForReal() {
|
||||
state.clear();
|
||||
disposeBuffers();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A simple interface to draw arbitrary 2D geometry.
|
||||
|
||||
Usage notes:
|
||||
* While Graphics allows for multiple unique textures, each texture swap causes a new drawcall,
|
||||
and due to that it's recommended to minimize the amount of used textures per Graphics instance,
|
||||
ideally limiting to only one texture.
|
||||
* Due to how Graphics operate, removing them from the active `h2d.Scene` will cause a loss of all data.
|
||||
**/
|
||||
class Graphics extends Drawable {
|
||||
var content:GraphicsContent;
|
||||
var tmpPoints:Array<GPoint>;
|
||||
var pindex:Int;
|
||||
var curR:Float;
|
||||
var curG:Float;
|
||||
var curB:Float;
|
||||
var curA:Float;
|
||||
var lineSize:Float;
|
||||
var lineR:Float;
|
||||
var lineG:Float;
|
||||
var lineB:Float;
|
||||
var lineA:Float;
|
||||
var doFill:Bool;
|
||||
|
||||
var xMin:Float;
|
||||
var yMin:Float;
|
||||
var xMax:Float;
|
||||
var yMax:Float;
|
||||
var xMinSize:Float;
|
||||
var yMinSize:Float;
|
||||
var xMaxSize:Float;
|
||||
var yMaxSize:Float;
|
||||
|
||||
var ma:Float = 1.;
|
||||
var mb:Float = 0.;
|
||||
var mc:Float = 0.;
|
||||
var md:Float = 1.;
|
||||
var mx:Float = 0.;
|
||||
var my:Float = 0.;
|
||||
|
||||
/**
|
||||
The Tile used as source of Texture to render.
|
||||
**/
|
||||
public var tile:h2d.Tile;
|
||||
|
||||
/**
|
||||
Adds bevel cut-off at line corners.
|
||||
|
||||
The value is a percentile in range of 0...1, dictating at which point edges get beveled based on their angle.
|
||||
Value of 0 being not beveled and 1 being always beveled.
|
||||
**/
|
||||
public var bevel = 0.25; // 0 = not beveled, 1 = always beveled
|
||||
|
||||
/**
|
||||
Create a new Graphics instance.
|
||||
@param parent An optional parent `h2d.Object` instance to which Graphics adds itself if set.
|
||||
**/
|
||||
public function new(?parent) {
|
||||
super(parent);
|
||||
content = new GraphicsContent();
|
||||
tile = h2d.Tile.fromColor(0xFFFFFF);
|
||||
clear();
|
||||
}
|
||||
|
||||
override function onRemove() {
|
||||
super.onRemove();
|
||||
clear();
|
||||
content.disposeForReal();
|
||||
}
|
||||
|
||||
/**
|
||||
Clears the Graphics contents.
|
||||
**/
|
||||
public function clear() {
|
||||
content.clear();
|
||||
tmpPoints = [];
|
||||
pindex = 0;
|
||||
lineSize = 0;
|
||||
xMin = Math.POSITIVE_INFINITY;
|
||||
yMin = Math.POSITIVE_INFINITY;
|
||||
yMax = Math.NEGATIVE_INFINITY;
|
||||
xMax = Math.NEGATIVE_INFINITY;
|
||||
xMinSize = Math.POSITIVE_INFINITY;
|
||||
yMinSize = Math.POSITIVE_INFINITY;
|
||||
yMaxSize = Math.NEGATIVE_INFINITY;
|
||||
xMaxSize = Math.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
override function getBoundsRec(relativeTo, out, forSize) {
|
||||
super.getBoundsRec(relativeTo, out, forSize);
|
||||
if (tile != null) {
|
||||
if (forSize)
|
||||
addBounds(relativeTo, out, xMinSize, yMinSize, xMaxSize - xMinSize, yMaxSize - yMinSize);
|
||||
else
|
||||
addBounds(relativeTo, out, xMin, yMin, xMax - xMin, yMax - yMin);
|
||||
}
|
||||
}
|
||||
|
||||
function isConvex(points:Array<GPoint>) {
|
||||
var first = true, sign = false;
|
||||
for (i in 0...points.length) {
|
||||
var p1 = points[i];
|
||||
var p2 = points[(i + 1) % points.length];
|
||||
var p3 = points[(i + 2) % points.length];
|
||||
var s = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) > 0;
|
||||
if (first) {
|
||||
first = false;
|
||||
sign = s;
|
||||
} else if (sign != s)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function flushLine(start) {
|
||||
var pts = tmpPoints;
|
||||
var last = pts.length - 1;
|
||||
var prev = pts[last];
|
||||
var p = pts[0];
|
||||
|
||||
content.setTile(h2d.Tile.fromColor(0xFFFFFF));
|
||||
var closed = p.x == prev.x && p.y == prev.y;
|
||||
var count = pts.length;
|
||||
if (!closed) {
|
||||
var prevLast = pts[last - 1];
|
||||
if (prevLast == null)
|
||||
prevLast = p;
|
||||
var gp = new GPoint();
|
||||
gp.load(prev.x * 2 - prevLast.x, prev.y * 2 - prevLast.y, 0, 0, 0, 0);
|
||||
pts.push(gp);
|
||||
var pNext = pts[1];
|
||||
if (pNext == null)
|
||||
pNext = p;
|
||||
var gp = new GPoint();
|
||||
gp.load(p.x * 2 - pNext.x, p.y * 2 - pNext.y, 0, 0, 0, 0);
|
||||
prev = gp;
|
||||
} else if (p != prev) {
|
||||
count--;
|
||||
last--;
|
||||
prev = pts[last];
|
||||
}
|
||||
|
||||
for (i in 0...count) {
|
||||
var next = pts[(i + 1) % pts.length];
|
||||
|
||||
var nx1 = prev.y - p.y;
|
||||
var ny1 = p.x - prev.x;
|
||||
var ns1 = Math.invSqrt(nx1 * nx1 + ny1 * ny1);
|
||||
|
||||
var nx2 = p.y - next.y;
|
||||
var ny2 = next.x - p.x;
|
||||
var ns2 = Math.invSqrt(nx2 * nx2 + ny2 * ny2);
|
||||
|
||||
var nx = nx1 * ns1 + nx2 * ns2;
|
||||
var ny = ny1 * ns1 + ny2 * ns2;
|
||||
var ns = Math.invSqrt(nx * nx + ny * ny);
|
||||
|
||||
nx *= ns;
|
||||
ny *= ns;
|
||||
|
||||
var size = nx * nx1 * ns1 + ny * ny1 * ns1; // N.N1
|
||||
|
||||
// *HACK* we should instead properly detect limits when the angle is too small
|
||||
if (size < 0.1)
|
||||
size = 0.1;
|
||||
|
||||
var d = lineSize * 0.5 / size;
|
||||
nx *= d;
|
||||
ny *= d;
|
||||
|
||||
if (size > bevel) {
|
||||
content.add(p.x + nx, p.y + ny, 0, 0, p.r, p.g, p.b, p.a);
|
||||
content.add(p.x - nx, p.y - ny, 0, 0, p.r, p.g, p.b, p.a);
|
||||
|
||||
var pnext = i == last ? start : pindex + 2;
|
||||
|
||||
if (i < count - 1 || closed) {
|
||||
content.addIndex(pindex);
|
||||
content.addIndex(pindex + 1);
|
||||
content.addIndex(pnext);
|
||||
|
||||
content.addIndex(pindex + 1);
|
||||
content.addIndex(pnext);
|
||||
content.addIndex(pnext + 1);
|
||||
}
|
||||
pindex += 2;
|
||||
} else {
|
||||
// bevel
|
||||
var n0x = next.x - p.x;
|
||||
var n0y = next.y - p.y;
|
||||
var sign = n0x * nx + n0y * ny;
|
||||
|
||||
var nnx = -ny;
|
||||
var nny = nx;
|
||||
|
||||
var size = nnx * nx1 * ns1 + nny * ny1 * ns1;
|
||||
var d = lineSize * 0.5 / size;
|
||||
nnx *= d;
|
||||
nny *= d;
|
||||
|
||||
var pnext = i == last ? start : pindex + 3;
|
||||
|
||||
if (sign > 0) {
|
||||
content.add(p.x + nx, p.y + ny, 0, 0, p.r, p.g, p.b, p.a);
|
||||
content.add(p.x - nnx, p.y - nny, 0, 0, p.r, p.g, p.b, p.a);
|
||||
content.add(p.x + nnx, p.y + nny, 0, 0, p.r, p.g, p.b, p.a);
|
||||
|
||||
content.addIndex(pindex);
|
||||
content.addIndex(pnext);
|
||||
content.addIndex(pindex + 2);
|
||||
|
||||
content.addIndex(pindex + 2);
|
||||
content.addIndex(pnext);
|
||||
content.addIndex(pnext + 1);
|
||||
} else {
|
||||
content.add(p.x + nnx, p.y + nny, 0, 0, p.r, p.g, p.b, p.a);
|
||||
content.add(p.x - nx, p.y - ny, 0, 0, p.r, p.g, p.b, p.a);
|
||||
content.add(p.x - nnx, p.y - nny, 0, 0, p.r, p.g, p.b, p.a);
|
||||
|
||||
content.addIndex(pindex + 1);
|
||||
content.addIndex(pnext);
|
||||
content.addIndex(pindex + 2);
|
||||
|
||||
content.addIndex(pindex + 1);
|
||||
content.addIndex(pnext);
|
||||
content.addIndex(pnext + 1);
|
||||
}
|
||||
|
||||
content.addIndex(pindex);
|
||||
content.addIndex(pindex + 1);
|
||||
content.addIndex(pindex + 2);
|
||||
|
||||
pindex += 3;
|
||||
}
|
||||
|
||||
prev = p;
|
||||
p = next;
|
||||
}
|
||||
content.setTile(tile);
|
||||
}
|
||||
|
||||
static var EARCUT = null;
|
||||
|
||||
function flushFill(i0) {
|
||||
if (tmpPoints.length < 3)
|
||||
return;
|
||||
|
||||
var pts = tmpPoints;
|
||||
var p0 = pts[0];
|
||||
var p1 = pts[pts.length - 1];
|
||||
var last = null;
|
||||
// closed poly
|
||||
if (hxd.Math.abs(p0.x - p1.x) < 1e-9 && hxd.Math.abs(p0.y - p1.y) < 1e-9)
|
||||
last = pts.pop();
|
||||
|
||||
if (isConvex(pts)) {
|
||||
for (i in 1...pts.length - 1) {
|
||||
content.addIndex(i0);
|
||||
content.addIndex(i0 + i);
|
||||
content.addIndex(i0 + i + 1);
|
||||
}
|
||||
} else {
|
||||
var ear = EARCUT;
|
||||
if (ear == null)
|
||||
EARCUT = ear = new hxd.earcut.Earcut();
|
||||
for (i in ear.triangulate(pts))
|
||||
content.addIndex(i + i0);
|
||||
}
|
||||
|
||||
if (last != null)
|
||||
pts.push(last);
|
||||
}
|
||||
|
||||
function flush() {
|
||||
if (tmpPoints.length == 0)
|
||||
return;
|
||||
if (doFill) {
|
||||
flushFill(pindex);
|
||||
pindex += tmpPoints.length;
|
||||
if (content.next())
|
||||
pindex = 0;
|
||||
}
|
||||
if (lineSize > 0) {
|
||||
flushLine(pindex);
|
||||
if (content.next())
|
||||
pindex = 0;
|
||||
}
|
||||
tmpPoints = [];
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a solid color fill.
|
||||
|
||||
Beginning new fill will finish previous fill operation without need to call `Graphics.endFill`.
|
||||
|
||||
@param color An RGB color with which to fill the drawn shapes.
|
||||
@param alpha A transparency of the fill color.
|
||||
**/
|
||||
public function beginFill(color:Int = 0, alpha = 1.) {
|
||||
flush();
|
||||
tile = h2d.Tile.fromColor(0xFFFFFF);
|
||||
content.setTile(tile);
|
||||
setColor(color, alpha);
|
||||
doFill = true;
|
||||
}
|
||||
|
||||
/**
|
||||
Position a virtual tile at the given position and scale. Every draw will display a part of this tile relative
|
||||
to these coordinates.
|
||||
|
||||
Note that in by default, Tile is not wrapped, and in order to render tiling texture, `Drawable.tileWrap` have to be set.
|
||||
Additionally, both `Tile.dx` and `Tile.dy` are ignored (use `dx`/`dy` arguments instead)
|
||||
as well as tile defined size of the tile through `Tile.width` and `Tile.height` (use `scaleX`/`scaleY` relative to texture size).
|
||||
|
||||
Beginning new fill will finish previous fill operation without need to call `Graphics.endFill`.
|
||||
|
||||
@param dx An X offset of the Tile relative to Graphics.
|
||||
@param dy An Y offset of the Tile relative to Graphics.
|
||||
@param scaleX A horizontal scale factor applied to the Tile texture.
|
||||
@param scaleY A vertical scale factor applied to the Tile texture.
|
||||
@param tile The tile to fill with. If null, uses previously used Tile with `beginTileFill` or throws an error.
|
||||
Previous tile is remembered across `Graphics.clear` calls.
|
||||
**/
|
||||
public function beginTileFill(?dx:Float, ?dy:Float, ?scaleX:Float, ?scaleY:Float, ?tile:h2d.Tile) {
|
||||
if (tile == null)
|
||||
tile = this.tile;
|
||||
if (tile == null)
|
||||
throw "Tile not specified";
|
||||
flush();
|
||||
this.tile = tile;
|
||||
content.setTile(tile);
|
||||
setColor(0xFFFFFF);
|
||||
doFill = true;
|
||||
|
||||
if (dx == null)
|
||||
dx = 0;
|
||||
if (dy == null)
|
||||
dy = 0;
|
||||
if (scaleX == null)
|
||||
scaleX = 1;
|
||||
if (scaleY == null)
|
||||
scaleY = 1;
|
||||
dx -= tile.x;
|
||||
dy -= tile.y;
|
||||
|
||||
var tex = tile.getTexture();
|
||||
var pixWidth = 1 / tex.width;
|
||||
var pixHeight = 1 / tex.height;
|
||||
ma = pixWidth / scaleX;
|
||||
mb = 0;
|
||||
mc = 0;
|
||||
md = pixHeight / scaleY;
|
||||
mx = -dx * ma;
|
||||
my = -dy * md;
|
||||
}
|
||||
|
||||
/**
|
||||
Draws a Tile at given position.
|
||||
See `Graphics.beginTileFill` for limitations.
|
||||
|
||||
This methods ends current fill operation.
|
||||
@param x The X position of the tile.
|
||||
@param y The Y position of the tile.
|
||||
@param tile The tile to draw.
|
||||
**/
|
||||
public function drawTile(x:Float, y:Float, tile:h2d.Tile) {
|
||||
beginTileFill(x, y, tile);
|
||||
drawRect(x, y, tile.width, tile.height);
|
||||
endFill();
|
||||
}
|
||||
|
||||
/**
|
||||
Sets an outline style. Changing the line style ends the currently drawn line.
|
||||
|
||||
@param size Width of the outline. Setting size to 0 will remove the outline.
|
||||
@param color An outline RGB color.
|
||||
@param alpha An outline transparency.
|
||||
**/
|
||||
public function lineStyle(size:Float = 0, color = 0, alpha = 1.) {
|
||||
flush();
|
||||
this.lineSize = size;
|
||||
lineA = alpha;
|
||||
lineR = ((color >> 16) & 0xFF) / 255.;
|
||||
lineG = ((color >> 8) & 0xFF) / 255.;
|
||||
lineB = (color & 0xFF) / 255.;
|
||||
}
|
||||
|
||||
/**
|
||||
Ends the current line and starts new one at given position.
|
||||
**/
|
||||
public inline function moveTo(x, y) {
|
||||
flush();
|
||||
lineTo(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
Ends the current fill operation.
|
||||
**/
|
||||
public function endFill() {
|
||||
flush();
|
||||
doFill = false;
|
||||
}
|
||||
|
||||
/**
|
||||
Changes current fill color.
|
||||
Does not interrupt current fill operation and can be utilized to customize color per vertex.
|
||||
During tile fill operation, color serves as a tile color multiplier.
|
||||
@param color The new fill color.
|
||||
@param alpha The new fill transparency.
|
||||
**/
|
||||
public inline function setColor(color:Int, alpha:Float = 1.) {
|
||||
curA = alpha;
|
||||
curR = ((color >> 16) & 0xFF) / 255.;
|
||||
curG = ((color >> 8) & 0xFF) / 255.;
|
||||
curB = (color & 0xFF) / 255.;
|
||||
}
|
||||
|
||||
/**
|
||||
Draws a rectangle with given parameters.
|
||||
@param x The rectangle top-left corner X position.
|
||||
@param y The rectangle top-left corner Y position.
|
||||
@param w The rectangle width.
|
||||
@param h The rectangle height.
|
||||
**/
|
||||
public function drawRect(x:Float, y:Float, w:Float, h:Float) {
|
||||
flush();
|
||||
lineTo(x, y);
|
||||
lineTo(x + w, y);
|
||||
lineTo(x + w, y + h);
|
||||
lineTo(x, y + h);
|
||||
lineTo(x, y);
|
||||
var e = 0.01; // see #776
|
||||
tmpPoints[0].x += e;
|
||||
tmpPoints[0].y += e;
|
||||
tmpPoints[1].y += e;
|
||||
tmpPoints[3].x += e;
|
||||
tmpPoints[4].x += e;
|
||||
tmpPoints[4].y += e;
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
Draws a rounded rectangle with given parameters.
|
||||
@param x The rectangle top-left corner X position.
|
||||
@param y The rectangle top-left corner Y position.
|
||||
@param w The rectangle width.
|
||||
@param h The rectangle height.
|
||||
@param radius Radius of the rectangle corners.
|
||||
@param nsegments Amount of segments used for corners. When `0` segment count calculated automatically.
|
||||
**/
|
||||
public function drawRoundedRect(x:Float, y:Float, w:Float, h:Float, radius:Float, nsegments = 0) {
|
||||
if (radius <= 0) {
|
||||
return drawRect(x, y, w, h);
|
||||
}
|
||||
x += radius;
|
||||
y += radius;
|
||||
w -= radius * 2;
|
||||
h -= radius * 2;
|
||||
flush();
|
||||
if (nsegments == 0)
|
||||
nsegments = Math.ceil(Math.abs(radius * hxd.Math.degToRad(90) / 4));
|
||||
if (nsegments < 3)
|
||||
nsegments = 3;
|
||||
var angle = hxd.Math.degToRad(90) / (nsegments - 1);
|
||||
inline function corner(x, y, angleStart) {
|
||||
for (i in 0...nsegments) {
|
||||
var a = i * angle + hxd.Math.degToRad(angleStart);
|
||||
lineTo(x + Math.cos(a) * radius, y + Math.sin(a) * radius);
|
||||
}
|
||||
}
|
||||
lineTo(x, y - radius);
|
||||
lineTo(x + w, y - radius);
|
||||
corner(x + w, y, 270);
|
||||
lineTo(x + w + radius, y + h);
|
||||
corner(x + w, y + h, 0);
|
||||
lineTo(x, y + h + radius);
|
||||
corner(x, y + h, 90);
|
||||
lineTo(x - radius, y);
|
||||
corner(x, y, 180);
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
Draws a circle centered at given position.
|
||||
@param cx X center position of the circle.
|
||||
@param cy Y center position of the circle.
|
||||
@param radius Radius of the circle.
|
||||
@param nsegments Amount of segments used to draw the circle. When `0`, amount of segments calculated automatically.
|
||||
**/
|
||||
public function drawCircle(cx:Float, cy:Float, radius:Float, nsegments = 0) {
|
||||
flush();
|
||||
if (nsegments == 0)
|
||||
nsegments = Math.ceil(Math.abs(radius * 3.14 * 2 / 4));
|
||||
if (nsegments < 3)
|
||||
nsegments = 3;
|
||||
var angle = Math.PI * 2 / nsegments;
|
||||
for (i in 0...nsegments + 1) {
|
||||
var a = i * angle;
|
||||
lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
|
||||
}
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
Draws an ellipse centered at given position.
|
||||
@param cx X center position of the ellipse.
|
||||
@param cy Y center position of the ellipse.
|
||||
@param radiusX Horizontal radius of an ellipse.
|
||||
@param radiusY Vertical radius of an ellipse.
|
||||
@param rotationAngle Ellipse rotation in radians.
|
||||
@param nsegments Amount of segments used to draw an ellipse. When `0`, amount of segments calculated automatically.
|
||||
**/
|
||||
public function drawEllipse(cx:Float, cy:Float, radiusX:Float, radiusY:Float, rotationAngle:Float = 0, nsegments = 0) {
|
||||
flush();
|
||||
if (nsegments == 0)
|
||||
nsegments = Math.ceil(Math.abs(radiusY * 3.14 * 2 / 4));
|
||||
if (nsegments < 3)
|
||||
nsegments = 3;
|
||||
var angle = Math.PI * 2 / nsegments;
|
||||
var x1, y1;
|
||||
for (i in 0...nsegments + 1) {
|
||||
var a = i * angle;
|
||||
x1 = Math.cos(a) * Math.cos(rotationAngle) * radiusX - Math.sin(a) * Math.sin(rotationAngle) * radiusY;
|
||||
y1 = Math.cos(rotationAngle) * Math.sin(a) * radiusY + Math.cos(a) * Math.sin(rotationAngle) * radiusX;
|
||||
lineTo(cx + x1, cy + y1);
|
||||
}
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
Draws a pie centered at given position.
|
||||
@param cx X center position of the pie.
|
||||
@param cy Y center position of the pie.
|
||||
@param radius Radius of the pie.
|
||||
@param angleStart Starting angle of the pie in radians.
|
||||
@param angleLength The pie size in clockwise direction with `2*PI` being full circle.
|
||||
@param nsegments Amount of segments used to draw the pie. When `0`, amount of segments calculated automatically.
|
||||
**/
|
||||
public function drawPie(cx:Float, cy:Float, radius:Float, angleStart:Float, angleLength:Float, nsegments = 0) {
|
||||
if (Math.abs(angleLength) >= Math.PI * 2) {
|
||||
return drawCircle(cx, cy, radius, nsegments);
|
||||
}
|
||||
flush();
|
||||
lineTo(cx, cy);
|
||||
if (nsegments == 0)
|
||||
nsegments = Math.ceil(Math.abs(radius * angleLength / 4));
|
||||
if (nsegments < 3)
|
||||
nsegments = 3;
|
||||
var angle = angleLength / (nsegments - 1);
|
||||
for (i in 0...nsegments) {
|
||||
var a = i * angle + angleStart;
|
||||
lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
|
||||
}
|
||||
lineTo(cx, cy);
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
Draws a double-edged pie centered at given position.
|
||||
@param cx X center position of the pie.
|
||||
@param cy Y center position of the pie.
|
||||
@param radius The outer radius of the pie.
|
||||
@param innerRadius The inner radius of the pie.
|
||||
@param angleStart Starting angle of the pie in radians.
|
||||
@param angleLength The pie size in clockwise direction with `2*PI` being full circle.
|
||||
@param nsegments Amount of segments used to draw the pie. When `0`, amount of segments calculated automatically.
|
||||
**/
|
||||
public function drawPieInner(cx:Float, cy:Float, radius:Float, innerRadius:Float, angleStart:Float, angleLength:Float, nsegments = 0) {
|
||||
flush();
|
||||
if (Math.abs(angleLength) >= Math.PI * 2 + 1e-3)
|
||||
angleLength = Math.PI * 2 + 1e-3;
|
||||
|
||||
var cs = Math.cos(angleStart);
|
||||
var ss = Math.sin(angleStart);
|
||||
var ce = Math.cos(angleStart + angleLength);
|
||||
var se = Math.sin(angleStart + angleLength);
|
||||
|
||||
lineTo(cx + cs * innerRadius, cy + ss * innerRadius);
|
||||
|
||||
if (nsegments == 0)
|
||||
nsegments = Math.ceil(Math.abs(radius * angleLength / 4));
|
||||
if (nsegments < 3)
|
||||
nsegments = 3;
|
||||
var angle = angleLength / (nsegments - 1);
|
||||
for (i in 0...nsegments) {
|
||||
var a = i * angle + angleStart;
|
||||
lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
|
||||
}
|
||||
lineTo(cx + ce * innerRadius, cy + se * innerRadius);
|
||||
for (i in 0...nsegments) {
|
||||
var a = (nsegments - 1 - i) * angle + angleStart;
|
||||
lineTo(cx + Math.cos(a) * innerRadius, cy + Math.sin(a) * innerRadius);
|
||||
}
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
Draws a rectangular pie centered at given position.
|
||||
@param cx X center position of the pie.
|
||||
@param cy Y center position of the pie.
|
||||
@param width Width of the pie.
|
||||
@param height Height of the pie.
|
||||
@param angleStart Starting angle of the pie in radians.
|
||||
@param angleLength The pie size in clockwise direction with `2*PI` being solid rectangle.
|
||||
@param nsegments Amount of segments used to draw the pie. When `0`, amount of segments calculated automatically.
|
||||
**/
|
||||
public function drawRectanglePie(cx:Float, cy:Float, width:Float, height:Float, angleStart:Float, angleLength:Float, nsegments = 0) {
|
||||
if (Math.abs(angleLength) >= Math.PI * 2) {
|
||||
return drawRect(cx - (width / 2), cy - (height / 2), width, height);
|
||||
}
|
||||
flush();
|
||||
lineTo(cx, cy);
|
||||
if (nsegments == 0)
|
||||
nsegments = Math.ceil(Math.abs(Math.max(width, height) * angleLength / 4));
|
||||
if (nsegments < 3)
|
||||
nsegments = 3;
|
||||
var angle = angleLength / (nsegments - 1);
|
||||
var square2 = Math.sqrt(2);
|
||||
for (i in 0...nsegments) {
|
||||
var a = i * angle + angleStart;
|
||||
|
||||
var _width = Math.cos(a) * (width / 2 + 1) * square2;
|
||||
var _height = Math.sin(a) * (height / 2 + 1) * square2;
|
||||
|
||||
_width = Math.abs(_width) >= width / 2 ? (Math.cos(a) < 0 ? width / 2 * -1 : width / 2) : _width;
|
||||
_height = Math.abs(_height) >= height / 2 ? (Math.sin(a) < 0 ? height / 2 * -1 : height / 2) : _height;
|
||||
|
||||
lineTo(cx + _width, cy + _height);
|
||||
}
|
||||
lineTo(cx, cy);
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a quadratic Bezier curve using the current line style from the current drawing position to (cx, cy) and using the control point that (bx, by) specifies.
|
||||
* IvanK Lib port ( http://lib.ivank.net )
|
||||
*/
|
||||
public function curveTo(bx:Float, by:Float, cx:Float, cy:Float) {
|
||||
var ax = tmpPoints.length == 0 ? 0 : tmpPoints[tmpPoints.length - 1].x;
|
||||
var ay = tmpPoints.length == 0 ? 0 : tmpPoints[tmpPoints.length - 1].y;
|
||||
var t = 2 / 3;
|
||||
cubicCurveTo(ax + t * (bx - ax), ay + t * (by - ay), cx + t * (bx - cx), cy + t * (by - cy), cx, cy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a cubic Bezier curve from the current drawing position to the specified anchor point.
|
||||
* IvanK Lib port ( http://lib.ivank.net )
|
||||
* @param bx control X for start point
|
||||
* @param by control Y for start point
|
||||
* @param cx control X for end point
|
||||
* @param cy control Y for end point
|
||||
* @param dx end X
|
||||
* @param dy end Y
|
||||
* @param nsegments = 40
|
||||
*/
|
||||
public function cubicCurveTo(bx:Float, by:Float, cx:Float, cy:Float, dx:Float, dy:Float, nsegments = 40) {
|
||||
var ax = tmpPoints.length == 0 ? 0 : tmpPoints[tmpPoints.length - 1].x;
|
||||
var ay = tmpPoints.length == 0 ? 0 : tmpPoints[tmpPoints.length - 1].y;
|
||||
var tobx = bx - ax, toby = by - ay;
|
||||
var tocx = cx - bx, tocy = cy - by;
|
||||
var todx = dx - cx, tody = dy - cy;
|
||||
var step = 1 / nsegments;
|
||||
|
||||
for (i in 1...nsegments) {
|
||||
var d = i * step;
|
||||
var px = ax + d * tobx, py = ay + d * toby;
|
||||
var qx = bx + d * tocx, qy = by + d * tocy;
|
||||
var rx = cx + d * todx, ry = cy + d * tody;
|
||||
var toqx = qx - px, toqy = qy - py;
|
||||
var torx = rx - qx, tory = ry - qy;
|
||||
|
||||
var sx = px + d * toqx, sy = py + d * toqy;
|
||||
var tx = qx + d * torx, ty = qy + d * tory;
|
||||
var totx = tx - sx, toty = ty - sy;
|
||||
lineTo(sx + d * totx, sy + d * toty);
|
||||
}
|
||||
lineTo(dx, dy);
|
||||
}
|
||||
|
||||
/**
|
||||
Draws a straight line from the current drawing position to the given position.
|
||||
**/
|
||||
public inline function lineTo(x:Float, y:Float) {
|
||||
addVertex(x, y, curR, curG, curB, curA, x * ma + y * mc + mx, x * mb + y * md + my);
|
||||
}
|
||||
|
||||
/**
|
||||
Advanced usage. Adds new vertex to the current polygon with given parameters and current line style.
|
||||
@param x Vertex X position
|
||||
@param y Vertex Y position
|
||||
@param r Red tint value of the vertex when performing fill operation.
|
||||
@param g Green tint value of the vertex when performing fill operation.
|
||||
@param b Blue tint value of the vertex when performing fill operation.
|
||||
@param a Alpha of the vertex when performing fill operation.
|
||||
@param u Normalized horizontal Texture position from the current Tile fill operation.
|
||||
@param v Normalized vertical Texture position from the current Tile fill operation.
|
||||
**/
|
||||
public function addVertex(x:Float, y:Float, r:Float, g:Float, b:Float, a:Float, u:Float = 0., v:Float = 0.) {
|
||||
var half = lineSize / 2.0;
|
||||
if (x - half < xMin)
|
||||
xMin = x - half;
|
||||
if (y - half < yMin)
|
||||
yMin = y - half;
|
||||
if (x + half > xMax)
|
||||
xMax = x + half;
|
||||
if (y + half > yMax)
|
||||
yMax = y + half;
|
||||
if (x < xMinSize)
|
||||
xMinSize = x;
|
||||
if (y < yMinSize)
|
||||
yMinSize = y;
|
||||
if (x > xMaxSize)
|
||||
xMaxSize = x;
|
||||
if (y > yMaxSize)
|
||||
yMaxSize = y;
|
||||
if (doFill)
|
||||
content.add(x, y, u, v, r, g, b, a);
|
||||
var gp = new GPoint();
|
||||
gp.load(x, y, lineR, lineG, lineB, lineA);
|
||||
tmpPoints.push(gp);
|
||||
}
|
||||
|
||||
override function draw(ctx:RenderContext) {
|
||||
if (!ctx.beginDrawBatchState(this))
|
||||
return;
|
||||
content.doRender(ctx);
|
||||
}
|
||||
|
||||
override function sync(ctx:RenderContext) {
|
||||
super.sync(ctx);
|
||||
flush();
|
||||
content.flush();
|
||||
}
|
||||
}
|
||||
305
src/gui/GuiMLTextListCtrl.hx
Normal file
305
src/gui/GuiMLTextListCtrl.hx
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
package gui;
|
||||
|
||||
import h2d.filter.Filter;
|
||||
import h2d.HtmlText;
|
||||
import h2d.Flow;
|
||||
import h3d.Engine;
|
||||
import h2d.Tile;
|
||||
import h2d.Bitmap;
|
||||
import h3d.mat.Texture;
|
||||
import shaders.GuiClipFilter;
|
||||
import h2d.Graphics;
|
||||
import gui.GuiControl.MouseState;
|
||||
import h2d.Scene;
|
||||
import h2d.Text;
|
||||
import h2d.Font;
|
||||
import src.MarbleGame;
|
||||
import src.Settings;
|
||||
|
||||
class GuiMLTextListCtrl extends GuiControl {
|
||||
public var texts:Array<String>;
|
||||
public var onSelectedFunc:Int->Void;
|
||||
|
||||
var font:Font;
|
||||
var textObjs:Array<Text>;
|
||||
var g:Graphics;
|
||||
var _prevSelected:Int = -1;
|
||||
|
||||
public var selectedColor:Int = 0x206464;
|
||||
public var selectedFillColor:Int = 0xC8C8C8;
|
||||
|
||||
public var textYOffset:Int = 0;
|
||||
|
||||
public var scroll:Float = 0;
|
||||
|
||||
public var scrollable:Bool = false;
|
||||
|
||||
var filter:Filter = null;
|
||||
|
||||
var flow:Flow;
|
||||
var _imageLoader:String->Tile;
|
||||
|
||||
public function new(font:Font, texts:Array<String>, imageLoader:String->Tile, ?filter:Filter = null) {
|
||||
super();
|
||||
this.font = font;
|
||||
this.texts = texts;
|
||||
this._manualScroll = true;
|
||||
this.textObjs = [];
|
||||
this.filter = filter;
|
||||
this._imageLoader = imageLoader;
|
||||
for (text in texts) {
|
||||
var tobj = new HtmlText(font);
|
||||
tobj.lineHeightMode = TextOnly;
|
||||
tobj.loadImage = imageLoader;
|
||||
tobj.text = text;
|
||||
tobj.textColor = 0;
|
||||
if (filter != null)
|
||||
tobj.filter = filter;
|
||||
textObjs.push(tobj);
|
||||
}
|
||||
this.g = new Graphics();
|
||||
}
|
||||
|
||||
public function setTexts(texts:Array<String>) {
|
||||
var renderRect = this.getRenderRectangle();
|
||||
for (textObj in this.textObjs) {
|
||||
textObj.remove();
|
||||
}
|
||||
this.textObjs = [];
|
||||
for (text in texts) {
|
||||
var tobj = new HtmlText(font);
|
||||
tobj.loadImage = this._imageLoader;
|
||||
tobj.lineHeightMode = TextOnly;
|
||||
tobj.text = text;
|
||||
tobj.textColor = 0;
|
||||
if (filter != null)
|
||||
tobj.filter = filter;
|
||||
textObjs.push(tobj);
|
||||
|
||||
if (this.scrollable) {
|
||||
if (this.flow != null) {
|
||||
if (this.flow.contains(tobj))
|
||||
this.flow.removeChild(tobj);
|
||||
|
||||
this.flow.addChild(tobj);
|
||||
|
||||
this.flow.getProperties(tobj).isAbsolute = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.texts = texts;
|
||||
this._prevSelected = -1;
|
||||
if (this.onSelectedFunc != null)
|
||||
this.onSelectedFunc(-1);
|
||||
|
||||
redrawSelectionRect(renderRect);
|
||||
|
||||
for (i in 0...textObjs.length) {
|
||||
var text = textObjs[i];
|
||||
text.setPosition(Math.floor((!scrollable ? renderRect.position.x : 0) + 5),
|
||||
Math.floor((!scrollable ? renderRect.position.y : 0)
|
||||
+ (i * (text.font.size + 4 * Settings.uiScale) + (5 + textYOffset) * Settings.uiScale - this.scroll)));
|
||||
|
||||
if (_prevSelected == i) {
|
||||
text.textColor = selectedColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override function render(scene2d:Scene, ?parent:h2d.Flow) {
|
||||
var renderRect = this.getRenderRectangle();
|
||||
var htr = this.getHitTestRect(false);
|
||||
|
||||
if (parent != null) {
|
||||
if (parent.contains(g))
|
||||
parent.removeChild(g);
|
||||
parent.addChild(g);
|
||||
|
||||
var off = this.getOffsetFromParent();
|
||||
parent.getProperties(g).isAbsolute = true;
|
||||
|
||||
g.setPosition(off.x, off.y - this.scroll);
|
||||
}
|
||||
|
||||
if (scrollable) {
|
||||
this.flow = new Flow();
|
||||
|
||||
this.flow.maxWidth = cast htr.extent.x;
|
||||
this.flow.maxHeight = cast htr.extent.y;
|
||||
this.flow.multiline = true;
|
||||
this.flow.layout = Stack;
|
||||
this.flow.overflow = FlowOverflow.Hidden;
|
||||
|
||||
if (parent != null) {
|
||||
if (parent.contains(this.flow)) {
|
||||
parent.removeChild(this.flow);
|
||||
}
|
||||
parent.addChild(this.flow);
|
||||
var off = this.getOffsetFromParent();
|
||||
var props = parent.getProperties(this.flow);
|
||||
props.isAbsolute = true;
|
||||
|
||||
this.flow.setPosition(off.x, off.y);
|
||||
}
|
||||
}
|
||||
|
||||
for (i in 0...textObjs.length) {
|
||||
var text = textObjs[i];
|
||||
if (!scrollable) {
|
||||
if (scene2d.contains(text))
|
||||
scene2d.removeChild(text);
|
||||
scene2d.addChild(text);
|
||||
} else {
|
||||
if (this.flow.contains(text))
|
||||
this.flow.removeChild(text);
|
||||
this.flow.addChild(text);
|
||||
|
||||
this.flow.getProperties(text).isAbsolute = true;
|
||||
}
|
||||
|
||||
text.setPosition(Math.floor((!scrollable ? renderRect.position.x : 0) + 5),
|
||||
Math.floor((!scrollable ? renderRect.position.y : 0)
|
||||
+ (i * (text.font.size + 4 * Settings.uiScale) + (5 + textYOffset) * Settings.uiScale - this.scroll)));
|
||||
|
||||
if (_prevSelected == i) {
|
||||
text.textColor = selectedColor;
|
||||
}
|
||||
}
|
||||
|
||||
redrawSelectionRect(htr);
|
||||
super.render(scene2d, parent);
|
||||
}
|
||||
|
||||
public function calculateFullHeight() {
|
||||
return (this.texts.length * (font.size + 4 * Settings.uiScale));
|
||||
}
|
||||
|
||||
public override function dispose() {
|
||||
super.dispose();
|
||||
for (text in textObjs) {
|
||||
text.remove();
|
||||
}
|
||||
this.g.remove();
|
||||
if (this.scrollable) {
|
||||
this.flow.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public override function onRemove() {
|
||||
super.onRemove();
|
||||
for (text in textObjs) {
|
||||
if (MarbleGame.canvas.scene2d.contains(text)) {
|
||||
MarbleGame.canvas.scene2d.removeChild(text); // Refresh "layer"
|
||||
}
|
||||
text.remove();
|
||||
}
|
||||
if (MarbleGame.canvas.scene2d.contains(g))
|
||||
MarbleGame.canvas.scene2d.removeChild(g);
|
||||
g.remove();
|
||||
}
|
||||
|
||||
public override function onMouseMove(mouseState:MouseState) {
|
||||
var mousePos = mouseState.position;
|
||||
var renderRect = this.getRenderRectangle();
|
||||
var yStart = renderRect.position.y;
|
||||
var dy = mousePos.y - yStart;
|
||||
var hoverIndex = Math.floor(dy / (font.size + 4 * Settings.uiScale));
|
||||
if (hoverIndex >= this.texts.length) {
|
||||
hoverIndex = -1;
|
||||
}
|
||||
|
||||
// Update the texts
|
||||
for (i in 0...textObjs.length) {
|
||||
var selected = i == hoverIndex || i == this._prevSelected;
|
||||
var text = textObjs[i];
|
||||
text.textColor = selected ? selectedColor : 0;
|
||||
// fill color = 0xC8C8C8
|
||||
}
|
||||
// obviously in renderRect
|
||||
}
|
||||
|
||||
public override function onMouseLeave(mouseState:MouseState) {
|
||||
for (i in 0...textObjs.length) {
|
||||
if (i == this._prevSelected)
|
||||
continue;
|
||||
var text = textObjs[i];
|
||||
text.textColor = 0;
|
||||
// fill color = 0xC8C8C8
|
||||
}
|
||||
}
|
||||
|
||||
public override function onMousePress(mouseState:MouseState) {
|
||||
super.onMousePress(mouseState);
|
||||
|
||||
var mousePos = mouseState.position;
|
||||
var renderRect = this.getRenderRectangle();
|
||||
var yStart = renderRect.position.y;
|
||||
var dy = mousePos.y - yStart;
|
||||
var selectedIndex = Math.floor((dy + this.scroll) / (font.size + 4 * Settings.uiScale));
|
||||
if (selectedIndex >= this.texts.length) {
|
||||
selectedIndex = -1;
|
||||
}
|
||||
if (_prevSelected != selectedIndex) {
|
||||
_prevSelected = selectedIndex;
|
||||
|
||||
redrawSelectionRect(renderRect);
|
||||
}
|
||||
|
||||
if (onSelectedFunc != null) {
|
||||
onSelectedFunc(selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function redrawSelectionRect(renderRect:Rect) {
|
||||
if (_prevSelected != -1) {
|
||||
g.clear();
|
||||
g.beginFill(selectedFillColor);
|
||||
|
||||
var off = this.getOffsetFromParent();
|
||||
// Check if we are between the top and bottom, render normally in that case
|
||||
var topY = 2 * Settings.uiScale + (_prevSelected * (font.size + 4 * Settings.uiScale)) + g.y;
|
||||
var bottomY = 2 * Settings.uiScale + (_prevSelected * (font.size + 4 * Settings.uiScale)) + g.y + font.size + 4 * Settings.uiScale;
|
||||
var topRectY = off.y;
|
||||
var bottomRectY = off.y + renderRect.extent.y;
|
||||
|
||||
if (topY >= topRectY && bottomY <= bottomRectY)
|
||||
g.drawRect(0, 5 * Settings.uiScale
|
||||
+ (_prevSelected * (font.size + 4 * Settings.uiScale))
|
||||
- 3 * Settings.uiScale, renderRect.extent.x,
|
||||
font.size
|
||||
+ 4 * Settings.uiScale);
|
||||
// We need to do math the draw the partially visible top selected
|
||||
if (topY <= topRectY && bottomY >= topRectY) {
|
||||
g.drawRect(0, this.scroll, renderRect.extent.x, topY + font.size + 4 * Settings.uiScale - off.y);
|
||||
}
|
||||
// Same for the bottom
|
||||
if (topY <= bottomRectY && bottomY >= bottomRectY) {
|
||||
g.drawRect(0, this.scroll
|
||||
+ renderRect.extent.y
|
||||
- font.size
|
||||
- 4 * Settings.uiScale
|
||||
+ (topY + font.size + 4 * Settings.uiScale - bottomRectY),
|
||||
renderRect.extent.x, off.y
|
||||
+ renderRect.extent.y
|
||||
- (topY));
|
||||
}
|
||||
g.endFill();
|
||||
} else {
|
||||
g.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public override function onScroll(scrollX:Float, scrollY:Float) {
|
||||
super.onScroll(scrollX, scrollY);
|
||||
var renderRect = this.getRenderRectangle();
|
||||
|
||||
this.scroll = scrollY;
|
||||
var hittestrect = this.getHitTestRect(false);
|
||||
for (i in 0...textObjs.length) {
|
||||
var text = textObjs[i];
|
||||
text.y = Math.floor((i * (text.font.size + 4 * Settings.uiScale) + (5 + textYOffset) * Settings.uiScale - scrollY));
|
||||
g.y = -scrollY;
|
||||
}
|
||||
redrawSelectionRect(hittestrect);
|
||||
}
|
||||
}
|
||||
|
|
@ -82,4 +82,9 @@ class GuiTextInput extends GuiControl {
|
|||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public function setCaretColor(col:Int) {
|
||||
text.cursorTile = h2d.Tile.fromColor(col, Std.int(1 / hxd.Window.getInstance().windowToPixelRatio), text.font.size);
|
||||
text.cursorTile.dy = 2 / hxd.Window.getInstance().windowToPixelRatio;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class GuiTextListCtrl extends GuiControl {
|
|||
|
||||
public var selectedColor:Int = 0x206464;
|
||||
public var selectedFillColor:Int = 0xC8C8C8;
|
||||
public var textColor:Int = 0;
|
||||
|
||||
public var textYOffset:Int = 0;
|
||||
|
||||
|
|
@ -34,16 +35,17 @@ class GuiTextListCtrl extends GuiControl {
|
|||
|
||||
var flow:Flow;
|
||||
|
||||
public function new(font:Font, texts:Array<String>) {
|
||||
public function new(font:Font, texts:Array<String>, textColor:Int = 0) {
|
||||
super();
|
||||
this.font = font;
|
||||
this.texts = texts;
|
||||
this._manualScroll = true;
|
||||
this.textObjs = [];
|
||||
this.textColor = textColor;
|
||||
for (text in texts) {
|
||||
var tobj = new Text(font);
|
||||
tobj.text = text;
|
||||
tobj.textColor = 0;
|
||||
tobj.textColor = textColor;
|
||||
textObjs.push(tobj);
|
||||
}
|
||||
this.g = new Graphics();
|
||||
|
|
@ -58,7 +60,7 @@ class GuiTextListCtrl extends GuiControl {
|
|||
for (text in texts) {
|
||||
var tobj = new Text(font);
|
||||
tobj.text = text;
|
||||
tobj.textColor = 0;
|
||||
tobj.textColor = textColor;
|
||||
textObjs.push(tobj);
|
||||
|
||||
if (this.scrollable) {
|
||||
|
|
@ -195,7 +197,7 @@ class GuiTextListCtrl extends GuiControl {
|
|||
for (i in 0...textObjs.length) {
|
||||
var selected = i == hoverIndex || i == this._prevSelected;
|
||||
var text = textObjs[i];
|
||||
text.textColor = selected ? selectedColor : 0;
|
||||
text.textColor = selected ? selectedColor : textColor;
|
||||
// fill color = 0xC8C8C8
|
||||
}
|
||||
// obviously in renderRect
|
||||
|
|
@ -206,7 +208,7 @@ class GuiTextListCtrl extends GuiControl {
|
|||
if (i == this._prevSelected)
|
||||
continue;
|
||||
var text = textObjs[i];
|
||||
text.textColor = 0;
|
||||
text.textColor = textColor;
|
||||
// fill color = 0xC8C8C8
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package gui;
|
||||
|
||||
import net.MasterServerClient;
|
||||
import net.MasterServerClient.RemoteServerInfo;
|
||||
import net.Net;
|
||||
import h2d.filter.DropShadow;
|
||||
import hxd.res.BitmapFont;
|
||||
import src.MarbleGame;
|
||||
|
|
@ -28,48 +31,6 @@ class JoinServerGui extends GuiImage {
|
|||
return [normal, hover, pressed];
|
||||
}
|
||||
|
||||
this.horizSizing = Width;
|
||||
this.vertSizing = Height;
|
||||
this.position = new Vector();
|
||||
this.extent = new Vector(640, 480);
|
||||
|
||||
var window = new GuiImage(ResourceLoader.getResource("data/ui/mp/join/window.png", ResourceLoader.getImage, this.imageResources).toTile());
|
||||
window.horizSizing = Center;
|
||||
window.vertSizing = Center;
|
||||
window.position = new Vector(-60, 5);
|
||||
window.extent = new Vector(759, 469);
|
||||
|
||||
var hostBtn = new GuiButton(loadButtonImages("data/ui/mp/join/host"));
|
||||
hostBtn.position = new Vector(521, 379);
|
||||
hostBtn.extent = new Vector(93, 45);
|
||||
hostBtn.pressedAction = (e) -> {
|
||||
MarbleGame.canvas.setContent(new MPPlayMissionGui());
|
||||
}
|
||||
window.addChild(hostBtn);
|
||||
|
||||
var joinBtn = new GuiButton(loadButtonImages("data/ui/mp/join/join"));
|
||||
joinBtn.position = new Vector(628, 379);
|
||||
joinBtn.extent = new Vector(93, 45);
|
||||
window.addChild(joinBtn);
|
||||
|
||||
var refreshBtn = new GuiButton(loadButtonImages("data/ui/mp/join/refresh/refresh-1"));
|
||||
refreshBtn.position = new Vector(126, 379);
|
||||
refreshBtn.extent = new Vector(45, 45);
|
||||
window.addChild(refreshBtn);
|
||||
|
||||
var serverSettingsBtn = new GuiButton(loadButtonImages("data/ui/mp/play/settings"));
|
||||
serverSettingsBtn.position = new Vector(171, 379);
|
||||
serverSettingsBtn.extent = new Vector(45, 45);
|
||||
window.addChild(serverSettingsBtn);
|
||||
|
||||
var exitBtn = new GuiButton(loadButtonImages("data/ui/mp/join/leave"));
|
||||
exitBtn.position = new Vector(32, 379);
|
||||
exitBtn.extent = new Vector(93, 45);
|
||||
exitBtn.pressedAction = (e) -> {
|
||||
MarbleGame.canvas.setContent(new MainMenuGui());
|
||||
}
|
||||
window.addChild(exitBtn);
|
||||
|
||||
var markerFelt32fontdata = ResourceLoader.getFileEntry("data/font/MarkerFelt.fnt");
|
||||
var markerFelt32b = new BitmapFont(markerFelt32fontdata.entry);
|
||||
@:privateAccess markerFelt32b.loader = ResourceLoader.loader;
|
||||
|
|
@ -90,6 +51,113 @@ class JoinServerGui extends GuiImage {
|
|||
}
|
||||
}
|
||||
|
||||
this.horizSizing = Width;
|
||||
this.vertSizing = Height;
|
||||
this.position = new Vector();
|
||||
this.extent = new Vector(640, 480);
|
||||
|
||||
var window = new GuiImage(ResourceLoader.getResource("data/ui/mp/join/window.png", ResourceLoader.getImage, this.imageResources).toTile());
|
||||
window.horizSizing = Center;
|
||||
window.vertSizing = Center;
|
||||
window.position = new Vector(-60, 5);
|
||||
window.extent = new Vector(759, 469);
|
||||
|
||||
var serverListContainer = new GuiControl();
|
||||
serverListContainer.position = new Vector(30, 80);
|
||||
serverListContainer.extent = new Vector(475, 290);
|
||||
window.addChild(serverListContainer);
|
||||
|
||||
var curSelection = -1;
|
||||
var serverList = new GuiTextListCtrl(markerFelt18, [], 0xFFFFFF);
|
||||
serverList.position = new Vector(0, 0);
|
||||
serverList.extent = new Vector(475, 63);
|
||||
serverList.onSelectedFunc = (sel) -> {
|
||||
curSelection = sel;
|
||||
}
|
||||
serverListContainer.addChild(serverList);
|
||||
|
||||
var serverDisplays = [];
|
||||
|
||||
var ourServerList:Array<RemoteServerInfo> = [];
|
||||
var platformToString = ["unknown", "pc", "mac", "web", "android"];
|
||||
|
||||
function updateServerListDisplay() {
|
||||
serverDisplays = ourServerList.map(x -> '${x.name}');
|
||||
serverList.setTexts(serverDisplays);
|
||||
}
|
||||
|
||||
MasterServerClient.connectToMasterServer(() -> {
|
||||
MasterServerClient.instance.getServerList((servers) -> {
|
||||
ourServerList = servers;
|
||||
updateServerListDisplay();
|
||||
});
|
||||
});
|
||||
|
||||
var maxPlayers = 8;
|
||||
var privateSlots = 0;
|
||||
var privateGame = false;
|
||||
|
||||
var hostBtn = new GuiButton(loadButtonImages("data/ui/mp/join/host"));
|
||||
hostBtn.position = new Vector(521, 379);
|
||||
hostBtn.extent = new Vector(93, 45);
|
||||
hostBtn.pressedAction = (e) -> {
|
||||
Net.hostServer('${Settings.highscoreName}\'s Server', maxPlayers, privateSlots, privateGame, () -> {
|
||||
MarbleGame.canvas.setContent(new MPPlayMissionGui(true));
|
||||
});
|
||||
}
|
||||
window.addChild(hostBtn);
|
||||
|
||||
var joinBtn = new GuiButton(loadButtonImages("data/ui/mp/join/join"));
|
||||
joinBtn.position = new Vector(628, 379);
|
||||
joinBtn.extent = new Vector(93, 45);
|
||||
joinBtn.pressedAction = (e) -> {
|
||||
if (curSelection != -1) {
|
||||
var selectedServerVersion = ourServerList[curSelection].version;
|
||||
// if (selectedServerVersion != MarbleGame.currentVersion) {
|
||||
// var pup = new MessageBoxOkDlg("You are using a different version of the game than the server. Please update your game.");
|
||||
// MarbleGame.canvas.pushDialog(pup);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// MarbleGame.canvas.setContent(new MultiplayerLoadingGui("Connecting"));
|
||||
var failed = true;
|
||||
haxe.Timer.delay(() -> {
|
||||
if (failed) {
|
||||
// if (MarbleGame.canvas.content is MultiplayerLoadingGui) {
|
||||
// var loadGui:MultiplayerLoadingGui = cast MarbleGame.canvas.content;
|
||||
// if (loadGui != null) {
|
||||
// loadGui.setErrorStatus("Failed to connect to server. Please try again.");
|
||||
Net.disconnect();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}, 15000);
|
||||
Net.joinServer(ourServerList[curSelection].name, false, () -> {
|
||||
failed = false;
|
||||
Net.remoteServerInfo = ourServerList[curSelection];
|
||||
});
|
||||
}
|
||||
}
|
||||
window.addChild(joinBtn);
|
||||
|
||||
var refreshBtn = new GuiButton(loadButtonImages("data/ui/mp/join/refresh/refresh-1"));
|
||||
refreshBtn.position = new Vector(126, 379);
|
||||
refreshBtn.extent = new Vector(45, 45);
|
||||
window.addChild(refreshBtn);
|
||||
|
||||
var serverSettingsBtn = new GuiButton(loadButtonImages("data/ui/mp/play/settings"));
|
||||
serverSettingsBtn.position = new Vector(171, 379);
|
||||
serverSettingsBtn.extent = new Vector(45, 45);
|
||||
window.addChild(serverSettingsBtn);
|
||||
|
||||
var exitBtn = new GuiButton(loadButtonImages("data/ui/mp/join/leave"));
|
||||
exitBtn.position = new Vector(32, 379);
|
||||
exitBtn.extent = new Vector(93, 45);
|
||||
exitBtn.pressedAction = (e) -> {
|
||||
MarbleGame.canvas.setContent(new MainMenuGui());
|
||||
}
|
||||
window.addChild(exitBtn);
|
||||
|
||||
var titleText = new GuiText(markerFelt32);
|
||||
titleText.position = new Vector(30, 20);
|
||||
titleText.extent = new Vector(647, 30);
|
||||
|
|
|
|||
|
|
@ -13,11 +13,19 @@ import h3d.Vector;
|
|||
import src.Util;
|
||||
import src.Settings;
|
||||
import src.Mission;
|
||||
import src.MissionList;
|
||||
import net.ClientConnection.NetPlatform;
|
||||
import net.Net;
|
||||
import net.NetCommands;
|
||||
|
||||
class MPPlayMissionGui extends GuiImage {
|
||||
static var currentSelectionStatic:Int = -1;
|
||||
static var currentCategoryStatic:String = "beginner";
|
||||
|
||||
static var setLevelFn:(String, Int) -> Void;
|
||||
static var playSelectedLevel:(String, Int) -> Void;
|
||||
static var setLevelStr:String->Void;
|
||||
|
||||
var currentSelection:Int = 0;
|
||||
var currentCategory:String = "beginner";
|
||||
var currentList:Array<Mission>;
|
||||
|
|
@ -36,7 +44,7 @@ class MPPlayMissionGui extends GuiImage {
|
|||
var previewToken:Int = 0;
|
||||
#end
|
||||
|
||||
public function new() {
|
||||
public function new(isHost:Bool = true) {
|
||||
MissionList.buildMissionList();
|
||||
function chooseBg() {
|
||||
var rand = Math.random();
|
||||
|
|
@ -268,7 +276,8 @@ class MPPlayMissionGui extends GuiImage {
|
|||
playBtn.position = new Vector(565, 514);
|
||||
playBtn.extent = new Vector(93, 44);
|
||||
playBtn.pressedAction = (sender) -> {
|
||||
MarbleGame.instance.playMission(currentList[currentSelection]);
|
||||
NetCommands.toggleReadiness(Net.isClient ? Net.clientId : 0);
|
||||
// MarbleGame.instance.playMission(currentList[currentSelection], true);
|
||||
}
|
||||
window.addChild(playBtn);
|
||||
|
||||
|
|
@ -484,6 +493,15 @@ class MPPlayMissionGui extends GuiImage {
|
|||
#end
|
||||
}
|
||||
|
||||
playSelectedLevel = (cat:String, index:Int) -> {
|
||||
// if (custSelected) {
|
||||
// NetCommands.playCustomLevel(MPCustoms.missionList[custSelectedIdx].path);
|
||||
// } else {
|
||||
var curMission = MissionList.missionList["multiplayer"][cat][index]; // mission[index];
|
||||
MarbleGame.instance.playMission(curMission, true);
|
||||
// }
|
||||
}
|
||||
|
||||
currentList = MissionList.missionList["multiplayer"]["beginner"];
|
||||
|
||||
setCategoryFunc(currentCategoryStatic, null, false);
|
||||
|
|
@ -504,4 +522,66 @@ class MPPlayMissionGui extends GuiImage {
|
|||
if (Key.isPressed(Key.RIGHT))
|
||||
setSelectedFunc(currentSelection + 1);
|
||||
}
|
||||
|
||||
inline function platformToString(platform:NetPlatform) {
|
||||
return switch (platform) {
|
||||
case Unknown: return "unknown";
|
||||
case Android: return "android";
|
||||
case MacOS: return "mac";
|
||||
case PC: return "pc";
|
||||
case Web: return "web";
|
||||
}
|
||||
}
|
||||
|
||||
public function updateLobbyNames() {
|
||||
return;
|
||||
var playerListArr = [];
|
||||
if (Net.isHost) {
|
||||
playerListArr.push({
|
||||
name: Settings.highscoreName,
|
||||
state: Net.lobbyHostReady,
|
||||
platform: Net.getPlatform()
|
||||
});
|
||||
}
|
||||
if (Net.isClient) {
|
||||
playerListArr.push({
|
||||
name: Settings.highscoreName,
|
||||
state: Net.lobbyClientReady,
|
||||
platform: Net.getPlatform()
|
||||
});
|
||||
}
|
||||
if (Net.clientIdMap != null) {
|
||||
for (c => v in Net.clientIdMap) {
|
||||
playerListArr.push({
|
||||
name: v.name,
|
||||
state: v.lobbyReady,
|
||||
platform: v.platform
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// if (!showingCustoms)
|
||||
// playerList.setTexts(playerListArr.map(player -> {
|
||||
// return '<img src="${player.state ? "ready" : "notready"}"></img><img src="${platformToString(player.platform)}"></img>${player.name}';
|
||||
// }));
|
||||
|
||||
var pubCount = 1; // Self
|
||||
var privCount = 0;
|
||||
for (cid => cc in Net.clientIdMap) {
|
||||
if (cc.isPrivate) {
|
||||
privCount++;
|
||||
} else {
|
||||
pubCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (Net.isHost) {
|
||||
// updatePlayerCountFn(pubCount, privCount, Net.serverInfo.maxPlayers - Net.serverInfo.privateSlots, Net.serverInfo.privateSlots);
|
||||
}
|
||||
}
|
||||
|
||||
public function updatePlayerCount(pub:Int, priv:Int, publicTotal:Int, privateTotal:Int) {
|
||||
return;
|
||||
// updatePlayerCountFn(pub, priv, publicTotal, privateTotal);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,493 @@ import src.Settings;
|
|||
import src.ResourceLoaderWorker;
|
||||
|
||||
class MarbleSelectGui extends GuiImage {
|
||||
public static var marbleData = [
|
||||
[
|
||||
{
|
||||
name: "Staff's Original",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "3D Marble",
|
||||
dts: "data/shapes/balls/3dMarble.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Mid P",
|
||||
dts: "data/shapes/balls/midp.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Spade",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin4",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "GMD Logo",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin5",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Textured Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin6",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Golden Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin7",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Rainbow Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin8",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Brown Swirls",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin9",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Caution Stripes",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin10",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Earth",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin11",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Golf Ball",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin12",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Jupiter",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin13",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "MB Gold Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin14",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "MBP on the Marble!",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin15",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Moshe",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin16",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Strong Bad",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin17",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Venus",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin18",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Water",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin19",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Evil Eye",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin20",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Desert and Sky",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin21",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Dirt Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin22",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Friction Textured Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin23",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Grass",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin24",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Mars",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin25",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Phil's Golf Ball",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin26",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Molten",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin27",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Lightning",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin28",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Phil'sEmpire",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin29",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Matan's Red Dragon",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin30",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Metallic Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin31",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Sun",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin32",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Underwater",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin33",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "GarageGames logo",
|
||||
dts: "data/shapes/balls/garageGames.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Big Marble 1",
|
||||
dts: "data/shapes/balls/bm1.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Big Marble 2",
|
||||
dts: "data/shapes/balls/bm2.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Big Marble 3",
|
||||
dts: "data/shapes/balls/bm3.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Small Marble 1",
|
||||
dts: "data/shapes/balls/sm1.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Small Marble 2",
|
||||
dts: "data/shapes/balls/sm2.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Small Marble 3",
|
||||
dts: "data/shapes/balls/sm3.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
name: "Deep Blue",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin1",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Blood Red",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin2",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Gang Green",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin6",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Pink Candy",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin27",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Chocolate",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin5",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Grape",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin4",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Lemon",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin28",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Lime Green",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin8",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Blueberry",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin7",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Tangerine",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin3",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "8 Ball",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin9",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Ace of Hearts",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin22",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Football",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin16",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "9 Ball",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin29",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Ace of Spades",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin24",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "GarageGames",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin10",
|
||||
shader: "ClassicMarb2"
|
||||
},
|
||||
{
|
||||
name: "Bob",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin30",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Skully",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin18",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Jack-o-Lantern",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin34",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Walled Up",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin25",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Sunny Side Up",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin11",
|
||||
shader: "ClassicMetal"
|
||||
},
|
||||
{
|
||||
name: "Lunar",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin31",
|
||||
shader: "ClassicMetal"
|
||||
},
|
||||
{
|
||||
name: "Battery",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin14",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Static",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin32",
|
||||
shader: "ClassicMarb2"
|
||||
},
|
||||
{
|
||||
name: "Earth",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin20",
|
||||
shader: "ClassicMarbGlass20"
|
||||
},
|
||||
{
|
||||
name: "Red and X",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin13",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Orange Spiral",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin12",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Blue Spiral",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin15",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Sliced Marble",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin21",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Orange Checkers",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin19",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Torque",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin33",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Fred",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin17",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Pirate",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin26",
|
||||
shader: "ClassicMarbGlass18"
|
||||
},
|
||||
{
|
||||
name: "Shuriken",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin23",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Eyeball",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin35",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Woody",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin36",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Dat Nostalgia",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin37",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Graffiti",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin38",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Asteroid",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin39",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Disco Ball",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin40",
|
||||
shader: "Default"
|
||||
}
|
||||
],
|
||||
];
|
||||
|
||||
public function new() {
|
||||
var img = ResourceLoader.getImage("data/ui/marbleSelect/marbleSelect.png");
|
||||
super(img.resource.toTile());
|
||||
|
|
@ -21,494 +508,6 @@ class MarbleSelectGui extends GuiImage {
|
|||
this.position = new Vector(73, -59);
|
||||
this.extent = new Vector(493, 361);
|
||||
|
||||
var marbleData = [
|
||||
[
|
||||
{
|
||||
name: "Staff's Original",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "3D Marble",
|
||||
dts: "data/shapes/balls/3dMarble.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Mid P",
|
||||
dts: "data/shapes/balls/midp.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Spade",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin4",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "GMD Logo",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin5",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Textured Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin6",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Golden Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin7",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Rainbow Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin8",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Brown Swirls",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin9",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Caution Stripes",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin10",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Earth",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin11",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Golf Ball",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin12",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Jupiter",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin13",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "MB Gold Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin14",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "MBP on the Marble!",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin15",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Moshe",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin16",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Strong Bad",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin17",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Venus",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin18",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Water",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin19",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Evil Eye",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin20",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Desert and Sky",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin21",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Dirt Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin22",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Friction Textured Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin23",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Grass",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin24",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Mars",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin25",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Phil's Golf Ball",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin26",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Molten",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin27",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Lightning",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin28",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Phil'sEmpire",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin29",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Matan's Red Dragon",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin30",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Metallic Marble",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin31",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Sun",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin32",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Underwater",
|
||||
dts: "data/shapes/balls/ball-superball.dts",
|
||||
skin: "skin33",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "GarageGames logo",
|
||||
dts: "data/shapes/balls/garageGames.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Big Marble 1",
|
||||
dts: "data/shapes/balls/bm1.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Big Marble 2",
|
||||
dts: "data/shapes/balls/bm2.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Big Marble 3",
|
||||
dts: "data/shapes/balls/bm3.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Small Marble 1",
|
||||
dts: "data/shapes/balls/sm1.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Small Marble 2",
|
||||
dts: "data/shapes/balls/sm2.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Small Marble 3",
|
||||
dts: "data/shapes/balls/sm3.dts",
|
||||
skin: "base",
|
||||
shader: "Default"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
name: "Deep Blue",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin1",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Blood Red",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin2",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Gang Green",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin6",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Pink Candy",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin27",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Chocolate",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin5",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Grape",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin4",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Lemon",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin28",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Lime Green",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin8",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Blueberry",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin7",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Tangerine",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin3",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "8 Ball",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin9",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Ace of Hearts",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin22",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Football",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin16",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "9 Ball",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin29",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Ace of Spades",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin24",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "GarageGames",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin10",
|
||||
shader: "ClassicMarb2"
|
||||
},
|
||||
{
|
||||
name: "Bob",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin30",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Skully",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin18",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Jack-o-Lantern",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin34",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Walled Up",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin25",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Sunny Side Up",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin11",
|
||||
shader: "ClassicMetal"
|
||||
},
|
||||
{
|
||||
name: "Lunar",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin31",
|
||||
shader: "ClassicMetal"
|
||||
},
|
||||
{
|
||||
name: "Battery",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin14",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Static",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin32",
|
||||
shader: "ClassicMarb2"
|
||||
},
|
||||
{
|
||||
name: "Earth",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin20",
|
||||
shader: "ClassicMarbGlass20"
|
||||
},
|
||||
{
|
||||
name: "Red and X",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin13",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Orange Spiral",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin12",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Blue Spiral",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin15",
|
||||
shader: "ClassicGlassPureSphere"
|
||||
},
|
||||
{
|
||||
name: "Sliced Marble",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin21",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Orange Checkers",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin19",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Torque",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin33",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Fred",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin17",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Pirate",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin26",
|
||||
shader: "ClassicMarbGlass18"
|
||||
},
|
||||
{
|
||||
name: "Shuriken",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin23",
|
||||
shader: "ClassicMarb3"
|
||||
},
|
||||
{
|
||||
name: "Eyeball",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin35",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Woody",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin36",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Dat Nostalgia",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin37",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Graffiti",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin38",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Asteroid",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin39",
|
||||
shader: "Default"
|
||||
},
|
||||
{
|
||||
name: "Disco Ball",
|
||||
dts: "data/shapes/balls/pack1/pack1marble.dts",
|
||||
skin: "uskin40",
|
||||
shader: "Default"
|
||||
}
|
||||
|
||||
],
|
||||
];
|
||||
|
||||
var categoryNames = ["Official Marbles", "MBUltra"];
|
||||
|
||||
var curSelection:Int = Settings.optionsSettings.marbleIndex;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import src.ResourceLoader;
|
|||
import h3d.Vector;
|
||||
import src.Util;
|
||||
import src.MarbleGame;
|
||||
import src.MissionList;
|
||||
|
||||
class PlayMissionGui extends GuiImage {
|
||||
static var currentSelectionStatic:Int = -1;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import h3d.Vector;
|
|||
import src.Util;
|
||||
import src.MarbleGame;
|
||||
import src.Settings;
|
||||
import src.MissionList;
|
||||
|
||||
class ReplayCenterGui extends GuiImage {
|
||||
public function new() {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import hxd.res.BitmapFont;
|
|||
import h3d.Vector;
|
||||
import src.ResourceLoader;
|
||||
import src.Settings;
|
||||
import src.MissionList;
|
||||
|
||||
class SearchGui extends GuiImage {
|
||||
public function new(game:String, isCustom:Bool) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import src.Settings;
|
|||
import src.Settings.PlayStatistics;
|
||||
import src.Mission;
|
||||
import src.Util;
|
||||
import src.MissionList;
|
||||
|
||||
class StatisticsGui extends GuiImage {
|
||||
public function new(game:String) {
|
||||
|
|
|
|||
|
|
@ -255,6 +255,28 @@ class HuntMode extends NullMode {
|
|||
}
|
||||
}
|
||||
|
||||
public inline function setGemHiddenStatus(gemId:Int, status:Bool) {
|
||||
var gemSpawn = gemSpawnPoints[gemId];
|
||||
if (gemSpawn.gem != null) {
|
||||
gemSpawn.gem.pickedUp = status;
|
||||
gemSpawn.gem.setHide(status);
|
||||
gemSpawn.gemBeam.setHide(status);
|
||||
if (status)
|
||||
this.activeGems.push(gemSpawn.gem);
|
||||
else
|
||||
this.activeGems.remove(gemSpawn.gem);
|
||||
} else {
|
||||
throw new haxe.Exception("Setting gem status for non existent gem!");
|
||||
}
|
||||
}
|
||||
|
||||
public function setActiveSpawnSphere(gems:Array<Int>) {
|
||||
hideExisting();
|
||||
for (gem in gems) {
|
||||
spawnGem(gem);
|
||||
}
|
||||
}
|
||||
|
||||
function getGemWeight(gem:Gem) {
|
||||
if (gem.gemColor == "red")
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class NullMode implements GameMode {
|
|||
// If there's a start pad, start there
|
||||
position = startPad.getAbsPos().getPosition();
|
||||
quat = startPad.getRotationQuat().clone();
|
||||
position.z += 3;
|
||||
} else {
|
||||
position = new Vector(0, 0, 300);
|
||||
}
|
||||
|
|
|
|||
154
src/net/BitStream.hx
Normal file
154
src/net/BitStream.hx
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
package net;
|
||||
|
||||
import haxe.io.FPHelper;
|
||||
import haxe.io.BytesOutput;
|
||||
import haxe.io.BytesInput;
|
||||
import haxe.io.Bytes;
|
||||
|
||||
class InputBitStream {
|
||||
var data:Bytes;
|
||||
var position:Int;
|
||||
var shift:Int;
|
||||
|
||||
public function new(data:Bytes) {
|
||||
this.data = data;
|
||||
this.position = 0;
|
||||
this.shift = 0;
|
||||
}
|
||||
|
||||
function readBits(bits:Int = 8) {
|
||||
if (this.shift + bits >= 8) {
|
||||
var extra = (this.shift + bits) % 8;
|
||||
var remain = bits - extra;
|
||||
var first = data.get(position) >> shift;
|
||||
var result = first;
|
||||
this.position++;
|
||||
if (extra > 0) {
|
||||
var second = (data.get(position) & (0xFF >> (8 - extra))) << remain;
|
||||
result |= second;
|
||||
}
|
||||
this.shift = extra;
|
||||
return result;
|
||||
} else {
|
||||
var result = (data.get(position) >> shift) & (0xFF >> (8 - bits));
|
||||
shift += bits;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public function readInt(bits:Int = 32) {
|
||||
var value = 0;
|
||||
var shift = 0;
|
||||
while (bits > 0) {
|
||||
value |= readBits(bits < 8 ? bits : 8) << shift;
|
||||
shift += 8;
|
||||
bits -= 8;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public function readFlag() {
|
||||
return readInt(1) != 0;
|
||||
}
|
||||
|
||||
public function readByte() {
|
||||
return readInt(8);
|
||||
}
|
||||
|
||||
public function readUInt16() {
|
||||
return readInt(16);
|
||||
}
|
||||
|
||||
public function readInt32() {
|
||||
return readInt(32);
|
||||
}
|
||||
|
||||
public function readFloat() {
|
||||
return FPHelper.i32ToFloat(readInt32());
|
||||
}
|
||||
|
||||
public function readString() {
|
||||
var length = readUInt16();
|
||||
var str = "";
|
||||
for (i in 0...length) {
|
||||
str += String.fromCharCode(readByte());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
class OutputBitStream {
|
||||
var data:BytesOutput;
|
||||
var position:Int;
|
||||
var shift:Int;
|
||||
var lastByte:Int;
|
||||
|
||||
public function new(data:BytesOutput = null) {
|
||||
this.data = data;
|
||||
if (this.data == null)
|
||||
this.data = new BytesOutput();
|
||||
this.position = 0;
|
||||
this.shift = 0;
|
||||
this.lastByte = 0;
|
||||
}
|
||||
|
||||
function writeBits(value:Int, bits:Int) {
|
||||
value = value & (0xFF >> (8 - bits));
|
||||
if (this.shift + bits >= 8) {
|
||||
var extra = (shift + bits) % 8;
|
||||
var remain = bits - extra;
|
||||
|
||||
var first = value & (0xFF >> (8 - remain));
|
||||
lastByte |= first << shift;
|
||||
|
||||
var second = (value >> remain) & (0xFF >> (8 - extra));
|
||||
this.data.writeByte(this.lastByte);
|
||||
this.lastByte = second;
|
||||
this.shift = extra;
|
||||
} else {
|
||||
lastByte |= (value << this.shift) & (0xFF >> (8 - bits - this.shift));
|
||||
this.shift += bits;
|
||||
}
|
||||
}
|
||||
|
||||
public function writeInt(value:Int, bits:Int = 32) {
|
||||
while (bits > 0) {
|
||||
this.writeBits(value & 0xFF, bits < 8 ? bits : 8);
|
||||
value >>= 8;
|
||||
bits -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
public function writeFlag(value:Bool) {
|
||||
writeInt(value ? 1 : 0, 1);
|
||||
}
|
||||
|
||||
public function writeByte(value:Int) {
|
||||
writeInt(value, 8);
|
||||
}
|
||||
|
||||
public function writeUInt16(value:Int) {
|
||||
writeInt(value, 16);
|
||||
}
|
||||
|
||||
public function writeInt32(value:Int) {
|
||||
writeInt(value, 32);
|
||||
}
|
||||
|
||||
public function getBytes() {
|
||||
this.data.writeByte(this.lastByte);
|
||||
return this.data.getBytes();
|
||||
}
|
||||
|
||||
public function writeFloat(value:Float) {
|
||||
writeInt(FPHelper.floatToI32(value), 32);
|
||||
}
|
||||
|
||||
public function writeString(value:String) {
|
||||
writeUInt16(value.length);
|
||||
for (i in 0...value.length) {
|
||||
writeByte(StringTools.fastCodeAt(value, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/net/ClientConnection.hx
Normal file
138
src/net/ClientConnection.hx
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package net;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import datachannel.RTCPeerConnection;
|
||||
import datachannel.RTCDataChannel;
|
||||
import net.MoveManager;
|
||||
import src.TimeState;
|
||||
|
||||
enum abstract GameplayState(Int) from Int to Int {
|
||||
var UNKNOWN;
|
||||
var LOBBY;
|
||||
var GAME;
|
||||
}
|
||||
|
||||
enum abstract NetPlatform(Int) from Int to Int {
|
||||
var Unknown;
|
||||
var PC;
|
||||
var MacOS;
|
||||
var Web;
|
||||
var Android;
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
class ClientConnection extends GameConnection {
|
||||
var socket:RTCPeerConnection;
|
||||
var datachannel:RTCDataChannel;
|
||||
var datachannelUnreliable:RTCDataChannel;
|
||||
var rtt:Float;
|
||||
var pingSendTime:Float;
|
||||
var _rttRecords:Array<Float> = [];
|
||||
var lastRecvTime:Float;
|
||||
var didWarnTimeout:Bool = false;
|
||||
|
||||
public function new(id:Int, socket:RTCPeerConnection, datachannel:RTCDataChannel, datachannelUnreliable:RTCDataChannel) {
|
||||
super(id);
|
||||
this.socket = socket;
|
||||
this.datachannel = datachannel;
|
||||
this.datachannelUnreliable = datachannelUnreliable;
|
||||
this.state = GameplayState.LOBBY;
|
||||
this.rtt = 0;
|
||||
this.name = "Unknown";
|
||||
}
|
||||
|
||||
override function sendBytes(b:Bytes) {
|
||||
datachannel.sendBytes(b);
|
||||
}
|
||||
|
||||
override function sendBytesUnreliable(b:Bytes) {
|
||||
datachannelUnreliable.sendBytes(b);
|
||||
}
|
||||
|
||||
public inline function needsTimeoutWarn(t:Float) {
|
||||
return (t - lastRecvTime) > 10 && !didWarnTimeout;
|
||||
}
|
||||
|
||||
public inline function needsTimeoutKick(t:Float) {
|
||||
return (t - lastRecvTime) > 15 && didWarnTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
class DummyConnection extends GameConnection {
|
||||
public function new(id:Int) {
|
||||
super(id);
|
||||
this.state = GameplayState.GAME;
|
||||
this.lobbyReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
abstract class GameConnection {
|
||||
var id:Int;
|
||||
var state:GameplayState;
|
||||
var moveManager:MoveManager;
|
||||
var name:String;
|
||||
var lobbyReady:Bool;
|
||||
var platform:NetPlatform;
|
||||
var marbleId:Int;
|
||||
var isPrivate:Bool;
|
||||
|
||||
function new(id:Int) {
|
||||
this.id = id;
|
||||
this.moveManager = new MoveManager(this);
|
||||
this.lobbyReady = false;
|
||||
}
|
||||
|
||||
public function ready() {
|
||||
state = GameplayState.GAME;
|
||||
}
|
||||
|
||||
public function toggleLobbyReady() {
|
||||
lobbyReady = !lobbyReady;
|
||||
}
|
||||
|
||||
public function queueMove(m:NetMove) {
|
||||
moveManager.queueMove(m);
|
||||
}
|
||||
|
||||
public inline function acknowledgeMove(m:NetMove, timeState:TimeState) {
|
||||
return moveManager.acknowledgeMove(m, timeState);
|
||||
}
|
||||
|
||||
public inline function getQueuedMoves() {
|
||||
return @:privateAccess moveManager.queuedMoves;
|
||||
}
|
||||
|
||||
public inline function getQueuedMovesLength() {
|
||||
return moveManager.getQueueSize();
|
||||
}
|
||||
|
||||
public function recordMove(marble:src.Marble, motionDir:h3d.Vector, timeState:TimeState, serverTicks:Int) {
|
||||
return moveManager.recordMove(marble, motionDir, timeState, serverTicks);
|
||||
}
|
||||
|
||||
public function getNextMove() {
|
||||
return moveManager.getNextMove();
|
||||
}
|
||||
|
||||
public function sendBytes(b:haxe.io.Bytes) {}
|
||||
|
||||
public function sendBytesUnreliable(b:haxe.io.Bytes) {}
|
||||
|
||||
public inline function getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public inline function setName(value:String) {
|
||||
name = value;
|
||||
}
|
||||
|
||||
public inline function setMarbleId(value:Int) {
|
||||
marbleId = value;
|
||||
}
|
||||
|
||||
public inline function getMarbleId() {
|
||||
return marbleId;
|
||||
}
|
||||
}
|
||||
29
src/net/GemPredictionStore.hx
Normal file
29
src/net/GemPredictionStore.hx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package net;
|
||||
|
||||
import net.NetPacket.GemSpawnPacket;
|
||||
import net.NetPacket.GemPickupPacket;
|
||||
|
||||
class GemPredictionStore {
|
||||
var predictions:Array<Bool>;
|
||||
|
||||
public inline function new() {
|
||||
predictions = [];
|
||||
}
|
||||
|
||||
public inline function alloc() {
|
||||
predictions.push(true);
|
||||
}
|
||||
|
||||
public inline function getState(netIndex:Int) {
|
||||
return predictions[netIndex];
|
||||
}
|
||||
|
||||
public inline function acknowledgeGemPickup(packet:GemPickupPacket) {
|
||||
predictions[packet.gemId] = true;
|
||||
}
|
||||
|
||||
public inline function acknowledgeGemSpawn(packet:GemSpawnPacket) {
|
||||
for (gemId in packet.gemIds)
|
||||
predictions[gemId] = false;
|
||||
}
|
||||
}
|
||||
86
src/net/MarblePredictionStore.hx
Normal file
86
src/net/MarblePredictionStore.hx
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package net;
|
||||
|
||||
import net.NetPacket.MarbleUpdatePacket;
|
||||
import net.NetPacket.MarbleMovePacket;
|
||||
import src.TimeState;
|
||||
import src.Marble;
|
||||
import h3d.Vector;
|
||||
|
||||
@:publicFields
|
||||
class MarblePrediction {
|
||||
var tick:Int;
|
||||
var position:Vector;
|
||||
var velocity:Vector;
|
||||
var omega:Vector;
|
||||
var isControl:Bool;
|
||||
var blastAmount:Int;
|
||||
|
||||
public function new(marble:Marble, tick:Int) {
|
||||
this.tick = tick;
|
||||
position = @:privateAccess marble.newPos.clone();
|
||||
velocity = @:privateAccess marble.velocity.clone();
|
||||
omega = @:privateAccess marble.omega.clone();
|
||||
blastAmount = @:privateAccess marble.blastTicks;
|
||||
isControl = @:privateAccess marble.controllable;
|
||||
}
|
||||
|
||||
public inline function getError(p:MarbleUpdatePacket) {
|
||||
// Just doing position errors is enough to make it work
|
||||
var subs = position.sub(p.position).lengthSq(); // + velocity.sub(p.velocity).lengthSq() + omega.sub(p.omega).lengthSq();
|
||||
if (p.netFlags != 0)
|
||||
subs += 1;
|
||||
// if (p.powerUpId != powerupItemId)
|
||||
// if (tick % 10 == 0)
|
||||
// subs += 1; // temp
|
||||
// if (isControl)
|
||||
// subs += Math.abs(blastAmount - p.blastAmount);
|
||||
return subs;
|
||||
}
|
||||
}
|
||||
|
||||
class MarblePredictionStore {
|
||||
var predictions:Map<Marble, Array<MarblePrediction>>;
|
||||
|
||||
public function new() {
|
||||
predictions = [];
|
||||
}
|
||||
|
||||
public function storeState(marble:Marble, tick:Int) {
|
||||
var state = new MarblePrediction(marble, tick);
|
||||
if (predictions.exists(marble)) {
|
||||
var arr = predictions[marble];
|
||||
while (arr.length != 0 && arr[0].tick >= tick)
|
||||
arr.shift();
|
||||
arr.push(state);
|
||||
} else {
|
||||
predictions.set(marble, [state]);
|
||||
}
|
||||
}
|
||||
|
||||
public function retrieveState(marble:Marble, tick:Int) {
|
||||
if (predictions.exists(marble)) {
|
||||
var arr = predictions[marble];
|
||||
while (arr.length != 0 && arr[0].tick < tick)
|
||||
arr.shift();
|
||||
if (arr.length == 0)
|
||||
return null;
|
||||
var p = arr[0];
|
||||
if (p.tick == tick)
|
||||
return p;
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function clearStatesAfterTick(marble:Marble, tick:Int) {
|
||||
if (predictions.exists(marble)) {
|
||||
var arr = predictions[marble];
|
||||
while (arr.length != 0 && arr[arr.length - 1].tick >= tick)
|
||||
arr.pop();
|
||||
}
|
||||
}
|
||||
|
||||
public function removeMarbleFromPrediction(marble:Marble) {
|
||||
this.predictions.remove(marble);
|
||||
}
|
||||
}
|
||||
94
src/net/MarbleUpdateQueue.hx
Normal file
94
src/net/MarbleUpdateQueue.hx
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
package net;
|
||||
|
||||
import h3d.Vector;
|
||||
import net.NetPacket.MarbleNetFlags;
|
||||
import net.NetPacket.MarbleUpdatePacket;
|
||||
import net.Net;
|
||||
|
||||
@:publicFields
|
||||
class OtherMarbleUpdate {
|
||||
var packets:Array<MarbleUpdatePacket> = [];
|
||||
var lastBlastTick:Int;
|
||||
var lastHeliTick:Int;
|
||||
var lastMegaTick:Int;
|
||||
var lastPowerUpId:Int;
|
||||
var lastGravityUp:Vector;
|
||||
|
||||
public function new() {}
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
class MarbleUpdateQueue {
|
||||
var otherMarbleUpdates:Map<Int, OtherMarbleUpdate> = [];
|
||||
var myMarbleUpdate:MarbleUpdatePacket;
|
||||
var ourMoveApplied:Bool = false;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function enqueue(update:MarbleUpdatePacket) {
|
||||
var cc = update.clientId;
|
||||
if (cc != Net.clientId) {
|
||||
// if (myMarbleUpdate != null && update.serverTicks > myMarbleUpdate.serverTicks)
|
||||
// ourMoveApplied = true;
|
||||
if (otherMarbleUpdates.exists(cc)) {
|
||||
var otherUpdate = otherMarbleUpdates[cc];
|
||||
var ourList = otherUpdate.packets;
|
||||
// Copy the netflagg'd fields
|
||||
if (update.netFlags & MarbleNetFlags.DoBlast == 0)
|
||||
update.blastTick = otherUpdate.lastBlastTick;
|
||||
else
|
||||
otherUpdate.lastBlastTick = update.blastTick;
|
||||
if (update.netFlags & MarbleNetFlags.DoHelicopter == 0)
|
||||
update.heliTick = otherUpdate.lastHeliTick;
|
||||
else
|
||||
otherUpdate.lastHeliTick = update.heliTick;
|
||||
if (update.netFlags & MarbleNetFlags.DoMega == 0)
|
||||
update.megaTick = otherUpdate.lastMegaTick;
|
||||
else
|
||||
otherUpdate.lastMegaTick = update.megaTick;
|
||||
if (update.netFlags & MarbleNetFlags.PickupPowerup == 0)
|
||||
update.powerUpId = otherUpdate.lastPowerUpId;
|
||||
else
|
||||
otherUpdate.lastPowerUpId = update.powerUpId;
|
||||
if (update.netFlags & MarbleNetFlags.GravityChange == 0)
|
||||
update.gravityDirection = otherUpdate.lastGravityUp;
|
||||
else
|
||||
otherUpdate.lastGravityUp = update.gravityDirection;
|
||||
ourList.push(update);
|
||||
} else {
|
||||
var otherUpdate = new OtherMarbleUpdate();
|
||||
otherUpdate.packets.push(update);
|
||||
// Copy the netflagg'd fields
|
||||
if (update.netFlags & MarbleNetFlags.DoBlast != 0)
|
||||
otherUpdate.lastBlastTick = update.blastTick;
|
||||
if (update.netFlags & MarbleNetFlags.DoHelicopter != 0)
|
||||
otherUpdate.lastHeliTick = update.heliTick;
|
||||
if (update.netFlags & MarbleNetFlags.DoMega != 0)
|
||||
otherUpdate.lastMegaTick = update.megaTick;
|
||||
if (update.netFlags & MarbleNetFlags.PickupPowerup != 0)
|
||||
otherUpdate.lastPowerUpId = update.powerUpId;
|
||||
if (update.netFlags & MarbleNetFlags.GravityChange != 0)
|
||||
otherUpdate.lastGravityUp = update.gravityDirection;
|
||||
otherMarbleUpdates[cc] = otherUpdate;
|
||||
}
|
||||
} else {
|
||||
if (myMarbleUpdate == null || update.serverTicks > myMarbleUpdate.serverTicks) {
|
||||
if (myMarbleUpdate != null) {
|
||||
// Copy the netflagg'd fields
|
||||
if (update.netFlags & MarbleNetFlags.DoBlast == 0)
|
||||
update.blastTick = myMarbleUpdate.blastTick;
|
||||
if (update.netFlags & MarbleNetFlags.DoHelicopter == 0)
|
||||
update.heliTick = myMarbleUpdate.heliTick;
|
||||
if (update.netFlags & MarbleNetFlags.DoMega == 0)
|
||||
update.megaTick = myMarbleUpdate.megaTick;
|
||||
if (update.netFlags & MarbleNetFlags.PickupPowerup == 0)
|
||||
update.powerUpId = myMarbleUpdate.powerUpId;
|
||||
if (update.netFlags & MarbleNetFlags.GravityChange == 0)
|
||||
update.gravityDirection = myMarbleUpdate.gravityDirection;
|
||||
}
|
||||
myMarbleUpdate = update;
|
||||
ourMoveApplied = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
323
src/net/MasterServerClient.hx
Normal file
323
src/net/MasterServerClient.hx
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
package net;
|
||||
|
||||
import gui.MessageBoxOkDlg;
|
||||
import src.MarbleGame;
|
||||
import haxe.Json;
|
||||
import net.Net.ServerInfo;
|
||||
import haxe.net.WebSocket;
|
||||
import src.Console;
|
||||
|
||||
typedef RemoteServerInfo = {
|
||||
name:String,
|
||||
players:Int,
|
||||
maxPlayers:Int,
|
||||
platform:Int,
|
||||
version:String
|
||||
}
|
||||
|
||||
class MasterServerClient {
|
||||
#if js
|
||||
static var serverIp = "wss://mbomaster.randomityguy.me:8443";
|
||||
#else
|
||||
static var serverIp = "ws://89.58.58.191:8080";
|
||||
#end
|
||||
public static var instance:MasterServerClient;
|
||||
|
||||
var ws:WebSocket;
|
||||
var serverListCb:Array<RemoteServerInfo>->Void;
|
||||
|
||||
var open = false;
|
||||
|
||||
static var wsToken:Int = 0;
|
||||
|
||||
#if hl
|
||||
var wsThread:sys.thread.Thread;
|
||||
|
||||
static var responses:sys.thread.Deque<() -> Void> = new sys.thread.Deque<() -> Void>();
|
||||
|
||||
var toSend:sys.thread.Deque<String> = new sys.thread.Deque<String>();
|
||||
var stopping:Bool = false;
|
||||
var stopMutex:sys.thread.Mutex = new sys.thread.Mutex();
|
||||
#end
|
||||
|
||||
public function new(onOpenFunc:() -> Void, onErrorFunc:() -> Void) {
|
||||
#if hl
|
||||
wsThread = sys.thread.Thread.create(() -> {
|
||||
hl.Gc.enable(false);
|
||||
hl.Gc.blocking(true); // Wtf is this shit
|
||||
#end
|
||||
wsToken++;
|
||||
|
||||
var myToken = wsToken;
|
||||
|
||||
ws = WebSocket.create(serverIp);
|
||||
#if hl
|
||||
hl.Gc.enable(true);
|
||||
hl.Gc.blocking(false);
|
||||
#end
|
||||
ws.onopen = () -> {
|
||||
open = true;
|
||||
#if hl
|
||||
responses.add(() -> onOpenFunc());
|
||||
#end
|
||||
#if js
|
||||
onOpenFunc();
|
||||
#end
|
||||
}
|
||||
ws.onmessageString = (m) -> {
|
||||
#if hl
|
||||
responses.add(() -> handleMessage(m));
|
||||
#end
|
||||
#if js
|
||||
handleMessage(m);
|
||||
#end
|
||||
}
|
||||
ws.onerror = (m) -> {
|
||||
#if hl
|
||||
responses.add(() -> {
|
||||
MarbleGame.canvas.pushDialog(new MessageBoxOkDlg("Failed to connect to master server: " + m));
|
||||
});
|
||||
if (onErrorFunc != null)
|
||||
responses.add(() -> {
|
||||
onErrorFunc();
|
||||
});
|
||||
#end
|
||||
#if js
|
||||
MarbleGame.canvas.pushDialog(new MessageBoxOkDlg("Failed to connect to master server: " + m));
|
||||
if (onErrorFunc != null)
|
||||
onErrorFunc();
|
||||
#end
|
||||
#if hl
|
||||
stopMutex.acquire();
|
||||
#end
|
||||
if (myToken == wsToken) {
|
||||
open = false;
|
||||
ws = null;
|
||||
instance = null;
|
||||
}
|
||||
#if hl
|
||||
stopMutex.acquire();
|
||||
stopping = true;
|
||||
stopMutex.release();
|
||||
if (myToken == wsToken) {
|
||||
wsThread = null;
|
||||
}
|
||||
#end
|
||||
}
|
||||
ws.onclose = (?e) -> {
|
||||
#if hl
|
||||
stopMutex.acquire();
|
||||
#end
|
||||
if (myToken == wsToken) {
|
||||
open = false;
|
||||
ws = null;
|
||||
instance = null;
|
||||
}
|
||||
#if hl
|
||||
stopping = true;
|
||||
stopMutex.release();
|
||||
if (myToken == wsToken) {
|
||||
wsThread = null;
|
||||
}
|
||||
#end
|
||||
}
|
||||
#if hl
|
||||
while (true) {
|
||||
stopMutex.acquire();
|
||||
if (stopping)
|
||||
break;
|
||||
while (true) {
|
||||
var s = toSend.pop(false);
|
||||
if (s == null)
|
||||
break;
|
||||
#if hl
|
||||
hl.Gc.blocking(true);
|
||||
#end
|
||||
ws.sendString(s);
|
||||
#if hl
|
||||
hl.Gc.blocking(false);
|
||||
#end
|
||||
}
|
||||
|
||||
#if hl
|
||||
hl.Gc.blocking(true);
|
||||
#end
|
||||
ws.process();
|
||||
#if hl
|
||||
hl.Gc.blocking(false);
|
||||
#end
|
||||
stopMutex.release();
|
||||
Sys.sleep(0.1);
|
||||
}
|
||||
#end
|
||||
#if hl
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
public static function process() {
|
||||
#if sys
|
||||
var resp = responses.pop(false);
|
||||
if (resp != null) {
|
||||
resp();
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public static function connectToMasterServer(onConnect:() -> Void, onError:() -> Void = null) {
|
||||
if (instance == null)
|
||||
instance = new MasterServerClient(onConnect, onError);
|
||||
else {
|
||||
if (instance.open)
|
||||
onConnect();
|
||||
else {
|
||||
if (instance != null && instance.ws != null)
|
||||
instance.ws.close();
|
||||
instance = new MasterServerClient(onConnect, onError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function disconnectFromMasterServer() {
|
||||
if (instance != null && instance.ws != null) {
|
||||
instance.ws.close();
|
||||
if (instance != null) {
|
||||
instance.open = false;
|
||||
instance.ws = null;
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function queueMessage(m:String) {
|
||||
#if hl
|
||||
toSend.add(m);
|
||||
#end
|
||||
#if js
|
||||
ws.sendString(m);
|
||||
#end
|
||||
}
|
||||
|
||||
public function heartBeat() {
|
||||
queueMessage(Json.stringify({
|
||||
type: "heartbeat"
|
||||
}));
|
||||
}
|
||||
|
||||
public function sendServerInfo(serverInfo:ServerInfo) {
|
||||
queueMessage(Json.stringify({
|
||||
type: "serverInfo",
|
||||
name: serverInfo.name,
|
||||
players: serverInfo.players,
|
||||
maxPlayers: serverInfo.maxPlayers,
|
||||
privateSlots: serverInfo.privateSlots,
|
||||
privateServer: serverInfo.privateServer,
|
||||
inviteCode: serverInfo.inviteCode,
|
||||
state: serverInfo.state,
|
||||
platform: serverInfo.platform,
|
||||
version: "MBP" // MarbleGame.currentVersion
|
||||
}));
|
||||
}
|
||||
|
||||
public function sendConnectToServer(serverName:String, sdp:String, isInvite:Bool = false) {
|
||||
if (!isInvite) {
|
||||
queueMessage(Json.stringify({
|
||||
type: "connect",
|
||||
serverName: serverName,
|
||||
sdp: sdp
|
||||
}));
|
||||
} else {
|
||||
queueMessage(Json.stringify({
|
||||
type: "connectInvite",
|
||||
sdp: sdp,
|
||||
inviteCode: serverName
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public function getServerList(serverListCb:Array<RemoteServerInfo>->Void) {
|
||||
this.serverListCb = serverListCb;
|
||||
queueMessage(Json.stringify({
|
||||
type: "serverList"
|
||||
}));
|
||||
}
|
||||
|
||||
function handleMessage(message:String) {
|
||||
var conts = Json.parse(message);
|
||||
Console.log('Received ${conts.type}');
|
||||
if (conts.type == "serverList") {
|
||||
if (serverListCb != null) {
|
||||
serverListCb(conts.servers);
|
||||
}
|
||||
}
|
||||
if (conts.type == "connect") {
|
||||
if (!Net.isHost) {
|
||||
queueMessage(Json.stringify({
|
||||
type: "connectFailed",
|
||||
success: false,
|
||||
reason: "The server has shut down"
|
||||
}));
|
||||
return;
|
||||
}
|
||||
var joiningPrivate = conts.isPrivate;
|
||||
|
||||
if (Net.serverInfo.players >= Net.serverInfo.maxPlayers) {
|
||||
queueMessage(Json.stringify({
|
||||
type: "connectFailed",
|
||||
success: false,
|
||||
reason: "The server is full"
|
||||
}));
|
||||
return;
|
||||
}
|
||||
var pubSlotsAvail = Net.serverInfo.maxPlayers - Net.serverInfo.privateSlots;
|
||||
var privSlotsAvail = Net.serverInfo.privateSlots;
|
||||
|
||||
var pubCount = 1; // Self
|
||||
var privCount = 0;
|
||||
for (cid => cc in Net.clientIdMap) {
|
||||
if (cc.isPrivate) {
|
||||
privCount++;
|
||||
} else {
|
||||
pubCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!joiningPrivate && pubCount >= pubSlotsAvail) {
|
||||
queueMessage(Json.stringify({
|
||||
type: "connectFailed",
|
||||
success: false,
|
||||
reason: "The server is full"
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (joiningPrivate && privCount >= privSlotsAvail) {
|
||||
joiningPrivate = false; // Join publicly
|
||||
}
|
||||
|
||||
Net.addClientFromSdp(conts.sdp, joiningPrivate, (sdpReply) -> {
|
||||
queueMessage(Json.stringify({
|
||||
success: true,
|
||||
type: "connectResponse",
|
||||
sdp: sdpReply,
|
||||
clientId: conts.clientId
|
||||
}));
|
||||
});
|
||||
}
|
||||
if (conts.type == "connectResponse") {
|
||||
Console.log("Remote Description Received!");
|
||||
var sdpObj = Json.parse(conts.sdp);
|
||||
if (@:privateAccess Net.client != null)
|
||||
@:privateAccess Net.client.setRemoteDescription(sdpObj.sdp, sdpObj.type);
|
||||
}
|
||||
if (conts.type == "connectFailed") {
|
||||
// var loadGui:MultiplayerLoadingGui = cast MarbleGame.canvas.content;
|
||||
// if (loadGui != null) {
|
||||
// loadGui.setErrorStatus(conts.reason);
|
||||
// }
|
||||
}
|
||||
if (conts.type == "turnserver") {
|
||||
Net.turnServer = conts.server; // Turn server!
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/net/Move.hx
Normal file
12
src/net/Move.hx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package net;
|
||||
|
||||
import h3d.Vector;
|
||||
|
||||
class Move {
|
||||
public var d:Vector;
|
||||
public var jump:Bool;
|
||||
public var powerup:Bool;
|
||||
public var blast:Bool;
|
||||
|
||||
public function new() {}
|
||||
}
|
||||
294
src/net/MoveManager.hx
Normal file
294
src/net/MoveManager.hx
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
package net;
|
||||
|
||||
import net.BitStream.OutputBitStream;
|
||||
import net.BitStream.InputBitStream;
|
||||
import net.NetPacket.MarbleUpdatePacket;
|
||||
import shapes.PowerUp;
|
||||
import net.NetPacket.MarbleMovePacket;
|
||||
import src.TimeState;
|
||||
import src.Console;
|
||||
import net.ClientConnection;
|
||||
import net.Net.NetPacketType;
|
||||
import src.MarbleWorld;
|
||||
import net.Move;
|
||||
import h3d.Vector;
|
||||
import src.Gamepad;
|
||||
import src.Settings;
|
||||
import hxd.Key;
|
||||
import src.MarbleGame;
|
||||
import src.Util;
|
||||
import src.Marble;
|
||||
|
||||
@:publicFields
|
||||
class NetMove {
|
||||
var motionDir:Vector;
|
||||
var move:Move;
|
||||
var id:Int;
|
||||
var timeState:TimeState;
|
||||
var serverTicks:Int;
|
||||
|
||||
public function new(move:Move, motionDir:Vector, timeState:TimeState, serverTicks:Int, id:Int) {
|
||||
this.move = move;
|
||||
this.motionDir = motionDir;
|
||||
this.id = id;
|
||||
this.serverTicks = serverTicks;
|
||||
this.timeState = timeState;
|
||||
}
|
||||
}
|
||||
|
||||
class MoveManager {
|
||||
var connection:GameConnection;
|
||||
var queuedMoves:Array<NetMove>;
|
||||
var nextMoveId:Int;
|
||||
var lastMove:NetMove;
|
||||
var lastAckMoveId:Int = -1;
|
||||
var ackRTT:Int = -1;
|
||||
|
||||
var maxMoves = 45;
|
||||
var maxSendMoveListSize = 30;
|
||||
|
||||
var serverTargetMoveListSize = 3;
|
||||
var serverMaxMoveListSize = 8;
|
||||
var serverAvgMoveListSize = 3.0;
|
||||
var serverSmoothMoveAvg = 0.15;
|
||||
var serverMoveListSizeSlack = 1.5;
|
||||
var serverDefaultMinTargetMoveListSize = 3;
|
||||
var serverAbnormalMoveCount = 0;
|
||||
var serverLastRecvMove = 0;
|
||||
var serverLastAckMove = 0;
|
||||
|
||||
public var stall = false;
|
||||
|
||||
public function new(connection:GameConnection) {
|
||||
queuedMoves = [];
|
||||
nextMoveId = 0;
|
||||
this.connection = connection;
|
||||
var mv = new Move();
|
||||
mv.d = new Vector(0, 0);
|
||||
}
|
||||
|
||||
public function recordMove(marble:Marble, motionDir:Vector, timeState:TimeState, serverTicks:Int) {
|
||||
if (queuedMoves.length >= maxMoves || stall) {
|
||||
return queuedMoves[queuedMoves.length - 1];
|
||||
}
|
||||
var move = new Move();
|
||||
move.d = new Vector();
|
||||
if (!MarbleGame.instance.paused) {
|
||||
move.d.x = Gamepad.getAxis(Settings.gamepadSettings.moveYAxis);
|
||||
move.d.y = -Gamepad.getAxis(Settings.gamepadSettings.moveXAxis);
|
||||
// if (@:privateAccess !MarbleGame.instance.world.playGui.isChatFocused()) {
|
||||
if (Key.isDown(Settings.controlsSettings.forward)) {
|
||||
move.d.x -= 1;
|
||||
}
|
||||
if (Key.isDown(Settings.controlsSettings.backward)) {
|
||||
move.d.x += 1;
|
||||
}
|
||||
if (Key.isDown(Settings.controlsSettings.left)) {
|
||||
move.d.y += 1;
|
||||
}
|
||||
if (Key.isDown(Settings.controlsSettings.right)) {
|
||||
move.d.y -= 1;
|
||||
}
|
||||
if (Key.isDown(Settings.controlsSettings.jump)
|
||||
|| MarbleGame.instance.touchInput.jumpButton.pressed
|
||||
|| Gamepad.isDown(Settings.gamepadSettings.jump)) {
|
||||
move.jump = true;
|
||||
}
|
||||
if ((!Util.isTouchDevice() && Key.isDown(Settings.controlsSettings.powerup))
|
||||
|| (Util.isTouchDevice() && MarbleGame.instance.touchInput.powerupButton.pressed)
|
||||
|| Gamepad.isDown(Settings.gamepadSettings.powerup)) {
|
||||
move.powerup = true;
|
||||
}
|
||||
|
||||
if (Key.isDown(Settings.controlsSettings.blast)
|
||||
|| (MarbleGame.instance.touchInput.blastbutton.pressed)
|
||||
|| Gamepad.isDown(Settings.gamepadSettings.blast))
|
||||
move.blast = true;
|
||||
|
||||
if (MarbleGame.instance.touchInput.movementInput.pressed) {
|
||||
move.d.y = -MarbleGame.instance.touchInput.movementInput.value.x;
|
||||
move.d.x = MarbleGame.instance.touchInput.movementInput.value.y;
|
||||
}
|
||||
// }
|
||||
|
||||
// quantize moves for client
|
||||
var qx = Std.int((move.d.x * 16) + 16);
|
||||
var qy = Std.int((move.d.y * 16) + 16);
|
||||
move.d.x = (qx - 16) / 16.0;
|
||||
move.d.y = (qy - 16) / 16.0;
|
||||
}
|
||||
|
||||
var netMove = new NetMove(move, motionDir, timeState.clone(), serverTicks, nextMoveId++);
|
||||
queuedMoves.push(netMove);
|
||||
|
||||
if (nextMoveId >= 65535) // 65535 is reserved for null move
|
||||
nextMoveId = 0;
|
||||
|
||||
var moveStartIdx = queuedMoves.length - maxSendMoveListSize;
|
||||
if (moveStartIdx < 0)
|
||||
moveStartIdx = 0;
|
||||
|
||||
var b = new OutputBitStream();
|
||||
var movePacket = new MarbleMovePacket();
|
||||
movePacket.clientId = Net.clientId;
|
||||
movePacket.moves = queuedMoves.slice(moveStartIdx);
|
||||
movePacket.clientTicks = timeState.ticks;
|
||||
b.writeByte(NetPacketType.MarbleMove);
|
||||
movePacket.serialize(b);
|
||||
|
||||
Net.sendPacketToHostUnreliable(b);
|
||||
|
||||
return netMove;
|
||||
}
|
||||
|
||||
function copyMove(to:Int, from:Int) {
|
||||
queuedMoves[to].move = queuedMoves[from].move;
|
||||
queuedMoves[to].motionDir.load(queuedMoves[from].motionDir);
|
||||
}
|
||||
|
||||
public inline function duplicateLastMove() {
|
||||
if (queuedMoves.length == 0)
|
||||
return;
|
||||
queuedMoves.insert(0, queuedMoves[0]);
|
||||
}
|
||||
|
||||
public static inline function packMove(m:NetMove, b:OutputBitStream) {
|
||||
b.writeUInt16(m.id);
|
||||
b.writeByte(Std.int((m.move.d.x * 16) + 16));
|
||||
b.writeByte(Std.int((m.move.d.y * 16) + 16));
|
||||
b.writeFlag(m.move.jump);
|
||||
b.writeFlag(m.move.powerup);
|
||||
b.writeFlag(m.move.blast);
|
||||
b.writeFloat(m.motionDir.x);
|
||||
b.writeFloat(m.motionDir.y);
|
||||
b.writeFloat(m.motionDir.z);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static inline function unpackMove(b:InputBitStream) {
|
||||
var moveId = b.readUInt16();
|
||||
var move = new Move();
|
||||
move.d = new Vector();
|
||||
move.d.x = (b.readByte() - 16) / 16.0;
|
||||
move.d.y = (b.readByte() - 16) / 16.0;
|
||||
move.jump = b.readFlag();
|
||||
move.powerup = b.readFlag();
|
||||
move.blast = b.readFlag();
|
||||
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(), 0, moveId);
|
||||
return netMove;
|
||||
}
|
||||
|
||||
public inline function queueMove(m:NetMove) {
|
||||
if (serverLastRecvMove < m.id && serverLastAckMove < m.id) {
|
||||
queuedMoves.push(m);
|
||||
serverLastRecvMove = m.id;
|
||||
}
|
||||
// if (queuedMoves.length != 0) {
|
||||
// var lastQueuedMove = queuedMoves[queuedMoves.length - 1];
|
||||
// if (lastQueuedMove.id < m.id)
|
||||
// queuedMoves.push(m);
|
||||
// } else if (lastMove == null || lastMove.id < m.id) {
|
||||
// queuedMoves.push(m);
|
||||
// }
|
||||
}
|
||||
|
||||
public function getNextMove() {
|
||||
if (Net.isHost) {
|
||||
serverAvgMoveListSize *= (1 - serverSmoothMoveAvg);
|
||||
serverAvgMoveListSize += serverSmoothMoveAvg * queuedMoves.length;
|
||||
if (serverAvgMoveListSize < serverTargetMoveListSize - serverMoveListSizeSlack
|
||||
&& queuedMoves.length < serverTargetMoveListSize
|
||||
&& queuedMoves.length != 0) {
|
||||
serverAvgMoveListSize = Math.max(Std.int(serverAvgMoveListSize + serverMoveListSizeSlack + 0.5), queuedMoves.length);
|
||||
// serverAbnormalMoveCount++;
|
||||
// if (serverAbnormalMoveCount > 3) {
|
||||
// serverTargetMoveListSize += 1;
|
||||
// if (serverTargetMoveListSize > serverMaxMoveListSize)
|
||||
// serverTargetMoveListSize = serverMaxMoveListSize;
|
||||
// }
|
||||
// Send null move
|
||||
return null;
|
||||
}
|
||||
if (queuedMoves.length > serverMaxMoveListSize
|
||||
|| (serverAvgMoveListSize > serverTargetMoveListSize + serverMoveListSizeSlack
|
||||
&& queuedMoves.length > serverTargetMoveListSize)) {
|
||||
// if (queuedMoves.length > serverMaxMoveListSize) {
|
||||
var dropAmt = queuedMoves.length - serverTargetMoveListSize;
|
||||
while (dropAmt-- > 0) {
|
||||
queuedMoves.pop();
|
||||
}
|
||||
// }
|
||||
serverAvgMoveListSize = serverTargetMoveListSize;
|
||||
// serverAbnormalMoveCount++;
|
||||
// if (serverAbnormalMoveCount > 3) {
|
||||
// serverTargetMoveListSize -= 1;
|
||||
// if (serverTargetMoveListSize < serverDefaultMinTargetMoveListSize)
|
||||
// serverTargetMoveListSize = serverDefaultMinTargetMoveListSize;
|
||||
// } else {
|
||||
// serverAbnormalMoveCount = 0;
|
||||
// }
|
||||
}
|
||||
}
|
||||
if (queuedMoves.length == 0) {
|
||||
// if (lastMove != null) {
|
||||
// lastMove.id++; // So that we force client's move to be overriden by this one
|
||||
// }
|
||||
return lastMove;
|
||||
} else {
|
||||
lastMove = queuedMoves[0];
|
||||
queuedMoves.shift();
|
||||
lastAckMoveId = lastMove.id;
|
||||
return lastMove;
|
||||
}
|
||||
}
|
||||
|
||||
public inline function getQueueSize() {
|
||||
return queuedMoves.length;
|
||||
}
|
||||
|
||||
public function acknowledgeMove(m:NetMove, timeState:TimeState) {
|
||||
if (m.id == 65535 || m.id == -1) {
|
||||
return null;
|
||||
}
|
||||
if (m.id <= lastAckMoveId)
|
||||
return null; // Already acked
|
||||
if (queuedMoves.length == 0)
|
||||
return null;
|
||||
if (m.id >= nextMoveId) {
|
||||
return queuedMoves[0]; // Input lag
|
||||
}
|
||||
while (m.id != queuedMoves[0].id) {
|
||||
queuedMoves.shift();
|
||||
}
|
||||
var delta = -1;
|
||||
var mv = null;
|
||||
if (m.id == queuedMoves[0].id) {
|
||||
delta = queuedMoves[0].id - lastAckMoveId;
|
||||
mv = queuedMoves.shift();
|
||||
ackRTT = timeState.ticks - mv.timeState.ticks;
|
||||
// maxMoves = ackRTT + 2;
|
||||
}
|
||||
lastAckMoveId = m.id;
|
||||
return mv;
|
||||
}
|
||||
|
||||
public function getMoveForTick(m:Int) {
|
||||
if (m <= lastAckMoveId)
|
||||
return null;
|
||||
if (m == 65535 || m == -1)
|
||||
return null;
|
||||
if (queuedMoves.length == 0)
|
||||
return null;
|
||||
for (i in 0...queuedMoves.length) {
|
||||
if (queuedMoves[i].id == m)
|
||||
return queuedMoves[i];
|
||||
if (queuedMoves[i].id > m)
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
836
src/net/Net.hx
Normal file
836
src/net/Net.hx
Normal file
|
|
@ -0,0 +1,836 @@
|
|||
package net;
|
||||
|
||||
import net.NetPacket.ScoreboardPacket;
|
||||
import gui.MPPlayMissionGui;
|
||||
import gui.Canvas;
|
||||
import net.MasterServerClient.RemoteServerInfo;
|
||||
import src.ResourceLoader;
|
||||
import src.AudioManager;
|
||||
import net.NetPacket.GemPickupPacket;
|
||||
import net.NetPacket.GemSpawnPacket;
|
||||
import net.BitStream.InputBitStream;
|
||||
import net.BitStream.OutputBitStream;
|
||||
import net.NetPacket.PowerupPickupPacket;
|
||||
import net.ClientConnection;
|
||||
import net.NetPacket.MarbleUpdatePacket;
|
||||
import net.NetPacket.MarbleMovePacket;
|
||||
import haxe.Json;
|
||||
import datachannel.RTCPeerConnection;
|
||||
import datachannel.RTCDataChannel;
|
||||
import src.Console;
|
||||
import net.NetCommands;
|
||||
import src.MarbleGame;
|
||||
import src.Settings;
|
||||
|
||||
enum abstract NetPacketType(Int) from Int to Int {
|
||||
var NullPacket;
|
||||
var ClientIdAssign;
|
||||
var NetCommand;
|
||||
var Ping;
|
||||
var PingBack;
|
||||
var MarbleUpdate;
|
||||
var MarbleMove;
|
||||
var PowerupPickup;
|
||||
var GemSpawn;
|
||||
var GemPickup;
|
||||
var PlayerInfo;
|
||||
var ScoreBoardInfo;
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
class ServerInfo {
|
||||
var name:String;
|
||||
var players:Int;
|
||||
var maxPlayers:Int;
|
||||
var privateSlots:Int;
|
||||
var privateServer:Bool;
|
||||
var inviteCode:Int;
|
||||
var state:String;
|
||||
var platform:NetPlatform;
|
||||
|
||||
public function new(name:String, players:Int, maxPlayers:Int, privateSlots:Int, privateServer:Bool, inviteCode:Int, state:String, platform:NetPlatform) {
|
||||
this.name = name;
|
||||
this.players = players;
|
||||
this.maxPlayers = maxPlayers;
|
||||
this.privateSlots = privateSlots;
|
||||
this.privateServer = privateServer;
|
||||
this.inviteCode = inviteCode;
|
||||
this.state = state;
|
||||
this.platform = platform;
|
||||
}
|
||||
}
|
||||
|
||||
class Net {
|
||||
static var client:RTCPeerConnection;
|
||||
static var clientDatachannel:RTCDataChannel;
|
||||
static var clientDatachannelUnreliable:RTCDataChannel;
|
||||
|
||||
public static var isMP:Bool;
|
||||
public static var isHost:Bool;
|
||||
public static var isClient:Bool;
|
||||
|
||||
public static var lobbyHostReady:Bool;
|
||||
public static var lobbyClientReady:Bool;
|
||||
public static var hostReady:Bool;
|
||||
|
||||
static var clientIdAllocs:Int = 1;
|
||||
public static var clientId:Int;
|
||||
public static var networkRNG:Float;
|
||||
public static var clients:Map<RTCPeerConnection, GameConnection> = [];
|
||||
public static var clientIdMap:Map<Int, GameConnection> = [];
|
||||
public static var clientConnection:ClientConnection;
|
||||
public static var serverInfo:ServerInfo;
|
||||
public static var remoteServerInfo:RemoteServerInfo;
|
||||
|
||||
static var stunServers = ["stun:stun.l.google.com:19302"];
|
||||
|
||||
public static var turnServer:String = "";
|
||||
|
||||
public static function hostServer(name:String, maxPlayers:Int, privateSlots:Int, privateServer:Bool, onHosted:() -> Void) {
|
||||
serverInfo = new ServerInfo(name, 1, maxPlayers, privateSlots, privateServer, Std.int(999999 * Math.random()), "LOBBY", getPlatform());
|
||||
MasterServerClient.connectToMasterServer(() -> {
|
||||
isHost = true;
|
||||
isClient = false;
|
||||
clientId = 0;
|
||||
isMP = true;
|
||||
MasterServerClient.instance.sendServerInfo(serverInfo);
|
||||
onHosted();
|
||||
});
|
||||
}
|
||||
|
||||
public static function addClientFromSdp(sdpString:String, privateJoin:Bool, onFinishSdp:String->Void) {
|
||||
var peer = new RTCPeerConnection(stunServers, "0.0.0.0");
|
||||
var sdpObj = Json.parse(sdpString);
|
||||
peer.setRemoteDescription(sdpObj.sdp, sdpObj.type);
|
||||
addClient(peer, privateJoin, onFinishSdp);
|
||||
}
|
||||
|
||||
static function addClient(peer:RTCPeerConnection, privateJoin:Bool, onFinishSdp:String->Void) {
|
||||
var candidates = [];
|
||||
peer.onLocalCandidate = (c) -> {
|
||||
Console.log('Local candidate: ' + c);
|
||||
if (c != "")
|
||||
candidates.push('a=${c}');
|
||||
}
|
||||
peer.onStateChange = (s) -> {
|
||||
switch (s) {
|
||||
case RTC_CLOSED:
|
||||
Console.log("RTC State change: Connection closed!");
|
||||
case RTC_CONNECTED:
|
||||
Console.log("RTC State change: Connected!");
|
||||
case RTC_CONNECTING:
|
||||
Console.log("RTC State change: Connecting...");
|
||||
case RTC_DISCONNECTED:
|
||||
Console.log("RTC State change: Disconnected!");
|
||||
case RTC_FAILED:
|
||||
Console.log("RTC State change: Failed!");
|
||||
case RTC_NEW:
|
||||
Console.log("RTC State change: New...");
|
||||
}
|
||||
}
|
||||
|
||||
var sdpFinished = false;
|
||||
|
||||
var finishSdp = () -> {
|
||||
if (sdpFinished)
|
||||
return;
|
||||
if (peer == null)
|
||||
return;
|
||||
sdpFinished = true;
|
||||
var sdpObj = StringTools.trim(peer.localDescription);
|
||||
sdpObj = sdpObj + '\r\n' + candidates.join('\r\n') + '\r\n';
|
||||
onFinishSdp(Json.stringify({
|
||||
sdp: sdpObj,
|
||||
type: "answer"
|
||||
}));
|
||||
}
|
||||
|
||||
peer.onGatheringStateChange = (s) -> {
|
||||
switch (s) {
|
||||
case RTC_GATHERING_COMPLETE:
|
||||
Console.log("Gathering complete!");
|
||||
case RTC_GATHERING_INPROGRESS:
|
||||
Console.log("Gathering in progress...");
|
||||
case RTC_GATHERING_NEW:
|
||||
Console.log("Gathering new...");
|
||||
}
|
||||
if (s == RTC_GATHERING_COMPLETE) {
|
||||
finishSdp();
|
||||
}
|
||||
}
|
||||
var reliable:datachannel.RTCDataChannel = null;
|
||||
var unreliable:datachannel.RTCDataChannel = null;
|
||||
peer.onDataChannel = (dc:datachannel.RTCDataChannel) -> {
|
||||
if (dc.name == "mp")
|
||||
reliable = dc;
|
||||
if (dc.name == "unreliable") {
|
||||
unreliable = dc;
|
||||
switch (dc.reliability) {
|
||||
case Reliable:
|
||||
Console.log("Error opening unreliable datachannel!");
|
||||
case Unreliable(maxRetransmits, maxLifetime):
|
||||
Console.log("Opened unreliable datachannel: " + maxRetransmits + " " + maxLifetime);
|
||||
}
|
||||
}
|
||||
if (reliable != null && unreliable != null)
|
||||
onClientConnect(peer, reliable, unreliable, privateJoin);
|
||||
}
|
||||
}
|
||||
|
||||
static function addGhost(id:Int) {
|
||||
var ghost = new DummyConnection(id);
|
||||
clientIdMap[id] = ghost;
|
||||
}
|
||||
|
||||
public static function joinServer(serverName:String, isInvite:Bool, connectedCb:() -> Void) {
|
||||
MasterServerClient.connectToMasterServer(() -> {
|
||||
client = new RTCPeerConnection(stunServers, "0.0.0.0");
|
||||
var candidates = [];
|
||||
|
||||
var closing = false;
|
||||
|
||||
isMP = true;
|
||||
isHost = false;
|
||||
isClient = true;
|
||||
|
||||
var closeFunc = (msg:String, forceShow:Bool) -> {
|
||||
if (closing)
|
||||
return;
|
||||
closing = true;
|
||||
var weLeftOurselves = !Net.isClient; // If we left ourselves, this would be set to false due to order of ops, disconnect being called first, and then the datachannel closing
|
||||
disconnect();
|
||||
if (MarbleGame.instance.world != null) {
|
||||
MarbleGame.instance.quitMission();
|
||||
}
|
||||
if (!weLeftOurselves || forceShow) {
|
||||
// if (!(MarbleGame.canvas.content is MultiplayerLoadingGui)) {
|
||||
// var loadGui = new MultiplayerLoadingGui(msg);
|
||||
// MarbleGame.canvas.setContent(loadGui);
|
||||
// loadGui.setErrorStatus(msg);
|
||||
// } else {
|
||||
// var loadGui = cast(MarbleGame.canvas.content, MultiplayerLoadingGui);
|
||||
// loadGui.setErrorStatus(msg);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
client.onLocalCandidate = (c) -> {
|
||||
Console.log('Local candidate: ' + c);
|
||||
if (c != "")
|
||||
candidates.push('a=${c}');
|
||||
}
|
||||
client.onStateChange = (s) -> {
|
||||
switch (s) {
|
||||
case RTC_CLOSED:
|
||||
Console.log("RTC State change: Connection closed!");
|
||||
closeFunc("Connection closed", true);
|
||||
case RTC_CONNECTED:
|
||||
Console.log("RTC State change: Connected!");
|
||||
case RTC_CONNECTING:
|
||||
Console.log("RTC State change: Connecting...");
|
||||
case RTC_DISCONNECTED:
|
||||
Console.log("RTC State change: Disconnected!");
|
||||
case RTC_FAILED:
|
||||
Console.log("RTC State change: Failed!");
|
||||
case RTC_NEW:
|
||||
Console.log("RTC State change: New...");
|
||||
}
|
||||
}
|
||||
|
||||
var sdpFinished = false;
|
||||
var finishSdp = () -> {
|
||||
if (sdpFinished)
|
||||
return;
|
||||
sdpFinished = true;
|
||||
if (client == null)
|
||||
return;
|
||||
Console.log("Local Description Set!");
|
||||
var sdpObj = StringTools.trim(client.localDescription);
|
||||
sdpObj = sdpObj + '\r\n' + candidates.join('\r\n') + '\r\n';
|
||||
MasterServerClient.instance.sendConnectToServer(serverName, Json.stringify({
|
||||
sdp: sdpObj,
|
||||
type: "offer"
|
||||
}), isInvite);
|
||||
}
|
||||
|
||||
client.onGatheringStateChange = (s) -> {
|
||||
switch (s) {
|
||||
case RTC_GATHERING_COMPLETE:
|
||||
Console.log("Gathering complete!");
|
||||
case RTC_GATHERING_INPROGRESS:
|
||||
Console.log("Gathering in progress...");
|
||||
case RTC_GATHERING_NEW:
|
||||
Console.log("Gathering new...");
|
||||
}
|
||||
if (s == RTC_GATHERING_COMPLETE) {
|
||||
finishSdp();
|
||||
}
|
||||
}
|
||||
|
||||
// haxe.Timer.delay(() -> {
|
||||
// finishSdp();
|
||||
// }, 5000);
|
||||
|
||||
clientDatachannel = client.createDatachannel("mp");
|
||||
clientDatachannelUnreliable = client.createDatachannelWithOptions("unreliable", true, null, 600);
|
||||
|
||||
var openFlags = 0;
|
||||
|
||||
var onDatachannelOpen = (idx:Int) -> {
|
||||
if (!Net.isMP) {
|
||||
// Close
|
||||
client.close();
|
||||
return;
|
||||
}
|
||||
openFlags |= idx;
|
||||
if (openFlags == 3) {
|
||||
// if (MarbleGame.canvas.content is MultiplayerLoadingGui) {
|
||||
// var loadGui:MultiplayerLoadingGui = cast MarbleGame.canvas.content;
|
||||
// if (loadGui != null) {
|
||||
// loadGui.setLoadingStatus("Handshaking");
|
||||
// }
|
||||
// }
|
||||
Console.log("Successfully connected!");
|
||||
clients.set(client, new ClientConnection(0, client, clientDatachannel, clientDatachannelUnreliable)); // host is always 0
|
||||
clientIdMap[0] = clients[client];
|
||||
clientConnection = cast clients[client];
|
||||
onConnectedToServer();
|
||||
haxe.Timer.delay(() -> connectedCb(), 1500); // 1.5 second delay to do the RTT calculation
|
||||
}
|
||||
}
|
||||
var onDatachannelMessage = (dc:RTCDataChannel, b:haxe.io.Bytes) -> {
|
||||
onPacketReceived(clientConnection, client, clientDatachannel, new InputBitStream(b));
|
||||
}
|
||||
|
||||
var onDatachannelClose = (dc:RTCDataChannel) -> {
|
||||
closeFunc("Disconnected", true);
|
||||
}
|
||||
|
||||
var onDatachannelError = (msg:String) -> {
|
||||
Console.log('Errored out due to ${msg}');
|
||||
closeFunc("Connection error", true);
|
||||
}
|
||||
|
||||
clientDatachannel.onOpen = (n) -> {
|
||||
onDatachannelOpen(1);
|
||||
}
|
||||
clientDatachannel.onMessage = (b) -> {
|
||||
onDatachannelMessage(clientDatachannel, b);
|
||||
}
|
||||
clientDatachannel.onClosed = () -> {
|
||||
onDatachannelClose(clientDatachannel);
|
||||
}
|
||||
clientDatachannel.onError = (msg) -> {
|
||||
onDatachannelError(msg);
|
||||
}
|
||||
clientDatachannelUnreliable.onOpen = (n) -> {
|
||||
onDatachannelOpen(2);
|
||||
}
|
||||
clientDatachannelUnreliable.onMessage = (b) -> {
|
||||
onDatachannelMessage(clientDatachannelUnreliable, b);
|
||||
}
|
||||
clientDatachannelUnreliable.onClosed = () -> {
|
||||
onDatachannelClose(clientDatachannelUnreliable);
|
||||
}
|
||||
clientDatachannelUnreliable.onError = (msg) -> {
|
||||
onDatachannelError(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function disconnect() {
|
||||
if (Net.isClient) {
|
||||
NetCommands.clientLeave(Net.clientId);
|
||||
Net.isMP = false;
|
||||
Net.isClient = false;
|
||||
Net.isHost = false;
|
||||
if (Net.client != null)
|
||||
Net.client.close();
|
||||
Net.client = null;
|
||||
Net.clientDatachannel = null;
|
||||
Net.clientId = 0;
|
||||
Net.clientIdAllocs = 1;
|
||||
Net.clients.clear();
|
||||
Net.clientIdMap.clear();
|
||||
Net.clientConnection = null;
|
||||
Net.serverInfo = null;
|
||||
Net.remoteServerInfo = null;
|
||||
Net.lobbyHostReady = false;
|
||||
Net.lobbyClientReady = false;
|
||||
Net.hostReady = false;
|
||||
// MultiplayerLevelSelectGui.custSelected = false;
|
||||
}
|
||||
if (Net.isHost) {
|
||||
NetCommands.serverClosed();
|
||||
for (client => gc in clients) {
|
||||
client.close();
|
||||
}
|
||||
Net.isMP = false;
|
||||
Net.isClient = false;
|
||||
Net.isHost = false;
|
||||
Net.clientId = 0;
|
||||
Net.clientIdAllocs = 1;
|
||||
Net.clients.clear();
|
||||
Net.clientIdMap.clear();
|
||||
MasterServerClient.disconnectFromMasterServer();
|
||||
Net.serverInfo = null;
|
||||
Net.remoteServerInfo = null;
|
||||
Net.lobbyHostReady = false;
|
||||
Net.lobbyClientReady = false;
|
||||
Net.hostReady = false;
|
||||
// MultiplayerLevelSelectGui.custSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function checkPacketTimeout(dt:Float) {
|
||||
if (!Net.isMP)
|
||||
return;
|
||||
static var accum = 0.0;
|
||||
static var wsAccum = 0.0;
|
||||
accum += dt;
|
||||
wsAccum += dt;
|
||||
if (accum > 1.0) {
|
||||
accum = 0;
|
||||
var t = Console.time();
|
||||
for (dc => cc in clients) {
|
||||
if (cc is ClientConnection) {
|
||||
var conn = cast(cc, ClientConnection);
|
||||
if (conn.needsTimeoutWarn(t)) {
|
||||
conn.didWarnTimeout = true;
|
||||
if (Net.isClient) {
|
||||
NetCommands.requestPing();
|
||||
}
|
||||
if (Net.isHost) {
|
||||
NetCommands.pingClient(cc, t);
|
||||
}
|
||||
}
|
||||
if (conn.needsTimeoutKick(t)) {
|
||||
if (Net.isHost) {
|
||||
dc.close();
|
||||
}
|
||||
if (Net.isClient) {
|
||||
disconnect();
|
||||
if (MarbleGame.instance.world != null) {
|
||||
MarbleGame.instance.quitMission();
|
||||
}
|
||||
// if (!(MarbleGame.canvas.content is MultiplayerLoadingGui)) {
|
||||
// var loadGui = new MultiplayerLoadingGui("Timed out");
|
||||
// MarbleGame.canvas.setContent(loadGui);
|
||||
// loadGui.setErrorStatus("Timed out");
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wsAccum >= 15.0) {
|
||||
wsAccum = 0;
|
||||
if (Net.isHost) {
|
||||
if (MasterServerClient.instance != null)
|
||||
MasterServerClient.instance.sendServerInfo(serverInfo); // Heartbeat
|
||||
else
|
||||
MasterServerClient.connectToMasterServer(() -> {
|
||||
MasterServerClient.instance.sendServerInfo(serverInfo); // Heartbeat
|
||||
});
|
||||
}
|
||||
if (Net.isClient) {
|
||||
if (MasterServerClient.instance != null)
|
||||
MasterServerClient.instance.heartBeat();
|
||||
else
|
||||
MasterServerClient.connectToMasterServer(() -> {
|
||||
MasterServerClient.instance.heartBeat();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function onClientConnect(c:RTCPeerConnection, dc:RTCDataChannel, dcu:RTCDataChannel, joiningPrivate:Bool) {
|
||||
if (!Net.isMP) {
|
||||
c.close();
|
||||
return;
|
||||
}
|
||||
var clientId = allocateClientId();
|
||||
if (clientId == -1) {
|
||||
c.close();
|
||||
return; // Failed to allocate ID
|
||||
}
|
||||
var cc = new ClientConnection(clientId, c, dc, dcu);
|
||||
clients.set(c, cc);
|
||||
cc.isPrivate = joiningPrivate;
|
||||
clientIdMap[clientId] = clients[c];
|
||||
cc.lastRecvTime = Console.time(); // So it doesnt get timed out
|
||||
|
||||
var closing = false;
|
||||
|
||||
var onMessage = (dc:RTCDataChannel, msgBytes:haxe.io.Bytes) -> {
|
||||
onPacketReceived(cc, c, dc, new InputBitStream(msgBytes));
|
||||
}
|
||||
var onClosed = () -> {
|
||||
if (closing)
|
||||
return;
|
||||
closing = true;
|
||||
clients.remove(c);
|
||||
onClientLeave(cc);
|
||||
}
|
||||
|
||||
var onError = (msg:String) -> {
|
||||
if (closing)
|
||||
return;
|
||||
closing = true;
|
||||
clients.remove(c);
|
||||
Console.log('Client ${cc.id} errored out due to: ${msg}');
|
||||
onClientLeave(cc);
|
||||
}
|
||||
|
||||
dc.onMessage = (msgBytes) -> {
|
||||
onMessage(dc, msgBytes);
|
||||
}
|
||||
dc.onClosed = () -> {
|
||||
onClosed();
|
||||
}
|
||||
dc.onError = (msg) -> {
|
||||
onError(msg);
|
||||
}
|
||||
|
||||
dcu.onMessage = (msgBytes) -> {
|
||||
onMessage(dcu, msgBytes);
|
||||
}
|
||||
dcu.onClosed = () -> {
|
||||
onClosed();
|
||||
}
|
||||
dcu.onError = (msg) -> {
|
||||
onError(msg);
|
||||
}
|
||||
|
||||
var b = haxe.io.Bytes.alloc(2);
|
||||
b.set(0, ClientIdAssign);
|
||||
b.set(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
|
||||
cast(clients[c], ClientConnection).pingSendTime = Console.time();
|
||||
dc.sendBytes(b);
|
||||
Console.log("Sending ping packet!");
|
||||
|
||||
// AudioManager.playSound(ResourceLoader.getAudio("data/sound/spawn_alternate.wav").resource);
|
||||
|
||||
serverInfo.players = 1;
|
||||
for (k => v in clients) { // Recount
|
||||
serverInfo.players++;
|
||||
}
|
||||
|
||||
serverInfo.players++;
|
||||
MasterServerClient.instance.sendServerInfo(serverInfo); // notify the server of the new player
|
||||
|
||||
if (MarbleGame.canvas.content is MPPlayMissionGui) {
|
||||
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
cast(clients[client], ClientConnection).pingSendTime = Console.time();
|
||||
clientDatachannel.sendBytes(b);
|
||||
Console.log("Sending ping packet!");
|
||||
}
|
||||
|
||||
static function onClientLeave(cc:ClientConnection) {
|
||||
if (!Net.isMP || cc == null)
|
||||
return;
|
||||
NetCommands.clientDisconnected(cc.id);
|
||||
|
||||
if (cc.id != 0) {
|
||||
freeClientId(cc.id);
|
||||
}
|
||||
|
||||
serverInfo.players = 1;
|
||||
for (k => v in clients) { // Recount
|
||||
serverInfo.players++;
|
||||
}
|
||||
|
||||
MasterServerClient.instance.sendServerInfo(serverInfo); // notify the server of the player leave
|
||||
|
||||
// AudioManager.playSound(ResourceLoader.getAudio("data/sound/infotutorial.wav").resource);
|
||||
|
||||
if (MarbleGame.canvas.content is MPPlayMissionGui) {
|
||||
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
|
||||
}
|
||||
}
|
||||
|
||||
static function onClientHandshakeComplete(conn:ClientConnection) {
|
||||
// Send our current mission to connecting client
|
||||
// if (MultiplayerLevelSelectGui.custSelected) {
|
||||
// NetCommands.setLobbyCustLevelNameClient(conn, MultiplayerLevelSelectGui.custPath);
|
||||
// } else {
|
||||
NetCommands.setLobbyLevelIndexClient(conn, MPPlayMissionGui.currentCategoryStatic, MPPlayMissionGui.currentSelectionStatic);
|
||||
// }
|
||||
|
||||
if (serverInfo.state == "PLAYING") { // We initiated the game, directly add in the marble
|
||||
// if (MultiplayerLevelSelectGui.custSelected) {
|
||||
// NetCommands.playCustomLevelMidJoinClient(conn, MultiplayerLevelSelectGui.custPath);
|
||||
// } else
|
||||
// NetCommands.playLevelMidJoinClient(conn, MPPlayMissionGui.currentCategoryStatic, MPPlayMissionGui.currentSelectionStatic);
|
||||
MarbleGame.instance.world.addJoiningClient(conn, () -> {});
|
||||
var playerInfoBytes = sendPlayerInfosBytes();
|
||||
for (dc => cc in clients) {
|
||||
if (cc != conn) {
|
||||
cc.sendBytes(playerInfoBytes);
|
||||
NetCommands.addMidGameJoinMarbleClient(cc, conn.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (serverInfo.state == "LOBBY") {
|
||||
// Connect client to lobby
|
||||
NetCommands.enterLobbyClient(conn);
|
||||
}
|
||||
}
|
||||
|
||||
public static function sendPlayerInfosBytes() {
|
||||
var b = new haxe.io.BytesOutput();
|
||||
b.writeByte(PlayerInfo);
|
||||
var cnt = 0;
|
||||
for (c in clientIdMap)
|
||||
cnt++;
|
||||
b.writeByte(cnt + 1); // all + host
|
||||
for (c => v in clientIdMap) {
|
||||
b.writeByte(c);
|
||||
b.writeByte(v.lobbyReady ? 1 : 0);
|
||||
b.writeByte(v.platform);
|
||||
b.writeByte(v.marbleId);
|
||||
var name = v.getName();
|
||||
b.writeByte(name.length);
|
||||
for (i in 0...name.length) {
|
||||
b.writeByte(StringTools.fastCodeAt(name, i));
|
||||
}
|
||||
}
|
||||
// Write host data
|
||||
b.writeByte(0);
|
||||
b.writeByte(Net.lobbyHostReady ? 1 : 0);
|
||||
b.writeByte(getPlatform());
|
||||
b.writeByte(Settings.optionsSettings.marbleIndex);
|
||||
var name = Settings.highscoreName;
|
||||
b.writeByte(name.length);
|
||||
for (i in 0...name.length) {
|
||||
b.writeByte(StringTools.fastCodeAt(name, i));
|
||||
}
|
||||
return b.getBytes();
|
||||
}
|
||||
|
||||
static function onPacketReceived(conn:ClientConnection, c:RTCPeerConnection, dc:RTCDataChannel, input:InputBitStream) {
|
||||
if (!Net.isMP)
|
||||
return; // only for MP
|
||||
conn.lastRecvTime = Console.time();
|
||||
conn.didWarnTimeout = false;
|
||||
|
||||
var packetType = input.readByte();
|
||||
switch (packetType) {
|
||||
case NetCommand:
|
||||
NetCommands.readPacket(input);
|
||||
|
||||
case ClientIdAssign:
|
||||
clientId = input.readByte(); // 8 bit client id, hopefully we don't exceed this
|
||||
Console.log('Client ID set to ${clientId}');
|
||||
NetCommands.setPlayerData(clientId, Settings.highscoreName, Settings.optionsSettings.marbleIndex); // Send our player name to the server
|
||||
NetCommands.transmitPlatform(clientId, getPlatform()); // send our platform too
|
||||
|
||||
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 now = Console.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}');
|
||||
if (Net.isHost) {
|
||||
var b = sendPlayerInfosBytes();
|
||||
for (cc in clients) {
|
||||
cc.sendBytes(b);
|
||||
}
|
||||
onClientHandshakeComplete(conn);
|
||||
}
|
||||
}
|
||||
|
||||
case MarbleUpdate:
|
||||
var marbleUpdatePacket = new MarbleUpdatePacket();
|
||||
marbleUpdatePacket.deserialize(input);
|
||||
var cc = marbleUpdatePacket.clientId;
|
||||
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
||||
var m = MarbleGame.instance.world.lastMoves;
|
||||
m.enqueue(marbleUpdatePacket);
|
||||
}
|
||||
|
||||
case MarbleMove:
|
||||
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
||||
var movePacket = new MarbleMovePacket();
|
||||
movePacket.deserialize(input);
|
||||
var cc = clientIdMap[movePacket.clientId];
|
||||
if (cc.state == GAME)
|
||||
for (move in movePacket.moves)
|
||||
cc.queueMove(move);
|
||||
}
|
||||
|
||||
case PowerupPickup:
|
||||
var powerupPickupPacket = new PowerupPickupPacket();
|
||||
powerupPickupPacket.deserialize(input);
|
||||
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
||||
var m = @:privateAccess MarbleGame.instance.world.powerupPredictions;
|
||||
m.acknowledgePowerupPickup(powerupPickupPacket, MarbleGame.instance.world.timeState, clientConnection.moveManager.getQueueSize());
|
||||
}
|
||||
|
||||
case GemSpawn:
|
||||
var gemSpawnPacket = new GemSpawnPacket();
|
||||
gemSpawnPacket.deserialize(input);
|
||||
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
||||
MarbleGame.instance.world.spawnHuntGemsClientSide(gemSpawnPacket.gemIds);
|
||||
@:privateAccess MarbleGame.instance.world.gemPredictions.acknowledgeGemSpawn(gemSpawnPacket);
|
||||
}
|
||||
|
||||
case GemPickup:
|
||||
var gemPickupPacket = new GemPickupPacket();
|
||||
gemPickupPacket.deserialize(input);
|
||||
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
||||
// @:privateAccess MarbleGame.instance.world.playGui.incrementPlayerScore(gemPickupPacket.clientId, gemPickupPacket.scoreIncr);
|
||||
@:privateAccess MarbleGame.instance.world.gemPredictions.acknowledgeGemPickup(gemPickupPacket);
|
||||
}
|
||||
|
||||
case PlayerInfo:
|
||||
var count = input.readByte();
|
||||
var newP = false;
|
||||
for (i in 0...count) {
|
||||
var id = input.readByte();
|
||||
var cready = input.readByte() == 1;
|
||||
var platform = input.readByte();
|
||||
var marble = input.readByte();
|
||||
if (id != 0 && id != Net.clientId && !clientIdMap.exists(id)) {
|
||||
Console.log('Adding ghost connection ${id}');
|
||||
addGhost(id);
|
||||
newP = true;
|
||||
}
|
||||
var nameLength = input.readByte();
|
||||
var name = "";
|
||||
for (j in 0...nameLength) {
|
||||
name += String.fromCharCode(input.readByte());
|
||||
}
|
||||
if (clientIdMap.exists(id)) {
|
||||
clientIdMap[id].setName(name);
|
||||
clientIdMap[id].setMarbleId(marble);
|
||||
clientIdMap[id].lobbyReady = cready;
|
||||
clientIdMap[id].platform = platform;
|
||||
}
|
||||
if (Net.clientId == id) {
|
||||
Net.lobbyClientReady = cready;
|
||||
}
|
||||
}
|
||||
if (newP) {
|
||||
// AudioManager.playSound(ResourceLoader.getAudio("sounds/spawn_alternate.wav").resource);
|
||||
}
|
||||
if (MarbleGame.canvas.content is MPPlayMissionGui) {
|
||||
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
|
||||
}
|
||||
|
||||
case ScoreBoardInfo:
|
||||
var scoreboardPacket = new ScoreboardPacket();
|
||||
scoreboardPacket.deserialize(input);
|
||||
// if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
||||
// @:privateAccess MarbleGame.instance.world.playGui.updatePlayerScores(scoreboardPacket);
|
||||
// }
|
||||
|
||||
case _:
|
||||
Console.log("unknown command: " + packetType);
|
||||
}
|
||||
}
|
||||
|
||||
static function allocateClientId() {
|
||||
for (id in 0...32) {
|
||||
if (Net.clientIdAllocs & (1 << id) == 0) {
|
||||
Net.clientIdAllocs |= (1 << id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static function freeClientId(id:Int) {
|
||||
Net.clientIdAllocs &= ~(1 << id);
|
||||
}
|
||||
|
||||
public static function sendPacketToAll(packetData:OutputBitStream) {
|
||||
var bytes = packetData.getBytes();
|
||||
for (c => v in clients) {
|
||||
v.sendBytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static function sendPacketToIngame(packetData:OutputBitStream) {
|
||||
var bytes = packetData.getBytes();
|
||||
for (c => v in clients) {
|
||||
if (v.state == GAME)
|
||||
v.sendBytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static function sendPacketToHost(packetData:OutputBitStream) {
|
||||
if (clientDatachannel.state == Open) {
|
||||
var bytes = packetData.getBytes();
|
||||
clientDatachannel.sendBytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static function sendPacketToHostUnreliable(packetData:OutputBitStream) {
|
||||
if (clientDatachannelUnreliable.state == Open) {
|
||||
var bytes = packetData.getBytes();
|
||||
clientDatachannelUnreliable.sendBytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static function sendPacketToClient(client:GameConnection, packetData:OutputBitStream) {
|
||||
var bytes = packetData.getBytes();
|
||||
client.sendBytes(bytes);
|
||||
}
|
||||
|
||||
public static function addDummyConnection() {
|
||||
if (Net.isHost) {
|
||||
addGhost(Net.clientId++);
|
||||
}
|
||||
}
|
||||
|
||||
public static inline function getPlatform() {
|
||||
#if js
|
||||
return NetPlatform.Web;
|
||||
#end
|
||||
#if hl
|
||||
#if MACOS_BUNDLE
|
||||
return NetPlatform.MacOS;
|
||||
#else
|
||||
#if android
|
||||
return NetPlatform.Android;
|
||||
#else
|
||||
return NetPlatform.PC;
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}
|
||||
}
|
||||
345
src/net/NetCommands.hx
Normal file
345
src/net/NetCommands.hx
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
package net;
|
||||
|
||||
import gui.MPPlayMissionGui;
|
||||
import net.ClientConnection.NetPlatform;
|
||||
import gui.EndGameGui;
|
||||
import modes.HuntMode;
|
||||
import net.ClientConnection.GameplayState;
|
||||
import net.Net.NetPacketType;
|
||||
import src.MarbleGame;
|
||||
import src.MissionList;
|
||||
import src.Console;
|
||||
|
||||
@:build(net.RPCMacro.build())
|
||||
class NetCommands {
|
||||
@:rpc(server) public static function setLobbyLevelIndex(category:String, i:Int) {
|
||||
if (MPPlayMissionGui.setLevelFn == null) {
|
||||
MPPlayMissionGui.currentCategoryStatic = category;
|
||||
MPPlayMissionGui.currentSelectionStatic = i;
|
||||
} else {
|
||||
MPPlayMissionGui.setLevelFn(category, i);
|
||||
}
|
||||
}
|
||||
|
||||
// @:rpc(server) public static function setLobbyCustLevelName(str:String) {
|
||||
// if (MPPlayMissionGui.setLevelFn != null) {
|
||||
// MPPlayMissionGui.setLevelStr(str);
|
||||
// } else {
|
||||
// MultiplayerLevelSelectGui.custSelected = true;
|
||||
// MultiplayerLevelSelectGui.custPath = str;
|
||||
// }
|
||||
// }
|
||||
|
||||
@:rpc(server) public static function playLevel(category:String, levelIndex:Int) {
|
||||
MPPlayMissionGui.playSelectedLevel(category, levelIndex);
|
||||
if (Net.isHost) {
|
||||
Net.serverInfo.state = "WAITING";
|
||||
MasterServerClient.instance.sendServerInfo(Net.serverInfo); // notify the server of the wait state
|
||||
}
|
||||
}
|
||||
|
||||
// @:rpc(server) public static function playCustomLevel(levelPath:String) {
|
||||
// var levelEntry = MPCustoms.missionList.filter(x -> x.path == levelPath)[0];
|
||||
// MarbleGame.canvas.setContent(new MultiplayerLoadingGui("Downloading", false));
|
||||
// MPCustoms.play(levelEntry, () -> {}, () -> {
|
||||
// MarbleGame.canvas.setContent(new MultiplayerGui());
|
||||
// Net.disconnect(); // disconnect from the server
|
||||
// });
|
||||
// if (Net.isHost) {
|
||||
// Net.serverInfo.state = "WAITING";
|
||||
// MasterServerClient.instance.sendServerInfo(Net.serverInfo); // notify the server of the wait state
|
||||
// }
|
||||
// }
|
||||
|
||||
@:rpc(server) public static function playLevelMidJoin(index:Int) {
|
||||
if (Net.isClient) {
|
||||
var difficultyMissions = MissionList.missionList['ultra']["multiplayer"];
|
||||
var curMission = difficultyMissions[index];
|
||||
MarbleGame.instance.playMission(curMission, true);
|
||||
}
|
||||
}
|
||||
|
||||
// @:rpc(server) public static function playCustomLevelMidJoin(path:String) {
|
||||
// if (Net.isClient) {
|
||||
// playCustomLevel(path);
|
||||
// }
|
||||
// }
|
||||
|
||||
@:rpc(server) public static function enterLobby() {
|
||||
if (Net.isClient) {
|
||||
MarbleGame.canvas.setContent(new MPPlayMissionGui(false));
|
||||
}
|
||||
}
|
||||
|
||||
@: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 toggleReadiness(clientId:Int) {
|
||||
if (Net.isHost) {
|
||||
if (clientId == 0)
|
||||
Net.lobbyHostReady = !Net.lobbyHostReady;
|
||||
else
|
||||
Net.clientIdMap[clientId].toggleLobbyReady();
|
||||
var allReady = true;
|
||||
for (id => client in Net.clientIdMap) {
|
||||
if (!client.lobbyReady) {
|
||||
allReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (MarbleGame.canvas.content is MPPlayMissionGui) {
|
||||
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
|
||||
}
|
||||
var b = Net.sendPlayerInfosBytes();
|
||||
for (cc in Net.clients) {
|
||||
cc.sendBytes(b);
|
||||
}
|
||||
|
||||
if (allReady && Net.lobbyHostReady) {
|
||||
// if (MultiplayerLevelSelectGui.custSelected) {
|
||||
// NetCommands.playCustomLevel(MultiplayerLevelSelectGui.custPath);
|
||||
// } else
|
||||
NetCommands.playLevel(MPPlayMissionGui.currentCategoryStatic, MPPlayMissionGui.currentSelectionStatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(client) public static function clientIsReady(clientId:Int) {
|
||||
if (Net.isHost) {
|
||||
if (Net.serverInfo.state == "WAITING") {
|
||||
Console.log('Client ${clientId} is ready!');
|
||||
if (clientId != -1)
|
||||
Net.clientIdMap[clientId].ready();
|
||||
else
|
||||
Net.hostReady = true;
|
||||
var allReady = true;
|
||||
for (id => client in Net.clientIdMap) {
|
||||
if (client.state != GameplayState.GAME) {
|
||||
allReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allReady && Net.hostReady) {
|
||||
if (MarbleGame.instance.world != null) {
|
||||
Console.log('All are ready, starting');
|
||||
MarbleGame.instance.world.allClientsReady();
|
||||
}
|
||||
Net.serverInfo.state = "PLAYING";
|
||||
MasterServerClient.instance.sendServerInfo(Net.serverInfo); // notify the server of the playing state
|
||||
}
|
||||
} else {
|
||||
// Mid game join
|
||||
Console.log("Mid game join for client " + clientId);
|
||||
// Send em our present world state
|
||||
if (MarbleGame.instance.world != null) {
|
||||
var packets = MarbleGame.instance.world.getWorldStateForClientJoin();
|
||||
var c = Net.clientIdMap[clientId];
|
||||
for (packet in packets) {
|
||||
c.sendBytes(packet);
|
||||
}
|
||||
Net.clientIdMap[clientId].ready();
|
||||
|
||||
if (MarbleGame.instance.world.serverStartTicks == 0) {
|
||||
var allReady = true;
|
||||
for (id => client in Net.clientIdMap) {
|
||||
if (client.state != GameplayState.GAME) {
|
||||
allReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allReady) {
|
||||
if (MarbleGame.instance.world != null) {
|
||||
MarbleGame.instance.world.allClientsReady();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Send the start ticks
|
||||
NetCommands.setStartTicksMidJoinClient(c, MarbleGame.instance.world.serverStartTicks, MarbleGame.instance.world.timeState.ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(server) public static function addMidGameJoinMarble(cc:Int) {
|
||||
if (Net.isClient) {
|
||||
if (MarbleGame.instance.world != null) {
|
||||
MarbleGame.instance.world.addJoiningClientGhost(Net.clientIdMap[cc], () -> {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(server) public static function setStartTicks(ticks:Int) {
|
||||
if (MarbleGame.instance.world != null) {
|
||||
MarbleGame.instance.world.serverStartTicks = ticks + 1; // Extra tick so we don't get 0
|
||||
if (Net.isClient) {
|
||||
@:privateAccess MarbleGame.instance.world.marble.serverTicks = ticks;
|
||||
}
|
||||
MarbleGame.instance.world.startTime = MarbleGame.instance.world.timeState.timeSinceLoad + 3.5 + 0.032; // 1 extra tick
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(server) public static function setStartTicksMidJoin(startTicks:Int, currentTicks:Int) {
|
||||
if (MarbleGame.instance.world != null) {
|
||||
MarbleGame.instance.world.serverStartTicks = startTicks + 1; // Extra tick so we don't get 0
|
||||
MarbleGame.instance.world.startTime = MarbleGame.instance.world.timeState.timeSinceLoad + 0.032; // 1 extra tick
|
||||
MarbleGame.instance.world.timeState.ticks = currentTicks;
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(server) public static function timerRanOut() {
|
||||
if (Net.isClient && MarbleGame.instance.world != null) {
|
||||
if (MarbleGame.instance.paused) {
|
||||
MarbleGame.instance.handlePauseGame(); // Unpause
|
||||
}
|
||||
var huntMode:HuntMode = cast MarbleGame.instance.world.gameMode;
|
||||
huntMode.onTimeExpire();
|
||||
}
|
||||
if (Net.isHost) {
|
||||
Net.serverInfo.state = "WAITING";
|
||||
MasterServerClient.instance.sendServerInfo(Net.serverInfo); // notify the server of the playing state
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(server) public static function clientDisconnected(clientId:Int) {
|
||||
var conn = Net.clientIdMap.get(clientId);
|
||||
if (MarbleGame.instance.world != null) {
|
||||
MarbleGame.instance.world.removePlayer(conn);
|
||||
|
||||
var allReady = true;
|
||||
for (id => client in Net.clientIdMap) {
|
||||
if (client.state != GameplayState.GAME && client != conn) {
|
||||
allReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allReady && MarbleGame.instance.world.serverStartTicks == 0) {
|
||||
MarbleGame.instance.world.allClientsReady();
|
||||
}
|
||||
}
|
||||
Net.clientIdMap.remove(clientId);
|
||||
if (MarbleGame.canvas.content is MPPlayMissionGui) {
|
||||
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(server) public static function clientJoin(clientId:Int) {}
|
||||
|
||||
@:rpc(client) public static function clientLeave(clientId:Int) {
|
||||
if (Net.isHost) {
|
||||
@:privateAccess Net.onClientLeave(cast Net.clientIdMap[clientId]);
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(server) public static function serverClosed() {
|
||||
if (Net.isClient) {
|
||||
if (MarbleGame.instance.world != null) {
|
||||
MarbleGame.instance.quitMission();
|
||||
}
|
||||
// var loadGui = new MultiplayerLoadingGui("Server closed");
|
||||
// MarbleGame.canvas.setContent(loadGui);
|
||||
// loadGui.setErrorStatus("Server closed");
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(client) public static function setPlayerData(clientId:Int, name:String, marble:Int) {
|
||||
if (Net.isHost) {
|
||||
Net.clientIdMap[clientId].setName(name);
|
||||
Net.clientIdMap[clientId].setMarbleId(marble);
|
||||
if (MarbleGame.canvas.content is MPPlayMissionGui) {
|
||||
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(client) public static function transmitPlatform(clientId:Int, platform:Int) {
|
||||
if (Net.isHost) {
|
||||
Net.clientIdMap[clientId].platform = platform;
|
||||
if (MarbleGame.canvas.content is MPPlayMissionGui) {
|
||||
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(server) public static function endGame() {
|
||||
for (c => v in Net.clientIdMap) {
|
||||
v.state = LOBBY;
|
||||
v.lobbyReady = false;
|
||||
}
|
||||
if (Net.isClient) {
|
||||
if (MarbleGame.instance.world != null) {
|
||||
MarbleGame.instance.quitMission();
|
||||
}
|
||||
}
|
||||
if (Net.isHost) {
|
||||
Net.lobbyHostReady = false;
|
||||
Net.hostReady = false;
|
||||
|
||||
Net.serverInfo.state = "LOBBY";
|
||||
MasterServerClient.instance.sendServerInfo(Net.serverInfo); // notify the server of the playing state
|
||||
var b = Net.sendPlayerInfosBytes();
|
||||
for (cc in Net.clients) {
|
||||
cc.sendBytes(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(server) public static function restartGame() {
|
||||
var world = MarbleGame.instance.world;
|
||||
if (Net.isHost) {
|
||||
world.startTime = 1e8;
|
||||
haxe.Timer.delay(() -> world.allClientsReady(), 1500);
|
||||
}
|
||||
if (Net.isClient) {
|
||||
var gui = MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1];
|
||||
if (gui is EndGameGui) {
|
||||
var egg = cast(gui, EndGameGui);
|
||||
// egg.retryFunc(null);
|
||||
world.restartMultiplayerState();
|
||||
}
|
||||
}
|
||||
world.multiplayerStarted = false;
|
||||
}
|
||||
|
||||
@:rpc(server) public static function ping(sendTime:Float) {
|
||||
if (Net.isClient) {
|
||||
pingBack(Console.time() - sendTime);
|
||||
}
|
||||
}
|
||||
|
||||
@:rpc(client) public static function pingBack(ping:Float) {
|
||||
// Do nothing???
|
||||
}
|
||||
|
||||
@:rpc(client) public static function requestPing() {
|
||||
if (Net.isHost) {
|
||||
ping(Console.time());
|
||||
}
|
||||
}
|
||||
|
||||
// @:rpc(client) public static function sendChatMessage(msg:String) {
|
||||
// if (Net.isHost) {
|
||||
// sendServerChatMessage(msg);
|
||||
// }
|
||||
// }
|
||||
// @:rpc(server) public static function sendServerChatMessage(msg:String) {
|
||||
// if (MarbleGame.instance.world != null) {
|
||||
// if (MarbleGame.instance.world._ready) {
|
||||
// @:privateAccess MarbleGame.instance.world.playGui.addChatMessage(msg);
|
||||
// }
|
||||
// } else {
|
||||
// if (MarbleGame.canvas.content is MultiplayerLevelSelectGui) {
|
||||
// cast(MarbleGame.canvas.content, MultiplayerLevelSelectGui).addChatMessage(msg);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
258
src/net/NetPacket.hx
Normal file
258
src/net/NetPacket.hx
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
package net;
|
||||
|
||||
import net.BitStream.InputBitStream;
|
||||
import net.BitStream.OutputBitStream;
|
||||
import h3d.Vector;
|
||||
import net.MoveManager.NetMove;
|
||||
|
||||
interface NetPacket {
|
||||
public function serialize(b:OutputBitStream):Void;
|
||||
public function deserialize(b:InputBitStream):Void;
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
class MarbleMovePacket implements NetPacket {
|
||||
var clientId:Int;
|
||||
var clientTicks:Int;
|
||||
var moves:Array<NetMove>;
|
||||
|
||||
public function new() {
|
||||
moves = [];
|
||||
}
|
||||
|
||||
public inline function deserialize(b:InputBitStream) {
|
||||
clientId = b.readByte();
|
||||
clientTicks = b.readUInt16();
|
||||
var count = b.readInt(5);
|
||||
moves = [];
|
||||
for (i in 0...count) {
|
||||
moves.push(MoveManager.unpackMove(b));
|
||||
}
|
||||
}
|
||||
|
||||
public inline function serialize(b:OutputBitStream) {
|
||||
b.writeByte(clientId);
|
||||
b.writeUInt16(clientTicks);
|
||||
b.writeInt(moves.length, 5);
|
||||
for (move in moves)
|
||||
MoveManager.packMove(move, b);
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract MarbleNetFlags(Int) from Int to Int {
|
||||
var NullFlag = 0;
|
||||
var DoBlast = 1 << 0;
|
||||
var DoHelicopter = 1 << 1;
|
||||
var DoMega = 1 << 2;
|
||||
var PickupPowerup = 1 << 3;
|
||||
var GravityChange = 1 << 4;
|
||||
var UsePowerup = 1 << 5;
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
class MarbleUpdatePacket implements NetPacket {
|
||||
var clientId:Int;
|
||||
var move:NetMove;
|
||||
var serverTicks:Int;
|
||||
var calculationTicks:Int = -1;
|
||||
var position:Vector;
|
||||
var velocity:Vector;
|
||||
var omega:Vector;
|
||||
var blastAmount:Int;
|
||||
var blastTick:Int;
|
||||
var megaTick:Int;
|
||||
var heliTick:Int;
|
||||
var gravityDirection:Vector;
|
||||
var oob:Bool;
|
||||
var powerUpId:Int;
|
||||
var moveQueueSize:Int;
|
||||
var netFlags:Int;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public inline function serialize(b:OutputBitStream) {
|
||||
b.writeByte(clientId);
|
||||
MoveManager.packMove(move, b);
|
||||
b.writeUInt16(serverTicks);
|
||||
b.writeByte(moveQueueSize);
|
||||
b.writeFloat(position.x);
|
||||
b.writeFloat(position.y);
|
||||
b.writeFloat(position.z);
|
||||
b.writeFloat(velocity.x);
|
||||
b.writeFloat(velocity.y);
|
||||
b.writeFloat(velocity.z);
|
||||
b.writeFloat(omega.x);
|
||||
b.writeFloat(omega.y);
|
||||
b.writeFloat(omega.z);
|
||||
b.writeInt(blastAmount, 11);
|
||||
if (netFlags & MarbleNetFlags.DoBlast > 0) {
|
||||
b.writeFlag(true);
|
||||
b.writeUInt16(blastTick);
|
||||
} else {
|
||||
b.writeFlag(false);
|
||||
}
|
||||
if (netFlags & MarbleNetFlags.DoHelicopter > 0) {
|
||||
b.writeFlag(true);
|
||||
b.writeUInt16(heliTick);
|
||||
} else {
|
||||
b.writeFlag(false);
|
||||
}
|
||||
if (netFlags & MarbleNetFlags.DoMega > 0) {
|
||||
b.writeFlag(true);
|
||||
b.writeUInt16(megaTick);
|
||||
} else {
|
||||
b.writeFlag(false);
|
||||
}
|
||||
b.writeFlag(oob);
|
||||
if (netFlags & MarbleNetFlags.UsePowerup > 0) {
|
||||
b.writeFlag(true);
|
||||
} else {
|
||||
b.writeFlag(false);
|
||||
}
|
||||
|
||||
if (netFlags & MarbleNetFlags.PickupPowerup > 0) {
|
||||
b.writeFlag(true);
|
||||
b.writeInt(powerUpId, 9);
|
||||
} else {
|
||||
b.writeFlag(false);
|
||||
}
|
||||
if (netFlags & MarbleNetFlags.GravityChange > 0) {
|
||||
b.writeFlag(true);
|
||||
b.writeFloat(gravityDirection.x);
|
||||
b.writeFloat(gravityDirection.y);
|
||||
b.writeFloat(gravityDirection.z);
|
||||
} else {
|
||||
b.writeFlag(false);
|
||||
}
|
||||
}
|
||||
|
||||
public inline function deserialize(b:InputBitStream) {
|
||||
clientId = b.readByte();
|
||||
move = MoveManager.unpackMove(b);
|
||||
serverTicks = b.readUInt16();
|
||||
moveQueueSize = b.readByte();
|
||||
position = new Vector(b.readFloat(), b.readFloat(), b.readFloat());
|
||||
velocity = new Vector(b.readFloat(), b.readFloat(), b.readFloat());
|
||||
omega = new Vector(b.readFloat(), b.readFloat(), b.readFloat());
|
||||
blastAmount = b.readInt(11);
|
||||
this.netFlags = 0;
|
||||
if (b.readFlag()) {
|
||||
blastTick = b.readUInt16();
|
||||
this.netFlags |= MarbleNetFlags.DoBlast;
|
||||
}
|
||||
if (b.readFlag()) {
|
||||
heliTick = b.readUInt16();
|
||||
this.netFlags |= MarbleNetFlags.DoHelicopter;
|
||||
}
|
||||
if (b.readFlag()) {
|
||||
megaTick = b.readUInt16();
|
||||
this.netFlags |= MarbleNetFlags.DoMega;
|
||||
}
|
||||
oob = b.readFlag();
|
||||
if (b.readFlag())
|
||||
this.netFlags |= MarbleNetFlags.UsePowerup;
|
||||
if (b.readFlag()) {
|
||||
powerUpId = b.readInt(9);
|
||||
this.netFlags |= MarbleNetFlags.PickupPowerup;
|
||||
}
|
||||
if (b.readFlag()) {
|
||||
gravityDirection = new Vector(b.readFloat(), b.readFloat(), b.readFloat());
|
||||
this.netFlags |= MarbleNetFlags.GravityChange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
class PowerupPickupPacket implements NetPacket {
|
||||
var clientId:Int;
|
||||
var serverTicks:Int;
|
||||
var powerupItemId:Int;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public inline function deserialize(b:InputBitStream) {
|
||||
clientId = b.readByte();
|
||||
serverTicks = b.readUInt16();
|
||||
powerupItemId = b.readInt(10);
|
||||
}
|
||||
|
||||
public inline function serialize(b:OutputBitStream) {
|
||||
b.writeByte(clientId);
|
||||
b.writeUInt16(serverTicks);
|
||||
b.writeInt(powerupItemId, 10);
|
||||
}
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
class GemSpawnPacket implements NetPacket {
|
||||
var gemIds:Array<Int>;
|
||||
|
||||
public function new() {
|
||||
gemIds = [];
|
||||
}
|
||||
|
||||
public function serialize(b:OutputBitStream) {
|
||||
b.writeInt(gemIds.length, 5);
|
||||
for (gemId in gemIds) {
|
||||
b.writeInt(gemId, 11);
|
||||
}
|
||||
}
|
||||
|
||||
public function deserialize(b:InputBitStream) {
|
||||
var count = b.readInt(5);
|
||||
for (i in 0...count) {
|
||||
gemIds.push(b.readInt(11));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
class GemPickupPacket implements NetPacket {
|
||||
var clientId:Int;
|
||||
var serverTicks:Int;
|
||||
var gemId:Int;
|
||||
var scoreIncr:Int;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public inline function deserialize(b:InputBitStream) {
|
||||
clientId = b.readByte();
|
||||
serverTicks = b.readUInt16();
|
||||
gemId = b.readInt(11);
|
||||
scoreIncr = b.readInt(4);
|
||||
}
|
||||
|
||||
public inline function serialize(b:OutputBitStream) {
|
||||
b.writeByte(clientId);
|
||||
b.writeUInt16(serverTicks);
|
||||
b.writeInt(gemId, 11);
|
||||
b.writeInt(scoreIncr, 4);
|
||||
}
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
class ScoreboardPacket implements NetPacket {
|
||||
var scoreBoard:Map<Int, Int>;
|
||||
|
||||
public function new() {
|
||||
scoreBoard = new Map();
|
||||
}
|
||||
|
||||
public inline function deserialize(b:InputBitStream) {
|
||||
var count = b.readInt(4);
|
||||
for (i in 0...count) {
|
||||
scoreBoard[b.readInt(6)] = b.readInt(10);
|
||||
}
|
||||
}
|
||||
|
||||
public inline function serialize(b:OutputBitStream) {
|
||||
var keycount = 0;
|
||||
for (k => v in scoreBoard)
|
||||
keycount++;
|
||||
b.writeInt(keycount, 4);
|
||||
for (key => v in scoreBoard) {
|
||||
b.writeInt(key, 6);
|
||||
b.writeInt(v, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/net/PowerupPredictionStore.hx
Normal file
24
src/net/PowerupPredictionStore.hx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package net;
|
||||
|
||||
import src.TimeState;
|
||||
import net.NetPacket.PowerupPickupPacket;
|
||||
|
||||
class PowerupPredictionStore {
|
||||
var predictions:Array<Float>;
|
||||
|
||||
public inline function new() {
|
||||
predictions = [];
|
||||
}
|
||||
|
||||
public inline function alloc() {
|
||||
predictions.push(Math.NEGATIVE_INFINITY);
|
||||
}
|
||||
|
||||
public inline function getState(netIndex:Int) {
|
||||
return predictions[netIndex];
|
||||
}
|
||||
|
||||
public inline function acknowledgePowerupPickup(packet:PowerupPickupPacket, timeState:TimeState, futureTicks:Int) {
|
||||
predictions[packet.powerupItemId] = timeState.currentAttemptTime - futureTicks * 0.032; // Approximate
|
||||
}
|
||||
}
|
||||
175
src/net/RPCMacro.hx
Normal file
175
src/net/RPCMacro.hx
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
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();
|
||||
|
||||
var fieldsToAdd = [];
|
||||
|
||||
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 TPath({
|
||||
name: 'String'
|
||||
}): {
|
||||
deserializeFns.push(macro var $argName = stream.readString());
|
||||
callExprs.push(macro $i{argName});
|
||||
serializeFns.push(macro stream.writeString($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 net.BitStream.OutputBitStream();
|
||||
stream.writeByte(NetPacketType.NetCommand);
|
||||
stream.writeByte($v{rpcFnId});
|
||||
$b{serializeFns};
|
||||
Net.sendPacketToAll(stream);
|
||||
}
|
||||
};
|
||||
var origExpr = f.expr;
|
||||
var lastExprSingle = macro {
|
||||
if (Net.isHost) {
|
||||
var stream = new net.BitStream.OutputBitStream();
|
||||
stream.writeByte(NetPacketType.NetCommand);
|
||||
stream.writeByte($v{rpcFnId});
|
||||
$b{serializeFns};
|
||||
Net.sendPacketToClient(client, stream);
|
||||
}
|
||||
};
|
||||
|
||||
var singleClientfn:Field = {
|
||||
name: field.name + "Client",
|
||||
pos: Context.currentPos(),
|
||||
access: [APublic, AStatic],
|
||||
kind: FFun({
|
||||
args: [
|
||||
{
|
||||
name: "client",
|
||||
type: haxe.macro.TypeTools.toComplexType(Context.getType('net.ClientConnection.GameConnection'))
|
||||
}
|
||||
].concat(f.args),
|
||||
expr: macro $b{[origExpr, lastExprSingle]}
|
||||
})
|
||||
};
|
||||
fieldsToAdd.push(singleClientfn);
|
||||
|
||||
f.expr = macro $b{[f.expr, lastExpr]};
|
||||
|
||||
case EConst(CIdent("client")):
|
||||
var lastExpr = macro {
|
||||
if (!Net.isHost) {
|
||||
var stream = new net.BitStream.OutputBitStream();
|
||||
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('net.BitStream.InputBitStream'))
|
||||
}
|
||||
],
|
||||
expr: macro {
|
||||
var fnId = stream.readByte();
|
||||
|
||||
$e{
|
||||
{
|
||||
expr: ESwitch(macro fnId, cases, null),
|
||||
pos: Context.currentPos()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
fields.push(deserializeField);
|
||||
for (fn in fieldsToAdd) {
|
||||
fields.push(fn);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ class RewindManager {
|
|||
public function recordFrame() {
|
||||
var rf = new RewindFrame();
|
||||
rf.timeState = level.timeState.clone();
|
||||
rf.marblePosition = level.marble.getAbsPos().getPosition().clone();
|
||||
rf.marblePosition = level.marble.collider.transform.getPosition().clone();
|
||||
rf.marbleOrientation = level.marble.getRotationQuat().clone();
|
||||
rf.marbleVelocity = level.marble.velocity.clone();
|
||||
rf.marbleAngularVelocity = level.marble.omega.clone();
|
||||
|
|
@ -98,7 +98,7 @@ class RewindManager {
|
|||
rf.powerupStates.push(ab.lastContactTime);
|
||||
}
|
||||
}
|
||||
rf.blastAmt = level.blastAmount;
|
||||
rf.blastAmt = level.marble.blastAmount;
|
||||
rf.oobState = {
|
||||
oob: level.marble.outOfBounds,
|
||||
timeState: level.marble.outOfBoundsTime != null ? level.marble.outOfBoundsTime.clone() : null
|
||||
|
|
@ -121,7 +121,7 @@ class RewindManager {
|
|||
|
||||
public function applyFrame(rf:RewindFrame) {
|
||||
level.timeState = rf.timeState.clone();
|
||||
level.marble.setPosition(rf.marblePosition.x, rf.marblePosition.y, rf.marblePosition.z);
|
||||
level.marble.setMarblePosition(rf.marblePosition.x, rf.marblePosition.y, rf.marblePosition.z);
|
||||
level.marble.setRotationQuat(rf.marbleOrientation.clone());
|
||||
level.marble.velocity.set(rf.marbleVelocity.x, rf.marbleVelocity.y, rf.marbleVelocity.z);
|
||||
level.marble.omega.set(rf.marbleAngularVelocity.x, rf.marbleAngularVelocity.y, rf.marbleAngularVelocity.z);
|
||||
|
|
@ -219,7 +219,7 @@ class RewindManager {
|
|||
level.marble.outOfBounds = rf.oobState.oob;
|
||||
level.marble.camera.oob = rf.oobState.oob;
|
||||
level.marble.outOfBoundsTime = rf.oobState.timeState != null ? rf.oobState.timeState.clone() : null;
|
||||
level.blastAmount = rf.blastAmt;
|
||||
level.marble.blastAmount = rf.blastAmt;
|
||||
@:privateAccess level.checkpointUp = rf.checkpointState.checkpointUp;
|
||||
@:privateAccess level.checkpointCollectedGems = rf.checkpointState.checkpointCollectedGems;
|
||||
@:privateAccess level.cheeckpointBlast = rf.checkpointState.checkpointBlast;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import src.TimeState;
|
|||
import h3d.Vector;
|
||||
import src.DtsObject;
|
||||
import src.MarbleWorld;
|
||||
import net.NetPacket.MarbleNetFlags;
|
||||
|
||||
class AntiGravity extends PowerUp {
|
||||
public function new(element:MissionElementItem, norespawn:Bool = false) {
|
||||
|
|
@ -33,7 +34,7 @@ class AntiGravity extends PowerUp {
|
|||
if (marble == level.marble)
|
||||
this.level.setUp(marble, direction, timeState);
|
||||
else {
|
||||
// @:privateAccess marble.netFlags |= MarbleNetFlags.GravityChange;
|
||||
@:privateAccess marble.netFlags |= MarbleNetFlags.GravityChange;
|
||||
marble.currentUp.load(direction);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class Blast extends PowerUp {
|
|||
}
|
||||
|
||||
public function use(marble:src.Marble, timeState:TimeState) {
|
||||
this.level.blastAmount = 1.03;
|
||||
marble.blastAmount = 1.03;
|
||||
marble.blastTicks = 36000 >> 5; // Fix me
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,10 +128,12 @@ class Nuke extends DtsObject {
|
|||
this.setCollisionEnabled(false);
|
||||
|
||||
// if (!this.level.rewinding)
|
||||
AudioManager.playSound(ResourceLoader.getResource("data/sound/nukeexplode.wav", ResourceLoader.getAudio, this.soundResources));
|
||||
this.level.particleManager.createEmitter(nukeParticle, nukeParticleData, this.getAbsPos().getPosition());
|
||||
this.level.particleManager.createEmitter(nukeSmokeParticle, nukeSmokeParticleData, this.getAbsPos().getPosition());
|
||||
this.level.particleManager.createEmitter(nukeSparksParticle, nukeSparkParticleData, this.getAbsPos().getPosition());
|
||||
if (@:privateAccess !marble.isNetUpdate) {
|
||||
AudioManager.playSound(ResourceLoader.getResource("data/sound/nukeexplode.wav", ResourceLoader.getAudio, this.soundResources));
|
||||
this.level.particleManager.createEmitter(nukeParticle, nukeParticleData, this.getAbsPos().getPosition());
|
||||
this.level.particleManager.createEmitter(nukeSmokeParticle, nukeSmokeParticleData, this.getAbsPos().getPosition());
|
||||
this.level.particleManager.createEmitter(nukeSparksParticle, nukeSparkParticleData, this.getAbsPos().getPosition());
|
||||
}
|
||||
|
||||
var minePos = this.getAbsPos().getPosition();
|
||||
var dtsCenter = this.dts.bounds.center();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import src.Util;
|
|||
import h3d.Vector;
|
||||
import src.DtsObject;
|
||||
import src.Marble;
|
||||
import net.Net;
|
||||
import net.BitStream.OutputBitStream;
|
||||
import net.NetPacket.PowerupPickupPacket;
|
||||
|
||||
abstract class PowerUp extends DtsObject {
|
||||
public var lastPickUpTime:Float = -1;
|
||||
|
|
@ -40,6 +43,23 @@ abstract class PowerUp extends DtsObject {
|
|||
if (this.pickUp(marble)) {
|
||||
// this.level.replay.recordMarbleInside(this);
|
||||
|
||||
if (level.isMultiplayer && Net.isHost) {
|
||||
var b = new OutputBitStream();
|
||||
b.writeByte(NetPacketType.PowerupPickup);
|
||||
var pickupPacket = new PowerupPickupPacket();
|
||||
pickupPacket.clientId = @:privateAccess marble.connection != null ? @:privateAccess marble.connection.id : 0;
|
||||
pickupPacket.serverTicks = timeState.ticks;
|
||||
pickupPacket.powerupItemId = this.netIndex;
|
||||
pickupPacket.serialize(b);
|
||||
Net.sendPacketToIngame(b);
|
||||
pickupClient = pickupPacket.clientId;
|
||||
pickupTicks = pickupPacket.serverTicks;
|
||||
}
|
||||
|
||||
if (level.isMultiplayer && Net.isClient) {
|
||||
pickupClient = @:privateAccess marble.connection != null ? @:privateAccess marble.connection.id : Net.clientId;
|
||||
}
|
||||
|
||||
this.lastPickUpTime = timeState.currentAttemptTime;
|
||||
if (this.autoUse)
|
||||
this.use(marble, timeState);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue