diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 69627df8..c758cf7f 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1,5 +1,6 @@ package src; +import src.Radar; import rewind.InputRecorder; import net.NetPacket.ScoreboardPacket; import net.NetPacket.PowerupPickupPacket; @@ -135,6 +136,7 @@ class MarbleWorld extends Scheduler { var playGui:PlayGui; var loadingGui:LoadingGui; + var radar:Radar; public var interiors:Array = []; public var pathedInteriors:Array = []; @@ -380,6 +382,10 @@ class MarbleWorld extends Scheduler { this.playGui = new PlayGui(); this.instanceManager = new InstanceManager(scene); this.particleManager = new ParticleManager(cast this); + if (this.isMultiplayer || this.game == "ultra") { + this.radar = new Radar(cast this, this.scene2d); + radar.init(); + } var worker = new ResourceLoaderWorker(() -> { var renderer = cast(this.scene.renderer, h3d.scene.fwd.Renderer); @@ -647,6 +653,9 @@ class MarbleWorld extends Scheduler { this.playGui.formatGemCounter(this.gemCount, this.totalGems); } + if (radar != null) + radar.reset(); + // Record/Playback trapdoor and landmine states if (full) { var tidx = 0; @@ -1670,6 +1679,8 @@ class MarbleWorld extends Scheduler { } } + if (radar != null) + radar.update(dt); this.updateGameState(); if (!this.isMultiplayer) this.updateBlast(this.marble, timeState); @@ -1815,6 +1826,8 @@ class MarbleWorld extends Scheduler { this.marble.cubemapRenderer.render(e, 0.002); } if (_instancesNeedsUpdate) { + if (this.radar != null) + this.radar.render(); _instancesNeedsUpdate = false; this.instanceManager.render(); } @@ -2659,6 +2672,11 @@ class MarbleWorld extends Scheduler { this.playGui.dispose(); scene.removeChildren(); + if (radar != null) { + radar.dispose(); + radar = null; + } + CollisionPool.freeMemory(); for (interior in this.interiors) { diff --git a/src/Radar.hx b/src/Radar.hx new file mode 100644 index 00000000..163efe62 --- /dev/null +++ b/src/Radar.hx @@ -0,0 +1,208 @@ +package src; + +import hxd.res.BitmapFont; +import h3d.Matrix; +import src.DtsObject; +import h3d.Vector; +import gui.Graphics; +import src.GameObject; +import h2d.Scene; +import src.MarbleWorld; +import src.Util; +import src.Marble; +import src.Settings; +import src.ResourceLoader; + +class Radar { + var level:MarbleWorld; + var scene2d:Scene; + + var g:Graphics; + + var marbleNameTexts:Map; + + public var ellipseScreenFraction = new Vector(0.79, 0.85); + public var fullArrowLength = 60.0; + public var fullArrowWidth = 40.0; + public var maxArrowAlpha = 0.6; + public var maxTargetAlpha = 0.4; + public var minArrowFraction = 0.4; + + var radarTiles:Array; + + var time:Float = 0.0; + + var _dirty = false; + + public function new(level:MarbleWorld, scene2d:Scene) { + this.level = level; + this.scene2d = scene2d; + this.marbleNameTexts = []; + var radarTileRedGem = ResourceLoader.getImage("data/ui/mp/radar/GemItemRed.png").resource.toTile(); + var radarTileYellowGem = ResourceLoader.getImage("data/ui/mp/radar/GemItemYellow.png").resource.toTile(); + var radarTileBlueGem = ResourceLoader.getImage("data/ui/mp/radar/GemItemBlue.png").resource.toTile(); + var radarTileGreenGem = ResourceLoader.getImage("data/ui/mp/radar/GemItemGreen.png").resource.toTile(); + var radarTileOrangeGem = ResourceLoader.getImage("data/ui/mp/radar/GemItemOrange.png").resource.toTile(); + var radarTilePinkGem = ResourceLoader.getImage("data/ui/mp/radar/GemItemPink.png").resource.toTile(); + var radarTilePurpleGem = ResourceLoader.getImage("data/ui/mp/radar/GemItemPurple.png").resource.toTile(); + var radarTileTurquoiseGem = ResourceLoader.getImage("data/ui/mp/radar/GemItemTurquoise.png").resource.toTile(); + var radarTileBlackGem = ResourceLoader.getImage("data/ui/mp/radar/GemItemBlack.png").resource.toTile(); + var radarTilePlatinumGem = ResourceLoader.getImage("data/ui/mp/radar/GemItemPlatinum.png").resource.toTile(); + var radarTileEndPad = ResourceLoader.getImage("data/ui/mp/radar/EndPad.png").resource.toTile(); + radarTiles = [ + radarTileRedGem, radarTileYellowGem, radarTileBlueGem, radarTileGreenGem, radarTileOrangeGem, radarTilePinkGem, radarTilePurpleGem, + radarTileTurquoiseGem, radarTileBlackGem, radarTilePlatinumGem, radarTileEndPad + ]; + } + + public function init() { + g = new Graphics(scene2d); + } + + public function update(dt:Float) { + time += dt; + _dirty = true; + } + + public function render() { + if (!_dirty) + return; + g.clear(); + var gemCount = 0; + for (gem in level.gems) { + if (!gem.pickedUp) { + renderArrow(gem.boundingCollider.boundingBox.getCenter().toVector(), gem.radarGemColor, radarTiles[gem.radarGemIndex]); + gemCount++; + } + } + if (@:privateAccess level.endPad != null && gemCount == 0) { + renderArrow(@:privateAccess level.endPad.getAbsPos().getPosition(), 0xE6E6E6, radarTiles[10]); + } + var fadeDistance = level.scene.camera.zFar * 0.1; + for (marble => marbleName in marbleNameTexts) { + if (marbleName != null) + marbleName.alpha = 0; + } + for (marble in level.marbles) { + if (marble != level.marble) { + var shapePos = marble.getAbsPos().getPosition(); + var shapeDir = shapePos.sub(level.scene.camera.pos); + var shapeDist = shapeDir.lengthSq(); + if (shapeDist == 0 || shapeDist > level.scene.camera.zFar * level.scene.camera.zFar) { + dontRenderName(marble); + continue; + } + var validProjection = frustumHasPoint(level.scene.camera.frustum, shapePos); + if (!validProjection) { + dontRenderName(marble); + continue; + } + shapePos.z += 0.5; // Vertical offset + + var projectedPos = level.scene.camera.project(shapePos.x, shapePos.y, shapePos.z, scene2d.width, scene2d.height); + var opacity = (shapeDist < fadeDistance) ? 1.0 : (1.0 - (shapeDist - fadeDistance) / (level.scene.camera.zFar - fadeDistance)); + renderName(projectedPos, marble, opacity); + } + } + _dirty = false; + } + + public function blink() { + time = 0; + } + + public function reset() { + time = 0; + g.clear(); + } + + public function dispose() { + g.clear(); + scene2d.removeChild(g); + g = null; + for (txt in marbleNameTexts) { + if (txt != null) { + scene2d.removeChild(txt); + } + } + marbleNameTexts = null; + } + + inline function planeDistance(plane:h3d.col.Plane, p:Vector) { + return @:privateAccess plane.nx * p.x + @:privateAccess plane.ny * p.y + @:privateAccess plane.nz * p.z - @:privateAccess plane.d; + } + + function frustumHasPoint(frustum:h3d.col.Frustum, p:Vector) { + if (planeDistance(frustum.pleft, p) < 0) + return false; + if (planeDistance(frustum.pright, p) < 0) + return false; + if (planeDistance(frustum.ptop, p) < 0) + return false; + if (planeDistance(frustum.pbottom, p) < 0) + return false; + if (frustum.checkNearFar) { + if (planeDistance(frustum.pnear, p) < 0) + return false; + if (planeDistance(frustum.pfar, p) < 0) + return false; + } + return true; + } + + function renderArrow(pos:Vector, color:Int, tile:h2d.Tile) { + var validProjection = frustumHasPoint(level.scene.camera.frustum, pos); + var projectedPos = level.scene.camera.project(pos.x, pos.y, pos.z, scene2d.width, scene2d.height); + + if (validProjection && tile != null) { + g.lineStyle(0, 0, 0); + g.drawTile(projectedPos.x - tile.width / 2, projectedPos.y - tile.height / 2, tile); + } else if (!validProjection) { + var centerDiff = projectedPos.sub(new Vector(scene2d.width / 2, scene2d.height / 2)); + + var theta = Math.atan2(centerDiff.y, centerDiff.x); + if (projectedPos.z > 1) + theta += Math.PI; + + var ellipsePos = new Vector(scene2d.width * (ellipseScreenFraction.x * Math.cos(theta) + 1) / 2, + scene2d.height * (ellipseScreenFraction.y * Math.sin(theta) + 1) / 2); + var arrowDir = projectedPos.sub(new Vector(scene2d.width / 2, scene2d.height / 2)).normalized(); + var arrowDirPerp = new Vector(-arrowDir.y, arrowDir.x); + if (projectedPos.z > 1) + arrowDir.scale(-1); + + var tipPosition = ellipsePos.add(arrowDir.multiply(fullArrowLength)); + var tipUpperPosition = ellipsePos.add(arrowDirPerp.multiply(fullArrowWidth / 2)); + var tipLowerPosition = ellipsePos.add(arrowDirPerp.multiply(-fullArrowWidth / 2)); + + g.beginFill(color, 0.6); + g.lineStyle(1, 0, 0.6); + g.moveTo(tipPosition.x, tipPosition.y); + g.lineTo(tipUpperPosition.x, tipUpperPosition.y); + g.lineTo(tipLowerPosition.x, tipLowerPosition.y); + g.endFill(); + } + } + + function renderName(pos:Vector, marble:Marble, opacity:Float) { + if (!marbleNameTexts.exists(marble)) { + var arialb14fontdata = ResourceLoader.getFileEntry("data/font/Arial Bold.fnt"); + var arialb14b = new BitmapFont(arialb14fontdata.entry); + @:privateAccess arialb14b.loader = ResourceLoader.loader; + var arialBold14 = arialb14b.toSdfFont(cast 16 * Settings.uiScale, MultiChannel); + var txt = new h2d.Text(arialBold14, scene2d); + marbleNameTexts.set(marble, txt); + txt.textColor = 0xFFFF00; + } + var textObj = marbleNameTexts.get(marble); + textObj.text = @:privateAccess marble.connection.getName(); + textObj.setPosition(pos.x - textObj.textWidth / 2, pos.y - textObj.textHeight); + textObj.alpha = opacity; + } + + function dontRenderName(marble:Marble) { + if (marbleNameTexts.exists(marble)) { + marbleNameTexts.get(marble).alpha = 0; + } + } +} diff --git a/src/gui/PlayGui.hx b/src/gui/PlayGui.hx index dc3ef7d8..524b8413 100644 --- a/src/gui/PlayGui.hx +++ b/src/gui/PlayGui.hx @@ -774,20 +774,26 @@ class PlayGui { } public function formatGemHuntCounter(collected:Int) { - gemCountNumbers[0].anim.visible = true; - gemCountNumbers[1].anim.visible = true; + var collectedHundredths = Math.floor(collected / 100); + var collectedTenths = Math.floor(collected / 10) % 10; + var collectedOnes = collected % 10; + + if (collected >= 100) + gemCountNumbers[0].anim.visible = true; + else + gemCountNumbers[0].anim.visible = false; + if (collected >= 10) + gemCountNumbers[1].anim.visible = true; + else + gemCountNumbers[1].anim.visible = false; gemCountNumbers[2].anim.visible = true; gemCountNumbers[3].anim.visible = false; gemCountNumbers[4].anim.visible = false; gemCountNumbers[5].anim.visible = false; - var collectedHundredths = Math.floor(collected / 100); - var collectedTenths = Math.floor(collected / 10) % 10; - var collectedOnes = collected % 10; - - gemCountNumbers[0].anim.currentFrame = collectedHundredths; - gemCountNumbers[1].anim.currentFrame = collectedTenths; - gemCountNumbers[2].anim.currentFrame = collectedOnes; + gemCountNumbers[0].anim.currentFrame = 10 + collectedHundredths; + gemCountNumbers[1].anim.currentFrame = 10 + collectedTenths; + gemCountNumbers[2].anim.currentFrame = 10 + collectedOnes; gemCountSlash.bmp.visible = false; gemImageSceneTargetBitmap.visible = true; } diff --git a/src/shapes/Gem.hx b/src/shapes/Gem.hx index 458d4290..548d3b0d 100644 --- a/src/shapes/Gem.hx +++ b/src/shapes/Gem.hx @@ -13,6 +13,8 @@ class Gem extends DtsObject { public var pickedUp:Bool; public var netIndex:Int; public var pickUpClient:Int = -1; + public var radarGemColor:Int; + public var radarGemIndex:Int; var gemColor:String; @@ -33,6 +35,47 @@ class Gem extends DtsObject { this.identifier = "Gem" + color; this.matNameOverride.set('base.gem', color + ".gem"); gemColor = color + ".gem"; + var colLower = color.toLowerCase(); + switch (colLower) { + case "red": + radarGemColor = 0xFF0000; + radarGemIndex = 0; + case "blue": + radarGemColor = 0x6666E6; + radarGemIndex = 2; + + case "yellow": + radarGemColor = 0xFEFF00; + radarGemIndex = 1; + + case "green": + radarGemColor = 0x66E666; + radarGemIndex = 3; + + case "orange": + radarGemColor = 0xE6BA66; + radarGemIndex = 4; + + case "pink": + radarGemColor = 0xE666E5; + radarGemIndex = 5; + + case "purple": + radarGemColor = 0xC566E6; + radarGemIndex = 6; + + case "turquoise": + radarGemColor = 0x66E5E6; + radarGemIndex = 7; + + case "black": + radarGemColor = 0x666666; + radarGemIndex = 8; + + case "platinum": + radarGemColor = 0xA5A5A5; + radarGemIndex = 9; + } } public override function init(level:MarbleWorld, onFinish:Void->Void) {