diff --git a/src/Marble.hx b/src/Marble.hx index de22fce9..568e7a1d 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -2163,9 +2163,9 @@ class Marble extends GameObject { this.blastPerc = amount; var impulse = this.currentUp.multiply(amount * 8); this.applyImpulse(impulse); - if (this.controllable) - AudioManager.playSound(ResourceLoader.getResource('data/sound/use_blast.wav', ResourceLoader.getAudio, this.soundResources)); if (!this.isNetUpdate) { + if (this.controllable) + AudioManager.playSound(ResourceLoader.getResource('data/sound/use_blast.wav', ResourceLoader.getAudio, this.soundResources)); this.blastWave.doSequenceOnceBeginTime = this.level.timeState.timeSinceLoad; this.blastUseTime = this.level.timeState.currentAttemptTime; } diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index b7c0be73..53965429 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1,5 +1,7 @@ package src; +import net.GemPredictionStore; +import modes.HuntMode; import net.NetPacket.MarbleNetFlags; import net.PowerupPredictionStore; import net.MarblePredictionStore; @@ -211,6 +213,7 @@ class MarbleWorld extends Scheduler { var clientMarbles:Map = []; var predictions:MarblePredictionStore; var powerupPredictions:PowerupPredictionStore; + var gemPredictions:GemPredictionStore; public var lastMoves:MarbleUpdateQueue; @@ -254,6 +257,7 @@ class MarbleWorld extends Scheduler { lastMoves = new MarbleUpdateQueue(); predictions = new MarblePredictionStore(); powerupPredictions = new PowerupPredictionStore(); + gemPredictions = new GemPredictionStore(); } // Set the network RNG for hunt @@ -333,6 +337,19 @@ class MarbleWorld extends Scheduler { this.collisionWorld.finalizeStaticGeometry(); this.playGui.init(this.scene2d, this.mission.game.toLowerCase()); this.scene.addChild(this.sky); + + if (this.isMultiplayer) { + // Add us + if (Net.isHost) { + this.playGui.addPlayer(0, 'Player 0', true); + } else { + this.playGui.addPlayer(Net.clientId, 'Player ${Net.clientId}', true); + } + for (client in Net.clientIdMap) { + this.playGui.addPlayer(client.id, 'Player ${client.id}', false); + } + } + this._ready = true; // AudioManager.playShell(); MarbleGame.canvas.clearContent(); @@ -1160,6 +1177,10 @@ class MarbleWorld extends Scheduler { // Console.log('Revert powerup pickup: ${pw.lastPickUpTime} -> ${val}'); pw.lastPickUpTime = powerupPredictions.getState(pw.netIndex); } + var huntMode:HuntMode = cast this.gameMode; + for (activeGem in @:privateAccess huntMode.activeGemSpawnGroup) { + huntMode.setGemHiddenStatus(activeGem, gemPredictions.getState(activeGem)); + } // } } @@ -1182,10 +1203,10 @@ class MarbleWorld extends Scheduler { Debug.drawSphere(@:privateAccess marbleToUpdate.newPos, marbleToUpdate._radius); var distFromUs = @:privateAccess marbleToUpdate.newPos.distance(this.marble.newPos); - if (distFromUs < 5) - m.calculationTicks = ourQueuedMoves.length; // ourQueuedMoves.length; - else - m.calculationTicks = Std.int(Math.max(1, ourQueuedMoves.length - (distFromUs - 5) / 3)); + // if (distFromUs < 5) + m.calculationTicks = ourQueuedMoves.length; // ourQueuedMoves.length; + // else + // m.calculationTicks = Std.int(Math.max(1, ourQueuedMoves.length - (distFromUs - 5) / 3)); // - Std.int((@:privateAccess Net.clientConnection.moveManager.ackRTT - ourLastMove.moveQueueSize) / 2); marblesToTick.set(client, m); @@ -1233,6 +1254,14 @@ class MarbleWorld extends Scheduler { return -1; } + public function spawnHuntGemsClientSide(gemIds:Array) { + if (this.isMultiplayer && Net.isClient) { + var huntMode:HuntMode = cast this.gameMode; + huntMode.setActiveSpawnSphere(gemIds); + radar.blink(); + } + } + public function rollback(t:Float) { var newT = timeState.currentAttemptTime - t; var rewindFrame = rewindManager.getNextRewindFrame(timeState.currentAttemptTime - t); diff --git a/src/Util.hx b/src/Util.hx index 2d812499..098fc9ef 100644 --- a/src/Util.hx +++ b/src/Util.hx @@ -342,6 +342,13 @@ class Util { '${(hours > 0 ? (hoursTen > 0 ? '${hoursTen}' : '') +'${hoursOne}' + ':' : '')}${minutesTen}${minutesOne}:${secondsTen}${secondsOne}.${hundredthTen}${hundredthOne}${thousandth}'; } + public static function rightPad(str:String, len:Int, cutOff:Int) { + str = str.substring(0, len - cutOff); + while (str.length < len) + str += " "; + return str; + } + public static function getKeyForButton(button:Int) { var keyName = Key.getKeyName(button); if (keyName == "MouseLeft") diff --git a/src/gui/GuiMLTextListCtrl.hx b/src/gui/GuiMLTextListCtrl.hx index 85055b6d..be501556 100644 --- a/src/gui/GuiMLTextListCtrl.hx +++ b/src/gui/GuiMLTextListCtrl.hx @@ -1,5 +1,6 @@ package gui; +import h2d.filter.Filter; import h2d.HtmlText; import h2d.Flow; import h3d.Engine; @@ -33,14 +34,17 @@ class GuiMLTextListCtrl extends GuiControl { public var scrollable:Bool = false; + var filter:Filter = null; + var flow:Flow; var _imageLoader:String->Tile; - public function new(font:Font, texts:Array, imageLoader:String->Tile) { + public function new(font:Font, texts:Array, imageLoader:String->Tile, ?filter:Filter = null) { super(); this.font = font; this.texts = texts; this.textObjs = []; + this.filter = filter; this._imageLoader = imageLoader; for (text in texts) { var tobj = new HtmlText(font); @@ -48,6 +52,8 @@ class GuiMLTextListCtrl extends GuiControl { tobj.loadImage = imageLoader; tobj.text = text; tobj.textColor = 0; + if (filter != null) + tobj.filter = filter; textObjs.push(tobj); } this.g = new Graphics(); @@ -65,6 +71,8 @@ class GuiMLTextListCtrl extends GuiControl { tobj.lineHeightMode = TextOnly; tobj.text = text; tobj.textColor = 0; + if (filter != null) + tobj.filter = filter; textObjs.push(tobj); if (this.scrollable) { diff --git a/src/gui/GuiTextListCtrl.hx b/src/gui/GuiTextListCtrl.hx index 161cd0cb..c00d0052 100644 --- a/src/gui/GuiTextListCtrl.hx +++ b/src/gui/GuiTextListCtrl.hx @@ -25,6 +25,7 @@ class GuiTextListCtrl extends GuiControl { public var selectedColor:Int = 0x206464; public var selectedFillColor:Int = 0xC8C8C8; + public var textColor:Int = 0; public var textYOffset:Int = 0; @@ -34,15 +35,16 @@ class GuiTextListCtrl extends GuiControl { var flow:Flow; - public function new(font:Font, texts:Array) { + public function new(font:Font, texts:Array, textColor:Int = 0) { super(); this.font = font; this.texts = texts; this.textObjs = []; + this.textColor = textColor; for (text in texts) { var tobj = new Text(font); tobj.text = text; - tobj.textColor = 0; + tobj.textColor = textColor; textObjs.push(tobj); } this.g = new Graphics(); @@ -57,7 +59,7 @@ class GuiTextListCtrl extends GuiControl { for (text in texts) { var tobj = new Text(font); tobj.text = text; - tobj.textColor = 0; + tobj.textColor = textColor; textObjs.push(tobj); if (this.scrollable) { @@ -178,7 +180,7 @@ class GuiTextListCtrl extends GuiControl { for (i in 0...textObjs.length) { var selected = i == hoverIndex || i == this._prevSelected; var text = textObjs[i]; - text.textColor = selected ? selectedColor : 0; + text.textColor = selected ? selectedColor : textColor; // fill color = 0xC8C8C8 } // obviously in renderRect @@ -189,7 +191,7 @@ class GuiTextListCtrl extends GuiControl { if (i == this._prevSelected) continue; var text = textObjs[i]; - text.textColor = 0; + text.textColor = textColor; // fill color = 0xC8C8C8 } } diff --git a/src/gui/PlayGui.hx b/src/gui/PlayGui.hx index 514ca5d8..641fe0a1 100644 --- a/src/gui/PlayGui.hx +++ b/src/gui/PlayGui.hx @@ -38,6 +38,13 @@ typedef MiddleMessage = { yPos:Float } +typedef PlayerInfo = { + id:Int, + name:String, + us:Bool, + score:Int +} + class PlayGui { var scene2d:h2d.Scene; @@ -64,6 +71,12 @@ class PlayGui { var blastFillUltra:GuiImage; var blastFrame:GuiImage; + var playerListContainerOuter:GuiControl; + var playerListContainer:GuiControl; + var playerListCtrl:GuiMLTextListCtrl; + var playerListScoresCtrl:GuiMLTextListCtrl; + var playerList:Array = []; + var imageResources:Array> = []; var textureResources:Array> = []; var soundResources:Array> = []; @@ -137,11 +150,14 @@ class PlayGui { gemCountNumbers.push(new GuiAnim(numberTiles)); } initTimer(); - initGemCounter(); + if (!MarbleGame.instance.world.isMultiplayer) + initGemCounter(); initPowerupBox(); if (game == 'ultra') initBlastBar(); initTexts(); + if (MarbleGame.instance.world.isMultiplayer) + initPlayerList(); if (Util.isTouchDevice()) { MarbleGame.instance.touchInput.showControls(this.playGuiCtrlOuter, game == 'ultra'); @@ -574,6 +590,106 @@ class PlayGui { } } + function initPlayerList() { + var arial14fontdata = ResourceLoader.getFileEntry("data/font/Arial Bold.fnt"); + var arial14b = new BitmapFont(arial14fontdata.entry); + @:privateAccess arial14b.loader = ResourceLoader.loader; + var arial14 = arial14b.toSdfFont(cast 22 * Settings.uiScale, MultiChannel); + + var coliseumfontdata = ResourceLoader.getFileEntry("data/font/ColiseumRR.fnt"); + var coliseumb = new BitmapFont(coliseumfontdata.entry); + @:privateAccess coliseumb.loader = ResourceLoader.loader; + var coliseum = coliseumb.toSdfFont(cast 44 * Settings.uiScale, MultiChannel); + + playerListContainer = new GuiControl(); + playerListContainer.horizSizing = Right; + playerListContainer.vertSizing = Bottom; + playerListContainer.position = new Vector(0, 0); + playerListContainer.extent = new Vector(392, 360); + this.playGuiCtrl.addChild(playerListContainer); + + var scoreBackdrop = new GuiImage(ResourceLoader.getResource("data/ui/game/scoreBackdrop.png", ResourceLoader.getImage, this.imageResources).toTile()); + scoreBackdrop.position = new Vector(0, 0); + scoreBackdrop.extent = new Vector(386, 128); + playerListContainer.addChild(scoreBackdrop); + + var scorePlusMinus = new GuiImage(ResourceLoader.getResource("data/ui/game/scoreBackdropMinus.png", ResourceLoader.getImage, this.imageResources) + .toTile()); + scorePlusMinus.position = new Vector(20, 17); + scorePlusMinus.extent = new Vector(22, 111); + scoreBackdrop.addChild(scorePlusMinus); + + function imgLoader(path:String) { + switch (path) { + case "us": + return ResourceLoader.getResource("data/ui/xbox/GreenDot.png", ResourceLoader.getImage, this.imageResources).toTile(); + case "them": + return ResourceLoader.getResource("data/ui/xbox/EmptyDot.png", ResourceLoader.getImage, this.imageResources).toTile(); + } + return null; + } + + // var playerList = [ + // 'Player 1 1', + // 'Player 2 2' + // ]; + + var ds = new h2d.filter.DropShadow(1.414, 0.785, 0x000000, 1, 0, 0.4, 1, true); + + playerListCtrl = new GuiMLTextListCtrl(arial14, [], imgLoader, ds); + + playerListCtrl.position = new Vector(27, 43); + playerListCtrl.extent = new Vector(392, 271); + playerListCtrl.scrollable = true; + playerListCtrl.onSelectedFunc = (sel) -> {} + playerListContainer.addChild(playerListCtrl); + + playerListScoresCtrl = new GuiMLTextListCtrl(arial14, [], imgLoader, ds); + + playerListScoresCtrl.position = new Vector(277, 43); + playerListScoresCtrl.extent = new Vector(392, 271); + playerListScoresCtrl.scrollable = true; + playerListScoresCtrl.onSelectedFunc = (sel) -> {} + playerListContainer.addChild(playerListScoresCtrl); + } + + public function redrawPlayerList() { + var pl = []; + var plScores = []; + playerList.sort((a, b) -> a.score > b.score ? -1 : (a.score < b.score ? 1 : 0)); + for (item in playerList) { + pl.push('${Util.rightPad(item.name, 25, 3)}'); + plScores.push('${item.score}'); + } + playerListCtrl.setTexts(pl); + playerListScoresCtrl.setTexts(plScores); + } + + public function addPlayer(id:Int, name:String, us:Bool) { + playerList.push({ + id: id, + name: name, + us: us, + score: 0 + }); + redrawPlayerList(); + } + + public function removePlayer(id:Int) { + var f = playerList.filter(x -> x.id == id); + if (f.length != 0) + playerList.remove(f[0]); + redrawPlayerList(); + } + + public function incrementPlayerScore(id:Int, score:Int) { + var f = playerList.filter(x -> x.id == id); + if (f.length != 0) + f[0].score += score; + + redrawPlayerList(); + } + public function setHelpTextOpacity(value:Float) { @:privateAccess helpTextForeground.text._textColorVec.a = value; @:privateAccess helpTextBackground.text._textColorVec.a = value; @@ -702,6 +818,8 @@ class PlayGui { } public function formatGemCounter(collected:Int, total:Int) { + if (MarbleGame.instance.world.isMultiplayer) + return; if (total == 0) { for (number in gemCountNumbers) { number.anim.visible = false; @@ -734,6 +852,8 @@ class PlayGui { } public function formatGemHuntCounter(collected:Int) { + if (MarbleGame.instance.world.isMultiplayer) + return; gemCountNumbers[0].anim.visible = true; gemCountNumbers[1].anim.visible = true; gemCountNumbers[2].anim.visible = true; diff --git a/src/modes/HuntMode.hx b/src/modes/HuntMode.hx index 76ea646d..248d01d4 100644 --- a/src/modes/HuntMode.hx +++ b/src/modes/HuntMode.hx @@ -1,5 +1,11 @@ package modes; +import net.NetCommands; +import net.NetPacket.GemPickupPacket; +import haxe.Exception; +import net.BitStream.OutputBitStream; +import net.NetPacket.GemSpawnPacket; +import net.Net; import rewind.RewindManager; import hxd.Rand; import rewind.RewindableState; @@ -26,6 +32,7 @@ import src.Marble; @:publicFields class GemSpawnSphere { + var netIndex:Int; var position:Vector; var rotation:Quat; var element:MissionElementSpawnSphere; @@ -176,8 +183,14 @@ class HuntMode extends NullMode { playerSpawnPoints.push(spawnSphere); spawnPointTaken.push(false); } - if (dbname == "gemspawnspheremarker") - gemSpawnPoints.push(new GemSpawnSphere(spawnSphere)); + if (dbname == "gemspawnspheremarker") { + var sphere = new GemSpawnSphere(spawnSphere); + sphere.netIndex = gemSpawnPoints.length; + gemSpawnPoints.push(sphere); + if (level.isMultiplayer) { + @:privateAccess level.gemPredictions.alloc(); + } + } } else if (element._type == MissionElementType.SimGroup) { scanMission(cast element); } @@ -271,42 +284,75 @@ class HuntMode extends NullMode { } override function onRestart() { - rng.setSeed(100); - rng2.setSeed(100); - if (Settings.optionsSettings.huntRandom) { - rng.setSeed(cast Math.random() * 10000); - rng2.setSeed(cast Math.random() * 10000); + if (!this.level.isMultiplayer || Net.isHost) { + rng.setSeed(100); + rng2.setSeed(100); + if (Settings.optionsSettings.huntRandom) { + rng.setSeed(cast Math.random() * 10000); + rng2.setSeed(cast Math.random() * 10000); + } + setupGems(); } - setupGems(); points = 0; @:privateAccess level.playGui.formatGemHuntCounter(points); } override function onGemPickup(marble:Marble, gem:Gem) { - if (marble == level.marble) - AudioManager.playSound(ResourceLoader.getResource('data/sound/gem_collect.wav', ResourceLoader.getAudio, - @:privateAccess this.level.soundResources)); - else - AudioManager.playSound(ResourceLoader.getResource('data/sound/opponent_gem_collect.wav', ResourceLoader.getAudio, - @:privateAccess this.level.soundResources)); + if (@:privateAccess !marble.isNetUpdate) { + if (marble == level.marble) + AudioManager.playSound(ResourceLoader.getResource('data/sound/gem_collect.wav', ResourceLoader.getAudio, + @:privateAccess this.level.soundResources)); + else + AudioManager.playSound(ResourceLoader.getResource('data/sound/opponent_gem_collect.wav', ResourceLoader.getAudio, + @:privateAccess this.level.soundResources)); + } activeGems.remove(gem); var beam = gemToBeamMap.get(gem); beam.setHide(true); - refillGemGroups(); - if (marble == level.marble) { - switch (gem.gemColor) { - case "red.gem": - points += 1; - @:privateAccess level.playGui.addMiddleMessage('+1', 0xFF6666); - case "yellow.gem": - points += 2; - @:privateAccess level.playGui.addMiddleMessage('+2', 0xFFFF66); - case "blue.gem": - points += 5; - @:privateAccess level.playGui.addMiddleMessage('+5', 0x6666FF); + if (!this.level.isMultiplayer || Net.isHost) { + refillGemGroups(); + } + + var incr = 0; + switch (gem.gemColor) { + case "red.gem": + incr = 1; + case "yellow.gem": + incr = 2; + case "blue.gem": + incr = 5; + } + + if (@:privateAccess !marble.isNetUpdate) { + if (marble == level.marble) { + switch (gem.gemColor) { + case "red.gem": + points += 1; + @:privateAccess level.playGui.addMiddleMessage('+1', 0xFF6666); + case "yellow.gem": + points += 2; + @:privateAccess level.playGui.addMiddleMessage('+2', 0xFFFF66); + case "blue.gem": + points += 5; + @:privateAccess level.playGui.addMiddleMessage('+5', 0x6666FF); + } + @:privateAccess level.playGui.formatGemHuntCounter(points); } - @:privateAccess level.playGui.formatGemHuntCounter(points); + } + + if (this.level.isMultiplayer && Net.isHost) { + var packet = new GemPickupPacket(); + packet.clientId = @:privateAccess marble.connection == null ? 0 : @:privateAccess marble.connection.id; + packet.gemId = gem.netIndex; + packet.serverTicks = level.timeState.ticks; + packet.scoreIncr = incr; + var os = new OutputBitStream(); + os.writeByte(GemPickup); + packet.serialize(os); + Net.sendPacketToAll(os); + + @:privateAccess level.playGui.incrementPlayerScore(packet.clientId, packet.scoreIncr); } } @@ -369,6 +415,15 @@ class HuntMode extends NullMode { activeGemSpawnGroup = spawnGroup; fillGemGroup(spawnGroup); @:privateAccess level.radar.blink(); + + if (level.isMultiplayer && Net.isHost) { + var bs = new OutputBitStream(); + bs.writeByte(GemSpawn); + var packet = new GemSpawnPacket(); + packet.gemIds = spawnGroup; + packet.serialize(bs); + Net.sendPacketToAll(bs); + } } } @@ -384,6 +439,7 @@ class HuntMode extends NullMode { var melem = new MissionElementItem(); melem.datablock = "GemItem" + gemSpawn.gemColor; var gem = new Gem(melem); + gem.netIndex = gi; gemSpawn.gem = gem; gem.setPosition(gemSpawn.position.x, gemSpawn.position.y, gemSpawn.position.z); gem.setRotationQuat(gemSpawn.rotation); @@ -406,6 +462,57 @@ class HuntMode extends NullMode { } } + public function setActiveSpawnSphere(spawnGroup:Array) { + for (sphereId in spawnGroup) { + var gemSpawn = gemSpawnPoints[sphereId]; + 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); + gem.netIndex = sphereId; + 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 + }); + } + } + activeGemSpawnGroup = spawnGroup; + } + + public inline function setGemHiddenStatus(gemId:Int, status:Bool) { + var gemSpawn = gemSpawnPoints[gemId]; + if (gemSpawn.gem != null) { + gemSpawn.gem.pickedUp = status; + gemSpawn.gem.setHide(status); + gemSpawn.gemBeam.setHide(status); + if (status) + this.activeGems.push(gemSpawn.gem); + else + this.activeGems.remove(gemSpawn.gem); + } else { + throw new Exception("Setting gem status for non existent gem!"); + } + } + function pickGemSpawnGroup() { var searchRadius = gemGroupRadius * 2; @@ -477,6 +584,45 @@ class HuntMode extends NullMode { override function onTimeExpire() { if (level.finishTime != null) return; + if (!this.level.isMultiplayer || Net.isHost) { + AudioManager.playSound(ResourceLoader.getResource('data/sound/finish.wav', ResourceLoader.getAudio, @:privateAccess level.soundResources)); + level.finishTime = level.timeState.clone(); + level.marble.setMode(Start); + level.marble.camera.finish = true; + level.finishYaw = level.marble.camera.CameraYaw; + level.finishPitch = level.marble.camera.CameraPitch; + level.displayAlert("Congratulations! You've finished!"); + level.cancel(@:privateAccess level.oobSchedule); + level.cancel(@:privateAccess level.marble.oobSchedule); + if (Net.isHost) { + NetCommands.timerRanOut(); + } + if (!level.isWatching) { + var myScore = { + name: "Player", + time: getFinishScore() + }; + Settings.saveScore(level.mission.path, myScore, getScoreType()); + var notifies = AchievementsGui.check(); + var delay = 5.0; + var achDelay = 0.0; + for (i in 0...9) { + if (notifies & (1 << i) > 0) + achDelay += 3; + } + if (notifies > 0) + achDelay += 0.5; + @:privateAccess level.schedule(level.timeState.currentAttemptTime + Math.max(delay, achDelay), () -> cast level.showFinishScreen()); + } + // Stop the ongoing sounds + if (@:privateAccess level.timeTravelSound != null) { + @:privateAccess level.timeTravelSound.stop(); + @:privateAccess level.timeTravelSound = null; + } + } + } + + public function doTimerRunOut() { AudioManager.playSound(ResourceLoader.getResource('data/sound/finish.wav', ResourceLoader.getAudio, @:privateAccess level.soundResources)); level.finishTime = level.timeState.clone(); level.marble.setMode(Start); @@ -484,24 +630,8 @@ class HuntMode extends NullMode { level.finishYaw = level.marble.camera.CameraYaw; level.finishPitch = level.marble.camera.CameraPitch; level.displayAlert("Congratulations! You've finished!"); - level.cancel(@:privateAccess level.oobSchedule); - level.cancel(@:privateAccess level.marble.oobSchedule); if (!level.isWatching) { - var myScore = { - name: "Player", - time: getFinishScore() - }; - Settings.saveScore(level.mission.path, myScore, getScoreType()); - var notifies = AchievementsGui.check(); - var delay = 5.0; - var achDelay = 0.0; - for (i in 0...9) { - if (notifies & (1 << i) > 0) - achDelay += 3; - } - if (notifies > 0) - achDelay += 0.5; - @:privateAccess level.schedule(level.timeState.currentAttemptTime + Math.max(delay, achDelay), () -> cast level.showFinishScreen()); + @:privateAccess level.schedule(level.timeState.currentAttemptTime, () -> cast level.showFinishScreen()); } // Stop the ongoing sounds if (@:privateAccess level.timeTravelSound != null) { diff --git a/src/net/GemPredictionStore.hx b/src/net/GemPredictionStore.hx new file mode 100644 index 00000000..e571f41b --- /dev/null +++ b/src/net/GemPredictionStore.hx @@ -0,0 +1,29 @@ +package net; + +import net.NetPacket.GemSpawnPacket; +import net.NetPacket.GemPickupPacket; + +class GemPredictionStore { + var predictions:Array; + + public function new() { + predictions = []; + } + + public function alloc() { + predictions.push(true); + } + + public inline function getState(netIndex:Int) { + return predictions[netIndex]; + } + + public function acknowledgeGemPickup(packet:GemPickupPacket) { + predictions[packet.gemId] = true; + } + + public function acknowledgeGemSpawn(packet:GemSpawnPacket) { + for (gemId in packet.gemIds) + predictions[gemId] = false; + } +} diff --git a/src/net/MarblePredictionStore.hx b/src/net/MarblePredictionStore.hx index 3add76c2..b56235ab 100644 --- a/src/net/MarblePredictionStore.hx +++ b/src/net/MarblePredictionStore.hx @@ -30,7 +30,7 @@ class MarblePrediction { if (p.netFlags != 0) subs += 1; // if (p.powerUpId != powerupItemId) - // subs += 1; + subs += 1; // temp // if (isControl) // subs += Math.abs(blastAmount - p.blastAmount); return subs; diff --git a/src/net/MarbleUpdateQueue.hx b/src/net/MarbleUpdateQueue.hx index 9139f7df..fae750ec 100644 --- a/src/net/MarbleUpdateQueue.hx +++ b/src/net/MarbleUpdateQueue.hx @@ -1,6 +1,5 @@ package net; -import hl.I64; import net.NetPacket.MarbleNetFlags; import net.NetPacket.MarbleUpdatePacket; import net.Net; diff --git a/src/net/Net.hx b/src/net/Net.hx index 0379a37f..b9803d60 100644 --- a/src/net/Net.hx +++ b/src/net/Net.hx @@ -1,5 +1,7 @@ package net; +import net.NetPacket.GemPickupPacket; +import net.NetPacket.GemSpawnPacket; import net.BitStream.InputBitStream; import net.BitStream.OutputBitStream; import net.NetPacket.PowerupPickupPacket; @@ -24,6 +26,8 @@ enum abstract NetPacketType(Int) from Int to Int { var MarbleUpdate; var MarbleMove; var PowerupPickup; + var GemSpawn; + var GemPickup; var PlayerInfo; } @@ -262,6 +266,22 @@ class Net { m.acknowledgePowerupPickup(powerupPickupPacket, MarbleGame.instance.world.timeState, clientConnection.moveManager.getQueueSize()); } + case GemSpawn: + var gemSpawnPacket = new GemSpawnPacket(); + gemSpawnPacket.deserialize(input); + if (MarbleGame.instance.world != null) { + MarbleGame.instance.world.spawnHuntGemsClientSide(gemSpawnPacket.gemIds); + @:privateAccess MarbleGame.instance.world.gemPredictions.acknowledgeGemSpawn(gemSpawnPacket); + } + + case GemPickup: + var gemPickupPacket = new GemPickupPacket(); + gemPickupPacket.deserialize(input); + if (MarbleGame.instance.world != null) { + @:privateAccess MarbleGame.instance.world.playGui.incrementPlayerScore(gemPickupPacket.clientId, gemPickupPacket.scoreIncr); + @:privateAccess MarbleGame.instance.world.gemPredictions.acknowledgeGemPickup(gemPickupPacket); + } + case PlayerInfo: var count = input.readByte(); for (i in 0...count) { diff --git a/src/net/NetCommands.hx b/src/net/NetCommands.hx index b40e41f2..f2380f44 100644 --- a/src/net/NetCommands.hx +++ b/src/net/NetCommands.hx @@ -1,5 +1,6 @@ package net; +import modes.HuntMode; import net.ClientConnection.GameplayState; import net.Net.NetPacketType; import gui.MultiplayerLevelSelectGui; @@ -51,4 +52,11 @@ class NetCommands { MarbleGame.instance.world.startRealTime = MarbleGame.instance.world.timeState.timeSinceLoad + t; } } + + @:rpc(server) public static function timerRanOut() { + if (Net.isClient && MarbleGame.instance.world != null) { + var huntMode:HuntMode = cast MarbleGame.instance.world.gameMode; + huntMode.onTimeExpire(); + } + } } diff --git a/src/net/NetPacket.hx b/src/net/NetPacket.hx index 7cfff9e7..1404a161 100644 --- a/src/net/NetPacket.hx +++ b/src/net/NetPacket.hx @@ -152,3 +152,50 @@ class PowerupPickupPacket implements NetPacket { b.writeInt(powerupItemId, 9); } } + +@:publicFields +class GemSpawnPacket implements NetPacket { + var gemIds:Array; + + public function new() { + gemIds = []; + } + + public function serialize(b:OutputBitStream) { + b.writeInt(gemIds.length, 5); + for (gemId in gemIds) { + b.writeInt(gemId, 10); + } + } + + public function deserialize(b:InputBitStream) { + var count = b.readInt(5); + for (i in 0...count) { + gemIds.push(b.readInt(10)); + } + } +} + +@:publicFields +class GemPickupPacket implements NetPacket { + var clientId:Int; + var serverTicks:Int; + var gemId:Int; + var scoreIncr:Int; + + public function new() {} + + public inline function deserialize(b:InputBitStream) { + clientId = b.readByte(); + serverTicks = b.readUInt16(); + gemId = b.readInt(10); + scoreIncr = b.readInt(4); + } + + public inline function serialize(b:OutputBitStream) { + b.writeByte(clientId); + b.writeUInt16(serverTicks); + b.writeInt(gemId, 10); + b.writeInt(scoreIncr, 4); + } +} diff --git a/src/shapes/Gem.hx b/src/shapes/Gem.hx index a46ca941..e5ef8869 100644 --- a/src/shapes/Gem.hx +++ b/src/shapes/Gem.hx @@ -12,6 +12,8 @@ import src.Marble; class Gem extends DtsObject { public var pickedUp:Bool; + public var netIndex:Int; + var gemColor:String; public var radarColor:Int;