diff --git a/LICENSE b/LICENSE index 3e82543..5ac38e6 100644 --- a/LICENSE +++ b/LICENSE @@ -622,7 +622,7 @@ copy of the Program in return for a fee. LICENSE TEMPLATE Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/README.md b/README.md index cfe30e8..67ff45b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ delectable yum yum Rec Room custom server for communities. Fast runtime and easy setup.
Built -for Rec Room build 526 (Timestamp: 637098805133024772, Version: 20191120) +for Rec Room build 1063 (Timestamp: 637191339113673856, Version: 20200306) drawing diff --git a/res/img/Bowling.png b/res/img/Bowling.png new file mode 100644 index 0000000..dfb5f57 Binary files /dev/null and b/res/img/Bowling.png differ diff --git a/res/img/Crescendo.png b/res/img/Crescendo.png new file mode 100644 index 0000000..952978f Binary files /dev/null and b/res/img/Crescendo.png differ diff --git a/res/img/Lake.png b/res/img/Lake.png new file mode 100644 index 0000000..4ec39a1 Binary files /dev/null and b/res/img/Lake.png differ diff --git a/res/img/LaserTag.png b/res/img/LaserTag.png new file mode 100644 index 0000000..960ceb2 Binary files /dev/null and b/res/img/LaserTag.png differ diff --git a/res/img/MakerRoom.png b/res/img/MakerRoom.png new file mode 100644 index 0000000..c960a0c Binary files /dev/null and b/res/img/MakerRoom.png differ diff --git a/res/img/StuntRunner.png b/res/img/StuntRunner.png new file mode 100644 index 0000000..4b1ebc4 Binary files /dev/null and b/res/img/StuntRunner.png differ diff --git a/res/rooms.json b/res/rooms.json index a0aee40..09dbc83 100644 --- a/res/rooms.json +++ b/res/rooms.json @@ -1,51 +1,30 @@ [ { - "Name": "Calibration", - "ReplicationId": "30040e05-b7b9-9f44-eb08-b9f154d2ecfc", - "Description": "PSVR room calibration", + "Name": "DormRoom", + "ReplicationId": "68251132-5662-5c34-08b1-4a830a27955b", + "Description": "Your private room", "Accessibility": 2, "SupportsLevelVoting": false, "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, - "Scenes": [ - { - "Name": "Home", - "ReplicationId": "fe66923e-4034-4ec4-4bca-11a973bf5515", - "RoomSceneLocationId": "f5fbd9c9-e853-4036-9d48-5f68e861af04", - "IsSandbox": false, - "CanMatchmakeInto": true, - "SupportsJoinInProgress": false, - "UseLevelBasedMatchmaking": false, - "UseAgeBasedMatchmaking": false, - "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 1 - } - ] - }, - { - "Name": "DormRoom", - "ReplicationId": "68251132-5662-5c34-08b1-4a830a27955b", - "Description": "Your private room.", - "Accessibility": 2, - "SupportsLevelVoting": false, - "CloningAllowed": true, - "SupportsScreens": true, - "SupportsWalkVR": true, - "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", "ReplicationId": "92084aee-1f44-a3b4-18f1-375601606506", "RoomSceneLocationId": "76d98498-60a1-430c-ab76-b54a29b7a163", - "IsSandbox": false, + "IsSandbox": true, "CanMatchmakeInto": true, "SupportsJoinInProgress": false, "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 1 + "MaxPlayers": 4, + "ReleaseStatus": 2 } ] }, @@ -59,6 +38,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -70,7 +52,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": true, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 12 + "MaxPlayers": 12, + "ReleaseStatus": 2 } ] }, @@ -80,10 +63,13 @@ "Description": "Take turns drawing, acting, and guessing funny phrases with your friends!", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": false, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -95,7 +81,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": true, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 } ] }, @@ -105,10 +92,13 @@ "Description": "A leisurely stroll through the grass. Throw your disc into the goal. Sounds easy, right?", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -120,7 +110,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 4 + "MaxPlayers": 4, + "ReleaseStatus": 2 } ] }, @@ -130,10 +121,13 @@ "Description": "Throw your disc through hazards and around wind machines on this challenging course!", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -145,7 +139,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 4 + "MaxPlayers": 4, + "ReleaseStatus": 2 } ] }, @@ -155,10 +150,13 @@ "Description": "Throw dodgeballs to knock out your friends in this gym classic!", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -170,7 +168,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 6 + "MaxPlayers": 6, + "ReleaseStatus": 2 } ] }, @@ -180,10 +179,13 @@ "Description": "A simple rally game between two players in a plexiglass tube with a zero-g ball.", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": false, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -195,7 +197,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 2 + "MaxPlayers": 2, + "ReleaseStatus": 2 } ] }, @@ -205,10 +208,13 @@ "Description": "Red and Blue teams splat each other in capture the flag and team battle.", "Accessibility": 1, "SupportsLevelVoting": true, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "River", @@ -220,7 +226,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 }, { "Name": "Homestead", @@ -232,7 +239,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 }, { "Name": "Quarry", @@ -244,7 +252,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 }, { "Name": "Clearcut", @@ -256,7 +265,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 }, { "Name": "Spillway", @@ -268,7 +278,21 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 + }, + { + "Name": "Drive-in", + "ReplicationId": "dc185520-0fa2-46d5-bc27-a613c6679c7c", + "RoomSceneLocationId": "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3", + "IsSandbox": false, + "CanMatchmakeInto": true, + "SupportsJoinInProgress": true, + "UseLevelBasedMatchmaking": true, + "UseAgeBasedMatchmaking": false, + "UseRecRoyaleMatchmaking": false, + "MaxPlayers": 8, + "ReleaseStatus": 2 } ] }, @@ -278,10 +302,13 @@ "Description": "Red and Blue teams splat each other in capture the flag and team battle.", "Accessibility": 1, "SupportsLevelVoting": true, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": false, "SupportsWalkVR": false, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "River", @@ -293,7 +320,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 }, { "Name": "Homestead", @@ -305,7 +333,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 }, { "Name": "Quarry", @@ -317,7 +346,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 }, { "Name": "Clearcut", @@ -329,7 +359,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 }, { "Name": "Spillway", @@ -341,7 +372,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 } ] }, @@ -351,10 +383,13 @@ "Description": "The goblin king stole Coach's Golden Trophy. Team up and embark on an epic quest to recover it!", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -366,7 +401,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": true, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 4 + "MaxPlayers": 4, + "ReleaseStatus": 2 } ] }, @@ -376,10 +412,13 @@ "Description": "Robot invaders threaten the galaxy! Team up with your friends and bring the laser heat!", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -391,7 +430,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": true, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 4 + "MaxPlayers": 4, + "ReleaseStatus": 2 } ] }, @@ -401,10 +441,13 @@ "Description": "Can your band of adventurers brave the enchanted wilds, and lift the curse of the crimson cauldron?", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -416,7 +459,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": true, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 4 + "MaxPlayers": 4, + "ReleaseStatus": 2 } ] }, @@ -426,10 +470,13 @@ "Description": "Can your pirate crew get to the Isle, defeat its fearsome guardian, and escape with the gold?", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -441,7 +488,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": true, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 3 + "MaxPlayers": 3, + "ReleaseStatus": 2 } ] }, @@ -451,10 +499,13 @@ "Description": "Teams of three run around slamming themselves into an over-sized soccer ball. Goal!", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -466,23 +517,27 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 6 + "MaxPlayers": 6, + "ReleaseStatus": 2 } ] }, { - "Name": "LaserTagHangar", + "Name": "LaserTag", "ReplicationId": "8cf5a0b5-d683-51f4-2bb7-57821b533cad", - "Description": "Teams battle each other and waves of robots in a classic warehouse arena.", + "Description": "Teams battle each other and waves of robots.", "Accessibility": 1, - "SupportsLevelVoting": false, - "CloningAllowed": true, + "SupportsLevelVoting": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { - "Name": "Home", + "Name": "Hangar", "ReplicationId": "8f8fcaf8-7b13-e114-880c-72ee8c9fcb78", "RoomSceneLocationId": "239e676c-f12f-489f-bf3a-d4c383d692c3", "IsSandbox": false, @@ -491,24 +546,12 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 - } - ] - }, - { - "Name": "LaserTagCyberJunk", - "ReplicationId": "c47969aa-c9ac-85e4-ea35-470f2a11d47f", - "Description": "Teams battle each other and waves of robots in a totally cyber neon future city.", - "Accessibility": 1, - "SupportsLevelVoting": false, - "CloningAllowed": true, - "SupportsScreens": true, - "SupportsWalkVR": true, - "SupportsTeleportVR": true, - "Scenes": [ + "MaxPlayers": 8, + "ReleaseStatus": 2 + }, { - "Name": "Home", - "ReplicationId": "d59f7e16-9f27-6924-a899-2739bfc056fe", + "Name": "CyberJunkCity", + "ReplicationId": "333f409d-f9ad-46c9-b6fb-b0bbd30cb0b2", "RoomSceneLocationId": "9d6456ce-6264-48b4-808d-2d96b3d91038", "IsSandbox": false, "CanMatchmakeInto": true, @@ -516,7 +559,8 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 8 + "MaxPlayers": 8, + "ReleaseStatus": 2 } ] }, @@ -526,10 +570,13 @@ "Description": "Squads of three battle it out on Frontier Island. Last squad standing wins!", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -541,32 +588,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": true, - "MaxPlayers": 18 - } - ] - }, - { - "Name": "RecRoyaleVR", - "ReplicationId": "729cddc7-1488-8004-3abc-b7ab05c3ec83", - "Description": "Squads of three battle it out on Frontier Island. Last squad standing wins!", - "Accessibility": 1, - "SupportsLevelVoting": false, - "CloningAllowed": true, - "SupportsScreens": false, - "SupportsWalkVR": true, - "SupportsTeleportVR": false, - "Scenes": [ - { - "Name": "Home", - "ReplicationId": "1edd6d85-40fb-7e34-aa26-8ef754ef654e", - "RoomSceneLocationId": "253fa009-6e65-4c90-91a1-7137a56a267f", - "IsSandbox": false, - "CanMatchmakeInto": true, - "SupportsJoinInProgress": false, - "UseLevelBasedMatchmaking": false, - "UseAgeBasedMatchmaking": false, - "UseRecRoyaleMatchmaking": true, - "MaxPlayers": 18 + "MaxPlayers": 18, + "ReleaseStatus": 2 } ] }, @@ -576,10 +599,13 @@ "Description": "Battle it out on Frontier Island. Last person standing wins!", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -591,7 +617,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": true, - "MaxPlayers": 16 + "MaxPlayers": 16, + "ReleaseStatus": 2 } ] }, @@ -605,6 +632,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -616,7 +646,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -630,6 +661,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -641,7 +675,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 40 + "MaxPlayers": 40, + "ReleaseStatus": 2 } ] }, @@ -655,6 +690,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -666,7 +704,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 40 + "MaxPlayers": 40, + "ReleaseStatus": 2 } ] }, @@ -680,6 +719,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -691,32 +733,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 - } - ] - }, - { - "Name": "ArtTesting", - "ReplicationId": "cc338cd1-9883-0c14-99b3-ab069a360ee3", - "Description": "bla", - "Accessibility": 0, - "SupportsLevelVoting": false, - "CloningAllowed": false, - "SupportsScreens": true, - "SupportsWalkVR": true, - "SupportsTeleportVR": true, - "Scenes": [ - { - "Name": "Home", - "ReplicationId": "33bdd61b-bd99-abe4-6943-3032c741f5e8", - "RoomSceneLocationId": "42699ed2-0c1b-4f3d-93a2-ce01dfce7a79", - "IsSandbox": false, - "CanMatchmakeInto": true, - "SupportsJoinInProgress": false, - "UseLevelBasedMatchmaking": false, - "UseAgeBasedMatchmaking": false, - "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 1 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -730,6 +748,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -741,7 +762,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -755,6 +777,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -766,7 +791,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -780,6 +806,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -791,7 +820,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -805,6 +835,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -816,7 +849,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -830,6 +864,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -841,7 +878,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -855,6 +893,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -866,7 +907,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -880,6 +922,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -891,7 +936,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -905,6 +951,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -916,7 +965,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -930,6 +980,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -941,7 +994,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -955,6 +1009,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -966,7 +1023,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -980,6 +1038,9 @@ "SupportsScreens": true, "SupportsWalkVR": true, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -991,7 +1052,8 @@ "UseLevelBasedMatchmaking": false, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 20 + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] }, @@ -1001,10 +1063,13 @@ "Description": "Throw dodgeballs to knock out your friends in this gym classic!", "Accessibility": 1, "SupportsLevelVoting": false, - "CloningAllowed": true, + "CloningAllowed": false, "SupportsScreens": false, "SupportsWalkVR": false, "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, "Scenes": [ { "Name": "Home", @@ -1016,8 +1081,167 @@ "UseLevelBasedMatchmaking": true, "UseAgeBasedMatchmaking": false, "UseRecRoyaleMatchmaking": false, - "MaxPlayers": 6 + "MaxPlayers": 6, + "ReleaseStatus": 2 + } + ] + }, + { + "Name": "Crescendo", + "ReplicationId": "7131bb49-6e2c-45da-a83d-ddf61d6463be", + "Description": "Gather your vampire hunting crew, conquer a legendary castle, and restore peace to Rec Room!", + "Accessibility": 1, + "SupportsLevelVoting": false, + "CloningAllowed": false, + "SupportsScreens": true, + "SupportsWalkVR": true, + "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, + "Scenes": [ + { + "Name": "Home", + "ReplicationId": "66726423-6e03-4548-9990-f1b10e8c5c2e", + "RoomSceneLocationId": "49cb8993-a956-43e2-86f4-1318f279b22a", + "IsSandbox": false, + "CanMatchmakeInto": true, + "SupportsJoinInProgress": false, + "UseLevelBasedMatchmaking": false, + "UseAgeBasedMatchmaking": true, + "UseRecRoyaleMatchmaking": false, + "MaxPlayers": 3, + "ReleaseStatus": 2 + } + ] + }, + { + "Name": "Bowling", + "ReplicationId": "0ba2a62a-2d02-403b-9f85-7472e472b632", + "Description": "Hang out with friends, bowl a few games, eat snacks!", + "Accessibility": 1, + "SupportsLevelVoting": false, + "CloningAllowed": false, + "SupportsScreens": true, + "SupportsWalkVR": true, + "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, + "Scenes": [ + { + "Name": "Home", + "ReplicationId": "d61f36d3-f702-4002-a065-e1b6a59d4036", + "RoomSceneLocationId": "ae929543-9a07-41d5-8ee9-dbbee8c36800", + "IsSandbox": false, + "CanMatchmakeInto": true, + "SupportsJoinInProgress": false, + "UseLevelBasedMatchmaking": true, + "UseAgeBasedMatchmaking": true, + "UseRecRoyaleMatchmaking": false, + "MaxPlayers": 8, + "ReleaseStatus": 2 + } + ] + }, + { + "Name": "BowlingAlley", + "ReplicationId": "63de6dd3-8b9a-4ce5-afbe-9e01fefcad02", + "Description": "Lone Shoe Lanes is the hottest hangout in town. ", + "Accessibility": 2, + "SupportsLevelVoting": false, + "CloningAllowed": true, + "SupportsScreens": true, + "SupportsWalkVR": true, + "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, + "Scenes": [ + { + "Name": "Home", + "ReplicationId": "b492f0d4-5f72-4429-9ea5-decf369e7aa7", + "RoomSceneLocationId": "ae929543-9a07-41d5-8ee9-dbbee8c36800", + "IsSandbox": true, + "CanMatchmakeInto": true, + "SupportsJoinInProgress": true, + "UseLevelBasedMatchmaking": false, + "UseAgeBasedMatchmaking": false, + "UseRecRoyaleMatchmaking": false, + "MaxPlayers": 8, + "ReleaseStatus": 2 + } + ] + }, + { + "Name": "StuntRunner", + "ReplicationId": "96797ca7-67a4-4c8d-b50f-a956dfd6f525", + "Description": "Sprint, climb, and wall jump in this high-speed obstacle course where every second counts!", + "Accessibility": 1, + "SupportsLevelVoting": false, + "CloningAllowed": false, + "SupportsScreens": true, + "SupportsWalkVR": true, + "SupportsTeleportVR": false, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, + "Scenes": [ + { + "Name": "StuntRunner", + "ReplicationId": "f8a71ce1-c989-4e00-9e10-3f04c241f6f3", + "RoomSceneLocationId": "b7281665-a715-4051-826b-8e08e69c6172", + "IsSandbox": false, + "CanMatchmakeInto": true, + "SupportsJoinInProgress": true, + "UseLevelBasedMatchmaking": false, + "UseAgeBasedMatchmaking": false, + "UseRecRoyaleMatchmaking": false, + "MaxPlayers": 12, + "ReleaseStatus": 2 + }, + { + "Name": "TheMainEvent", + "ReplicationId": "cacc6e68-644a-448d-b83f-59ea690318ec", + "RoomSceneLocationId": "3a636bd2-f896-424c-9225-c184522c0d87", + "IsSandbox": false, + "CanMatchmakeInto": false, + "SupportsJoinInProgress": false, + "UseLevelBasedMatchmaking": false, + "UseAgeBasedMatchmaking": false, + "UseRecRoyaleMatchmaking": false, + "MaxPlayers": 4, + "ReleaseStatus": 2 + } + ] + }, + { + "Name": "StuntRunnerBaseRoom", + "ReplicationId": "ea3cacc0-e075-4b7f-92e3-21a81ab24e3e", + "Description": "A soundstage warehouse used to film Stunt Runner and other RecTV shows.", + "Accessibility": 2, + "SupportsLevelVoting": false, + "CloningAllowed": true, + "SupportsScreens": true, + "SupportsWalkVR": true, + "SupportsTeleportVR": true, + "AllowsJuniors": true, + "DisableMicAutoMute": false, + "ReleaseStatus": 2, + "Scenes": [ + { + "Name": "Home", + "ReplicationId": "57ec15a2-34b5-42d4-a5d8-ddad6888e0e3", + "RoomSceneLocationId": "882e9b96-7115-4b03-86f6-c0c9d8e22e00", + "IsSandbox": true, + "CanMatchmakeInto": true, + "SupportsJoinInProgress": true, + "UseLevelBasedMatchmaking": false, + "UseAgeBasedMatchmaking": false, + "UseRecRoyaleMatchmaking": false, + "MaxPlayers": 20, + "ReleaseStatus": 2 } ] } -] +] \ No newline at end of file diff --git a/src/apiutils.ts b/src/apiutils.ts index 2a6583b..47c537d 100644 --- a/src/apiutils.ts +++ b/src/apiutils.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -131,11 +131,11 @@ export function genericResponse( }; } type RecNetResponse = { - Success: boolean; - Message: string; + success: boolean; + error?: string; }; -export function RecNetResponse(success: boolean, message: string) { - const msg: RecNetResponse = { Success: success, Message: message }; +export function RecNetResponse(success: boolean, message?: string) { + const msg: RecNetResponse = { success: success, error: message }; return (_rq: express.Request, rs: express.Response) => { rs.json(msg); }; @@ -190,7 +190,7 @@ export class RateLimiter { * @param interval In seconds: rate at which hit counts will be cleared * @param limit Number of hits (inclusive) before requests are blocked */ - constructor(interval: number, limit: number) { + constructor(interval: number = 20, limit: number = 2) { this.#hitLimit = limit; this.#intervalId = setInterval(() => { @@ -224,6 +224,11 @@ export class RateLimiter { nxt: express.NextFunction, ) => { const address = getSrcIpDefault(rq); + if (address == '127.0.0.1' || address == '::1') { + nxt(); + return; + } + this.#addressIncrement(address); const hits = this.#getAddressHits(address); @@ -339,7 +344,7 @@ export function AuthenticationType(type: AuthType) { } export function LoginLock(rq: express.Request, rs: express.Response, nxt: express.NextFunction) { - log.d(`LoginLock for ${rs.locals.profile.getId()}: ${rq.body.LoginLock}`); + //log.d(`LoginLock for ${rs.locals.profile.getId()}: ${rq.body.LoginLock}`); const matches = Matchmaking.lockMatches(rs.locals.profile, rq.body.LoginLock); if (matches == null) { rs.json(genericResponseFormat(true, "Login Lock failure")); diff --git a/src/config.ts b/src/config.ts index b313289..fd8d007 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ along with this program. If not, see . */ import Logging from "@proxnet/undead-logging"; import * as fs from "node:fs"; -import { PhotonRegionCodeString } from "./data/live/types.ts"; +import { PhotonRegionCodeNumber, PhotonRegionCodeString } from "./data/live/types.ts"; const log = new Logging("Config"); @@ -49,7 +49,7 @@ type PublicConfiguration = { levelScale: number; maxLevels: number; patches: string[]; - photonRegionId: PhotonRegionCodeString; + photonRegionId: PhotonRegionCodeString | PhotonRegionCodeNumber; }; type LoggingConfiguration = { @@ -111,7 +111,7 @@ export const defaultConfig: GalvanicConfiguration = { levelScale: 1, maxLevels: 30, patches: [], - photonRegionId: PhotonRegionCodeString.UnitedStates, + photonRegionId: PhotonRegionCodeNumber.us, }, logging: { notfound: false, diff --git a/src/data/config.ts b/src/data/config.ts index bbd5ee9..eb3beb5 100644 --- a/src/data/config.ts +++ b/src/data/config.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -19,10 +19,6 @@ import { Config } from "../config.ts"; import { Redis } from "../db.ts"; import { Objectives } from "./objectives.ts"; -export type Config = { - Key: string; - Value: string; -}; export type LevelProgressionItem = { Level: number; RequiredXp: number; @@ -38,13 +34,13 @@ export type AutoMicMutingConfig = { MicSpamWarningStateVolumeMultiplier: number } export type PublicConfig = { + ShareBaseUrl: string ServerMaintenance: { StartsInMinutes: number; }; LevelProgressionMaps: LevelProgressionItem[]; DailyObjectives: Objectives.Objective[][]; - ConfigTable: Config[]; - AutoMicMutingConfig: AutoMicMutingConfig + AutoMicMutingConfig: AutoMicMutingConfig, }; /** @@ -72,7 +68,6 @@ export function getConfig() { }, LevelProgressionMaps: generateLevelProgressionMap(), DailyObjectives: [], - ConfigTable: [], AutoMicMutingConfig: { MicSpamVolumeThreshold: 1.125, MicVolumeSampleInterval: 0.25, @@ -82,7 +77,8 @@ export function getConfig() { MicSpamSamplePercentageForForceMute: 0.8, MicSpamSamplePercentageForForceMuteToEnd: 0.2, MicSpamWarningStateVolumeMultiplier: 0.25 - } + }, + ShareBaseUrl: `${config.web.api.securepublichost ? 'https' : 'http'}://${config.web.api.publichost}/{0}` // {0} is replaced by the game }; return conf; diff --git a/src/data/content/avatar.ts b/src/data/content/avatar.ts index d123b2d..79a3aa6 100644 --- a/src/data/content/avatar.ts +++ b/src/data/content/avatar.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/content/baseimages.ts b/src/data/content/baseimages.ts index f543417..5d3b4a0 100644 --- a/src/data/content/baseimages.ts +++ b/src/data/content/baseimages.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/content/consumable.ts b/src/data/content/consumable.ts index d807844..50ee12c 100644 --- a/src/data/content/consumable.ts +++ b/src/data/content/consumable.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/content/gamerooms.ts b/src/data/content/gamerooms.ts index 12eaa75..5f0cb78 100644 --- a/src/data/content/gamerooms.ts +++ b/src/data/content/gamerooms.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/content/images.ts b/src/data/content/images.ts index f7814c6..c778cdc 100644 --- a/src/data/content/images.ts +++ b/src/data/content/images.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/content/room.ts b/src/data/content/room.ts index c3ce3ab..66f3158 100644 --- a/src/data/content/room.ts +++ b/src/data/content/room.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/content/rooms.ts b/src/data/content/rooms.ts index c044d62..050e215 100644 --- a/src/data/content/rooms.ts +++ b/src/data/content/rooms.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ along with this program. If not, see . */ import { Redis } from "../../db.ts"; import { RootPath } from "./baseimages.ts"; -import { BuiltinRoom, RoomDetails, RoomState } from "./roomtypes.ts"; +import { BuiltinRoom, RoomAccessibility, RoomDetails, RoomState } from "./roomtypes.ts"; import { RoomFetch } from "./room.ts"; import { Profile } from "../profiles.ts"; import Logging from "@proxnet/undead-logging"; @@ -172,7 +172,7 @@ class RoomsBase { await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Room_Names, details.Room.Name), details.Room.RoomId); } - async cloneRoom(roomid: number, newname: string, newowner: Profile, dorm?: boolean) { + async cloneRoom(roomid: number, newname: string, newowner: Profile) { const canBeClonedRaw = await Redis.Database.hget(Redis.buildKey( Redis.KeyGroups.Rooms.Root, roomid.toString(), @@ -183,22 +183,21 @@ class RoomsBase { try { canBeCloned = JSON.parse(canBeClonedRaw) as boolean; } catch { + log.d(`Cloneroom ${roomid}: parse error`); + return null; + } + if (!canBeCloned) { + log.d(`Cloneroom ${roomid}: cannot be cloned`); return null; } - if (!canBeCloned) return null; const beforeRoom = await Rooms.get(roomid); // room must exist if (!beforeRoom || !beforeRoom.Room.CloningAllowed) return null; // room must be cloneable - if (await Rooms.getByName(newname) && beforeRoom.Room.Name !== 'DormRoom') return null; // room name cannot be taken + if (beforeRoom.Room.Name !== 'DormRoom' && await Rooms.getByName(newname)) return null; // room name cannot be taken const newId = await this.#getAvailableRoomId(); beforeRoom.Room.CreatorPlayerId = newowner.getId(); beforeRoom.Room.RoomId = newId; for (const subroom of beforeRoom.Scenes) subroom.RoomId = newId; - if (dorm) { - beforeRoom.Room.IsAGRoom = true; - beforeRoom.Room.IsDormRoom = true; - beforeRoom.Room.CloningAllowed = false; - } await Rooms.#setRoom(beforeRoom); return beforeRoom; @@ -211,19 +210,19 @@ class RoomsBase { Redis.KeyGroups.Rooms.PlayerDorms ), player.getId().toString()); if (unparsedId) { + log.d(`Unparsed dorm ID for profile ${player.getId()}: ${unparsedId}`); const parsedId = parseInt(unparsedId); - if (!isNaN(parsedId)) return await Rooms.get(parsedId); + if (isNaN(parsedId)) { + log.d(`Returning new dorm for profile ${player.getId()}`); + return await Rooms.get(parsedId); + } } - const baseDorm = await Rooms.getByName("DormRoom"); - - log.d('Base dorm existed'); - if (!baseDorm) return null; - log.d('Base dorm was not null'); - const newDorm = await this.cloneRoom(baseDorm.Room.RoomId, "DormRoom", player, true); - log.d('New dorm existed'); + const newDorm = await this.generateNewDorm(player); + await this.#setRoom(newDorm); + log.d(`New dorm for ${player.getId()} existed`); if (!newDorm) return null; - log.d('New dorm was not null'); + log.d(`New dorm for ${player.getId()} was not null`); await Redis.Database.hset(Redis.buildKey( Redis.KeyGroups.Rooms.Root, Redis.KeyGroups.Rooms.PlayerDorms @@ -231,10 +230,59 @@ class RoomsBase { return newDorm; } + async generateNewDorm(player: Profile) { + const id = await this.#getAvailableRoomId(); + const basedorm: RoomDetails = { + Room: { + RoomId: id, + Name: `DormRoom`, + Description: "Your private room.", + CreatorPlayerId: player.getId(), + ImageName: "DefaultProfileImage.png", + State: RoomState.Active, + Accessibility: RoomAccessibility.Private, + SupportsLevelVoting: false, + IsAGRoom: false, + IsDormRoom: true, + CloningAllowed: false, + SupportsScreens: true, + SupportsTeleportVR: true, + SupportsWalkVR: true, + AllowsJuniors: true, + RoomWarningMask: 0, + CustomRoomWarning: "", + DisableMicAutoMute: false + }, + Scenes: [ + { + RoomId: id, + RoomSceneId: 1, + Name: "Home", + RoomSceneLocationId: "76d98498-60a1-430c-ab76-b54a29b7a163", + IsSandbox: true, + CanMatchmakeInto: true, + MaxPlayers: 4, + DataBlobName: "", + DataModifiedAt: new Date().toISOString() + } + ], + CoOwners: [], + InvitedCoOwners: [], + Hosts: [], + InvitedHosts: [], + CheerCount: 0, + VisitCount: 0, + FavoriteCount: 0, + Tags: [] + } + return basedorm; + } + async generateBuiltinRooms() { if ((await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.BuiltinGenerated))) !== null) return true; for (const builtinRoom of rooms) { + if (builtinRoom.Name == 'DormRoom') continue; const newId = await this.#getAvailableRoomId(); await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.AGRooms), newId); const roomDets: RoomDetails = { @@ -320,6 +368,11 @@ class RoomsBase { else return parsedIds.map(val => val.toString()); } + getSubroomNameFromId(room: RoomDetails, subroomId: number) { + const subroom = room.Scenes.find(scene => scene.RoomSceneId == subroomId); + if (subroom) return subroom.Name; + else return null; + } } diff --git a/src/data/content/roomtypes.ts b/src/data/content/roomtypes.ts index 854670a..8a8bfe0 100644 --- a/src/data/content/roomtypes.ts +++ b/src/data/content/roomtypes.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/content/settings.ts b/src/data/content/settings.ts index c0441ac..84728bc 100644 --- a/src/data/content/settings.ts +++ b/src/data/content/settings.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -18,9 +18,19 @@ along with this program. If not, see . */ export enum SettingKey { PlayerStatusVisibility = "PlayerStatusVisibility", VRMovementMode = "VR_MOVEMENT_MODE", + RecroomOOBE = "Recroom.OOBE", + PlayerSessionCount = "PlayerSessionCount", + BACKPACK_FAVORITE_TOOL = "BACKPACK_FAVORITE_TOOL", + Recroom_ChallengeMap = "Recroom.ChallengeMap", + FIRST_TIME_IN_FLAGS = "FIRST_TIME_IN_FLAGS" } export enum SettingDefault { - PlayerStatusVisibility = "1", - VRMovementMode = "0" + PlayerStatusVisibility = "0", + VRMovementMode = "0", + RecroomOOBE = "100", + PlayerSessionCount = "1", + BACKPACK_FAVORITE_TOOL = "-1", + Recroom_ChallengeMap = "0", + FIRST_TIME_IN_FLAGS = "0" } \ No newline at end of file diff --git a/src/data/content/storefronts.ts b/src/data/content/storefronts.ts index b43ef91..dd09df5 100644 --- a/src/data/content/storefronts.ts +++ b/src/data/content/storefronts.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -28,4 +28,23 @@ export enum StorefrontTypes { Bowling = 500, StuntRunner = 600, DormMirror = 700 +} + +export enum StorefrontBalanceType { + NonPurchasedNonP2P = -2, + NonPurchasedP2P, + SteamPurchased, + OculusPurchased, + PlayStationPurchased, + MicrosoftPurchased, + IOSPurchased = 5 +} + +export enum CurrencyType { + Invalid, + LaserTagTickets, + RecCenterTokens, + LostSkullsGold = 100, + DraculaSilver, + RecRoyale_Season1 = 200 } \ No newline at end of file diff --git a/src/data/live/base.ts b/src/data/live/base.ts index d4db6a6..6819e0a 100644 --- a/src/data/live/base.ts +++ b/src/data/live/base.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -66,6 +66,7 @@ class MatchmakingBase { else if (instance.isPrivate) return { errorCode: MatchmakingErrorCode.RoomInstanceIsPrivate }; await Instances.setPlayerInstance(options.profile, instance); + Instances.clearAllRoomEmptyInstances(instance.roomId); return { errorCode: MatchmakingErrorCode.Success, roomInstance: instance }; } @@ -73,35 +74,76 @@ class MatchmakingBase { } else { + // check to make sure room exists, is not private, and is active const targetRoom = options.roomName !== 'DormRoom' ? await Rooms.getByName(options.roomName) : await Rooms.getProfileDormDefault(options.profile); if (!targetRoom) return { errorCode: MatchmakingErrorCode.NoSuchRoom }; - if (targetRoom.Room.Accessibility == RoomAccessibility.Private) return { errorCode: MatchmakingErrorCode.RoomIsPrivate }; + if (targetRoom.Room.Accessibility == RoomAccessibility.Private && targetRoom.Room.CreatorPlayerId !== options.profile.getId()) + return { errorCode: MatchmakingErrorCode.RoomIsPrivate }; if (targetRoom.Room.State !== RoomState.Active) return { errorCode: MatchmakingErrorCode.RoomIsNotActive }; const roomId = targetRoom.Room.RoomId; + Instances.clearAllRoomEmptyInstances(roomId); + + // get all available instance let allInstances = Instances.getAllRoomInstances(roomId).values().toArray().filter(instance => !instance.isPrivate && !instance.isFull); const subroomId = targetRoom.Scenes.find(scene => scene.Name == options.subRoomName)?.RoomSceneId; if (subroomId) allInstances = allInstances.filter(instance => instance.subRoomId == subroomId); - const foundInstance = allInstances[Math.floor(Math.random() * allInstances.length)]; + // filter instances that do not support join in progress and are in progress + const builtinRooms = Rooms.getAllBuiltinRooms(); + const joinInProgressSubrooms = builtinRooms.map(room => + ({Name: room.Name, Scenes: room.Scenes.map(scene => + ({Name: scene.Name, Supported: scene.SupportsJoinInProgress}) + )}) + ); + allInstances = allInstances.filter(instance => { + const subroomName = Rooms.getSubroomNameFromId(targetRoom, instance.subRoomId); + if (!subroomName) return false; + const subrooms = joinInProgressSubrooms.find(room => room.Name == targetRoom.Room.Name); + if (!subrooms) return false; + const supportsJoinInProgress = subrooms.Scenes.find(subroom => subroom.Name == subroomName)?.Supported; + if (supportsJoinInProgress) return true; + else if (!instance.isInProgress) return true; + else return false; + }); + + allInstances = allInstances.sort((a, b) => { + const pidsA = Instances.getInstancePlayers(a); + const pidsB = Instances.getInstancePlayers(b); + return pidsA.size - pidsB.size; + }).reverse(); // Largest instances first + + const foundInstance = allInstances[0]; if (!foundInstance) { const matchmakeableSubrooms = targetRoom.Scenes.filter(scene => scene.CanMatchmakeInto); - const newInstance = Instances.createInstance({ + const newInstance = await Instances.createInstance({ Room: targetRoom, SceneIndex: Math.floor(Math.random() * matchmakeableSubrooms.length), - FirstPlayer: options.profile + FirstPlayer: options.profile, + Private: options.private, + IsDorm: options.roomName == 'DormRoom' }); + + Instances.clearAllRoomEmptyInstances(roomId); return { errorCode: MatchmakingErrorCode.Success, roomInstance: newInstance }; } else { + const currentInstance = options.profile.getInstance(); + if (currentInstance?.roomInstanceId == foundInstance.roomInstanceId) + return { errorCode: MatchmakingErrorCode.AlreadyInBestInstance }; + await Instances.setPlayerInstance(options.profile, foundInstance); + + Instances.clearAllRoomEmptyInstances(roomId); + return { errorCode: MatchmakingErrorCode.Success, roomInstance: foundInstance }; } - + } + } diff --git a/src/data/live/instances.ts b/src/data/live/instances.ts index 6016cad..d37151f 100644 --- a/src/data/live/instances.ts +++ b/src/data/live/instances.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import Logging from "@proxnet/undead-logging"; -import { Profile } from "../profiles.ts"; +import UnifiedProfile, { Profile } from "../profiles.ts"; import { RoomInstance, InstanceOptions } from "./types.ts"; import { Config } from "../../config.ts"; import Presence from "./presence.ts"; @@ -114,22 +114,28 @@ class InstancesBase { * * If one is, the player will be automatically added to the instance and their `profile.getInstance()` will be synchronized. */ - createInstance(options: InstanceOptions) { + async createInstance(options: InstanceOptions) { const scene = options.Room.Scenes[options.SceneIndex]; const newId = this.#generateUniqueInstanceId(); if (!scene) throw new Error("The specified scene did not exist."); + let instanceName = scene.Name === "Home" || scene.Name === options.Room.Room.Name ? `^${options.Room.Room.Name}` : `^${options.Room.Room.Name}.${scene.Name}`; + if (options.IsDorm) { + const dormCreatorPlayer = UnifiedProfile.get(options.Room.Room.CreatorPlayerId); + const player = await dormCreatorPlayer.export(); + if (player) instanceName = `@${player.displayName}'s Dorm`; + } const newInstance: RoomInstance = { roomInstanceId: newId, roomId: options.Room.Room.RoomId, subRoomId: scene.RoomSceneId, location: scene.RoomSceneLocationId, - dataBlob: scene.DataBlobName == "" ? undefined : scene.DataBlobName, + dataBlob: scene.DataBlobName, eventId: options.EventId, photonRegionId: config.public.photonRegionId, - photonRoomId: `20191120-GC${newId}`, - name: scene.Name === "Home" ? `^${options.Room.Room.Name}` : `^${options.Room.Room.Name}.${scene.Name}`, + photonRoomId: `20200306-GC${newId}`, + name: instanceName, maxCapacity: scene.MaxPlayers, isFull: false, isPrivate: typeof options.Private !== 'boolean' ? false : options.Private, @@ -161,7 +167,9 @@ class InstancesBase { } if (this.instanceCanFitPlayer(instance)) { - this.getInstancePlayers(instance).add(player); + const instancePlayers = this.getInstancePlayers(instance); + log.i(`Player ${player.getId()} went to '${instance.name}' with ${instancePlayers.size} other players`); + instancePlayers.add(player); player.setInstance(instance); const pres = await Presence.get(player); diff --git a/src/data/live/presence.ts b/src/data/live/presence.ts index e518161..8df3b85 100644 --- a/src/data/live/presence.ts +++ b/src/data/live/presence.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/live/types.ts b/src/data/live/types.ts index b9ae7fa..58c8646 100644 --- a/src/data/live/types.ts +++ b/src/data/live/types.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -58,7 +58,7 @@ export interface RoomInstance { location: string, dataBlob?: string, eventId?: number, - photonRegionId: PhotonRegionCodeString, + photonRegionId: PhotonRegionCodeString | PhotonRegionCodeNumber, photonRoomId: string, name?: string, maxCapacity: number, @@ -75,7 +75,8 @@ export interface InstanceOptions { EventId?: number, Name?: string, Private?: boolean, - FirstPlayer?: Profile + FirstPlayer?: Profile, + IsDorm?: boolean } diff --git a/src/data/objectives.ts b/src/data/objectives.ts index 115e777..2bc9588 100644 --- a/src/data/objectives.ts +++ b/src/data/objectives.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/platformtypes.ts b/src/data/platformtypes.ts index e33e56e..5d88162 100644 --- a/src/data/platformtypes.ts +++ b/src/data/platformtypes.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -24,4 +24,14 @@ export enum PlatformMask { HeadlessBot = 16, IOS = 32, All = -1 +} + +export enum PlatformType { + All = -1, + Steam, + Oculus, + PlayStation, + Microsoft, + HeadlessBot, + IOS } \ No newline at end of file diff --git a/src/data/profile/avatar.ts b/src/data/profile/avatar.ts index 6d86593..f39d325 100644 --- a/src/data/profile/avatar.ts +++ b/src/data/profile/avatar.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/profile/base/events.ts b/src/data/profile/base/events.ts index 92b322a..088feaa 100644 --- a/src/data/profile/base/events.ts +++ b/src/data/profile/base/events.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/profile/base/profilemanagerbase.ts b/src/data/profile/base/profilemanagerbase.ts index a49890b..24c69ae 100644 --- a/src/data/profile/base/profilemanagerbase.ts +++ b/src/data/profile/base/profilemanagerbase.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -21,6 +21,10 @@ export class ProfileContentManager { this.profileId = profileId; } + onProfileInit() { + return; + } + profileId: number; } \ No newline at end of file diff --git a/src/data/profile/progression.ts b/src/data/profile/progression.ts index 663d1e1..0616a51 100644 --- a/src/data/profile/progression.ts +++ b/src/data/profile/progression.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/profile/relationships.ts b/src/data/profile/relationships.ts index 320c247..52f9892 100644 --- a/src/data/profile/relationships.ts +++ b/src/data/profile/relationships.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/profile/reputation.ts b/src/data/profile/reputation.ts index 0fa098d..d9ad485 100644 --- a/src/data/profile/reputation.ts +++ b/src/data/profile/reputation.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/data/profile/rooms.ts b/src/data/profile/rooms.ts new file mode 100644 index 0000000..c84cc50 --- /dev/null +++ b/src/data/profile/rooms.ts @@ -0,0 +1,33 @@ +/* Galvanic Corrosion - Rec Room custom server for communities. + +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . */ + +import { Redis } from "../../db.ts"; +import { ProfileContentManager } from "./base/profilemanagerbase.ts"; + +export class ProfileRoomsManager extends ProfileContentManager { + + #rootKey = Redis.buildKey( + Redis.KeyGroups.Profiles.Root, + this.profileId.toString(), + Redis.KeyGroups.Profiles.Avatar.Root + ); + + async getRooms() { + // get player rooms + } + +} \ No newline at end of file diff --git a/src/data/profile/settings.ts b/src/data/profile/settings.ts index f8455de..0c5777a 100644 --- a/src/data/profile/settings.ts +++ b/src/data/profile/settings.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { Redis } from "../../db.ts"; -import { SettingKey } from "../content/settings.ts"; +import { SettingKey, SettingDefault } from "../content/settings.ts"; import { ProfileContentManager } from "./base/profilemanagerbase.ts"; export interface Setting { @@ -26,6 +26,14 @@ export interface Setting { export class ProfileSettingsManager extends ProfileContentManager { + override async onProfileInit() { + await this.setSetting(SettingKey.RecroomOOBE, SettingDefault.RecroomOOBE); + await this.setSetting(SettingKey.PlayerStatusVisibility, SettingDefault.PlayerStatusVisibility); + await this.setSetting(SettingKey.FIRST_TIME_IN_FLAGS, SettingDefault.FIRST_TIME_IN_FLAGS); + await this.setSetting(SettingKey.BACKPACK_FAVORITE_TOOL, SettingDefault.BACKPACK_FAVORITE_TOOL); + await this.setSetting(SettingKey.Recroom_ChallengeMap, SettingDefault.Recroom_ChallengeMap); + } + async getSettings() { const settings = await Redis.Database.hgetall(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings)); const returnSettings: Setting[] = []; diff --git a/src/data/profiles.ts b/src/data/profiles.ts index fb37336..4d6747d 100644 --- a/src/data/profiles.ts +++ b/src/data/profiles.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -140,7 +140,11 @@ class Profile { newUsername, ); - return new Profile(newId); + const profile = new Profile(newId); + + profile.Settings.onProfileInit(); + + return profile; } // surely this can be written better @@ -164,7 +168,7 @@ class Profile { platforms: 1, username: values[2] == null ? "DATABASEERROR" : values[2], displayName: values[3] == null ? (values[2] == null ? "DATABASEERROR" : values[2]) : values[3], - email: "" // removes a notification in messages (untested) + email: "notanemail@notanemailsite.local" // we are confirmed }); }); } @@ -219,7 +223,7 @@ class Profile { async getBio() { const bio = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.#id.toString(), Redis.KeyGroups.Profiles.Bio)); - if (!bio) return "This player has not yet set their bio. Remind them to set one!"; + if (!bio) return ""; else return bio; } @@ -302,7 +306,7 @@ class Profile { sub: this.#id, role: (await this.getIsOperator()) ? 'developer' : 'user', exp: Math.round(Date.now() / 1000) + Math.round(config.auth.timeout * 60 * 60), - typ: AuthType.Game + typ: AuthType.Game, }; return await JsonWebToken.encode(payload, config.auth.secret, { algorithm: "HS512" }); } @@ -335,6 +339,15 @@ class UnifiedProfileBase { return await Profile.existsByName(name); } + getAllSockets() { + const returnSockets: Set = new Set(); + for (const profile of profiles.values()) { + const socket = profile.getSocketHandler(); + if (socket) returnSockets.add(socket); + } + return returnSockets; + } + } const UnifiedProfile = new UnifiedProfileBase(); diff --git a/src/data/usernames.ts b/src/data/usernames.ts index 9af3541..cc66a3b 100644 --- a/src/data/usernames.ts +++ b/src/data/usernames.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -18,54 +18,36 @@ along with this program. If not, see . */ const Dictionary = { Adjectives: [ "Amazing", - "Adventurous", "Affable", "Agreeable", "Ambitious", "Amicable", "Animated", - "Approachable", - "Articulate", "Astute", - "Attractive", "Authentic", - "Benevolent", "Blissful", "Bold", "Bright", "Buoyant", "Calm", - "Captivating", - "Charismatic", "Cheerful", "Clever", - "Compassionate", "Confident", - "Considerate", "Content", - "Cooperative", - "Courageous", "Creative", "Cultured", "Curious", "Dashing", "Dazzling", "Dedicated", - "Delightful", - "Dependable", - "Determined", "Diligent", "Dynamic", "Earnest", "Easygoing", "Ebullient", - "Effervescent", - "Empathetic", - "Enchanting", "Endearing", "Energetic", "Engaging", - "Enthusiastic", "Exuberant", "Fantastic", "Fearless", @@ -77,28 +59,17 @@ const Dictionary = { "Genuine", "Gracious", "Grateful", - "Harmonious", - "Heartwarming", "Helpful", "Honest", "Humble", "Humorous", - "Imaginative", - "Impeccable", "Incisive", - "Incredible", - "Independent", - "Industrious", "Ingenious", - "Insightful", - "Intelligent", "Intuitive", - "Invigorating", "Jovial", "Jubilant", "Just", "Kind", - "Knowledgeable", "Likable", "Lively", "Lovable", @@ -110,24 +81,14 @@ const Dictionary = { "Masterful", "Mature", "Merciful", - "Methodical", - "Meticulous", "Mindful", "Motivated", "Natural", "Nurturing", "Observant", - "Optimistic", "Outgoing", - "Passionate", "Patient", "Peaceful", - "Perceptive", - "Perseverant", - "Persistent", - "Persuasive", - "Personable", - "Philanthropic", "Placid", "Playful", "Pleasant", @@ -136,20 +97,14 @@ const Dictionary = { "Powerful", "Pragmatic", "Proactive", - "Proficient", "Prudent", "Punctual", - "Purposeful", "Radiant", "Rational", "Real", "Receptive", - "Reflective", "Reliable", "Resilient", - "Resourceful", - "Respectful", - "Responsible", "Robust", "Sagacious", "Serene", @@ -159,33 +114,23 @@ const Dictionary = { "Sociable", "Spirited", "Splendid", - "Spontaneous", "Steady", "Sterling", "Strong", "Sublime", - "Successful", - "Supportive", - "Sympathetic", "Talented", "Tenacious", - "Thoughtful", "Tireless", "Tolerant", "Tough", "Tranquil", - "Trustworthy", - "Unassuming", - "Understanding", "Unique", - "Unpretentious", "Upbeat", "Valiant", "Vibrant", "Virtuous", "Visionary", "Vivacious", - "Warmhearted", "Welcoming", "Wise", "Witty", @@ -198,7 +143,6 @@ const Dictionary = { "Elysium", "Horizon", "Catalyst", - "Luminescence", "Utopia", "Eclipse", "Nebula", @@ -220,7 +164,6 @@ const Dictionary = { "Vibrance", "Astral", "Jubilant", - "Ascendancy", "Zen", "Nebulous", "Ecliptic", @@ -233,7 +176,6 @@ const Dictionary = { "Enigma", "Luminous", "Epoch", - "Serendipity", "Zenithal", "Paragon", "Panorama", @@ -255,7 +197,6 @@ const Dictionary = { "Endeavor", "Visionary", "Epoch", - "Renaissance", "Panache", "Jubilee", "Resonance", @@ -264,13 +205,11 @@ const Dictionary = { "Ethereal", "Cascade", "Radiance", - "Synchronicity", "Nebula", "Equinox", "Pulsar", "Apex", "Ethos", - "Wanderlust", "Zenith", "Nebula", "Vertex", diff --git a/src/data/users.ts b/src/data/users.ts index 407d587..3ac7413 100644 --- a/src/data/users.ts +++ b/src/data/users.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/db.ts b/src/db.ts index b4c0ef5..2261ad4 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -76,6 +76,7 @@ export const KeyGroups = { }, Profile_Usernames: "profile-usernames", PlatformAssociations: "platforms", + AddressBans: "address-bans", Profiles: { Root: "profiles", Username: "username", @@ -87,6 +88,7 @@ export const KeyGroups = { DeviceClass: "deviceClass", Xp: "xp", Bio: "bio", + Rooms: "rooms", Relationships: { Root: "relationships", IncomingFriendRequests: "incomingFriendRequests", diff --git a/src/discord.ts b/src/discord.ts index f0b4ea6..e89f8ce 100644 --- a/src/discord.ts +++ b/src/discord.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/main.ts b/src/main.ts index 64ac3df..ba08ec6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -28,6 +28,7 @@ import UnifiedProfile, { ProfileTokenFormat } from "./data/profiles.ts"; import { SocketHandoff } from "./socket/handoff.ts"; import { SignalRSocketHandler } from "./socket/socket.ts"; import Rooms from "./data/content/rooms.ts"; +import { GameConfigs } from "./data/config.ts"; const instanceId = generateRandomString(64); @@ -114,10 +115,6 @@ if (!(await Rooms.generateBuiltinRooms())) log.i(`Generated built-in rooms`); try { - /** - * Galvanic WebSocket Server - */ - type AuthResultBase = { valid: boolean } @@ -133,10 +130,13 @@ try { const authHeader = req.headers.get('authorization'); if (!authHeader) return { valid: false } as AuthResult; - const token = authHeader.split(" ")[1]; + log.d(authHeader); + const token = authHeader.split(", ")[1]; // Why is the header formatted like this? if (!token) return { valid: false } as AuthResult; + const splitToken = token.split(' ')[1]; + if (!splitToken) return { valid: false } as AuthResult; - const decodedToken = await decode(token, config.auth.secret, {algorithm: 'HS512'}); + const decodedToken = await decode(splitToken, config.auth.secret, {algorithm: 'HS512'}); const schemaResult = ProfileTokenSchema.safeParse(decodedToken); if (!schemaResult.success) return { valid: false } as AuthResult; else return { token: decodedToken, valid: true } as AuthResult; @@ -149,6 +149,14 @@ try { const abort = new AbortController(); + abort.signal.addEventListener('abort', () => { + log.n("Closing all sockets"); + const sockets = UnifiedProfile.getAllSockets(); + for (const socket of sockets.values()) { + socket.destroy(socket)(); + } // I used the socket to destroy the socket + }); + // Galvanic WebSocket Deno.serve({port: config.web.socket.port, hostname: config.web.socket.host, signal: abort.signal, onListen: addr => { log.n(`Socket listening on http://${addr.hostname}:${addr.port}`); @@ -209,6 +217,9 @@ try { if (!(await UnifiedProfile.existsByName("Coach"))) UnifiedProfile.create({ username: "Coach", id: 1 }); // create Coach id 1 if they do not exist if (!(await UnifiedProfile.existsByName("Server"))) UnifiedProfile.create({ username: "Server", id: 2 }); // create Server id 2 if they do not exist + if (!(await GameConfigs.getGameConfig('splitTestSoftOverrides'))) GameConfigs.setGameConfig('splitTestSoftOverrides', ''); + if (!(await GameConfigs.getGameConfig('splitTestHardOverrides'))) GameConfigs.setGameConfig('splitTestHardOverrides', ''); + }); http.on('error', err => { diff --git a/src/routes/account.ts b/src/routes/account.ts index 665e507..dd6d893 100644 --- a/src/routes/account.ts +++ b/src/routes/account.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -19,7 +19,7 @@ import { APIUtils } from "../apiutils.ts"; import { route as AccountRoute } from "./account/account.ts"; import { route as ParentalControlRoute } from "./account/parentalcontrol.ts"; -export const route = APIUtils.createRouter("/accountservice"); +export const route = APIUtils.createRouter("/accounts"); route.router.use(AccountRoute.path, AccountRoute.router); route.router.use(ParentalControlRoute.path, ParentalControlRoute.router); \ No newline at end of file diff --git a/src/routes/account/account.ts b/src/routes/account/account.ts index 69ddc92..367ba9c 100644 --- a/src/routes/account/account.ts +++ b/src/routes/account/account.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -19,6 +19,7 @@ import { APIUtils } from "../../apiutils.ts"; import express from "express"; import UnifiedProfile, { Profile } from "../../data/profiles.ts"; import { z } from "zod"; +import { AuthType } from "../../data/users.ts"; export const route = APIUtils.createRouter("/account"); @@ -28,7 +29,7 @@ const CreateAccountRequestBodySchema = z.object({ deviceId: z.string() }); -const rateLimit = new APIUtils.RateLimiter(25, 5); +const rateLimit = new APIUtils.RateLimiter(); route.router.post("/create", @@ -102,6 +103,15 @@ route.router.get("/me", ); +route.router.post("/me/displayname", + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + + APIUtils.RecNetResponse(true, "DisplayName customization is not yet implemented."), + +); + interface BioFetchParams { id?: string } diff --git a/src/routes/account/parentalcontrol.ts b/src/routes/account/parentalcontrol.ts index 4a7b9be..e551d3f 100644 --- a/src/routes/account/parentalcontrol.ts +++ b/src/routes/account/parentalcontrol.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api.ts b/src/routes/api.ts index 9890d63..3cd01dc 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/PlayerReporting.ts b/src/routes/api/PlayerReporting.ts index b28c163..0b52f32 100644 --- a/src/routes/api/PlayerReporting.ts +++ b/src/routes/api/PlayerReporting.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/avatar.ts b/src/routes/api/avatar.ts index c673852..1065a35 100644 --- a/src/routes/api/avatar.ts +++ b/src/routes/api/avatar.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/challenge.ts b/src/routes/api/challenge.ts index 9e17317..522e97e 100644 --- a/src/routes/api/challenge.ts +++ b/src/routes/api/challenge.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -16,7 +16,30 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { APIUtils } from "../../apiutils.ts"; +import { AuthType } from "../../data/users.ts"; export const route = APIUtils.createRouter("/challenge"); -// create router for now \ No newline at end of file +// todo: challenges; all of it +// will require a ChallengeBuilder to compile challenge configs +route.router.get('/v2/getCurrent', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + + (_rq, rs) => { + rs.json({ + ChallengeMapId: 0, + StartAt: new Date().toISOString(), + EndAt: new Date(Date.now() + 604_800_000).toISOString(), // 1 week + ServerTime: new Date().toISOString(), + Challenges: [], + Gift: { + GiftDropId: 0, + Xp: 0, + Level: 0 + } + }); + }, + +); \ No newline at end of file diff --git a/src/routes/api/checklist.ts b/src/routes/api/checklist.ts index 2ce13b4..e5b4690 100644 --- a/src/routes/api/checklist.ts +++ b/src/routes/api/checklist.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/communityboard.ts b/src/routes/api/communityboard.ts index 4399e75..d6d733d 100644 --- a/src/routes/api/communityboard.ts +++ b/src/routes/api/communityboard.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/config.ts b/src/routes/api/config.ts index c314f5d..edc1245 100644 --- a/src/routes/api/config.ts +++ b/src/routes/api/config.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ import { GameConfigs } from "../../data/config.ts"; export const route = APIUtils.createRouter("/config"); -const rateLimit = new APIUtils.RateLimiter(60, 2); +const rateLimit = new APIUtils.RateLimiter(); route.router.get("/v2", rateLimit.middle(), (_rq, rs) => { const config = GameConfigs.getConfig(); diff --git a/src/routes/api/consumables.ts b/src/routes/api/consumables.ts index 0a6c1fd..96d1cc3 100644 --- a/src/routes/api/consumables.ts +++ b/src/routes/api/consumables.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/equipment.ts b/src/routes/api/equipment.ts index 4fed534..427e6e6 100644 --- a/src/routes/api/equipment.ts +++ b/src/routes/api/equipment.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -27,4 +27,4 @@ route.router.get('/v2/getUnlocked', APIUtils.emptyArrayResponse -) \ No newline at end of file +); \ No newline at end of file diff --git a/src/routes/api/gameconfigs.ts b/src/routes/api/gameconfigs.ts index 6a9bdad..d49b3b4 100644 --- a/src/routes/api/gameconfigs.ts +++ b/src/routes/api/gameconfigs.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -16,7 +16,12 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { APIUtils } from "../../apiutils.ts"; +import { GameConfigs } from "../../data/config.ts"; export const route = APIUtils.createRouter("/gameconfigs"); -route.router.get("/v1/all", APIUtils.emptyArrayResponse); \ No newline at end of file +const rateLimit = new APIUtils.RateLimiter(); + +route.router.get("/v1/all", rateLimit.middle(), async (_rq, rs) => { + rs.json((await GameConfigs.getAllGameConfigs()).entries().map(val => ({Key: val[0], Value: val[1]})).toArray()); +}); \ No newline at end of file diff --git a/src/routes/api/images.ts b/src/routes/api/images.ts index c7f9527..46cf4ac 100644 --- a/src/routes/api/images.ts +++ b/src/routes/api/images.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/messages.ts b/src/routes/api/messages.ts index da8ab30..04a249e 100644 --- a/src/routes/api/messages.ts +++ b/src/routes/api/messages.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/objectives.ts b/src/routes/api/objectives.ts index 017a204..939a8c1 100644 --- a/src/routes/api/objectives.ts +++ b/src/routes/api/objectives.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -15,8 +15,10 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -import { APIUtils } from "../../apiutils.ts"; +import { z } from "zod"; +import { APIUtils, NoBody } from "../../apiutils.ts"; import { AuthType } from "../../data/users.ts"; +import express from "express"; export const route = APIUtils.createRouter('/objectives'); @@ -32,4 +34,28 @@ route.router.get('/v1/myprogress', }); }, +); + +interface ClearGroupRequestBody { + Group: number +} +const ClearGroupRequestSchema = z.object({ + Group: z.number() +}); +route.router.post('/v1/cleargroup', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + express.json(), + APIUtils.logBody, + APIUtils.validateRequestBody(ClearGroupRequestSchema), + + (rq: express.Request, rs: express.Response) => { + rs.json({ + Group: rq.body.Group, + IsCompleted: false, + ClearedAt: new Date().toISOString() // todo: objectives; all of it + }); + }, + ); \ No newline at end of file diff --git a/src/routes/api/playerReputation.ts b/src/routes/api/playerReputation.ts index 90c3fa2..d6f461a 100644 --- a/src/routes/api/playerReputation.ts +++ b/src/routes/api/playerReputation.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -38,4 +38,16 @@ route.router.get('/v1/:id', rs.json(await UnifiedProfile.get(parsedPlayerId).Reputation.getReputation()); } +); + +route.router.get('/v1/bulk', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + + express.urlencoded({ extended: true }), + APIUtils.logBody, + + APIUtils.emptyArrayResponse + ); \ No newline at end of file diff --git a/src/routes/api/playerevents.ts b/src/routes/api/playerevents.ts index b36b06e..5be5c7d 100644 --- a/src/routes/api/playerevents.ts +++ b/src/routes/api/playerevents.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/players.ts b/src/routes/api/players.ts index 6dab77d..18add56 100644 --- a/src/routes/api/players.ts +++ b/src/routes/api/players.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -19,10 +19,11 @@ import Logging from "@proxnet/undead-logging"; import { APIUtils } from "../../apiutils.ts"; import express from "express"; import UnifiedProfile from "../../data/profiles.ts"; +import { AuthType } from "../../data/users.ts"; const log = new Logging("ProgressionRoute"); -const rateLimit = new APIUtils.RateLimiter(60, 2); +const rateLimit = new APIUtils.RateLimiter(); export const route = APIUtils.createRouter("/players"); @@ -48,4 +49,15 @@ route.router.get('/v1/progression/:id', rs.json(res); } +); + +route.router.post('/v1/progression/bulk', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + express.urlencoded({ extended: true }), + APIUtils.logBody, + + APIUtils.emptyArrayResponse + ); \ No newline at end of file diff --git a/src/routes/api/playersubscriptions.ts b/src/routes/api/playersubscriptions.ts index 5db75f2..791a009 100644 --- a/src/routes/api/playersubscriptions.ts +++ b/src/routes/api/playersubscriptions.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/quickplay.ts b/src/routes/api/quickplay.ts index 5830840..d401eca 100644 --- a/src/routes/api/quickplay.ts +++ b/src/routes/api/quickplay.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/relationships.ts b/src/routes/api/relationships.ts index b5f5770..897563c 100644 --- a/src/routes/api/relationships.ts +++ b/src/routes/api/relationships.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/api/rooms.ts b/src/routes/api/rooms.ts index 6150cc2..991ae40 100644 --- a/src/routes/api/rooms.ts +++ b/src/routes/api/rooms.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -66,7 +66,7 @@ route.router.get('/v1/hot', async (_rq, rs) => { // temporary: return all public AG rooms for testing const rooms = await Rooms.getAllBuiltinRoomGenerations(); - rs.json(rooms.map(room => room.Room).filter(room => room.Accessibility !== RoomAccessibility.Private)); + rs.json(rooms.map(room => room.Room).filter(room => room.Accessibility == RoomAccessibility.Public)); }, ); @@ -98,11 +98,16 @@ route.router.get('/v2/name/:name', } const room = await Rooms.getByName(rq.params.name.trim()); - if (!room || rq.params.name == 'DormRoom') { - rs.sendStatus(404); + if (room) { + rs.json(room.Room); + return; + } else if (rq.params.name == 'DormRoom') { + const dorm = await Rooms.getProfileDormDefault(rs.locals.profile); + if (dorm) rs.json(dorm.Room); + else rs.sendStatus(404); return; } else { - rs.json(room.Room); + rs.sendStatus(404); return; } }, @@ -118,4 +123,4 @@ route.router.post('/v1/roomRolePermissions', rs.sendStatus(200); }, -); \ No newline at end of file +); diff --git a/src/routes/api/settings.ts b/src/routes/api/settings.ts index 5e29795..5afa691 100644 --- a/src/routes/api/settings.ts +++ b/src/routes/api/settings.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -59,11 +59,8 @@ route.router.post('/v2/set', (rq: express.Request, rs: express.Response) => { rs.locals.profile.Settings.setSettingRaw(rq.body.Key, rq.body.Value), - rs.json({ - Succeeded: true, - Error: "", - ShouldRetry: false - }); + rs.sendStatus(200); + log.d(`${rs.locals.profile.getId()} set settings key '${rq.body.Key}' to '${rq.body.Value}'`); } ); \ No newline at end of file diff --git a/src/routes/api/storefronts.ts b/src/routes/api/storefronts.ts index d6777b2..e7a4b73 100644 --- a/src/routes/api/storefronts.ts +++ b/src/routes/api/storefronts.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -17,6 +17,8 @@ along with this program. If not, see . */ import { APIUtils } from "../../apiutils.ts"; import express from "express"; +import { AuthType } from "../../data/users.ts"; +import { CurrencyType, StorefrontBalanceType } from "../../data/content/storefronts.ts"; export const route = APIUtils.createRouter('/storefronts'); @@ -26,6 +28,7 @@ interface StorefrontFetchParams { route.router.get('/v3/giftdropstore/:id', APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), (rq: express.Request, rs: express.Response) => { if (!rq.params.id) { @@ -39,4 +42,28 @@ route.router.get('/v3/giftdropstore/:id', }); }, +); + +interface BalanceParams { + currencyType?: string +} +route.router.get('/v4/balance/:currencyType', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + + (rq: express.Request, rs: express.Response) => { + const parsedCurrencyType = parseInt(rq.params.currencyType ? rq.params.currencyType : "2"); + if (isNaN(parsedCurrencyType)) { + rs.sendStatus(400); + } else { + rs.json([{ + Balance: 0, + BalanceType: StorefrontBalanceType.NonPurchasedNonP2P, + CurrencyType: parsedCurrencyType + }]); + } + + }, + ); \ No newline at end of file diff --git a/src/routes/api/versioncheck.ts b/src/routes/api/versioncheck.ts index 007c063..0f3590f 100644 --- a/src/routes/api/versioncheck.ts +++ b/src/routes/api/versioncheck.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -19,7 +19,9 @@ import { APIUtils } from "../../apiutils.ts"; export const route = APIUtils.createRouter("/versioncheck"); -const validVersion = "20191120"; +export const validVersions = [ + "20200306" +]; enum VersionStatus { ValidForPlay, @@ -35,10 +37,10 @@ route.router.get("/v4", (rq, rs) => { const requestedVer = rq.query["v"]; const pQuery = rq.query["p"]; - if (typeof requestedVer == "undefined" || typeof pQuery == "undefined") { + if (typeof requestedVer == "undefined" || typeof pQuery == "undefined" || typeof requestedVer == 'object') { rs.statusCode = 400; - rs.json(APIUtils.genericResponseFormat(true, "One or more query parameters were not found.")); - } else if (requestedVer !== validVersion) { + rs.json(APIUtils.genericResponseFormat(true, "One or more query parameters were not found or invalid.")); + } else if (!validVersions.includes(requestedVer as string)) { const res: ValidVersionResponse = { VersionStatus: VersionStatus.UpdateRequired, }; diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 1889b6a..c097f5f 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -18,8 +18,10 @@ along with this program. If not, see . */ import { APIUtils } from "../apiutils.ts"; import { route as CachedLoginRoute } from "./auth/cachedlogin.ts"; import { route as ConnectRoute } from "./auth/connect.ts"; +import { route as AccountRoute } from "./auth/account.ts"; -export const route = APIUtils.createRouter("/authservice"); +export const route = APIUtils.createRouter("/auth"); route.router.use(CachedLoginRoute.path, CachedLoginRoute.router); route.router.use(ConnectRoute.path, ConnectRoute.router); +route.router.use(AccountRoute.path, AccountRoute.router); \ No newline at end of file diff --git a/src/routes/auth/account.ts b/src/routes/auth/account.ts new file mode 100644 index 0000000..5c3149c --- /dev/null +++ b/src/routes/auth/account.ts @@ -0,0 +1,41 @@ +/* Galvanic Corrosion - Rec Room custom server for communities. + +Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . */ + +import { APIUtils } from "../../apiutils.ts"; +import { AuthType } from "../../data/users.ts"; + +export const route = APIUtils.createRouter("/account"); + +route.router.post('/me/changepassword', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + + APIUtils.RecNetResponse(true, "Passwords are unsupported on Galvanic Corrosion.") + +); + +route.router.get('/me/haspassword', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + + (rq, rs) => { + rs.json(true); + }, + +); \ No newline at end of file diff --git a/src/routes/auth/cachedlogin.ts b/src/routes/auth/cachedlogin.ts index cfc1067..610800a 100644 --- a/src/routes/auth/cachedlogin.ts +++ b/src/routes/auth/cachedlogin.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/auth/connect.ts b/src/routes/auth/connect.ts index b6aa2c0..7973ffe 100644 --- a/src/routes/auth/connect.ts +++ b/src/routes/auth/connect.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -24,6 +24,7 @@ import Logging from "@proxnet/undead-logging"; import { z } from "zod"; import { AuthType } from "../../data/users.ts"; import { Redis } from "../../db.ts"; +import { validVersions } from "../api/versioncheck.ts"; const config = Config.getConfig(); @@ -116,7 +117,7 @@ route.router.post("/token", const conditionsMet = ![ rq.body.client_id === "recroom", rq.body.platform === "0", - rq.body.ver === '20191120', + validVersions.includes(rq.body.ver), rq.body.device_class.length === 1, !isNaN(Number(rq.body.device_class)), !(rq.body.device_id.length > 96), diff --git a/src/routes/cdn.ts b/src/routes/cdn.ts index 507092f..1aa472c 100644 --- a/src/routes/cdn.ts +++ b/src/routes/cdn.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/cdn/config.ts b/src/routes/cdn/config.ts index cd7ba0f..4fa970f 100644 --- a/src/routes/cdn/config.ts +++ b/src/routes/cdn/config.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/img.ts b/src/routes/img.ts index dc4e5ec..63c8c9b 100644 --- a/src/routes/img.ts +++ b/src/routes/img.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -36,6 +36,7 @@ interface ImageQueryOptions { cropSquare?: string; width?: string; height?: string; + sig?: string; } route.router.get( @@ -49,8 +50,6 @@ route.router.get( rq.path.substring(1, rq.path.length).replaceAll("%20", " "), ); - // why does it think it is never reassigned? line 39 - // deno-lint-ignore prefer-const let image: Image; const imageSource = baseImages.includes(filename) ? BaseImages.getBaseImage(filename) @@ -80,6 +79,23 @@ route.router.get( else height = num; } + if (cropSquare == 1) { + if (image.width > image.height) { + image.crop( + Math.round(image.width / 2) - Math.round(image.height / 2), + 0, + image.height, + image.height, + ); + } else { + image.crop( + 0, + Math.round(image.height / 2) - Math.round(image.width / 2), + image.width, + image.width, + ); + } + } if (width && height) { const targetWidth = width > image.width ? image.width : width; const targetHeight = height > image.height ? image.height : height; @@ -102,23 +118,8 @@ route.router.get( } } else if (width) image.resize(width, Image.RESIZE_AUTO); else if (height) image.resize(Image.RESIZE_AUTO, height); - if (cropSquare == 1) { - if (image.width > image.height) { - image.crop( - Math.round(image.width / 2) - Math.round(image.height / 2), - 0, - image.height, - image.height, - ); - } else {image.crop( - 0, - Math.round(image.height / 2) - Math.round(image.width / 2), - image.width, - image.width, - );} - } - rs.setHeader('content-signature', 'key-id=KEY:RSA:p1.rec.net; data=aGk='); // enable image signature patch on client + if (rq.query.sig) rs.setHeader('content-signature', 'key-id=KEY:RSA:p1.rec.net; data=aGk='); rs.type("png").send(Buffer.from(await image.encode())); }, ); diff --git a/src/routes/match.ts b/src/routes/match.ts index 0b42916..b633eb2 100644 --- a/src/routes/match.ts +++ b/src/routes/match.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/match/goto.ts b/src/routes/match/goto.ts index 6c8749c..4c075bd 100644 --- a/src/routes/match/goto.ts +++ b/src/routes/match/goto.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -35,8 +35,9 @@ route.router.post('/room/:roomName', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), + APIUtils.startTimer, - async (rq: express.Request, rs: express.Response) => { + async (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { if (!rq.params.roomName) { log.d('Matchmake failed: No room specified'); rs.json({ @@ -45,22 +46,30 @@ route.router.post('/room/:roomName', return; } rs.json(await Matchmaking.matchmake({ profile: rs.locals.profile, roomName: rq.params.roomName })); + nxt(); }, + APIUtils.stopTimer + ); route.router.post('/room/:roomName/:subRoomName', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), + APIUtils.startTimer, - async (rq: express.Request, rs: express.Response) => { + async (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { if (!rq.params.roomName) { + log.d('Matchmake failed: No room specified'); rs.json({ errorCode: MatchmakingErrorCode.NoSuchRoom }); return; } rs.json(await Matchmaking.matchmake({ profile: rs.locals.profile, roomName: rq.params.roomName, subRoomName: rq.params.subRoomName })); + nxt(); }, -) \ No newline at end of file + APIUtils.stopTimer + +); \ No newline at end of file diff --git a/src/routes/match/player.ts b/src/routes/match/player.ts index 9df6494..fe5bd3d 100644 --- a/src/routes/match/player.ts +++ b/src/routes/match/player.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -25,6 +25,7 @@ import Logging from "@proxnet/undead-logging"; import UnifiedProfile from "../../data/profiles.ts"; import { PlayerStatusVisibility, VRMovementMode } from "../../data/live/types.ts"; import { SettingKey } from "../../data/content/settings.ts"; +import Instances from "../../data/live/instances.ts"; const log = new Logging("MatchPlayerRoute"); @@ -85,7 +86,7 @@ route.router.post('/logout', APIUtils.validateRequestBody(LoginSchema), (_rq, rs) => { - Matchmaking.deleteLoginLock(rs.locals.profile); + Instances.removePlayerFromCurrentInstance(rs.locals.profile); rs.sendStatus(200); } @@ -110,14 +111,14 @@ interface StatusVisibilityBody { statusVisibility: PlayerStatusVisibility } const StatusVisibilitySchema = z.object({ - statusVisibility: z.nativeEnum(PlayerStatusVisibility) + statusVisibility: z.enum(Object.values(PlayerStatusVisibility).map(String) as [string, ...string[]]) }); - route.router.put('/statusvisibility', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), express.urlencoded({ extended: true }), + APIUtils.logBody, APIUtils.validateRequestBody(StatusVisibilitySchema), async (rq: express.Request, rs: express.Response) => { @@ -148,4 +149,15 @@ route.router.put('/vrmovementmode', rs.sendStatus(200); }, +); + +route.router.put('/photonregionpings', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + + (rq, rs) => { + rs.sendStatus(200); + } + ); \ No newline at end of file diff --git a/src/routes/match/room.ts b/src/routes/match/room.ts index a025925..58a58d8 100644 --- a/src/routes/match/room.ts +++ b/src/routes/match/room.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/routes/nameserver.ts b/src/routes/nameserver.ts index a93c62c..e5a6dcf 100644 --- a/src/routes/nameserver.ts +++ b/src/routes/nameserver.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -24,6 +24,7 @@ const protocol = config.web.api.securepublichost ? "https" : "http"; export const route = APIUtils.createRouter("/ns"); type NameserverHosts = { + Accounts: string; Auth: string; API: string; WWW: string; @@ -38,6 +39,7 @@ type NameserverHosts = { }; const nameserver: NameserverHosts = { + Accounts: `${protocol}://${config.web.api.publichost}/accounts`, Auth: `${protocol}://${config.web.api.publichost}/auth`, API: `${protocol}://${config.web.api.publichost}`, WWW: `${protocol}://${config.web.api.publichost}`, diff --git a/src/routes/user.ts b/src/routes/user.ts index 4955288..b9ec304 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -57,7 +57,7 @@ const AuthRequestRootSchema = z.object({ pubkey: z.string(), }); -const rateLimit = new APIUtils.RateLimiter(60, 2); +const rateLimit = new APIUtils.RateLimiter(); route.router.post("/auth", @@ -140,7 +140,7 @@ route.router.post("/auth", }, ); -const checkRateLimit = new APIUtils.RateLimiter(10, 3); +const checkRateLimit = new APIUtils.RateLimiter(); route.router.get('/checkExpired', checkRateLimit.middle(), async (rq, rs) => { @@ -152,7 +152,7 @@ route.router.get('/checkExpired', checkRateLimit.middle(), async (rq, rs) => { try { const decodedToken = await decode(token, config.auth.secret, { algorithm: "HS512", leeway: 31536000 }); // 1 year leeway - rs.json(decodedToken.exp < Math.round(Date.now() / 1000)); + rs.json(decodedToken.exp < Math.round(Date.now() / 1000)); // check it manually } catch { rs.json(true); } diff --git a/src/socket/handoff.ts b/src/socket/handoff.ts index ffc526b..a78aa7e 100644 --- a/src/socket/handoff.ts +++ b/src/socket/handoff.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/socket/route.ts b/src/socket/route.ts index 011eca1..1b6f708 100644 --- a/src/socket/route.ts +++ b/src/socket/route.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/socket/socket.ts b/src/socket/socket.ts index 23ca2c0..554d515 100644 --- a/src/socket/socket.ts +++ b/src/socket/socket.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify @@ -44,7 +44,7 @@ export class SignalRSocketHandler { #Targets: Map = new Map(); - #PresenceUpdateId: number; + #PeriodicalId: number; constructor(socket: WebSocket, player: Profile) { @@ -57,9 +57,12 @@ export class SignalRSocketHandler { this.#Targets.set('SubscribeToPlayers', new PlayerSocketSubscriptionTarget()); - this.#PresenceUpdateId = setInterval(async () => { - const pres = await Presence.get(this.#profile); - this.sendNotification("PresenceUpdate", await pres.export()); + this.#PeriodicalId = setInterval(async () => { + if (this.#socket.readyState !== this.#socket.CLOSED) { + const pres = await Presence.get(this.#profile); + this.sendNotification("PresenceUpdate", await pres.export()); + this.sendRaw({ type: 6 }); + } }, 8000); } @@ -83,7 +86,7 @@ export class SignalRSocketHandler { this.sendRaw({}); return; } else { - this.#log.d(`CLIENT MESSAGE\n Type: ${message.data.type} (${SignalMessageType[message.data.type]})\n ${JSON.stringify(message.data)}`); + //this.#log.d(`CLIENT MESSAGE\n Type: ${message.data.type} (${SignalMessageType[message.data.type]})\n ${JSON.stringify(message.data)}`); if (message.data.type == SignalMessageType.Invocation && message.data.invocationId) { // don't send completion messages for nonblocking invocations const res = await this.#dispatchTarget(message.data.target, message.data.arguments[0]); // rec room only uses the first index if (res.type == TargetResultType.Success) { @@ -145,18 +148,19 @@ export class SignalRSocketHandler { destroy(sock: SignalRSocketHandler) { return () => { - clearInterval(sock.#PresenceUpdateId); + clearInterval(sock.#PeriodicalId); sock.sendRaw({ type: 7, error: "Socket closed" }); sock.#socket.close(); - sock.#log.i(`Closed hub socket`); + sock.#log.i(`Closed socket`); + sock.#profile.clearSocketHandler(); } } sendRaw(data: object) { this.#socket.send(`${JSON.stringify(data)}\u001e`); - // todo sometime: make this less confusing - const type = `Type: ${JSON.stringify(data) == '{}' ? 'Protocol Message' : `${(data as SignalRMessage).type} (${SignalMessageType[(data as SignalRMessage).type]})`}`; - this.#log.d(`SERVER MESSAGE\n ${type}\n ${JSON.stringify(data as SignalRMessage)}`); + // todo sometime: make the below less confusing + //const type = `Type: ${JSON.stringify(data) == '{}' ? 'Protocol Message' : `${(data as SignalRMessage).type} (${SignalMessageType[(data as SignalRMessage).type]})`}`; + //this.#log.d(`SERVER MESSAGE\n ${type}\n ${JSON.stringify(data as SignalRMessage)}`); } sendNotification(id: PushNotificationId | string, args: object) { diff --git a/src/socket/targets/SubscribeToPlayers.ts b/src/socket/targets/SubscribeToPlayers.ts index d160ad0..83c00af 100644 --- a/src/socket/targets/SubscribeToPlayers.ts +++ b/src/socket/targets/SubscribeToPlayers.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/socket/targets/targetbase.ts b/src/socket/targets/targetbase.ts index 74c350e..69133da 100644 --- a/src/socket/targets/targetbase.ts +++ b/src/socket/targets/targetbase.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/socket/types.ts b/src/socket/types.ts index 20e2429..1e52cd8 100644 --- a/src/socket/types.ts +++ b/src/socket/types.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/types/express.ts b/src/types/express.ts index d2f4548..0ae161b 100644 --- a/src/types/express.ts +++ b/src/types/express.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify diff --git a/src/types/http.ts b/src/types/http.ts index 0e32514..5388628 100644 --- a/src/types/http.ts +++ b/src/types/http.ts @@ -1,5 +1,5 @@ /* Galvanic Corrosion - Rec Room custom server for communities. - + Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify