refactor a lot to get powerups sorta working

This commit is contained in:
RandomityGuy 2024-01-26 21:57:36 +05:30
parent 1fef9a5bdc
commit bd8527bae6
28 changed files with 286 additions and 151 deletions

View file

@ -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() {}

View file

@ -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<PathedInterior>, packets:Array<haxe.io.Bytes>) {

View file

@ -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<ClientConnection, Marble> = [];
public var lastMoves:Map<Int, MarbleUpdatePacket> = [];
// 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));
}

View file

@ -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;
}
}

View file

@ -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;
// }
}

View file

@ -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];
}
}

View file

@ -12,6 +12,8 @@ class Polygon extends MeshPrimitive {
public var uvs:Array<UV>;
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) {

View file

@ -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)

View file

@ -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);

67
src/net/NetPacket.hx Normal file
View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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());

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -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));
}

View file

@ -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 <func:bind mousefire> 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 <func:bind mousefire> 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;

View file

@ -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) {

View file

@ -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) {

View file

@ -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);
}

View file

@ -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);

View file

@ -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");

View file

@ -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);

View file

@ -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);
}

View file

@ -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;

View file

@ -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);
}