diff --git a/src/DtsObject.hx b/src/DtsObject.hx index 582edf29..76305db1 100644 --- a/src/DtsObject.hx +++ b/src/DtsObject.hx @@ -1,5 +1,6 @@ package src; +import src.TimeState; import shaders.Billboard; import collision.BoxCollisionEntity; import shaders.DtsTexture; @@ -559,7 +560,7 @@ class DtsObject extends GameObject { this.level.collisionWorld.updateTransform(this.boundingCollider); } - public function update(currentTime:Float, dt:Float) { + public function update(timeState:TimeState) { for (sequence in this.dts.sequences) { if (!this.showSequences) break; @@ -569,7 +570,7 @@ class DtsObject extends GameObject { var rot = sequence.rotationMatters.length > 0 ? sequence.rotationMatters[0] : 0; var trans = sequence.translationMatters.length > 0 ? sequence.translationMatters[0] : 0; var affectedCount = 0; - var completion = (currentTime + dt) / sequence.duration; + var completion = timeState.timeSinceLoad / sequence.duration; var quaternions:Array = null; var translations:Array = null; @@ -745,7 +746,7 @@ class DtsObject extends GameObject { if (iflSequence.length == 0 || !this.showSequences) continue; - var completion = (currentTime + dt) / (iflSequence[0].duration); + var completion = timeState.timeSinceLoad / (iflSequence[0].duration); var keyframe = Math.floor(completion * info.length) % info.length; var currentFile = info[keyframe]; var texture = ResourceLoader.getTexture(this.directoryPath + '/' + currentFile); @@ -760,7 +761,7 @@ class DtsObject extends GameObject { if (this.ambientRotate) { var spinAnimation = new Quat(); - spinAnimation.initRotateAxis(0, 0, -1, (currentTime + dt) * this.ambientSpinFactor); + spinAnimation.initRotateAxis(0, 0, -1, timeState.timeSinceLoad * this.ambientSpinFactor); var orientation = this.getRotationQuat(); // spinAnimation.multiply(orientation, spinAnimation); diff --git a/src/GameObject.hx b/src/GameObject.hx index 3acf15e5..47f28df6 100644 --- a/src/GameObject.hx +++ b/src/GameObject.hx @@ -1,5 +1,6 @@ package src; +import src.TimeState; import collision.CollisionInfo; import h3d.scene.Object; @@ -8,13 +9,13 @@ class GameObject extends Object { public var currentOpacity:Float = 1; public var isCollideable:Bool = false; - public function onMarbleContact(time:Float, ?contact:CollisionInfo) {} + public function onMarbleContact(time:TimeState, ?contact:CollisionInfo) {} - public function onMarbleInside(time:Float) {} + public function onMarbleInside(time:TimeState) {} - public function onMarbleEnter(time:Float) {} + public function onMarbleEnter(time:TimeState) {} - public function onMarbleLeave(time:Float) {} + public function onMarbleLeave(time:TimeState) {} public function onLevelStart() {} diff --git a/src/Main.hx b/src/Main.hx index 59c3fdcc..1a890bbc 100644 --- a/src/Main.hx +++ b/src/Main.hx @@ -1,5 +1,6 @@ package; +import shapes.EndPad; import shapes.LandMine; import shapes.StartPad; import shapes.TriangleBumper; @@ -143,14 +144,19 @@ class Main extends hxd.App { world.addDtsObject(tb); var spad = new StartPad(); - tb.x = 5; - tb.y = 3; + spad.x = 5; + spad.y = 3; world.addDtsObject(spad); var lm = new LandMine(); lm.x = 7; world.addDtsObject(lm); + var epad = new EndPad(); + epad.x = 5; + epad.x = -3; + world.addDtsObject(epad); + // var le:ParticleEmitterOptions = { // ejectionPeriod: 0.01, diff --git a/src/Marble.hx b/src/Marble.hx index ccbc10d1..8fcfb7d1 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1,5 +1,6 @@ package src; +import src.TimeState; import src.ParticleSystem.ParticleEmitter; import src.ParticleSystem.ParticleData; import src.ParticleSystem.ParticleEmitterOptions; @@ -191,9 +192,9 @@ class Marble extends GameObject { level.addDtsObject(this.helicopter); } - function findContacts(collisiomWorld:CollisionWorld, dt:Float) { + function findContacts(collisiomWorld:CollisionWorld, timeState:TimeState) { this.contacts = queuedContacts; - var c = collisiomWorld.sphereIntersection(this.collider, dt); + var c = collisiomWorld.sphereIntersection(this.collider, timeState); contacts = contacts.concat(c); } @@ -637,11 +638,11 @@ class Marble extends GameObject { return intersectT; } - function advancePhysics(currentTime:Float, dt:Float, m:Move, collisionWorld:CollisionWorld, pathedInteriors:Array) { - var timeRemaining = dt; + function advancePhysics(timeState:TimeState, m:Move, collisionWorld:CollisionWorld, pathedInteriors:Array) { + var timeRemaining = timeState.dt; var it = 0; - var piTime = currentTime; + var piTime = timeState.currentAttemptTime; // if (this.controllable) { // for (interior in pathedInteriors) { @@ -667,20 +668,23 @@ class Marble extends GameObject { if (timeRemaining < 0.00800000037997961) timeStep = timeRemaining; - this.findContacts(collisionWorld, timeStep); + var tempState = timeState.clone(); + tempState.dt = timeStep; + + this.findContacts(collisionWorld, tempState); var cmf = this.computeMoveForces(m); var isCentered:Bool = cmf.result; var aControl = cmf.aControl; var desiredOmega = cmf.desiredOmega; var stoppedPaths = false; - stoppedPaths = this.velocityCancel(currentTime, dt, isCentered, false, stoppedPaths, pathedInteriors); - var A = this.getExternalForces(currentTime, m, timeStep); + stoppedPaths = this.velocityCancel(timeState.currentAttemptTime, timeStep, isCentered, false, stoppedPaths, pathedInteriors); + var A = this.getExternalForces(timeState.currentAttemptTime, m, timeStep); var retf = this.applyContactForces(timeStep, m, isCentered, aControl, desiredOmega, A); A = retf[0]; var a = retf[1]; this.velocity = this.velocity.add(A.multiply(timeStep)); this.omega = this.omega.add(a.multiply(timeStep)); - stoppedPaths = this.velocityCancel(currentTime, dt, isCentered, true, stoppedPaths, pathedInteriors); + stoppedPaths = this.velocityCancel(timeState.currentAttemptTime, timeStep, isCentered, true, stoppedPaths, pathedInteriors); this._totalTime += timeStep; if (contacts.length != 0) { this._contactTime += timeStep; @@ -703,7 +707,10 @@ class Marble extends GameObject { for (interior in pathedInteriors) { // interior.popTickState(); // interior.setStopped(stoppedPaths); - interior.update(piTime, timeStep); + var piDT = timeState.clone(); + piDT.currentAttemptTime = piTime; + piDT.dt = timeStep; + interior.update(piDT); } } @@ -724,7 +731,10 @@ class Marble extends GameObject { this.collider.velocity = this.velocity; if (this.heldPowerup != null && m.powerup) { - this.heldPowerup.use(currentTime); + var pTime = timeState.clone(); + pTime.dt = timeStep; + pTime.currentAttemptTime = piTime; + this.heldPowerup.use(pTime); this.heldPowerup = null; } @@ -734,7 +744,7 @@ class Marble extends GameObject { this.queuedContacts = []; } - public function update(currentTime:Float, dt:Float, collisionWorld:CollisionWorld, pathedInteriors:Array) { + public function update(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array) { var move = new Move(); move.d = new Vector(); if (this.controllable) { @@ -758,17 +768,17 @@ class Marble extends GameObject { } } - advancePhysics(currentTime, dt, move, collisionWorld, pathedInteriors); + advancePhysics(timeState, move, collisionWorld, pathedInteriors); if (this.controllable) { - this.camera.update(currentTime, dt); + this.camera.update(timeState.currentAttemptTime, timeState.dt); } - updatePowerupStates(currentTime, dt); + updatePowerupStates(timeState.currentAttemptTime, timeState.dt); this.trailEmitter(); if (bounceEmitDelay > 0) - bounceEmitDelay -= dt; + bounceEmitDelay -= timeState.dt; if (bounceEmitDelay < 0) bounceEmitDelay = 0; diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index c9a1f9d4..71bca58f 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1,5 +1,6 @@ package src; +import src.TimeState; import gui.PlayGui; import src.ParticleSystem.ParticleManager; import src.Util; @@ -35,8 +36,7 @@ class MarbleWorld extends Scheduler { var shapeImmunity:Array = []; var shapeOrTriggerInside:Array = []; - public var currentTime:Float = 0; - public var elapsedTime:Float = 0; + public var timeState:TimeState = new TimeState(); public var bonusTime:Float = 0; public var sky:Sky; @@ -78,8 +78,8 @@ class MarbleWorld extends Scheduler { } public function restart() { - this.currentTime = 0; - this.elapsedTime = 0; + this.timeState.currentAttemptTime = 0; + this.timeState.gameplayClock = 0; this.bonusTime = 0; this.outOfBounds = false; this.marble.camera.CameraPitch = 0.45; @@ -99,19 +99,19 @@ class MarbleWorld extends Scheduler { } public function updateGameState() { - if (this.currentTime < 0.5) { + if (this.timeState.currentAttemptTime < 0.5) { this.playGui.setCenterText('none'); } - if (this.currentTime >= 0.5 && this.currentTime < 2) { + if ((this.timeState.currentAttemptTime >= 0.5) && (this.timeState.currentAttemptTime < 2)) { this.playGui.setCenterText('ready'); } - if (this.currentTime >= 2 && this.currentTime < 3.5) { + if ((this.timeState.currentAttemptTime >= 2) && (this.timeState.currentAttemptTime < 3.5)) { this.playGui.setCenterText('set'); } - if (this.currentTime >= 3.5 && this.currentTime < 5.5) { + if ((this.timeState.currentAttemptTime >= 3.5) && (this.timeState.currentAttemptTime < 5.5)) { this.playGui.setCenterText('go'); } - if (this.currentTime >= 5.5) { + if (this.timeState.currentAttemptTime >= 5.5) { this.playGui.setCenterText('none'); } if (this.outOfBounds) { @@ -169,18 +169,18 @@ class MarbleWorld extends Scheduler { } public function update(dt:Float) { - this.tickSchedule(currentTime); + this.updateTimer(dt); + this.tickSchedule(timeState.currentAttemptTime); this.updateGameState(); for (obj in dtsObjects) { - obj.update(currentTime, dt); + obj.update(timeState); } for (marble in marbles) { - marble.update(currentTime, dt, collisionWorld, this.pathedInteriors); + marble.update(timeState, collisionWorld, this.pathedInteriors); } this.instanceManager.update(dt); - this.particleManager.update(1000 * currentTime, dt); - this.updateTimer(dt); - this.playGui.update(currentTime, dt); + this.particleManager.update(1000 * timeState.timeSinceLoad, dt); + this.playGui.update(timeState); if (this.marble != null) { callCollisionHandlers(marble); @@ -193,31 +193,33 @@ class MarbleWorld extends Scheduler { } public function updateTimer(dt:Float) { - currentTime += dt; + this.timeState.dt = dt; + this.timeState.currentAttemptTime += dt; + this.timeState.timeSinceLoad += dt; if (this.bonusTime != 0) { this.bonusTime -= dt; if (this.bonusTime < 0) { - this.elapsedTime -= this.bonusTime; + this.timeState.gameplayClock -= this.bonusTime; this.bonusTime = 0; } } else { - this.elapsedTime += dt; + this.timeState.gameplayClock += dt; } - playGui.formatTimer(this.elapsedTime); + playGui.formatTimer(this.timeState.gameplayClock); } function updateTexts() { var helpTextTime = this.helpTextTimeState; var alertTextTime = this.alertTextTimeState; - var helpTextCompletion = Math.pow(Util.clamp((this.currentTime - helpTextTime - 3), 0, 1), 2); - var alertTextCompletion = Math.pow(Util.clamp((this.currentTime - alertTextTime - 3), 0, 1), 2); + var helpTextCompletion = Math.pow(Util.clamp((this.timeState.currentAttemptTime - helpTextTime - 3), 0, 1), 2); + var alertTextCompletion = Math.pow(Util.clamp((this.timeState.currentAttemptTime - alertTextTime - 3), 0, 1), 2); this.playGui.setHelpTextOpacity(1 - helpTextCompletion); this.playGui.setAlertTextOpacity(1 - alertTextCompletion); } public function displayAlert(text:String) { this.playGui.setAlertText(text); - this.alertTextTimeState = this.currentTime; + this.alertTextTimeState = this.timeState.currentAttemptTime; } function callCollisionHandlers(marble:Marble) { @@ -234,7 +236,7 @@ class MarbleWorld extends Scheduler { if (contact.go is DtsObject) { var shape:DtsObject = cast contact.go; - var contacttest = shape.colliders.filter(x -> x != null).map(x -> x.sphereIntersection(contactsphere, 0)); + var contacttest = shape.colliders.filter(x -> x != null).map(x -> x.sphereIntersection(contactsphere, timeState)); var contactlist:Array = []; for (l in contacttest) { contactlist = contactlist.concat(l); @@ -243,13 +245,13 @@ class MarbleWorld extends Scheduler { if (!calledShapes.contains(shape) && !this.shapeImmunity.contains(shape) && contactlist.length != 0) { calledShapes.push(shape); newImmunity.push(shape); - shape.onMarbleContact(currentTime); + shape.onMarbleContact(timeState); } - shape.onMarbleInside(currentTime); + shape.onMarbleInside(timeState); if (!this.shapeOrTriggerInside.contains(shape)) { this.shapeOrTriggerInside.push(shape); - shape.onMarbleEnter(currentTime); + shape.onMarbleEnter(timeState); } inside.push(shape); } @@ -259,7 +261,7 @@ class MarbleWorld extends Scheduler { for (object in shapeOrTriggerInside) { if (!inside.contains(object)) { this.shapeOrTriggerInside.remove(object); - object.onMarbleLeave(currentTime); + object.onMarbleLeave(timeState); } } @@ -286,9 +288,9 @@ class MarbleWorld extends Scheduler { return q; } - public function setUp(vec:Vector, time:Float) { + public function setUp(vec:Vector, timeState:TimeState) { this.currentUp = vec; - var currentQuat = this.getOrientationQuat(time); + var currentQuat = this.getOrientationQuat(timeState.currentAttemptTime); var oldUp = new Vector(0, 0, 1); oldUp.transform(currentQuat.toMatrix()); @@ -329,7 +331,7 @@ class MarbleWorld extends Scheduler { this.newOrientationQuat = quatChange; this.oldOrientationQuat = currentQuat; - this.orientationChangeTime = time; + this.orientationChangeTime = timeState.currentAttemptTime; } } diff --git a/src/ParticleSystem.hx b/src/ParticleSystem.hx index 46ebc4b4..8ad47a4b 100644 --- a/src/ParticleSystem.hx +++ b/src/ParticleSystem.hx @@ -1,5 +1,6 @@ package src; +import src.TimeState; import h3d.prim.UV; import h3d.parts.Data.BlendMode; import src.MarbleWorld; @@ -230,7 +231,7 @@ class ParticleEmitter { this.currentWaitPeriod = this.o.ejectionPeriod; var pos = this.getPosAtTime(time).clone(); if (this.o.spawnOffset != null) - pos.add(this.o.spawnOffset()); // Call the spawnOffset function if it's there + pos = pos.add(this.o.spawnOffset()); // Call the spawnOffset function if it's there // This isn't necessarily uniform but it's fine for the purpose. var randomPointOnSphere = new Vector(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1).normalized(); // Compute the total velocity diff --git a/src/PathedInterior.hx b/src/PathedInterior.hx index 800ca86f..928aca55 100644 --- a/src/PathedInterior.hx +++ b/src/PathedInterior.hx @@ -1,5 +1,6 @@ package src; +import src.TimeState; import src.MarbleWorld; import h3d.Matrix; import h3d.Vector; @@ -43,7 +44,7 @@ class PathedInterior extends InteriorObject { this.reset(); } - public function update(currentTime:Float, dt:Float) { + public function update(timeState:TimeState) { // this.previousState = { // currentTime: currentTime, // targetTime: targetTime, @@ -71,7 +72,7 @@ class PathedInterior extends InteriorObject { // if (!stopped) // this.currentTime = currentTime; - velocity = position.sub(this.prevPosition).multiply(1 / dt); + velocity = position.sub(this.prevPosition).multiply(1 / timeState.dt); } public function setStopped(stopped:Bool = true) { diff --git a/src/TimeState.hx b/src/TimeState.hx new file mode 100644 index 00000000..a90f9af0 --- /dev/null +++ b/src/TimeState.hx @@ -0,0 +1,20 @@ +package src; + +@:publicFields +class TimeState { + var timeSinceLoad:Float; + var currentAttemptTime:Float; + var gameplayClock:Float; + var dt:Float; + + public function new() {} + + public function clone() { + var n = new TimeState(); + n.timeSinceLoad = this.timeSinceLoad; + n.currentAttemptTime = this.currentAttemptTime; + n.gameplayClock = this.gameplayClock; + n.dt = this.dt; + return n; + } +} diff --git a/src/collision/BoxCollisionEntity.hx b/src/collision/BoxCollisionEntity.hx index 9578af84..d5db9c5a 100644 --- a/src/collision/BoxCollisionEntity.hx +++ b/src/collision/BoxCollisionEntity.hx @@ -1,5 +1,6 @@ package collision; +import src.TimeState; import h3d.Matrix; import src.GameObject; import src.Marble; @@ -27,7 +28,7 @@ class BoxCollisionEntity extends CollisionEntity { return boundingBox.rayIntersection(Ray.fromValues(rayOrigin.x, rayOrigin.y, rayOrigin.z, rayDirection.x, rayDirection.y, rayDirection.z), true) != -1; } - public override function sphereIntersection(collisionEntity:SphereCollisionEntity, dt:Float) { + public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState) { return []; } } diff --git a/src/collision/CollisionEntity.hx b/src/collision/CollisionEntity.hx index c8bd5aa7..e30bba94 100644 --- a/src/collision/CollisionEntity.hx +++ b/src/collision/CollisionEntity.hx @@ -1,5 +1,6 @@ package collision; +import src.TimeState; import src.GameObject; import dif.math.Point3F; import dif.math.PlaneF; @@ -79,7 +80,7 @@ class CollisionEntity implements IOctreeObject { this.priority = priority; } - public function sphereIntersection(collisionEntity:SphereCollisionEntity, dt:Float) { + public function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState) { var position = collisionEntity.transform.getPosition(); var velocity = collisionEntity.velocity; var radius = collisionEntity.radius; @@ -91,7 +92,7 @@ class CollisionEntity implements IOctreeObject { var surfaces = octree.radiusSearch(localpos, radius * 1.1); var tform = transform.clone(); - tform.setPosition(tform.getPosition().add(this.velocity.multiply(dt))); + tform.setPosition(tform.getPosition().add(this.velocity.multiply(timeState.dt))); function toDifPoint(pt:Vector) { return new Point3F(pt.x, pt.y, pt.z); diff --git a/src/collision/CollisionHull.hx b/src/collision/CollisionHull.hx index 47ff5c52..4aec9244 100644 --- a/src/collision/CollisionHull.hx +++ b/src/collision/CollisionHull.hx @@ -1,5 +1,6 @@ package collision; +import src.TimeState; import src.GameObject; import h3d.col.Bounds; import collision.gjk.GJK; @@ -17,7 +18,7 @@ class CollisionHull extends CollisionEntity { super(go); } - public override function sphereIntersection(collisionEntity:SphereCollisionEntity, dt:Float):Array { + public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState):Array { var bbox = this.boundingBox; var box = new Bounds(); var pos = collisionEntity.transform.getPosition(); @@ -33,7 +34,7 @@ class CollisionHull extends CollisionEntity { sph.position = pos; sph.radius = collisionEntity.radius; var newTform = this.transform.clone(); - var newpos = this.transform.getPosition().add(this.velocity.multiply(dt)); + var newpos = this.transform.getPosition().add(this.velocity.multiply(timeState.dt)); newTform.setPosition(newpos); hull.setTransform(newTform); @@ -48,7 +49,7 @@ class CollisionHull extends CollisionEntity { cinfo.otherObject = this.go; cinfo.friction = friction; cinfo.force = force; - this.go.onMarbleContact(dt, cinfo); + this.go.onMarbleContact(timeState, cinfo); return [cinfo]; } } diff --git a/src/collision/CollisionWorld.hx b/src/collision/CollisionWorld.hx index cd18baf2..ec7102f8 100644 --- a/src/collision/CollisionWorld.hx +++ b/src/collision/CollisionWorld.hx @@ -1,5 +1,6 @@ package collision; +import src.TimeState; import h3d.col.Bounds; import h3d.col.Sphere; import h3d.Vector; @@ -14,11 +15,11 @@ class CollisionWorld { this.octree = new Octree(); } - public function sphereIntersection(spherecollision:SphereCollisionEntity, dt:Float) { + public function sphereIntersection(spherecollision:SphereCollisionEntity, timeState:TimeState) { var position = spherecollision.transform.getPosition(); var radius = spherecollision.radius; var velocity = spherecollision.velocity; - var searchdist = (velocity.length() * dt) + radius; + var searchdist = (velocity.length() * timeState.dt) + radius; var intersections = this.octree.radiusSearch(position, searchdist); var box = new Bounds(); @@ -35,14 +36,14 @@ class CollisionWorld { var entity:CollisionEntity = cast obj; if (entity.go.isCollideable) { - contacts = contacts.concat(entity.sphereIntersection(spherecollision, dt)); + contacts = contacts.concat(entity.sphereIntersection(spherecollision, timeState)); } } for (obj in dynamicEntities) { if (obj != spherecollision) { if (obj.boundingBox.collide(box) && obj.go.isCollideable) - contacts = contacts.concat(obj.sphereIntersection(spherecollision, dt)); + contacts = contacts.concat(obj.sphereIntersection(spherecollision, timeState)); } } return contacts; diff --git a/src/collision/SphereCollisionEntity.hx b/src/collision/SphereCollisionEntity.hx index 51664b69..7cd8ef2f 100644 --- a/src/collision/SphereCollisionEntity.hx +++ b/src/collision/SphereCollisionEntity.hx @@ -1,5 +1,6 @@ package collision; +import src.TimeState; import src.Marble; import h3d.col.Ray; import h3d.Vector; @@ -31,7 +32,7 @@ class SphereCollisionEntity extends CollisionEntity { return boundingBox.rayIntersection(Ray.fromValues(rayOrigin.x, rayOrigin.y, rayOrigin.z, rayDirection.x, rayDirection.y, rayDirection.z), true) != -1; } - public override function sphereIntersection(collisionEntity:SphereCollisionEntity, dt:Float) { + public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState) { var contacts = []; var thispos = transform.getPosition(); var position = collisionEntity.transform.getPosition(); diff --git a/src/gui/PlayGui.hx b/src/gui/PlayGui.hx index 43378e2f..7cf8c71c 100644 --- a/src/gui/PlayGui.hx +++ b/src/gui/PlayGui.hx @@ -1,5 +1,6 @@ package gui; +import src.TimeState; import format.gif.Data.Block; import hxd.res.BitmapFont; import h2d.Text; @@ -306,11 +307,11 @@ class PlayGui { engine.popTarget(); } - public function update(currentTime:Float, dt:Float) { - this.gemImageObject.update(currentTime, dt); - this.gemImageScene.setElapsedTime(dt); + public function update(timeState:TimeState) { + this.gemImageObject.update(timeState); + this.gemImageScene.setElapsedTime(timeState.dt); if (this.powerupImageObject != null) - this.powerupImageObject.update(currentTime, dt); - this.powerupImageScene.setElapsedTime(dt); + this.powerupImageObject.update(timeState); + this.powerupImageScene.setElapsedTime(timeState.dt); } } diff --git a/src/shapes/AntiGravity.hx b/src/shapes/AntiGravity.hx index 7402a857..fe56caea 100644 --- a/src/shapes/AntiGravity.hx +++ b/src/shapes/AntiGravity.hx @@ -1,5 +1,6 @@ package shapes; +import src.TimeState; import h3d.Vector; import src.DtsObject; @@ -20,10 +21,10 @@ class AntiGravity extends PowerUp { return !direction.equals(this.level.currentUp); } - public function use(time:Float) { + public function use(timeState:TimeState) { var direction = new Vector(0, 0, -1); direction.transform(this.getRotationQuat().toMatrix()); - this.level.setUp(direction, time); + this.level.setUp(direction, timeState); // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) // AudioManager.play(this.sounds[1]); diff --git a/src/shapes/EndPad.hx b/src/shapes/EndPad.hx index f55b560f..3a633ab1 100644 --- a/src/shapes/EndPad.hx +++ b/src/shapes/EndPad.hx @@ -1,8 +1,21 @@ package shapes; +import src.TimeState; +import collision.CollisionInfo; +import src.Util; +import src.ResourceLoader; +import src.ParticleSystem.ParticleData; +import src.MarbleWorld; import src.DtsObject; +import src.MarbleWorld.Scheduler; +import src.ParticleSystem.ParticleEmitter; +import src.ParticleSystem.ParticleEmitterOptions; +import h3d.Vector; class EndPad extends DtsObject { + var fireworks:Array = []; + var isEntered:Bool = false; + public function new() { super(); this.dtsPath = "data/shapes/pads/endarea.dts"; @@ -10,4 +23,266 @@ class EndPad extends DtsObject { this.isCollideable = true; this.identifier = "EndPad"; } + + override function onMarbleContact(timeState:TimeState, ?contact:CollisionInfo) { + if (!isEntered) { + isEntered = true; + spawnFirework(timeState); + } + } + + function spawnFirework(time:TimeState) { + var firework = new Firework(this.getAbsPos().getPosition(), time.timeSinceLoad, this.level); + this.fireworks.push(firework); + // AudioManager.play(this.sounds[0], 1, AudioManager.soundGain, this.worldPosition); + } + + override function update(timeState:TimeState) { + super.update(timeState); + + for (firework in this.fireworks) { + firework.tick(timeState.timeSinceLoad); + if (timeState.timeSinceLoad - firework.spawnTime >= 10) + this.fireworks.remove(firework); + // We can safely remove the firework + } + } +} + +final fireworkSmoke:ParticleEmitterOptions = { + ejectionPeriod: 100, + ambientVelocity: new Vector(0, 0, 1), + ejectionVelocity: 0, + velocityVariance: 0, + emitterLifetime: 4000, + spawnOffset: () -> { + var r = Math.sqrt(Math.random()); + var theta = Math.random() * Math.PI * 2; + var randomPointInCircle = new Vector(r * Math.cos(theta), r * Math.sin(theta)); + return new Vector(randomPointInCircle.x * 1.6, randomPointInCircle.y * 1.6, Math.random() * 0.4 - 0.5); + }, + inheritedVelFactor: 0, + particleOptions: { + texture: 'particles/saturn.png', + blending: Alpha, + spinSpeed: 0, + spinRandomMin: -90, + spinRandomMax: 90, + lifetime: 2000, + lifetimeVariance: 200, + dragCoefficient: 0.5, + acceleration: 0, + colors: [new Vector(1, 1, 0, 0), new Vector(1, 0, 0, 1), new Vector(1, 0, 0, 0)], + sizes: [0.1, 0.2, 0.3], + times: [0, 0.2, 1] + } +}; + +final redTrail:ParticleEmitterOptions = { + ejectionPeriod: 30, + ambientVelocity: new Vector(0, 0, 0), + ejectionVelocity: 0, + velocityVariance: 0, + emitterLifetime: 10000, + inheritedVelFactor: 0, + particleOptions: { + texture: 'particles/spark.png', + blending: Alpha, + spinSpeed: 0, + spinRandomMin: -90, + spinRandomMax: 90, + lifetime: 600, + lifetimeVariance: 100, + dragCoefficient: 0, + acceleration: 0, + colors: [new Vector(1, 1, 0, 1), new Vector(1, 0, 0, 1), new Vector(1, 0, 0, 0)], + sizes: [0.1, 0.05, 0.01], + times: [0, 0.5, 1] + } +}; + +final blueTrail:ParticleEmitterOptions = { + ejectionPeriod: 30, + ambientVelocity: new Vector(0, 0, 0), + ejectionVelocity: 0, + velocityVariance: 0, + emitterLifetime: 10000, + inheritedVelFactor: 0, + particleOptions: { + texture: 'particles/spark.png', + blending: Alpha, + spinSpeed: 0, + spinRandomMin: -90, + spinRandomMax: 90, + lifetime: 600, + lifetimeVariance: 100, + dragCoefficient: 0, + acceleration: 0, + colors: [new Vector(0, 0, 1, 1), new Vector(0.5, 0.5, 1, 1), new Vector(1, 1, 1, 0)], + sizes: [0.1, 0.05, 0.01], + times: [0, 0.5, 1] + } +}; + +final redSpark:ParticleEmitterOptions = { + ejectionPeriod: 1, + ambientVelocity: new Vector(0, 0, 0), + ejectionVelocity: 0.8, + velocityVariance: 0.25, + emitterLifetime: 10, + inheritedVelFactor: 0, + particleOptions: { + texture: 'particles/star.png', + blending: Alpha, + spinSpeed: 40, + spinRandomMin: -90, + spinRandomMax: 90, + lifetime: 500, + lifetimeVariance: 50, + dragCoefficient: 0.5, + acceleration: 0, + colors: [new Vector(1, 1, 0, 1), new Vector(1, 1, 0, 1), new Vector(1, 0, 0, 0)], + sizes: [0.2, 0.2, 0.2], + times: [0, 0.5, 1] + } +}; + +final blueSpark:ParticleEmitterOptions = { + ejectionPeriod: 1, + ambientVelocity: new Vector(0, 0, 0), + ejectionVelocity: 0.5, + velocityVariance: 0.25, + emitterLifetime: 10, + inheritedVelFactor: 0, + particleOptions: { + texture: 'particles/bubble.png', + blending: Alpha, + spinSpeed: 40, + spinRandomMin: -90, + spinRandomMax: 90, + lifetime: 2000, + lifetimeVariance: 200, + dragCoefficient: 0, + acceleration: 0, + colors: [new Vector(0, 0, 1, 1), new Vector(0.5, 0.5, 1, 1), new Vector(1, 1, 1, 0)], + sizes: [0.2, 0.2, 0.2], + times: [0, 0.5, 1] + } +}; + +typedef Trail = { + var type:String; + var smokeEmitter:ParticleEmitter; + var targetPos:Vector; + var spawnTime:Float; + var lifetime:Float; +} + +@:publicFields +class Firework extends Scheduler { + var pos:Vector; + var spawnTime:Float; + var trails:Array = []; + + /** The fireworks are spawned in waves, this controls how many are left. */ + var wavesLeft = 4; + + var level:MarbleWorld; + + var fireworkSmokeData:ParticleData; + var fireworkRedTrailData:ParticleData; + var fireworkBlueTrailData:ParticleData; + var fireworkRedSparkData:ParticleData; + var fireworkBlueSparkData:ParticleData; + + public function new(pos:Vector, spawnTime:Float, level:MarbleWorld) { + this.pos = pos; + this.spawnTime = spawnTime; + this.level = level; + + fireworkSmokeData = new ParticleData(); + fireworkSmokeData.identifier = "fireworkSmoke"; + fireworkSmokeData.texture = ResourceLoader.getTexture("data/particles/saturn.png"); + + fireworkRedTrailData = new ParticleData(); + fireworkRedTrailData.identifier = "fireworkRedTrail"; + fireworkRedTrailData.texture = ResourceLoader.getTexture("data/particles/spark.png"); + + fireworkBlueTrailData = new ParticleData(); + fireworkBlueTrailData.identifier = "fireworkBlueTrail"; + fireworkBlueTrailData.texture = ResourceLoader.getTexture("data/particles/spark.png"); + + fireworkRedSparkData = new ParticleData(); + fireworkRedSparkData.identifier = "fireworkRedSpark"; + fireworkRedSparkData.texture = ResourceLoader.getTexture("data/particles/star.png"); + + fireworkBlueSparkData = new ParticleData(); + fireworkBlueSparkData.identifier = "fireworkBlueSpark"; + fireworkBlueSparkData.texture = ResourceLoader.getTexture("data/particles/bubble.png"); + + level.particleManager.createEmitter(fireworkSmoke, fireworkSmokeData, this.pos); // Start the smoke + this.doWave(this.spawnTime); // Start the first wave + } + + public function tick(time:Float) { + this.tickSchedule(time); + + // Update the trails + for (trail in this.trails) { + var completion = Util.clamp((time - trail.spawnTime) / trail.lifetime, 0, 1); + completion = 1 - Math.pow((1 - completion), 2); // ease-out + + // Make the trail travel along an arc (parabola, whatever) + var pos = this.pos.clone().multiply(1 - completion).add(trail.targetPos.clone().multiply(completion)); + pos = pos.sub(new Vector(0, 0, 1).multiply(Math.pow(completion, 2))); + trail.smokeEmitter.setPos(pos, time); + + if (completion == 1) { + // The trail has reached its end, remove the emitter and spawn the explosion. + level.particleManager.removeEmitter(trail.smokeEmitter); + this.trails.remove(trail); + + if (trail.type == 'red') { + level.particleManager.createEmitter(redSpark, fireworkRedSparkData, pos); + } else { + level.particleManager.createEmitter(blueSpark, fireworkBlueSparkData, pos); + } + } + } + } + + /** Spawns a bunch of trails going in random directions. */ + function doWave(time:Float) { + var count = Math.floor(17 + Math.random() * 10); + for (i in 0...count) + this.spawnTrail(time); + this.wavesLeft--; + if (this.wavesLeft > 0) { + var nextWaveTime = time + 0.5 + 1 * Math.random(); + this.schedule(nextWaveTime, () -> this.doWave(nextWaveTime)); + } + return null; + } + + function spawnTrail(time:Float) { + var type = (Math.random() < 0.5) ? 'red' : 'blue'; + var lifetime = 0.25 + Math.random() * 2; + var distanceFac = 0.5 + lifetime / 5; // Make sure the firework doesn't travel a great distance way too quickly + var emitter = level.particleManager.createEmitter((type == 'red') ? redTrail : blueTrail, + (type == 'red') ? fireworkRedTrailData : fireworkBlueTrailData, this.pos); + + var r = Math.sqrt(Math.random()); + var theta = Math.random() * Math.PI * 2; + + var randomPointInCircle = new Vector(r * Math.cos(theta), r * Math.sin(theta)); + var targetPos = new Vector(randomPointInCircle.x * 3, randomPointInCircle.y * 3, 1 + Math.sqrt(Math.random()) * 3).multiply(distanceFac).add(this.pos); + var trail:Trail = { + type: type, + smokeEmitter: emitter, + targetPos: targetPos, + spawnTime: time, + lifetime: lifetime + }; + this.trails.push(trail); + } } diff --git a/src/shapes/Gem.hx b/src/shapes/Gem.hx index d1d0b65f..619d0794 100644 --- a/src/shapes/Gem.hx +++ b/src/shapes/Gem.hx @@ -1,5 +1,6 @@ package shapes; +import src.TimeState; import src.DtsObject; class Gem extends DtsObject { @@ -19,7 +20,7 @@ class Gem extends DtsObject { this.matNameOverride.set('base.gem', color + ".gem"); } - public function setHide(hide:Bool) { + public override function setHide(hide:Bool) { if (hide) { this.pickedUp = true; this.setOpacity(0); @@ -29,8 +30,8 @@ class Gem extends DtsObject { } } - override function onMarbleInside(time:Float) { - super.onMarbleInside(time); + override function onMarbleInside(timeState:TimeState) { + super.onMarbleInside(timeState); if (this.pickedUp) return; this.pickedUp = true; diff --git a/src/shapes/Helicopter.hx b/src/shapes/Helicopter.hx index 7f8b9820..202ee778 100644 --- a/src/shapes/Helicopter.hx +++ b/src/shapes/Helicopter.hx @@ -1,5 +1,6 @@ package shapes; +import src.TimeState; import src.DtsObject; class Helicopter extends PowerUp { @@ -17,9 +18,9 @@ class Helicopter extends PowerUp { return this.level.pickUpPowerUp(this); } - public function use(time:Float) { + public function use(timeState:TimeState) { var marble = this.level.marble; - marble.enableHelicopter(time); + marble.enableHelicopter(timeState.currentAttemptTime); // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) // AudioManager.play(this.sounds[1]); diff --git a/src/shapes/LandMine.hx b/src/shapes/LandMine.hx index eddf49da..77fd7259 100644 --- a/src/shapes/LandMine.hx +++ b/src/shapes/LandMine.hx @@ -1,5 +1,6 @@ package shapes; +import src.TimeState; import collision.CollisionHull; import collision.CollisionInfo; import src.DtsObject; @@ -112,10 +113,10 @@ class LandMine extends DtsObject { landMineSparkParticleData.texture = ResourceLoader.getTexture("data/particles/spark.png"); } - override function onMarbleContact(time:Float, ?contact:CollisionInfo) { + override function onMarbleContact(timeState:TimeState, ?contact:CollisionInfo) { if (this.isCollideable) { // marble.velocity = marble.velocity.add(vec); - this.disappearTime = this.level.currentTime; + this.disappearTime = timeState.timeSinceLoad; this.setCollisionEnabled(false); // if (!this.level.rewinding) @@ -143,9 +144,9 @@ class LandMine extends DtsObject { return v; } - override function update(currentTime:Float, dt:Float) { - super.update(currentTime, dt); - if (currentTime >= this.disappearTime + 5 || currentTime < this.disappearTime) { + override function update(timeState:TimeState) { + super.update(timeState); + if (timeState.timeSinceLoad >= this.disappearTime + 5 || timeState.timeSinceLoad < this.disappearTime) { this.setHide(false); } else { this.setHide(true); @@ -164,7 +165,7 @@ class LandMine extends DtsObject { } } - var opacity = Util.clamp((currentTime - (this.disappearTime + 5)), 0, 1); + var opacity = Util.clamp((timeState.timeSinceLoad - (this.disappearTime + 5)), 0, 1); this.setOpacity(opacity); } } diff --git a/src/shapes/PowerUp.hx b/src/shapes/PowerUp.hx index cd88c128..5e84dc8c 100644 --- a/src/shapes/PowerUp.hx +++ b/src/shapes/PowerUp.hx @@ -1,5 +1,6 @@ package shapes; +import src.TimeState; import src.Util; import h3d.Vector; import src.DtsObject; @@ -35,29 +36,29 @@ abstract class PowerUp extends DtsObject { this.ambientRotate = true; } - public override function onMarbleInside(time:Float) { - var pickupable = this.lastPickUpTime == -1 || (time - this.lastPickUpTime) >= this.cooldownDuration; + public override function onMarbleInside(timeState:TimeState) { + var pickupable = this.lastPickUpTime == -1 || (timeState.currentAttemptTime - this.lastPickUpTime) >= this.cooldownDuration; if (!pickupable) return; if (this.pickUp()) { // this.level.replay.recordMarbleInside(this); - this.lastPickUpTime = time; + this.lastPickUpTime = timeState.currentAttemptTime; if (this.autoUse) - this.use(time); + this.use(timeState); this.level.displayAlert('You picked up a ${this.pickUpName}!'); // if (this.element.showhelponpickup === "1" && !this.autoUse) displayHelp(`Press to use the ${this.pickUpName}!`); } } - public override function update(currentTime:Float, dt:Float) { - super.update(currentTime, dt); + public override function update(timeState:TimeState) { + super.update(timeState); var opacity = 1.0; if (this.lastPickUpTime > 0 && this.cooldownDuration > 0) { var availableTime = this.lastPickUpTime + this.cooldownDuration; - opacity = Util.clamp((currentTime - availableTime), 0, 1); + opacity = Util.clamp((timeState.currentAttemptTime - availableTime), 0, 1); } this.setOpacity(opacity); @@ -65,5 +66,5 @@ abstract class PowerUp extends DtsObject { public abstract function pickUp():Bool; - public abstract function use(time:Float):Void; + public abstract function use(timeState:TimeState):Void; } diff --git a/src/shapes/ShockAbsorber.hx b/src/shapes/ShockAbsorber.hx index e2bf20fe..f6655fb5 100644 --- a/src/shapes/ShockAbsorber.hx +++ b/src/shapes/ShockAbsorber.hx @@ -1,5 +1,6 @@ package shapes; +import src.TimeState; import src.DtsObject; class ShockAbsorber extends PowerUp { @@ -16,9 +17,9 @@ class ShockAbsorber extends PowerUp { return this.level.pickUpPowerUp(this); } - public function use(time:Float) { + public function use(timeState:TimeState) { var marble = this.level.marble; - marble.enableShockAbsorber(time); + marble.enableShockAbsorber(timeState.currentAttemptTime); // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) // AudioManager.play(this.sounds[1]); diff --git a/src/shapes/SuperBounce.hx b/src/shapes/SuperBounce.hx index a3e486a7..bc40b9bf 100644 --- a/src/shapes/SuperBounce.hx +++ b/src/shapes/SuperBounce.hx @@ -1,5 +1,6 @@ package shapes; +import src.TimeState; import src.DtsObject; class SuperBounce extends PowerUp { @@ -16,9 +17,9 @@ class SuperBounce extends PowerUp { return this.level.pickUpPowerUp(this); } - public function use(time:Float) { + public function use(timeState:TimeState) { var marble = this.level.marble; - marble.enableSuperBounce(time); + marble.enableSuperBounce(timeState.currentAttemptTime); // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) // AudioManager.play(this.sounds[1]); diff --git a/src/shapes/SuperJump.hx b/src/shapes/SuperJump.hx index d01da720..5bea0af0 100644 --- a/src/shapes/SuperJump.hx +++ b/src/shapes/SuperJump.hx @@ -1,5 +1,6 @@ package shapes; +import src.TimeState; import src.ResourceLoader; import src.ParticleSystem.ParticleData; import h3d.Vector; @@ -47,7 +48,7 @@ class SuperJump extends PowerUp { return this.level.pickUpPowerUp(this); } - public function use(time:Float) { + public function use(timeState:TimeState) { var marble = this.level.marble; marble.velocity = marble.velocity.add(this.level.currentUp.multiply(20)); this.level.particleManager.createEmitter(superJumpParticleOptions, this.sjEmitterParticleData, null, () -> marble.getAbsPos().getPosition()); diff --git a/src/shapes/SuperSpeed.hx b/src/shapes/SuperSpeed.hx index 524ac5ff..6139a6b3 100644 --- a/src/shapes/SuperSpeed.hx +++ b/src/shapes/SuperSpeed.hx @@ -1,5 +1,6 @@ package shapes; +import src.TimeState; import src.ResourceLoader; import src.ParticleSystem.ParticleData; import src.ParticleSystem.ParticleEmitterOptions; @@ -54,7 +55,7 @@ class SuperSpeed extends PowerUp { return this.level.pickUpPowerUp(this); } - public function use(time:Float) { + public function use(timeState:TimeState) { var marble = this.level.marble; var movementVector = marble.getMarbleAxis()[0]; diff --git a/src/shapes/TimeTravel.hx b/src/shapes/TimeTravel.hx index c7266f10..f29720c4 100644 --- a/src/shapes/TimeTravel.hx +++ b/src/shapes/TimeTravel.hx @@ -1,5 +1,7 @@ package shapes; +import src.TimeState; + class TimeTravel extends PowerUp { var timeBonus:Float = 5; @@ -19,5 +21,5 @@ class TimeTravel extends PowerUp { return true; } - public function use(time:Float) {} + public function use(time:TimeState) {} } diff --git a/src/shapes/Trapdoor.hx b/src/shapes/Trapdoor.hx index 65e8e74e..058c9d51 100644 --- a/src/shapes/Trapdoor.hx +++ b/src/shapes/Trapdoor.hx @@ -1,5 +1,6 @@ package shapes; +import src.TimeState; import collision.CollisionInfo; import src.Util; import src.DtsObject; @@ -21,12 +22,12 @@ class Trapdoor extends DtsObject { this.hasNonVisualSequences = true; } - public override function update(currentTime:Float, dt:Float) { - var currentCompletion = this.getCurrentCompletion(currentTime); + public override function update(timeState:TimeState) { + var currentCompletion = this.getCurrentCompletion(timeState); // Override the keyframe this.sequenceKeyframeOverride.set(this.dts.sequences[0], currentCompletion * (this.dts.sequences[0].numKeyFrames - 1)); - super.update(currentTime, dt); + super.update(timeState); var diff = (currentCompletion - this.lastCompletion); var direction = 0; @@ -43,22 +44,22 @@ class Trapdoor extends DtsObject { this.lastDirection = direction; } - function getCurrentCompletion(time:Float) { - var elapsed = time - this.lastContactTime; + function getCurrentCompletion(timeState:TimeState) { + var elapsed = timeState.timeSinceLoad - this.lastContactTime; var completion = Util.clamp(elapsed / 1.6666676998138428, 0, 1); if (elapsed > 5) completion = Util.clamp(1 - (elapsed - 5) / 1.6666676998138428, 0, 1); return completion; } - override function onMarbleContact(time:Float, ?contact:CollisionInfo) { - super.onMarbleContact(this.level.currentTime, contact); - if (this.level.currentTime - this.lastContactTime <= 0) + override function onMarbleContact(time:TimeState, ?contact:CollisionInfo) { + super.onMarbleContact(time, contact); + if (time.timeSinceLoad - this.lastContactTime <= 0) return; // The trapdoor is queued to open, so don't do anything. - var currentCompletion = this.getCurrentCompletion(this.level.currentTime); + var currentCompletion = this.getCurrentCompletion(time); // Set the last contact time accordingly so that the trapdoor starts closing (again) - this.lastContactTime = this.level.currentTime - currentCompletion * 1.6666676998138428; + this.lastContactTime = time.timeSinceLoad - currentCompletion * 1.6666676998138428; if (currentCompletion == 0) this.lastContactTime += this.timeout;