From bd8527bae6b5755586544c7e2c52d8559e4c2809 Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Fri, 26 Jan 2024 21:57:36 +0530 Subject: [PATCH] refactor a lot to get powerups sorta working --- src/GameObject.hx | 9 +-- src/Marble.hx | 60 +++++------------ src/MarbleWorld.hx | 103 +++++++++++++++++++++++------ src/TimeState.hx | 2 + src/collision/CollisionEntity.hx | 2 +- src/collision/CollisionHull.hx | 2 +- src/mesh/Polygon.hx | 13 ++-- src/net/MoveManager.hx | 16 +++-- src/net/Net.hx | 29 ++++---- src/net/NetPacket.hx | 67 +++++++++++++++++++ src/rewind/RewindManager.hx | 6 +- src/shapes/AbstractBumper.hx | 5 +- src/shapes/AntiGravity.hx | 5 +- src/shapes/Blast.hx | 5 +- src/shapes/EasterEgg.hx | 5 +- src/shapes/Gem.hx | 5 +- src/shapes/Helicopter.hx | 10 +-- src/shapes/MegaMarble.hx | 11 +-- src/shapes/PowerUp.hx | 29 ++++---- src/shapes/SuperJump.hx | 13 ++-- src/shapes/SuperSpeed.hx | 13 ++-- src/shapes/TimeTravel.hx | 5 +- src/shapes/Trapdoor.hx | 5 +- src/triggers/CheckpointTrigger.hx | 5 +- src/triggers/HelpTrigger.hx | 3 +- src/triggers/InBoundsTrigger.hx | 3 +- src/triggers/MustChangeTrigger.hx | 3 +- src/triggers/OutOfBoundsTrigger.hx | 3 +- 28 files changed, 286 insertions(+), 151 deletions(-) create mode 100644 src/net/NetPacket.hx diff --git a/src/GameObject.hx b/src/GameObject.hx index 8b8285fb..3eee4ab2 100644 --- a/src/GameObject.hx +++ b/src/GameObject.hx @@ -6,6 +6,7 @@ import h3d.scene.Object; import src.Resource; import h3d.mat.Texture; import hxd.res.Sound; +import src.Marble; class GameObject extends Object { public var identifier:String; @@ -22,13 +23,13 @@ class GameObject extends Object { return currentOpacity; } - public function onMarbleContact(time:TimeState, ?contact:CollisionInfo) {} + public function onMarbleContact(marble:Marble, time:TimeState, ?contact:CollisionInfo) {} - public function onMarbleInside(time:TimeState) {} + public function onMarbleInside(marble:Marble, time:TimeState) {} - public function onMarbleEnter(time:TimeState) {} + public function onMarbleEnter(marble:Marble, time:TimeState) {} - public function onMarbleLeave(time:TimeState) {} + public function onMarbleLeave(marble:Marble, time:TimeState) {} public function onLevelStart() {} diff --git a/src/Marble.hx b/src/Marble.hx index 71fff3de..ef6cb056 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1,5 +1,6 @@ package src; +import net.NetPacket.MarbleUpdatePacket; import net.MoveManager; import net.MoveManager.NetMove; import shaders.marble.CrystalMarb; @@ -1573,7 +1574,7 @@ class Marble extends GameObject { var pTime = timeState.clone(); pTime.dt = timeStep; pTime.currentAttemptTime = passedTime; - this.heldPowerup.use(pTime); + this.heldPowerup.use(this, pTime); this.heldPowerup = null; if (this.level.isRecording) { this.level.replay.recordPowerupPickup(null); @@ -1608,7 +1609,7 @@ class Marble extends GameObject { newPos = this.collider.transform.getPosition(); - if (this.controllable && this.prevPos != null) { + if (this.prevPos != null) { var tempTimeState = timeState.clone(); tempTimeState.currentAttemptTime = passedTime; this.level.callCollisionHandlers(cast this, tempTimeState, oldPos, newPos); @@ -1622,55 +1623,24 @@ class Marble extends GameObject { public function packUpdate(move:NetMove) { var b = new haxe.io.BytesOutput(); b.writeByte(NetPacketType.MarbleUpdate); - b.writeUInt16(connection != null ? connection.id : 0); - MoveManager.packMove(move, b); - b.writeUInt16(this.level.ticks); // So we can get the clients to do stuff about it - b.writeFloat(this.newPos.x); - b.writeFloat(this.newPos.y); - b.writeFloat(this.newPos.z); - b.writeFloat(this.velocity.x); - b.writeFloat(this.velocity.y); - b.writeFloat(this.velocity.z); - b.writeFloat(this.omega.x); - b.writeFloat(this.omega.y); - b.writeFloat(this.omega.z); + var marbleUpdate = new MarbleUpdatePacket(); + marbleUpdate.clientId = connection != null ? connection.id : 0; + marbleUpdate.serverTicks = move.timeState.ticks; + marbleUpdate.position = this.newPos; + marbleUpdate.velocity = this.velocity; + marbleUpdate.omega = this.omega; + marbleUpdate.move = move; + marbleUpdate.serialize(b); return b.getBytes(); } - public function unpackUpdate(b:haxe.io.BytesInput) { + public function unpackUpdate(p:MarbleUpdatePacket) { // Assume packet header is already read - var serverMove = MoveManager.unpackMove(b); - if (Net.isClient) - Net.clientConnection.moveManager.acknowledgeMove(serverMove.id); - var serverTicks = b.readUInt16(); this.oldPos = this.newPos; - this.newPos = new Vector(b.readFloat(), b.readFloat(), b.readFloat()); + this.newPos = p.position; this.collider.transform.setPosition(this.newPos); - this.velocity = new Vector(b.readFloat(), b.readFloat(), b.readFloat()); - this.omega = new Vector(b.readFloat(), b.readFloat(), b.readFloat()); - - // Apply the moves we have queued - if (Net.isClient) { - this.isNetUpdate = true; - if (this.controllable) { - for (move in @:privateAccess Net.clientConnection.moveManager.queuedMoves) { - moveMotionDir = move.motionDir; - advancePhysics(move.timeState, move.move, this.level.collisionWorld, this.level.pathedInteriors); - } - } else { - var tickDiff = this.level.ticks - serverTicks; - if (tickDiff > 0) { - var timeState = this.level.timeState.clone(); - timeState.dt = 0.032; - var m = serverMove.move; - moveMotionDir = serverMove.motionDir; - for (o in 0...tickDiff) { - advancePhysics(timeState, m, this.level.collisionWorld, this.level.pathedInteriors); - } - } - } - this.isNetUpdate = false; - } + this.velocity = p.velocity; + this.omega = p.omega; } public function updateServer(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array, packets:Array) { diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 2dda41f4..08620b7c 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1,5 +1,7 @@ package src; +import net.NetPacket.MarbleUpdatePacket; +import net.NetPacket.MarbleMovePacket; import net.MoveManager; import net.NetCommands; import net.Net; @@ -196,12 +198,14 @@ class MarbleWorld extends Scheduler { public var startRealTime:Float = 0; public var multiplayerStarted:Bool = false; - public var ticks:Int = 0; // How many 32ms ticks have happened var tickAccumulator:Float = 0.0; + var maxPredictionTicks:Int = 16; var clientMarbles:Map = []; + public var lastMoves:Map = []; + // Loading var resourceLoadFuncs:Array<(() -> Void)->Void> = []; @@ -588,7 +592,7 @@ class MarbleWorld extends Scheduler { interior.reset(); this.setUp(startquat.up, this.timeState, true); - this.deselectPowerUp(); + this.deselectPowerUp(this.marble); playGui.setCenterText(''); AudioManager.playSound(ResourceLoader.getResource('data/sound/spawn_alternate.wav', ResourceLoader.getAudio, this.soundResources)); @@ -1020,6 +1024,53 @@ class MarbleWorld extends Scheduler { } } + public function applyReceivedMoves() { + for (client => lastMove in lastMoves) { + if (lastMove.applied) + continue; + if (lastMove.clientId == Net.clientId) + marble.unpackUpdate(lastMove); + else + clientMarbles[Net.clientIdMap[client]].unpackUpdate(lastMove); + } + } + + public function applyClientPrediction() { + for (client => lastMove in lastMoves) { + if (lastMove.applied) + continue; + var marbleToUpdate = lastMove.clientId == Net.clientId ? marble : clientMarbles[Net.clientIdMap[client]]; + + @:privateAccess marbleToUpdate.isNetUpdate = true; + if (marbleToUpdate == marble) { + var moveManager = @:privateAccess Net.clientConnection.moveManager; + var catchUpTickCount = 0; + Net.clientConnection.moveManager.acknowledgeMove(lastMove.move.id); + var advanceTimeState = timeState.clone(); + advanceTimeState.dt = 0.032; + for (move in @:privateAccess moveManager.queuedMoves) { + @:privateAccess marbleToUpdate.moveMotionDir = move.motionDir; + @:privateAccess marbleToUpdate.advancePhysics(advanceTimeState, move.move, this.collisionWorld, this.pathedInteriors); + } + } else { + var tickDiff = timeState.ticks - lastMove.serverTicks; + if (tickDiff > this.maxPredictionTicks) + tickDiff = this.maxPredictionTicks; + if (tickDiff > 0) { + var advanceTimeState = timeState.clone(); + advanceTimeState.dt = 0.032; + var m = lastMove.move.move; + @:privateAccess marbleToUpdate.moveMotionDir = lastMove.move.motionDir; + for (o in 0...(tickDiff + 1)) { + @:privateAccess marbleToUpdate.advancePhysics(advanceTimeState, m, this.collisionWorld, this.pathedInteriors); + } + } + } + @:privateAccess marbleToUpdate.isNetUpdate = false; + lastMove.applied = true; + } + } + public function rollback(t:Float) { var newT = timeState.currentAttemptTime - t; var rewindFrame = rewindManager.getNextRewindFrame(timeState.currentAttemptTime - t); @@ -1216,6 +1267,14 @@ class MarbleWorld extends Scheduler { if (this.isMultiplayer) { tickAccumulator += timeState.dt; while (tickAccumulator >= 0.032) { + // Apply the server side ticks + if (Net.isClient) { + applyReceivedMoves(); + // Catch up + applyClientPrediction(); + } + + // Do the clientside prediction sim var fixedDt = timeState.clone(); fixedDt.dt = 0.032; tickAccumulator -= 0.032; @@ -1231,7 +1290,7 @@ class MarbleWorld extends Scheduler { } } } - ticks++; + timeState.ticks++; } marble.updateClient(timeState, this.pathedInteriors); for (client => marble in clientMarbles) { @@ -1491,10 +1550,10 @@ class MarbleWorld extends Scheduler { var shape:DtsObject = cast contact.go; if (contact.boundingBox.collide(box)) { - shape.onMarbleInside(timeState); + shape.onMarbleInside(marble, timeState); if (!this.shapeOrTriggerInside.contains(contact.go)) { this.shapeOrTriggerInside.push(contact.go); - shape.onMarbleEnter(timeState); + shape.onMarbleEnter(marble, timeState); } inside.push(contact.go); } @@ -1504,10 +1563,10 @@ class MarbleWorld extends Scheduler { var triggeraabb = trigger.collider.boundingBox; if (triggeraabb.collide(box)) { - trigger.onMarbleInside(timeState); + trigger.onMarbleInside(marble, timeState); if (!this.shapeOrTriggerInside.contains(contact.go)) { this.shapeOrTriggerInside.push(contact.go); - trigger.onMarbleEnter(timeState); + trigger.onMarbleEnter(marble, timeState); } inside.push(contact.go); } @@ -1518,7 +1577,7 @@ class MarbleWorld extends Scheduler { for (object in shapeOrTriggerInside) { if (!inside.contains(object)) { this.shapeOrTriggerInside.remove(object); - object.onMarbleLeave(timeState); + object.onMarbleLeave(marble, timeState); } } @@ -1726,26 +1785,30 @@ class MarbleWorld extends Scheduler { return true; } - public function pickUpPowerUp(powerUp:PowerUp) { + public function pickUpPowerUp(marble:Marble, powerUp:PowerUp) { if (powerUp == null) return false; - if (this.marble.heldPowerup != null) - if (this.marble.heldPowerup.identifier == powerUp.identifier) + if (marble.heldPowerup != null) + if (marble.heldPowerup.identifier == powerUp.identifier) return false; Console.log("PowerUp pickup: " + powerUp.identifier); - this.marble.heldPowerup = powerUp; - this.playGui.setPowerupImage(powerUp.identifier); - MarbleGame.instance.touchInput.powerupButton.setEnabled(true); + marble.heldPowerup = powerUp; + if (this.marble == marble) { + this.playGui.setPowerupImage(powerUp.identifier); + MarbleGame.instance.touchInput.powerupButton.setEnabled(true); + } if (this.isRecording) { this.replay.recordPowerupPickup(powerUp); } return true; } - public function deselectPowerUp() { - this.marble.heldPowerup = null; - this.playGui.setPowerupImage(""); - MarbleGame.instance.touchInput.powerupButton.setEnabled(false); + public function deselectPowerUp(marble:Marble) { + marble.heldPowerup = null; + if (this.marble == marble) { + this.playGui.setPowerupImage(""); + MarbleGame.instance.touchInput.powerupButton.setEnabled(false); + } } public function addBonusTime(t:Float) { @@ -1929,11 +1992,11 @@ class MarbleWorld extends Scheduler { this.playGui.setCenterText(''); this.clearSchedule(); this.outOfBounds = false; - this.deselectPowerUp(); // Always deselect first + this.deselectPowerUp(this.marble); // Always deselect first // Wait a bit to select the powerup to prevent immediately using it incase the user skipped the OOB screen by clicking if (this.checkpointHeldPowerup != null) { var powerup = this.checkpointHeldPowerup; - this.pickUpPowerUp(powerup); + this.pickUpPowerUp(this.marble, powerup); } AudioManager.playSound(ResourceLoader.getResource('data/sound/spawn_alternate.wav', ResourceLoader.getAudio, this.soundResources)); } diff --git a/src/TimeState.hx b/src/TimeState.hx index a90f9af0..8fbe3df8 100644 --- a/src/TimeState.hx +++ b/src/TimeState.hx @@ -6,6 +6,7 @@ class TimeState { var currentAttemptTime:Float; var gameplayClock:Float; var dt:Float; + var ticks:Int; // How many 32ms ticks have happened public function new() {} @@ -15,6 +16,7 @@ class TimeState { n.currentAttemptTime = this.currentAttemptTime; n.gameplayClock = this.gameplayClock; n.dt = this.dt; + n.ticks = ticks; return n; } } diff --git a/src/collision/CollisionEntity.hx b/src/collision/CollisionEntity.hx index 24c59d36..f2e12c0d 100644 --- a/src/collision/CollisionEntity.hx +++ b/src/collision/CollisionEntity.hx @@ -247,7 +247,7 @@ class CollisionEntity implements IOctreeObject implements IBVHObject { cinfo.force = surface.force; cinfo.friction = surface.friction; contacts.push(cinfo); - this.go.onMarbleContact(timeState, cinfo); + this.go.onMarbleContact(collisionEntity.marble, timeState, cinfo); // surfaceBestContact = cinfo; // } } diff --git a/src/collision/CollisionHull.hx b/src/collision/CollisionHull.hx index 27533661..2e9f59e2 100644 --- a/src/collision/CollisionHull.hx +++ b/src/collision/CollisionHull.hx @@ -49,7 +49,7 @@ class CollisionHull extends CollisionEntity { cinfo.otherObject = this.go; cinfo.friction = friction; cinfo.force = force; - this.go.onMarbleContact(timeState, cinfo); + this.go.onMarbleContact(collisionEntity.marble, timeState, cinfo); return [cinfo]; } } diff --git a/src/mesh/Polygon.hx b/src/mesh/Polygon.hx index 2c4d7609..c6e3d507 100644 --- a/src/mesh/Polygon.hx +++ b/src/mesh/Polygon.hx @@ -12,6 +12,8 @@ class Polygon extends MeshPrimitive { public var uvs:Array; public var idx:hxd.IndexBuffer; + var bounds:h3d.col.Bounds; + var scaled = 1.; var translatedX = 0.; var translatedY = 0.; @@ -23,10 +25,13 @@ class Polygon extends MeshPrimitive { } override function getBounds() { - var b = new h3d.col.Bounds(); - for (p in points) - b.addPoint(p); - return b; + if (bounds == null) { + var b = new h3d.col.Bounds(); + for (p in points) + b.addPoint(p); + bounds = b; + } + return bounds; } override function alloc(engine:h3d.Engine) { diff --git a/src/net/MoveManager.hx b/src/net/MoveManager.hx index 673421b3..2abb3051 100644 --- a/src/net/MoveManager.hx +++ b/src/net/MoveManager.hx @@ -1,5 +1,6 @@ package net; +import net.NetPacket.MarbleMovePacket; import src.TimeState; import src.Console; import net.Net.ClientConnection; @@ -35,7 +36,7 @@ class MoveManager { var lastMove:NetMove; var lastAckMoveId:Int = -1; - static var maxMoves = 45; // Taken from Torque + static var maxMoves = 16; public function new(connection:ClientConnection) { queuedMoves = []; @@ -84,15 +85,19 @@ class MoveManager { nextMoveId = 0; var b = new haxe.io.BytesOutput(); + var movePacket = new MarbleMovePacket(); + movePacket.clientId = Net.clientId; + movePacket.move = netMove; + movePacket.clientTicks = timeState.ticks; b.writeByte(NetPacketType.MarbleMove); - b.writeUInt16(Net.clientId); + movePacket.serialize(b); - Net.sendPacketToHost(packMove(netMove, b)); + Net.sendPacketToHost(b); return netMove; } - public static function packMove(m:NetMove, b:haxe.io.BytesOutput) { + public static inline function packMove(m:NetMove, b:haxe.io.BytesOutput) { b.writeUInt16(m.id); b.writeFloat(m.move.d.x); b.writeFloat(m.move.d.y); @@ -108,7 +113,7 @@ class MoveManager { return b; } - public static function unpackMove(b:haxe.io.BytesInput) { + public static inline function unpackMove(b:haxe.io.BytesInput) { var moveId = b.readUInt16(); var move = new Move(); move.d = new Vector(); @@ -147,7 +152,6 @@ class MoveManager { if (queuedMoves.length == 0) return; while (m != queuedMoves[0].id) { - trace('Ignoring move ${queuedMoves[0].id}, need ${m}'); queuedMoves.shift(); } if (m == queuedMoves[0].id) diff --git a/src/net/Net.hx b/src/net/Net.hx index 762f5319..ad1bb4cd 100644 --- a/src/net/Net.hx +++ b/src/net/Net.hx @@ -1,5 +1,7 @@ package net; +import net.NetPacket.MarbleUpdatePacket; +import net.NetPacket.MarbleMovePacket; import haxe.Json; import datachannel.RTCPeerConnection; import datachannel.RTCDataChannel; @@ -241,21 +243,24 @@ class Net { } case MarbleUpdate: - var marbleClientId = input.readUInt16(); - if (marbleClientId == clientId) { - if (MarbleGame.instance.world != null) - MarbleGame.instance.world.marble.unpackUpdate(input); - } else { - var cc = clientIdMap[marbleClientId]; - if (MarbleGame.instance.world != null) - @:privateAccess MarbleGame.instance.world.clientMarbles[cc].unpackUpdate(input); + var marbleUpdatePacket = new MarbleUpdatePacket(); + marbleUpdatePacket.deserialize(input); + var cc = marbleUpdatePacket.clientId; + if (MarbleGame.instance.world != null) { + var m = MarbleGame.instance.world.lastMoves; + if (m.exists(cc)) { + if (m[cc].serverTicks < marbleUpdatePacket.serverTicks) + m.set(cc, marbleUpdatePacket); + } else { + m.set(cc, marbleUpdatePacket); + } } case MarbleMove: - var marbleClientId = input.readUInt16(); - var cc = clientIdMap[marbleClientId]; - var m = MoveManager.unpackMove(input); - cc.moveManager.queueMove(m); + var movePacket = new MarbleMovePacket(); + movePacket.deserialize(input); + var cc = clientIdMap[movePacket.clientId]; + cc.moveManager.queueMove(movePacket.move); case _: trace("unknown command: " + packetType); diff --git a/src/net/NetPacket.hx b/src/net/NetPacket.hx new file mode 100644 index 00000000..0106fd19 --- /dev/null +++ b/src/net/NetPacket.hx @@ -0,0 +1,67 @@ +package net; + +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; +} + +@:publicFields +class MarbleMovePacket implements NetPacket { + var clientId:Int; + var clientTicks:Int; + var move:NetMove; + + public function new() {} + + public inline function deserialize(b:haxe.io.BytesInput) { + clientId = b.readUInt16(); + clientTicks = b.readUInt16(); + move = MoveManager.unpackMove(b); + } + + public inline function serialize(b:haxe.io.BytesOutput) { + b.writeUInt16(clientId); + b.writeUInt16(clientTicks); + MoveManager.packMove(move, b); + } +} + +@:publicFields +class MarbleUpdatePacket implements NetPacket { + var clientId:Int; + var move:NetMove; + var serverTicks:Int; + var position:Vector; + var velocity:Vector; + var omega:Vector; + var applied:Bool = false; + + public function new() {} + + public inline function serialize(b:haxe.io.BytesOutput) { + b.writeUInt16(clientId); + MoveManager.packMove(move, b); + b.writeUInt16(serverTicks); + 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); + } + + public inline function deserialize(b:haxe.io.BytesInput) { + clientId = b.readUInt16(); + move = MoveManager.unpackMove(b); + serverTicks = b.readUInt16(); + 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()); + } +} diff --git a/src/rewind/RewindManager.hx b/src/rewind/RewindManager.hx index a102b923..1c289fc2 100644 --- a/src/rewind/RewindManager.hx +++ b/src/rewind/RewindManager.hx @@ -128,13 +128,13 @@ class RewindManager { if (level.marble.heldPowerup == null) { if (rf.marblePowerup != null) { - level.pickUpPowerUp(rf.marblePowerup); + level.pickUpPowerUp(level.marble, rf.marblePowerup); } } else { if (rf.marblePowerup == null) { - level.deselectPowerUp(); + level.deselectPowerUp(level.marble); } else { - level.pickUpPowerUp(rf.marblePowerup); + level.pickUpPowerUp(level.marble, rf.marblePowerup); } } diff --git a/src/shapes/AbstractBumper.hx b/src/shapes/AbstractBumper.hx index 01b0c69b..58023597 100644 --- a/src/shapes/AbstractBumper.hx +++ b/src/shapes/AbstractBumper.hx @@ -4,6 +4,7 @@ import collision.CollisionInfo; import src.DtsObject; import src.TimeState; import src.Util; +import src.Marble; class AbstractBumper extends DtsObject { var lastContactTime = Math.NEGATIVE_INFINITY; @@ -27,8 +28,8 @@ class AbstractBumper extends DtsObject { return completion; } - override function onMarbleContact(time:TimeState, ?contact:CollisionInfo) { - super.onMarbleContact(time, contact); + override function onMarbleContact(marble:Marble, time:TimeState, ?contact:CollisionInfo) { + super.onMarbleContact(marble, time, contact); if (time.timeSinceLoad - this.lastContactTime <= 0) return; var currentCompletion = this.getCurrentCompletion(time); diff --git a/src/shapes/AntiGravity.hx b/src/shapes/AntiGravity.hx index 87e56a4e..8beb0f13 100644 --- a/src/shapes/AntiGravity.hx +++ b/src/shapes/AntiGravity.hx @@ -1,5 +1,6 @@ package shapes; +import src.Marble; import mis.MisParser; import dts.DtsFile; import src.ResourceLoader; @@ -26,13 +27,13 @@ class AntiGravity extends PowerUp { this.cooldownDuration = Math.NEGATIVE_INFINITY; } - public function pickUp():Bool { + public function pickUp(marble:Marble):Bool { var direction = new Vector(0, 0, -1); direction.transform(this.getRotationQuat().toMatrix()); return !direction.equals(this.level.currentUp); } - public function use(timeState:TimeState) { + public function use(marble:Marble, timeState:TimeState) { if (!this.level.rewinding) { var direction = new Vector(0, 0, -1); direction.transform(this.getRotationQuat().toMatrix()); diff --git a/src/shapes/Blast.hx b/src/shapes/Blast.hx index 56d65a79..7c05a5ab 100644 --- a/src/shapes/Blast.hx +++ b/src/shapes/Blast.hx @@ -1,5 +1,6 @@ package shapes; +import src.Marble; import src.MarbleWorld; import src.ResourceLoader; import src.TimeState; @@ -27,11 +28,11 @@ class Blast extends PowerUp { }); } - public function pickUp():Bool { + public function pickUp(marble:Marble):Bool { return true; } - public function use(timeState:TimeState) { + public function use(marble:Marble, timeState:TimeState) { this.level.blastAmount = 1.2; } diff --git a/src/shapes/EasterEgg.hx b/src/shapes/EasterEgg.hx index 86bae6fe..ef3f6067 100644 --- a/src/shapes/EasterEgg.hx +++ b/src/shapes/EasterEgg.hx @@ -1,5 +1,6 @@ package shapes; +import src.Marble; import gui.AchievementsGui; import src.Settings; import mis.MissionElement.MissionElementItem; @@ -16,7 +17,7 @@ class EasterEgg extends PowerUp { this.autoUse = true; } - public function pickUp():Bool { + public function pickUp(marble:Marble):Bool { var found:Bool = false; if (Settings.easterEggs.exists(this.level.mission.path)) { found = true; @@ -42,7 +43,7 @@ class EasterEgg extends PowerUp { }); } - public function use(timeState:src.TimeState) {} + public function use(marble:Marble, timeState:src.TimeState) {} override function getPreloadMaterials(dts:dts.DtsFile) { var mats = super.getPreloadMaterials(dts); diff --git a/src/shapes/Gem.hx b/src/shapes/Gem.hx index 5ed7d9ad..ddee676e 100644 --- a/src/shapes/Gem.hx +++ b/src/shapes/Gem.hx @@ -7,6 +7,7 @@ import src.TimeState; import src.DtsObject; import src.ResourceLoaderWorker; import src.ResourceLoader; +import src.Marble; class Gem extends DtsObject { public var pickedUp:Bool; @@ -60,8 +61,8 @@ class Gem extends DtsObject { } } - override function onMarbleInside(timeState:TimeState) { - super.onMarbleInside(timeState); + override function onMarbleInside(marble:Marble, timeState:TimeState) { + super.onMarbleInside(marble, timeState); if (this.pickedUp || this.level.rewinding) return; this.pickedUp = true; diff --git a/src/shapes/Helicopter.hx b/src/shapes/Helicopter.hx index a6bd5297..bbd7c19f 100644 --- a/src/shapes/Helicopter.hx +++ b/src/shapes/Helicopter.hx @@ -1,5 +1,6 @@ package shapes; +import src.Marble; import h3d.mat.Material; import src.ResourceLoader; import mis.MissionElement.MissionElementItem; @@ -28,14 +29,13 @@ class Helicopter extends PowerUp { }); } - public function pickUp():Bool { - return this.level.pickUpPowerUp(this); + public function pickUp(marble:Marble):Bool { + return this.level.pickUpPowerUp(marble, this); } - public function use(timeState:TimeState) { - var marble = this.level.marble; + public function use(marble:Marble, timeState:TimeState) { marble.enableHelicopter(timeState.currentAttemptTime); - this.level.deselectPowerUp(); + this.level.deselectPowerUp(marble); } override function postProcessMaterial(matName:String, material:Material) { diff --git a/src/shapes/MegaMarble.hx b/src/shapes/MegaMarble.hx index b96c09bb..01eeb8a6 100644 --- a/src/shapes/MegaMarble.hx +++ b/src/shapes/MegaMarble.hx @@ -6,6 +6,7 @@ import src.ResourceLoader; import src.TimeState; import mis.MissionElement.MissionElementItem; import src.AudioManager; +import src.Marble; class MegaMarble extends PowerUp { public function new(element:MissionElementItem) { @@ -35,13 +36,13 @@ class MegaMarble extends PowerUp { }); } - public function pickUp():Bool { - return this.level.pickUpPowerUp(this); + public function pickUp(marble:Marble):Bool { + return this.level.pickUpPowerUp(marble, this); } - public function use(timeState:TimeState) { - this.level.marble.enableMegaMarble(timeState.currentAttemptTime); - this.level.deselectPowerUp(); + public function use(marble:Marble, timeState:TimeState) { + marble.enableMegaMarble(timeState.currentAttemptTime); + this.level.deselectPowerUp(marble); AudioManager.playSound(ResourceLoader.getResource('data/sound/use_mega.wav', ResourceLoader.getAudio, this.soundResources)); } diff --git a/src/shapes/PowerUp.hx b/src/shapes/PowerUp.hx index d61b48a9..373b7325 100644 --- a/src/shapes/PowerUp.hx +++ b/src/shapes/PowerUp.hx @@ -1,5 +1,6 @@ package shapes; +import src.Marble; import src.AudioManager; import hxd.res.Sound; import mis.MissionElement.MissionElementItem; @@ -26,27 +27,29 @@ abstract class PowerUp extends DtsObject { this.element = element; } - public override function onMarbleInside(timeState:TimeState) { + public override function onMarbleInside(marble:Marble, timeState:TimeState) { var pickupable = this.lastPickUpTime == -1 || (timeState.currentAttemptTime - this.lastPickUpTime) >= this.cooldownDuration; if (!pickupable) return; - if (this.pickUp()) { + if (this.pickUp(marble)) { // this.level.replay.recordMarbleInside(this); this.lastPickUpTime = timeState.currentAttemptTime; if (this.autoUse) - this.use(timeState); + this.use(marble, timeState); - if (customPickupMessage != null) - this.level.displayAlert(customPickupMessage); - else - this.level.displayAlert('You picked up ${this.pickUpName}!'); - if (this.element.showhelponpickup == "1" && !this.autoUse) - this.level.displayHelp('Press to use the ${this.pickUpName}!'); + if (level.marble == marble) { + if (customPickupMessage != null) + this.level.displayAlert(customPickupMessage); + else + this.level.displayAlert('You picked up ${this.pickUpName}!'); + if (this.element.showhelponpickup == "1" && !this.autoUse) + this.level.displayHelp('Press to use the ${this.pickUpName}!'); - if (pickupSound != null && !this.level.rewinding) { - AudioManager.playSound(pickupSound); + if (pickupSound != null && !this.level.rewinding) { + AudioManager.playSound(pickupSound); + } } } } @@ -62,9 +65,9 @@ abstract class PowerUp extends DtsObject { this.setOpacity(opacity); } - public abstract function pickUp():Bool; + public abstract function pickUp(marble:Marble):Bool; - public abstract function use(timeState:TimeState):Void; + public abstract function use(marble:Marble, timeState:TimeState):Void; public override function reset() { this.lastPickUpTime = Math.NEGATIVE_INFINITY; diff --git a/src/shapes/SuperJump.hx b/src/shapes/SuperJump.hx index c4b8f31d..e4f27726 100644 --- a/src/shapes/SuperJump.hx +++ b/src/shapes/SuperJump.hx @@ -1,5 +1,6 @@ package shapes; +import src.Marble; import src.ResourceLoader; import mis.MissionElement.MissionElementItem; import src.TimeState; @@ -62,21 +63,21 @@ class SuperJump extends PowerUp { }); } - public function pickUp():Bool { - return this.level.pickUpPowerUp(this); + public function pickUp(marble:Marble):Bool { + return this.level.pickUpPowerUp(marble, this); } - public function use(timeState:TimeState) { - var marble = this.level.marble; + public function use(marble:Marble, timeState:TimeState) { var masslessFactor = marble.getMass() * 0.7 + 1 - 0.7; var boost = this.level.currentUp.multiply(20 * masslessFactor / marble.getMass()); marble.velocity = marble.velocity.add(boost); this.level.particleManager.createEmitter(superJumpParticleOptions, this.sjEmitterParticleData, null, () -> marble.getAbsPos().getPosition()); // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) - AudioManager.playSound(ResourceLoader.getResource("data/sound/use_superjump.wav", ResourceLoader.getAudio, this.soundResources)); + if (level.marble == marble) + AudioManager.playSound(ResourceLoader.getResource("data/sound/use_superjump.wav", ResourceLoader.getAudio, this.soundResources)); // this.level.particles.createEmitter(superJumpParticleOptions, null, () => Util.vecOimoToThree(marble.body.getPosition())); - this.level.deselectPowerUp(); + this.level.deselectPowerUp(marble); } override function getPreloadMaterials(dts:dts.DtsFile) { diff --git a/src/shapes/SuperSpeed.hx b/src/shapes/SuperSpeed.hx index e6979a56..0a4b3bb2 100644 --- a/src/shapes/SuperSpeed.hx +++ b/src/shapes/SuperSpeed.hx @@ -1,5 +1,6 @@ package shapes; +import src.Marble; import mis.MissionElement.MissionElementItem; import src.TimeState; import src.ResourceLoader; @@ -63,12 +64,11 @@ class SuperSpeed extends PowerUp { }); } - public function pickUp():Bool { - return this.level.pickUpPowerUp(this); + public function pickUp(marble:Marble):Bool { + return this.level.pickUpPowerUp(marble, this); } - public function use(timeState:TimeState) { - var marble = this.level.marble; + public function use(marble:Marble, timeState:TimeState) { var movementVector = marble.getMarbleAxis()[0]; // Okay, so super speed directionality is a bit strange. In general, the direction is based on the normal vector of the last surface you had contact with. @@ -86,9 +86,10 @@ class SuperSpeed extends PowerUp { // marble.body.addLinearVelocity(Util.vecThreeToOimo(movementVector).scale(24.7)); // Whirligig's determined value // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) - AudioManager.playSound(ResourceLoader.getResource("data/sound/use_speed.wav", ResourceLoader.getAudio, this.soundResources)); + if (level.marble == marble) + AudioManager.playSound(ResourceLoader.getResource("data/sound/use_speed.wav", ResourceLoader.getAudio, this.soundResources)); this.level.particleManager.createEmitter(superSpeedParticleOptions, this.ssEmitterParticleData, null, () -> marble.getAbsPos().getPosition()); - this.level.deselectPowerUp(); + this.level.deselectPowerUp(marble); } override function postProcessMaterial(matName:String, material:h3d.mat.Material) { diff --git a/src/shapes/TimeTravel.hx b/src/shapes/TimeTravel.hx index 402657d6..487cacc0 100644 --- a/src/shapes/TimeTravel.hx +++ b/src/shapes/TimeTravel.hx @@ -1,5 +1,6 @@ package shapes; +import src.Marble; import src.ResourceLoader; import mis.MissionElement.MissionElementItem; import src.TimeState; @@ -39,11 +40,11 @@ class TimeTravel extends PowerUp { }); } - public function pickUp():Bool { + public function pickUp(marble:Marble):Bool { return true; } - public function use(time:TimeState) { + public function use(marble:Marble, time:TimeState) { if (!this.level.rewinding) level.addBonusTime(this.timeBonus); } diff --git a/src/shapes/Trapdoor.hx b/src/shapes/Trapdoor.hx index 19cd6f47..9078cb3f 100644 --- a/src/shapes/Trapdoor.hx +++ b/src/shapes/Trapdoor.hx @@ -10,6 +10,7 @@ import src.ForceObject; import src.ResourceLoader; import src.AudioManager; import src.MarbleWorld; +import src.Marble; class Trapdoor extends DtsObject { var lastContactTime = -1e8; @@ -64,8 +65,8 @@ class Trapdoor extends DtsObject { return completion; } - override function onMarbleContact(time:TimeState, ?contact:CollisionInfo) { - super.onMarbleContact(time, contact); + override function onMarbleContact(marble:Marble, time:TimeState, ?contact:CollisionInfo) { + super.onMarbleContact(marble, time, contact); if (time.timeSinceLoad - this.lastContactTime <= 0) return; // The trapdoor is queued to open, so don't do anything. var currentCompletion = this.getCurrentCompletion(time); diff --git a/src/triggers/CheckpointTrigger.hx b/src/triggers/CheckpointTrigger.hx index 64a3fbe7..1b1c1ab0 100644 --- a/src/triggers/CheckpointTrigger.hx +++ b/src/triggers/CheckpointTrigger.hx @@ -7,6 +7,7 @@ import src.MarbleWorld; import mis.MissionElement.MissionElementTrigger; import src.ResourceLoader; import mis.MisParser; +import src.Marble; class CheckpointTrigger extends Trigger { public var disableOOB = false; @@ -28,8 +29,8 @@ class CheckpointTrigger extends Trigger { }); } - public override function onMarbleEnter(time:src.TimeState) { - super.onMarbleEnter(time); + public override function onMarbleEnter(marble:Marble, time:src.TimeState) { + super.onMarbleEnter(marble, time); if (simGroup == null) return; var shape = level.simGroups[simGroup].filter(x -> x.identifier == "Checkpoint"); diff --git a/src/triggers/HelpTrigger.hx b/src/triggers/HelpTrigger.hx index 5cc31c2f..169e742c 100644 --- a/src/triggers/HelpTrigger.hx +++ b/src/triggers/HelpTrigger.hx @@ -3,9 +3,10 @@ package triggers; import src.TimeState; import src.ResourceLoader; import src.AudioManager; +import src.Marble; class HelpTrigger extends Trigger { - override function onMarbleEnter(timeState:TimeState) { + override function onMarbleEnter(marble:Marble, timeState:TimeState) { AudioManager.playSound(ResourceLoader.getResource('data/sound/infotutorial.wav', ResourceLoader.getAudio, this.soundResources)); if (this.element.text != null && this.element.text != "") this.level.displayHelp(this.element.text); diff --git a/src/triggers/InBoundsTrigger.hx b/src/triggers/InBoundsTrigger.hx index a4160f09..9ef3c84c 100644 --- a/src/triggers/InBoundsTrigger.hx +++ b/src/triggers/InBoundsTrigger.hx @@ -2,9 +2,10 @@ package triggers; import src.TimeState; import src.ResourceLoader; +import src.Marble; class InBoundsTrigger extends Trigger { - override function onMarbleLeave(timeState:TimeState) { + override function onMarbleLeave(marble:Marble, timeState:TimeState) { this.level.goOutOfBounds(); // this.level.replay.recordMarbleLeave(this); } diff --git a/src/triggers/MustChangeTrigger.hx b/src/triggers/MustChangeTrigger.hx index a9035dc6..37e8f4f1 100644 --- a/src/triggers/MustChangeTrigger.hx +++ b/src/triggers/MustChangeTrigger.hx @@ -4,6 +4,7 @@ import src.PathedInterior; import mis.MissionElement.MissionElementTrigger; import src.TimeState; import mis.MisParser; +import src.Marble; class MustChangeTrigger extends Trigger { var interior:PathedInterior; @@ -13,7 +14,7 @@ class MustChangeTrigger extends Trigger { this.interior = interior; } - public override function onMarbleEnter(time:TimeState) { + public override function onMarbleEnter(marble:Marble, time:TimeState) { var ttime = MisParser.parseNumber(this.element.targettime); if (ttime > 0) ttime /= 1000; diff --git a/src/triggers/OutOfBoundsTrigger.hx b/src/triggers/OutOfBoundsTrigger.hx index 22c976c0..c005a396 100644 --- a/src/triggers/OutOfBoundsTrigger.hx +++ b/src/triggers/OutOfBoundsTrigger.hx @@ -2,9 +2,10 @@ package triggers; import src.TimeState; import src.ResourceLoader; +import src.Marble; class OutOfBoundsTrigger extends Trigger { - override function onMarbleInside(time:TimeState) { + override function onMarbleInside(marble:Marble, time:TimeState) { this.level.goOutOfBounds(); // this.level.replay.recordMarbleInside(this); }