kind of do powerups and use our own bitstream

This commit is contained in:
RandomityGuy 2024-02-25 23:48:17 +05:30
parent 3bf672df9a
commit 16dc9dc718
14 changed files with 180 additions and 69 deletions

View file

@ -1,5 +1,6 @@
package src;
import net.BitStream.OutputBitStream;
import net.ClientConnection;
import net.ClientConnection.GameConnection;
import net.NetPacket.MarbleUpdatePacket;
@ -577,7 +578,7 @@ class Marble extends GameObject {
A = A.add(force.multiply(1 / mass));
}
for (marble in level.marbles) {
if (marble != this) {
if (marble != cast this) {
var force = marble.getForce(this.collider.transform.getPosition(), tick);
A = A.add(force.multiply(1 / mass));
}
@ -1608,7 +1609,7 @@ class Marble extends GameObject {
var pTime = timeState.clone();
pTime.dt = timeStep;
pTime.currentAttemptTime = passedTime;
this.heldPowerup.use(this, pTime);
this.heldPowerup.use(cast this, pTime);
this.heldPowerup = null;
if (this.level.isRecording) {
this.level.replay.recordPowerupPickup(null);
@ -1655,7 +1656,7 @@ class Marble extends GameObject {
// MP Only Functions
public function packUpdate(move:NetMove, timeState:TimeState) {
var b = new haxe.io.BytesOutput();
var b = new OutputBitStream();
b.writeByte(NetPacketType.MarbleUpdate);
var marbleUpdate = new MarbleUpdatePacket();
marbleUpdate.clientId = connection != null ? connection.id : 0;
@ -1670,6 +1671,7 @@ class Marble extends GameObject {
marbleUpdate.heliTick = this.helicopterUseTick;
marbleUpdate.megaTick = this.megaMarbleUseTick;
marbleUpdate.oob = this.outOfBounds;
marbleUpdate.powerUpId = this.heldPowerup != null ? this.heldPowerup.netIndex : 0xFFFF;
marbleUpdate.serialize(b);
return b.getBytes();
}
@ -1694,6 +1696,11 @@ class Marble extends GameObject {
this.megaMarbleUseTick = p.megaTick;
this.outOfBounds = p.oob;
this.camera.oob = p.oob;
if (p.powerUpId == 0xFFFF) {
this.level.deselectPowerUp(cast this);
} else {
this.level.pickUpPowerUp(cast this, this.level.powerUps[p.powerUpId]);
}
if (this.controllable && Net.isClient) {
// We are client, need to do something about the queue
var mm = Net.clientConnection.moveManager;
@ -1712,7 +1719,7 @@ class Marble extends GameObject {
if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching && !this.level.isReplayingMovement) {
if (Net.isClient) {
var axis = getMarbleAxis()[1];
move = Net.clientConnection.moveManager.recordMove(this, axis, timeState);
move = Net.clientConnection.moveManager.recordMove(cast this, axis, timeState);
} else if (Net.isHost) {
var axis = getMarbleAxis()[1];
var innerMove = recordMove();
@ -1763,6 +1770,11 @@ class Marble extends GameObject {
}
}
if (move.move.jump && this.outOfBounds) {
this.level.cancel(this.oobSchedule);
this.level.restart(cast this);
}
return move;
// if (Net.isHost) {
// packets.push({b: packUpdate(move, timeState), c: this.connection != null ? this.connection.id : 0});
@ -1770,7 +1782,7 @@ class Marble extends GameObject {
}
public function updateClient(timeState:TimeState, pathedInteriors:Array<PathedInterior>) {
this.level.updateBlast(this, timeState);
this.level.updateBlast(cast this, timeState);
if (oldPos != null && newPos != null) {
var deltaT = physicsAccumulator / 0.032;
var renderPos = Util.lerpThreeVectors(this.oldPos, this.newPos, deltaT);

View file

@ -1,5 +1,6 @@
package src;
import net.PowerupPredictionStore;
import net.MarblePredictionStore;
import net.MarblePredictionStore.MarblePrediction;
import net.MarbleUpdateQueue;
@ -208,6 +209,7 @@ class MarbleWorld extends Scheduler {
var clientMarbles:Map<GameConnection, Marble> = [];
var predictions:MarblePredictionStore;
var powerupPredictions:PowerupPredictionStore;
public var lastMoves:MarbleUpdateQueue;
@ -239,17 +241,18 @@ class MarbleWorld extends Scheduler {
this.scene2d = scene2d;
this.mission = mission;
this.game = mission.game.toLowerCase();
this.gameMode = GameModeFactory.getGameMode(this, mission.missionInfo.gamemode);
this.gameMode = GameModeFactory.getGameMode(cast this, mission.missionInfo.gamemode);
this.replay = new Replay(mission.path, mission.isClaMission ? mission.id : 0);
this.isRecording = record;
this.rewindManager = new RewindManager(this);
this.inputRecorder = new InputRecorder(this);
this.rewindManager = new RewindManager(cast this);
this.inputRecorder = new InputRecorder(cast this);
this.isMultiplayer = multiplayer;
if (this.isMultiplayer) {
isRecording = false;
isWatching = false;
lastMoves = new MarbleUpdateQueue();
predictions = new MarblePredictionStore();
powerupPredictions = new PowerupPredictionStore();
}
// Set the network RNG for hunt
@ -345,7 +348,7 @@ class MarbleWorld extends Scheduler {
this.playGui = new PlayGui();
this.instanceManager = new InstanceManager(scene);
this.particleManager = new ParticleManager(cast this);
this.radar = new Radar(this, this.scene2d);
this.radar = new Radar(cast this, this.scene2d);
radar.init();
var worker = new ResourceLoaderWorker(() -> {
@ -955,8 +958,13 @@ class MarbleWorld extends Scheduler {
var worker = new ResourceLoaderWorker(() -> {
obj.idInLevel = this.dtsObjects.length; // Set the id of the thing
this.dtsObjects.push(obj);
if (obj is PowerUp)
if (obj is PowerUp) {
var pw:PowerUp = cast obj;
pw.netIndex = this.powerUps.length;
this.powerUps.push(cast obj);
if (Net.isClient)
powerupPredictions.alloc();
}
if (obj is ForceObject) {
this.forceObjects.push(cast obj);
}
@ -1142,13 +1150,16 @@ class MarbleWorld extends Scheduler {
advanceTimeState.dt = 0.032;
advanceTimeState.ticks = ourLastMoveTime;
if (marbleNeedsPrediction & (1 << Net.clientId) > 0) {
if (qm != null) {
var mvs = qm.powerupStates.copy();
for (pw in marble.level.powerUps) {
pw.lastPickUpTime = mvs.shift();
}
if (marbleNeedsPrediction > 0) {
// if (qm != null) {
// var mvs = qm.powerupStates.copy();
for (pw in marble.level.powerUps) {
// var val = mvs.shift();
// if (pw.lastPickUpTime != val)
// Console.log('Revert powerup pickup: ${pw.lastPickUpTime} -> ${val}');
pw.lastPickUpTime = powerupPredictions.getState(pw.netIndex);
}
// }
}
ackLag = ourQueuedMoves.length;
@ -1189,7 +1200,6 @@ class MarbleWorld extends Scheduler {
var m = move.move;
// Debug.drawSphere(@:privateAccess this.marble.newPos, this.marble._radius);
if (marbleNeedsPrediction & (1 << Net.clientId) > 0) {
this.marble.heldPowerup = move.powerup;
@:privateAccess this.marble.moveMotionDir = move.motionDir;
@:privateAccess this.marble.advancePhysics(advanceTimeState, m, this.collisionWorld, this.pathedInteriors);
this.predictions.storeState(this.marble, move.timeState.ticks);
@ -1476,12 +1486,14 @@ class MarbleWorld extends Scheduler {
ProfilerUI.measure("updateAudio");
AudioManager.update(this.scene);
if (this.marble.outOfBounds
&& this.finishTime == null
&& (Key.isDown(Settings.controlsSettings.jump) || Gamepad.isDown(Settings.gamepadSettings.jump))
&& !this.isWatching) {
this.restart(this.marble);
return;
if (!this.isMultiplayer) {
if (this.marble.outOfBounds
&& this.finishTime == null
&& (Key.isDown(Settings.controlsSettings.jump) || Gamepad.isDown(Settings.gamepadSettings.jump))
&& !this.isWatching) {
this.restart(this.marble);
return;
}
}
if (!this.isWatching) {
@ -1690,8 +1702,8 @@ class MarbleWorld extends Scheduler {
this.helpTextTimeState = this.timeState.timeSinceLoad;
}
public function pickUpGem(gem:Gem) {
this.gameMode.onGemPickup(gem);
public function pickUpGem(marble:Marble, gem:Gem) {
this.gameMode.onGemPickup(marble, gem);
}
public function callCollisionHandlers(marble:Marble, timeState:TimeState, start:Vector, end:Vector) {

View file

@ -26,7 +26,7 @@ interface GameMode {
public function onTimeExpire():Void;
public function onRestart():Void;
public function onRespawn(marble:Marble):Void;
public function onGemPickup(gem:Gem):Void;
public function onGemPickup(marble:Marble, gem:Gem):Void;
public function getPreloadFiles():Array<String>;
public function constructRewindState():RewindableState;

View file

@ -282,25 +282,32 @@ class HuntMode extends NullMode {
@:privateAccess level.playGui.formatGemHuntCounter(points);
}
override function onGemPickup(gem:Gem) {
AudioManager.playSound(ResourceLoader.getResource('data/sound/gem_collect.wav', ResourceLoader.getAudio, @:privateAccess this.level.soundResources));
override function onGemPickup(marble:Marble, gem:Gem) {
if (marble == level.marble)
AudioManager.playSound(ResourceLoader.getResource('data/sound/gem_collect.wav', ResourceLoader.getAudio,
@:privateAccess this.level.soundResources));
else
AudioManager.playSound(ResourceLoader.getResource('data/sound/opponent_gem_collect.wav', ResourceLoader.getAudio,
@:privateAccess this.level.soundResources));
activeGems.remove(gem);
var beam = gemToBeamMap.get(gem);
beam.setHide(true);
refillGemGroups();
switch (gem.gemColor) {
case "red.gem":
points += 1;
@:privateAccess level.playGui.addMiddleMessage('+1', 0xFF6666);
case "yellow.gem":
points += 2;
@:privateAccess level.playGui.addMiddleMessage('+2', 0xFFFF66);
case "blue.gem":
points += 5;
@:privateAccess level.playGui.addMiddleMessage('+5', 0x6666FF);
if (marble == level.marble) {
switch (gem.gemColor) {
case "red.gem":
points += 1;
@:privateAccess level.playGui.addMiddleMessage('+1', 0xFF6666);
case "yellow.gem":
points += 2;
@:privateAccess level.playGui.addMiddleMessage('+2', 0xFFFF66);
case "blue.gem":
points += 5;
@:privateAccess level.playGui.addMiddleMessage('+5', 0x6666FF);
}
@:privateAccess level.playGui.formatGemHuntCounter(points);
}
@:privateAccess level.playGui.formatGemHuntCounter(points);
}
function setupGems() {

View file

@ -59,7 +59,7 @@ class NullMode implements GameMode {
public function onRespawn(marble:Marble) {}
public function onGemPickup(gem:Gem) {
public function onGemPickup(marble:Marble, gem:Gem) {
this.level.gemCount++;
var string:String;

View file

@ -1,5 +1,6 @@
package net;
import haxe.io.FPHelper;
import haxe.io.BytesOutput;
import haxe.io.BytesInput;
import haxe.io.Bytes;
@ -64,7 +65,7 @@ class InputBitStream {
}
public function readFloat() {
return readInt32();
return FPHelper.i32ToFloat(readInt32());
}
}
@ -129,4 +130,8 @@ class OutputBitStream {
this.data.writeByte(this.lastByte);
return this.data.getBytes();
}
public function writeFloat(value:Float) {
writeInt(FPHelper.floatToI32(value), 32);
}
}

View file

@ -14,6 +14,7 @@ class MarblePrediction {
var omega:Vector;
var isControl:Bool;
var blastAmount:Int;
var powerupItemId:Int;
public function new(marble:Marble, tick:Int) {
this.tick = tick;
@ -22,11 +23,14 @@ class MarblePrediction {
omega = @:privateAccess marble.omega.clone();
blastAmount = @:privateAccess marble.blastTicks;
isControl = @:privateAccess marble.controllable;
powerupItemId = marble.heldPowerup != null ? marble.heldPowerup.netIndex : 0xFFFF;
}
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.powerUpId != powerupItemId)
subs += 1;
// if (isControl)
// subs += Math.abs(blastAmount - p.blastAmount);
return subs;

View file

@ -1,5 +1,7 @@
package net;
import net.BitStream.OutputBitStream;
import net.BitStream.InputBitStream;
import net.NetPacket.MarbleUpdatePacket;
import shapes.PowerUp;
import net.NetPacket.MarbleMovePacket;
@ -23,9 +25,6 @@ class NetMove {
var move:Move;
var id:Int;
var timeState:TimeState;
// For rewind purposes
var powerup:PowerUp;
var powerupStates:Array<Float>;
public function new(move:Move, motionDir:Vector, timeState:TimeState, id:Int) {
this.move = move;
@ -96,17 +95,12 @@ class MoveManager {
}
var netMove = new NetMove(move, motionDir, timeState.clone(), nextMoveId++);
netMove.powerup = marble.heldPowerup;
netMove.powerupStates = [];
for (pw in marble.level.powerUps) {
netMove.powerupStates.push(pw.lastPickUpTime);
}
queuedMoves.push(netMove);
if (nextMoveId >= 65535) // 65535 is reserved for null move
nextMoveId = 0;
var b = new haxe.io.BytesOutput();
var b = new OutputBitStream();
var movePacket = new MarbleMovePacket();
movePacket.clientId = Net.clientId;
movePacket.move = netMove;
@ -119,7 +113,7 @@ class MoveManager {
return netMove;
}
public static inline function packMove(m:NetMove, b:haxe.io.BytesOutput) {
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));
@ -137,7 +131,7 @@ class MoveManager {
return b;
}
public static inline function unpackMove(b:haxe.io.BytesInput) {
public static inline function unpackMove(b:InputBitStream) {
var moveId = b.readUInt16();
var move = new Move();
move.d = new Vector();
@ -155,7 +149,7 @@ class MoveManager {
return netMove;
}
public function queueMove(m:NetMove) {
public inline function queueMove(m:NetMove) {
queuedMoves.push(m);
}
@ -169,7 +163,7 @@ class MoveManager {
}
}
public function getQueueSize() {
public inline function getQueueSize() {
return queuedMoves.length;
}

View file

@ -1,5 +1,8 @@
package net;
import net.BitStream.InputBitStream;
import net.BitStream.OutputBitStream;
import net.NetPacket.PowerupPickupPacket;
import net.ClientConnection;
import net.NetPacket.MarbleUpdatePacket;
import net.NetPacket.MarbleMovePacket;
@ -20,6 +23,7 @@ enum abstract NetPacketType(Int) from Int to Int {
var PingBack;
var MarbleUpdate;
var MarbleMove;
var PowerupPickup;
var PlayerInfo;
}
@ -141,7 +145,7 @@ class Net {
haxe.Timer.delay(() -> connectedCb(), 1500); // 1.5 second delay to do the RTT calculation
}
clientDatachannel.onMessage = (b) -> {
onPacketReceived(client, clientDatachannel, new haxe.io.BytesInput(b));
onPacketReceived(client, clientDatachannel, new InputBitStream(b));
}
isMP = true;
@ -155,7 +159,7 @@ class Net {
clients.set(c, new ClientConnection(clientId, c, dc));
clientIdMap[clientId] = clients[c];
dc.onMessage = (msgBytes) -> {
onPacketReceived(c, dc, new haxe.io.BytesInput(msgBytes));
onPacketReceived(c, dc, new InputBitStream(msgBytes));
}
var b = haxe.io.Bytes.alloc(3);
b.set(0, ClientIdAssign);
@ -194,7 +198,7 @@ class Net {
return b.getBytes();
}
static function onPacketReceived(c:RTCPeerConnection, dc:RTCDataChannel, input:haxe.io.BytesInput) {
static function onPacketReceived(c:RTCPeerConnection, dc:RTCDataChannel, input:InputBitStream) {
var packetType = input.readByte();
switch (packetType) {
case NetCommand:
@ -250,6 +254,14 @@ class Net {
var cc = clientIdMap[movePacket.clientId];
cc.moveManager.queueMove(movePacket.move);
case PowerupPickup:
var powerupPickupPacket = new PowerupPickupPacket();
powerupPickupPacket.deserialize(input);
if (MarbleGame.instance.world != null) {
var m = @:privateAccess MarbleGame.instance.world.powerupPredictions;
m.acknowledgePowerupPickup(powerupPickupPacket, MarbleGame.instance.world.timeState, clientConnection.moveManager.getQueueSize());
}
case PlayerInfo:
var count = input.readByte();
for (i in 0...count) {
@ -265,14 +277,14 @@ class Net {
}
}
public static function sendPacketToAll(packetData:haxe.io.BytesOutput) {
public static function sendPacketToAll(packetData:OutputBitStream) {
var bytes = packetData.getBytes();
for (c => v in clients) {
v.sendBytes(bytes);
}
}
public static function sendPacketToHost(packetData:haxe.io.BytesOutput) {
public static function sendPacketToHost(packetData:OutputBitStream) {
var bytes = packetData.getBytes();
clientDatachannel.sendBytes(bytes);
}

View file

@ -1,11 +1,13 @@
package net;
import net.BitStream.InputBitStream;
import net.BitStream.OutputBitStream;
import h3d.Vector;
import net.MoveManager.NetMove;
interface NetPacket {
public function serialize(b:haxe.io.BytesOutput):Void;
public function deserialize(b:haxe.io.BytesInput):Void;
public function serialize(b:OutputBitStream):Void;
public function deserialize(b:InputBitStream):Void;
}
@:publicFields
@ -16,13 +18,13 @@ class MarbleMovePacket implements NetPacket {
public function new() {}
public inline function deserialize(b:haxe.io.BytesInput) {
public inline function deserialize(b:InputBitStream) {
clientId = b.readUInt16();
clientTicks = b.readUInt16();
move = MoveManager.unpackMove(b);
}
public inline function serialize(b:haxe.io.BytesOutput) {
public inline function serialize(b:OutputBitStream) {
b.writeUInt16(clientId);
b.writeUInt16(clientTicks);
MoveManager.packMove(move, b);
@ -43,11 +45,12 @@ class MarbleUpdatePacket implements NetPacket {
var megaTick:Int;
var heliTick:Int;
var oob:Bool;
var powerUpId:Int;
var moveQueueSize:Int;
public function new() {}
public inline function serialize(b:haxe.io.BytesOutput) {
public inline function serialize(b:OutputBitStream) {
b.writeUInt16(clientId);
MoveManager.packMove(move, b);
b.writeUInt16(serverTicks);
@ -66,9 +69,10 @@ class MarbleUpdatePacket implements NetPacket {
b.writeUInt16(heliTick);
b.writeUInt16(megaTick);
b.writeByte(oob ? 1 : 0);
b.writeUInt16(powerUpId);
}
public inline function deserialize(b:haxe.io.BytesInput) {
public inline function deserialize(b:InputBitStream) {
clientId = b.readUInt16();
move = MoveManager.unpackMove(b);
serverTicks = b.readUInt16();
@ -81,5 +85,27 @@ class MarbleUpdatePacket implements NetPacket {
heliTick = b.readUInt16();
megaTick = b.readUInt16();
oob = b.readByte() != 0;
powerUpId = b.readUInt16();
}
}
@: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.readUInt16();
serverTicks = b.readUInt16();
powerupItemId = b.readUInt16();
}
public inline function serialize(b:OutputBitStream) {
b.writeUInt16(clientId);
b.writeUInt16(serverTicks);
b.writeUInt16(powerupItemId);
}
}

View file

@ -0,0 +1,24 @@
package net;
import src.TimeState;
import net.NetPacket.PowerupPickupPacket;
class PowerupPredictionStore {
var predictions:Array<Float>;
public function new() {
predictions = [];
}
public function alloc() {
predictions.push(Math.NEGATIVE_INFINITY);
}
public inline function getState(netIndex:Int) {
return predictions[netIndex];
}
public function acknowledgePowerupPickup(packet:PowerupPickupPacket, timeState:TimeState, futureTicks:Int) {
predictions[packet.powerupItemId] = timeState.currentAttemptTime - futureTicks * 0.032; // Approximate
}
}

View file

@ -59,7 +59,7 @@ class RPCMacro {
case EConst(CIdent("server")):
var lastExpr = macro {
if (Net.isHost) {
var stream = new haxe.io.BytesOutput();
var stream = new net.BitStream.OutputBitStream();
stream.writeByte(NetPacketType.NetCommand);
stream.writeByte($v{rpcFnId});
$b{serializeFns};
@ -72,7 +72,7 @@ class RPCMacro {
case EConst(CIdent("client")):
var lastExpr = macro {
if (!Net.isHost) {
var stream = new haxe.io.BytesOutput();
var stream = new net.BitStream.OutputBitStream();
stream.writeByte(NetPacketType.NetCommand);
stream.writeByte($v{rpcFnId});
$b{serializeFns};
@ -113,7 +113,7 @@ class RPCMacro {
args: [
{
name: "stream",
type: haxe.macro.TypeTools.toComplexType(Context.getType('haxe.io.Input'))
type: haxe.macro.TypeTools.toComplexType(Context.getType('net.BitStream.InputBitStream'))
}
],
expr: macro {

View file

@ -67,7 +67,7 @@ class Gem extends DtsObject {
return;
this.pickedUp = true;
this.setOpacity(0); // Hide the gem
this.level.pickUpGem(this);
this.level.pickUpGem(marble, this);
// this.level.replay.recordMarbleInside(this);
}

View file

@ -1,5 +1,8 @@
package shapes;
import net.BitStream.OutputBitStream;
import net.NetPacket.PowerupPickupPacket;
import net.Net;
import src.Marble;
import src.AudioManager;
import hxd.res.Sound;
@ -16,6 +19,7 @@ abstract class PowerUp extends DtsObject {
public var pickUpName:String;
public var element:MissionElementItem;
public var pickupSound:Sound;
public var netIndex:Int;
var customPickupMessage:String = null;
@ -35,6 +39,17 @@ 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.sendPacketToAll(b);
}
this.lastPickUpTime = timeState.currentAttemptTime;
if (this.autoUse)
this.use(marble, timeState);