From ecaa938f957f0e7e90cbe1d777d8c8c90b244e96 Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Sat, 6 Apr 2024 18:31:33 +0530 Subject: [PATCH] work on better lobby and gravity impl start --- src/Marble.hx | 5 +- src/MarbleWorld.hx | 16 ++- src/Settings.hx | 2 +- src/gui/Canvas.hx | 2 + src/gui/CreateMatchGui.hx | 2 +- src/gui/EnterNameDlg.hx | 133 ++++++++++------------- src/gui/MPServerListGui.hx | 2 + src/gui/MultiplayerGui.hx | 4 + src/gui/MultiplayerLevelSelectGui.hx | 157 +++++++++++++++------------ src/gui/MultiplayerLoadingGui.hx | 68 +++++++++++- src/net/BitStream.hx | 16 +++ src/net/ClientConnection.hx | 17 +++ src/net/MarbleUpdateQueue.hx | 10 ++ src/net/MasterServerClient.hx | 10 +- src/net/Net.hx | 142 +++++++++++------------- src/net/NetCommands.hx | 39 ++++++- src/net/NetPacket.hx | 15 ++- src/net/RPCMacro.hx | 8 ++ 18 files changed, 407 insertions(+), 241 deletions(-) diff --git a/src/Marble.hx b/src/Marble.hx index 56839257..fc96d2a2 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1732,6 +1732,7 @@ class Marble extends GameObject { marbleUpdate.oob = this.outOfBounds; marbleUpdate.powerUpId = this.heldPowerup != null ? this.heldPowerup.netIndex : 0x1FF; marbleUpdate.netFlags = this.netFlags; + marbleUpdate.gravityDirection = this.currentUp; this.netFlags = 0; marbleUpdate.serialize(b); return b.getBytes(); @@ -1756,6 +1757,8 @@ class Marble extends GameObject { this.blastUseTick = p.blastTick; this.helicopterUseTick = p.heliTick; this.megaMarbleUseTick = p.megaTick; + this.currentUp = p.gravityDirection; + this.level.setUp(cast this, this.currentUp, this.level.timeState, true); this.outOfBounds = p.oob; this.camera.oob = p.oob; if (p.powerUpId == 0x1FF) { @@ -2306,7 +2309,7 @@ class Marble extends GameObject { this.blastTicks = 0; this.helicopterUseTick = 0; this.megaMarbleUseTick = 0; - this.netFlags = MarbleNetFlags.DoBlast | MarbleNetFlags.DoMega | MarbleNetFlags.DoHelicopter | MarbleNetFlags.PickupPowerup; + this.netFlags = MarbleNetFlags.DoBlast | MarbleNetFlags.DoMega | MarbleNetFlags.DoHelicopter | MarbleNetFlags.PickupPowerup | MarbleNetFlags.GravityChange; this.lastContactNormal = new Vector(0, 0, 1); this.contactEntities = []; this._firstTick = true; diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 57a94cb9..c446e6b9 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -342,12 +342,12 @@ class MarbleWorld extends Scheduler { if (this.isMultiplayer) { // Add us if (Net.isHost) { - this.playGui.addPlayer(0, 'Player 0', true); + this.playGui.addPlayer(0, Settings.highscoreName.substr(0, 15), true); } else { - this.playGui.addPlayer(Net.clientId, 'Player ${Net.clientId}', true); + this.playGui.addPlayer(Net.clientId, Settings.highscoreName.substr(0, 15), true); } for (client in Net.clientIdMap) { - this.playGui.addPlayer(client.id, 'Player ${client.id}', false); + this.playGui.addPlayer(client.id, client.name.substr(0, 15), false); } } @@ -651,6 +651,7 @@ class MarbleWorld extends Scheduler { if (isMultiplayer) { marble.megaMarbleUseTick = 0; marble.helicopterUseTick = 0; + marble.collider.radius = marble._radius = 0.3; } else { @:privateAccess marble.helicopterEnableTime = -1e8; @:privateAccess marble.megaMarbleEnableTime = -1e8; @@ -1204,8 +1205,8 @@ 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; + // if (distFromUs < 5) // { + m.calculationTicks = ourQueuedMoves.length; // } else { // m.calculationTicks = Std.int(Math.max(1, ourQueuedMoves.length - (distFromUs - 5) / 3)); // } @@ -2058,7 +2059,12 @@ class MarbleWorld extends Scheduler { } public function setUp(marble:Marble, vec:Vector, timeState:TimeState, instant:Bool = false) { + if (vec == marble.currentUp) + return; marble.currentUp = vec; + if (isMultiplayer && Net.isHost) { + @:privateAccess marble.netFlags |= MarbleNetFlags.GravityChange; + } if (marble == this.marble) { var currentQuat = this.getOrientationQuat(timeState.currentAttemptTime); var oldUp = new Vector(0, 0, 1); diff --git a/src/Settings.hx b/src/Settings.hx index b09ef48d..99d6f88b 100644 --- a/src/Settings.hx +++ b/src/Settings.hx @@ -200,7 +200,7 @@ class Settings { public static var achievementProgression:Int; - public static var highscoreName = ""; + public static var highscoreName = "Player"; public static var uiScale = 1.0; diff --git a/src/gui/Canvas.hx b/src/gui/Canvas.hx index 8a67f2f8..c6ce2f44 100644 --- a/src/gui/Canvas.hx +++ b/src/gui/Canvas.hx @@ -11,6 +11,7 @@ import gui.GuiControl.MouseState; class Canvas extends GuiControl { var scene2d:Scene; var marbleGame:MarbleGame; + var content:GuiControl; public function new(scene, marbleGame:MarbleGame) { super(); @@ -30,6 +31,7 @@ class Canvas extends GuiControl { public function setContent(content:GuiControl) { this.dispose(); + this.content = content; this.addChild(content); this.render(scene2d); } diff --git a/src/gui/CreateMatchGui.hx b/src/gui/CreateMatchGui.hx index 5e445b7d..b8f111ed 100644 --- a/src/gui/CreateMatchGui.hx +++ b/src/gui/CreateMatchGui.hx @@ -114,8 +114,8 @@ class CreateMatchGui extends GuiImage { nextButton.gamepadAccelerator = ["A"]; nextButton.accelerators = [hxd.Key.ENTER]; nextButton.pressedAction = (e) -> { + Net.hostServer('${Settings.highscoreName}\'s Server', maxPlayers, privateSlots, privateGame); MarbleGame.canvas.setContent(new MultiplayerLevelSelectGui(true)); - Net.hostServer("My Server", maxPlayers, privateSlots, privateGame); }; bottomBar.addChild(nextButton); } diff --git a/src/gui/EnterNameDlg.hx b/src/gui/EnterNameDlg.hx index 5b44ea02..0a54a402 100644 --- a/src/gui/EnterNameDlg.hx +++ b/src/gui/EnterNameDlg.hx @@ -9,62 +9,53 @@ import h3d.Vector; import src.ResourceLoader; import src.MarbleGame; -class EnterNameDlg extends GuiControl { - public function new(place:Int, okFunc:String->Void) { - super(); - this.position = new Vector(); - this.extent = new Vector(640, 480); +class EnterNameDlg extends GuiImage { + public function new() { + var res = ResourceLoader.getImage("data/ui/xbox/roundedBG.png").resource.toTile(); + super(res); this.horizSizing = Width; this.vertSizing = Height; + this.position = new Vector(); + this.extent = new Vector(640, 480); - function loadButtonImages(path:String) { - var normal = ResourceLoader.getResource('${path}_n.png', ResourceLoader.getImage, this.imageResources).toTile(); - var hover = ResourceLoader.getResource('${path}_h.png', ResourceLoader.getImage, this.imageResources).toTile(); - var pressed = ResourceLoader.getResource('${path}_d.png', ResourceLoader.getImage, this.imageResources).toTile(); - return [normal, hover, pressed]; - } - - var arial14fontdata = ResourceLoader.getFileEntry("data/font/arial.fnt"); + 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 12 * Settings.uiScale, MultiChannel); + var arial14 = arial14b.toSdfFont(cast 21 * Settings.uiScale, h2d.Font.SDFChannel.MultiChannel); - var domcasual32fontdata = ResourceLoader.getFileEntry("data/font/DomCasualD.fnt"); - var domcasual32b = new BitmapFont(domcasual32fontdata.entry); - @:privateAccess domcasual32b.loader = ResourceLoader.loader; - var domcasual32 = domcasual32b.toSdfFont(cast 26 * Settings.uiScale, MultiChannel); - var domcasual48 = domcasual32b.toSdfFont(cast 42 * Settings.uiScale, MultiChannel); + var yesNoFrame = new GuiImage(ResourceLoader.getResource("data/ui/xbox/popupGUI.png", ResourceLoader.getImage, this.imageResources).toTile()); + yesNoFrame.horizSizing = Center; + yesNoFrame.vertSizing = Center; + yesNoFrame.position = new Vector(70, 30); + yesNoFrame.extent = new Vector(512, 400); + this.addChild(yesNoFrame); - function mlFontLoader(text:String) { - switch (text) { - case "DomCasual32": - return domcasual32; - case "DomCasual48": - return domcasual48; - case "Arial14": - return arial14; - default: - return null; - } - } + var text = "Enter your name"; - var dlg = new GuiImage(ResourceLoader.getResource("data/ui/endgame/enternamebox.png", ResourceLoader.getImage, this.imageResources).toTile()); - dlg.horizSizing = Center; - dlg.vertSizing = Center; - dlg.position = new Vector(110, 112); - dlg.extent = new Vector(420, 256); - this.addChild(dlg); + var yesNoText = new GuiMLText(arial14, null); + yesNoText.position = new Vector(103, 85); + yesNoText.extent = new Vector(313, 186); + yesNoText.text.text = text; + yesNoText.text.textColor = 0xEBEBEB; + yesNoFrame.addChild(yesNoText); - var enterNameEdit = new GuiTextInput(domcasual32); - enterNameEdit.text.textColor = 0; - enterNameEdit.text.selectionColor.setColor(0xFFFFFFFF); - enterNameEdit.text.selectionTile = h2d.Tile.fromColor(0x808080, 0, hxd.Math.ceil(enterNameEdit.text.font.lineHeight)); - enterNameEdit.position = new Vector(28, 130); - enterNameEdit.extent = new Vector(363, 38); - enterNameEdit.text.text = Settings.highscoreName; - haxe.Timer.delay(() -> { - enterNameEdit.text.focus(); - }, 5); + var textFrame = new GuiControl(); + textFrame.position = new Vector(33, 107); + textFrame.extent = new Vector(232, 40); + textFrame.horizSizing = Center; + yesNoFrame.addChild(textFrame); + + var textInput = new GuiTextInput(arial14); + textInput.position = new Vector(6, 5); + textInput.extent = new Vector(216, 40); + textInput.horizSizing = Width; + textInput.vertSizing = Height; + textInput.text.textColor = 0xEBEBEB; + textInput.text.selectionColor.setColor(0x8DFF8D); + textInput.text.selectionTile = h2d.Tile.fromColor(0x88BCEE, 0, hxd.Math.ceil(textInput.text.font.lineHeight)); + textFrame.addChild(textInput); + + textInput.text.text = Settings.highscoreName; enterNameEdit.text.onFocus = (e) -> { dlg.vertSizing = Bottom; dlg.position = new Vector(110, 56); @@ -76,34 +67,28 @@ class EnterNameDlg extends GuiControl { dlg.render(MarbleGame.canvas.scene2d); } - var okbutton = new GuiButton(loadButtonImages("data/ui/endgame/ok")); - okbutton.position = new Vector(151, 184); - okbutton.extent = new Vector(110, 55); - okbutton.accelerator = hxd.Key.ENTER; - okbutton.gamepadAccelerator = ["A"]; - okbutton.pressedAction = (sender) -> { - MarbleGame.canvas.popDialog(this); - Settings.highscoreName = enterNameEdit.text.text; - okFunc(enterNameEdit.text.text); + var okButton = new GuiXboxButton("Ok", 120); + okButton.position = new Vector(211, 248); + okButton.extent = new Vector(120, 94); + okButton.vertSizing = Top; + okButton.accelerators = [hxd.Key.ENTER]; + okButton.gamepadAccelerator = ["A"]; + okButton.pressedAction = (sender) -> { + Settings.highscoreName = textInput.text.text.substr(0, 15); // Max 15 pls + Settings.save(); + MarbleGame.canvas.setContent(new MultiplayerGui()); } - dlg.addChild(okbutton); + yesNoFrame.addChild(okButton); - var wnd = new GuiImage(ResourceLoader.getResource("data/ui/endgame/window.png", ResourceLoader.getImage, this.imageResources).toTile()); - wnd.horizSizing = Width; - wnd.vertSizing = Height; - wnd.position = new Vector(16, 119); - wnd.extent = new Vector(388, 56); - dlg.addChild(wnd); - - var enterNameText = new GuiMLText(domcasual32, mlFontLoader); - enterNameText.text.textColor = 0xFFFFFF; - enterNameText.text.filter = new DropShadow(1.414, 0.785, 0x7777777F, 1, 0, 0.4, 1, true); - enterNameText.position = new Vector(37, 23); - enterNameText.extent = new Vector(345, 85); - // enterNameText.justify = Center; - enterNameText.text.text = '

Well Done!
You have the${["", " second", " third", " fourth", " fifth"][place]} top time!

'; - dlg.addChild(enterNameText); - - dlg.addChild(enterNameEdit); + var cancelButton = new GuiXboxButton("Cancel", 120); + cancelButton.position = new Vector(321, 248); + cancelButton.extent = new Vector(120, 94); + cancelButton.vertSizing = Top; + cancelButton.accelerators = [hxd.Key.ENTER]; + cancelButton.gamepadAccelerator = ["A"]; + cancelButton.pressedAction = (sender) -> { + MarbleGame.canvas.setContent(new MultiplayerGui()); + } + yesNoFrame.addChild(cancelButton); } } diff --git a/src/gui/MPServerListGui.hx b/src/gui/MPServerListGui.hx index e7dabf54..05e55ca9 100644 --- a/src/gui/MPServerListGui.hx +++ b/src/gui/MPServerListGui.hx @@ -120,8 +120,10 @@ class MPServerListGui extends GuiImage { nextButton.accelerators = [hxd.Key.ENTER]; nextButton.gamepadAccelerator = ["X"]; nextButton.pressedAction = (e) -> { + MarbleGame.canvas.setContent(new MultiplayerLoadingGui("Connecting")); Net.joinServer(ourServerList[curSelection].name, () -> { MarbleGame.canvas.setContent(new MultiplayerLevelSelectGui(false)); + Net.remoteServerInfo = ourServerList[curSelection]; }); }; bottomBar.addChild(nextButton); diff --git a/src/gui/MultiplayerGui.hx b/src/gui/MultiplayerGui.hx index f63547a4..c9b0bc5e 100644 --- a/src/gui/MultiplayerGui.hx +++ b/src/gui/MultiplayerGui.hx @@ -78,6 +78,10 @@ class MultiplayerGui extends GuiImage { // }); }); + btnList.addButton(5, 'Change Display Name', (e) -> { + MarbleGame.canvas.setContent(new EnterNameDlg()); + }); + var bottomBar = new GuiControl(); bottomBar.position = new Vector(0, 590); bottomBar.extent = new Vector(640, 200); diff --git a/src/gui/MultiplayerLevelSelectGui.hx b/src/gui/MultiplayerLevelSelectGui.hx index 6dec7aef..817527a0 100644 --- a/src/gui/MultiplayerLevelSelectGui.hx +++ b/src/gui/MultiplayerLevelSelectGui.hx @@ -1,5 +1,6 @@ package gui; +import net.Net; import net.NetCommands; import modes.GameMode.ScoreType; import src.Util; @@ -19,6 +20,8 @@ class MultiplayerLevelSelectGui extends GuiImage { static var setLevelFn:Int->Void; static var playSelectedLevel:Void->Void; + var playerList:GuiMLTextListCtrl; + var updatePlayerCountFn:(Int, Int, Int, Int) -> Void; var innerCtrl:GuiControl; public function new(isHost:Bool) { @@ -29,8 +32,15 @@ class MultiplayerLevelSelectGui extends GuiImage { var arial14b = new BitmapFont(arial14fontdata.entry); @:privateAccess arial14b.loader = ResourceLoader.loader; var arial14 = arial14b.toSdfFont(cast 21 * Settings.uiScale, h2d.Font.SDFChannel.MultiChannel); + var arial12 = arial14b.toSdfFont(cast 16 * Settings.uiScale, h2d.Font.SDFChannel.MultiChannel); function mlFontLoader(text:String) { - return arial14; + switch (text) { + case "arial14": + return arial14; + case "arial12": + return arial12; + } + return null; } MarbleGame.instance.toRecord = false; @@ -127,11 +137,34 @@ class MultiplayerLevelSelectGui extends GuiImage { rootTitle.position = new Vector(100, 30); rootTitle.extent = new Vector(1120, 80); rootTitle.text.textColor = 0xFFFFFF; - rootTitle.text.text = "SELECT LEVEL"; + rootTitle.text.text = "LOBBY"; rootTitle.text.alpha = 0.5; innerCtrl.addChild(rootTitle); - var bottomBar = new GuiControl(); + var playerWnd = new GuiImage(ResourceLoader.getResource("data/ui/xbox/achievementWindow.png", ResourceLoader.getImage, this.imageResources).toTile()); + playerWnd.horizSizing = Right; + playerWnd.vertSizing = Bottom; + playerWnd.position = new Vector(330, 58); + playerWnd.extent = new Vector(640, 480); + innerCtrl.addChild(playerWnd); + + var playerListArr = [Settings.highscoreName]; + if (Net.isClient) { + for (c => v in Net.clientIdMap) { + playerListArr.push(v.getName()); + } + } + + playerList = new GuiMLTextListCtrl(arial14, playerListArr, null); + playerList.selectedColor = 0xF29515; + playerList.selectedFillColor = 0xEBEBEB; + playerList.position = new Vector(25, 22); + playerList.extent = new Vector(550, 480); + playerList.scrollable = true; + playerList.onSelectedFunc = (sel) -> {} + playerWnd.addChild(playerList); + + var bottomBar = new GuiControl(); bottomBar.position = new Vector(0, 590); bottomBar.extent = new Vector(640, 200); bottomBar.horizSizing = Width; @@ -144,7 +177,10 @@ class MultiplayerLevelSelectGui extends GuiImage { backButton.horizSizing = Right; backButton.gamepadAccelerator = ["B"]; backButton.accelerators = [hxd.Key.ESCAPE, hxd.Key.BACKSPACE]; - backButton.pressedAction = (e) -> MarbleGame.canvas.setContent(new DifficultySelectGui()); + backButton.pressedAction = (e) -> { + Net.disconnect(); + MarbleGame.canvas.setContent(new CreateMatchGui()); + } bottomBar.addChild(backButton); // var lbButton = new GuiXboxButton("Leaderboard", 220); @@ -153,18 +189,17 @@ class MultiplayerLevelSelectGui extends GuiImage { // lbButton.horizSizing = Right; // bottomBar.addChild(lbButton); - if (isHost) { - var nextButton = new GuiXboxButton("Play", 160); - nextButton.position = new Vector(960, 0); - nextButton.vertSizing = Bottom; - nextButton.horizSizing = Right; - nextButton.gamepadAccelerator = ["A"]; - nextButton.accelerators = [hxd.Key.ENTER]; - nextButton.pressedAction = (e) -> { - NetCommands.playLevel(); - }; - bottomBar.addChild(nextButton); - } + var nextButton = new GuiXboxButton("Ready", 160); + nextButton.position = new Vector(960, 0); + nextButton.vertSizing = Bottom; + nextButton.horizSizing = Right; + nextButton.gamepadAccelerator = ["A"]; + nextButton.accelerators = [hxd.Key.ENTER]; + nextButton.pressedAction = (e) -> { + NetCommands.toggleReadiness(Net.isClient ? Net.clientId : 0); + }; + bottomBar.addChild(nextButton); + playSelectedLevel = () -> { MarbleGame.instance.playMission(curMission, true); } @@ -176,42 +211,19 @@ class MultiplayerLevelSelectGui extends GuiImage { levelWnd.horizSizing = Right; innerCtrl.addChild(levelWnd); - var statIcon = new GuiImage(ResourceLoader.getResource("data/ui/xbox/statIcon.png", ResourceLoader.getImage, this.imageResources).toTile()); - statIcon.position = new Vector(29, 54); - statIcon.extent = new Vector(20, 20); - levelWnd.addChild(statIcon); - - var eggIcon = new GuiImage(ResourceLoader.getResource("data/ui/xbox/eggIcon.png", ResourceLoader.getImage, this.imageResources).toTile()); - eggIcon.position = new Vector(29, 79); - eggIcon.extent = new Vector(20, 20); - levelWnd.addChild(eggIcon); - var c0 = 0xEBEBEB; var c1 = 0x8DFF8D; var c2 = 0x88BCEE; var c3 = 0xFF7575; var levelInfoLeft = new GuiMLText(arial14, mlFontLoader); - levelInfoLeft.position = new Vector(69, 54); - levelInfoLeft.extent = new Vector(180, 100); - levelInfoLeft.text.text = '

My Best Time:
Par Time:

'; - levelInfoLeft.text.lineSpacing = 6; + levelInfoLeft.position = new Vector(33, 40); + levelInfoLeft.extent = new Vector(480, 100); + levelInfoLeft.text.text = ''; + levelInfoLeft.text.lineSpacing = 0; + levelInfoLeft.text.filter = new h2d.filter.DropShadow(2, 0.785, 0x000000, 1, 0, 0.4, 1, true); levelWnd.addChild(levelInfoLeft); - var levelInfoMid = new GuiMLText(arial14, mlFontLoader); - levelInfoMid.position = new Vector(269, 54); - levelInfoMid.extent = new Vector(180, 100); - levelInfoMid.text.text = '

None
99:59:99

'; - levelInfoMid.text.lineSpacing = 6; - levelWnd.addChild(levelInfoMid); - - var levelInfoRight = new GuiMLText(arial14, mlFontLoader); - levelInfoRight.position = new Vector(379, 54); - levelInfoRight.extent = new Vector(180, 100); - levelInfoRight.text.text = '

Level 1
Difficulty 1

'; - levelInfoRight.text.lineSpacing = 6; - levelWnd.addChild(levelInfoRight); - var levelNames = difficultyMissions.map(x -> x.title); var levelSelectOpts = new GuiXboxOptionsList(6, "Level", levelNames); @@ -230,10 +242,6 @@ class MultiplayerLevelSelectGui extends GuiImage { var misFile = Path.withoutExtension(Path.withoutDirectory(curMission.path)); var mis = difficultyMissions[idx]; var requestToken = currentToken; - if (Settings.easterEggs.exists(mis.path)) - eggIcon.bmp.visible = true; - else - eggIcon.bmp.visible = false; MarbleGame.instance.setPreviewMission(misFile, () -> { lock = false; if (requestToken != currentToken) @@ -243,34 +251,31 @@ class MultiplayerLevelSelectGui extends GuiImage { loadText.text.visible = false; loadTextBg.text.visible = false; }); - - var scoreType = mis.missionInfo.gamemode != null - && mis.missionInfo.gamemode.toLowerCase() == 'scrum' ? ScoreType.Score : ScoreType.Time; - - var myScore = Settings.getScores(mis.path); - var scoreDisp = "None"; - if (myScore.length != 0) - scoreDisp = scoreType == Time ? Util.formatTime(myScore[0].time) : Util.formatScore(myScore[0].time); - var isPar = myScore.length != 0 && myScore[0].time < mis.qualifyTime; - var scoreColor = "#EBEBEB"; - if (isPar) - scoreColor = "#8DFF8D"; - if (scoreType == Score && myScore.length == 0) - scoreColor = "#EBEBEB"; - if (scoreType == Time) { - levelInfoLeft.text.text = '

My Best Time:
Par Time:

'; - levelInfoMid.text.text = '

${scoreDisp}
${Util.formatTime(mis.qualifyTime)}

'; + var hostName = Settings.highscoreName; + if (!Net.isHost) { + hostName = Net.clientIdMap[0].getName(); } - if (scoreType == Score) { - levelInfoLeft.text.text = '

My Best Score:

'; - levelInfoMid.text.text = '

${scoreDisp}

'; + + if (Net.isHost) { + updatePlayerCountFn = (pub:Int, priv:Int, publicTotal:Int, privateTotal:Int) -> { + levelInfoLeft.text.text = '

Host: ${hostName}

' + + '

Level: ${mis.title}

' + + '

Private Slots: ${pub}/${publicTotal}, Public Slots: ${priv}/${privateTotal}

'; + } + + updatePlayerCountFn(0, 0, Net.serverInfo.maxPlayers - Net.serverInfo.privateSlots, Net.serverInfo.privateSlots); + } + if (Net.isClient) { + updatePlayerCountFn = (pub:Int, priv:Int, publicTotal:Int, privateTotal:Int) -> { + levelInfoLeft.text.text = '

Host: ${hostName}

' + '

Level: ${mis.title}

'; + } + updatePlayerCountFn(0, 0, 0, 0); } - levelInfoRight.text.text = '

Level ${mis.missionInfo.level}
Difficulty ${mis.missionInfo.difficulty == null ? "" : mis.missionInfo.difficulty}

'; return true; } setLevelFn = setLevel; - levelSelectOpts.position = new Vector(380, 435); + levelSelectOpts.position = new Vector(380, 430); levelSelectOpts.extent = new Vector(815, 94); levelSelectOpts.vertSizing = Bottom; levelSelectOpts.horizSizing = Right; @@ -295,4 +300,16 @@ class MultiplayerLevelSelectGui extends GuiImage { super.onResize(width, height); } + + public function updateLobbyNames() { + var names = [Settings.highscoreName]; + for (id => c in Net.clientIdMap) { + names.push(c.getName()); + } + playerList.setTexts(names); + } + + public function updatePlayerCount(pub:Int, priv:Int, publicTotal:Int, privateTotal:Int) { + updatePlayerCountFn(pub, priv, publicTotal, privateTotal); + } } diff --git a/src/gui/MultiplayerLoadingGui.hx b/src/gui/MultiplayerLoadingGui.hx index 9f06fd66..485b297b 100644 --- a/src/gui/MultiplayerLoadingGui.hx +++ b/src/gui/MultiplayerLoadingGui.hx @@ -10,8 +10,11 @@ import src.Util; class MultiplayerLoadingGui extends GuiImage { var loadText:GuiText; var loadTextBg:GuiText; + var loadAnim:GuiLoadAnim; + var bottomBar:GuiControl; + var innerCtrl:GuiControl; - public function new(missionName:String) { + public function new(initialStatus:String) { var res = ResourceLoader.getImage("data/ui/game/CloudBG.jpg").resource.toTile(); super(res); this.position = new Vector(); @@ -31,7 +34,7 @@ class MultiplayerLoadingGui extends GuiImage { @:privateAccess arial14b.loader = ResourceLoader.loader; var arial14 = arial14b.toSdfFont(cast 21 * Settings.uiScale, h2d.Font.SDFChannel.MultiChannel); - var loadAnim = new GuiLoadAnim(); + loadAnim = new GuiLoadAnim(); loadAnim.position = new Vector(610, 253); loadAnim.extent = new Vector(63, 63); loadAnim.horizSizing = Center; @@ -44,7 +47,7 @@ class MultiplayerLoadingGui extends GuiImage { loadTextBg.horizSizing = Center; loadTextBg.vertSizing = Bottom; loadTextBg.justify = Center; - loadTextBg.text.text = "Loading"; + loadTextBg.text.text = initialStatus; loadTextBg.text.textColor = 0; this.addChild(loadTextBg); @@ -54,12 +57,69 @@ class MultiplayerLoadingGui extends GuiImage { loadText.horizSizing = Center; loadText.vertSizing = Bottom; loadText.justify = Center; - loadText.text.text = "Loading"; + loadText.text.text = initialStatus; this.addChild(loadText); + + #if hl + var scene2d = hxd.Window.getInstance(); + #end + #if js + var scene2d = MarbleGame.instance.scene2d; + #end + + var offsetX = (scene2d.width - 1280) / 2; + var offsetY = (scene2d.height - 720) / 2; + + var subX = 640 - (scene2d.width - offsetX) * 640 / scene2d.width; + var subY = 480 - (scene2d.height - offsetY) * 480 / scene2d.height; + + innerCtrl = new GuiControl(); + innerCtrl.position = new Vector(offsetX, offsetY); + innerCtrl.extent = new Vector(640 - subX, 480 - subY); + innerCtrl.horizSizing = Width; + innerCtrl.vertSizing = Height; + this.addChild(innerCtrl); + + bottomBar = new GuiControl(); + bottomBar.position = new Vector(0, 590); + bottomBar.extent = new Vector(640, 200); + bottomBar.horizSizing = Width; + bottomBar.vertSizing = Bottom; + innerCtrl.addChild(bottomBar); } public function setLoadingStatus(str:String) { loadText.text.text = str; loadTextBg.text.text = str; } + + public function setErrorStatus(str:String) { + loadText.text.text = str; + loadTextBg.text.text = str; + loadAnim.anim.visible = false; + + var backButton = new GuiXboxButton("Ok", 160); + backButton.position = new Vector(960, 0); + backButton.vertSizing = Bottom; + backButton.horizSizing = Right; + backButton.gamepadAccelerator = ["A"]; + backButton.accelerators = [hxd.Key.ENTER]; + backButton.pressedAction = (e) -> { + MarbleGame.canvas.setContent(new MultiplayerGui()); + }; + bottomBar.addChild(backButton); + MarbleGame.canvas.render(MarbleGame.canvas.scene2d); + } + + override function onResize(width:Int, height:Int) { + var offsetX = (width - 1280) / 2; + var offsetY = (height - 720) / 2; + + var subX = 640 - (width - offsetX) * 640 / width; + var subY = 480 - (height - offsetY) * 480 / height; + innerCtrl.position = new Vector(offsetX, offsetY); + innerCtrl.extent = new Vector(640 - subX, 480 - subY); + + super.onResize(width, height); + } } diff --git a/src/net/BitStream.hx b/src/net/BitStream.hx index 15da901e..63f6849f 100644 --- a/src/net/BitStream.hx +++ b/src/net/BitStream.hx @@ -67,6 +67,15 @@ class InputBitStream { public function readFloat() { return FPHelper.i32ToFloat(readInt32()); } + + public function readString() { + var length = readByte(); + var str = ""; + for (i in 0...length) { + str += String.fromCharCode(readByte()); + } + return str; + } } class OutputBitStream { @@ -135,4 +144,11 @@ class OutputBitStream { public function writeFloat(value:Float) { writeInt(FPHelper.floatToI32(value), 32); } + + public function writeString(value:String) { + writeByte(value.length); + for (i in 0...value.length) { + writeByte(StringTools.fastCodeAt(value, i)); + } + } } diff --git a/src/net/ClientConnection.hx b/src/net/ClientConnection.hx index 664e2750..3f17aa12 100644 --- a/src/net/ClientConnection.hx +++ b/src/net/ClientConnection.hx @@ -26,6 +26,7 @@ class ClientConnection extends GameConnection { this.datachannel = datachannel; this.state = GameplayState.LOBBY; this.rtt = 0; + this.name = "Unknown"; } override function sendBytes(b:Bytes) { @@ -38,6 +39,7 @@ class DummyConnection extends GameConnection { public function new(id:Int) { super(id); this.state = GameplayState.GAME; + this.lobbyReady = true; } } @@ -46,16 +48,23 @@ abstract class GameConnection { var id:Int; var state:GameplayState; var moveManager:MoveManager; + var name:String; + var lobbyReady:Bool; public function new(id:Int) { this.id = id; this.moveManager = new MoveManager(this); + this.lobbyReady = false; } public function ready() { state = GameplayState.GAME; } + public function toggleLobbyReady() { + lobbyReady = !lobbyReady; + } + public function queueMove(m:NetMove) { moveManager.queueMove(m); } @@ -81,4 +90,12 @@ abstract class GameConnection { } public function sendBytes(b:haxe.io.Bytes) {} + + public inline function getName() { + return name; + } + + public inline function setName(value:String) { + name = value; + } } diff --git a/src/net/MarbleUpdateQueue.hx b/src/net/MarbleUpdateQueue.hx index 42c9a18c..44f8e35a 100644 --- a/src/net/MarbleUpdateQueue.hx +++ b/src/net/MarbleUpdateQueue.hx @@ -1,5 +1,6 @@ package net; +import h3d.Vector; import net.NetPacket.MarbleNetFlags; import net.NetPacket.MarbleUpdatePacket; import net.Net; @@ -11,6 +12,7 @@ class OtherMarbleUpdate { var lastHeliTick:Int; var lastMegaTick:Int; var lastPowerUpId:Int; + var lastGravityUp:Vector; public function new() {} } @@ -48,6 +50,10 @@ class MarbleUpdateQueue { update.powerUpId = otherUpdate.lastPowerUpId; else otherUpdate.lastPowerUpId = update.powerUpId; + if (update.netFlags & MarbleNetFlags.GravityChange == 0) + update.gravityDirection = otherUpdate.lastGravityUp; + else + otherUpdate.lastGravityUp = update.gravityDirection; ourList.push(update); } else { var otherUpdate = new OtherMarbleUpdate(); @@ -61,6 +67,8 @@ class MarbleUpdateQueue { otherUpdate.lastMegaTick = update.megaTick; if (update.netFlags & MarbleNetFlags.PickupPowerup != 0) otherUpdate.lastPowerUpId = update.powerUpId; + if (update.netFlags & MarbleNetFlags.GravityChange != 0) + otherUpdate.lastGravityUp = update.gravityDirection; otherMarbleUpdates[cc] = otherUpdate; } } else { @@ -75,6 +83,8 @@ class MarbleUpdateQueue { update.megaTick = myMarbleUpdate.megaTick; if (update.netFlags & MarbleNetFlags.PickupPowerup == 0) update.powerUpId = myMarbleUpdate.powerUpId; + if (update.netFlags & MarbleNetFlags.GravityChange == 0) + update.gravityDirection = myMarbleUpdate.gravityDirection; } myMarbleUpdate = update; ourMoveApplied = false; diff --git a/src/net/MasterServerClient.hx b/src/net/MasterServerClient.hx index 29e6b370..cafffe52 100644 --- a/src/net/MasterServerClient.hx +++ b/src/net/MasterServerClient.hx @@ -7,6 +7,7 @@ import net.Net.ServerInfo; import hx.ws.WebSocket; import src.Console; import hx.ws.Types.MessageType; +import gui.MultiplayerLoadingGui; typedef RemoteServerInfo = { name:String, @@ -93,7 +94,7 @@ class MasterServerClient { if (conts.type == "connect") { if (!Net.isHost) { ws.send(Json.stringify({ - type: "connectResponse", + type: "connectFailed", success: false, reason: "The server has shut down" })); @@ -101,7 +102,7 @@ class MasterServerClient { } if (Net.serverInfo.players >= Net.serverInfo.maxPlayers) { ws.send(Json.stringify({ - type: "connectResponse", + type: "connectFailed", success: false, reason: "The server is full" })); @@ -122,7 +123,10 @@ class MasterServerClient { @:privateAccess Net.client.setRemoteDescription(sdpObj.sdp, sdpObj.type); } if (conts.type == "connectFailed") { - MarbleGame.canvas.pushDialog(new MessageBoxOkDlg(conts.reason)); + var loadGui:MultiplayerLoadingGui = cast MarbleGame.canvas.content; + if (loadGui != null) { + loadGui.setErrorStatus(conts.reason); + } } } } diff --git a/src/net/Net.hx b/src/net/Net.hx index 7bbbc22d..6ef35821 100644 --- a/src/net/Net.hx +++ b/src/net/Net.hx @@ -1,5 +1,9 @@ package net; +import gui.MultiplayerLevelSelectGui; +import gui.Canvas; +import net.MasterServerClient.RemoteServerInfo; +import gui.MultiplayerLoadingGui; import src.ResourceLoader; import src.AudioManager; import net.NetPacket.GemPickupPacket; @@ -18,6 +22,7 @@ import src.Console; import net.NetCommands; import src.MarbleGame; import hx.ws.Types.MessageType; +import src.Settings; enum abstract NetPacketType(Int) from Int to Int { var NullPacket; @@ -60,13 +65,11 @@ class Net { static var client:RTCPeerConnection; static var clientDatachannel:RTCDataChannel; - static var masterWs:WebSocket; - public static var isMP:Bool; public static var isHost:Bool; public static var isClient:Bool; - public static var startMP:Bool; + public static var lobbyHostReady:Bool; public static var clientId:Int; public static var networkRNG:Float; @@ -74,6 +77,7 @@ class Net { public static var clientIdMap:Map = []; public static var clientConnection:ClientConnection; public static var serverInfo:ServerInfo; + public static var remoteServerInfo:RemoteServerInfo; public static function hostServer(name:String, maxPlayers:Int, privateSlots:Int, privateServer:Bool) { serverInfo = new ServerInfo(name, 1, maxPlayers, privateSlots, privateServer, Std.int(999999 * Math.random()), "Lobby", getPlatform()); @@ -84,30 +88,6 @@ class Net { isMP = true; MasterServerClient.instance.sendServerInfo(serverInfo); }); - - // host = new RTCPeerConnection(["stun.l.google.com:19302"], "0.0.0.0"); - // host.bind("127.0.0.1", 28000, (c) -> { - // onClientConnect(c); - // isMP = true; - // }); - // isHost = true; - // isClient = false; - // clientId = 0; - // masterWs = new WebSocket("ws://localhost:8080"); - - // masterWs.onmessage = (m) -> { - // switch (m) { - // case StrMessage(content): - // var conts = Json.parse(content); - // var peer = new RTCPeerConnection(["stun:stun.l.google.com:19302"], "0.0.0.0"); - // peer.setRemoteDescription(conts.sdp, conts.type); - // addClient(peer); - - // case BytesMessage(content): {} - // } - // } - - // isMP = true; } public static function addClientFromSdp(sdpString:String, onFinishSdp:String->Void) { @@ -167,6 +147,10 @@ class Net { clientDatachannel = client.createDatachannel("mp"); clientDatachannel.onOpen = (n) -> { + var loadGui:MultiplayerLoadingGui = cast MarbleGame.canvas.content; + if (loadGui != null) { + loadGui.setLoadingStatus("Handshaking"); + } Console.log("Successfully connected!"); clients.set(client, new ClientConnection(0, client, clientDatachannel)); // host is always 0 clientIdMap[0] = clients[client]; @@ -182,6 +166,11 @@ class Net { if (MarbleGame.instance.world != null) { MarbleGame.instance.quitMission(); } + if (!(MarbleGame.canvas.content is MultiplayerLoadingGui)) { + var loadGui = new MultiplayerLoadingGui("Server closed"); + MarbleGame.canvas.setContent(loadGui); + loadGui.setErrorStatus("Server closed"); + } } clientDatachannel.onError = (msg) -> { Console.log('Errored out due to ${msg}'); @@ -189,63 +178,15 @@ class Net { if (MarbleGame.instance.world != null) { MarbleGame.instance.quitMission(); } + var loadGui = new MultiplayerLoadingGui("Connection error"); + MarbleGame.canvas.setContent(loadGui); + loadGui.setErrorStatus("Connection error"); } isMP = true; isHost = false; isClient = true; }); - // masterWs = new WebSocket("ws://localhost:8080"); - // masterWs.onopen = () -> { - // client = new RTCPeerConnection(["stun:stun.l.google.com:19302"], "0.0.0.0"); - // var candidates = []; - - // client.onLocalCandidate = (c) -> { - // if (c != "") - // candidates.push('a=${c}'); - // } - // client.onGatheringStateChange = (s) -> { - // if (s == RTC_GATHERING_COMPLETE) { - // Console.log("Local Description Set!"); - // var sdpObj = StringTools.trim(client.localDescription); - // sdpObj = sdpObj + '\r\n' + candidates.join('\r\n') + '\r\n'; - // masterWs.send(Json.stringify({ - // type: "connect", - // sdpObj: { - // sdp: sdpObj, - // type: "offer" - // } - // })); - // } - // } - - // masterWs.onmessage = (m) -> { - // switch (m) { - // case StrMessage(content): - // Console.log("Remote Description Received!"); - // var conts = Json.parse(content); - // client.setRemoteDescription(conts.sdp, conts.type); - // case _: {} - // } - // } - - // clientDatachannel = client.createDatachannel("mp"); - // clientDatachannel.onOpen = (n) -> { - // Console.log("Successfully connected!"); - // clients.set(client, new ClientConnection(0, client, clientDatachannel)); // host is always 0 - // clientIdMap[0] = clients[client]; - // clientConnection = cast clients[client]; - // onConnectedToServer(); - // haxe.Timer.delay(() -> connectedCb(), 1500); // 1.5 second delay to do the RTT calculation - // } - // clientDatachannel.onMessage = (b) -> { - // onPacketReceived(client, clientDatachannel, new InputBitStream(b)); - // } - - // isMP = true; - // isHost = false; - // isClient = true; - // } } public static function disconnect() { @@ -259,6 +200,9 @@ class Net { Net.clientId = 0; Net.clientIdMap.clear(); Net.clientConnection = null; + Net.serverInfo = null; + Net.remoteServerInfo = null; + Net.lobbyHostReady = false; } if (Net.isHost) { NetCommands.serverClosed(); @@ -271,6 +215,9 @@ class Net { Net.clients.clear(); Net.clientIdMap.clear(); MasterServerClient.disconnectFromMasterServer(); + Net.serverInfo = null; + Net.remoteServerInfo = null; + Net.lobbyHostReady = false; } } @@ -308,6 +255,10 @@ class Net { serverInfo.players++; MasterServerClient.instance.sendServerInfo(serverInfo); // notify the server of the new player + + if (MarbleGame.canvas.content is MultiplayerLevelSelectGui) { + cast(MarbleGame.canvas.content, MultiplayerLevelSelectGui).updateLobbyNames(); + } } static function onConnectedToServer() { @@ -327,6 +278,12 @@ class Net { serverInfo.players--; MasterServerClient.instance.sendServerInfo(serverInfo); // notify the server of the player leave NetCommands.clientDisconnected(cc.id); + + AudioManager.playSound(ResourceLoader.getAudio("data/sound/infotutorial.wav").resource); + + if (MarbleGame.canvas.content is MultiplayerLevelSelectGui) { + cast(MarbleGame.canvas.content, MultiplayerLevelSelectGui).updateLobbyNames(); + } } static function sendPlayerInfosBytes() { @@ -335,9 +292,22 @@ class Net { var cnt = 0; for (c in clientIdMap) cnt++; - b.writeByte(cnt); - for (c => v in clientIdMap) + b.writeByte(cnt + 1); // all + host + for (c => v in clientIdMap) { b.writeByte(c); + var name = v.getName(); + b.writeByte(name.length); + for (i in 0...name.length) { + b.writeByte(StringTools.fastCodeAt(name, i)); + } + } + // Write host data + b.writeByte(0); + var name = Settings.highscoreName; + b.writeByte(name.length); + for (i in 0...name.length) { + b.writeByte(StringTools.fastCodeAt(name, i)); + } return b.getBytes(); } @@ -352,6 +322,7 @@ class Net { case ClientIdAssign: clientId = input.readByte(); // 8 bit client id, hopefully we don't exceed this Console.log('Client ID set to ${clientId}'); + NetCommands.setPlayerName(clientId, Settings.highscoreName); // Send our player name to the server case Ping: var pingLeft = input.readByte(); @@ -435,10 +406,21 @@ class Net { addGhost(id); newP = true; } + var nameLength = input.readByte(); + var name = ""; + for (j in 0...nameLength) { + name += String.fromCharCode(input.readByte()); + } + if (clientIdMap.exists(id)) { + clientIdMap[id].setName(name); + } } if (newP) { AudioManager.playSound(ResourceLoader.getAudio("sounds/spawn_alternate.wav").resource); } + if (MarbleGame.canvas.content is MultiplayerLevelSelectGui) { + cast(MarbleGame.canvas.content, MultiplayerLevelSelectGui).updateLobbyNames(); + } case _: Console.log("unknown command: " + packetType); diff --git a/src/net/NetCommands.hx b/src/net/NetCommands.hx index 3ecd71aa..d315b5fb 100644 --- a/src/net/NetCommands.hx +++ b/src/net/NetCommands.hx @@ -5,6 +5,7 @@ import net.ClientConnection.GameplayState; import net.Net.NetPacketType; import gui.MultiplayerLevelSelectGui; import src.MarbleGame; +import gui.MultiplayerLoadingGui; @:build(net.RPCMacro.build()) class NetCommands { @@ -28,13 +29,34 @@ class NetCommands { } } + @:rpc(client) public static function toggleReadiness(clientId:Int) { + if (Net.isHost) { + if (clientId == 0) + Net.lobbyHostReady = !Net.lobbyHostReady; + else + Net.clientIdMap[clientId].toggleLobbyReady(); + var allReady = true; + for (id => client in Net.clientIdMap) { + if (!client.lobbyReady) { + allReady = false; + break; + } + } + if (allReady && Net.lobbyHostReady) { + NetCommands.playLevel(); + } + } + } + @:rpc(client) public static function clientIsReady(clientId:Int) { if (Net.isHost) { Net.clientIdMap[clientId].ready(); var allReady = true; for (id => client in Net.clientIdMap) { - if (client.state != GameplayState.GAME) + if (client.state != GameplayState.GAME) { allReady = false; + break; + } } if (allReady) { if (MarbleGame.instance.world != null) { @@ -66,6 +88,9 @@ class NetCommands { MarbleGame.instance.world.removePlayer(conn); } Net.clientIdMap.remove(clientId); + if (MarbleGame.canvas.content is MultiplayerLevelSelectGui) { + cast(MarbleGame.canvas.content, MultiplayerLevelSelectGui).updateLobbyNames(); + } } @:rpc(server) public static function clientJoin(clientId:Int) {} @@ -81,6 +106,18 @@ class NetCommands { if (MarbleGame.instance.world != null) { MarbleGame.instance.quitMission(); } + var loadGui = new MultiplayerLoadingGui("Server closed"); + MarbleGame.canvas.setContent(loadGui); + loadGui.setErrorStatus("Server closed"); + } + } + + @:rpc(client) public static function setPlayerName(clientId:Int, name:String) { + if (Net.isHost) { + Net.clientIdMap[clientId].setName(name); + if (MarbleGame.canvas.content is MultiplayerLevelSelectGui) { + cast(MarbleGame.canvas.content, MultiplayerLevelSelectGui).updateLobbyNames(); + } } } } diff --git a/src/net/NetPacket.hx b/src/net/NetPacket.hx index 1404a161..d194a474 100644 --- a/src/net/NetPacket.hx +++ b/src/net/NetPacket.hx @@ -37,6 +37,7 @@ enum abstract MarbleNetFlags(Int) from Int to Int { var DoHelicopter = 1 << 1; var DoMega = 1 << 2; var PickupPowerup = 1 << 3; + var GravityChange = 1 << 4; } @:publicFields @@ -52,6 +53,7 @@ class MarbleUpdatePacket implements NetPacket { var blastTick:Int; var megaTick:Int; var heliTick:Int; + var gravityDirection:Vector; var oob:Bool; var powerUpId:Int; var moveQueueSize:Int; @@ -99,6 +101,14 @@ class MarbleUpdatePacket implements NetPacket { } else { b.writeFlag(false); } + if (netFlags & MarbleNetFlags.GravityChange > 0) { + b.writeFlag(true); + b.writeFloat(gravityDirection.x); + b.writeFloat(gravityDirection.y); + b.writeFloat(gravityDirection.z); + } else { + b.writeFlag(false); + } } public inline function deserialize(b:InputBitStream) { @@ -126,9 +136,12 @@ class MarbleUpdatePacket implements NetPacket { oob = b.readFlag(); if (b.readFlag()) { powerUpId = b.readInt(9); - trace('pickup: ${powerUpId}'); this.netFlags |= MarbleNetFlags.PickupPowerup; } + if (b.readFlag()) { + gravityDirection = new Vector(b.readFloat(), b.readFloat(), b.readFloat()); + this.netFlags |= MarbleNetFlags.GravityChange; + } } } diff --git a/src/net/RPCMacro.hx b/src/net/RPCMacro.hx index f043ac5c..b9643f48 100644 --- a/src/net/RPCMacro.hx +++ b/src/net/RPCMacro.hx @@ -42,6 +42,14 @@ class RPCMacro { serializeFns.push(macro stream.writeFloat($i{argName})); } + case TPath({ + name: 'String' + }): { + deserializeFns.push(macro var $argName = stream.readString()); + callExprs.push(macro $i{argName}); + serializeFns.push(macro stream.writeString($i{argName})); + } + case _: {} } }