diff --git a/src/Marble.hx b/src/Marble.hx index 466eba6a..571df94a 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1,5 +1,7 @@ package src; +import h3d.shader.AlphaMult; +import shaders.DtsTexture; import collision.gjk.GJK; import collision.gjk.ConvexHull; import hxd.snd.effect.Pitch; @@ -167,6 +169,8 @@ class Marble extends GameObject { var shockAbsorberEnableTime:Float = -1e8; var helicopterEnableTime:Float = -1e8; + var teleportEnableTime:Null = null; + var teleportDisableTime:Null = null; var bounceEmitDelay:Float = 0; var bounceEmitterData:ParticleData; @@ -187,6 +191,10 @@ class Marble extends GameObject { public var prevPos:Vector; + var cloak:Bool = false; + + var teleporting:Bool = false; + public function new() { super(); var geom = Sphere.defaultUnitSphere(); @@ -195,6 +203,11 @@ class Marble extends GameObject { var marbleMaterial = Material.create(marbleTexture); marbleMaterial.shadows = false; marbleMaterial.castShadows = true; + // marbleMaterial.mainPass.removeShader(marbleMaterial.textureShader); + // var dtsShader = new DtsTexture(); + // dtsShader.texture = marbleTexture; + // dtsShader.currentOpacity = 1; + // marbleMaterial.mainPass.addShader(dtsShader); var obj = new Mesh(geom, marbleMaterial, this); obj.scale(_radius); @@ -1396,6 +1409,7 @@ class Marble extends GameObject { } updatePowerupStates(timeState.currentAttemptTime, timeState.dt); + this.updateTeleporterState(timeState); this.trailEmitter(); if (bounceEmitDelay > 0) @@ -1453,6 +1467,47 @@ class Marble extends GameObject { this.helicopterEnableTime = time; } + function updateTeleporterState(time:TimeState) { + var teleportFadeCompletion:Float = 0; + + if (this.teleportEnableTime != null) + teleportFadeCompletion = Util.clamp((time.currentAttemptTime - this.teleportEnableTime) / 0.5, 0, 1); + if (this.teleportDisableTime != null) + teleportFadeCompletion = Util.clamp(1 - (time.currentAttemptTime - this.teleportDisableTime) / 0.5, 0, 1); + + if (teleportFadeCompletion > 0) { + var mesh:Mesh = cast this.children[0]; + var shad:AlphaMult = mesh.material.mainPass.getShader(AlphaMult); + if (shad == null) { + shad = new AlphaMult(); + mesh.material.mainPass.addShader(shad); + mesh.material.blendMode = Alpha; + this.teleporting = true; + } + shad.alpha = Util.lerp(1, 0.25, teleportFadeCompletion); + } else { + if (this.teleporting) { + var mesh:Mesh = cast this.children[0]; + mesh.material.mainPass.removeShader(mesh.material.mainPass.getShader(AlphaMult)); + mesh.material.blendMode = None; + this.teleporting = false; + } + } + } + + public function setCloaking(active:Bool, time:TimeState) { + this.cloak = active; + if (this.cloak) { + var completion = (this.teleportDisableTime != null) ? Util.clamp((time.currentAttemptTime - this.teleportDisableTime) / 0.5, 0, 1) : 1; + this.teleportEnableTime = time.currentAttemptTime - 0.5 * (1 - completion); + this.teleportDisableTime = null; + } else { + var completion = Util.clamp((time.currentAttemptTime - this.teleportEnableTime) / 0.5, 0, 1); + this.teleportDisableTime = time.currentAttemptTime - 0.5 * (1 - completion); + this.teleportEnableTime = null; + } + } + public override function reset() { this.velocity = new Vector(); this.collider.velocity = new Vector(); @@ -1461,5 +1516,14 @@ class Marble extends GameObject { this.shockAbsorberEnableTime = Math.NEGATIVE_INFINITY; this.helicopterEnableTime = Math.NEGATIVE_INFINITY; this.lastContactNormal = new Vector(0, 0, 1); + this.cloak = false; + if (this.teleporting) { + var mesh:Mesh = cast this.children[0]; + mesh.material.mainPass.removeShader(mesh.material.mainPass.getShader(AlphaMult)); + mesh.material.blendMode = None; + } + this.teleporting = false; + this.teleportDisableTime = null; + this.teleportEnableTime = null; } } diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index a8d5a30c..c802ab0e 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1,5 +1,7 @@ package src; +import triggers.TeleportTrigger; +import triggers.DestinationTrigger; import shapes.Nuke; import shapes.Magnet; import src.Replay; @@ -744,6 +746,10 @@ class MarbleWorld extends Scheduler { trigger = new InBoundsTrigger(element, cast this); } else if (element.datablock == "HelpTrigger") { trigger = new HelpTrigger(element, cast this); + } else if (element.datablock == "TeleportTrigger") { + trigger = new TeleportTrigger(element, cast this); + } else if (element.datablock == "DestinationTrigger") { + trigger = new DestinationTrigger(element, cast this); } else { onFinish(); return; @@ -907,6 +913,9 @@ class MarbleWorld extends Scheduler { for (obj in dtsObjects) { obj.update(timeState); } + for (obj in triggers) { + obj.update(timeState); + } ProfilerUI.measure("updateMarbles"); for (marble in marbles) { marble.update(timeState, collisionWorld, this.pathedInteriors); diff --git a/src/mis/MisParser.hx b/src/mis/MisParser.hx index ace186cd..ff501b99 100644 --- a/src/mis/MisParser.hx +++ b/src/mis/MisParser.hx @@ -519,4 +519,15 @@ class MisParser { } return result; } + + /** Parses a boolean value. */ + public static function parseBoolean(string:String) { + if (string == null) + return false; + if (string == "") + return false; + if (string == "0") + return false; + return true; + } } diff --git a/src/mis/MissionElement.hx b/src/mis/MissionElement.hx index 93e351b8..233661af 100644 --- a/src/mis/MissionElement.hx +++ b/src/mis/MissionElement.hx @@ -227,6 +227,22 @@ class MissionElementTrigger extends MissionElementBase { var instant:Null; var icontinuetottime:Null; + // checkpoint stuff: + var respawnpoint:Null; + var add:Null; + var sub:Null; + var gravity:Null; + var disableOob:Null; + // teleport/destination trigger stuff: + var destination:Null; + var delay:Null; + var centerdestpoint:Null; + var keepvelocity:Null; + var inversevelocity:Null; + var keepangular:Null; + var keepcamera:Null; + var camerayaw:Null; + public function new() { _type = MissionElementType.Trigger; } diff --git a/src/triggers/DestinationTrigger.hx b/src/triggers/DestinationTrigger.hx new file mode 100644 index 00000000..1276e523 --- /dev/null +++ b/src/triggers/DestinationTrigger.hx @@ -0,0 +1,5 @@ +package triggers; + +class DestinationTrigger extends Trigger { + // Stub +} diff --git a/src/triggers/TeleportTrigger.hx b/src/triggers/TeleportTrigger.hx new file mode 100644 index 00000000..47c88194 --- /dev/null +++ b/src/triggers/TeleportTrigger.hx @@ -0,0 +1,119 @@ +package triggers; + +import h3d.Vector; +import src.ResourceLoader; +import src.AudioManager; +import mis.MisParser; +import src.MarbleWorld; +import mis.MissionElement.MissionElementTrigger; + +class TeleportTrigger extends Trigger { + var delay:Float = 2; + + var entryTime:Null = null; + var exitTime:Null = null; + + public function new(element:MissionElementTrigger, level:MarbleWorld) { + super(element, level); + if (element.delay != null) + this.delay = MisParser.parseNumber(element.delay) / 1000; + } + + override function onMarbleEnter(time:src.TimeState) { + this.exitTime = null; + this.level.marble.setCloaking(true, time); + if (this.entryTime != null) + return; + this.entryTime = time.currentAttemptTime; + this.level.displayAlert("Teleporter has been activated, please wait."); + AudioManager.playSound(ResourceLoader.getResource("data/sound/teleport.wav", ResourceLoader.getAudio, this.soundResources)); + } + + override function onMarbleLeave(time:src.TimeState) { + this.exitTime = time.currentAttemptTime; + this.level.marble.setCloaking(false, time); + } + + public override function update(timeState:src.TimeState) { + if (this.entryTime == null) + return; + + if (timeState.currentAttemptTime - this.entryTime >= this.delay) { + this.executeTeleport(); + return; + } + + // There's a little delay after exiting before the teleporter gets cancelled + if (this.exitTime != null && timeState.currentAttemptTime - this.exitTime > 0.050) { + this.entryTime = null; + this.exitTime = null; + return; + } + } + + override function init(onFinish:() -> Void) { + ResourceLoader.load("sound/teleport.wav").entry.load(onFinish); + } + + function executeTeleport() { + this.entryTime = null; + + function chooseNonNull(a:String, b:String) { + if (a != null) + return a; + if (b != null) + return b; + return null; + } + + // Find the destination trigger + if (this.element.destination == null) + return; + var destinationList = this.level.triggers.filter(x -> x is DestinationTrigger + && x.element._name.toLowerCase() == this.element.destination.toLowerCase()); + if (destinationList.length == 0) + return; // Who knows + + var destination = destinationList[0]; + + var pos = MisParser.parseVector3(destination.element.position); + pos.x = -pos.x; + + // Determine where to place the marble + var position:Vector; + if (MisParser.parseBoolean(chooseNonNull(this.element.centerdestpoint, destination.element.centerdestpoint))) { + position = destination.collider.boundingBox.getCenter().toVector(); // Put the marble in the middle of the thing + } else { + position = destination.vertices[0].add(new Vector(0, 0, 3)).add(pos); // destination.vertices[0].clone().add(new Vector(0, 0, 3)); + } + this.level.marble.prevPos.load(position); + this.level.marble.setPosition(position.x, position.y, position.z); + + if (!MisParser.parseBoolean(chooseNonNull(this.element.keepvelocity, destination.element.keepvelocity))) + this.level.marble.velocity.set(0, 0, 0); + if (MisParser.parseBoolean(chooseNonNull(this.element.inversevelocity, destination.element.inversevelocity))) + this.level.marble.velocity.scale(-1); + if (!MisParser.parseBoolean(chooseNonNull(this.element.keepangular, destination.element.keepangular))) + this.level.marble.omega.set(0, 0, 0); + + // Determine camera orientation + if (!MisParser.parseBoolean(chooseNonNull(this.element.keepcamera, destination.element.keepcamera))) { + var yaw:Float; + if (this.element.camerayaw != null) + yaw = MisParser.parseNumber(this.element.camerayaw) * Math.PI / 180; + else if (destination.element.camerayaw != null) + yaw = MisParser.parseNumber(destination.element.camerayaw) * Math.PI / 180; + else + yaw = 0; + + yaw = -yaw; // Need to flip it for some reason + + this.level.marble.camera.CameraYaw = yaw + Math.PI / 2; + this.level.marble.camera.CameraPitch = 0.45; + this.level.marble.camera.nextCameraYaw = yaw + Math.PI / 2; + this.level.marble.camera.nextCameraPitch = 0.45; + } + + AudioManager.playSound(ResourceLoader.getResource("data/sound/spawn.wav", ResourceLoader.getAudio, this.soundResources)); + } +} diff --git a/src/triggers/Trigger.hx b/src/triggers/Trigger.hx index d7d00ae2..ca006718 100644 --- a/src/triggers/Trigger.hx +++ b/src/triggers/Trigger.hx @@ -1,5 +1,6 @@ package triggers; +import src.TimeState; import h3d.scene.Mesh; import h3d.mat.Material; import h3d.prim.Cube; @@ -17,6 +18,8 @@ class Trigger extends GameObject { var level:MarbleWorld; var element:MissionElementTrigger; + var vertices:Array; + public var collider:BoxCollisionEntity; public function new(element:MissionElementTrigger, level:MarbleWorld) { @@ -43,7 +46,7 @@ class Trigger extends GameObject { var mat = new Matrix(); var quat = MisParser.parseRotation(element.rotation); - // quat.x = -quat.x; + quat.x = -quat.x; // quat.w = -quat.w; quat.toMatrix(mat); var scale = MisParser.parseVector3(element.scale); @@ -52,7 +55,7 @@ class Trigger extends GameObject { pos.x = -pos.x; // mat.setPosition(pos); - var vertices = [p1, p2, p3, p4, p5, p6, p7, p8].map((vert) -> vert.transformed(mat)); + vertices = [p1, p2, p3, p4, p5, p6, p7, p8].map((vert) -> vert.transformed(mat)); var boundingbox = new Bounds(); for (vector in vertices) { @@ -74,6 +77,8 @@ class Trigger extends GameObject { // mesh.setPosition(boundingbox.xMin, boundingbox.yMin, boundingbox.zMin); } + public function update(timeState:TimeState) {} + public function init(onFinish:Void->Void) { onFinish(); }