From d98da24da28f07c7849a3a6b446d25f7a0f2d7f2 Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Sat, 1 Jul 2023 14:54:39 +0530 Subject: [PATCH] hunt mode! --- data/skies/gemCubemapUp2.png | Bin 0 -> 3297 bytes data/skies/gemCubemapUp3.png | Bin 0 -> 3163 bytes src/Marble.hx | 4 +- src/MarbleWorld.hx | 127 ++++++++-------- src/Radar.hx | 3 +- src/mis/MissionElement.hx | 4 + src/modes/GameMode.hx | 29 ++++ src/modes/HuntMode.hx | 279 +++++++++++++++++++++++++++++++++++ src/modes/NullMode.hx | 82 ++++++++++ src/octree/PriorityQueue.hx | 2 +- src/shapes/Gem.hx | 65 +++++++- src/shapes/GemBeam.hx | 94 ++++++++++++ 12 files changed, 621 insertions(+), 68 deletions(-) create mode 100644 data/skies/gemCubemapUp2.png create mode 100644 data/skies/gemCubemapUp3.png create mode 100644 src/modes/GameMode.hx create mode 100644 src/modes/HuntMode.hx create mode 100644 src/modes/NullMode.hx create mode 100644 src/shapes/GemBeam.hx diff --git a/data/skies/gemCubemapUp2.png b/data/skies/gemCubemapUp2.png new file mode 100644 index 0000000000000000000000000000000000000000..5fc78d0df1bd91a1552153df6ecf8aa776030767 GIT binary patch literal 3297 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 literal 0 HcmV?d00001 diff --git a/data/skies/gemCubemapUp3.png b/data/skies/gemCubemapUp3.png new file mode 100644 index 0000000000000000000000000000000000000000..021783c2e9c91dc470a34647dd173a0a42f0fa59 GIT binary patch literal 3163 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 B7Z?Bl literal 0 HcmV?d00001 diff --git a/src/Marble.hx b/src/Marble.hx index add302ae..4111c25a 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -239,6 +239,7 @@ class Marble extends GameObject { public var heldPowerup:PowerUp; public var lastContactNormal:Vector; + public var lastContactPosition:Vector; var helicopter:HelicopterImage; var blastWave:BlastWave; @@ -269,8 +270,6 @@ class Marble extends GameObject { public var mode:Mode = Play; - public var startPad:StartPad; - public var prevPos:Vector; var cloak:Bool = false; @@ -866,6 +865,7 @@ class Marble extends GameObject { a.set(a.x + aFriction.x, a.y + aFriction.y, a.z + aFriction.z); lastContactNormal = bestContact.normal; + lastContactPosition = this.getAbsPos().getPosition(); } a.set(a.x + aControl.x, a.y + aControl.y, a.z + aControl.z); if (this.mode == Finish) { diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 9b1aea63..0e83056e 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -89,6 +89,9 @@ import src.ResourceLoaderWorker; import haxe.io.Path; import src.Console; import src.Gamepad; +import modes.GameMode; +import modes.NullMode; +import modes.GameMode.GameModeFactory; class MarbleWorld extends Scheduler { public var collisionWorld:CollisionWorld; @@ -120,6 +123,8 @@ class MarbleWorld extends Scheduler { var endPad:EndPad; var skyElement:MissionElementSky; + public var gameMode:GameMode; + // Lighting public var ambient:Vector; public var dirLight:Vector; @@ -203,6 +208,7 @@ class MarbleWorld extends Scheduler { this.scene2d = scene2d; this.mission = mission; this.game = mission.game.toLowerCase(); + this.gameMode = GameModeFactory.getGameMode(this, mission.missionInfo.gamemode); this.replay = new Replay(mission.path, mission.isClaMission ? mission.id : 0); this.isRecording = record; this.rewindManager = new RewindManager(this); @@ -254,6 +260,7 @@ class MarbleWorld extends Scheduler { }; this.mission.load(); scanMission(this.mission.root); + this.gameMode.missionScan(this.mission); this.resourceLoadFuncs.push(fwd -> this.initScene(fwd)); this.resourceLoadFuncs.push(fwd -> this.initMarble(fwd)); this.resourceLoadFuncs.push(fwd -> { @@ -428,6 +435,14 @@ class MarbleWorld extends Scheduler { return 0; // Load checkpoint } + if (!full) { + var respawnT = this.gameMode.getRespawnTransform(); + if (respawnT != null) { + respawn(respawnT.position, respawnT.orientation, respawnT.up); + return 0; + } + } + if (!this.isWatching) { this.replay.clear(); } else { @@ -437,7 +452,7 @@ class MarbleWorld extends Scheduler { this.rewindManager.clear(); this.timeState.currentAttemptTime = 0; - this.timeState.gameplayClock = 0; + this.timeState.gameplayClock = this.gameMode.getStartTime(); this.bonusTime = 0; this.outOfBounds = false; this.blastAmount = 0; @@ -486,12 +501,12 @@ class MarbleWorld extends Scheduler { this.cancel(this.oobSchedule); this.cancel(this.oobSchedule2); - var startquat = this.getStartPositionAndOrientation(); + var startquat = this.gameMode.getSpawnTransform(); - this.marble.setMarblePosition(startquat.position.x, startquat.position.y, startquat.position.z + 0.727843); + this.marble.setMarblePosition(startquat.position.x, startquat.position.y, startquat.position.z); this.marble.reset(); - var euler = startquat.quat.toEuler(); + var euler = startquat.orientation.toEuler(); this.marble.camera.init(cast this); this.marble.camera.CameraYaw = euler.z + Math.PI / 2; this.marble.camera.CameraPitch = 0.45; @@ -500,7 +515,6 @@ class MarbleWorld extends Scheduler { this.marble.camera.oob = false; this.marble.camera.finish = false; this.marble.setMode(Start); - this.marble.startPad = cast startquat.pad; sky.follow = marble.camera; var missionInfo:MissionElementScriptObject = cast this.mission.root.elements.filter((element) -> element._type == MissionElementType.ScriptObject @@ -513,18 +527,54 @@ class MarbleWorld extends Scheduler { for (interior in this.interiors) interior.reset(); - this.currentUp = new Vector(0, 0, 1); - this.orientationChangeTime = -1e8; - this.oldOrientationQuat = new Quat(); - this.newOrientationQuat = new Quat(); + this.setUp(startquat.up, this.timeState, true); this.deselectPowerUp(); playGui.setCenterText(''); AudioManager.playSound(ResourceLoader.getResource('data/sound/spawn_alternate.wav', ResourceLoader.getAudio, this.soundResources)); + this.gameMode.onRestart(); + return 0; } + public function respawn(respawnPos:Vector, respawnQuat:Quat, respawnUp:Vector) { + var marble = this.marble; + // Determine where to spawn the marble + this.marble.setMarblePosition(respawnPos.x, respawnPos.y, respawnPos.z); + marble.velocity.set(0, 0, 0); + marble.omega.set(0, 0, 0); + Console.log('Respawn:'); + Console.log('Marble Position: ${respawnPos.x} ${respawnPos.y} ${respawnPos.z}'); + Console.log('Marble Velocity: ${marble.velocity.x} ${marble.velocity.y} ${marble.velocity.z}'); + Console.log('Marble Angular: ${marble.omega.x} ${marble.omega.y} ${marble.omega.z}'); + // Set camera orientation + var euler = respawnQuat.toEuler(); + this.marble.camera.CameraYaw = euler.z + Math.PI / 2; + this.marble.camera.CameraPitch = 0.45; + this.marble.camera.nextCameraYaw = this.marble.camera.CameraYaw; + this.marble.camera.nextCameraPitch = this.marble.camera.CameraPitch; + this.marble.camera.oob = false; + @:privateAccess this.marble.helicopterEnableTime = -1e8; + @:privateAccess this.marble.megaMarbleEnableTime = -1e8; + if (this.isRecording) { + this.replay.recordCameraState(this.marble.camera.CameraYaw, this.marble.camera.CameraPitch); + this.replay.recordMarbleInput(0, 0); + this.replay.recordMarbleState(respawnPos, marble.velocity, marble.getRotationQuat(), marble.omega); + this.replay.recordMarbleStateFlags(false, false, true, false); + } + + // In this case, we set the gravity to the relative "up" vector of the checkpoint shape. + var up = new Vector(0, 0, 1); + up.transform(respawnQuat.toMatrix()); + this.setUp(up, this.timeState, true); + + this.playGui.setCenterText(''); + this.clearSchedule(); + this.outOfBounds = false; + AudioManager.playSound(ResourceLoader.getResource('data/sound/spawn_alternate.wav', ResourceLoader.getAudio, this.soundResources)); + } + public function updateGameState() { if (this.outOfBounds) return; // We will update state manually @@ -539,25 +589,6 @@ class MarbleWorld extends Scheduler { } } - function getStartPositionAndOrientation() { - // The player is spawned at the last start pad in the mission file. - var startPad = this.dtsObjects.filter(x -> x is StartPad).pop(); - var position:Vector; - var quat:Quat = new Quat(); - if (startPad != null) { - // If there's a start pad, start there - position = startPad.getAbsPos().getPosition(); - quat = startPad.getRotationQuat().clone(); - } else { - position = new Vector(0, 0, 300); - } - return { - position: position, - quat: quat, - pad: startPad - }; - } - function addToSimgroup(obj:GameObject, simGroup:MissionElementSimGroup) { if (simGroup == null) return; @@ -1098,11 +1129,13 @@ class MarbleWorld extends Scheduler { var prevGameplayClock = this.timeState.gameplayClock; + var timeMultiplier = this.gameMode.timeMultiplier(); + if (!this.isWatching) { if (this.bonusTime != 0 && this.timeState.currentAttemptTime >= 3.5) { this.bonusTime -= dt; if (this.bonusTime < 0) { - this.timeState.gameplayClock -= this.bonusTime; + this.timeState.gameplayClock -= this.bonusTime * timeMultiplier; this.bonusTime = 0; } if (timeTravelSound == null) { @@ -1115,9 +1148,9 @@ class MarbleWorld extends Scheduler { timeTravelSound = null; } if (this.timeState.currentAttemptTime >= 3.5) { - this.timeState.gameplayClock += dt; + this.timeState.gameplayClock += dt * timeMultiplier; } else if (this.timeState.currentAttemptTime + dt >= 3.5) { - this.timeState.gameplayClock += (this.timeState.currentAttemptTime + dt) - 3.5; + this.timeState.gameplayClock += ((this.timeState.currentAttemptTime + dt) - 3.5) * timeMultiplier; } } this.timeState.currentAttemptTime += dt; @@ -1220,37 +1253,7 @@ class MarbleWorld extends Scheduler { } public function pickUpGem(gem:Gem) { - this.gemCount++; - var string:String; - - // Show a notification (and play a sound) based on the gems remaining - if (this.gemCount == this.totalGems) { - string = "You have all the gems, head for the finish!"; - // if (!this.rewinding) - AudioManager.playSound(ResourceLoader.getResource('data/sound/gem_all.wav', ResourceLoader.getAudio, this.soundResources)); - - // Some levels with this package end immediately upon collection of all gems - // if (this.mission.misFile.activatedPackages.includes('endWithTheGems')) { - // let - // completionOfImpact = this.physics.computeCompletionOfImpactWithBody(gem.bodies[0], 2); // Get the exact point of impact - // this.touchFinish(completionOfImpact); - // } - } else { - string = "You picked up a gem. "; - - var remaining = this.totalGems - this.gemCount; - if (remaining == 1) { - string += "Only one gem to go!"; - } else { - string += '${remaining} gems to go!'; - } - - // if (!this.rewinding) - AudioManager.playSound(ResourceLoader.getResource('data/sound/gem_collect.wav', ResourceLoader.getAudio, this.soundResources)); - } - - displayAlert(string); - this.playGui.formatGemCounter(this.gemCount, this.totalGems); + this.gameMode.onGemPickup(gem); } public function callCollisionHandlers(marble:Marble, timeState:TimeState, start:Vector, end:Vector, startQuat:Quat, endQuat:Quat) { diff --git a/src/Radar.hx b/src/Radar.hx index 6396fc12..21340ed8 100644 --- a/src/Radar.hx +++ b/src/Radar.hx @@ -40,7 +40,7 @@ class Radar { var gemCount = 0; for (gem in level.gems) { if (!gem.pickedUp) { - renderArrow(gem.boundingCollider.boundingBox.getCenter().toVector(), 0xE60000); + renderArrow(gem.boundingCollider.boundingBox.getCenter().toVector(), gem.radarColor); gemCount++; } } @@ -68,7 +68,6 @@ class Radar { var fovY = (level.scene.camera.fovY * 0.5) * Math.PI / 180.0; var blink = time < 3 ? ((Std.int(Math.floor((time * 1000) / 500))) % 2 == 1) : false; - trace((Std.int(time * 1000) / 500)); var gravityMat = level.getOrientationQuat(level.timeState.currentAttemptTime).toMatrix(); diff --git a/src/mis/MissionElement.hx b/src/mis/MissionElement.hx index 37d38a14..6bc62d1c 100644 --- a/src/mis/MissionElement.hx +++ b/src/mis/MissionElement.hx @@ -60,6 +60,9 @@ class MissionElementScriptObject extends MissionElementBase { var alarmstarttime:String; var game:String; var difficulty:String; + var gamemode:String; + var gemgroupradius:String; + var maxgemspergroup:String; public function new() { _type = MissionElementType.ScriptObject; @@ -160,6 +163,7 @@ class MissionElementSpawnSphere extends MissionElementBase { var datablock:String; var resettime:Null; var timeout:Null; + var gemdatablock:String; public function new() { _type = MissionElementType.SpawnSphere; diff --git a/src/modes/GameMode.hx b/src/modes/GameMode.hx new file mode 100644 index 00000000..5c45f5a4 --- /dev/null +++ b/src/modes/GameMode.hx @@ -0,0 +1,29 @@ +package modes; + +import shapes.Gem; +import h3d.Quat; +import h3d.Vector; +import src.MarbleWorld; +import src.Mission; + +interface GameMode { + public function getSpawnTransform():{position:Vector, orientation:Quat, up:Vector}; + public function getRespawnTransform():{position:Vector, orientation:Quat, up:Vector}; + public function missionScan(mission:Mission):Void; + public function getStartTime():Float; + public function timeMultiplier():Float; + + public function onRestart():Void; + public function onGemPickup(gem:Gem):Void; +} + +class GameModeFactory { + public static function getGameMode(level:MarbleWorld, mode:String):GameMode { + if (mode != null) + switch (mode.toLowerCase()) { + case "scrum": + return new HuntMode(level); + } + return new NullMode(level); + } +} diff --git a/src/modes/HuntMode.hx b/src/modes/HuntMode.hx new file mode 100644 index 00000000..86270b36 --- /dev/null +++ b/src/modes/HuntMode.hx @@ -0,0 +1,279 @@ +package modes; + +import shapes.GemBeam; +import shapes.Gem; +import src.Console; +import h3d.col.Bounds; +import octree.IOctreeObject.RayIntersectionData; +import octree.Octree; +import octree.IOctreeObject.IOctreeObject; +import mis.MisParser; +import h3d.Vector; +import h3d.Quat; +import mis.MissionElement.MissionElementType; +import mis.MissionElement; +import src.Mission; +import mis.MissionElement.MissionElementSpawnSphere; +import src.AudioManager; +import src.ResourceLoader; + +@:publicFields +class GemSpawnSphere { + var position:Vector; + var rotation:Quat; + var element:MissionElementSpawnSphere; + var gem:Gem; + var gemBeam:GemBeam; + var gemColor:String; + + public function new(elem:MissionElementSpawnSphere) { + position = MisParser.parseVector3(elem.position); + position.x *= -1; + rotation = MisParser.parseRotation(elem.rotation); + rotation.x *= -1; + rotation.w *= -1; + element = elem; + gemColor = "red"; + if (elem.gemdatablock != null) { + switch (elem.gemdatablock.toLowerCase()) { + case "gemitem_2pts": + gemColor = "yellow"; + case "gemitem_5pts": + gemColor = "blue"; + default: + gemColor = "red"; + } + } + } +} + +class GemOctreeElem implements IOctreeObject { + public var boundingBox:Bounds; + public var spawn:GemSpawnSphere; + + var priority:Int; + + public function new(vec:Vector, spawn:GemSpawnSphere) { + boundingBox = new Bounds(); + boundingBox.addPoint(vec.add(new Vector(-0.5, -0.5, -0.5)).toPoint()); + boundingBox.addPoint(vec.add(new Vector(0.5, 0.5, 0.5)).toPoint()); + this.spawn = spawn; + } + + public function getElementType() { + return 2; + } + + public function setPriority(priority:Int) { + this.priority = priority; + } + + public function rayCast(rayOrigin:Vector, rayDirection:Vector):Array { + throw new haxe.exceptions.NotImplementedException(); // Not applicable + } +} + +class HuntMode extends NullMode { + var gemSpawnPoints:Array = []; + var playerSpawnPoints:Array = []; + + var gemOctree:Octree; + var gemGroupRadius:Float; + var maxGemsPerGroup:Int; + var activeGemSpawnGroup:Array; + var activeGems:Array = []; + var gemBeams:Array = []; + var gemToBeamMap:Map = []; + + override function missionScan(mission:Mission) { + function scanMission(simGroup:MissionElementSimGroup) { + for (element in simGroup.elements) { + if ([MissionElementType.SpawnSphere].contains(element._type)) { + var spawnSphere:MissionElementSpawnSphere = cast element; + var dbname = spawnSphere.datablock.toLowerCase(); + if (dbname == "spawnspheremarker") + playerSpawnPoints.push(spawnSphere); + if (dbname == "gemspawnspheremarker") + gemSpawnPoints.push(new GemSpawnSphere(spawnSphere)); + } else if (element._type == MissionElementType.SimGroup) { + scanMission(cast element); + } + } + } + scanMission(mission.root); + }; + + override function getSpawnTransform() { + var randomSpawn = playerSpawnPoints[Math.floor(Math.random() * playerSpawnPoints.length)]; + var spawnPos = MisParser.parseVector3(randomSpawn.position); + spawnPos.x *= -1; + var spawnRot = MisParser.parseRotation(randomSpawn.rotation); + spawnRot.x *= -1; + spawnRot.w *= -1; + var spawnMat = spawnRot.toMatrix(); + var up = spawnMat.up(); + spawnPos = spawnPos.add(up.multiply(0.727843 / 3)); // 1.5 -> 0.5 + return { + position: spawnPos, + orientation: spawnRot, + up: up + } + } + + override function getRespawnTransform() { + var lastContactPos = this.level.marble.lastContactPosition; + // Pick closest spawn point + var closestSpawn:MissionElementSpawnSphere = null; + var closestDistance = 1e10; + for (spawn in playerSpawnPoints) { + var pos = MisParser.parseVector3(spawn.position); + pos.x *= -1; + var dist = pos.distance(lastContactPos); + if (dist < closestDistance) { + closestDistance = dist; + closestSpawn = spawn; + } + } + if (closestSpawn != null) { + var spawnPos = MisParser.parseVector3(closestSpawn.position); + spawnPos.x *= -1; + var spawnRot = MisParser.parseRotation(closestSpawn.rotation); + spawnRot.x *= -1; + spawnRot.w *= -1; + var spawnMat = spawnRot.toMatrix(); + var up = spawnMat.up(); + spawnPos = spawnPos.add(up.multiply(0.727843 / 3)); // 1.5 -> 0.5 + return { + position: spawnPos, + orientation: spawnRot, + up: up + } + } + return null; + } + + override public function getStartTime() { + return level.mission.qualifyTime; + } + + override public function timeMultiplier() { + return -1; + } + + override function onRestart() { + setupGems(); + } + + override function onGemPickup(gem:Gem) { + AudioManager.playSound(ResourceLoader.getResource('data/sound/gem_collect.wav', ResourceLoader.getAudio, @:privateAccess this.level.soundResources)); + activeGems.remove(gem); + var beam = gemToBeamMap.get(gem); + beam.setHide(true); + refillGemGroups(); + } + + function setupGems() { + gemGroupRadius = 20.0; + maxGemsPerGroup = 4; + if (level.mission.missionInfo.gemgroupradius != null && level.mission.missionInfo.gemgroupradius != "") + gemGroupRadius = Std.parseFloat(level.mission.missionInfo.gemgroupradius); + if (level.mission.missionInfo.maxgemspergroup != null && level.mission.missionInfo.maxgemspergroup != "") + maxGemsPerGroup = Std.parseInt(level.mission.missionInfo.maxgemspergroup); + + gemOctree = new Octree(); + for (gemSpawn in gemSpawnPoints) { + var vec = gemSpawn.position; + gemOctree.insert(new GemOctreeElem(vec, gemSpawn)); + } + + activeGems = []; + refillGemGroups(); + } + + function refillGemGroups() { + if (activeGems.length == 0) { + var spawnGroup = pickGemSpawnGroup(); + activeGemSpawnGroup = spawnGroup; + fillGemGroup(spawnGroup); + } + } + + function fillGemGroup(group:Array) { + for (gemSpawn in group) { + if (gemSpawn.gem != null) { + gemSpawn.gem.pickedUp = false; + gemSpawn.gem.setHide(false); + gemSpawn.gemBeam.setHide(false); + this.activeGems.push(gemSpawn.gem); + } else { + var melem = new MissionElementItem(); + melem.datablock = "GemItem" + gemSpawn.gemColor; + var gem = new Gem(melem); + gemSpawn.gem = gem; + gem.setPosition(gemSpawn.position.x, gemSpawn.position.y, gemSpawn.position.z); + gem.setRotationQuat(gemSpawn.rotation); + this.activeGems.push(gem); + + var gemBeam = new GemBeam(); + gemBeam.setPosition(gemSpawn.position.x, gemSpawn.position.y, gemSpawn.position.z); + gemBeam.setRotationQuat(gemSpawn.rotation); + this.gemBeams.push(gemBeam); + + gemSpawn.gemBeam = gemBeam; + this.gemToBeamMap.set(gem, gemBeam); + + level.addDtsObject(gemBeam, () -> { + level.addDtsObject(gem, () -> { + level.gems.push(gem); + }); // Please be fast lol + }); + } + } + } + + function pickGemSpawnGroup() { + var searchRadius = gemGroupRadius * 2; + + for (i in 0...6) { + var groupMainPt = new Vector(); + var group = findGemSpawnGroup(groupMainPt); + if (group.length == 0) { + Console.log("Gem spawn group has no spawn points!"); + continue; + } + + var ok = true; + if (activeGemSpawnGroup != null) { + for (gemSpawn in activeGemSpawnGroup) { + if (gemSpawn.position.distance(groupMainPt) < searchRadius) { + ok = false; + break; + } + } + } + if (!ok) + continue; + return group; + } + Console.log("Unable to find spawn group that works with active gem groups, using random!"); + var groupMainPt = new Vector(); + return findGemSpawnGroup(groupMainPt); + } + + function findGemSpawnGroup(outSpawnPoint:Vector) { + // Pick random spawn point + var spawnPoint = gemSpawnPoints[Math.floor(Math.random() * gemSpawnPoints.length)]; + var pos = spawnPoint.position; + + var results = []; + var search = gemOctree.radiusSearch(pos, gemGroupRadius); + for (elem in search) { + var gemElem:GemOctreeElem = cast elem; + results.push(gemElem.spawn); + if (results.length >= maxGemsPerGroup) + break; + } + outSpawnPoint.load(pos); + return results; + } +} diff --git a/src/modes/NullMode.hx b/src/modes/NullMode.hx new file mode 100644 index 00000000..3cc40aa3 --- /dev/null +++ b/src/modes/NullMode.hx @@ -0,0 +1,82 @@ +package modes; + +import shapes.Gem; +import h3d.Quat; +import h3d.Vector; +import shapes.StartPad; +import src.MarbleWorld; +import src.Mission; +import src.AudioManager; +import src.ResourceLoader; + +class NullMode implements GameMode { + var level:MarbleWorld; + + public function new(level:MarbleWorld) { + this.level = level; + } + + public function getSpawnTransform() { + // The player is spawned at the last start pad in the mission file. + var startPad = this.level.dtsObjects.filter(x -> x is StartPad).pop(); + var position:Vector; + var quat:Quat = new Quat(); + if (startPad != null) { + // If there's a start pad, start there + position = startPad.getAbsPos().getPosition(); + quat = startPad.getRotationQuat().clone(); + } else { + position = new Vector(0, 0, 300); + } + position.z += 0.727843; + return { + position: position, + orientation: quat, + up: new Vector(0, 0, 1) + }; + } + + public function getRespawnTransform():{up:Vector, position:Vector, orientation:Quat} { + return null; + } + + public function missionScan(mission:Mission) { + // Do nothing + } + + public function getStartTime() { + return 0.0; + } + + public function timeMultiplier() { + return 1.0; + } + + public function onRestart() {} + + public function onGemPickup(gem:Gem) { + this.level.gemCount++; + var string:String; + + // Show a notification (and play a sound) based on the gems remaining + if (this.level.gemCount == this.level.totalGems) { + string = "You have all the gems, head for the finish!"; + AudioManager.playSound(ResourceLoader.getResource('data/sound/gem_all.wav', ResourceLoader.getAudio, @:privateAccess this.level.soundResources)); + } else { + string = "You picked up a gem. "; + + var remaining = this.level.totalGems - this.level.gemCount; + if (remaining == 1) { + string += "Only one gem to go!"; + } else { + string += '${remaining} gems to go!'; + } + + AudioManager.playSound(ResourceLoader.getResource('data/sound/gem_collect.wav', ResourceLoader.getAudio, + @:privateAccess this.level.soundResources)); + } + + this.level.displayAlert(string); + @:privateAccess this.level.playGui.formatGemCounter(this.level.gemCount, this.level.totalGems); + } +} diff --git a/src/octree/PriorityQueue.hx b/src/octree/PriorityQueue.hx index c8367b3b..2748c3e1 100644 --- a/src/octree/PriorityQueue.hx +++ b/src/octree/PriorityQueue.hx @@ -12,7 +12,7 @@ class PriorityQueue { public function enqueue(val:T, priority:Float) { var node = new PriorityQueueNode(val, priority); - if (this.queue == null) { + if (this.queue == null || this.queue.length == 0) { this.queue = [node]; } else { if (this.queue[0].priority >= priority) { diff --git a/src/shapes/Gem.hx b/src/shapes/Gem.hx index 5e9b77fd..1a9fc08e 100644 --- a/src/shapes/Gem.hx +++ b/src/shapes/Gem.hx @@ -13,6 +13,8 @@ class Gem extends DtsObject { var gemColor:String; + public var radarColor:Int; + public function new(element:MissionElementItem) { super(); dtsPath = "data/shapes/items/gem.dts"; @@ -30,6 +32,14 @@ class Gem extends DtsObject { this.identifier = "Gem" + color; this.matNameOverride.set('base.gem', color + ".gem"); gemColor = color + ".gem"; + radarColor = switch (color) { + case "blue": + 0x0000E6; + case "yellow": + 0xE6FF00; + default: + 0xE60000; + } } public override function init(level:MarbleWorld, onFinish:Void->Void) { @@ -68,7 +78,18 @@ class Gem extends DtsObject { override function getPreloadMaterials(dts:dts.DtsFile) { var mats = super.getPreloadMaterials(dts); - mats.push("data/skies/gemCubemapUp.png"); + switch (gemColor) { + case "yellow": + mats.push('data/shapes/items/yellow.gem.png'); + mats.push("data/skies/gemCubemapUp2.png"); + case "blue": + mats.push('data/shapes/items/blue.gem.png'); + mats.push("data/skies/gemCubemapUp4.png"); + default: + mats.push('data/shapes/items/red.gem.png'); + mats.push("data/skies/gemCubemapUp.png"); + } + return mats; } @@ -94,5 +115,47 @@ class Gem extends DtsObject { material.shadows = false; material.receiveShadows = true; } + if (matName == "yellow.gem") { + var diffuseTex = ResourceLoader.getTexture('data/shapes/items/yellow.gem.png').resource; + diffuseTex.wrap = Repeat; + diffuseTex.mipMap = Nearest; + + var cubemapTex = new h3d.mat.Texture(64, 64, [Cube]); + var cubemapFace = ResourceLoader.getImage('data/skies/gemCubemapUp2.png').resource; + for (i in 0...6) { + cubemapTex.uploadPixels(cubemapFace.getPixels(), 0, i); + } + var shader = new shaders.DefaultCubemapNormalNoSpecMaterial(diffuseTex, 1, cubemapTex); + var dtsTex = material.mainPass.getShader(shaders.DtsTexture); + dtsTex.passThrough = true; + material.mainPass.removeShader(material.textureShader); + material.mainPass.addShader(shader); + var thisprops:Dynamic = material.getDefaultProps(); + thisprops.light = false; // We will calculate our own lighting + material.props = thisprops; + material.shadows = false; + material.receiveShadows = true; + } + if (matName == "blue.gem") { + var diffuseTex = ResourceLoader.getTexture('data/shapes/items/blue.gem.png').resource; + diffuseTex.wrap = Repeat; + diffuseTex.mipMap = Nearest; + + var cubemapTex = new h3d.mat.Texture(64, 64, [Cube]); + var cubemapFace = ResourceLoader.getImage('data/skies/gemCubemapUp3.png').resource; + for (i in 0...6) { + cubemapTex.uploadPixels(cubemapFace.getPixels(), 0, i); + } + var shader = new shaders.DefaultCubemapNormalNoSpecMaterial(diffuseTex, 1, cubemapTex); + var dtsTex = material.mainPass.getShader(shaders.DtsTexture); + dtsTex.passThrough = true; + material.mainPass.removeShader(material.textureShader); + material.mainPass.addShader(shader); + var thisprops:Dynamic = material.getDefaultProps(); + thisprops.light = false; // We will calculate our own lighting + material.props = thisprops; + material.shadows = false; + material.receiveShadows = true; + } } } diff --git a/src/shapes/GemBeam.hx b/src/shapes/GemBeam.hx new file mode 100644 index 00000000..aa6e9bd2 --- /dev/null +++ b/src/shapes/GemBeam.hx @@ -0,0 +1,94 @@ +package shapes; + +import h3d.mat.Material; +import src.DtsObject; +import src.ResourceLoader; + +class GemBeam extends DtsObject { + public function new() { + super(); + this.dtsPath = "data/shapes/items/gembeam.dts"; + this.isCollideable = false; + this.isTSStatic = false; + this.identifier = "GemBeam"; + this.useInstancing = true; + this.animateSubObjectOpacities = true; + } + + public override function init(level:src.MarbleWorld, onFinish:() -> Void) { + super.init(level, onFinish); + } + + override function update(timeState:src.TimeState) { + super.update(timeState); + } + + override function getPreloadMaterials(dts:dts.DtsFile) { + var mats = super.getPreloadMaterials(dts); + mats.push("data/shapes/pads/mistyglow.png"); + return mats; + } + + override function postProcessMaterial(matName:String, material:Material) { + if (matName == "mistyglow") { + var diffuseTex = ResourceLoader.getTexture("data/shapes/pads/mistyglow.png").resource; + diffuseTex.wrap = Repeat; + diffuseTex.mipMap = Nearest; + // aa + var trivialShader = new shaders.TrivialMaterial(diffuseTex); + material.mainPass.removeShader(material.textureShader); + var glowpass = material.mainPass.clone(); + + glowpass.addShader(trivialShader); + var dtsshader = glowpass.getShader(shaders.DtsTexture); + dtsshader.passThrough = true; + glowpass.setPassName("glow"); + glowpass.depthTest = LessEqual; + glowpass.depthWrite = false; + glowpass.enableLights = false; + glowpass.setBlendMode(Alpha); + // glowpass.blendSrc = SrcAlpha; + // glowpass.blendDst = OneMinusSrcAlpha; + material.addPass(glowpass); + + material.mainPass.setPassName("glowPreNoRender"); + material.mainPass.removeShader(material.textureShader); + material.mainPass.addShader(trivialShader); + dtsshader = material.mainPass.getShader(shaders.DtsTexture); + dtsshader.passThrough = true; + material.mainPass.enableLights = false; + + // var thisprops:Dynamic = material.getDefaultProps(); + // thisprops.light = false; // We will calculate our own lighting + // material.props = thisprops; + material.shadows = false; + // material.blendMode = Alpha; + material.mainPass.depthWrite = false; + + // var diffuseTex = ResourceLoader.getTexture("data/shapes/pads/mistyglow.png").resource; + // diffuseTex.wrap = Repeat; + // diffuseTex.mipMap = Nearest; + + // var trivialShader = new shaders.TrivialMaterial(diffuseTex); + + // // var glowpass = material.mainPass.clone(); + // // glowpass.addShader(trivialShader); + // // var dtsshader = glowpass.getShader(shaders.DtsTexture); + // // dtsshader.passThrough = true; + // // glowpass.removeShader(dtsshader); + // // glowpass.setPassName("glow"); + // // glowpass.depthTest = LessEqual; + // // glowpass.depthWrite = false; + // // glowpass.enableLights = false; + // // glowpass.setBlendMode(AlphaAdd); + // // material.addPass(glowpass); + + // material.mainPass.setPassName("glowPre"); + // material.mainPass.addShader(trivialShader); + // var dtsshader = material.mainPass.getShader(shaders.DtsTexture); + // dtsshader.passThrough = true; + // material.mainPass.enableLights = false; + // material.mainPass.setBlendMode(Alpha); + } + } +}