This repository has been archived on 2026-03-19. You can view files and clone it, but cannot push or open issues or pull requests.
Files
galvanic-corrosion/src/data/live/base.ts
zombieb 5c69269b70
Some checks failed
Galvanic Corrosion Cross-Compile / build (push) Failing after 38s
Frostbite is gone????
* Rewrite rooms backend, "RoomFactory" and "SubroomFactory"
    - Used for modifying and fetching rooms
* Progression and reputation bulk endpoints
* Announcement endpoint temp
* OOBE is now the only initial pref key
    - Will be removed in the future when cohortnux is implemented
* Misc minor fixes and clarifications
* Simplified namegen dictionary
    - The previous one was generated with ChatGPT, hence the duplicated strings. I googled "random username generator" and borrowed a random result's generation dictionary.
* QuickPlay support with "initialRoom" in config (untested)
2025-04-15 21:15:15 -04:00

153 lines
6.5 KiB
TypeScript

/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
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 <https://www.gnu.org/licenses/>. */
import Rooms from "../content/rooms.ts";
import { RoomDataTypes } from "../content/rooms/DataTypes.ts";
import { Profile } from "../profiles.ts";
import Instances from "./instances.ts";
import { MatchmakingErrorCode, RoomInstance } from "./types.ts";
const loginLocks: Map<number, string> = new Map();
interface MatchmakingOptions {
roomName: string,
subRoomName?: string,
private?: boolean,
instanceId?: number,
profile: Profile
}
interface MatchmakingResponse {
errorCode: MatchmakingErrorCode,
roomInstance?: RoomInstance
}
class MatchmakingBase {
createLoginLock(prof: Profile, lock: string) {
if (loginLocks.has(prof.getId())) return;
else loginLocks.set(prof.getId(), lock);
}
lockMatches(prof: Profile, lock: string) {
const checkLock = loginLocks.get(prof.getId());
if (checkLock) return checkLock == lock;
else return null;
}
deleteLoginLock(prof: Profile) {
loginLocks.delete(prof.getId());
}
async matchmake(options: MatchmakingOptions): Promise<MatchmakingResponse> {
if (options.instanceId) {
const instance = Instances.getInstance(options.instanceId);
if (instance) {
if (Instances.playerIsInInstance(options.profile, instance)) return { errorCode: MatchmakingErrorCode.AlreadyInTargetInstance };
else {
if (instance.isFull) return { errorCode: MatchmakingErrorCode.InsufficientSpace }
else if (instance.isPrivate) return { errorCode: MatchmakingErrorCode.RoomInstanceIsPrivate };
await Instances.setPlayerInstance(options.profile, instance);
Instances.clearAllRoomEmptyInstances(instance.roomId);
return { errorCode: MatchmakingErrorCode.Success, roomInstance: instance };
}
} else return { errorCode: MatchmakingErrorCode.NoSuchGame }
} 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 == RoomDataTypes.RoomAccessibility.Private && targetRoom.Room.CreatorPlayerId !== options.profile.getId())
return { errorCode: MatchmakingErrorCode.RoomIsPrivate };
if (targetRoom.Room.State !== RoomDataTypes.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);
// 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 = await Instances.createInstance({
Room: targetRoom,
SceneIndex: Math.floor(Math.random() * matchmakeableSubrooms.length),
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 };
}
}
}
}
const Matchmaking = new MatchmakingBase();
export default Matchmaking;