diff --git a/src/Marble.hx b/src/Marble.hx index d3cfbdd2..d65446cb 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -114,6 +114,56 @@ final trailParticleOptions:ParticleEmitterOptions = { } }; +final blastParticleOptions:ParticleEmitterOptions = { + ejectionPeriod: 1, + ambientVelocity: new Vector(0, 0, -0.3), + ejectionVelocity: 4, + velocityVariance: 0, + emitterLifetime: 300, + inheritedVelFactor: 0, + particleOptions: { + texture: 'particles/smoke.png', + blending: Alpha, + spinSpeed: 20, + spinRandomMin: 0, + spinRandomMax: 0, + lifetime: 500, + lifetimeVariance: 100, + dragCoefficient: 1, + acceleration: 0, + colors: [new Vector(0, 1, 1, 0.1), new Vector(0, 1, 1, 0.5), new Vector(0, 1, 1, 0.9)], + sizes: [0.125, 0.125, 0.125], + times: [0, 0.4, 1] + } +} + +final blastMaxParticleOptions:ParticleEmitterOptions = { + ejectionPeriod: 1, + ambientVelocity: new Vector(0, 0, -0.3), + ejectionVelocity: 4, + velocityVariance: 0, + emitterLifetime: 300, + inheritedVelFactor: 0, + particleOptions: { + texture: 'particles/smoke.png', + blending: Alpha, + spinSpeed: 20, + spinRandomMin: 0, + spinRandomMax: 0, + lifetime: 500, + lifetimeVariance: 100, + dragCoefficient: 1, + acceleration: 0, + colors: [ + new Vector(1, 0.7, 0, 0.1), + new Vector(1, 0.7, 0, 0.5), + new Vector(1, 0.7, 0, 0.9) + ], + sizes: [0.125, 0.125, 0.125], + times: [0, 0.4, 1] + } +} + class Marble extends GameObject { public var camera:CameraController; public var cameraObject:Object; @@ -178,6 +228,8 @@ class Marble extends GameObject { var bounceEmitterData:ParticleData; var trailEmitterData:ParticleData; + var blastEmitterData:ParticleData; + var blastMaxEmitterData:ParticleData; var trailEmitterNode:ParticleEmitter; var rollSound:Channel; @@ -215,6 +267,14 @@ class Marble extends GameObject { this.trailEmitterData.identifier = "MarbleTrailParticle"; this.trailEmitterData.texture = ResourceLoader.getResource("data/particles/smoke.png", ResourceLoader.getTexture, this.textureResources); + this.blastEmitterData = new ParticleData(); + this.blastEmitterData.identifier = "MarbleBlastParticle"; + this.blastEmitterData.texture = ResourceLoader.getResource("data/particles/smoke.png", ResourceLoader.getTexture, this.textureResources); + + this.blastMaxEmitterData = new ParticleData(); + this.blastMaxEmitterData.identifier = "MarbleBlastMaxParticle"; + this.blastMaxEmitterData.texture = ResourceLoader.getResource("data/particles/smoke.png", ResourceLoader.getTexture, this.textureResources); + this.rollSound = AudioManager.playSound(ResourceLoader.getResource("data/sound/rolling_hard.wav", ResourceLoader.getAudio, this.soundResources), this.getAbsPos().getPosition(), true); this.slipSound = AudioManager.playSound(ResourceLoader.getResource("data/sound/sliding.wav", ResourceLoader.getAudio, this.soundResources), @@ -1501,6 +1561,21 @@ class Marble extends GameObject { } } + public function useBlast() { + if (this.level.blastAmount < 0.2 || this.level.game != "ultra") + return; + var impulse = this.level.currentUp.multiply(Math.max(Math.sqrt(this.level.blastAmount), this.level.blastAmount) * 10); + this.applyImpulse(impulse); + AudioManager.playSound(ResourceLoader.getResource('data/sound/blast.wav', ResourceLoader.getAudio, this.soundResources)); + this.level.particleManager.createEmitter(this.level.blastAmount > 1 ? blastMaxParticleOptions : blastParticleOptions, + this.level.blastAmount > 1 ? blastMaxEmitterData : blastEmitterData, this.getAbsPos().getPosition(), () -> { + this.getAbsPos().getPosition().add(this.level.currentUp.multiply(-this._radius * 0.4)); + }, + new Vector(1, 1, + 1).add(new Vector(Math.abs(this.level.currentUp.x), Math.abs(this.level.currentUp.y), Math.abs(this.level.currentUp.z)).multiply(-0.8))); + this.level.blastAmount = 0; + } + public function applyImpulse(impulse:Vector) { this.appliedImpulses.push(impulse); } diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 61549677..a2164c80 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -119,6 +119,7 @@ class MarbleWorld extends Scheduler { public var scene:Scene; public var scene2d:h2d.Scene; public var mission:Mission; + public var game:String; public var marble:Marble; public var worldOrientation:Quat; @@ -130,6 +131,7 @@ class MarbleWorld extends Scheduler { public var finishYaw:Float; public var totalGems:Int = 0; public var gemCount:Int = 0; + public var blastAmount:Float = 0; public var cursorLock:Bool = true; @@ -153,6 +155,7 @@ class MarbleWorld extends Scheduler { var checkpointCollectedGems:Map = []; var checkpointHeldPowerup:PowerUp = null; var checkpointUp:Vector = null; + var cheeckpointBlast:Float = 0; // Replay public var replay:Replay; @@ -179,6 +182,7 @@ class MarbleWorld extends Scheduler { this.scene = scene; this.scene2d = scene2d; this.mission = mission; + this.game = mission.game.toLowerCase(); this.replay = new Replay(mission.path); this.isRecording = record; } @@ -238,7 +242,7 @@ class MarbleWorld extends Scheduler { public function postInit() { // Add the sky at the last so that cubemap reflections work - this.playGui.init(this.scene2d, () -> { + this.playGui.init(this.scene2d, this.mission.game.toLowerCase(), () -> { this.scene.addChild(this.sky); this._ready = true; var musicFileName = 'data/sound/music/' + this.mission.missionInfo.music; @@ -300,7 +304,7 @@ class MarbleWorld extends Scheduler { worker.loadFile(file); } - this.scene.camera.zFar = Math.max(2000, Std.parseFloat(this.skyElement.visibledistance)); + this.scene.camera.zFar = Math.max(4000, Std.parseFloat(this.skyElement.visibledistance)); this.sky = new Sky(); @@ -384,6 +388,7 @@ class MarbleWorld extends Scheduler { this.timeState.gameplayClock = 0; this.bonusTime = 0; this.outOfBounds = false; + this.blastAmount = 0; this.outOfBoundsTime = null; this.finishTime = null; this.helpTextTimeState = Math.NEGATIVE_INFINITY; @@ -398,6 +403,7 @@ class MarbleWorld extends Scheduler { this.checkpointCollectedGems.clear(); this.checkpointHeldPowerup = null; this.checkpointUp = null; + this.cheeckpointBlast = 0; if (this.endPad != null) this.endPad.inFinish = false; @@ -1132,6 +1138,10 @@ class MarbleWorld extends Scheduler { this.tickSchedule(timeState.currentAttemptTime); + if (Key.isPressed(Settings.controlsSettings.blast) && !this.isWatching && this.game == "ultra") { + this.marble.useBlast(); + } + // Replay gravity if (this.isWatching) { if (this.replay.currentPlaybackFrame.gravityChange) { @@ -1143,6 +1153,7 @@ class MarbleWorld extends Scheduler { } this.updateGameState(); + this.updateBlast(timeState); ProfilerUI.measure("updateDTS"); for (obj in dtsObjects) { obj.update(timeState); @@ -1315,6 +1326,15 @@ class MarbleWorld extends Scheduler { this.replay.recordTimeState(timeState.currentAttemptTime, timeState.gameplayClock, this.bonusTime); } + function updateBlast(timestate:TimeState) { + if (this.game == "ultra") { + if (this.blastAmount < 1) { + this.blastAmount = Util.clamp(this.blastAmount + (timeState.dt / 25), 0, 1); + } + this.playGui.setBlastValue(this.blastAmount); + } + } + function updateTexts() { var helpTextTime = this.helpTextTimeState; var alertTextTime = this.alertTextTimeState; @@ -1689,6 +1709,7 @@ class MarbleWorld extends Scheduler { this.currentCheckpointTrigger = trigger; this.checkpointCollectedGems.clear(); this.checkpointUp = this.currentUp.clone(); + this.cheeckpointBlast = this.blastAmount; // Remember all gems that were collected up to this point for (gem in this.gems) { if (gem.pickedUp) @@ -1735,6 +1756,7 @@ class MarbleWorld extends Scheduler { this.marble.camera.nextCameraYaw = this.marble.camera.CameraYaw; this.marble.camera.nextCameraPitch = this.marble.camera.CameraPitch; this.marble.camera.oob = false; + this.blastAmount = this.cheeckpointBlast; if (this.isRecording) { this.replay.recordCameraState(this.marble.camera.CameraYaw, this.marble.camera.CameraPitch); this.replay.recordMarbleInput(0, 0); diff --git a/src/ParticleSystem.hx b/src/ParticleSystem.hx index e4be8ed1..f0f01d70 100644 --- a/src/ParticleSystem.hx +++ b/src/ParticleSystem.hx @@ -220,12 +220,14 @@ class ParticleEmitter { var creationTime:Float; var vel = new Vector(); var getPos:Void->Vector; + var spawnSphereSquish:Vector; - public function new(options:ParticleEmitterOptions, data:ParticleData, manager:ParticleManager, ?getPos:Void->Vector) { + public function new(options:ParticleEmitterOptions, data:ParticleData, manager:ParticleManager, ?getPos:Void->Vector, ?spawnSphereSquish:Vector) { this.o = options; this.manager = manager; this.getPos = getPos; this.data = data; + this.spawnSphereSquish = spawnSphereSquish != null ? spawnSphereSquish : new Vector(1, 1, 1); } public function spawn(time:Float) { @@ -257,6 +259,9 @@ class ParticleEmitter { 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(); + randomPointOnSphere.x *= this.spawnSphereSquish.x; + randomPointOnSphere.y *= this.spawnSphereSquish.y; + randomPointOnSphere.z *= this.spawnSphereSquish.z; // Compute the total velocity var initialVel = this.o.ejectionVelocity; initialVel += (this.o.velocityVariance * 2 * Math.random()) - this.o.velocityVariance; @@ -340,8 +345,8 @@ class ParticleManager { return this.currentTime; } - public function createEmitter(options:ParticleEmitterOptions, data:ParticleData, initialPos:Vector, ?getPos:Void->Vector) { - var emitter = new ParticleEmitter(options, data, cast this, getPos); + public function createEmitter(options:ParticleEmitterOptions, data:ParticleData, initialPos:Vector, ?getPos:Void->Vector, ?spawnSphereSquish:Vector) { + var emitter = new ParticleEmitter(options, data, cast this, getPos, spawnSphereSquish); emitter.currPos = (getPos != null) ? getPos() : initialPos.clone(); if (emitter.currPos == null) emitter.currPos = initialPos.clone(); diff --git a/src/Settings.hx b/src/Settings.hx index f39be520..ad052ce0 100644 --- a/src/Settings.hx +++ b/src/Settings.hx @@ -55,6 +55,7 @@ typedef ControlsSettings = { var cameraSensitivity:Float; var invertYAxis:Bool; var respawn:Int; + var blast:Int; } typedef TouchSettings = { @@ -115,7 +116,8 @@ class Settings { alwaysFreeLook: true, cameraSensitivity: 0.6, invertYAxis: false, - respawn: Key.BACKSPACE + respawn: Key.BACKSPACE, + blast: Key.E }; public static var touchSettings:TouchSettings = { diff --git a/src/gui/OptionsDlg.hx b/src/gui/OptionsDlg.hx index 0697728f..7c2f32a0 100644 --- a/src/gui/OptionsDlg.hx +++ b/src/gui/OptionsDlg.hx @@ -383,10 +383,11 @@ class OptionsDlg extends GuiImage { hotkeysPanel); makeRemapOption("Respawn:", 278, Util.getKeyForButton2(Settings.controlsSettings.respawn), (key) -> Settings.controlsSettings.respawn = key, hotkeysPanel, true); + makeRemapOption("Blast:", 326, Util.getKeyForButton2(Settings.controlsSettings.blast), (key) -> Settings.controlsSettings.blast = key, hotkeysPanel); if (Util.isTouchDevice()) { var textObj = new GuiText(markerFelt32); - textObj.position = new Vector(5, 326); + textObj.position = new Vector(368, 326); textObj.extent = new Vector(212, 14); textObj.text.text = "Touch Controls"; textObj.text.textColor = 0xFFFFFF; @@ -394,7 +395,7 @@ class OptionsDlg extends GuiImage { hotkeysPanel.addChild(textObj); var remapBtn = new GuiButtonText(loadButtonImages("data/ui/options/bind"), markerFelt24); - remapBtn.position = new Vector(203, 323); + remapBtn.position = new Vector(363 + 203, 323); remapBtn.txtCtrl.text.text = "Edit"; remapBtn.setExtent(new Vector(152, 49)); remapBtn.pressedAction = (sender) -> { diff --git a/src/gui/PlayGui.hx b/src/gui/PlayGui.hx index 2480032d..297f2180 100644 --- a/src/gui/PlayGui.hx +++ b/src/gui/PlayGui.hx @@ -60,6 +60,10 @@ class PlayGui { var alertTextForeground:GuiText; var alertTextBackground:GuiText; + var blastBar:GuiControl; + var blastFill:GuiImage; + var blastFrame:GuiImage; + var imageResources:Array> = []; var textureResources:Array> = []; var soundResources:Array> = []; @@ -97,7 +101,7 @@ class PlayGui { } } - public function init(scene2d:h2d.Scene, onFinish:Void->Void) { + public function init(scene2d:h2d.Scene, game:String, onFinish:Void->Void) { this.scene2d = scene2d; this._init = true; @@ -143,6 +147,8 @@ class PlayGui { }); initCenterText(); initPowerupBox(); + if (game == 'ultra') + initBlastBar(); initTexts(); if (Settings.optionsSettings.frameRateVis) initFPSMeter(); @@ -427,6 +433,45 @@ class PlayGui { playGuiCtrl.addChild(fpsMeterCtrl); } + function initBlastBar() { + blastBar = new GuiControl(); + blastBar.position = new Vector(6, 445); + blastBar.extent = new Vector(120, 28); + blastBar.vertSizing = Top; + this.playGuiCtrl.addChild(blastBar); + + blastFill = new GuiImage(ResourceLoader.getResource("data/ui/game/blastbar_bargreen.png", ResourceLoader.getImage, this.imageResources).toTile()); + blastFill.position = new Vector(5, 5); + blastFill.extent = new Vector(58, 17); + blastFill.doClipping = false; + blastBar.addChild(blastFill); + + blastFrame = new GuiImage(ResourceLoader.getResource("data/ui/game/blastbar.png", ResourceLoader.getImage, this.imageResources).toTile()); + blastFrame.position = new Vector(0, 0); + blastFrame.extent = new Vector(120, 28); + blastBar.addChild(blastFrame); + } + + public function setBlastValue(value:Float) { + if (value <= 1) { + if (blastFill.extent.y == 16) { // Was previously charged + blastFrame.bmp.tile = ResourceLoader.getResource("data/ui/game/blastbar.png", ResourceLoader.getImage, this.imageResources).toTile(); + } + var oldVal = blastFill.extent.x; + blastFill.extent = new Vector(Util.lerp(0, 110, value), 17); + if (oldVal < 22 && blastFill.extent.x >= 22) { + blastFill.bmp.tile = ResourceLoader.getResource("data/ui/game/blastbar_bargreen.png", ResourceLoader.getImage, this.imageResources).toTile(); + } + if (oldVal >= 22 && blastFill.extent.x < 22) { + blastFill.bmp.tile = ResourceLoader.getResource("data/ui/game/blastbar_bargray.png", ResourceLoader.getImage, this.imageResources).toTile(); + } + } else { + blastFill.extent = new Vector(0, 16); // WE will just use this extra number to store whether it was previously charged or not + blastFrame.bmp.tile = ResourceLoader.getResource("data/ui/game/blastbar_charged.png", ResourceLoader.getImage, this.imageResources).toTile(); + } + this.blastBar.render(scene2d); + } + public function setHelpTextOpacity(value:Float) { helpTextForeground.text.color.a = value; helpTextBackground.text.color.a = value; diff --git a/src/shaders/CubemapRenderer.hx b/src/shaders/CubemapRenderer.hx index 774fb47e..eafa10ab 100644 --- a/src/shaders/CubemapRenderer.hx +++ b/src/shaders/CubemapRenderer.hx @@ -20,7 +20,7 @@ class CubemapRenderer { this.scene = scene; this.sky = sky; this.cubemap = new Texture(128, 128, [Cube, Dynamic, Target], h3d.mat.Data.TextureFormat.RGB8); - this.camera = new Camera(90, 1, 1, 0.02); + this.camera = new Camera(90, 1, 1, 0.02, scene.camera.zFar); this.position = new Vector(); this.nextFaceToRender = 0; } diff --git a/src/shapes/Blast.hx b/src/shapes/Blast.hx index 467f3d5b..cbba68ea 100644 --- a/src/shapes/Blast.hx +++ b/src/shapes/Blast.hx @@ -31,7 +31,6 @@ class Blast extends PowerUp { } public function use(timeState:TimeState) { - var marble = this.level.marble; - this.level.deselectPowerUp(); + this.level.blastAmount = 1.03; } }