From f9a58d15a9c2121f350e798bb96ec44db91bfa89 Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:33:16 +0530 Subject: [PATCH] competitive mode and small bugfixes --- data/ui/game/timerhuntrespawn.png | Bin 0 -> 1328 bytes src/Marble.hx | 41 +++--- src/MarbleWorld.hx | 8 +- src/Mission.hx | 10 +- src/Settings.hx | 4 +- src/gui/MPServerDlg.hx | 29 ++++- src/gui/PlayGui.hx | 92 +++++++++++++- src/modes/GameMode.hx | 2 + src/modes/HuntMode.hx | 204 ++++++++++++++++++++++++++---- src/modes/NullMode.hx | 2 + src/net/Net.hx | 12 +- src/net/NetCommands.hx | 12 +- src/net/NetPacket.hx | 7 +- 13 files changed, 363 insertions(+), 60 deletions(-) create mode 100644 data/ui/game/timerhuntrespawn.png diff --git a/data/ui/game/timerhuntrespawn.png b/data/ui/game/timerhuntrespawn.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f63ae009e584c9d7dace66dd7f184bce6b1b4d GIT binary patch literal 1328 zcmV-01<(44P)-s8okc+~CPX*Bg1D~4h{l95Mly)On-MR1G9Em8G<)@GVu*>spu~%a zQFe#m14sx)1QO&R3PLcjE5q)<&a=Cxy30fLu(LBg4_OkzKk0OLRej(0@9$BMhPwd; zP`LAEYqfL+7#x9RStoqWge#z&(HjYJdYY0WWx-2kOft*CkMZ z#wv)f1X94tOomdq%zQw^?{k56tq)X!cLSc=0>3VYkeUFhJ&bxmU!_y9LunLnSxm-Z zy=C-jlx4xMj%R#4mus{u+SR$N_Ryi{Vs7sRqz;;}fE+kj#b?j42n2u;(( zb{jP7O->~|{vLgpg_b@CGBKT^d<$z|hnY_MH(7oMk6G}CghyV3bM1rF9GRKxZ1O#} zxenu249kiVi$pM$4|DU7PD3(*RNzP#8I5zs7fcmzaBTg0O2c|-UGO`S z*r9xq=@dv0njQ!Nq@@9R%F-SDSn=uT&#^2sW3vJ^H4hu9wb?* z@s{%0944V+<${w0W}_xm z$H8|Rpd>g^z|+FggVC%Go%F!>A!>pWAix&}6%4L~s6GQc+0#SQddlg-qhp-Nq&XC~ zIBOb6>1uDpOi0DS6p)BPJgV>MBxFayiXu&uvyljgYBi2`>dnK3Fnc#WB=q9m1(D%e z=>lIw1jOU4a%}E53`oQgmI<~4<%(8dMIezvMN{C`n8RRiEW+;X+n207eX%5mrdS&5 z7EWK{iet0S_t_aw;dw!>#Nv=0(UqlqXiA;fv$J@O8lTo}_T8|#+{^gDDl=eDm|!l4 zT!?T{80(6Iyer*Vjv9gd}gG_PYs9N^L5T{+0wNP4o@f#!UY7Z?KODKIJLEp zb25?SOX+d6VRPNKb)SOkQgN*Avgg?s_$`+UFA{($c&XQ_-&OV|gZC{DJi!m=PLl9F z(oU0Y0;o6nrS9<6^DlECOlBJD0um$WNECbM?j-cFg!dVOl;d#JaWFj((=afV;&UWN z+Q+oK@DRF$F9A0eyF-&bbWh#>-jCFgy{w5PnXsZn@Hp|zE6fx?JspXV@;u7GU+4{A zCwl4acP<+!tUw(8kO;u$NjwqA1xtd}>qGkK3|%@DOb9pyI^F@nN**#DU!#}|Eo*B7 zeOAyu=oyY_*d^d?fEbWO$G_z|1s`pN5B5XWfYSmVG=Ue 0) { if (Net.isHost) { - if ((timeState.ticks - this.megaMarbleUseTick) <= 312 && this.megaMarbleUseTick > 0) { + if ((timeState.ticks - this.megaMarbleUseTick) <= megaMarbleDurationTicks && this.megaMarbleUseTick > 0) { this._radius = 0.6666; this.collider.radius = 0.6666; - } else if ((timeState.ticks - this.megaMarbleUseTick) > 312) { + } else if ((timeState.ticks - this.megaMarbleUseTick) > megaMarbleDurationTicks) { this.collider.radius = this._radius = 0.2; this.megaMarbleUseTick = 0; this.netFlags |= MarbleNetFlags.DoMega; } } if (Net.isClient) { - if (this.serverTicks - this.megaMarbleUseTick <= 312 && this.megaMarbleUseTick > 0) { + if (this.serverTicks - this.megaMarbleUseTick <= megaMarbleDurationTicks && this.megaMarbleUseTick > 0) { this._radius = 0.6666; this.collider.radius = 0.6666; } else { @@ -1887,7 +1889,7 @@ class Marble extends GameObject { } if (Net.isMP) { - if (m.jump && this.outOfBounds) { + if (m.powerup && this.outOfBounds) { this.level.cancel(this.oobSchedule); this.level.restart(cast this); } @@ -1896,7 +1898,7 @@ class Marble extends GameObject { interior.popTickState(); } - if (m.respawn) { + if (m.respawn && !Net.connectedServerInfo.competitiveMode) { // Competitive mode disables quick respawning if (timeState.ticks - lastRespawnTick > (25000 >> 5)) { this.level.restart(cast this); lastRespawnTick = timeState.ticks; @@ -2418,17 +2420,19 @@ class Marble extends GameObject { new Vector(1, 1, 1).add(new Vector(Math.abs(this.currentUp.x), Math.abs(this.currentUp.y), Math.abs(this.currentUp.z)).multiply(-0.8))); this.blastTicks = 0; // Now send the impulse to other marbles - var strength = blastAmt * (blastAmt > 1 ? blastRechargeShockwaveStrength : blastShockwaveStrength); - var ourPos = this.collider.transform.getPosition(); - for (marble in level.marbles) { - if (marble != cast this) { - var theirPos = marble.collider.transform.getPosition(); - var posDiff = ourPos.distance(theirPos); - if (posDiff < strength) { - var myMod = isMegaMarbleEnabled(timeState) ? 0.7 : 1.0; - var theirMod = @:privateAccess marble.isMegaMarbleEnabled(timeState) ? 0.7 : 1.0; - var impulse = theirPos.sub(ourPos).normalized().multiply(strength * (theirMod / myMod)); - marble.applyImpulse(impulse); + if (!Net.connectedServerInfo.competitiveMode || blastAmt > 1) { // Competitor mode only allows ultra blasts + var strength = blastAmt * (blastAmt > 1 ? blastRechargeShockwaveStrength : blastShockwaveStrength); + var ourPos = this.collider.transform.getPosition(); + for (marble in level.marbles) { + if (marble != cast this) { + var theirPos = marble.collider.transform.getPosition(); + var posDiff = ourPos.distance(theirPos); + if (posDiff < strength) { + var myMod = isMegaMarbleEnabled(timeState) ? 0.7 : 1.0; + var theirMod = @:privateAccess marble.isMegaMarbleEnabled(timeState) ? 0.7 : 1.0; + var impulse = theirPos.sub(ourPos).normalized().multiply(strength * (theirMod / myMod)); + marble.applyImpulse(impulse); + } } } } @@ -2527,15 +2531,16 @@ class Marble extends GameObject { } inline function isMegaMarbleEnabled(timeState:TimeState) { + var megaMarbleTicks = Net.connectedServerInfo.competitiveMode ? 156 : 312; if (this.level == null) return false; if (!this.level.isMultiplayer) { return timeState.currentAttemptTime - this.megaMarbleEnableTime < 10; } else { if (Net.isHost) { - return (megaMarbleUseTick > 0 && (this.level.timeState.ticks - megaMarbleUseTick) <= 312); + return (megaMarbleUseTick > 0 && (this.level.timeState.ticks - megaMarbleUseTick) <= megaMarbleTicks); } else { - return (megaMarbleUseTick > 0 && (serverTicks - megaMarbleUseTick) <= 312); + return (megaMarbleUseTick > 0 && (serverTicks - megaMarbleUseTick) <= megaMarbleTicks); } } } diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index b942bbb9..413b9c22 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -386,6 +386,7 @@ class MarbleWorld extends Scheduler { this.endPad.generateCollider(); if (this.isMultiplayer) { this.playGui.formatGemHuntCounter(0); + this.playGui.formatCountdownTimer(0, 0); } else { this.playGui.formatGemCounter(this.gemCount, this.totalGems); } @@ -1684,10 +1685,10 @@ class MarbleWorld extends Scheduler { return -1; } - public function spawnHuntGemsClientSide(gemIds:Array) { + public function spawnHuntGemsClientSide(gemIds:Array, expireds:Array) { if (this.isMultiplayer && Net.isClient) { var huntMode:HuntMode = cast this.gameMode; - huntMode.setActiveSpawnSphere(gemIds); + huntMode.setActiveSpawnSphere(gemIds, expireds); // radar.blink(); } } @@ -1829,6 +1830,7 @@ class MarbleWorld extends Scheduler { ProfilerUI.measure("updateTimer"); this.updateTimer(dt); + this.gameMode.update(this.timeState); if (!this.isMultiplayer) { if ((Key.isPressed(Settings.controlsSettings.respawn) || Gamepad.isPressed(Settings.gamepadSettings.respawn)) @@ -2838,7 +2840,7 @@ class MarbleWorld extends Scheduler { this.deselectPowerUp(this.marble); // Always deselect first // Wait a bit to select the powerup to prevent immediately using it incase the user skipped the OOB screen by clicking if (this.checkpointHeldPowerup != null) - this.pickUpPowerUp(this.marble, this.checkpointHeldPowerup); + this.schedule(this.timeState.currentAttemptTime + 0.5, () -> this.pickUpPowerUp(this.marble, this.checkpointHeldPowerup)); AudioManager.playSound(ResourceLoader.getResource('data/sound/spawn.wav', ResourceLoader.getAudio, this.soundResources)); } diff --git a/src/Mission.hx b/src/Mission.hx index 90b6957d..85d3f283 100644 --- a/src/Mission.hx +++ b/src/Mission.hx @@ -244,11 +244,11 @@ class Mission { return path; if (ResourceLoader.exists(dirpath + fname)) return dirpath + fname; - if (game == 'gold') { - path = StringTools.replace(path, 'interiors/', 'interiors_mbg/'); - if (ResourceLoader.exists(path)) - return path; - } + + path = StringTools.replace(path, 'interiors/', 'interiors_mbg/'); + if (ResourceLoader.exists(path)) + return path; + path = StringTools.replace(path, "lbinteriors", "interiors"); // This shit ew if (ResourceLoader.exists(path)) return path; diff --git a/src/Settings.hx b/src/Settings.hx index 9e4bc90c..b4c1baca 100644 --- a/src/Settings.hx +++ b/src/Settings.hx @@ -106,6 +106,7 @@ typedef ServerSettings = { var password:String; var forceSpectators:Bool; var quickRespawn:Bool; + var competitiveMode:Bool; } typedef PlayStatistics = { @@ -210,7 +211,8 @@ class Settings { maxPlayers: 8, description: "My cool server", forceSpectators: false, - quickRespawn: true + quickRespawn: true, + competitiveMode: false, } public static var levelStatistics:Map = []; diff --git a/src/gui/MPServerDlg.hx b/src/gui/MPServerDlg.hx index 41f716c6..9ee2b593 100644 --- a/src/gui/MPServerDlg.hx +++ b/src/gui/MPServerDlg.hx @@ -95,6 +95,7 @@ class MPServerDlg extends GuiImage { var curServerMaxPlayers = Settings.serverSettings.maxPlayers; var curServerForceSpectators = Settings.serverSettings.forceSpectators; var curServerQuickRespawn = Settings.serverSettings.quickRespawn; + var curServerCompetitive = Settings.serverSettings.competitiveMode; saveBtn.pressedAction = (e) -> { Settings.serverSettings.name = curServerName; @@ -103,6 +104,7 @@ class MPServerDlg extends GuiImage { Settings.serverSettings.maxPlayers = curServerMaxPlayers; Settings.serverSettings.forceSpectators = curServerForceSpectators; Settings.serverSettings.quickRespawn = curServerQuickRespawn; + Settings.serverSettings.competitiveMode = curServerCompetitive; if (Net.isHost) { Net.serverInfo.name = curServerName; Net.serverInfo.description = curServerDescription; @@ -110,7 +112,7 @@ class MPServerDlg extends GuiImage { Net.serverInfo.password = curServerPassword; MasterServerClient.instance.sendServerInfo(Net.serverInfo); // Update data on master server NetCommands.sendServerSettings(Settings.serverSettings.name, Settings.serverSettings.description, Settings.serverSettings.quickRespawn, - Settings.serverSettings.forceSpectators); + Settings.serverSettings.forceSpectators, Settings.serverSettings.competitiveMode); } MarbleGame.canvas.popDialog(this); } @@ -119,7 +121,7 @@ class MPServerDlg extends GuiImage { serverSettingsContainer.vertSizing = Height; serverSettingsContainer.horizSizing = Left; serverSettingsContainer.position = new Vector(16, 65); - serverSettingsContainer.extent = new Vector(390, 276); + serverSettingsContainer.extent = new Vector(390, 306); this.addChild(serverSettingsContainer); var serverName = new GuiText(markerFelt18); @@ -306,5 +308,28 @@ class MPServerDlg extends GuiImage { curServerQuickRespawn = !curServerQuickRespawn; }; serverSettingsContainer.addChild(quickRespawnChk); + + var competitive = new GuiText(markerFelt18); + competitive.text.text = "Competitive Mode:"; + competitive.text.textColor = 0xFFFFFF; + competitive.text.dropShadow = { + dx: 1, + dy: 1, + alpha: 0.5, + color: 0 + }; + competitive.position = new Vector(0, 39 * 7); + competitive.extent = new Vector(206, 14); + serverSettingsContainer.addChild(competitive); + + var competitiveChk = new GuiButton(loadButtonImages("data/ui/mp/lb_chkbx")); + competitiveChk.position = new Vector(359, 9 * 4 + 29 * 8 + 4); + competitiveChk.extent = new Vector(31, 31); + competitiveChk.buttonType = Toggle; + competitiveChk.pressed = curServerCompetitive; + competitiveChk.pressedAction = (sender) -> { + curServerCompetitive = !curServerCompetitive; + }; + serverSettingsContainer.addChild(competitiveChk); } } diff --git a/src/gui/PlayGui.hx b/src/gui/PlayGui.hx index faa0b95f..1ee8eaa5 100644 --- a/src/gui/PlayGui.hx +++ b/src/gui/PlayGui.hx @@ -62,6 +62,10 @@ class PlayGui { var timerPoint:GuiAnim; var timerColon:GuiAnim; + var countdownNumbers:Array = []; + var countdownPoint:GuiAnim; + var countdownIcon:GuiImage; + var gemCountNumbers:Array = []; var gemCountSlash:GuiImage; var gemImageScene:h3d.scene.Scene; @@ -182,8 +186,14 @@ class PlayGui { timerNumbers.push(new GuiAnim(numberTiles)); } - for (i in 0...6) { - gemCountNumbers.push(new GuiAnim(numberTiles)); + if (MarbleGame.instance.world.isMultiplayer) { + for (i in 0...3) { + countdownNumbers.push(new GuiAnim(numberTiles)); + } + + for (i in 0...6) { + gemCountNumbers.push(new GuiAnim(numberTiles)); + } } var rsgo = []; @@ -211,6 +221,8 @@ class PlayGui { initChatHud(); if (Net.hostSpectate || Net.clientSpectate) initSpectatorMenu(); + + initGemCountdownTimer(); } if (Util.isTouchDevice()) { @@ -294,6 +306,44 @@ class PlayGui { playGuiCtrl.addChild(timerCtrl); } + public function initGemCountdownTimer() { + var timerCtrl = new GuiControl(); + timerCtrl.horizSizing = HorizSizing.Center; + timerCtrl.position = new Vector(215, 1); + timerCtrl.extent = new Vector(374, 58); + + countdownNumbers[0].position = new Vector(33, 10); + countdownNumbers[0].extent = new Vector(28, 37); + + countdownNumbers[1].position = new Vector(49, 10); + countdownNumbers[1].extent = new Vector(28, 37); + + var pointCols = [ + ResourceLoader.getResource('data/ui/game/numbers/point.png', ResourceLoader.getImage, this.imageResources).toTile(), + ResourceLoader.getResource('data/ui/game/numbers/point_green.png', ResourceLoader.getImage, this.imageResources).toTile(), + ResourceLoader.getResource('data/ui/game/numbers/point_red.png', ResourceLoader.getImage, this.imageResources).toTile() + ]; + + countdownPoint = new GuiAnim(pointCols); + countdownPoint.position = new Vector(59, 10); + countdownPoint.extent = new Vector(28, 37); + + countdownNumbers[2].position = new Vector(70, 10); + countdownNumbers[2].extent = new Vector(28, 37); + + countdownIcon = new GuiImage(ResourceLoader.getResource("data/ui/game/timerhuntrespawn.png", ResourceLoader.getImage, this.imageResources).toTile()); + countdownIcon.position = new Vector(0, 10); + countdownIcon.extent = new Vector(36, 36); + + timerCtrl.addChild(countdownIcon); + timerCtrl.addChild(countdownNumbers[0]); + timerCtrl.addChild(countdownNumbers[1]); + timerCtrl.addChild(countdownPoint); + timerCtrl.addChild(countdownNumbers[2]); + + playGuiCtrl.addChild(timerCtrl); + } + public function initCenterText() { RSGOCenterText.x = scene2d.width / 2 - RSGOCenterText.frames[0].width * Settings.uiScale / 2; RSGOCenterText.y = scene2d.height * 0.3; // - RSGOCenterText.frames[0].height / 2; @@ -988,6 +1038,44 @@ class PlayGui { timerColon.anim.currentFrame = color; } + public function formatCountdownTimer(time:Float, color:Int = 0) { + if (time == 0) { + countdownNumbers[0].anim.visible = false; + countdownNumbers[1].anim.visible = false; + countdownNumbers[2].anim.visible = false; + countdownPoint.anim.visible = false; + countdownIcon.bmp.visible = false; + } else { + countdownNumbers[0].anim.visible = true; + countdownNumbers[1].anim.visible = true; + countdownNumbers[2].anim.visible = true; + countdownPoint.anim.visible = true; + countdownIcon.bmp.visible = true; + } + + var et = time * 1000; + var hundredth = Math.floor((et % 1000) / 10); + var totalSeconds = Math.floor(et / 1000); + var seconds = totalSeconds % 60; + + var secondsOne = seconds % 10; + var secondsTen = (seconds - secondsOne) / 10; + var hundredthOne = hundredth % 10; + var hundredthTen = (hundredth - hundredthOne) / 10; + + if (secondsTen > 0) { + countdownNumbers[0].anim.visible = true; + countdownNumbers[0].anim.currentFrame = secondsTen + color * 10; + } else { + countdownNumbers[0].anim.visible = false; + } + + countdownNumbers[1].anim.currentFrame = secondsOne + color * 10; + countdownNumbers[2].anim.currentFrame = hundredthTen + color * 10; + + countdownPoint.anim.currentFrame = color; + } + public function render(engine:h3d.Engine) { engine.pushTarget(this.gemImageSceneTarget); diff --git a/src/modes/GameMode.hx b/src/modes/GameMode.hx index 87134a82..b4c6c6a5 100644 --- a/src/modes/GameMode.hx +++ b/src/modes/GameMode.hx @@ -1,5 +1,6 @@ package modes; +import src.TimeState; import src.Marble; import shapes.Gem; import h3d.Quat; @@ -27,6 +28,7 @@ interface GameMode { public function onClientRestart():Void; public function onRespawn(marble:Marble):Void; public function onGemPickup(marble:Marble, gem:Gem):Void; + public function update(t:TimeState):Void; public function getPreloadFiles():Array; } diff --git a/src/modes/HuntMode.hx b/src/modes/HuntMode.hx index e9d67329..1b456f3e 100644 --- a/src/modes/HuntMode.hx +++ b/src/modes/HuntMode.hx @@ -70,8 +70,13 @@ class HuntMode extends NullMode { var activeGemSpawnGroup:Array; var gemBeams:Array = []; var gemToBeamMap:Map = []; + var gemToBlackBeamMap:Map = []; var activeGems:Array = []; var points:Int = 0; + var gemsCentroid:Vector; + var idealSpawnIndex:Int; + var expiredGems:Map = []; + var competitiveTimerStartTicks:Int; override function missionScan(mission:Mission) { function scanMission(simGroup:MissionElementSimGroup) { @@ -92,11 +97,13 @@ class HuntMode extends NullMode { }; override function getSpawnTransform() { - var idx = Math.floor(rng2.randRange(0, playerSpawnPoints.length - 1)); - while (spawnPointTaken[idx]) { - idx = Math.floor(rng2.randRange(0, playerSpawnPoints.length - 1)); + var idx = Net.connectedServerInfo.competitiveMode ? idealSpawnIndex : Math.floor(rng2.randRange(0, playerSpawnPoints.length - 1)); + if (!Net.connectedServerInfo.competitiveMode) { + while (spawnPointTaken[idx]) { + idx = Math.floor(rng2.randRange(0, playerSpawnPoints.length - 1)); + } + spawnPointTaken[idx] = true; } - spawnPointTaken[idx] = true; var randomSpawn = playerSpawnPoints[idx]; var spawnPos = MisParser.parseVector3(randomSpawn.position); @@ -174,6 +181,7 @@ class HuntMode extends NullMode { if (this.gemSpawnPoints == null) { this.gemOctree = new Octree(); this.gemSpawnPoints = []; + this.gemsCentroid = new Vector(); for (gem in this.level.gems) { var spawn:GemSpawnPoint = new GemSpawnPoint(gem.getAbsPos().getPosition(), gem, gemSpawnPoints.length); gem.setHide(true); @@ -185,6 +193,21 @@ class HuntMode extends NullMode { if (level.isMultiplayer) { @:privateAccess level.gemPredictions.alloc(); } + gemsCentroid.load(gemsCentroid.add(gem.getAbsPos().getPosition())); + } + if (gemSpawnPoints.length > 0) + gemsCentroid.load(gemsCentroid.multiply(1.0 / gemSpawnPoints.length)); + + var closestSpawnIndex = 0; + var closestSpawnDistance = 1e8; + for (i in 0...playerSpawnPoints.length) { + var spawn = playerSpawnPoints[i]; + var spawnPos = MisParser.parseVector3(spawn.position); + spawnPos.x *= -1; + if (spawnPos.distance(gemsCentroid) < closestSpawnDistance) { + closestSpawnDistance = spawnPos.distance(gemsCentroid); + closestSpawnIndex = i; + } } } for (i in 0...spawnPointTaken.length) { @@ -206,8 +229,8 @@ class HuntMode extends NullMode { spawnHuntGems(); } - function spawnHuntGems() { - if (activeGems.length != 0) + function spawnHuntGems(force:Bool = false) { + if (activeGems.length != 0 && !force) return; var gemGroupRadius = 15.0; var maxGemsPerSpawn = 7; @@ -267,16 +290,39 @@ class HuntMode extends NullMode { return 0; }); var spawnSet = results.slice(0, maxGemsPerSpawn).map(x -> x.gem); + + if (force) { + for (activeGem in activeGemSpawnGroup) + spawnSet.remove(activeGem); + } + for (gem in spawnSet) { spawnGem(gem); } - activeGemSpawnGroup = spawnSet; + if (!force) + activeGemSpawnGroup = spawnSet; + else { + var uncollectedGems = []; + for (g in activeGemSpawnGroup) { + if (!gemSpawnPoints[g].gem.pickedUp) + uncollectedGems.push(g); + } + activeGemSpawnGroup = uncollectedGems.concat(spawnSet); + } if (level.isMultiplayer && Net.isHost) { var bs = new OutputBitStream(); bs.writeByte(GemSpawn); var packet = new GemSpawnPacket(); - packet.gemIds = spawnSet; + packet.gemIds = activeGemSpawnGroup; + packet.expireds = []; + for (i in 0...packet.gemIds.length) { + if (expiredGems.exists(gemSpawnPoints[packet.gemIds[i]].gem)) { + packet.expireds.push(true); + } else { + packet.expireds.push(false); + } + } packet.serialize(bs); Net.sendPacketToIngame(bs); } @@ -284,29 +330,43 @@ class HuntMode extends NullMode { lastSpawn = furthest; } - function spawnGem(spawn:Int) { + function spawnGem(spawn:Int, expired:Bool = false) { var gem = gemSpawnPoints[spawn]; gem.gem.setHide(false); gem.gem.pickedUp = false; this.level.collisionWorld.addEntity(gem.gem.boundingCollider); activeGems.push(gem.gem); - if (gem.gemBeam == null) { - gem.gemBeam = new GemBeam(StringTools.replace(gem.gem.gemColor, '.gem', '')); + if (!expired) { + if (gem.gemBeam == null) { + gem.gemBeam = new GemBeam(StringTools.replace(gem.gem.gemColor, '.gem', '')); - var gemPos = gem.gem.getAbsPos().getPosition(); + var gemPos = gem.gem.getAbsPos().getPosition(); - gem.gemBeam.setPosition(gemPos.x, gemPos.y, gemPos.z); - gem.gemBeam.setRotationQuat(gem.gem.getRotationQuat().clone()); - // gem.gemBeam.setOpacity(0.99); - this.gemBeams.push(gem.gemBeam); + gem.gemBeam.setPosition(gemPos.x, gemPos.y, gemPos.z); + gem.gemBeam.setRotationQuat(gem.gem.getRotationQuat().clone()); + // gem.gemBeam.setOpacity(0.99); + this.gemBeams.push(gem.gemBeam); - this.gemToBeamMap.set(gem.gem, gem.gemBeam); + this.gemToBeamMap.set(gem.gem, gem.gemBeam); - level.addDtsObject(gem.gemBeam, () -> { - // Please be fast lol - }); + level.addDtsObject(gem.gemBeam, () -> { + // Please be fast lol + }); + } else { + gem.gemBeam.setHide(false); + } } else { - gem.gemBeam.setHide(false); + if (gemToBlackBeamMap.exists(gem.gem)) { + gemToBlackBeamMap.get(gem.gem).setHide(false); + } else { + var blackBeam = new GemBeam("black"); + var pos = gem.gem.getAbsPos().getPosition(); + blackBeam.setPosition(gem.gem.x, gem.gem.y, gem.gem.z); + blackBeam.setRotationQuat(gem.gem.getRotationQuat().clone()); + blackBeam.setHide(false); + level.addDtsObject(blackBeam, () -> {}); + gemToBlackBeamMap.set(gem.gem, blackBeam); + } } } @@ -325,10 +385,11 @@ class HuntMode extends NullMode { } } - public function setActiveSpawnSphere(gems:Array) { + public function setActiveSpawnSphere(gems:Array, expireds:Array) { hideExisting(); - for (gem in gems) { - spawnGem(gem); + for (i in 0...gems.length) { + var gem = gems[i]; + spawnGem(gem, expireds[i]); } } @@ -351,6 +412,9 @@ class HuntMode extends NullMode { if (gs.gemBeam != null) { gs.gemBeam.setHide(true); } + if (gemToBlackBeamMap.exists(gs.gem)) { + gemToBlackBeamMap.get(gs.gem).setHide(true); + } } } } @@ -362,15 +426,18 @@ class HuntMode extends NullMode { override function onRestart() { setupGems(); points = 0; + competitiveTimerStartTicks = 0; @:privateAccess level.playGui.formatGemHuntCounter(points); } override function onMissionLoad() { prepareGems(); + competitiveTimerStartTicks = 0; } override function onClientRestart() { prepareGems(); + competitiveTimerStartTicks = 0; } override function onTimeExpire() { @@ -420,6 +487,17 @@ class HuntMode extends NullMode { @:privateAccess this.level.soundResources)); } activeGems.remove(gem); + + var wasExpiredGem = false; + + if (expiredGems.exists(gem)) { + wasExpiredGem = true; + expiredGems.remove(gem); + } + if (gemToBlackBeamMap.exists(gem)) { + gemToBlackBeamMap.get(gem).setHide(true); + } + var beam = gemToBeamMap.get(gem); beam.setHide(true); @@ -455,6 +533,43 @@ class HuntMode extends NullMode { } if (this.level.isMultiplayer && Net.isHost) { + if (Net.connectedServerInfo.competitiveMode && !wasExpiredGem) { + if (competitiveTimerStartTicks == 0) { + NetCommands.setCompetitiveTimerStartTicks(this.level.timeState.ticks); + } + var remaining = 0; + for (g in activeGems) + if (!expiredGems.exists(g)) + remaining++; + if (remaining == 3) { + var currentTime = level.timeState.ticks; + var endTime = competitiveTimerStartTicks + (20000 >> 5); + var remainingTicks = (endTime - currentTime); + if (remainingTicks > (15000 >> 5)) { + NetCommands.setCompetitiveTimerStartTicks(currentTime - (5000 >> 5)); + } + } + if (remaining == 2) { + var currentTime = level.timeState.ticks; + var endTime = competitiveTimerStartTicks + (20000 >> 5); + var remainingTicks = (endTime - currentTime); + if (remainingTicks > (10000 >> 5)) { + NetCommands.setCompetitiveTimerStartTicks(currentTime - (10000 >> 5)); + } + } + if (remaining == 1) { + var currentTime = level.timeState.ticks; + var endTime = competitiveTimerStartTicks + (20000 >> 5); + var remainingTicks = (endTime - currentTime); + if (remainingTicks > (5000 >> 5)) { + NetCommands.setCompetitiveTimerStartTicks(currentTime - (15000 >> 5)); + } + } + if (remaining == 0) { + NetCommands.setCompetitiveTimerStartTicks(0); + } + } + var packet = new GemPickupPacket(); packet.clientId = @:privateAccess marble.connection == null ? 0 : @:privateAccess marble.connection.id; packet.gemId = gem.netIndex; @@ -474,6 +589,47 @@ class HuntMode extends NullMode { } } + public function setCompetitiveTimerStartTicks(ticks:Int) { + competitiveTimerStartTicks = ticks; + } + + function spawnNextGemCluster() { + // Expire all existing + for (g in activeGems) { + expiredGems.set(g, true); + var gemBeam = gemToBeamMap.get(g); + gemBeam.setHide(true); + if (gemToBlackBeamMap.exists(g)) { + gemToBlackBeamMap.get(g).setHide(false); + } else { + var blackBeam = new GemBeam("black"); + var pos = g.getAbsPos().getPosition(); + blackBeam.setPosition(g.x, g.y, g.z); + blackBeam.setRotationQuat(g.getRotationQuat().clone()); + blackBeam.setHide(false); + level.addDtsObject(blackBeam, () -> {}); + gemToBlackBeamMap.set(g, blackBeam); + } + } + spawnHuntGems(true); + } + + override function update(t:src.TimeState) { + if (Net.connectedServerInfo.competitiveMode) { + if (competitiveTimerStartTicks != 0) { + var currentTime = Net.isHost ? t.ticks : @:privateAccess level.marble.serverTicks; + var endTime = competitiveTimerStartTicks + (20000 >> 5); + @:privateAccess level.playGui.formatCountdownTimer(Math.max(0, (endTime - currentTime) * 0.032), 0); + if (Net.isHost && endTime < currentTime) { + spawnNextGemCluster(); + NetCommands.setCompetitiveTimerStartTicks(0); + } + } else { + @:privateAccess level.playGui.formatCountdownTimer(0, 0); + } + } + } + override public function timeMultiplier() { return -1; } diff --git a/src/modes/NullMode.hx b/src/modes/NullMode.hx index 16d54164..003cd4c6 100644 --- a/src/modes/NullMode.hx +++ b/src/modes/NullMode.hx @@ -138,4 +138,6 @@ class NullMode implements GameMode { public function onClientRestart() {} public function onMissionLoad() {} + + public function update(t:src.TimeState) {} } diff --git a/src/net/Net.hx b/src/net/Net.hx index 595a9569..9d594755 100644 --- a/src/net/Net.hx +++ b/src/net/Net.hx @@ -73,6 +73,7 @@ class ConnectedServerInfo { var description:String; var quickRespawn:Bool; var forceSpectator:Bool; + var competitiveMode:Bool; } class Net { @@ -112,6 +113,13 @@ class Net { clientId = 0; isMP = true; MasterServerClient.instance.sendServerInfo(serverInfo); + Net.connectedServerInfo = { + name: name, + description: description, + competitiveMode: Settings.serverSettings.competitiveMode, + quickRespawn: Settings.serverSettings.quickRespawn, + forceSpectator: Settings.serverSettings.forceSpectators, + }; onHosted(); }); } @@ -602,7 +610,7 @@ class Net { // NetCommands.setLobbyCustLevelNameClient(conn, MultiplayerLevelSelectGui.custPath); // } else { NetCommands.sendServerSettingsClient(conn, Settings.serverSettings.name, Settings.serverSettings.description, Settings.serverSettings.quickRespawn, - Settings.serverSettings.forceSpectators); + Settings.serverSettings.forceSpectators, Settings.serverSettings.competitiveMode); NetCommands.setLobbyLevelIndexClient(conn, MPPlayMissionGui.currentCategoryStatic, MPPlayMissionGui.currentSelectionStatic); // } @@ -749,7 +757,7 @@ class Net { var gemSpawnPacket = new GemSpawnPacket(); gemSpawnPacket.deserialize(input); if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) { - MarbleGame.instance.world.spawnHuntGemsClientSide(gemSpawnPacket.gemIds); + MarbleGame.instance.world.spawnHuntGemsClientSide(gemSpawnPacket.gemIds, gemSpawnPacket.expireds); @:privateAccess MarbleGame.instance.world.gemPredictions.acknowledgeGemSpawn(gemSpawnPacket); } diff --git a/src/net/NetCommands.hx b/src/net/NetCommands.hx index a9dcbbe6..5ed0ebf7 100644 --- a/src/net/NetCommands.hx +++ b/src/net/NetCommands.hx @@ -425,12 +425,13 @@ class NetCommands { } } - @:rpc(server) public static function sendServerSettings(name:String, desc:String, quickRespawn:Bool, forceSpectator:Bool) { + @:rpc(server) public static function sendServerSettings(name:String, desc:String, quickRespawn:Bool, forceSpectator:Bool, competitive:Bool) { Net.connectedServerInfo = { name: name, description: desc, quickRespawn: quickRespawn, - forceSpectator: forceSpectator + forceSpectator: forceSpectator, + competitiveMode: competitive }; } @@ -452,4 +453,11 @@ class NetCommands { } MPPlayMissionGui.addChatMessage(msg); } + + @:rpc(server) public static function setCompetitiveTimerStartTicks(ticks:Int) { + if (MarbleGame.instance.world != null) { + var huntMode = cast(MarbleGame.instance.world.gameMode, HuntMode); + huntMode.setCompetitiveTimerStartTicks(ticks); + } + } } diff --git a/src/net/NetPacket.hx b/src/net/NetPacket.hx index 31a35293..a06bbcc2 100644 --- a/src/net/NetPacket.hx +++ b/src/net/NetPacket.hx @@ -262,15 +262,19 @@ class ExplodableUpdatePacket implements NetPacket { @:publicFields class GemSpawnPacket implements NetPacket { var gemIds:Array; + var expireds:Array; public function new() { gemIds = []; + expireds = []; } public function serialize(b:OutputBitStream) { b.writeInt(gemIds.length, 5); - for (gemId in gemIds) { + for (i in 0...gemIds.length) { + var gemId = gemIds[i]; b.writeInt(gemId, 11); + b.writeFlag(expireds[i]); } } @@ -278,6 +282,7 @@ class GemSpawnPacket implements NetPacket { var count = b.readInt(5); for (i in 0...count) { gemIds.push(b.readInt(11)); + expireds.push(b.readFlag()); } } }