mirror of
https://gitgud.io/BondageProjects/Bondage-College.git
synced 2026-04-28 04:19:50 +00:00
2050 lines
63 KiB
JavaScript
2050 lines
63 KiB
JavaScript
"use strict";
|
|
|
|
/**
|
|
* @file This file handles the chat lobby search & filter screen
|
|
*/
|
|
|
|
/** Background image */
|
|
var ChatSearchBackground = "Introduction";
|
|
/**
|
|
* The list of tags allowed as backgrounds for the room edit screens
|
|
* @type {BackgroundTag[]}
|
|
*/
|
|
var ChatSearchBackgroundTagList = [];
|
|
/** @type {ChatRoomSearchResult[]} */
|
|
var ChatSearchResult = [];
|
|
/** @type {ChatRoomSearchResult[]} */
|
|
var ChatSearchHiddenResult = [];
|
|
/**
|
|
* @type {never}
|
|
* @deprecated Use {@link ServerRoomSearch()}
|
|
*/
|
|
var ChatSearchLastSearchDataJSON = /** @type {never} */ (null);
|
|
var ChatSearchLastQuerySearchTime = 0;
|
|
var ChatSearchLastQueryJoin = "";
|
|
var ChatSearchLastQueryJoinTime = 0;
|
|
var ChatSearchResultOffset = 0;
|
|
/** The room grid's left offset */
|
|
var ChatSearchPageX = 25;
|
|
/** The room grid's top offset */
|
|
var ChatSearchPageY = 135;
|
|
var ChatSearchRoomsPerRow = 3;
|
|
var ChatSearchRoomsPerColumn = 8;
|
|
/**
|
|
* Layout parameters for the room grid
|
|
* @type {CommonGenerateGridParameters}
|
|
*/
|
|
var ChatSearchListParams = {
|
|
x: ChatSearchPageX,
|
|
y: ChatSearchPageY,
|
|
width: MainCanvasWidth - 2 * ChatSearchPageX,
|
|
height: (20 + 85) * ChatSearchRoomsPerColumn,
|
|
itemWidth: 630,
|
|
itemHeight: 85,
|
|
minMarginY: 24,
|
|
};
|
|
/** Pre-calculated. Must be updated if you change the grid parameters */
|
|
var ChatSearchRoomsPerPage = ChatSearchRoomsPerRow * ChatSearchRoomsPerColumn;
|
|
/** @type {ScreenSpecifier} */
|
|
var ChatSearchReturnScreen = ["Room", "MainHall"];
|
|
/** @type {null | Item[]} */
|
|
var ChatSearchSafewordAppearance = null;
|
|
/** @type {null | Partial<Record<AssetPoseCategory, AssetPoseName>>} */
|
|
var ChatSearchSafewordPose = null;
|
|
/** @type {null | Partial<Record<AssetPoseCategory, AssetPoseName>>} */
|
|
var ChatSearchPreviousActivePose = null;
|
|
/** @type {number[]} */
|
|
var ChatSearchTempHiddenRooms = [];
|
|
/** @type {"" | "Filter"} */
|
|
var ChatSearchMode = "";
|
|
var ChatSearchGhostPlayerOnClickActive = false;
|
|
var ChatSearchShowHiddenRoomsActive = false;
|
|
/** @type {null | { Index: number, RoomLabel: string, MemberLabel: string, WordsLabel: string }} */
|
|
var ChatSearchFilterUnhideConfirm = null;
|
|
var ChatSearchRejoinIncrement = 1;
|
|
/**
|
|
* @deprecated
|
|
* @type {never}
|
|
*/
|
|
var ChatSearchReturnToScreen;
|
|
/** @type {string} */
|
|
var ChatSearchQueryString = "";
|
|
/** @type {"" | ServerChatRoomLanguage} */
|
|
var ChatSearchLanguage = "";
|
|
/** @type {never} */
|
|
var ChatSearchLanguageTemp;
|
|
/** @type {ServerChatRoomGame} */
|
|
var ChatSearchGame = "";
|
|
/** @type {ServerChatRoomSpace | null} */
|
|
var ChatSearchSpace = null;
|
|
var ChatSearchFilterTermsTemp = "";
|
|
var ChatSearchCurrentRoomSpaceIndex = 0;
|
|
|
|
/** @type {HTMLDivElement | null} */
|
|
var ChatSearchRoomHeader;
|
|
/** @type {HTMLDivElement | null} */
|
|
var ChatSearchRoomGrid;
|
|
/** @type {HTMLFieldSetElement | null} */
|
|
var ChatSearchSearchMenu;
|
|
/** @type {HTMLDivElement | null} */
|
|
var ChatSearchPageCountElement;
|
|
/** @type {HTMLDialogElement | null} */
|
|
var ChatSearchDialogElement;
|
|
/** @type {HTMLButtonElement | null} */
|
|
var ChatSearchSearchMenuButton;
|
|
/** @type {HTMLDivElement | null} */
|
|
var ChatSearchSearchBodyElement;
|
|
/** @type {HTMLDialogElement | null} */
|
|
var ChatSearchFilterUnhideConfirmElement;
|
|
/** @type {HTMLDialogElement | null} */
|
|
var ChatSearchFilterHelpScreenElement;
|
|
|
|
/**
|
|
* Starts the chatroom selection screen.
|
|
* @param {ServerChatRoomSpace} space - Name of the chatroom space
|
|
* @param {ScreenSpecifier | undefined} returnScreen - Screen to go back to when exiting leaving the lobby.
|
|
* @param {ChatSearchLobbyOptions} [options]
|
|
* @returns {Promise<void>} - Nothing.
|
|
*/
|
|
async function ChatSearchStart(space, returnScreen, options) {
|
|
const validSpaces = /** @type {ServerChatRoomSpace[]} */ (["X", "", "M", "Asylum"]);
|
|
if (!validSpaces.includes(space)) {
|
|
console.error(`invalid space ${space}`);
|
|
return;
|
|
}
|
|
if (!returnScreen) {
|
|
console.error(`invalid return screen ${returnScreen}`);
|
|
return;
|
|
}
|
|
ChatSearchSpace = space;
|
|
ChatRoomSpace = space;
|
|
ChatSearchGame = options?.Game ?? "";
|
|
ChatRoomGame = ChatSearchGame;
|
|
ChatSearchReturnScreen = returnScreen;
|
|
ChatSearchBackground = options?.Background ?? "Introduction";
|
|
ChatSearchBackgroundTagList = options?.BackgroundTagList ?? BackgroundsTagList;
|
|
ChatSearchQueryString = "";
|
|
const [module, screen] = CommonGetScreen();
|
|
if (module === "Online" && screen === "ChatSearch") {
|
|
// Already on screen
|
|
return Promise.resolve();
|
|
}
|
|
return CommonSetScreen("Online", "ChatSearch");
|
|
}
|
|
|
|
/**
|
|
* Loads the chat search screen properties, creates the inputs and loads up the first 24 rooms.
|
|
* @type {ScreenLoadHandler}
|
|
*/
|
|
async function ChatSearchLoad() {
|
|
ChatRoomCustomizationClear();
|
|
ChatRoomActivateView(ChatRoomCharacterViewName);
|
|
ChatRoomMapViewEditMode = "";
|
|
ChatRoomMapViewEditBackup = [];
|
|
delete Player.MapData;
|
|
CurrentDarkFactor = 0.5;
|
|
if (ChatSearchSafewordAppearance == null) {
|
|
ChatSearchSafewordAppearance = Player.Appearance.slice(0);
|
|
ChatSearchSafewordPose = Player.ActivePoseMapping;
|
|
}
|
|
AsylumGGTSIntroDone = false;
|
|
AsylumGGTSTask = null;
|
|
AsylumGGTSPreviousPose = { ...Player.PoseMapping };
|
|
Player.ArousalSettings.OrgasmCount = 0;
|
|
ChatSearchLanguage = Player.ChatSearchSettings.Language;
|
|
|
|
ChatSearchPageCountElement = ElementCreate({
|
|
tag: "div",
|
|
attributes: {
|
|
id: "chat-search-page-count",
|
|
},
|
|
children: [
|
|
`0 / 0`,
|
|
],
|
|
});
|
|
const minRoomSizeInput = ElementCreate({
|
|
tag: "input",
|
|
attributes: {
|
|
id: "chat-search-search-menu-room-size-min",
|
|
type: "number",
|
|
inputmode: "numeric",
|
|
value: Player.ChatSearchSettings.RoomMinSize,
|
|
min: 2,
|
|
max: 20,
|
|
step: 1,
|
|
required: true,
|
|
},
|
|
classList: ["chat-search-room-size-input"],
|
|
eventListeners: {
|
|
change: () => {
|
|
const min = parseInt(minRoomSizeInput.value, 10);
|
|
if (!minRoomSizeInput.validity.valid) return;
|
|
|
|
Player.ChatSearchSettings.RoomMinSize = min;
|
|
minRoomSizeInput.valueAsNumber = min;
|
|
maxRoomSizeInput.min = String(min);
|
|
ChatSearchUpdateSearchSettings();
|
|
}
|
|
}
|
|
});
|
|
const maxRoomSizeInput = ElementCreate({
|
|
tag: "input",
|
|
attributes: {
|
|
id: "chat-search-search-menu-room-size-max",
|
|
type: "number",
|
|
inputmode: "numeric",
|
|
value: Player.ChatSearchSettings.RoomMaxSize,
|
|
min: 1,
|
|
max: 20,
|
|
step: 1,
|
|
required: true,
|
|
},
|
|
classList: ["chat-search-room-size-input"],
|
|
eventListeners: {
|
|
change: () => {
|
|
let max = parseInt(maxRoomSizeInput.value, 10);
|
|
if (!maxRoomSizeInput.validity.valid) return;
|
|
|
|
Player.ChatSearchSettings.RoomMaxSize = max;
|
|
maxRoomSizeInput.valueAsNumber = max;
|
|
minRoomSizeInput.max = String(max);
|
|
ChatSearchUpdateSearchSettings();
|
|
}
|
|
}
|
|
});
|
|
const space = ChatSearchGetSpace();
|
|
if (space && !Player.ChatSearchSettings.Space.includes(space)) {
|
|
Player.ChatSearchSettings.Space = space;
|
|
}
|
|
const searchInput = ElementCreateSearchInput(
|
|
"InputSearch",
|
|
() => {
|
|
const rooms = ChatSearchShowHiddenRoomsActive ? ChatSearchHiddenResult : ChatSearchResult;
|
|
return rooms.map(i => i.DisplayName).sort();
|
|
},
|
|
{
|
|
maxLength: 200,
|
|
value: ChatSearchQueryString,
|
|
placeholder: TextGet("SearchRoom"),
|
|
onInput: function (ev) {
|
|
clearButton.hidden = this.value.length === 0;
|
|
ChatSearchQueryString = this.value;
|
|
},
|
|
onKeydown: ChatSearchKeyDownListener,
|
|
},
|
|
{ search: { classList: ["chat-search-input-search-box"] } },
|
|
);
|
|
|
|
const filterInput = ElementCreateInput("InputFilter", "text", Player.ChatSearchSettings.FilterTerms);
|
|
ElementSetAttribute("InputFilter", "placeholder", TextGet("FilterExcludeTerms"));
|
|
filterInput.toggleAttribute("hidden", true);
|
|
filterInput.classList.add("chat-search-input-filter-box");
|
|
filterInput.addEventListener("input", function () {
|
|
clearButton.hidden = this.value.length === 0;
|
|
Player.ChatSearchSettings.FilterTerms = this.value;
|
|
});
|
|
filterInput.addEventListener("keydown", (event) => {
|
|
if (event.repeat || !CommonKey.IsPressed(event, "Enter")) return;
|
|
ChatSearchSaveFilterTerms();
|
|
});
|
|
|
|
const clearButton = ElementButton.Create("chat-search-clear-input-search", function (ev) {
|
|
switch (ChatSearchMode) {
|
|
case "":
|
|
ElementValue("InputSearch", "");
|
|
ChatSearchQuery("");
|
|
break;
|
|
case "Filter":
|
|
ElementValue("InputFilter", "");
|
|
break;
|
|
}
|
|
}, {
|
|
image: "Icons/cross.svg",
|
|
noStyling: true,
|
|
}, {
|
|
button: {
|
|
classList: ["chat-search-room-button"],
|
|
attributes: { hidden: !ChatSearchQueryString.length },
|
|
},
|
|
});
|
|
ChatSearchSearchMenuButton = ElementButton.Create(
|
|
"chat-search-room-open-search-menu",
|
|
function () {
|
|
const img = this.querySelector('img');
|
|
if (!img) return;
|
|
const open = this.getAttribute("aria-expanded") === "true";
|
|
if (open) {
|
|
this.setAttribute("aria-expanded", "false");
|
|
img.src = `Icons/CaretUp.svg`;
|
|
} else {
|
|
this.setAttribute("aria-expanded", "true");
|
|
img.src = `Icons/CaretDown.svg`;
|
|
if (document.activeElement?.id !== "InputSearch") {
|
|
ElementFocus("InputSearch");
|
|
}
|
|
}
|
|
ElementUnpackIDs.fromAttribute(this, "aria-controls").forEach(el => el.hidden = open);
|
|
},
|
|
{
|
|
tooltip: TextGet("SearchMenuButton"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/CaretUp.svg",
|
|
}, {
|
|
button: {
|
|
classList: ["chat-search-room-button"], attributes: {
|
|
"aria-expanded": "false",
|
|
"aria-controls": "chat-search-search-menu"
|
|
}
|
|
}
|
|
});
|
|
ChatSearchRoomHeader = ElementCreate(
|
|
{
|
|
tag: "div",
|
|
attributes: { id: "chat-search-room-header" },
|
|
children: [
|
|
{
|
|
tag: "div",
|
|
attributes: {
|
|
id: "chat-search-room-search-section",
|
|
},
|
|
classList: ["chat-search-room-header-section"],
|
|
children: [
|
|
{
|
|
tag: "div",
|
|
attributes: { id: "chat-search-input-search" },
|
|
children: [
|
|
searchInput,
|
|
filterInput,
|
|
clearButton,
|
|
],
|
|
},
|
|
ElementButton.Create(
|
|
"chat-search-room-search-button",
|
|
() => {
|
|
if (ChatSearchSearchMenuButton?.getAttribute("aria-expanded") === "true") {
|
|
ChatSearchSearchMenuButton.click();
|
|
}
|
|
ChatSearchQuery(ChatSearchQueryString);
|
|
},
|
|
{
|
|
tooltip: TextGet("Search"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/Search.png",
|
|
},
|
|
{
|
|
button: { classList: ["chat-search-room-button"] },
|
|
},
|
|
),
|
|
ChatSearchSearchMenuButton,
|
|
ElementButton.Create(
|
|
"chat-search-room-prev-page",
|
|
() => ChatSearchSetPageRelative(-1),
|
|
{
|
|
tooltip: TextGet("Prev"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/Prev.png",
|
|
},
|
|
{
|
|
button: {
|
|
classList: ["chat-search-room-button"],
|
|
attributes: { "aria-controls": "chat-search-room-grid" },
|
|
},
|
|
},
|
|
),
|
|
ChatSearchPageCountElement,
|
|
ElementButton.Create(
|
|
"chat-search-room-next-page",
|
|
() => ChatSearchSetPageRelative(1),
|
|
{
|
|
tooltip: TextGet("Next"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/Next.png",
|
|
},
|
|
{
|
|
button: {
|
|
classList: ["chat-search-room-button"],
|
|
attributes: { "aria-controls": "chat-search-room-grid" },
|
|
},
|
|
},
|
|
),
|
|
ElementButton.Create("chat-search-hide-rooms", function () {
|
|
ChatSearchToggleSearchMode();
|
|
}, {
|
|
tooltip: TextGet(ChatSearchMode != "Filter" ? "FilterMode" : "NormalMode"),
|
|
tooltipPosition: "bottom",
|
|
role: "checkbox",
|
|
image: "Icons/Private.png",
|
|
}, {
|
|
button: {
|
|
classList: ["chat-search-room-button"],
|
|
dataAttributes: {
|
|
mode: ChatSearchMode == "Filter" ? "FilterMode" : "NormalMode",
|
|
},
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
tag: "div",
|
|
attributes: {
|
|
id: "chat-search-room-filter-section",
|
|
role: "radiogroup",
|
|
"aria-required": "true",
|
|
hidden: true
|
|
},
|
|
classList: ["chat-search-room-header-section"],
|
|
children: [
|
|
ElementButton.Create("chat-search-temp-hide-button", function () {
|
|
ChatSearchGhostPlayerOnClickActive = false;
|
|
}, {
|
|
role: "radio",
|
|
tooltip: TextGet("TempHideOnClick"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/Trash.png",
|
|
}, {
|
|
button: {
|
|
classList: ["chat-search-room-button"],
|
|
attributes: {
|
|
"aria-checked": !ChatSearchGhostPlayerOnClickActive ? "true" : "false",
|
|
},
|
|
},
|
|
}),
|
|
ElementButton.Create("chat-search-ghost-list-button", function () {
|
|
ChatSearchGhostPlayerOnClickActive = true;
|
|
}, {
|
|
role: "radio",
|
|
tooltip: TextGet("GhostPlayerOnClick"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/GhostList.png",
|
|
}, {
|
|
button: {
|
|
classList: ["chat-search-room-button"],
|
|
attributes: {
|
|
"aria-checked": ChatSearchGhostPlayerOnClickActive ? "true" : "false",
|
|
},
|
|
},
|
|
}),
|
|
ElementButton.Create(
|
|
"chat-search-room-show-hidden-rooms-button",
|
|
() => ChatSearchToggleHiddenMode(),
|
|
{
|
|
role: "checkbox",
|
|
tooltip: TextGet("ShowHiddenRooms"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/InspectLock.png",
|
|
},
|
|
{
|
|
button: { classList: ["chat-search-room-button"] },
|
|
},
|
|
),
|
|
ElementButton.Create(
|
|
"chat-search-room-help-button",
|
|
() => {
|
|
if (ChatSearchFilterHelpScreenElement?.open) {
|
|
return ChatSearchFilterHelpScreenElement.close();
|
|
}
|
|
ChatSearchFilterHelpScreenElement?.showModal();
|
|
ElementPositionFixed(ChatSearchFilterHelpScreenElement, 25, 135, 1900, 800);
|
|
ChatSearchFilterHelpScreenElement?.addEventListener("keydown", (ev) => {
|
|
if (ev.key === "Escape") {
|
|
ChatSearchFilterHelpScreenElement?.close();
|
|
return;
|
|
}
|
|
}, { once: true });
|
|
ChatSearchFilterHelpScreenElement?.focus();
|
|
},
|
|
{
|
|
tooltip: TextGet("Help"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/Question.png",
|
|
},
|
|
{
|
|
button: { classList: ["chat-search-room-button"] },
|
|
},
|
|
),
|
|
],
|
|
},
|
|
{
|
|
tag: "div",
|
|
attributes: { id: "chat-search-room-navigation-section" },
|
|
classList: ["chat-search-room-header-section"],
|
|
children: [
|
|
ElementButton.Create(
|
|
"chat-search-room-create-room-button",
|
|
() => ChatAdminShowCreate(),
|
|
{
|
|
tooltip: TextGet("CreateRoom"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/Plus.png",
|
|
},
|
|
{
|
|
button: { classList: ["chat-search-room-button"] },
|
|
},
|
|
),
|
|
ElementButton.Create(
|
|
"chat-search-room-friend-list-button",
|
|
() => {
|
|
FriendListReturn = { Screen: CurrentScreen, Module: CurrentModule };
|
|
CommonSetScreen("Character", "FriendList");
|
|
},
|
|
{
|
|
tooltip: TextGet("FriendList"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/FriendList.png",
|
|
},
|
|
{
|
|
button: { classList: ["chat-search-room-button"] },
|
|
},
|
|
),
|
|
ElementButton.Create(
|
|
"chat-search-room-exit-button",
|
|
() => ChatSearchExit(false),
|
|
{
|
|
tooltip: TextGet("Exit"),
|
|
tooltipPosition: "bottom",
|
|
image: "Icons/Exit.png",
|
|
},
|
|
{
|
|
button: { classList: ["chat-search-room-button"] },
|
|
},
|
|
),
|
|
],
|
|
},
|
|
],
|
|
parent: document.body,
|
|
}
|
|
);
|
|
ChatSearchSearchBodyElement = ElementCreateDiv("chat-search-body");
|
|
ChatSearchSearchMenu = ElementCreate(
|
|
{
|
|
tag: "fieldset",
|
|
attributes: {
|
|
id: "chat-search-search-menu",
|
|
hidden: true,
|
|
},
|
|
children: [
|
|
// Room type
|
|
{
|
|
tag: "div",
|
|
attributes: {
|
|
id: "chat-search-search-menu-room-type",
|
|
},
|
|
classList: ["chat-search-search-menu-grid-item"],
|
|
children: [
|
|
ElementCreateSettingsLabel(TextGet("RoomType")),
|
|
ElementCreateRadioButtonGroup(
|
|
"chat-search-search-menu-room-type-radio-group",
|
|
/** @type {(this: HTMLButtonElement, ev: Event, key: "None" | ChatRoomMapType) => void} */
|
|
(ev, key) => {
|
|
if (key === "None") {
|
|
Player.ChatSearchSettings.MapTypes = "";
|
|
}
|
|
else {
|
|
Player.ChatSearchSettings.MapTypes = key;
|
|
}
|
|
ChatSearchUpdateSearchSettings();
|
|
},
|
|
!Player.ChatSearchSettings.MapTypes ? "None" : Player.ChatSearchSettings.MapTypes,
|
|
[
|
|
{
|
|
options: { image: "Icons/cross.svg", tooltip: TextGet("AllRooms"), },
|
|
htmlOptions: { button: { attributes: { value: "None" } } }
|
|
},
|
|
{
|
|
options: { image: "Icons/RoomTypeNormal.svg", tooltip: TextGet("NormalRooms") },
|
|
htmlOptions: { button: { attributes: { value: "Never" } } }
|
|
},
|
|
{
|
|
options: { image: "Icons/RoomTypeHybrid.svg", tooltip: TextGet("HybridRooms") },
|
|
htmlOptions: { button: { attributes: { value: "Hybrid" } } }
|
|
},
|
|
{
|
|
options: { image: "Icons/RoomTypeMap.svg", tooltip: TextGet("MapRooms") },
|
|
htmlOptions: { button: { attributes: { value: "Always" } } }
|
|
},
|
|
]),
|
|
],
|
|
},
|
|
// Lobby
|
|
{
|
|
tag: "div",
|
|
attributes: {
|
|
id: "chat-search-search-menu-room-lobby",
|
|
},
|
|
classList: ["chat-search-search-menu-grid-item"],
|
|
children: [
|
|
ElementCreateSettingsLabel(TextGet("Lobby")),
|
|
ElementCreateRadioButtonGroup(
|
|
"chat-search-search-menu-room-lobby-radio-group",
|
|
(ev, key) => {
|
|
Player.ChatSearchSettings.Space = ChatSearchSpace = key;
|
|
ChatSearchUpdateSearchSettings();
|
|
},
|
|
ChatSearchGetSpace() ?? "",
|
|
[
|
|
Player.GetGenders().includes("F") && {
|
|
options: { image: "Icons/Female.svg", tooltip: TextGet("Female") },
|
|
htmlOptions: { button: { attributes: { value: "" } } }
|
|
},
|
|
{
|
|
options: { image: "Icons/Gender.svg", tooltip: TextGet("Mixed") },
|
|
htmlOptions: { button: { attributes: { value: "X" } } }
|
|
},
|
|
Player.GetGenders().includes("M") && {
|
|
options: { image: "Icons/Male.svg", tooltip: TextGet("Male") },
|
|
htmlOptions: { button: { attributes: { value: "M" } } }
|
|
},
|
|
ChatSearchGetSpace() === "Asylum" && {
|
|
options: { image: "Icons/Asylum.png", tooltip: TextGet("Asylum") },
|
|
htmlOptions: { button: { attributes: { value: "Asylum" } } }
|
|
},
|
|
].filter(Boolean),
|
|
),
|
|
],
|
|
},
|
|
// Full rooms
|
|
{
|
|
tag: "div",
|
|
attributes: {
|
|
id: "chat-search-search-menu-full-rooms",
|
|
},
|
|
dataAttributes: {
|
|
checkbox: true,
|
|
},
|
|
classList: ["chat-search-search-menu-grid-item"],
|
|
children: [
|
|
ElementCreateSettingsLabel(TextGet("ShowFullRooms"), "left"),
|
|
ElementCheckbox.Create("chat-search-search-menu-full-rooms-input", function (ev) {
|
|
Player.ChatSearchSettings.FullRooms = this.checked;
|
|
ChatSearchUpdateSearchSettings();
|
|
}, {
|
|
checked: Player.ChatSearchSettings.FullRooms,
|
|
})
|
|
],
|
|
},
|
|
// Locked rooms
|
|
{
|
|
tag: "div",
|
|
attributes: {
|
|
id: "chat-search-search-menu-locked-rooms",
|
|
},
|
|
dataAttributes: {
|
|
checkbox: true,
|
|
},
|
|
classList: ["chat-search-search-menu-grid-item"],
|
|
children: [
|
|
ElementCreateSettingsLabel(TextGet("ShowLockedRooms"), "left"),
|
|
ElementCheckbox.Create("chat-search-search-menu-locked-rooms-input", function (ev) {
|
|
Player.ChatSearchSettings.ShowLocked = this.checked;
|
|
ChatSearchUpdateSearchSettings();
|
|
}, {
|
|
checked: Player.ChatSearchSettings.ShowLocked,
|
|
})
|
|
],
|
|
},
|
|
// Search description
|
|
{
|
|
tag: "div",
|
|
attributes: {
|
|
id: "chat-search-search-menu-search-description",
|
|
},
|
|
dataAttributes: {
|
|
checkbox: true,
|
|
},
|
|
classList: ["chat-search-search-menu-grid-item"],
|
|
children: [
|
|
ElementCreateSettingsLabel(TextGet("SearchDescription"), "left"),
|
|
ElementCheckbox.Create("chat-search-search-menu-search-description-input", function () {
|
|
Player.ChatSearchSettings.SearchDescriptions = this.checked;
|
|
ChatSearchUpdateSearchSettings();
|
|
}, {
|
|
checked: Player.ChatSearchSettings.SearchDescriptions,
|
|
}),
|
|
],
|
|
},
|
|
// Game
|
|
{
|
|
tag: "div",
|
|
attributes: {
|
|
id: "chat-search-search-menu-room-game",
|
|
},
|
|
classList: ["chat-search-search-menu-grid-item"],
|
|
children: [
|
|
ElementCreateSettingsLabel(TextGet("GameLabel")),
|
|
ElementCreateDropdown(
|
|
"chat-search-search-menu-room-game-dropdown",
|
|
[
|
|
...[...ChatAdminGameList, "Prison"].map(game => (
|
|
{ children: [TextGet(`Game${game === "" ? "AllRooms" : game}`)], attributes: { value: game, selected: ChatSearchGame == game } }
|
|
)),
|
|
],
|
|
function (ev) {
|
|
ChatSearchGame = Player.ChatSearchSettings.Game = /** @type {ServerChatRoomGame} */ (this.value);
|
|
ChatSearchUpdateSearchSettings();
|
|
},
|
|
null,
|
|
{
|
|
select: {
|
|
attributes: {
|
|
value: ChatSearchGame,
|
|
}
|
|
}
|
|
}
|
|
),
|
|
],
|
|
},
|
|
// Language
|
|
{
|
|
tag: "div",
|
|
attributes: { id: "chat-search-search-menu-room-language" },
|
|
classList: ["chat-search-search-menu-grid-item"],
|
|
children: [
|
|
ElementCreateSettingsLabel("Language"),
|
|
ElementCreateDropdown(
|
|
"chat-search-search-menu-room-language-dropdown",
|
|
[
|
|
.../** @type {ServerChatRoomLanguage[]} */([...ServerChatRoomSupportedLanguages, ""]).map(lang => (
|
|
{ children: [ChatSearchGetLanguageName(lang)], attributes: { value: lang, selected: Player.ChatSearchSettings.Language == lang } }
|
|
)),
|
|
],
|
|
function (ev) {
|
|
Player.ChatSearchSettings.Language = ChatSearchLanguage = /** @type {ServerChatRoomLanguage} */ (this.value);
|
|
ChatSearchUpdateSearchSettings();
|
|
},
|
|
{ required: true }
|
|
),
|
|
],
|
|
},
|
|
// Room size
|
|
{
|
|
tag: "div",
|
|
attributes: { id: "chat-search-search-menu-room-size" },
|
|
classList: ["chat-search-search-menu-grid-item"],
|
|
children: [
|
|
ElementCreateSettingsLabel("Room Size"),
|
|
{
|
|
tag: "div",
|
|
attributes: { id: "chat-search-search-menu-room-size-grid" },
|
|
children: [
|
|
minRoomSizeInput,
|
|
"/",
|
|
maxRoomSizeInput,
|
|
{
|
|
tag: "span",
|
|
classList: ["chat-search-search-menu-room-size-label"],
|
|
children: ["min"],
|
|
attributes: { "for": "chat-search-search-menu-room-size-min" }
|
|
},
|
|
{
|
|
tag: "span",
|
|
classList: ["chat-search-search-menu-room-size-label"],
|
|
children: ["max"],
|
|
style: { "grid-column": "3/3" },
|
|
attributes: { "for": "chat-search-search-menu-room-size-max" },
|
|
},
|
|
],
|
|
}
|
|
]
|
|
},
|
|
ElementButton.Create("chat-search-search-menu-search-button", function () {
|
|
const elements = /** @type {HTMLCollectionOf<Element & { validity: ValidityState, reportValidity(): boolean }>} */(ChatSearchSearchMenu?.elements) ?? [];
|
|
for (const el of elements) {
|
|
if (!el.validity.valid) {
|
|
el.reportValidity();
|
|
return;
|
|
}
|
|
}
|
|
ChatSearchQuery(ChatSearchQueryString);
|
|
}, null, {
|
|
button: {
|
|
classList: ["chat-search-search-menu-search-button"],
|
|
children: [
|
|
TextGet("Search"),
|
|
],
|
|
},
|
|
}),
|
|
],
|
|
parent: document.body,
|
|
});
|
|
|
|
if (ChatSearchGetSpace() === "Asylum") {
|
|
ElementWrap("chat-search-search-menu-room-lobby-radio-group")?.toggleAttribute("disabled", true);
|
|
}
|
|
|
|
ChatSearchRoomGrid = ElementCreateDiv("chat-search-room-grid");
|
|
ChatSearchFilterHelpScreenElement = ElementCreate({
|
|
tag: "dialog",
|
|
attributes: {
|
|
id: "chat-search-filter-help-screen",
|
|
},
|
|
children: [
|
|
{
|
|
tag: "ul",
|
|
classList: ["chat-search-filter-help-screen-content"],
|
|
children: CommonRange(1, 6).map(n => ({
|
|
tag: "li",
|
|
children: [TextGet(`HelpText${n}`)],
|
|
})),
|
|
},
|
|
ElementButton.Create("chat-search-filter-help-screen-close", function () {
|
|
ChatSearchFilterHelpScreenElement?.close();
|
|
}, null, {
|
|
button: {
|
|
classList: ["chat-search-filter-help-screen-close-button"],
|
|
children: [
|
|
TextGet("CloseHelp"),
|
|
],
|
|
},
|
|
}),
|
|
],
|
|
parent: document.body,
|
|
});
|
|
ChatSearchQuery(ChatSearchQueryString);
|
|
ChatRoomNotificationReset();
|
|
ChatSearchRejoinIncrement = 1;
|
|
TextPrefetch("Character", "FriendList");
|
|
TextPrefetch("Online", "ChatAdmin");
|
|
TextPrefetch("Online", "ChatRoom");
|
|
}
|
|
|
|
/** @type {ScreenResizeHandler} */
|
|
function ChatSearchResize() {
|
|
ElementPositionFixed(ChatSearchRoomGrid, ChatSearchListParams.x, ChatSearchListParams.y, ChatSearchListParams.width, ChatSearchListParams.height);
|
|
ElementPositionFixed(ChatSearchRoomHeader, 25, 25, 1950, 90);
|
|
ElementPositionFixed(ChatSearchSearchMenu, 25, 115, 810, 480);
|
|
ElementPositionFixed(ChatSearchSearchBodyElement, 25, 115, 810, 480);
|
|
if (document.getElementById("chat-search-room-page")) ElementPositionFixed("chat-search-room-page", (2000 - 1200) / 2, (1000 - 700) / 2, 1200, 700);
|
|
document.querySelectorAll('.chat-search-room-title').forEach(ElementFitText);
|
|
ElementPositionFixed(ChatSearchFilterHelpScreenElement, 25, 135, 1900, 800);
|
|
if (ChatSearchFilterUnhideConfirmElement) {
|
|
ElementPositionFixed(ChatSearchFilterUnhideConfirmElement, 50, 135, 1900, 800);
|
|
}
|
|
}
|
|
|
|
/** @type {ScreenUnloadHandler} */
|
|
function ChatSearchUnload() {
|
|
ElementRemove(ChatSearchRoomHeader);
|
|
ChatSearchRoomHeader = null;
|
|
ElementRemove(ChatSearchSearchMenu);
|
|
ChatSearchSearchMenu = null;
|
|
ElementRemove(ChatSearchRoomGrid);
|
|
ChatSearchRoomGrid = null;
|
|
ElementRemove(ChatSearchSearchBodyElement);
|
|
ChatSearchSearchBodyElement = null;
|
|
ElementRemove(ChatSearchPageCountElement);
|
|
ChatSearchPageCountElement = null;
|
|
ElementRemove(ChatSearchDialogElement);
|
|
ChatSearchDialogElement = null;
|
|
ElementRemove(ChatSearchSearchMenuButton);
|
|
ChatSearchSearchMenuButton = null;
|
|
ElementRemove(ChatSearchFilterHelpScreenElement);
|
|
ChatSearchFilterHelpScreenElement = null;
|
|
ChatRoomStimulationMessage("Walk");
|
|
}
|
|
|
|
/**
|
|
* When the chat Search screen runs, draws the screen
|
|
* @type {ScreenRunHandler}
|
|
*/
|
|
function ChatSearchRun() {
|
|
|
|
// Calls the other screens that could trigger
|
|
KidnapLeagueResetOnlineBountyProgress();
|
|
PandoraPenitentiaryCreate();
|
|
|
|
// Hidden rooms view only shows a back button
|
|
if (ChatSearchShowHiddenRoomsActive) {
|
|
DrawButton(1885, 25, 90, 90, "", "White", "Icons/DialogNormalMode.png", TextGet("NormalFilterMode"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles the click events on the chat search screen. Is called from CommonClick()
|
|
* @type {MouseEventListener}
|
|
*/
|
|
function ChatSearchClick() {
|
|
// Handle back button for hidden rooms view
|
|
if (ChatSearchShowHiddenRoomsActive) {
|
|
if (MouseIn(1885, 25, 90, 90)) ChatSearchToggleHiddenMode();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the rooms to be displayed
|
|
* @returns {ChatRoomSearchResult[]}
|
|
*/
|
|
function ChatSearchGetRooms() {
|
|
if (ChatSearchShowHiddenRoomsActive) {
|
|
return ChatSearchHiddenResult;
|
|
}
|
|
if (ChatSearchMode == "Filter") {
|
|
return [...ChatSearchResult, ...ChatSearchHiddenResult];
|
|
}
|
|
return ChatSearchResult.filter(room => !ChatSearchHiddenResult.includes(room));
|
|
}
|
|
/**
|
|
* Gets the pagination information for the current page.
|
|
* @returns {{currentPage: number, total: number}}
|
|
*/
|
|
function ChatSearchGetPagination() {
|
|
const rooms = ChatSearchGetRooms();
|
|
if (!rooms.length) {
|
|
// We have no rooms, hence no page
|
|
return { currentPage: 0, total: 0 };
|
|
}
|
|
let total = Math.ceil(rooms.length / ChatSearchRoomsPerPage);
|
|
let currentPage = ChatSearchResultOffset / ChatSearchRoomsPerPage + 1;
|
|
return { currentPage, total };
|
|
}
|
|
|
|
/**
|
|
* Sets the page based on the passed page number.
|
|
* @param {number} page
|
|
*/
|
|
function ChatSearchSetPage(page) {
|
|
const { total } = ChatSearchGetPagination();
|
|
page = total !== 0 ? CommonClamp(page, 1, total) : page;
|
|
ChatSearchResultOffset = Math.max(0, (page - 1) * ChatSearchRoomsPerPage);
|
|
if (ChatSearchPageCountElement) {
|
|
ChatSearchPageCountElement.textContent = `${page} / ${total}`;
|
|
}
|
|
ChatSearchGridUpdate(ChatSearchGetRooms().slice(ChatSearchResultOffset, ChatSearchResultOffset + ChatSearchRoomsPerPage));
|
|
}
|
|
|
|
/**
|
|
* Relatively sets the page based on the passed offsets.
|
|
* @param {number} offset
|
|
*/
|
|
function ChatSearchSetPageRelative(offset) {
|
|
const { currentPage, total } = ChatSearchGetPagination();
|
|
let page = (currentPage + offset) % (total + 1);
|
|
if (page === 0 && Math.sign(offset) === -1) page = total;
|
|
ChatSearchSetPage(page);
|
|
}
|
|
|
|
/**
|
|
* While in filter view, called when player clicks apply, presses enter, or returns to the normal view.
|
|
* Saves the "temp" options into their normal variables, and sends them to the server.
|
|
* Also refreshes the displayed rooms accordingly.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchSaveFilterTerms() {
|
|
let changed = false;
|
|
|
|
// Save Filter Terms option
|
|
if (Player.ChatSearchSettings.FilterTerms !== ChatSearchFilterTermsTemp) {
|
|
ChatSearchFilterTermsTemp = Player.ChatSearchSettings.FilterTerms;
|
|
ChatSearchUpdateSearchSettings();
|
|
changed = true;
|
|
|
|
// Re-apply the filter client side immediately - in case searching doesn't update fast enough
|
|
ChatSearchResult.unshift(...ChatSearchHiddenResult);
|
|
ChatSearchQuerySort();
|
|
ChatSearchApplyFilterTerms();
|
|
}
|
|
|
|
// If either/both options changed, refresh the room list
|
|
if (changed)
|
|
ChatSearchQuery(ChatSearchQueryString);
|
|
}
|
|
|
|
/**
|
|
* Handles the key presses while in the chat search screen.
|
|
* @type {KeyboardEventListener}
|
|
*/
|
|
function ChatSearchKeyDown(event) {
|
|
/** @type {HTMLInputElement | null} */
|
|
const searchInput = document.querySelector("input#InputSearch");
|
|
/** @type {HTMLInputElement | null} */
|
|
const filterInput = document.querySelector("input#InputFilter");
|
|
if (searchInput &&CommonKey.InputKeyDown(searchInput, event)) {
|
|
return true;
|
|
} else if (filterInput && CommonKey.InputKeyDown(filterInput, event)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** @type {ClipboardEventListener} */
|
|
function ChatSearchPaste(event) {
|
|
const searchInput = document.getElementById("InputSearch");
|
|
if (searchInput instanceof HTMLInputElement) {
|
|
CommonKey.InputPaste(searchInput, event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles the key presses while in the chat search screen.
|
|
* When the user presses enter, we launch the search query or save the temp options.
|
|
* @type {(this: HTMLInputElement, ev: KeyboardEvent) => void}
|
|
*/
|
|
function ChatSearchKeyDownListener(event) {
|
|
if (event.repeat || !CommonKey.IsPressed(event, "Enter")) return;
|
|
ChatSearchQuery(ChatSearchQueryString);
|
|
if (ChatSearchSearchMenuButton?.getAttribute("aria-expanded") === "true") {
|
|
ChatSearchSearchMenuButton.click();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles exiting from the chat search screen, removes the input.
|
|
* @type {ScreenExitHandler & { closeSubElements?: boolean }}
|
|
* @param {boolean} [exitScreen=false] - Whether to exit the screen or just menus within
|
|
*/
|
|
function ChatSearchExit(exitScreen = false) {
|
|
const interupt = ChatSearchBack();
|
|
if (!exitScreen && interupt) return;
|
|
ChatSearchMode = "";
|
|
ChatSearchShowHiddenRoomsActive = false;
|
|
ChatSearchFilterUnhideConfirm = null;
|
|
ChatSearchPreviousActivePose = { ...Player.ActivePoseMapping };
|
|
ChatRoomSetLastChatRoom(null);
|
|
ElementRemove("InputSearch");
|
|
CommonSetScreen(...ChatSearchReturnScreen);
|
|
DrawingGetTextSize.clearCache();
|
|
}
|
|
|
|
function ChatSearchBack() {
|
|
if (ChatSearchFilterHelpScreenElement?.open) {
|
|
ChatSearchFilterHelpScreenElement.close();
|
|
return true;
|
|
}
|
|
if (ChatSearchDialogElement?.open) {
|
|
ChatSearchDialogElement.close();
|
|
return true;
|
|
}
|
|
if (!ChatSearchSearchMenu?.hidden) {
|
|
ChatSearchSearchMenuButton?.click();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Draws the filter mode help screen: just text and a back button.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchFilterHelpDraw() {
|
|
DrawRect(50, 135, 1900, 800, "White");
|
|
DrawEmptyRect(50, 135, 1900, 800, "Black");
|
|
for (let i = 0; i < 7; i++)
|
|
DrawTextWrap(TextGet("HelpText" + (i + 1)), 70, 135 + i * 100, 1860, 70, "Black", undefined, 2);
|
|
}
|
|
|
|
/**
|
|
* Creates the filter mode unhide confirm screen: just text and confirm/cancel buttons.
|
|
* @param {string} roomLabel
|
|
* @param {string} memberLabel
|
|
* @param {string} wordsLabel
|
|
* @param {ChatRoomSearchResult} room
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchCreateFilterUnhideConfirm(roomLabel, memberLabel, wordsLabel, room) {
|
|
if (ChatSearchFilterUnhideConfirmElement) {
|
|
ElementRemove(ChatSearchFilterUnhideConfirmElement);
|
|
}
|
|
ChatSearchFilterUnhideConfirmElement = ElementCreate(
|
|
{
|
|
tag: "dialog",
|
|
attributes: {
|
|
id: "chat-search-filter-unhide-confirm",
|
|
},
|
|
children: [
|
|
{
|
|
tag: "div",
|
|
classList: ["chat-search-filter-unhide-confirm-container"],
|
|
children: [
|
|
{
|
|
tag: "p",
|
|
children: [TextGet("UnhideConfirmRoom").replace("{RoomLabel}", roomLabel)]
|
|
},
|
|
{
|
|
tag: "ul",
|
|
children: [
|
|
memberLabel != "" ? ({
|
|
tag: "li",
|
|
children: [TextGet("UnhideConfirmMember").replace("{MemberLabel}", memberLabel)]
|
|
}) : undefined,
|
|
wordsLabel != "" ? ({
|
|
tag: "li",
|
|
children: [TextGet("UnhideConfirmWords").replace("{WordsLabel}", wordsLabel)]
|
|
}) : undefined,
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
tag: "p",
|
|
classList: ["chat-search-filter-unhide-confirm-end"],
|
|
children: [TextGet("UnhideConfirmEnd")],
|
|
},
|
|
{
|
|
tag: "div",
|
|
classList: ["chat-search-filter-unhide-confirm-buttons"],
|
|
children: [
|
|
ElementButton.Create("chat-search-filter-unhide-confirm-cancel", function () {
|
|
if (ChatSearchFilterUnhideConfirmElement) {
|
|
ElementRemove(ChatSearchFilterUnhideConfirmElement);
|
|
ChatSearchFilterUnhideConfirmElement = null;
|
|
}
|
|
}, null, {
|
|
button: {
|
|
children: [
|
|
TextGet("UnhideCancel"),
|
|
],
|
|
},
|
|
},
|
|
),
|
|
ElementButton.Create("chat-search-filter-unhide-confirm-confirm", function () {
|
|
ChatSearchClickUnhideRoom(room, true);
|
|
if (ChatSearchFilterUnhideConfirmElement) {
|
|
ElementRemove(ChatSearchFilterUnhideConfirmElement);
|
|
ChatSearchFilterUnhideConfirmElement = null;
|
|
}
|
|
}, null, {
|
|
button: {
|
|
children: [
|
|
TextGet("UnhideConfirm"),
|
|
],
|
|
},
|
|
},
|
|
),
|
|
],
|
|
}
|
|
],
|
|
parent: document.body,
|
|
}
|
|
);
|
|
ElementPositionFixed(ChatSearchFilterUnhideConfirmElement, 50, 50, 1900, 800);
|
|
ChatSearchFilterUnhideConfirmElement.showModal();
|
|
ChatSearchFilterUnhideConfirmElement.focus();
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the translated language name for the given code.
|
|
* @param {ServerChatRoomLanguage} languageCode
|
|
*/
|
|
function ChatSearchGetLanguageName(languageCode) {
|
|
/** @type {Record<ServerChatRoomLanguage | "", string>} */
|
|
const languageDictionary = {
|
|
ES: TextGet("LanguageES"),
|
|
EN: TranslationGetLanguageName("EN", true),
|
|
CN: TranslationGetLanguageName("CN", true),
|
|
RU: TranslationGetLanguageName("RU", true),
|
|
FR: TranslationGetLanguageName("FR", true),
|
|
UA: TranslationGetLanguageName("UA", true),
|
|
DE: TranslationGetLanguageName("DE", true),
|
|
"": TextGet("AnyLanguage"),
|
|
};
|
|
return languageDictionary[languageCode];
|
|
}
|
|
|
|
/**
|
|
* Returns the translated room type for the given code.
|
|
* @param {ChatRoomMapType} roomType
|
|
*/
|
|
function ChatSearchGetRoomTypeName(roomType) {
|
|
const roomTypeDictionary = {
|
|
Always: TextGet("MapRooms"),
|
|
Hybrid: TextGet("HybridRooms"),
|
|
Never: TextGet("NormalRooms"),
|
|
};
|
|
return roomTypeDictionary[roomType];
|
|
}
|
|
|
|
/**
|
|
* Returns the translated space for the given code.
|
|
* @param {"" | "X" | "M" | "Asylum"} space
|
|
*/
|
|
function ChatSearchGetSpaceName(space) {
|
|
const spaceDictionary = {
|
|
"": TextGet("Female"),
|
|
X: TextGet("Mixed"),
|
|
M: TextGet("Male"),
|
|
Asylum: TextGet("Asylum"),
|
|
};
|
|
return spaceDictionary[space];
|
|
}
|
|
|
|
/**
|
|
* Updates the room grid with the given rooms
|
|
* @param {ChatRoomSearchResult[]} rooms
|
|
*/
|
|
function ChatSearchGridUpdate(rooms) {
|
|
if (!ChatSearchRoomGrid) return;
|
|
ChatSearchRoomGrid.innerHTML = "";
|
|
for (const [index, room] of rooms.entries()) {
|
|
ChatSearchCreateGridRoom(room, index);
|
|
}
|
|
|
|
document.querySelectorAll('.chat-search-room-title').forEach(ElementFitText);
|
|
}
|
|
|
|
/**
|
|
* Handle clicks on room buttons
|
|
* @param {ChatRoomSearchResult} room
|
|
* @returns
|
|
*/
|
|
function ChatSearchClickRoom(room) {
|
|
if (ChatSearchMode == "Filter") {
|
|
if (ChatSearchShowHiddenRoomsActive) return ChatSearchClickUnhideRoom(room, false);
|
|
if (ChatSearchHiddenResult.includes(room)) return ChatSearchClickUnhideRoom(room, false);
|
|
|
|
ChatSearchHiddenResult.push(room);
|
|
const roomIdx = ChatSearchResult.findIndex(r => r.Name === room.Name);
|
|
if (roomIdx >= 0) ChatSearchResult.splice(roomIdx, 1);
|
|
const roomElem = document.getElementById(`chat-search-room-join-button-${room.Order}`);
|
|
roomElem?.setAttribute("data-temporary-hidden", "true");
|
|
if (ChatSearchGhostPlayerOnClickActive) return ChatRoomListUpdate(Player.GhostList, true, room.CreatorMemberNumber);
|
|
|
|
ChatSearchTempHiddenRooms.push(room.CreatorMemberNumber);
|
|
} else if (ChatSearchMode === "") {
|
|
ChatSearchJoin(room.Name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a grid button for the given room
|
|
* @param {ChatRoomSearchResult} room
|
|
* @param {number} index
|
|
*/
|
|
function ChatSearchCreateGridRoom(room, index) {
|
|
const isBlocked = CharacterHasBlockedItem(Player, room.BlockCategory);
|
|
const tooltipElement = ChatSearchCreateGridRoomTooltip(room, index);
|
|
|
|
// Calculate tooltip position directly
|
|
const isLastRow = index >= ChatSearchRoomsPerPage - ChatSearchRoomsPerRow;
|
|
const tooltipPosition = isLastRow ? "top" : "bottom";
|
|
|
|
|
|
const icons = ChatSearchGridRoomGetIcons(room);
|
|
|
|
/** @type {(null | undefined | string | Node | HTMLOptionsUnion)[]} */
|
|
const content = [
|
|
ElementButton.Create(`chat-search-room-join-button-${room.Order}`, () => {
|
|
ChatSearchClickRoom(room);
|
|
}, {
|
|
tooltipPosition: tooltipPosition,
|
|
tooltip: tooltipElement,
|
|
clickDisabled() {
|
|
// We still try to join in case the server state is different
|
|
ChatSearchClickRoom(room);
|
|
},
|
|
}, {
|
|
button: {
|
|
classList: ["chat-search-room-join-button"],
|
|
attributes: {
|
|
"aria-disabled": room.MemberCount >= room.MemberLimit ? "true" : "false",
|
|
},
|
|
dataAttributes: {
|
|
locked: !room.CanJoin ? "true" : "false",
|
|
full: room.MemberCount >= room.MemberLimit ? "true" : "false",
|
|
withFriends: room.Friends.length > 0 ? "true" : "false",
|
|
blocked: isBlocked ? "true" : "false",
|
|
game: room.Game != "" ? "true" : "false",
|
|
temporaryHidden: ChatSearchHiddenResult.includes(room) ? "true" : "false",
|
|
},
|
|
},
|
|
}),
|
|
icons.length > 0 ? {
|
|
tag: "div",
|
|
classList: ["chat-search-room-icons"],
|
|
children: [
|
|
...icons.map((value) => ElementCreate({
|
|
tag: "img",
|
|
classList: ["chat-search-room-icon"],
|
|
attributes: { src: value ?? "" },
|
|
})),
|
|
],
|
|
} : undefined,
|
|
{
|
|
tag: "p",
|
|
classList: ["chat-search-room-title"],
|
|
children: [
|
|
{
|
|
tag: "span",
|
|
classList: ["chat-search-room-name"],
|
|
children: [room.Name]
|
|
},
|
|
"-",
|
|
{
|
|
tag: "span",
|
|
classList: ["chat-search-room-creator"],
|
|
children: [`${room.Creator}`]
|
|
},
|
|
{
|
|
tag: "span",
|
|
classList: ["chat-search-room-members"],
|
|
children: [`${room.MemberCount}/${room.MemberLimit}`]
|
|
},
|
|
],
|
|
},
|
|
{
|
|
tag: "p",
|
|
classList: ["chat-search-room-description"],
|
|
children: [room.Description]
|
|
},
|
|
ElementButton.Create(`chat-search-room-page-button-${room.Order}`, () => {
|
|
ChatSearchShowRoomPage(room);
|
|
}, {
|
|
tooltipPosition: "bottom",
|
|
tooltip: TextGet("ShowRoomPage"),
|
|
image: "Icons/Information.svg",
|
|
}, {
|
|
button: {
|
|
classList: ["chat-search-room-page-button"],
|
|
},
|
|
}),
|
|
];
|
|
ElementCreate(
|
|
{
|
|
tag: "div",
|
|
classList: ["chat-search-room"],
|
|
attributes: {
|
|
id: `chat-search-room-${room.Order}`,
|
|
},
|
|
children: content,
|
|
parent: ChatSearchRoomGrid ?? undefined,
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the list of icons for the given room
|
|
* @param {ChatRoomSearchResult} room
|
|
* @returns {string[]}
|
|
*/
|
|
function ChatSearchGridRoomGetIcons(room) {
|
|
const icons = [];
|
|
const hasMap = room.MapType === "Always" || room.MapType === "Hybrid";
|
|
|
|
if (hasMap) icons.push(`Icons/MapType${room.MapType}.png`);
|
|
if (ChatRoomDataIsPrivate(room)) icons.push("Icons/Private.png");
|
|
if (ChatRoomDataIsLocked(room)) icons.push(room.CanJoin ? "Icons/CheckUnlocked.png" : "Icons/CheckLocked.png");
|
|
return icons;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the given room can be joined
|
|
* @param {ChatRoomSearchResult} room
|
|
* @returns {boolean}
|
|
*/
|
|
function ChatSearchGridRoomCanJoin(room) {
|
|
return room.CanJoin && room.MemberCount < room.MemberLimit;
|
|
}
|
|
|
|
/**
|
|
* Creates the tooltip for the given room
|
|
* @param {ChatRoomSearchResult} room
|
|
* @param {number} index
|
|
* @returns {HTMLDivElement | undefined}
|
|
*/
|
|
function ChatSearchCreateGridRoomTooltip(room, index) {
|
|
/** @type {HTMLOptionsUnion[]} */
|
|
const children = [];
|
|
if (room.Friends.length > 0) {
|
|
children.push({
|
|
tag: "span",
|
|
classList: ["chat-search-room-tooltip-entry", "chat-search-room-tooltip-friends"],
|
|
children: [
|
|
TextGet("FriendsInRoom") + " " + room.Friends.map(f => `${f.MemberName} (${f.MemberNumber})`).join(", "),
|
|
]
|
|
});
|
|
}
|
|
if (room.Game != "") {
|
|
children.push({
|
|
tag: "span",
|
|
classList: ["chat-search-room-tooltip-entry", "chat-search-room-tooltip-game"],
|
|
children: [
|
|
TextGet("GameLabel") + " " + TextGet("Game" + room.Game),
|
|
],
|
|
});
|
|
}
|
|
if (room.BlockCategory.length > 0) {
|
|
children.push({
|
|
tag: "span",
|
|
classList: ["chat-search-room-tooltip-entry", "chat-search-room-tooltip-blocked"],
|
|
children: [
|
|
TextGet("Block") + " " + room.BlockCategory.map(c => TextGet(c)).join(", "),
|
|
],
|
|
});
|
|
}
|
|
const filterReasons = ChatSearchGetFilterReasons(room);
|
|
if (filterReasons.length > 0) {
|
|
// We remove everything, as having a reason means we're filtering
|
|
children.splice(0, children.length);
|
|
children.push({
|
|
tag: "span",
|
|
classList: ["chat-search-room-tooltip-entry", "chat-search-room-tooltip-blocked"],
|
|
children: [
|
|
TextGet("FilteredBecause") + " " + filterReasons.map(r => TextGet(`FilterReason${r}`)),
|
|
]
|
|
});
|
|
}
|
|
if (!children.length) return undefined;
|
|
return ElementCreate({
|
|
tag: "div",
|
|
attributes: { id: `chat-search-room-tooltip-${index}` },
|
|
classList: ["chat-search-room-tooltip"],
|
|
children,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates the tags for the room page
|
|
* @param {ChatRoomSearchResult} room
|
|
* @returns {HTMLElement[]}
|
|
*/
|
|
function ChatSearchCreateRoomPageTags(room) {
|
|
/** @type {HTMLElement[]} */
|
|
let roomPageTags = [];
|
|
|
|
/**
|
|
* Creates a tag for the room page
|
|
* @param {string} tag - The tag name
|
|
* @param {string} value - The text to display
|
|
* @param {ElementButton.StaticNode} [tooltip] - The tooltip to display
|
|
*/
|
|
function AddTag(tag, value, tooltip) {
|
|
roomPageTags.push(ElementButton.Create(null,
|
|
() => null,
|
|
{
|
|
tooltipPosition: "bottom",
|
|
tooltip: tooltip,
|
|
},
|
|
{
|
|
button: {
|
|
classList: ["chat-search-room-page-tag", "chat-search-room-page-tag-" + tag],
|
|
children: [value],
|
|
attributes: { "aria-disabled": "true", },
|
|
},
|
|
}
|
|
));
|
|
}
|
|
|
|
// Default tags
|
|
AddTag(
|
|
"space",
|
|
ChatSearchGetSpaceName(room.Space),
|
|
TextGet(`${room.Space}SpaceDescription`)
|
|
);
|
|
AddTag(
|
|
"language",
|
|
ChatSearchGetLanguageName(/** @type {ServerChatRoomLanguage} */(room.Language)),
|
|
TextGet("LanguageDescription").replace("%LANGUAGE%", ChatSearchGetLanguageName(/** @type {ServerChatRoomLanguage} */(room.Language)))
|
|
);
|
|
|
|
// Additional tags
|
|
if (room.Game !== "") AddTag(
|
|
"game",
|
|
TextGet(`Game${room.Game}`),
|
|
TextGet(`${room.Game}RoomGameDescription`)
|
|
);
|
|
if (room.MapType !== "Never") AddTag(
|
|
"map",
|
|
ChatSearchGetRoomTypeName(/** @type {ChatRoomMapType} */(room.MapType)),
|
|
TextGet(`${room.MapType}MapDescription`)
|
|
);
|
|
if (room.Friends.length > 0) AddTag(
|
|
"friends",
|
|
TextGet("FriendList"),
|
|
{ tag: /** @type {const} */("ul"), children: room.Friends.map(f => { return { tag: "li", children: [`${f.MemberName} (${f.MemberNumber})`] }; }) },
|
|
);
|
|
|
|
// Tags with multiple values
|
|
for (let blockCategory of room.BlockCategory) {
|
|
AddTag("block",
|
|
`${TextGet("Block")} ${TextGet(blockCategory)}`,
|
|
TextGet(`${blockCategory}BlockDescription`)
|
|
);
|
|
}
|
|
for (let access of room.Access) {
|
|
if (access === "All") continue;
|
|
AddTag(
|
|
"access",
|
|
TextGet(`${access}Access`)
|
|
);
|
|
}
|
|
|
|
return roomPageTags;
|
|
}
|
|
|
|
/**
|
|
* Shows the room page for the given room
|
|
* @param {ChatRoomSearchResult} room
|
|
*/
|
|
function ChatSearchShowRoomPage(room) {
|
|
if (ChatSearchDialogElement) {
|
|
ElementRemove(ChatSearchDialogElement);
|
|
}
|
|
const roomPageElements = [
|
|
ElementCreate({
|
|
tag: "h2",
|
|
classList: ["chat-search-room-page-title"],
|
|
children: [
|
|
room.Name,
|
|
],
|
|
}),
|
|
ElementCreate({
|
|
tag: "p",
|
|
classList: ["chat-search-room-page-creator"],
|
|
children: [
|
|
`By ${room.Creator}`
|
|
],
|
|
}),
|
|
ElementCreate({
|
|
tag: "p",
|
|
classList: ["chat-search-room-page-description"],
|
|
children: [
|
|
room.Description === "" ? "No description" : room.Description,
|
|
],
|
|
}),
|
|
ElementCreate({
|
|
tag: "div",
|
|
classList: ["chat-search-room-page-tags"],
|
|
children: ChatSearchCreateRoomPageTags(room),
|
|
}),
|
|
ElementButton.Create("chat-search-room-page-exit", () => {
|
|
ChatSearchDialogElement?.close();
|
|
}, {
|
|
image: "Icons/cross.svg",
|
|
}, {
|
|
button: {
|
|
classList: ["chat-search-room-page-exit-button"],
|
|
},
|
|
},
|
|
)
|
|
];
|
|
const roomPageFooter = ElementCreate(
|
|
{
|
|
tag: "footer",
|
|
classList: ["chat-search-room-page-footer"],
|
|
children: [
|
|
{
|
|
tag: "div",
|
|
classList: ["chat-search-room-page-member-count"],
|
|
children: [
|
|
`${room.MemberCount} / ${room.MemberLimit}`,
|
|
],
|
|
},
|
|
ElementButton.Create("chat-search-room-page-join",
|
|
() => {
|
|
ChatSearchDialogElement?.close();
|
|
ChatSearchJoin(room.Name);
|
|
}, null, {
|
|
button: {
|
|
classList: ["chat-search-room-page-join-button"],
|
|
attributes: {
|
|
autofocus: true,
|
|
},
|
|
children: [
|
|
ChatSearchGridRoomCanJoin(room) ? TextGet("JoinRoom") : TextGet("RoomUnavailable"),
|
|
],
|
|
},
|
|
},
|
|
),
|
|
],
|
|
}
|
|
);
|
|
ChatSearchDialogElement = ElementCreate({
|
|
tag: "dialog",
|
|
attributes: {
|
|
id: "chat-search-room-page-dialog",
|
|
},
|
|
children: [
|
|
{
|
|
tag: "div",
|
|
attributes: {
|
|
id: "chat-search-room-page",
|
|
},
|
|
style: {
|
|
backgroundImage: "var(--background)",
|
|
backgroundSize: "cover",
|
|
backgroundPosition: "center",
|
|
backgroundRepeat: "no-repeat",
|
|
},
|
|
children: [
|
|
...roomPageElements,
|
|
roomPageFooter,
|
|
]
|
|
},
|
|
],
|
|
parent: document.body,
|
|
});
|
|
ElementPositionFixed("chat-search-room-page", (2000 - 1200) / 2, (1000 - 700) / 2, 1200, 700);
|
|
ChatSearchDialogElement.addEventListener("keydown", (ev) => {
|
|
if (ev.key === "Escape") {
|
|
ChatSearchDialogElement?.close();
|
|
return;
|
|
}
|
|
if (ev.key === "Enter") {
|
|
ChatSearchDialogElement?.close();
|
|
ChatSearchJoin(room.Name);
|
|
}
|
|
}, { once: true });
|
|
|
|
ChatSearchDialogElement.showModal();
|
|
ChatSearchDialogElement.focus();
|
|
}
|
|
|
|
/**
|
|
* Garbles based on immersion settings
|
|
* @param {string} Text - The text to garble
|
|
* @returns {string} - Garbled text
|
|
*/
|
|
function ChatSearchMuffle(Text) {
|
|
let ret = Text;
|
|
if (Player.ImmersionSettings && Player.ImmersionSettings.ChatRoomMuffle && Player.GetBlindLevel() > 0) {
|
|
ret = SpeechGarbleByGagLevel(Player.GetBlindLevel() * Player.GetBlindLevel(), Text, true);
|
|
if (ret.length == 0)
|
|
return "...";
|
|
return ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Joins the room with the given name
|
|
* @param {string} RoomName - The name of the room to join
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchJoin(RoomName) {
|
|
if (ChatSearchLastQueryJoin != RoomName || (ChatSearchLastQueryJoin == RoomName && ChatSearchLastQueryJoinTime + 1000 < CommonTime())) {
|
|
ChatSearchLastQueryJoinTime = CommonTime();
|
|
ChatSearchLastQueryJoin = RoomName;
|
|
ServerSend("ChatRoomJoin", { Name: RoomName });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Switch the search screen between the normal view and the filter mode which allows hiding of rooms
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchToggleSearchMode() {
|
|
switch (ChatSearchMode) {
|
|
case "":
|
|
// Switch to filter mode
|
|
ChatSearchRoomGrid?.classList.add("chat-search-filter-search");
|
|
ElementWrap("InputSearch")?.toggleAttribute("hidden", true);
|
|
ElementWrap("InputFilter")?.toggleAttribute("hidden", false);
|
|
document.getElementById("chat-search-room-search-button")?.toggleAttribute("disabled", true);
|
|
if (ChatSearchSearchMenuButton?.getAttribute("aria-expanded") === "true") {
|
|
ChatSearchSearchMenuButton.click();
|
|
}
|
|
ChatSearchSearchMenuButton?.toggleAttribute("disabled", true);
|
|
|
|
// Save the current search query and load the filter string
|
|
ChatSearchQueryString = ElementValue("InputSearch");
|
|
ChatSearchFilterTermsTemp = Player.ChatSearchSettings.FilterTerms;
|
|
ElementValue("InputFilter", ChatSearchFilterTermsTemp);
|
|
ChatSearchMode = "Filter";
|
|
ChatSearchQuery("");
|
|
break;
|
|
|
|
case "Filter":
|
|
// Switch to Normal mode
|
|
ChatSearchRoomGrid?.classList.remove("chat-search-filter-search");
|
|
ElementWrap("InputSearch")?.toggleAttribute("hidden", false);
|
|
ElementWrap("InputFilter")?.toggleAttribute("hidden", true);
|
|
document.getElementById("chat-search-room-search-button")?.toggleAttribute("disabled", false);
|
|
ChatSearchSearchMenuButton?.toggleAttribute("disabled", false);
|
|
ChatSearchSaveFilterTerms();
|
|
ChatSearchMode = "";
|
|
ChatSearchQuery(ChatSearchQueryString);
|
|
break;
|
|
}
|
|
ElementWrap('chat-search-room-filter-section')?.toggleAttribute("hidden", ChatSearchMode != "Filter");
|
|
ElementWrap('chat-search-room-navigation-section')?.toggleAttribute("hidden", ChatSearchMode == "Filter");
|
|
const filterButton = document.getElementById('chat-search-hide-rooms');
|
|
if (filterButton) {
|
|
const tooltip = filterButton.querySelector(".button-tooltip");
|
|
if (tooltip) {
|
|
tooltip.textContent = TextGet(ChatSearchMode != "Filter" ? "FilterMode" : "NormalMode");
|
|
}
|
|
filterButton.dataset.mode = ChatSearchMode == "Filter" ? "FilterMode" : "NormalMode";
|
|
}
|
|
ChatSearchSetPageRelative(0);
|
|
}
|
|
|
|
/**
|
|
* Switch to the Hidden Rooms view or back again.
|
|
* Correctly handles adding/removing the input box as needed.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchToggleHiddenMode() {
|
|
ChatSearchShowHiddenRoomsActive = !ChatSearchShowHiddenRoomsActive;
|
|
ChatSearchSetPageRelative(0);
|
|
}
|
|
|
|
/**
|
|
* Does whatever is necessary to unhide a room.
|
|
* Shows a confirmation screen first, unless the only reason is "TempHidden".
|
|
* This is called when clicking a room in the list and also, if a confirmation is shown, called again when the confirm button is clicked.
|
|
*
|
|
* @param {ChatRoomSearchResult | number} C - Index of the room within ChatSearchHiddenResult
|
|
* @param {boolean} Confirmed - False when clicking on room list, true when clicking Confirm button
|
|
*/
|
|
function ChatSearchClickUnhideRoom(C, Confirmed) {
|
|
/** @type {ChatRoomSearchResult} */
|
|
const Room = typeof C === "number" ? ChatSearchHiddenResult[C] : C;
|
|
const roomIdx = ChatSearchHiddenResult.findIndex(r => r.Name === Room.Name);
|
|
if (roomIdx === -1) return false;
|
|
|
|
const Reasons = ChatSearchGetFilterReasons(Room);
|
|
const ReasonsHasWord = (Reasons.indexOf("Word") != -1);
|
|
const ReasonsHasTempHidden = (Reasons.indexOf("TempHidden") != -1);
|
|
const ReasonsHasGhostList = (Reasons.indexOf("GhostList") != -1);
|
|
|
|
// If the only reason is "TempHidden" we don't need a confirmation screen so just act like we clicked the confirm button already
|
|
if (Reasons.length == 1 && ReasonsHasTempHidden) Confirmed = true;
|
|
|
|
// If room matches filtered words, calculate the words to be removed/kept
|
|
let KeepTerms = [], RemoveTerms = [];
|
|
if (ReasonsHasWord) {
|
|
let OldTerms = Player.ChatSearchSettings.FilterTerms.split(',').filter(s => s);
|
|
for (let Idx = 0; Idx < OldTerms.length; Idx++)
|
|
if (ChatSearchMatchesTerms(Room, [OldTerms[Idx].toUpperCase()]))
|
|
RemoveTerms.push(OldTerms[Idx]);
|
|
else
|
|
KeepTerms.push(OldTerms[Idx]);
|
|
}
|
|
|
|
// If not confirmed, store data for later and show confirm screen
|
|
if (!Confirmed) {
|
|
const MemberLabel = ChatSearchMuffle(Room.Creator) + " (" + Room.CreatorMemberNumber + ")";
|
|
ChatSearchCreateFilterUnhideConfirm(ChatSearchMuffle(Room.DisplayName) + " - " + MemberLabel, MemberLabel, RemoveTerms.join(','), Room);
|
|
return;
|
|
}
|
|
|
|
if (ReasonsHasWord) {
|
|
// Remove all filtered terms that this room matches
|
|
Player.ChatSearchSettings.FilterTerms = KeepTerms.join(',');
|
|
ChatSearchUpdateSearchSettings();
|
|
// Update the temp var too because we don't reload it when we exit the hidden room list
|
|
ChatSearchFilterTermsTemp = Player.ChatSearchSettings.FilterTerms;
|
|
}
|
|
if (ReasonsHasTempHidden) {
|
|
// Remove from Temp Hidden list
|
|
const Idx = ChatSearchTempHiddenRooms.indexOf(Room.CreatorMemberNumber);
|
|
ChatSearchTempHiddenRooms.splice(Idx, 1);
|
|
}
|
|
if (ReasonsHasGhostList) {
|
|
// Remove creator from ghostlist
|
|
ChatRoomListUpdate(Player.GhostList, false, Room.CreatorMemberNumber);
|
|
}
|
|
|
|
// Move the room from the hidden result to the normal result
|
|
ChatSearchResult.push(Room);
|
|
ChatSearchHiddenResult.splice(roomIdx, 1);
|
|
const roomElem = document.getElementById(`chat-search-room-join-button-${Room.Order}`);
|
|
roomElem?.setAttribute("data-temporary-hidden", "false");
|
|
}
|
|
|
|
/**
|
|
* Handles the reception of the server response when joining a room or when getting banned/kicked
|
|
* @param {ServerChatRoomJoinResponse} data - Response from the server
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchResponse(data) {
|
|
if (typeof data !== "string") return;
|
|
if (((data == "RoomBanned") || (data == "RoomKicked")) && ServerPlayerIsInChatRoom()) {
|
|
// This will cause us to send an extra ChatRoomLeave message
|
|
ChatRoomLeave(true);
|
|
CommonSetScreen("Online", "ChatSearch");
|
|
}
|
|
ChatSearchSendToast(data);
|
|
}
|
|
|
|
/**
|
|
* Sends a toast message based on the given response type
|
|
* @param {ServerChatRoomJoinResponse} type
|
|
*/
|
|
function ChatSearchSendToast(type) {
|
|
ToastManager.dismissByCategory("ChatSearch");
|
|
|
|
if (["AlreadyInRoom", "RoomBanned", "RoomKicked", "RoomLocked", "RoomFull"].includes(type)) {
|
|
ToastManager.warning(TextGet("Response" + type), { category: "ChatSearch" });
|
|
return;
|
|
}
|
|
if (["CannotFindRoom", "AccountError", "InvalidRoomData"].includes(type)) {
|
|
ToastManager.error(TextGet("Response" + type), { category: "ChatSearch" });
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Censors the chat search result name and description based on the player preference
|
|
* @param {ServerChatRoomSearchData} searchData - The (potentially) to-be censored search result
|
|
* @returns {null | { DisplayName: string, Description: string }} - The censored name and description or, if fully censored, return `null` instead
|
|
*/
|
|
function ChatSearchCensor(searchData) {
|
|
const DisplayName = CommonCensor(searchData.Name);
|
|
const Description = CommonCensor(searchData.Description);
|
|
if (DisplayName === "¶¶¶" || Description === "¶¶¶") {
|
|
return null;
|
|
} else {
|
|
return { DisplayName, Description };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse the passed server search data, ensuring that all required fields are present.
|
|
* @param {ServerChatRoomSearchResultResponse} searchResults - The unparsed search data as received from the server
|
|
* @returns {(ServerChatRoomSearchData & { DisplayName: string, Order: number })[]} - The fully parsed room search data
|
|
*/
|
|
function ChatSearchParseResponse(searchResults) {
|
|
if (!CommonIsArray(searchResults)) {
|
|
return [];
|
|
}
|
|
|
|
/** @type {(ServerChatRoomSearchData & { DisplayName: string, Order: number })[]} */
|
|
const ret = [];
|
|
let i = 0;
|
|
for (const result of searchResults) {
|
|
const censoredData = ChatSearchCensor(result);
|
|
if (censoredData === null) {
|
|
continue;
|
|
}
|
|
if (result.MemberLimit < Player.ChatSearchSettings.RoomMinSize || result.MemberLimit > Player.ChatSearchSettings.RoomMaxSize) {
|
|
continue;
|
|
}
|
|
ret.push(/** @type {ChatRoomSearchResult} */({ ...result, ...censoredData, Order: i }));
|
|
i++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Handles the reception of the server data when it responds to the search query
|
|
* @param {ServerChatRoomSearchResultResponse} data - Response from the server, contains the room list matching the query
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchResultResponse(data) {
|
|
if (PandoraPenitentiaryIsInmate(Player)) {
|
|
PandoraPenitentiaryResult(ChatSearchParseResponse(data));
|
|
return;
|
|
}
|
|
ElementContent("InputSearch-datalist", "");
|
|
ChatSearchResult = ChatSearchParseResponse(data);
|
|
ChatSearchResultOffset = 0;
|
|
ChatSearchQuerySort();
|
|
ChatSearchApplyFilterTerms();
|
|
ChatSearchAutoJoinRoom();
|
|
ChatSearchSetPageRelative(0); // Refreshes the pagination
|
|
}
|
|
|
|
/**
|
|
* Automatically join a room, for example due to leashes or reconnect
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchAutoJoinRoom() {
|
|
if (ChatRoomJoinLeash != "") {
|
|
// This is a search triggered after entering the lobby while being leashed
|
|
// Join the room and unset the special leash-to-room flag
|
|
const leashRoom = ChatSearchResult.find(r => r.Name === ChatRoomJoinLeash);
|
|
if (leashRoom) {
|
|
ChatSearchJoin(leashRoom.Name);
|
|
}
|
|
ChatRoomJoinLeash = "";
|
|
return;
|
|
}
|
|
|
|
// This is a search triggered from a relog
|
|
if (Player.ImmersionSettings && Player.ImmersionSettings.ReturnToChatRoom && Player.LastChatRoom && !PandoraPenitentiaryIsInmate(Player) && ((ChatSearchReturnScreen?.[1] !== "AsylumEntrance") || (AsylumGGTSGetLevel(Player) <= 0))) {
|
|
let roomFound = false;
|
|
let roomIsFull = false;
|
|
// Try joining our previous room
|
|
for (let R = 0; R < ChatSearchResult.length; R++) {
|
|
const room = ChatSearchResult[R];
|
|
if (room.Name === Player.LastChatRoom.Name && room.Game == "") {
|
|
if (room.MemberCount < room.MemberLimit) {
|
|
var RoomName = room.Name;
|
|
roomFound = true;
|
|
ChatSearchJoin(RoomName);
|
|
break;
|
|
} else {
|
|
roomIsFull = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The room is gone, create from our previous room data if appropriate
|
|
if (!roomFound) {
|
|
if (Player.ImmersionSettings.ReturnToChatRoomAdmin
|
|
&& Player.LastChatRoom.Admin
|
|
&& Player.LastChatRoom.Background
|
|
&& Player.LastChatRoom.Visibility != null
|
|
&& Player.LastChatRoom.Limit
|
|
&& Player.LastChatRoom.Description != null
|
|
&& Player.LastChatRoom.Name) {
|
|
|
|
if ((ChatAdminMessage === "ResponseRoomAlreadyExist" || roomIsFull) && ChatSearchRejoinIncrement < 50) {
|
|
// The ChatRoomCreate call below failed. Append prefix and try again
|
|
ChatSearchRejoinIncrement += 1;
|
|
const ChatRoomSuffix = " " + ChatSearchRejoinIncrement;
|
|
Player.LastChatRoom.Name = Player.LastChatRoom.Name.substring(0, Math.min(Player.LastChatRoom.Name.length, 19 - ChatRoomSuffix.length)) + ChatRoomSuffix; // Added
|
|
ChatAdminMessage = "";
|
|
ChatSearchQuery(ChatSearchQueryString);
|
|
} else {
|
|
/** @type {ChatRoomSettings} */
|
|
const NewRoom = {
|
|
Name: Player.LastChatRoom.Name.trim(),
|
|
Description: Player.LastChatRoom.Description.trim(),
|
|
Admin: [Player.MemberNumber],
|
|
Whitelist: [],
|
|
Ban: [],
|
|
Background: Player.LastChatRoom.Background,
|
|
Limit: Math.min(Math.max(Player.LastChatRoom.Limit, 2), 10),
|
|
Game: "",
|
|
Visibility: Player.LastChatRoom.Visibility,
|
|
Access: ChatRoomAccessMode.PUBLIC,
|
|
BlockCategory: Player.LastChatRoom.BlockCategory,
|
|
Language: Player.LastChatRoom.Language,
|
|
Space: Player.LastChatRoom.Space,
|
|
Custom: Player.LastChatRoom.Custom,
|
|
MapData: Player.LastChatRoom.MapData,
|
|
};
|
|
ServerSend("ChatRoomCreate", NewRoom);
|
|
ChatAdminMessage = "CreatingRoom";
|
|
|
|
// Actually set the real Admin list. This will get restored by ChatRoomRecreate when it runs
|
|
if (Player.ImmersionSettings.ReturnToChatRoomAdmin && Player.LastChatRoom.Admin) {
|
|
NewRoom.Admin = Player.LastChatRoom.Admin;
|
|
ChatRoomNewRoomToUpdate = NewRoom;
|
|
ChatRoomNewRoomToUpdateTimer = CurrentTime + 1000;
|
|
}
|
|
}
|
|
} else {
|
|
ChatSearchSendToast(roomIsFull ? "RoomFull" : "CannotFindRoom");
|
|
ChatRoomSetLastChatRoom(null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const ChatSearchUpdateSearchSettings = CommonLimitFunction(function () {
|
|
ServerAccountUpdate.QueueData({ ChatSearchSettings: Player.ChatSearchSettings });
|
|
}, 0, 3000);
|
|
|
|
/**
|
|
* Sends the search query data to the server. The response will be handled by ChatSearchResponse once it is received
|
|
* @param {string} Query - The search term to look for
|
|
* @returns {Promise<void>} - Nothing
|
|
*/
|
|
async function ChatSearchQuery(Query) {
|
|
if (ChatRoomJoinLeash !== "") {
|
|
Query = ChatRoomJoinLeash.toUpperCase().trim();
|
|
} else if (Player.ImmersionSettings.ReturnToChatRoom) {
|
|
if (Player.LastChatRoom?.Name) {
|
|
Query = Player.LastChatRoom.Name?.toUpperCase().trim() ?? "";
|
|
} else {
|
|
ChatRoomSetLastChatRoom(null);
|
|
}
|
|
} else {
|
|
ChatSearchRejoinIncrement = 1; // Reset the join increment
|
|
}
|
|
|
|
/** @type {ServerChatRoomSearchRequest} */
|
|
const SearchData = {
|
|
Query: Query.toUpperCase().trim(),
|
|
Language: ChatSearchLanguage,
|
|
Space: ChatSearchGetSpace() ?? "",
|
|
Game: ChatSearchGame,
|
|
FullRooms: Player.ChatSearchSettings.FullRooms,
|
|
ShowLocked: Player.ChatSearchSettings.ShowLocked,
|
|
MapTypes: Player.ChatSearchSettings.MapTypes ? [Player.ChatSearchSettings.MapTypes] : [],
|
|
SearchDescs: Player.ChatSearchSettings.SearchDescriptions,
|
|
};
|
|
|
|
const res = await ServerRoomSearch(Query.toUpperCase().trim(), SearchData);
|
|
if (res.err) {
|
|
return;
|
|
}
|
|
|
|
ChatSearchResultResponse(res.value ?? []);
|
|
}
|
|
|
|
/**
|
|
* Sorts the room result based on a player's settings
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchQuerySort() {
|
|
// Send full rooms to the back of the list and save the order of creation.
|
|
ChatSearchResult.sort((R1, R2) => R1.MemberCount >= R1.MemberLimit ? 1 : (R2.MemberCount >= R2.MemberLimit ? -1 : (R1.Order - R2.Order)));
|
|
|
|
// Friendlist option overrides basic order, but keeps full rooms at the back for each number of each different total of friends.
|
|
if (Player.OnlineSettings.SearchFriendsFirst) {
|
|
ChatSearchResult.sort((R1, R2) => R2.Friends.length - R1.Friends.length);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove any rooms from the room list which contain the player's filter terms in the name
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatSearchApplyFilterTerms() {
|
|
ChatSearchHiddenResult = ChatSearchResult.filter(room => { return ChatSearchGetFilterReasons(room).length != 0; });
|
|
ChatSearchResult = ChatSearchResult.filter(room => { return ChatSearchGetFilterReasons(room).length == 0; });
|
|
}
|
|
|
|
/**
|
|
* Get a list of reasons why a room should be hidden.
|
|
* If the returned array is empty, the room should be shown.
|
|
* @param {{ Name: string, CreatorMemberNumber: number }} Room - the room object to check
|
|
* @returns {string[]} - list of reasons
|
|
*/
|
|
function ChatSearchGetFilterReasons(Room) {
|
|
const Reasons = [];
|
|
|
|
// for an exact room name match, ignore filters
|
|
if (ChatSearchMode == "" && Room.Name.toUpperCase() == ElementValue("InputSearch").toUpperCase().trim())
|
|
return [];
|
|
|
|
// are any words filtered?
|
|
if (ChatSearchMatchesTerms(Room, Player.ChatSearchSettings.FilterTerms.split(',').filter(s => s).map(s => s.toUpperCase())))
|
|
Reasons.push("Word");
|
|
|
|
// is room temp hidden?
|
|
if (ChatSearchTempHiddenRooms.indexOf(Room.CreatorMemberNumber) != -1)
|
|
Reasons.push("TempHidden");
|
|
|
|
// is creator on ghostlist?
|
|
if (Player.HasOnGhostlist(Room.CreatorMemberNumber))
|
|
Reasons.push("GhostList");
|
|
|
|
return Reasons;
|
|
}
|
|
|
|
/**
|
|
* Check if a room matches filtered-out terms and should thus be hidden.
|
|
* Also used when deciding which terms need to be removed from the filter option in order to make a room be no longer hidden.
|
|
* Only checks the room name, not the description.
|
|
* @param {{ Name: string }} Room - the room object to check
|
|
* @param {string[]} Terms - list of terms to check
|
|
* @returns {boolean} - true if room matches, false otherwise
|
|
*/
|
|
function ChatSearchMatchesTerms(Room, Terms) {
|
|
const roomName = Room.Name.toUpperCase();
|
|
return Terms.some(term => roomName.includes(term));
|
|
}
|
|
|
|
/**
|
|
* Calculates starting offset for the ignored rooms list when displaying results in filter/permission mode.
|
|
* @param {number} shownRooms - Number of rooms shown before the ignored rooms.
|
|
* @returns {number} - Starting offset for ingored rooms
|
|
*/
|
|
function ChatSearchCalculateIgnoredRoomsOffset(shownRooms) {
|
|
return ChatSearchResultOffset + shownRooms - ChatSearchResult.length;
|
|
}
|
|
|
|
/**
|
|
* Return the space we're currently in.
|
|
* Note that it will look at both the current room's, and if we're not in one, the current lobby
|
|
* @returns {ServerChatRoomSpace}
|
|
*/
|
|
function ChatSearchGetSpace() {
|
|
return ChatRoomData?.Space ?? ChatSearchSpace ?? ChatRoomSpaceType.MIXED;
|
|
}
|