mirror of
https://gitgud.io/BondageProjects/Bondage-College.git
synced 2025-04-10 02:19:20 +00:00
1129 lines
44 KiB
JavaScript
1129 lines
44 KiB
JavaScript
"use strict";
|
|
var LoginBackground = "Dressing";
|
|
/** @type {null | string[][]} */
|
|
var LoginCredits = null;
|
|
var LoginCreditsPosition = 0;
|
|
var LoginThankYou = "";
|
|
/* eslint-disable */
|
|
var LoginThankYouList = [
|
|
"Abysseia", "AlexG", "ArashiSama", "BlueWinter", "bryce", "Canti", "Christian", "Christoffer", "Claudia33",
|
|
"Cm382714", "Desch", "Dragokahn", "dynilath", "Edwin", "Glurak", "Greendragon", "john", "Laurie", "luke",
|
|
"Lyall", "Marashu", "Michel", "Mindtie", "Misa", "Nightcore", "Patrick", "René", "Robin", "Schrödingers",
|
|
"Setsu95", "Sticks", "Sunny", "Tam", "Tarram1010", "Teli", "Thkdt", "Troubadix", "Troy", "Varo",
|
|
"WhiteSniper", "XDWolfie", "Xepherio",
|
|
];
|
|
|
|
/* eslint-enable */
|
|
var LoginThankYouNext = 0;
|
|
var LoginSubmitted = false;
|
|
var LoginQueuePosition = -1;
|
|
/** The server login status */
|
|
var LoginErrorMessage = "";
|
|
/** @type {NPCCharacter} */
|
|
var LoginCharacter = null;
|
|
|
|
/* DEBUG: To measure FPS - uncomment this and change the + 4000 to + 40
|
|
var LoginLastCT = 0;
|
|
var LoginFrameCount = 0;
|
|
var LoginFrameTotalTime = 0;*/
|
|
|
|
/**
|
|
* Loads the next thank you bubble
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginDoNextThankYou() {
|
|
LoginThankYou = CommonRandomItemFromList(LoginThankYou, LoginThankYouList);
|
|
CharacterRelease(LoginCharacter, false);
|
|
CharacterAppearanceFullRandom(LoginCharacter);
|
|
if (InventoryGet(LoginCharacter, "ItemNeck") != null) InventoryRemove(LoginCharacter, "ItemNeck", false);
|
|
CharacterFullRandomRestrain(LoginCharacter);
|
|
LoginThankYouNext = CommonTime() + 4000;
|
|
}
|
|
|
|
/**
|
|
* Draw the credits
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginDrawCredits() {
|
|
|
|
/* DEBUG: To measure FPS - uncomment this and change the + 4000 to + 40
|
|
var CT = CommonTime();
|
|
if (CT - LoginLastCT < 10000) {
|
|
LoginFrameCount++;
|
|
if (LoginFrameCount > 1000)
|
|
LoginFrameTotalTime = LoginFrameTotalTime + CT - LoginLastCT;
|
|
}
|
|
LoginLastCT = CT;
|
|
if (LoginFrameCount > 1000) DrawText("Average FPS: " + (1000 / (LoginFrameTotalTime / (LoginFrameCount - 1000))).toFixed(2).toString(), 1000, 975, "white");
|
|
else DrawText("Calculating Average FPS...", 1000, 975, "white");*/
|
|
|
|
// For each credits in the list
|
|
LoginCreditsPosition += (TimerRunInterval * 60) / 1000;
|
|
if (LoginCreditsPosition > LoginCredits.length * 25 || LoginCreditsPosition < 0) LoginCreditsPosition = 0;
|
|
MainCanvas.font = "30px Arial";
|
|
for (let C = 0; C < LoginCredits.length; C++) {
|
|
|
|
// Sets the Y position (it scrolls from bottom to top)
|
|
var Y = 800 - Math.floor(LoginCreditsPosition * 2) + (C * 50);
|
|
|
|
// Draw the text if it's in drawing range
|
|
if ((Y > 0) && (Y <= 999)) {
|
|
|
|
// The "CreditTypeRepeat" starts scrolling again, other credit types are translated
|
|
var Cred = LoginCredits[C][0].trim();
|
|
if (Cred == "CreditTypeRepeat") {
|
|
LoginCreditsPosition = 0;
|
|
return;
|
|
} else {
|
|
if (Cred.substr(0, 10) == "CreditType") DrawText(TextGet(Cred), 320, Y, "white");
|
|
else {
|
|
if (Cred.indexOf("|") == -1) DrawText(Cred, 320, Y, "white");
|
|
else {
|
|
DrawText(Cred.substring(0, Cred.indexOf("|")), 180, Y, "white");
|
|
DrawText(Cred.substring(Cred.indexOf("|") + 1, 1000), 460, Y, "white");
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Restore the canvas font
|
|
MainCanvas.font = CommonGetFont(36);
|
|
|
|
}
|
|
|
|
var LoginEventListeners = {
|
|
/**
|
|
* @private
|
|
* @type {(this: HTMLInputElement, ev: KeyboardEvent) => void}
|
|
*/
|
|
_KeyDownInputName: function _KeyDownInputName(ev) {
|
|
if (CommonKey.IsPressed(ev, "Enter")) {
|
|
document.getElementById("InputPassword")?.focus();
|
|
ev.stopPropagation();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @type {(this: HTMLInputElement, ev: KeyboardEvent) => void}
|
|
*/
|
|
_KeyDownInputPassword: function _KeyDownInputPassword(ev) {
|
|
if (CommonKey.IsPressed(ev, "Enter")) {
|
|
const Name = ElementValue("InputName");
|
|
const Password = ElementValue("InputPassword");
|
|
LoginDoLogin(Name, Password);
|
|
ev.stopPropagation();
|
|
}
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Loads the character login screen
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginLoad() {
|
|
|
|
// Resets the player and other characters
|
|
Character = [];
|
|
CharacterNextId = 0;
|
|
// Create a blank character for our player. Its actual ID will be set when LoginResponse happens
|
|
Player = /** @type {PlayerCharacter} */ (CharacterCreate("Female3DCG", CharacterType.SIMPLE, ""));
|
|
LoginCharacter = CharacterLoadNPC("NPC_Login");
|
|
LoginDoNextThankYou();
|
|
LoginStatusReset();
|
|
if (LoginCredits == null) CommonReadCSV("LoginCredits", CurrentModule, CurrentScreen, "GameCredits");
|
|
ActivityDictionaryLoad();
|
|
OnlneGameDictionaryLoad();
|
|
const form = ElementCreateForm("Login");
|
|
|
|
const username = ElementCreateInput("InputName", "text", "", "20", form);
|
|
username.setAttribute("autocomplete", "username");
|
|
username.setAttribute("enterkeyhint", "next");
|
|
username.addEventListener("keydown", LoginEventListeners._KeyDownInputName);
|
|
username.focus();
|
|
|
|
const pass = ElementCreateInput("InputPassword", "password", "", "20", form);
|
|
pass.setAttribute("autocomplete", "current-password");
|
|
pass.setAttribute("enterkeyhint", "go");
|
|
pass.addEventListener("keydown", LoginEventListeners._KeyDownInputPassword);
|
|
|
|
const languages = TranslationDictionary.map(l => {
|
|
return {
|
|
children: [l.Icon, " ", l.LanguageName],
|
|
attributes: {
|
|
selected: l.LanguageCode === TranslationLanguage ? "" : undefined,
|
|
lang: l.LanguageCode,
|
|
value: l.LanguageCode,
|
|
},
|
|
};
|
|
});
|
|
ElementCreateDropdown("LanguageDropdown", languages, function(event) {
|
|
TranslationSwitchLanguage(/** @type {"" | "TW" | ServerChatRoomLanguage} */(this.value) || "EN");
|
|
TextLoad();
|
|
ActivityDictionaryLoad();
|
|
AssetLoadDescription("Female3DCG");
|
|
});
|
|
TextPrefetchFile(BackgroundsStringsPath);
|
|
TextPrefetchFile(AssetStringsPath);
|
|
TextPrefetchFile(InterfaceStringsPath);
|
|
TextPrefetch("Room", "MainHall");
|
|
DrawGetImage("Backgrounds/MainHall.jpg");
|
|
}
|
|
|
|
/**
|
|
* Runs the character login screen
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginRun() {
|
|
|
|
// Draw the credits
|
|
if (LoginCredits != null) LoginDrawCredits();
|
|
|
|
const CanLogin = ServerIsConnected && !LoginSubmitted;
|
|
|
|
// Draw the login controls
|
|
DrawText(TextGet("Welcome"), 1000, 50, "White", "Black");
|
|
DrawText(LoginGetStatus() ?? TextGet("EnterNamePassword"), 1000, 100, "White", "Black");
|
|
DrawText(TextGet("AccountName"), 1000, 200, "White", "Black");
|
|
DrawText(TextGet("Password"), 1000, 350, "White", "Black");
|
|
DrawButton(725, 500, 255, 60, TextGet("Login"), CanLogin ? "White" : "Grey", "");
|
|
DrawText(TextGet("CreateNewCharacter"), 1000, 670, "White", "Black");
|
|
DrawButton(825, 710, 350, 60, TextGet("NewCharacter"), CanLogin ? "White" : "Grey", "");
|
|
if (CheatAllow) DrawButton(825, 800, 350, 60, TextGet("Cheats"), "White", "");
|
|
DrawButton(825, 890, 350, 60, TextGet("PasswordReset"), CanLogin ? "White" : "Grey", "");
|
|
|
|
// Draw the character and thank you bubble
|
|
DrawCharacter(LoginCharacter, 1400, 100, 0.9);
|
|
if (LoginThankYouNext < CommonTime()) LoginDoNextThankYou();
|
|
DrawImage("Screens/" + CurrentModule + "/" + CurrentScreen + "/Bubble.png", 1400, 16);
|
|
DrawText(TextGet("ThankYou") + " " + LoginThankYou, 1625, 53, "Black", "Gray");
|
|
|
|
}
|
|
|
|
/** @type {ScreenFunctions["Resize"]} */
|
|
function LoginResize(load) {
|
|
ElementPosition("InputPassword", 1000, 410, 500);
|
|
ElementPosition("InputName", 1000, 260, 500);
|
|
|
|
ElementPosition("LanguageDropdown", 1145, 530, 255, 60);
|
|
const langSelect = document.getElementById("LanguageDropdown");
|
|
langSelect?.style.setProperty("font-family", `"Twemoji Country Flags", ${langSelect.style.fontFamily}`);
|
|
}
|
|
|
|
/**
|
|
* The list of item fixups to apply on login.
|
|
*
|
|
* Those are applied by the login code, after the player's item lists are set up
|
|
* but before the inventory and appearance are loaded from the server's data,
|
|
* and applies the specified asset fixups by swapping Old with New in the list
|
|
* of owned items, in the various player item lists, and in the appearance.
|
|
*
|
|
* If you're only moving items around, it should work just fine as long as
|
|
* the `Old` and `New` asset definitions are compatible.
|
|
* If it's an asset merge (say 3 into one typed asset), it will either set
|
|
* the fixed up item to the specified `Option` or the first one if unspecified.
|
|
*
|
|
* @type {{ Old: { Group: string, Name: string | '*' }, New: { Group: AssetGroupName, Name?: string, Option?: string } }[]}
|
|
*/
|
|
let LoginInventoryFixups = [
|
|
{ Old: { Group: "ItemLegs", Name: "WoodenHorse" }, New: { Group: "ItemDevices", Name: "WoodenHorse" } },
|
|
{ Old: { Group: "ItemVulvaPiercings", Name: "WeightedClitPiercing" }, New: { Group: "ItemVulvaPiercings", Name: "RoundClitPiercing", Option: "Weight" } },
|
|
{ Old: { Group: "ItemVulvaPiercings", Name: "BellClitPiercing" }, New: { Group: "ItemVulvaPiercings", Name: "RoundClitPiercing", Option: "Bell" } },
|
|
{ Old: { Group: "ItemArms", Name: "BitchSuitExposed"}, New: { Group: "ItemArms", Name: "BitchSuit", Option: "Exposed" } },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysBaguette" }, New: { Group: "ItemHandheld", Name: "Baguette"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysBallgag" }, New: { Group: "ItemHandheld", Name: "Ballgag"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysBelt" }, New: { Group: "ItemHandheld", Name: "Belt"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysBroom" }, New: { Group: "ItemHandheld", Name: "Broom"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysCandleWax" }, New: { Group: "ItemHandheld", Name: "CandleWax"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysCane" }, New: { Group: "ItemHandheld", Name: "Cane"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysCattleProd" }, New: { Group: "ItemHandheld", Name: "CattleProd"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysCrop" }, New: { Group: "ItemHandheld", Name: "Crop"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysElectricToothbrush" }, New: { Group: "ItemHandheld", Name: "ElectricToothbrush"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysFeather" }, New: { Group: "ItemHandheld", Name: "Feather"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysFeatherDuster" }, New: { Group: "ItemHandheld", Name: "FeatherDuster"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysFlogger" }, New: { Group: "ItemHandheld", Name: "Flogger"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysGavel" }, New: { Group: "ItemHandheld", Name: "Gavel"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysGlassEmpty" }, New: { Group: "ItemHandheld", Name: "GlassEmpty"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysGlassFilled" }, New: { Group: "ItemHandheld", Name: "GlassFilled"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysHairbrush" }, New: { Group: "ItemHandheld", Name: "Hairbrush"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysHeartCrop" }, New: { Group: "ItemHandheld", Name: "HeartCrop"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysIceCube" }, New: { Group: "ItemHandheld", Name: "IceCube"} },
|
|
{ Old: { Group: "ItemHands", Name: "KeyProp" }, New: { Group: "ItemHandheld", Name: "KeyProp"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysLargeDildo" }, New: { Group: "ItemHandheld", Name: "LargeDildo"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysLongDuster" }, New: { Group: "ItemHandheld", Name: "LongDuster"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysLongSock" }, New: { Group: "ItemHandheld", Name: "LongSock"} },
|
|
{ Old: { Group: "ItemHands", Name: "MedicalInjector" }, New: { Group: "ItemHandheld", Name: "MedicalInjector"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysLotion" }, New: { Group: "ItemHandheld", Name: "Lotion"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysPaddle" }, New: { Group: "ItemHandheld", Name: "Paddle"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysPanties" }, New: { Group: "ItemHandheld", Name: "Panties"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysPetToy" }, New: { Group: "ItemHandheld", Name: "PetToy"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysPhone1" }, New: { Group: "ItemHandheld", Name: "Phone1"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysPhone2" }, New: { Group: "ItemHandheld", Name: "Phone2"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysPlasticWrap" }, New: { Group: "ItemHandheld", Name: "PlasticWrap"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysPotionBottle" }, New: { Group: "ItemHandheld", Name: "PotionBottle"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysRainbowWand" }, New: { Group: "ItemHandheld", Name: "RainbowWand"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysRopeCoilLong" }, New: { Group: "ItemHandheld", Name: "RopeCoilLong"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysRopeCoilShort" }, New: { Group: "ItemHandheld", Name: "RopeCoilShort"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysRuler" }, New: { Group: "ItemHandheld", Name: "Ruler"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysScissors" }, New: { Group: "ItemHandheld", Name: "Scissors"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysShockRemote" }, New: { Group: "ItemHandheld", Name: "ShockRemote"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysShockWand" }, New: { Group: "ItemHandheld", Name: "ShockWand"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysSmallDildo" }, New: { Group: "ItemHandheld", Name: "SmallDildo"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysSmallVibratingWand" }, New: { Group: "ItemHandheld", Name: "SmallVibratingWand"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysSpatula" }, New: { Group: "ItemHandheld", Name: "Spatula"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysSword" }, New: { Group: "ItemHandheld", Name: "Sword"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysTapeRoll" }, New: { Group: "ItemHandheld", Name: "TapeRoll"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysTennisRacket" }, New: { Group: "ItemHandheld", Name: "TennisRacket"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysForSaleSign" }, New: { Group: "ItemHandheld", Name: "ForSaleSign"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysToothbrush" }, New: { Group: "ItemHandheld", Name: "Toothbrush"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysTowel" }, New: { Group: "ItemHandheld", Name: "Towel"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysVibeRemote" }, New: { Group: "ItemHandheld", Name: "VibeRemote"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysVibratingWand" }, New: { Group: "ItemHandheld", Name: "VibratingWand"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysVibrator" }, New: { Group: "ItemHandheld", Name: "Vibrator"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysWartenbergWheel" }, New: { Group: "ItemHandheld", Name: "WartenbergWheel"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysWhip" }, New: { Group: "ItemHandheld", Name: "Whip"} },
|
|
{ Old: { Group: "ItemHands", Name: "SpankingToysWhipPaddle" }, New: { Group:"ItemHandheld", Name:"WhipPaddle" } },
|
|
{ Old: { Group: "Pussy", Name: "PussyLight1" }, New: { Group: "Pussy", Name: "Pussy1" } },
|
|
{ Old: { Group: "Pussy", Name: "PussyLight2" }, New: { Group: "Pussy", Name: "Pussy2" } },
|
|
{ Old: { Group: "Pussy", Name: "PussyLight3" }, New: { Group: "Pussy", Name: "Pussy3" } },
|
|
{ Old: { Group: "Pussy", Name: "PussyDark1" }, New: { Group: "Pussy", Name: "Pussy1" } },
|
|
{ Old: { Group: "Pussy", Name: "PussyDark2" }, New: { Group: "Pussy", Name: "Pussy2" } },
|
|
{ Old: { Group: "Pussy", Name: "PussyDark3" }, New: { Group: "Pussy", Name: "Pussy3" } },
|
|
{ Old: { Group: "LeftAnklet", Name: "*" }, New: { Group: "AnkletLeft" }},
|
|
{ Old: { Group: "RightAnklet", Name: "*" }, New: { Group: "AnkletRight" }},
|
|
{ Old: { Group: "LeftHand", Name: "*" }, New: { Group: "HandAccessoryLeft" }},
|
|
{ Old: { Group: "RightHand", Name: "*" }, New: { Group: "HandAccessoryRight" }},
|
|
];
|
|
|
|
/**
|
|
* Perform the inventory fixups needed.
|
|
* @param {InventoryBundle[]} Inventory - The server-provided inventory object
|
|
*/
|
|
function LoginPerformInventoryFixups(Inventory) {
|
|
// Skip fixups on new characters
|
|
if (!Inventory) return;
|
|
|
|
for (const fixup of LoginInventoryFixups) {
|
|
// For every asset fixup to do, update the inventory
|
|
const item = Inventory.find(i => i.Group === fixup.Old.Group && (i.Name === fixup.Old.Name || fixup.Old.Name === "*"));
|
|
if (item) {
|
|
item.Group = fixup.New.Group;
|
|
if (fixup.New.Name) {
|
|
item.Name = fixup.New.Name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform the appearance fixups needed.
|
|
* TODO: only typed items are supported.
|
|
* @param {ItemBundle[]} Appearance - The server-provided appearance object
|
|
* @return {boolean}
|
|
*/
|
|
function LoginPerformAppearanceFixups(Appearance) {
|
|
if (!Appearance) return;
|
|
|
|
let fixedUp = false;
|
|
for (const fixup of LoginInventoryFixups) {
|
|
const idx = Appearance.findIndex(a => a.Group === fixup.Old.Group && (a.Name === fixup.Old.Name || fixup.Old.Name === "*"));
|
|
if (idx != -1) {
|
|
// The item is currently worn, remove it
|
|
let worn = Appearance[idx];
|
|
Appearance.splice(idx, 1);
|
|
|
|
// There're already something else in that slot, preserve it
|
|
if (Appearance.find(a => a.Group === fixup.New.Group)) {
|
|
continue;
|
|
}
|
|
|
|
// Set up the new item and its properties
|
|
worn.Group = fixup.New.Group;
|
|
if (fixup.New.Name) {
|
|
worn.Name = fixup.New.Name;
|
|
}
|
|
|
|
const asset = AssetGet("Female3DCG", worn.Group, worn.Name);
|
|
let opt = null;
|
|
if (asset.Archetype) {
|
|
switch (asset.Archetype) {
|
|
case ExtendedArchetype.TYPED:
|
|
{
|
|
const opts = TypedItemGetOptions(worn.Group, worn.Name);
|
|
if (typeof fixup.New.Option === "undefined")
|
|
opt = opts[0];
|
|
else
|
|
opt = opts.find(o => o.Name === fixup.New.Option);
|
|
|
|
if (!opt) {
|
|
console.error(`Unknown option ${fixup.New.Option}`);
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Replace old previous properties with the wanted ones
|
|
if (opt && opt.Property)
|
|
worn.Property = Object.assign(opt.Property);
|
|
} else if (asset.Extended) {
|
|
// Old-style extended item
|
|
|
|
} else {
|
|
delete worn.Property;
|
|
}
|
|
|
|
// Push back the updated data
|
|
Appearance.push(worn);
|
|
fixedUp = true;
|
|
}
|
|
}
|
|
return fixedUp;
|
|
}
|
|
|
|
/**
|
|
* Perform the crafting fixups needed
|
|
* @param {readonly CraftingItem[]} Crafting - The server-provided, uncompressed crafting data
|
|
*/
|
|
function LoginPerformCraftingFixups(Crafting) {
|
|
if (!Crafting || !CommonIsArray(Crafting)) return;
|
|
|
|
for (const fixup of LoginInventoryFixups) {
|
|
// Move crafts over to the new name
|
|
for (const craft of Crafting) {
|
|
if (!craft || craft.Item !== fixup.Old.Name) continue;
|
|
craft.Item = fixup.New.Name;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make sure the slave collar is equipped or unequipped based on the owner
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginValidCollar() {
|
|
let item = InventoryGet(Player, "ItemNeck");
|
|
if (!Player.IsFullyOwned() || LogQuery("ReleasedCollar", "OwnerRule")) {
|
|
// Not owned, or currently released from the collar, make sure we don't have it equipped
|
|
if (item && item.Asset.Name == "SlaveCollar") {
|
|
InventoryRemove(Player, "ItemNeck");
|
|
item = null;
|
|
if (CurrentScreen == "ChatRoom") {
|
|
ChatRoomCharacterItemUpdate(Player, "ItemNeck");
|
|
ChatRoomCharacterItemUpdate(Player, "ItemNeckAccessories");
|
|
ChatRoomCharacterItemUpdate(Player, "ItemNeckRestraints");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Player.IsFullyOwned() && !LogQuery("ReleasedCollar", "OwnerRule")) {
|
|
// Owned, but not currently released from the collar
|
|
if (item && !["SlaveCollar", "ClubSlaveCollar"].includes(item.Asset.Name)) {
|
|
// We're wearing something else, yank that
|
|
InventoryRemove(Player, "ItemNeck");
|
|
item = null;
|
|
if (CurrentScreen == "ChatRoom") ChatRoomCharacterItemUpdate(Player, "ItemNeck");
|
|
}
|
|
|
|
if (!item) {
|
|
// Now make sure we're wearing the slave collar
|
|
InventoryWear(Player, "SlaveCollar", "ItemNeck");
|
|
if (CurrentScreen == "ChatRoom") ChatRoomCharacterItemUpdate(Player, "ItemNeck");
|
|
}
|
|
}
|
|
|
|
// Check if we should switch to the club slave collar
|
|
if (LogQuery("ClubSlave", "Management") && !InventoryIsWorn(Player, "ClubSlaveCollar", "ItemNeck"))
|
|
InventoryWear(Player, "ClubSlaveCollar", "ItemNeck");
|
|
}
|
|
|
|
/**
|
|
* Adds or confiscates Club Mistress items from the player. Only players that are club Mistresses can have the Mistress
|
|
* Padlock & Key
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginMistressItems() {
|
|
if (LogQuery("ClubMistress", "Management")) {
|
|
InventoryAdd(Player, "MistressGloves", "Gloves", false);
|
|
InventoryAdd(Player, "MistressBoots", "Shoes", false);
|
|
InventoryAdd(Player, "MistressTop", "Cloth", false);
|
|
InventoryAdd(Player, "MistressBottom", "ClothLower", false);
|
|
InventoryAdd(Player, "MistressPadlock", "ItemMisc", false);
|
|
InventoryAdd(Player, "MistressPadlockKey", "ItemMisc", false);
|
|
InventoryAdd(Player, "MistressTimerPadlock", "ItemMisc", false);
|
|
InventoryAdd(Player, "DeluxeBoots", "Shoes", false);
|
|
} else {
|
|
InventoryDelete(Player, "MistressPadlock", "ItemMisc", false);
|
|
InventoryDelete(Player, "MistressPadlockKey", "ItemMisc", false);
|
|
InventoryDelete(Player, "MistressTimerPadlock", "ItemMisc", false);
|
|
InventoryDelete(Player, "MistressGloves", "Gloves", false);
|
|
InventoryDelete(Player, "MistressBoots", "Shoes", false);
|
|
InventoryDelete(Player, "MistressTop", "Cloth", false);
|
|
InventoryDelete(Player, "MistressBottom", "ClothLower", false);
|
|
InventoryDelete(Player, "DeluxeBoots", "Shoes", false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Give the matching RewardMemberNumber Club Card to the player
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginClubCard() {
|
|
|
|
// Loops in all the cards to see if there's a card that matches the player MembeNumber
|
|
for (let Card of ClubCardList)
|
|
if (Player.MemberNumber === Card.RewardMemberNumber) {
|
|
|
|
// Make sure the proper objects are created
|
|
Player.Game.ClubCard ??= { Deck: [], Reward: "" };
|
|
if (!CommonIsArray(Player.Game.ClubCard.Deck)) Player.Game.ClubCard.Deck = [];
|
|
if (typeof Player.Game.ClubCard.Reward !== "string") Player.Game.ClubCard.Reward = "";
|
|
|
|
// If the player doesn't have that card, we add it
|
|
let Char = String.fromCharCode(Card.ID);
|
|
if (Player.Game.ClubCard.Reward.indexOf(Char) < 0) {
|
|
Player.Game.ClubCard.Reward = Player.Game.ClubCard.Reward + Char;
|
|
ServerAccountUpdate.QueueData({ Game: Player.Game }, true);
|
|
}
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Adds or confiscates pony equipment from the player. Only players that are ponies or trainers can have the pony
|
|
* equipment.
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginStableItems() {
|
|
if (LogQuery("JoinedStable", "PonyExam") || LogQuery("JoinedStable", "TrainerExam")) {
|
|
InventoryAdd(Player, "HarnessPonyBits", "ItemMouth", false);
|
|
InventoryAdd(Player, "HarnessPonyBits", "ItemMouth2", false);
|
|
InventoryAdd(Player, "HarnessPonyBits", "ItemMouth3", false);
|
|
InventoryAdd(Player, "PonyBoots", "Shoes", false);
|
|
InventoryAdd(Player, "PonyBoots", "ItemBoots", false);
|
|
InventoryAdd(Player, "PonyHood", "ItemHood", false);
|
|
InventoryAdd(Player, "HoofMittens", "ItemHands", false);
|
|
} else {
|
|
InventoryDelete(Player, "HarnessPonyBits", "ItemMouth", false);
|
|
InventoryDelete(Player, "HarnessPonyBits", "ItemMouth2", false);
|
|
InventoryDelete(Player, "HarnessPonyBits", "ItemMouth3", false);
|
|
InventoryDelete(Player, "PonyBoots", "Shoes", false);
|
|
InventoryDelete(Player, "PonyBoots", "ItemBoots", false);
|
|
InventoryDelete(Player, "PonyHood", "ItemHood", false);
|
|
InventoryDelete(Player, "HoofMittens", "ItemHands", false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds or confiscates maid items from the player. Only players that have joined the Maid Sorority can have these items.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function LoginMaidItems() {
|
|
if (LogQuery("JoinedSorority", "Maid")) {
|
|
InventoryAdd(Player, "MaidOutfit1", "Cloth", false);
|
|
InventoryAdd(Player, "MaidOutfit2", "Cloth", false);
|
|
InventoryAdd(Player, "MaidHairband1", "Cloth", false);
|
|
InventoryAdd(Player, "MaidApron1", "Cloth", false);
|
|
InventoryAdd(Player, "MaidApron2", "Cloth", false);
|
|
InventoryAdd(Player, "FrillyApron", "ClothAccessory", false);
|
|
InventoryAdd(Player, "MaidHairband1", "Hat", false);
|
|
InventoryAdd(Player, "ServingTray", "ItemMisc", false);
|
|
} else {
|
|
InventoryDelete(Player, "MaidOutfit1", "Cloth", false);
|
|
InventoryDelete(Player, "MaidOutfit2", "Cloth", false);
|
|
InventoryDelete(Player, "MaidHairband1", "Cloth", false);
|
|
InventoryDelete(Player, "MaidApron1", "Cloth", false);
|
|
InventoryDelete(Player, "MaidApron2", "Cloth", false);
|
|
InventoryDelete(Player, "FrillyApron", "ClothAccessory", false);
|
|
InventoryDelete(Player, "MaidHairband1", "Hat", false);
|
|
InventoryDelete(Player, "ServingTray", "ItemMisc", false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures lover-exclusive items are removed if the player has no lovers.
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginLoversItems() {
|
|
const LoversNumbers = Player.GetLoversNumbers();
|
|
|
|
// check to remove love leather collar slave collar if no lover
|
|
// Note that as the Slave collar isn't yet an archetypal asset, that's why it gets skipped by validation
|
|
if (LoversNumbers.length < 1) {
|
|
const Collar = InventoryGet(Player,"ItemNeck");
|
|
if (Collar && Collar.Property && (Collar.Asset.Name == "SlaveCollar") && (Collar.Property.Type == "LoveLeatherCollar")) {
|
|
Collar.Property = CommonCloneDeep(InventoryItemNeckSlaveCollarTypes[0].Property);
|
|
Collar.Color = "Default";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds or removes Asylum items. Only players that have previously maxed out their patient or nurse reputation are
|
|
* eligible for their own set of Asylum restraints outside the Asylum.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function LoginAsylumItems() {
|
|
if (LogQuery("ReputationMaxed", "Asylum")) {
|
|
InventoryAddMany(Player, [
|
|
{Name: "MedicalBedRestraints", Group: "ItemArms"},
|
|
{Name: "MedicalBedRestraints", Group: "ItemLegs"},
|
|
{Name: "MedicalBedRestraints", Group: "ItemFeet"},
|
|
], false);
|
|
} else {
|
|
InventoryDelete(Player, "MedicalBedRestraints", "ItemArms", false);
|
|
InventoryDelete(Player, "MedicalBedRestraints", "ItemLegs", false);
|
|
InventoryDelete(Player, "MedicalBedRestraints", "ItemFeet", false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds items if specific cheats are enabled
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function LoginCheatItems() {
|
|
if (CheatFactor("FreeCollegeOutfit", 0) == 0) {
|
|
InventoryAdd(Player, "CollegeOutfit1", "Cloth");
|
|
InventoryAdd(Player, "CollegeSkirt", "ClothLower");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks every owned item to see if its BuyGroup contains an item the player does not have. This allows the player to
|
|
* collect any items that have been added to the game which are in a BuyGroup that they have already purchased.
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginValideBuyGroups() {
|
|
for (let A = 0; A < Asset.length; A++)
|
|
if ((Asset[A].BuyGroup != null) && InventoryAvailable(Player, Asset[A].Name, Asset[A].Group.Name))
|
|
for (let B = 0; B < Asset.length; B++)
|
|
if ((Asset[B] != null) && (Asset[B].BuyGroup != null) && (Asset[B].BuyGroup == Asset[A].BuyGroup) && !InventoryAvailable(Player, Asset[B].Name, Asset[B].Group.Name))
|
|
InventoryAdd(Player, Asset[B].Name, Asset[B].Group.Name, false);
|
|
}
|
|
|
|
/**
|
|
* Makes sure the difficulty restrictions are applied to the player
|
|
* @param {boolean} applyDefaults - If changing to the difficulty, set this to True to set LimitedItems to the default settings
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginDifficulty(applyDefaults) {
|
|
|
|
// If Extreme mode, the player cannot control her blocked items
|
|
if (Player.GetDifficulty() >= 3) {
|
|
LoginExtremeItemSettings(applyDefaults);
|
|
ServerPlayerBlockItemsSync();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the item permissions for the Extreme difficulty
|
|
* @param {boolean} applyDefaults - When initially changing to extreme/whitelist, TRUE sets strong locks to limited permissions. When enforcing
|
|
* settings, FALSE allows them to remain as they are since the player could have changed them to fully open.
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginExtremeItemSettings(applyDefaults) {
|
|
const LimitedAssets = new Set(MainHallStrongLocks.map(i => `ItemMisc/${i}`));
|
|
for (const [name, permission] of CommonEntries(Player.PermissionItems)) {
|
|
permission.Hidden = false;
|
|
const limitedAllowed = LimitedAssets.has(name);
|
|
|
|
for (const [type, typePermission] of Object.entries(permission.TypePermissions)) {
|
|
if (typePermission !== "Favorite") {
|
|
delete permission.TypePermissions[type];
|
|
}
|
|
}
|
|
|
|
switch (permission.Permission) {
|
|
case "Block":
|
|
permission.Permission = "Default";
|
|
break;
|
|
case "Limited":
|
|
if (!limitedAllowed) {
|
|
permission.Permission = "Default";
|
|
}
|
|
break;
|
|
case "Default":
|
|
// If the item permissions are 3 = "Owner/Lover/Whitelist" don't limit the locks, since that just blocks whitelisted players
|
|
if (limitedAllowed && applyDefaults && Player.ItemPermission <= 3) {
|
|
permission.Permission = "Limited";
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles server response, when login has been queued
|
|
* @param {number} Pos - The position in queue
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginQueue(Pos) {
|
|
if (typeof Pos !== "number") return;
|
|
LoginQueuePosition = Pos;
|
|
}
|
|
|
|
/**
|
|
* Fixes the Owner property on the player object if it's wrongly set
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginFixOwner() {
|
|
if ((Player.IsOwned() === false) && (Player.Owner !== "") && !Player.Owner.trim().startsWith("NPC-")) {
|
|
Player.Owner = "";
|
|
ServerAccountUpdate.QueueData({ Owner: Player.Owner });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the player character info from the server data
|
|
* @param {ServerAccountData} C
|
|
*/
|
|
function LoginSetupPlayer(C) {
|
|
Player.Name = C.Name;
|
|
Player.AccountName = C.AccountName;
|
|
Player.AssetFamily = C.AssetFamily ?? "Female3DCG";
|
|
Player.Title = C.Title;
|
|
Player.Nickname = typeof C.Nickname === "string" ? C.Nickname : "";
|
|
Player.Money = CommonIsNumeric(C.Money) ? C.Money : 0;
|
|
Player.Owner = typeof C.Owner === "string" ? C.Owner : "";
|
|
Player.Game = CommonIsObject(C.Game) ? C.Game : {};
|
|
if (typeof C.Description === "string" && C.Description.startsWith(ONLINE_PROFILE_DESCRIPTION_COMPRESSION_MAGIC)) {
|
|
const desc = LZString.decompressFromUTF16(C.Description.substring(1)) ?? "";
|
|
Player.Description = desc.substring(0, 10000);
|
|
} else {
|
|
Player.Description = "";
|
|
}
|
|
Player.Creation = C.Creation;
|
|
Player.Wardrobe = C.Wardrobe ? CharacterDecompressWardrobe(C.Wardrobe) : [];
|
|
WardrobeFixLength();
|
|
Player.WardrobeCharacterNames = C.WardrobeCharacterNames ?? [];
|
|
Player.CharacterID = C.ID.toString();
|
|
Player.OnlineID = C.ID.toString();
|
|
Player.MemberNumber = C.MemberNumber;
|
|
Player.Difficulty = ServerAccountDataSyncedValidate.Difficulty(C.Difficulty, Player);
|
|
const { permissions, shouldSync } = ServerUnPackItemPermissions(C, Player.Difficulty.Level >= 3);
|
|
Player.PermissionItems = permissions;
|
|
if (shouldSync) {
|
|
ServerPlayerBlockItemsSync();
|
|
}
|
|
Player.ChatSearchFilterTerms = C.ChatSearchFilterTerms ?? "";
|
|
|
|
|
|
// Sets the default language when creating or searching for chat rooms
|
|
ChatAdminDefaultLanguage = C.RoomCreateLanguage ?? "EN";
|
|
if (ChatAdminDefaultLanguage == null) ChatAdminDefaultLanguage = ChatAdminLanguageList[0];
|
|
if (ChatAdminLanguageList.indexOf(ChatAdminDefaultLanguage) < 0) ChatAdminDefaultLanguage = ChatAdminLanguageList[0];
|
|
ChatSearchLanguage = C.RoomSearchLanguage ?? "";
|
|
if (ChatSearchLanguage == null) ChatSearchLanguage = "";
|
|
if (ChatSearchLanguage && ChatAdminLanguageList.indexOf(ChatSearchLanguage) < 0) ChatSearchLanguage = "";
|
|
|
|
// Load the last chat room
|
|
Player.LastMapData = C.LastMapData ?? undefined;
|
|
if (C.LastChatRoom != null && typeof C.LastChatRoom !== "string") {
|
|
// Backward compatability: Convert old-style "Private" to "Visibility" and "Locked" to "Access"
|
|
// @ts-ignore
|
|
if (typeof C.LastChatRoom.Private === "boolean")
|
|
// @ts-ignore
|
|
C.LastChatRoom.Visibility = C.LastChatRoom.Private ? ChatRoomVisibilityMode.UNLISTED : ChatRoomVisibilityMode.PUBLIC;
|
|
// @ts-ignore
|
|
if (typeof C.LastChatRoom.Locked === "boolean")
|
|
// @ts-ignore
|
|
C.LastChatRoom.Access = C.LastChatRoom.Locked ? ChatRoomAccessMode.ADMIN : ChatRoomAccessMode.PUBLIC;
|
|
|
|
if (ChatRoomValidateProperties(C.LastChatRoom))
|
|
Player.LastChatRoom = C.LastChatRoom;
|
|
} else if (typeof C.LastChatRoom === "string" && C.LastChatRoom) {
|
|
// Backward compatibility: Automatically convert old-style data to a real ChatRoom object
|
|
/** @type {ChatRoomSettings} */
|
|
const room = {
|
|
Name: C.LastChatRoom,
|
|
Description: C.LastChatRoomDesc,
|
|
Admin: typeof C.LastChatRoomAdmin == "string" ? CommonConvertStringToArray(C.LastChatRoomAdmin) : [],
|
|
Whitelist: typeof C.LastChatRoomWhitelist == "string" ? CommonConvertStringToArray(C.LastChatRoomWhitelist) : [],
|
|
Ban: typeof C.LastChatRoomBan == "string" ? CommonConvertStringToArray(C.LastChatRoomBan) : [],
|
|
Background: C.LastChatRoomBG,
|
|
Limit: C.LastChatRoomSize,
|
|
Game: "",
|
|
Visibility: C.LastChatRoomPrivate ? ChatRoomVisibilityMode.UNLISTED : ChatRoomVisibilityMode.PUBLIC,
|
|
Access: ChatRoomVisibilityMode.PUBLIC,
|
|
BlockCategory: C.LastChatRoomBlockCategory,
|
|
Space: C.LastChatRoomSpace,
|
|
Language: C.LastChatRoomLanguage,
|
|
Custom: C.LastChatRoomCustom,
|
|
MapData: C.LastChatRoomMapData,
|
|
};
|
|
|
|
if (ChatRoomValidateProperties(room)) {
|
|
Player.LastChatRoom = room;
|
|
}
|
|
}
|
|
|
|
// Loads the ownership data
|
|
Player.Ownership = ServerAccountDataSyncedValidate.Ownership(C.Ownership);
|
|
if (Player.Ownership != null) {
|
|
Player.Owner = (Player.Ownership.Stage == 1) ? Player.Ownership.Name : "";
|
|
}
|
|
|
|
// @ts-expect-error: FIXME: Property 'Name' is optional in type 'ServerLovership' but required in type 'Lovership'
|
|
// Ensures lovership data is compatible and converts lovers to lovership
|
|
Player.Lovership = Array.isArray(C.Lovership) ? C.Lovership : C.Lovership != undefined ? [C.Lovership] : [];
|
|
if ((C.Lover != null) && (C.Lover != "undefined") && C.Lover.startsWith("NPC-")) {
|
|
Player.Lover = C.Lover;
|
|
ServerPlayerSync();
|
|
}
|
|
|
|
// Calls the preference init to make sure the preferences are loaded correctly
|
|
PreferenceInitPlayer(Player, C);
|
|
Player.ItemPermission = C.ItemPermission ?? 2;
|
|
Player.KinkyDungeonExploredLore = C.KinkyDungeonExploredLore;
|
|
Player.SavedExpressions = C.SavedExpressions ?? [];
|
|
PreferenceValidateArousalData(Player);
|
|
if (!Array.isArray(Player.SavedExpressions))
|
|
Player.SavedExpressions = [];
|
|
if (Player.SavedExpressions.length < 5)
|
|
for (let x = Player.SavedExpressions.length; x < 5; x++)
|
|
Player.SavedExpressions.push(null);
|
|
|
|
// Load Favorited Colors
|
|
if (!CommonIsArray(C.SavedColors)) {
|
|
Player.SavedColors = [];
|
|
for (let i = 0; i < ColorPickerNumSaved; i++) {
|
|
|
|
if (typeof Player.SavedColors[i] != "object" || isNaN(Player.SavedColors[i].H) || isNaN(Player.SavedColors[i].S) || isNaN(Player.SavedColors[i].V))
|
|
Player.SavedColors[i] = GetDefaultSavedColors()[i];
|
|
}
|
|
Player.SavedColors.length = ColorPickerNumSaved;
|
|
} else {
|
|
Player.SavedColors = C.SavedColors ?? [];
|
|
}
|
|
|
|
// Loads the online lists
|
|
Player.WhiteList = Array.isArray(C.WhiteList) ? C.WhiteList : [];
|
|
Player.BlackList = Array.isArray(C.BlackList) ? C.BlackList : [];
|
|
Player.FriendList = Array.isArray(C.FriendList) ? C.FriendList : [];
|
|
Player.GhostList = Array.isArray(C.GhostList) ? C.GhostList : [];
|
|
|
|
// Attempt to parse friend namespace
|
|
let friendNames = undefined;
|
|
if (typeof C.FriendNames === "string") {
|
|
try {
|
|
const data = LZString.decompressFromUTF16(C.FriendNames);
|
|
if (data === null) throw new Error();
|
|
const json = /** @type {[number, string][]} */(JSON.parse(data));
|
|
friendNames = new Map(json);
|
|
} catch (err) {
|
|
console.warn("An error occured while parsing friendnames, entries have been reset.");
|
|
}
|
|
}
|
|
Player.FriendNames = friendNames ?? new Map();
|
|
|
|
let submissivesList = undefined;
|
|
if (typeof C.SubmissivesList === "string") {
|
|
try {
|
|
const data = LZString.decompressFromUTF16(C.SubmissivesList);
|
|
if (data === null) throw new Error();
|
|
const json = JSON.parse(data);
|
|
if (!CommonIsArray(json)) throw new Error();
|
|
const numbers = /** @type {number[]} */(json.filter(Number));
|
|
submissivesList = new Set(numbers);
|
|
} catch (err) {
|
|
console.warn("An error occured while parsing submissives, entries have been reset.");
|
|
}
|
|
}
|
|
Player.SubmissivesList = submissivesList ?? new Set();
|
|
Player.Infiltration = C.Infiltration;
|
|
LoginDifficulty(false);
|
|
|
|
// Loads the crafting data
|
|
const CraftingDecompressed = CraftingDecompressServerData(C.Crafting);
|
|
|
|
// Loads the inventory data
|
|
if (typeof C.InventoryData === "string" && C.InventoryData !== "") {
|
|
// We keep track of that to be able to tell if there's been changes in the inventory
|
|
Player.InventoryData = C.InventoryData;
|
|
}
|
|
const loadedInventory = InventoryLoad(C.Inventory ?? "", C.InventoryData ?? "");
|
|
LoginPerformInventoryFixups(loadedInventory);
|
|
const fixedUp = LoginPerformAppearanceFixups(C.Appearance ?? []);
|
|
LoginPerformCraftingFixups(CraftingDecompressed);
|
|
InventoryAddMany(Player, loadedInventory, false);
|
|
ServerPlayerInventorySync();
|
|
const updated = ServerAppearanceLoadFromBundle(Player, C.AssetFamily, C.Appearance ?? [], C.MemberNumber);
|
|
if (fixedUp || updated) {
|
|
// Refresh the character server-side if we had to tweak the appearance
|
|
ServerPlayerAppearanceSync();
|
|
}
|
|
|
|
// Loads the current appearance, log, reputation and skills
|
|
LogLoad(C.Log ?? []);
|
|
ReputationLoad(C.Reputation);
|
|
SkillLoad(C.Skill);
|
|
Player.ConfiscatedItems = C.ConfiscatedItems ?? [];
|
|
Player.ExtensionSettings = C.ExtensionSettings ?? {};
|
|
|
|
if (Player.VisualSettings.PrivateRoomBackground) PrivateBackground = Player.VisualSettings.PrivateRoomBackground;
|
|
if (Player.VisualSettings.MainHallBackground) MainHallBackground = Player.VisualSettings.MainHallBackground;
|
|
|
|
PrivateCharacterMax = 4 + (LogQuery("Expansion", "PrivateRoom") ? 4 : 0) + (LogQuery("SecondExpansion", "PrivateRoom") ? 4 : 0);
|
|
CharacterRefresh(Player, false);
|
|
if (ManagementIsClubSlave()) CharacterNaked(Player);
|
|
|
|
// Starts the game in the main hall while loading characters in the private room
|
|
PrivateCharacter = [];
|
|
PrivateCharacter.push(Player);
|
|
let shouldSyncPrivate = false;
|
|
if (C.PrivateCharacter != null)
|
|
for (let P = 0; P < C.PrivateCharacter.length; P++)
|
|
shouldSyncPrivate = PrivateLoadCharacter(C.PrivateCharacter[P]) || shouldSyncPrivate;
|
|
if (shouldSyncPrivate) {
|
|
ServerPrivateCharacterSync();
|
|
}
|
|
SarahSetStatus();
|
|
|
|
// Fixes a few items
|
|
var InventoryBeforeFixes = InventoryStringify(Player);
|
|
LoginFixOwner();
|
|
LoginValidCollar();
|
|
LoginMistressItems();
|
|
LoginClubCard();
|
|
LoginStableItems();
|
|
LoginMaidItems();
|
|
LoginLoversItems();
|
|
LoginAsylumItems();
|
|
LoginCheatItems();
|
|
LoginValideBuyGroups();
|
|
PrisonRestoreConfiscatedItems();
|
|
CraftingLoadServer(CraftingDecompressed); // Only run this _after_ `Player.Inventory` is fully ready
|
|
|
|
if (InventoryBeforeFixes != InventoryStringify(Player)) ServerPlayerInventorySync();
|
|
CharacterAppearanceValidate(Player);
|
|
AsylumGGTSSAddItems();
|
|
ChatRoomCustomized = ((Player.OnlineSettings != null) && (Player.OnlineSettings.ShowRoomCustomization != null) && (Player.OnlineSettings.ShowRoomCustomization >= 2));
|
|
if (Player.Crafting.length > 80) Player.Crafting = Player.Crafting.slice(0, 80);
|
|
}
|
|
|
|
/**
|
|
* Handles player login response data
|
|
* @param {ServerLoginResponse} C - The Login response data - this will either be the player's character data if the
|
|
* login was successful, or a string error message if the login failed.
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginResponse(C) {
|
|
LoginCheckES2020();
|
|
|
|
if (typeof C === "string") {
|
|
LoginSetStatus(C, true);
|
|
return;
|
|
}
|
|
|
|
// The response must contain a name, an ID and an account name
|
|
if (!(CommonIsObject(C)
|
|
&& typeof C.AccountName === "string" && !!C.AccountName.length
|
|
&& typeof C.Name === "string" && !!C.Name.length
|
|
&& typeof C.ID === "string" && !!C.ID.length)) {
|
|
LoginSetStatus("ErrorLoadingCharacterData", true);
|
|
return;
|
|
}
|
|
|
|
// In relog mode, we jump back to the previous screen, keeping the current game flow
|
|
if (RelogData) {
|
|
Player.CharacterID = C.ID.toString();
|
|
Player.OnlineID = C.ID.toString();
|
|
CurrentCharacter = RelogData.Character;
|
|
|
|
if (RelogData.ChatRoomName) {
|
|
CommonSetScreen("Online", "ChatSearch");
|
|
ServerSend("ChatRoomJoin", { Name: RelogData.ChatRoomName });
|
|
} else {
|
|
const screen = /** @type {ScreenSpecifier} */([RelogData.Module, RelogData.Screen]);
|
|
CommonSetScreen(...screen);
|
|
}
|
|
return;
|
|
}
|
|
|
|
CharacterCreatePlayer();
|
|
|
|
// In regular mode, we set the account properties for a new club session
|
|
LoginSetupPlayer(C);
|
|
|
|
// Enables the AFK timer for the current player only
|
|
AfkTimerSetEnabled(Player.OnlineSettings.EnableAfkTimer);
|
|
ActivitySetArousal(Player, 0);
|
|
ActivityTimerProgress(Player, 0);
|
|
NotificationLoad();
|
|
|
|
if (ManagementIsClubSlave()) CharacterNaked(Player);
|
|
|
|
// We're done loading, now start the player in whatever screen is appropriate
|
|
if (LogQuery("Locked", "Cell")) {
|
|
// The player has been locked up, they must log back in the cell
|
|
CommonSetScreen("Room", "Cell");
|
|
} else if ((Player.Infiltration?.Punishment?.Timer ?? 0) > CurrentTime) {
|
|
// The player must log back in Pandora's Box prison
|
|
PandoraWillpower = 0;
|
|
InfiltrationDifficulty = Player.Infiltration.Punishment.Difficulty;
|
|
CommonSetScreen("Room", "PandoraPrison");
|
|
} else if (LogQuery("Committed", "Asylum") || LogQuery("Isolated", "Asylum") || (AsylumGGTSGetLevel(Player) >= 6)) {
|
|
// The player must log back in the asylum
|
|
if (AsylumGGTSGetLevel(Player) <= 5) {
|
|
AsylumEntranceWearPatientClothes(Player, true);
|
|
} else {
|
|
AsylumGGTSDroneDress(Player);
|
|
}
|
|
CommonSetScreen("Room", "AsylumBedroom");
|
|
} else if (LogValue("ForceGGTS", "Asylum") > 0) {
|
|
// The owner is forcing the player to do GGTS
|
|
CommonSetScreen("Room", "AsylumEntrance");
|
|
} else if (LogQuery("SleepCage", "Rule") && Player.IsOwned() === "npc" && PrivateOwnerInRoom()) {
|
|
// The player must start in her room, in her cage
|
|
InventoryRemove(Player, "ItemFeet");
|
|
InventoryRemove(Player, "ItemLegs");
|
|
Player.Cage = true;
|
|
PoseSetActive(Player, "Kneel", true);
|
|
CommonSetScreen("Room", "Private");
|
|
} else {
|
|
CommonSetScreen("Room", "MainHall");
|
|
}
|
|
}
|
|
|
|
/** Check if the player's browser has ES2020 support */
|
|
function LoginCheckES2020() {
|
|
try {
|
|
// eslint-disable-next-line no-eval
|
|
eval("({})?.foo");
|
|
// eslint-disable-next-line no-eval
|
|
eval("null ?? null");
|
|
// eslint-disable-next-line no-eval
|
|
eval("let a = null; a ??= 1;");
|
|
if (Array.prototype.flatMap === undefined) {
|
|
throw new Error("Array.prototype.flatMap is undefined");
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
throw new Error(`Outdated browser error: If you see this message, it means that your browser lacks ES2020 support and will not be working in Bondage Club`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles player click events on the character login screen
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginClick() {
|
|
|
|
// Opens the cheat panel
|
|
if (CheatAllow && MouseIn(825, 800, 350, 60)) {
|
|
CommonSetScreen("Character", "Cheat");
|
|
}
|
|
|
|
// Opens the password reset screen
|
|
if (ServerIsConnected && MouseIn(825, 890, 350, 60)) {
|
|
CommonSetScreen("Character", "PasswordReset");
|
|
}
|
|
|
|
// If we must create a new character
|
|
if (ServerIsConnected && MouseIn(825, 710, 350, 60)) {
|
|
CommonSetScreen("Character", "Disclaimer");
|
|
}
|
|
|
|
// Try to login
|
|
if (MouseIn(775, 500, 200, 60)) {
|
|
const Name = ElementValue("InputName");
|
|
const Password = ElementValue("InputPassword");
|
|
LoginDoLogin(Name, Password);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles player keyboard events on the character login screen, "enter" will login
|
|
* @type {KeyboardEventListener}
|
|
*/
|
|
function LoginKeyDown(ev) {
|
|
if (CommonKey.IsPressed(ev, "Enter")) {
|
|
const Name = ElementValue("InputName");
|
|
const Password = ElementValue("InputPassword");
|
|
LoginDoLogin(Name, Password);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Attempt to log the user in based on their input username and password
|
|
* @param {string} Name
|
|
* @param {string} Password
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginDoLogin(Name, Password) {
|
|
// Ensure the login request is not sent twice
|
|
if (LoginSubmitted || !ServerIsConnected) return;
|
|
|
|
if (!ServerAccountPasswordRegex.test(Name) || !ServerAccountPasswordRegex.test(Password)) {
|
|
LoginSetStatus("InvalidNamePassword");
|
|
return;
|
|
}
|
|
LoginStatusReset();
|
|
LoginSubmitted = true;
|
|
ServerSend("AccountLogin", { AccountName: Name, Password: Password });
|
|
}
|
|
|
|
/**
|
|
* Resets the login submission state
|
|
* @returns {void} Nothing
|
|
*/
|
|
function LoginStatusReset() {
|
|
LoginSubmitted = false;
|
|
LoginQueuePosition = -1;
|
|
LoginErrorMessage = "";
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} [ErrorMessage] - the login error message to set if the login is invalid - if not specified, will clear the login error message
|
|
*/
|
|
function LoginSetStatus(ErrorMessage = "", reset = false) {
|
|
if (reset) {
|
|
LoginStatusReset();
|
|
}
|
|
LoginErrorMessage = ErrorMessage ?? "";
|
|
}
|
|
|
|
/**
|
|
* Retrieves the correct message key based on the current state of the login page
|
|
* @returns {string | null} The current login status, or null if we're not currently attempting to log in
|
|
*/
|
|
function LoginGetStatus() {
|
|
if (LoginErrorMessage) {
|
|
return TextGet(LoginErrorMessage);
|
|
} else if (!ServerIsConnected) {
|
|
return TextGet("ConnectingToServer");
|
|
} else if (LoginQueuePosition !== -1) {
|
|
return TextGet("LoginQueueWait").replace("QUEUE_POS", `${LoginQueuePosition}`);
|
|
} else if (LoginSubmitted) {
|
|
return TextGet("ValidatingNamePassword");
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exit function - called when leaving the login page
|
|
* @type {ScreenFunctions["Exit"]}
|
|
*/
|
|
function LoginExit() {
|
|
|
|
}
|
|
|
|
/**
|
|
* Unload function - called when the login page unloads
|
|
*/
|
|
function LoginUnload() {
|
|
ElementRemove("InputName");
|
|
ElementRemove("InputPassword");
|
|
ElementRemove("LanguageDropdown");
|
|
CharacterDelete(LoginCharacter);
|
|
LoginCharacter = null;
|
|
}
|