Fix/Improvement: Many small fixes, documentation and formatting ()

* Formatting

* index.html JSDoc

* Documentation fixes
Add LogRecord type

* GLDraw docs

* Lint: LoginQueue to string

* Fix: GLDraw2DCanvasBlink alphaMasks

* Fix: Possible LogQueryRemote crash

* Lint: CommonDrawAppearanceBuild Color

* Lint: DynamicDrawOptions
This commit is contained in:
jomshir98 2021-06-29 17:13:36 +02:00 committed by GitHub
parent 3800430e26
commit 35c5cdab0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 357 additions and 157 deletions

View file

@ -84,7 +84,7 @@ var AssetFemale3DCG = [
]
},
{ Name: "TShirt1", HideItem: ["ItemNipplesPiercingsRoundPiercing", "ItemNipplesPiercingsWeightedPiercing", "ItemNipplesLactationPump", "BraRibbons", "ItemBreastRibbons"], Require: ["ClothLower"] },
{
{
Name: "TShirt2", Value: 25, DefaultColor: ["#333", "Default"], Hide: ["Bra"], HideItem: ["ItemNipplesPiercingsRoundPiercing", "ItemNipplesPiercingsWeightedPiercing", "ItemNipplesLactationPump", "ItemBreastRibbons"], Require: ["ClothLower"], Extended: true,
Layer: [
{ Name: "Shirt", AllowColorize: true, HasType: false },
@ -4165,7 +4165,8 @@ var AssetFemale3DCG = [
{ Name: "Text" },
]
},
{ Name: "ServingTray", Value: -1, Time: 5, Extended: true, Layer: [
{ Name: "ServingTray", Value: -1, Time: 5, Extended: true,
Layer: [
{ Name: "Tray", AllowColorize: false },
{ Name: "Objects", AllowTypes: ["Drinks", "Cake", "Cookies", "Toys"] },
{ Name: "Details", AllowTypes: ["Drinks", "Cake", "Toys"] },
@ -4302,7 +4303,7 @@ var AssetFemale3DCG = [
{ Name: "SmallDisplayCase", Priority: 58, Fetish: ["Metal"], Value: 40, Difficulty: -2, SelfBondage: 1, Time: 15, RemoveTime: 10, AllowLock: true, Audio: "LockLarge", Prerequisite: ["NotSuspended", "NotHogtied", "NotMounted", "NotKneelingSpread", "NoFeetSpreader"], SetPose: ["Kneel"], Effect: ["ForceKneel", "Prone", "Enclose", "DeafLight", "GagLight", "Freeze"], HideItem: ["ShoesFlippers"], Alpha: [{ Masks: [[1, 1, 70, 999], [420, 1, 80, 999]] }], RemoveAtLogin: true },
{ Name: "FuturisticCrate", Priority: 58, Category: ["SciFi"], Fetish: ["Metal"], Value: 70, Difficulty: -2, SelfBondage: 1, Time: 15, RemoveTime: 10, AllowLock: true, DrawLocks: false, Audio: "LockLarge", Prerequisite: ["NotSuspended"], LayerVisibility: true, Effect: [], AllowEffect: ["GagLight", "Freeze", "Prone", "BlindLight", "Enclose", "BlindNormal","BlindHeavy", "GagHeavy", "Freeze"], HideItem: ["ShoesFlippers"],
Alpha: [{ Masks: [[1, 1, 70, 999], [420, 1, 80, 999]] }], RemoveAtLogin: true, SetPose: ["BaseLower"], Extended: true, AllowType: ["Window", "SmallWindow", "Closed"],
DefaultColor: ["#93C48C", "#3B7F2C","Default", "Default", "#BBBBFF", "Default", ],
DefaultColor: ["#93C48C", "#3B7F2C","Default", "Default", "#BBBBFF", "Default", ],
Layer: [
{Name:"Body", Priority:1, HasType: false},
{Name:"Display", Priority:58, HasType: false},

View file

@ -10,6 +10,7 @@ var CharacterAppearanceAssets = [];
var CharacterAppearanceColorPickerGroupName = "";
var CharacterAppearanceColorPickerBackup = "";
var CharacterAppearanceColorPickerRefreshTimer = null;
/** @type {Character | null} */
var CharacterAppearanceSelection = null;
var CharacterAppearanceReturnRoom = "MainHall";
var CharacterAppearanceReturnModule = "Room";

View file

@ -4,10 +4,10 @@ var LoginMessage = "";
var LoginCredits = null;
var LoginCreditsPosition = 0;
var LoginThankYou = "";
var LoginThankYouList = ["Anna", "Aylea", "BlueEyedCat", "BlueWinter", "Brian", "Bryce", "Christian", "DarkStar", "Dini", "ElCriminal",
"Epona", "Escurse", "FanRunner", "Greendragon", "KamiKaze", "Kimuriel", "Longwave", "Michal", "Michel", "Mike",
"Mindtie", "Misa", "MrUniver", "Mzklopyu", "Nick", "Nightcore", "Overlord", "Rashiash", "Ray", "Remydy",
"Rika", "RobinHood", "Rutherford", "Ryner", "Samuel", "SeraDenoir", "Shadow", "SkyLord", "Stephanie", "Tam",
var LoginThankYouList = ["Anna", "Aylea", "BlueEyedCat", "BlueWinter", "Brian", "Bryce", "Christian", "DarkStar", "Dini", "ElCriminal",
"Epona", "Escurse", "FanRunner", "Greendragon", "KamiKaze", "Kimuriel", "Longwave", "Michal", "Michel", "Mike",
"Mindtie", "Misa", "MrUniver", "Mzklopyu", "Nick", "Nightcore", "Overlord", "Rashiash", "Ray", "Remydy",
"Rika", "RobinHood", "Rutherford", "Ryner", "Samuel", "SeraDenoir", "Shadow", "SkyLord", "Stephanie", "Tam",
"TopHat", "Trent", "Troubadix", "William", "Xepherio", "Yuna", "Yurei", "Znarf"];
var LoginThankYouNext = 0;
var LoginSubmitted = false;
@ -334,14 +334,14 @@ function LoginValidateArrays() {
Player.HiddenItems = CleanHiddenItems;
update = true;
}
var CleanFavoriteItems = AssetCleanArray(Player.FavoriteItems);
if (CleanFavoriteItems.length != Player.FavoriteItems.length) {
Player.FavoriteItems = CleanFavoriteItems;
update = true;
}
if (update)
ServerPlayerBlockItemsSync();
}
@ -384,7 +384,7 @@ function LoginExtremeItemSettings(applyDefaults) {
function LoginQueue(Pos) {
if (typeof Pos !== "number") return;
LoginMessage = TextGet("LoginQueueWait").replace("QUEUE_POS", Pos);
LoginMessage = TextGet("LoginQueueWait").replace("QUEUE_POS", `${Pos}`);
}
/**
@ -592,7 +592,7 @@ function LoginResponse(C) {
if (LogQuery("Locked", "Cell")) {
CommonSetScreen("Room", "Cell");
} else {
// If the player must log back in Pandora's Box prison
if ((Player.Infiltration != null) && (Player.Infiltration.Punishment != null) && (Player.Infiltration.Punishment.Timer != null) && (Player.Infiltration.Punishment.Timer > CurrentTime)) {
PandoraWillpower = 0;
@ -627,10 +627,10 @@ function LoginResponse(C) {
}
} else {
LoginStatusReset("ErrorLoadingCharacterData");
}
LoginStatusReset("ErrorLoadingCharacterData");
}
} else LoginStatusReset(C);
LoginUpdateMessage();
LoginUpdateMessage();
}
/**
@ -692,7 +692,7 @@ function LoginKeyDown() {
*/
function LoginDoLogin() {
// Ensure the login request is not sent twice
// Ensure the login request is not sent twice
if (!LoginSubmitted && ServerIsConnected) {
var Name = ElementValue("InputName");
var Password = ElementValue("InputPassword");
@ -702,7 +702,7 @@ function LoginDoLogin() {
ServerSend("AccountLogin", { AccountName: Name, Password: Password });
} else LoginStatusReset("InvalidNamePassword");
}
LoginUpdateMessage();
LoginUpdateMessage();
}
@ -711,20 +711,20 @@ function LoginDoLogin() {
* @returns {void} Nothing
*/
function LoginSetSubmitted() {
LoginSubmitted = true;
if (ServerIsConnected) LoginErrorMessage = "";
LoginSubmitted = true;
if (ServerIsConnected) LoginErrorMessage = "";
}
/**
* Resets the login submission state
* @param {boolean} IsRelog - whether or not we're on the relog screen
* @param {string} ErrorMessage - the login error message to set if the login is invalid - if not specified, will clear the login error message
* @param {string} [ErrorMessage] - the login error message to set if the login is invalid - if not specified, will clear the login error message
* @param {boolean} [IsRelog=false] - whether or not we're on the relog screen
* @returns {void} Nothing
*/
function LoginStatusReset(ErrorMessage, IsRelog) {
LoginSubmitted = false;
LoginIsRelog = !!IsRelog;
if (ErrorMessage) LoginErrorMessage = ErrorMessage;
LoginSubmitted = false;
LoginIsRelog = !!IsRelog;
if (ErrorMessage) LoginErrorMessage = ErrorMessage;
}
/**
@ -740,8 +740,8 @@ function LoginUpdateMessage() {
* @returns {string} The key of the message to display
*/
function LoginGetMessageKey() {
if (LoginErrorMessage) return LoginErrorMessage;
else if (!ServerIsConnected) return "ConnectingToServer";
else if (LoginSubmitted) return "ValidatingNamePassword";
else return LoginIsRelog ? "EnterPassword" : "EnterNamePassword";
if (LoginErrorMessage) return LoginErrorMessage;
else if (!ServerIsConnected) return "ConnectingToServer";
else if (LoginSubmitted) return "ValidatingNamePassword";
else return LoginIsRelog ? "EnterPassword" : "EnterNamePassword";
}

View file

@ -2927,7 +2927,7 @@ function ChatRoomGetOwnerRule(RuleType) { return ChatRoomGetRule(RuleType, "Owne
* Gets a rule from the current character
* @param {string} RuleType - The name of the rule to retrieve.
* @param {"Owner" | "Lover"} Sender - Type of the sender
* @returns {Rule} - The owner or lover rule corresponding to the requested rule name
* @returns {boolean} - The owner or lover rule corresponding to the requested rule name
*/
function ChatRoomGetRule(RuleType, Sender) {
return LogQueryRemote(CurrentCharacter, RuleType, Sender + "Rule");
@ -3190,7 +3190,7 @@ function ChatRoomGetLoadRules(C) {
/**
* Handles a response from another player containing the rules that the current player is allowed to read.
* @param {Character} C - Character to set the rules on
* @param {Rule[]} Rule - An array of rules that the current player can read.
* @param {LogRecord[]} Rule - An array of rules that the current player can read.
* @returns {void} - Nothing
*/
function ChatRoomSetLoadRules(C, Rule) {

View file

@ -730,7 +730,7 @@ function PrivateLoadCharacter(C) {
* Triggered when a new character is added to the player's private room.
* @param {object} Template - The base of the character, includes the name and appearance.
* @param {string} Archetype - The type of character such as maid or mistress.
* @param {boolean} CustomData - Whether or not the character has non-random traits.
* @param {boolean} [CustomData=false] - Whether or not the character has non-random traits.
* @returns {void} - Nothing.
*/
function PrivateAddCharacter(Template, Archetype, CustomData) {

View file

@ -338,14 +338,14 @@ function ActivityOrgasmStart(C) {
if ((C.ID == 0) || C.IsNpc()) {
if (C.ID == 0 && !ActivityOrgasmRuined) ActivityOrgasmGameResistCount = 0;
ActivityOrgasmWillpowerProgress(C);
if (!ActivityOrgasmRuined) {
C.ArousalSettings.OrgasmTimer = CurrentTime + (Math.random() * 10000) + 5000;
C.ArousalSettings.OrgasmStage = 2;
C.ArousalSettings.OrgasmCount = (C.ArousalSettings.OrgasmCount == null) ? 1 : C.ArousalSettings.OrgasmCount + 1;
ActivityOrgasmGameTimer = C.ArousalSettings.OrgasmTimer - CurrentTime;
if ((C.ID == 0) && (CurrentScreen == "ChatRoom")) {
let Dictionary = [];
Dictionary.push({ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber });
@ -354,7 +354,7 @@ function ActivityOrgasmStart(C) {
}
} else {
ActivityOrgasmStop(Player, 65 + Math.ceil(Math.random()*20));
if ((C.ID == 0) && (CurrentScreen == "ChatRoom")) {
let Dictionary = [];
let ChatModifier = C.ArousalSettings.OrgasmStage == 1 ? "Timeout" : "Surrender";
@ -363,8 +363,8 @@ function ActivityOrgasmStart(C) {
ActivityChatRoomArousalSync(C);
}
}
}
}
@ -421,12 +421,12 @@ function ActivityOrgasmGameGenerate(Progress) {
/**
* Triggers an orgasm for the player or an NPC which lasts from 5 to 15 seconds
* @param {Character} C - Character for which an orgasm was triggered
* @param {bool} Bypass - If true, this will do a ruined orgasm rather than a real one
* @param {boolean} [Bypass=false] - If true, this will do a ruined orgasm rather than a real one
* @returns {void} - Nothing
*/
function ActivityOrgasmPrepare(C, Bypass) {
ActivityOrgasmRuined = false;
if (C.Effect.includes("DenialMode")) {
C.ArousalSettings.Progress = 99;
if (C.ID == 0 && (Bypass || C.Effect.includes("RuinOrgasms"))) ActivityOrgasmRuined = true;
@ -438,7 +438,7 @@ function ActivityOrgasmPrepare(C, Bypass) {
if (C.ID == 0 && Bypass) ActivityOrgasmRuined = true;
else return;
}
if (C.ID == 0 && ActivityOrgasmRuined) {
ActivityOrgasmGameGenerate(0); // Resets the game
}

View file

@ -397,7 +397,10 @@ function AssetBuildDescription(Family, CSV) {
}
// Loads the description of the assets in a specific language
/**
* Loads the description of the assets in a specific language
* @param {string} Family The asset family to load the description for
*/
function AssetLoadDescription(Family) {
// Finds the full path of the CSV file to use cache

View file

@ -66,113 +66,185 @@ var AudioActions = [
GetAudioInfo: AudioPlayAssetSound
},
{
IsAction: (data) => ["pumps", "Suctightens", "InflatableBodyBagRestrain"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"pumps",
"Suctightens",
"InflatableBodyBagRestrain"
].find(A => data.Content.includes(A)),
Sound: "Inflation"
},
{
IsAction: (data) => ["FuturisticTrainingBeltSetStateNoneOff"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticTrainingBeltSetStateNoneOff"
].find(A => data.Content.includes(A)),
Sound: "FuturisticApply"
},
{
IsAction: (data) => [ "FuturisticTrainingBeltSetStateLowPriorityEdgeLow",
"FuturisticTrainingBeltSetStateLowPriorityEdgeLowSelf",
"FuturisticTrainingBeltSetStateHighPriorityEdgeLow",
"FuturisticTrainingBeltSetStateHighPriorityEdgeLowSelf"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticTrainingBeltSetStateLowPriorityEdgeLow",
"FuturisticTrainingBeltSetStateLowPriorityEdgeLowSelf",
"FuturisticTrainingBeltSetStateHighPriorityEdgeLow",
"FuturisticTrainingBeltSetStateHighPriorityEdgeLowSelf"
].find(A => data.Content.includes(A)),
Sound: "VibrationEdgeLow"
},
{
IsAction: (data) => [ "FuturisticTrainingBeltSetStateLowPriorityTeaseLow",
"FuturisticTrainingBeltSetStateLowPriorityLowLow",].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticTrainingBeltSetStateLowPriorityTeaseLow",
"FuturisticTrainingBeltSetStateLowPriorityLowLow"
].find(A => data.Content.includes(A)),
Sound: "VibrationTeaseLow"
},
{
IsAction: (data) => [ "FuturisticTrainingBeltSetStateCooldownOff"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticTrainingBeltSetStateCooldownOff"
].find(A => data.Content.includes(A)),
Sound: "VibrationCooldown"
},
{
IsAction: (data) => [ "FuturisticTrainingBeltSetStateLowPriorityEdgeMedium",
"FuturisticTrainingBeltSetStateLowPriorityEdgeMediumSelf",
"FuturisticTrainingBeltSetStateHighPriorityEdgeMedium",
"FuturisticTrainingBeltSetStateHighPriorityEdgeMediumSelf"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticTrainingBeltSetStateLowPriorityEdgeMedium",
"FuturisticTrainingBeltSetStateLowPriorityEdgeMediumSelf",
"FuturisticTrainingBeltSetStateHighPriorityEdgeMedium",
"FuturisticTrainingBeltSetStateHighPriorityEdgeMediumSelf"
].find(A => data.Content.includes(A)),
Sound: "VibrationEdgeMedium"
},
{
IsAction: (data) => [ "FuturisticTrainingBeltSetStateLowPriorityTeaseMedium",
"FuturisticTrainingBeltSetStateLowPriorityMedium"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticTrainingBeltSetStateLowPriorityTeaseMedium",
"FuturisticTrainingBeltSetStateLowPriorityMedium"
].find(A => data.Content.includes(A)),
Sound: "VibrationTeaseMedium"
},
{
IsAction: (data) => [ "FuturisticTrainingBeltSetStateLowPriorityEdgeHigh",
"FuturisticTrainingBeltSetStateLowPriorityEdgeHighSelf",
"FuturisticTrainingBeltSetStateHighPriorityEdgeHigh",
"FuturisticTrainingBeltSetStateHighPriorityEdgeHighSelf"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticTrainingBeltSetStateLowPriorityEdgeHigh",
"FuturisticTrainingBeltSetStateLowPriorityEdgeHighSelf",
"FuturisticTrainingBeltSetStateHighPriorityEdgeHigh",
"FuturisticTrainingBeltSetStateHighPriorityEdgeHighSelf"
].find(A => data.Content.includes(A)),
Sound: "VibrationEdgeHigh"
},
{
IsAction: (data) => [ "FuturisticTrainingBeltSetStateLowPriorityEdgeMaximum",
"FuturisticTrainingBeltSetStateLowPriorityEdgeMaximumSelf",
"FuturisticTrainingBeltSetStateLowPriorityTeaseMaximum",
"FuturisticTrainingBeltSetStateLowPriorityTeaseHigh",
"FuturisticTrainingBeltSetStateHighPriorityMax",
"FuturisticTrainingBeltSetStateLowPriorityMax",
"FuturisticTrainingBeltSetStateHighPriorityEdgeMaximum",
"FuturisticTrainingBeltSetStateHighPriorityEdgeMaximumSelf"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticTrainingBeltSetStateLowPriorityEdgeMaximum",
"FuturisticTrainingBeltSetStateLowPriorityEdgeMaximumSelf",
"FuturisticTrainingBeltSetStateLowPriorityTeaseMaximum",
"FuturisticTrainingBeltSetStateLowPriorityTeaseHigh",
"FuturisticTrainingBeltSetStateHighPriorityMax",
"FuturisticTrainingBeltSetStateLowPriorityMax",
"FuturisticTrainingBeltSetStateHighPriorityEdgeMaximum",
"FuturisticTrainingBeltSetStateHighPriorityEdgeMaximumSelf"
].find(A => data.Content.includes(A)),
Sound: "VibrationMaximum"
},
{
IsAction: (data) => ["InteractiveVisorHeadSet"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"InteractiveVisorHeadSet"
].find(A => data.Content.includes(A)),
Sound: "SciFiEffect"
},
{
IsAction: (data) => ["FuturisticPanelGagMouthSetLightBall", "FuturisticPanelGagMouthSetBall", "FuturisticPanelGagMouthSetPadded", "FuturisticPanelGagMouthSetPlug"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticPanelGagMouthSetLightBall",
"FuturisticPanelGagMouthSetBall",
"FuturisticPanelGagMouthSetPadded",
"FuturisticPanelGagMouthSetPlug"
].find(A => data.Content.includes(A)),
Sound: "SciFiPump"
},
{
IsAction: (data) => ["deflates", "Sucloosens"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"deflates",
"Sucloosens"
].find(A => data.Content.includes(A)),
Sound: "Deflation"
},
{
IsAction: (data) => ["ChainSet"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"ChainSet"
].find(A => data.Content.includes(A)),
Sound: "ChainLong"
},
{
IsAction: (data) => ["RopeSet"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"RopeSet"
].find(A => data.Content.includes(A)),
Sound: "RopeShort"
},
{
IsAction: (data) => ["ShacklesRestrain", "Ornate"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"ShacklesRestrain",
"Ornate"
].find(A => data.Content.includes(A)),
Sound: "CuffsMetal"
},
{
IsAction: (data) => ["FuturisticChastityBeltShock"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticChastityBeltShock"
].find(A => data.Content.includes(A)),
Sound: "Shocks"
},
{
IsAction: (data) => ["FuturisticChastityBeltSetClosed", "FuturisticChastityBeltSetOpen", "InventoryItemBreastFuturisticBraSet", "FuturisticHeelsSet", "FuturisticArmbinderSet", "FuturisticCuffsRestrain", "FuturisticLegCuffsRestrain", "FuturisticAnkleCuffsRestrain", "SciFiPleasurePantiesAction"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticChastityBeltSetClosed",
"FuturisticChastityBeltSetOpen",
"InventoryItemBreastFuturisticBraSet",
"FuturisticHeelsSet",
"FuturisticArmbinderSet",
"FuturisticCuffsRestrain",
"FuturisticLegCuffsRestrain",
"FuturisticAnkleCuffsRestrain",
"SciFiPleasurePantiesAction"
].find(A => data.Content.includes(A)),
Sound: "SciFiConfigure"
},
{
IsAction: (data) => ["FuturisticChastityBeltSetGeneric", "FuturisticChastityBeltSetPunish", "FuturisticChastityBeltSetGeneric", "FuturisticPanelGagMouthSetAutoPunish", "SciFiPleasurePantiesBeep"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticChastityBeltSetGeneric",
"FuturisticChastityBeltSetPunish",
"FuturisticChastityBeltSetGeneric",
"FuturisticPanelGagMouthSetAutoPunish",
"SciFiPleasurePantiesBeep"
].find(A => data.Content.includes(A)),
GetAudioInfo: AudioSciFiBeepSounds
},
{
IsAction: (data) => ["FuturisticPanelGagMouthSetAutoInflate"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticPanelGagMouthSetAutoInflate"
].find(A => data.Content.includes(A)),
Sound: "Inflation"
},
{
IsAction: (data) => ["FuturisticPanelGagMouthSetAutoDeflate"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticPanelGagMouthSetAutoDeflate"
].find(A => data.Content.includes(A)),
Sound: "Deflation"
},
{
IsAction: (data) => ["FuturisticCrateSet"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"FuturisticCrateSet"
].find(A => data.Content.includes(A)),
Sound: "SciFiConfigure"
},
{
IsAction: (data) => ["CollarShockUnitTrigger", "ShockCollarTrigger", "LoveChastityBeltShockTrigger", "SciFiPleasurePantiesShockTrigger", "TriggerShock", "CollarAutoShockUnitTrigger", "FuturisticVibratorShockTrigger"].find(A => data.Content.includes(A)),
IsAction: (data) => [
"CollarShockUnitTrigger",
"ShockCollarTrigger",
"LoveChastityBeltShockTrigger",
"SciFiPleasurePantiesShockTrigger",
"TriggerShock",
"CollarAutoShockUnitTrigger",
"FuturisticVibratorShockTrigger"
].find(A => data.Content.includes(A)),
GetAudioInfo: (data) => InventoryItemNeckAccessoriesCollarShockUnitDynamicAudio(data)
},
{
IsAction: (data) => ["Decrease", "Increase"].find(A => data.Content.includes(A)) && !data.Content.endsWith("-1"),
IsAction: (data) => [
"Decrease",
"Increase"
].find(A => data.Content.includes(A)) && !data.Content.endsWith("-1"),
GetAudioInfo: AudioVibratorSounds
},
];

View file

@ -708,7 +708,7 @@ function CharacterCanChangeToPose(C, poseName) {
/**
* Checks if a certain pose is whitelisted and available for the pose menu
* @param {Character} C - Character to check for the pose
* @param {string} Type - Pose type to check for within items
* @param {string|undefined} Type - Pose type to check for within items
* @param {string} Pose - Pose to check for whitelist
* @returns {boolean} - TRUE if the character has the pose available
*/
@ -1295,7 +1295,7 @@ function CharacterResetFacialExpression(C) {
/**
* Gets the currently selected character
* @returns {Character} - Currently selected character
* @returns {Character|null} - Currently selected character
*/
function CharacterGetCurrent() {
return (Player.FocusGroup != null) ? Player : CurrentCharacter;
@ -1490,7 +1490,7 @@ function CharacterCheckHooks(C, IgnoreHooks) {
})) refresh = true;
} else if (C.UnregisterHook("BeforeSortLayers", "HideRestraints")) refresh = true;
// Hook for layer visibility
// Visibility is a string individual layers have. If an item has any layers with visibility, it should have the LayerVisibility: true property
// We basically check the player's items and see if any are visible that have the LayerVisibility property.
@ -1513,7 +1513,7 @@ function CharacterCheckHooks(C, IgnoreHooks) {
(Layer.Visibility == "Owner" && C.IsOwnedByPlayer()) ||
(Layer.Visibility == "Lovers" && C.IsLoverOfPlayer()) ||
(Layer.Visibility == "Mistresses" && LogQuery("ClubMistress", "Management"))
));
));
}))) refresh = true;
// Use the regular hook when the character is not
else if (!IgnoreHooks && (C.UnregisterHook("AfterLoadCanvas", "LayerVisibilityDialog") || C.RegisterHook("AfterLoadCanvas", "LayerVisibility", (C) => {
@ -1526,9 +1526,9 @@ function CharacterCheckHooks(C, IgnoreHooks) {
(Layer.Visibility == "Owner" && C.IsOwnedByPlayer()) ||
(Layer.Visibility == "Lovers" && C.IsLoverOfPlayer()) ||
(Layer.Visibility == "Mistresses" && LogQuery("ClubMistress", "Management"))
));
));
}))) refresh = true;
} else if (C.UnregisterHook("AfterLoadCanvas", "LayerVisibility")) refresh = true;
}

View file

@ -319,7 +319,7 @@ function ColorPickerDraw(X, Y, Width, Height, Src, Callback) {
ColorPickerLayout.SaveButtonX = X + (((ColorPickerWidth / 6) - 90) / 2);
ColorPickerLayout.PrevButtonX = X + (ColorPickerWidth / 6) + (((ColorPickerWidth / 6) - 90) / 2);
ColorPickerLayout.NextButtonX = X + (ColorPickerWidth / 3) + (((ColorPickerWidth / 6) - 90) / 2);
var SVPanelOffset = ColorPickerLayout.SVPanelOffset;
var SVPanelHeight = ColorPickerLayout.SVPanelHeight;
var PaletteOffset = ColorPickerLayout.PaletteOffset;
@ -327,9 +327,9 @@ function ColorPickerDraw(X, Y, Width, Height, Src, Callback) {
var FavoritesPaletteOffset = ColorPickerLayout.FavoritesPaletteOffset;
var FavoritesPaletteHeight = ColorPickerLayout.FavoritesPaletteHeight;
var ButtonOffset = ColorPickerLayout.ButtonOffset;
var NextButtonX = ColorPickerLayout.NextButtonX
var SaveButtonX = ColorPickerLayout.SaveButtonX
var PrevButtonX = ColorPickerLayout.PrevButtonX
var NextButtonX = ColorPickerLayout.NextButtonX;
var SaveButtonX = ColorPickerLayout.SaveButtonX;
var PrevButtonX = ColorPickerLayout.PrevButtonX;
var HSV;
if (ColorPickerInitialHSV == null) {

View file

@ -211,7 +211,7 @@ function CommonReadCSV(Array, Path, Screen, File) {
/**
* AJAX utility to get a file and return its content. By default will retry requests 10 times
* @param {string} Path - Path of the resource to request
* @param {function} Callback - Callback to execute once the resource is received
* @param {(this: XMLHttpRequest, xhr: XMLHttpRequest) => void} Callback - Callback to execute once the resource is received
* @param {number} [RetriesLeft] - How many more times to retry if the request fails - after this hits zero, an error will be logged
* @returns {void} - Nothing
*/
@ -232,7 +232,7 @@ function CommonGet(Path, Callback, RetriesLeft) {
* Retry handler for CommonGet requests. Exponentially backs off retry attempts up to a limit of 1 minute. By default,
* retries up to a maximum of 10 times.
* @param {string} Path - The path of the resource to request
* @param {function} Callback - Callback to execute once the resource is received
* @param {(this: XMLHttpRequest, xhr: XMLHttpRequest) => void} Callback - Callback to execute once the resource is received
* @param {number} [RetriesLeft] - How many more times to retry - after this hits zero, an error will be logged
* @returns {void} - Nothing
*/
@ -731,7 +731,7 @@ function CommonCompareVersion(Current, Other) {
];
for (let i = 0; i < 3; i++) {
if (CurrentVer[i] !== OtherVer[i]) {
return Math.sign(OtherVer[i] - CurrentVer[i]);
return /** @type {-1|0|1} */ (Math.sign(OtherVer[i] - CurrentVer[i]));
}
}
return 0;

View file

@ -22,7 +22,7 @@
/**
* A callback function used to draw a canvas on a canvas
* @callback drawCanvas
* @param {string} Img - The canvas to draw
* @param {HTMLImageElement | HTMLCanvasElement} Img - The canvas to draw
* @param {number} x - The x coordinate to draw the canvas at
* @param {number} y - The y coordinate to draw the canvas at
*/
@ -182,10 +182,7 @@ function CommonDrawAppearanceBuild(C, {
return Acc;
}, []);
let Color = CA.Color;
if (Array.isArray(Color)) {
Color = Color[Layer.ColorIndex] || AG.ColorSchema[0];
}
let Color = Array.isArray(CA.Color) ? (CA.Color[Layer.ColorIndex] || AG.ColorSchema[0]) : CA.Color;
// Fix to legacy appearance data when Hands could be different to BodyUpper
if (GroupName === "Hands") Color = "Default";

View file

@ -113,7 +113,7 @@ function DialogSetReputation(RepType, Value) { ReputationChange(RepType, (parseI
/**
* Change the player's reputation progressively through dialog options (a reputation is easier to break than to build)
* @param {string} RepType - The name of the reputation to change
* @param {string} Value - The value, the player's reputation should be altered by
* @param {number|string} Value - The value, the player's reputation should be altered by
* @returns {void} - Nothing
*/
function DialogChangeReputation(RepType, Value) { ReputationProgress(RepType, Value); }
@ -743,7 +743,7 @@ function DialogMenuButtonBuild(C) {
if ((Item != null) && Item.Asset.Extended && ((Player.CanInteract()) || DialogAlwaysAllowRestraint() || Item.Asset.AlwaysInteract) && (!IsGroupBlocked || Item.Asset.AlwaysExtend) && (!Item.Asset.OwnerOnly || (C.IsOwnedByPlayer())) && (!Item.Asset.LoverOnly || (C.IsLoverOfPlayer()))) DialogMenuButton.push(ItemBlockedOrLimited ? "UseDisabled" : "Use");
// Extended icon doesnt show up if remote works
if (!DialogMenuButton.includes("Use") && DialogCanUseRemote(C, Item)) DialogMenuButton.push(ItemBlockedOrLimited ? "RemoteDisabled" : "Remote");
if (DialogCanColor(C, Item)) DialogMenuButton.push(ItemBlockedOrLimited ? "ColorPickDisabled" : "ColorPick");
// Make sure the target player zone is allowed for an activity

View file

@ -855,7 +855,7 @@ function DrawTextFit(Text, X, Y, Width, Color, BackColor) {
let Result = DrawingGetTextSize(Text, Width);
Text = Result[0];
MainCanvas.font = CommonGetFont(Result[1].toString());
// Draw a back color relief text if needed
if ((BackColor != null) && (BackColor != "")) {
MainCanvas.fillStyle = BackColor;
@ -870,7 +870,7 @@ function DrawTextFit(Text, X, Y, Width, Color, BackColor) {
/**
* Gets the text size needed to fit inside a given width according to the current font.
* This function is memoized because <code>MainCanvas.measureText(Text)</code> is a major resource hog.
* This function is memoized because <code>MainCanvas.measureText(Text)</code> is a major resource hog.
* @param {string} Text - Text to draw
* @param {number} Width - Width in which the text has to fit
* @returns {[string, number]} - Text to draw and its font size
@ -884,7 +884,7 @@ const DrawingGetTextSize = CommonMemoize((Text, Width) => {
if (metrics.width <= Width)
return [Text, S];
}
// Cuts the text if it would go over the box
while (Text.length > 0) {
Text = Text.substr(1);
@ -1304,7 +1304,7 @@ function DrawAssetPreview(X, Y, A, Options) {
const Path = `${AssetGetPreviewPath(A)}/${A.Name}${DynamicPreviewIcon}.png`;
if (Description == null) Description = C ? A.DynamicDescription(C) : A.Description;
if (IsFavorite) Description = "★ " + Description;
DrawPreviewBox(X, Y, Path, Description, { Background, Foreground, Vibrating, Border, Hover,
DrawPreviewBox(X, Y, Path, Description, { Background, Foreground, Vibrating, Border, Hover,
HoverBackground, Disabled });
}

View file

@ -355,10 +355,10 @@ function DynamicDrawTextArc(text, ctx, x, y, options) {
* @returns {void} - Nothing
*/
function DynamicDrawTextAndEffects(text, ctx, x, y, options) {
const { effect, width } = options;
DynamicDrawApplyOptions(ctx, options);
const effect = DynamicDrawTextEffects[options.effect] || {};
if (typeof effect.before === "function") effect.before(text, ctx, x, y, options);
ctx.fillText(text, x, y, width);
ctx.fillText(text, x, y, options.width);
if (typeof effect.after === "function") effect.after(text, ctx, x, y, options);
}
@ -370,7 +370,6 @@ function DynamicDrawTextAndEffects(text, ctx, x, y, options) {
function DynamicDrawParseOptions(options) {
options = options || {};
const parsedOptions = Object.assign({}, DynamicDrawTextDefaultOptions, options);
parsedOptions.effect = DynamicDrawTextEffects[parsedOptions.effect] || {};
return parsedOptions;
}

View file

@ -348,7 +348,7 @@ function ElementScrollToEnd(ID) {
* @param {string} ID - The id of the element to find the scroll percentage of.
* @returns {(number|null)} - A float representing the scroll percentage.
*/
function ElementGetScrollPercentage(ID) {
function ElementGetScrollPercentage(ID) {
var element = document.getElementById(ID);
if (element != null) return (element.scrollTop + element.clientHeight) / element.scrollHeight;

View file

@ -6,6 +6,7 @@ var GLDrawImageCache = new Map();
var GLDrawCacheLoadedImages = 0;
var GLDrawCacheTotalImages = 0;
/** @type {"webgl2"|"webgl"|"No WebGL"} */
var GLVersion;
var GLDrawCanvas;
@ -244,7 +245,7 @@ function GLDrawCreateProgram(gl, vertexShader, fragmentShader) {
/**
* Draws an image from a given url to a WebGLRenderingContext, used when the character is blinking
* @param {string} url - URL of the image to render
* @param {WebGLRenderingContext} gl - WebGL context
* @param {WebGL2RenderingContext} gl - WebGL context
* @param {number} dstX - Position of the image on the X axis
* @param {number} dstY - Position of the image on the Y axis
* @param {string} color - Color of the image to draw
@ -257,7 +258,7 @@ function GLDrawImageBlink(url, gl, dstX, dstY, color, fullAlpha, alphaMasks, opa
/**
* Draws an image from a given url to a WebGLRenderingContext
* @param {string} url - URL of the image to render
* @param {WebGLRenderingContext} gl - WebGL context
* @param {WebGL2RenderingContext} gl - WebGL context
* @param {number} dstX - Position of the image on the X axis
* @param {number} dstY - Position of the image on the Y axis
* @param {number} offsetX - Additional offset to add to the X axis (for blinking)
@ -311,17 +312,17 @@ function GLDrawImage(url, gl, dstX, dstY, offsetX, color, fullAlpha, alphaMasks,
/**
* Draws a canvas on the WebGL canvas for the blinking effect
* @param {WebGLRenderingContext} gl - WebGL context
* @param {ImageData} Img - Canvas to get the data of
* @param {WebGL2RenderingContext} gl - WebGL context
* @param {HTMLImageElement | HTMLCanvasElement} Img - Canvas to get the data of
* @param {number} X - Position of the image on the X axis
* @param {number} Y - Position of the image on the Y axis
* @param {number[][]} alphaMasks - A list of alpha masks to apply to the asset
*/
function GLDraw2DCanvasBlink(gl, Img, X, Y, alphaMasks) { GLDraw2DCanvas(gl, Img, X + 500, Y, 500, alphaMasks); }
function GLDraw2DCanvasBlink(gl, Img, X, Y, alphaMasks) { GLDraw2DCanvas(gl, Img, X + 500, Y, alphaMasks); }
/**
* Draws a canvas on the WebGL canvas
* @param {WebGLRenderingContext} gl - WebGL context
* @param {ImageData} Img - Canvas to get the data of
* @param {WebGL2RenderingContext} gl - WebGL context
* @param {HTMLImageElement | HTMLCanvasElement} Img - Canvas to get the data of
* @param {number} X - Position of the image on the X axis
* @param {number} Y - Position of the image on the Y axis
* @param {number[][]} alphaMasks - A list of alpha masks to apply to the asset
@ -349,7 +350,7 @@ function GLDrawBingImageToTextureInfo(gl, Img, textureInfo) {
/**
* Loads image texture data
* @param {WebGLRenderingContext} gl - WebGL context
* @param {WebGL2RenderingContext} gl - WebGL context
* @param {string} url - URL of the image
* @returns {{ width: number; height: number; texture: WebGLTexture; }} - The texture info of a given image
*/
@ -403,7 +404,7 @@ function GLDrawLoadImage(gl, url) {
/**
* Loads alpha mask data
* @param {WebGLRenderingContext} gl - The WebGL context
* @param {WebGL2RenderingContext} gl - The WebGL context
* @param {number} texWidth - The width of the texture to mask
* @param {number} texHeight - The height of the texture to mask
* @param {number} offsetX - The X offset at which the texture is to be drawn on the target canvas

View file

@ -1,4 +1,5 @@
"use strict";
/** @type {LogRecord[]} */
var Log = [];
/**
@ -6,7 +7,7 @@ var Log = [];
* @param {string} NewLogName - The name of the log
* @param {string} NewLogGroup - The name of the log's group
* @param {number} [NewLogValue] - Value for the log as the time in ms. Is undefined if the value is permanent
* @param {boolean} [Push] - TRUE if we must push the log to the server
* @param {boolean} [Push=true] - TRUE if we must push the log to the server
* @returns {void} - Nothing
*/
function LogAdd(NewLogName, NewLogGroup, NewLogValue, Push) {
@ -43,7 +44,7 @@ function LogAdd(NewLogName, NewLogGroup, NewLogValue, Push) {
* Deletes a log entry.
* @param {string} DelLogName - The name of the log
* @param {string} DelLogGroup - The name of the log's group
* @param {boolean} [Push] - TRUE if we must push the log to the server
* @param {boolean} [Push=true] - TRUE if we must push the log to the server
* @returns {void} - Nothing
*/
function LogDelete(DelLogName, DelLogGroup, Push) {
@ -82,7 +83,7 @@ function LogQuery(QueryLogName, QueryLogGroup) {
* Returns the value associated to a log.
* @param {string} QueryLogName - The name of the log to query the value
* @param {string} QueryLogGroup - The name of the log's group
* @returns {number | undefined} - Returns the value of the log which is a date represented in ms or undefined. Returns null if no matching log is found.
* @returns {number | null} - Returns the value of the log which is a date represented in ms or undefined. Returns null if no matching log is found.
*/
function LogValue(QueryLogName, QueryLogGroup) {
for (let L = 0; L < Log.length; L++)
@ -93,7 +94,7 @@ function LogValue(QueryLogName, QueryLogGroup) {
/**
* Loads the account log.
* @param {Array.<{Name: string, Group: string, Value: number}>} NewLog - Existing logs received by the server
* @param {LogRecord[]} NewLog - Existing logs received by the server
* @returns {void} - Nothing
*/
function LogLoad(NewLog) {
@ -121,14 +122,14 @@ function LogLoad(NewLog) {
function LogQueryRemote(C, QueryLogName, QueryLogGroup) {
if (C.ID == 0) return LogQuery(QueryLogName, QueryLogGroup);
if (!C.Rule || !Array.isArray(C.Rule)) return false;
var R = C.Rule.find(R => R.Name == QueryLogName && R.Group == QueryLogGroup);
return (R != null) && ((R.Value == null) || (R.Value >= CurrenTime));
var R = C.Rule.find(r => r.Name == QueryLogName && r.Group == QueryLogGroup);
return (R != null) && ((R.Value == null) || (R.Value >= CurrentTime));
}
/**
* Filters the Player's log and returns the rule entries that the player's owner is allowed to see.
* @param {boolean} OwnerIsLover - Indicates that the requester is also the player's lover.
* @returns {Rule[]} - A list of rules that the player's owner is permitted to see
* @returns {LogRecord[]} - A list of rules that the player's owner is permitted to see
*/
function LogGetOwnerReadableRules(OwnerIsLover) {
return Log.filter(L => L.Group == "OwnerRule" || (L.Group == "LoverRule" && (OwnerIsLover || L.Name.includes("Owner"))));
@ -136,7 +137,7 @@ function LogGetOwnerReadableRules(OwnerIsLover) {
/**
* Filters the Player's log and returns the rule entries that the player's lover is allowed to see.
* @returns {Rule[]} - A list of rules that the player's lover is permitted to see
* @returns {LogRecord[]} - A list of rules that the player's lover is permitted to see
*/
function LogGetLoverReadableRules() {
return Log.filter(L => L.Group == "LoverRule");

View file

@ -7,7 +7,7 @@
class DirectedGraph {
/**
* @param {string[]} vertices
* @param {[[string, string]]} edges
* @param {[string, string][]} edges
*/
constructor(vertices, edges) {
this.vertices = vertices;
@ -21,6 +21,7 @@ class DirectedGraph {
* @returns {Record<string, string[]>} - The adjacency list for the graph
*/
buildAdjacencyList() {
/** @type {Record<string, string[]>} */
const adjacencyList = {};
for (const v of this.vertices) {
adjacencyList[v] = [];
@ -125,6 +126,7 @@ class DirectedGraph {
const blocked = [];
const blockMap = [];
const cycles = [];
/** @type {DirectedGraph} */
let subgraph = this;
let startVertex;
let currentComponent;

View file

@ -32,7 +32,7 @@ function InventoryAdd(C, NewItemName, NewItemGroup, Push) {
* Adds multiple new items by group & name to the character inventory
* @param {Character} C - The character that gets the new items added to her inventory
* @param {Array.<{ Name: string, Group: string }>} NewItems - The new items to add
* @param {Boolean} Push - Set to TRUE to push to the server, pushed by default
* @param {Boolean} [Push=true] - Set to TRUE to push to the server, pushed by default
*/
function InventoryAddMany(C, NewItems, Push) {
@ -198,7 +198,7 @@ function InventoryPrerequisiteMessage(C, Prerequisite) {
|| !InventoryDoesItemExposeGroup(C, "Panties", "ItemVulva")
|| InventoryDoesItemBlockGroup(C, "Socks", "ItemVulva")
? "RemoveClothesForItem" : "";
// Ensure crotch is empty
case "VulvaEmpty": return ((InventoryGet(C, "ItemVulva") != null)) ? "MustFreeVulvaFirst" : "";
case "ClitEmpty": return ((InventoryGet(C, "ItemVulvaPiercings") != null)) ? "MustFreeClitFirst" : "";
@ -924,8 +924,8 @@ function InventoryTogglePermission(Item, Type) {
Player.LimitedItems = Player.LimitedItems.filter(removeFromPermissions);
} else if (InventoryIsFavorite(Player, Item.Asset.Name, Item.Asset.Group.Name, Type)) {
if (Player.GetDifficulty() >= 3)
Player.LimitedItems.push(permissionItem)
else
Player.LimitedItems.push(permissionItem);
else
Player.BlockItems.push(permissionItem);
Player.FavoriteItems = Player.FavoriteItems.filter(removeFromPermissions);
} else {

View file

@ -530,7 +530,7 @@ function ModularItemSetType(module, index, data) {
* Publishes the chatroom message for a modular item when one of its modules has changed.
* @param {ModularItemModule} module - The module that changed
* @param {number} index - The index of the newly chosen option within the module
* @param {ModularItemData} - The modular item's data
* @param {ModularItemData} data - The modular item's data
* @returns {void} - Nothing
*/
function ModularItemChatRoomMessage(module, index, { chatSetting, chatMessagePrefix }) {

View file

@ -91,7 +91,7 @@ function ReputationCharacterGet(C, RepType) {
/**
* Alter the reputation progress by a factor. The higher the rep, the slower it gets, a reputation is easier to break than to build. Takes the cheater version factor into account.
* @param {string} RepType - Type/name of the reputation
* @param {number} Value - Value of the reputation change before the factor is applied
* @param {number|string} Value - Value of the reputation change before the factor is applied
* @return {void} - Nothing
*/
function ReputationProgress(RepType, Value) {

View file

@ -52,7 +52,7 @@ function SkillChange(SkillType, SkillLevel, SkillProgress, Push) {
/**
* Loads the skill data from the server on login
* @param {Array.<{Type: string, Level: number, Progress: number}>} NewSkill - The player skills array sent by the server
* @param {Skill[]} NewSkill - The player skills array sent by the server
* @returns {void} - Nothing
*/
function SkillLoad(NewSkill) {

View file

@ -74,7 +74,7 @@ function SpeechGetGagLevel(C, AssetGroup) {
*/
function SpeechGetTotalGagLevel(C, NoDeaf=false) {
let GagEffect = 0;
GagEffect += SpeechGetGagLevel(C, "ItemMouth");
GagEffect += SpeechGetGagLevel(C, "ItemMouth2");
GagEffect += SpeechGetGagLevel(C, "ItemMouth3");
@ -105,10 +105,10 @@ function SpeechGetTotalGagLevel(C, NoDeaf=false) {
*/
function SpeechGarble(C, CD, NoDeaf=false) {
let NS = CD;
let GagEffect = SpeechGetTotalGagLevel(C, NoDeaf);
if (GagEffect > 0) NS = SpeechGarbleByGagLevel(GagEffect, CD);
// No gag effect, we return the regular text

View file

@ -299,8 +299,8 @@ function TypedItemGetOption(groupName, assetName, optionName) {
* due to prerequisites or other requirements).
* @param {Character} C - The character on whom the item is equipped
* @param {Item} item - The item whose options are being validated
* @param {ExtendedItemOption} option - The new option
* @param {ExtendedItemOption} previousOption - The previously applied option
* @param {ExtendedItemOption|ModularItemOption} option - The new option
* @param {ExtendedItemOption|ModularItemOption} previousOption - The previously applied option
* @returns {string|undefined} - undefined or an empty string if the validation passes. Otherwise, returns a string
* message informing the player of the requirements that are not met.
*/

View file

@ -11,8 +11,85 @@ type MemoizedFunction<T extends Function> = T & {
clearCache(): void;
};
// GL shim
interface WebGL2RenderingContext {
program?: WebGLProgram;
programFull?: WebGLProgram;
programHalf?: WebGLProgram;
textureCache?: Map<string, any>;
maskCache?: Map<string, any>;
}
interface WebGLProgram {
u_alpha?: WebGLUniformLocation;
u_color?: WebGLUniformLocation;
a_position?: number;
a_texcoord?: number;
u_matrix?: WebGLUniformLocation;
u_texture?: WebGLUniformLocation;
u_alpha_texture?: WebGLUniformLocation;
position_buffer?: WebGLBuffer;
texcoord_buffer?: WebGLBuffer;
}
interface HTMLCanvasElement {
GL?: WebGL2RenderingContext;
}
//#endregion
//#region index.html
/**
* Main game running state, runs the drawing
* @param {number} Timestamp
*/
declare function MainRun(Timestamp: number): void;
/**
* When the user presses a key, we send the KeyDown event to the current screen if it can accept it
* @param {KeyboardEvent} event
*/
declare function KeyDown(event: KeyboardEvent): void;
/**
* Handler for document-wide keydown event
* @param {KeyboardEvent} event
*/
declare function DocumentKeyDown(event: KeyboardEvent): void;
/**
* When the user clicks, we fire the click event for other screens
* @param {MouseEvent} event
*/
declare function Click(event: MouseEvent): void;
/**
* When the user touches the screen (mobile only), we fire the click event for other screens
* @param {TouchEvent} event
*/
declare function TouchStart(event: TouchEvent): void;
/**
* When touch moves, we keep it's position for other scripts
* @param {Touch} touch
*/
declare function TouchMove(touch: Touch): void;
/**
* When mouse move, we keep the mouse position for other scripts
* @param {MouseEvent} event
*/
declare function MouseMove(event: MouseEvent): void;
/**
* When the mouse is away from the control, we stop keeping the coordinates,
* we also check for false positives with "relatedTarget"
* @param {MouseEvent} event
*/
declare function LoseFocus(event: MouseEvent): void;
//#endregion
type IAssetFamily = "Female3DCG";
@ -255,6 +332,12 @@ interface Activity {
MakeSound?: boolean;
}
interface LogRecord {
Name: string;
Group: string;
Value: number;
}
/** An item is a pair of asset and its dynamic properties that define a worn asset. */
interface Item {
Asset: Asset;
@ -267,6 +350,7 @@ interface Skill {
Type: string;
Level: number;
Progress: number;
Ratio?: number;
}
interface Reputation {
@ -307,9 +391,9 @@ interface Character {
Skill: Skill[];
Pose: string[];
Effect: string[];
FocusGroup: AssetGroup;
Canvas: HTMLCanvasElement;
CanvasBlink: HTMLCanvasElement;
FocusGroup: AssetGroup | null;
Canvas: HTMLCanvasElement | null;
CanvasBlink: HTMLCanvasElement | null;
MustDraw: boolean;
BlinkFactor: number;
AllowItem: boolean;
@ -425,6 +509,7 @@ interface Character {
};
ArousalZoom?: boolean;
FixedImage?: string;
Rule?: LogRecord[];
}
interface PlayerCharacter extends Character {
@ -549,10 +634,12 @@ interface PlayerCharacter extends Character {
Wardrobe?: any[][];
WardrobeCharacterNames?: string[];
SavedExpressions?: any[];
SavedColors?: HSVColor[];
SavedColors: HSVColor[];
FriendList?: number[];
FriendNames?: Map<number, string>;
SubmissivesList?: Set<number>
SubmissivesList?: Set<number>;
KinkyDungeonKeybindings?: any;
Infiltration?: any;
}
//#region Extended items
@ -662,6 +749,14 @@ interface ModularItemOption {
BondageLevel?: number;
/** The required self-bondage skill level for this option when using it on oneself */
SelfBondageLevel?: number;
/** The required prerequisites that must be met before this option can be selected */
Prerequisite?: string|string[];
/**
* Whether or not prerequisites should be considered on the character's
* appearance without the item equipped. Should be set to `true` if the item itself might interfere with prerequisites on
* some of its options
*/
SelfBlockCheck?: boolean;
/** A list of groups that this option blocks - defaults to [] */
Block?: string[];
/** A list of groups that this option hides - defaults to [] */

View file

@ -155,6 +155,7 @@ function ValidationResolveModifyDiff(previousItem, newItem, params) {
const asset = previousItem.Asset;
const group = asset.Group;
const previousProperty = previousItem.Property || {};
/** @type {ItemProperties} */
const newProperty = newItem.Property = newItem.Property || {};
const itemBlocked = ValidationIsItemBlockedOrLimited(C, sourceMemberNumber, group.Name, asset.Name) ||
ValidationIsItemBlockedOrLimited(C, sourceMemberNumber, group.Name, asset.Name, newProperty.Type);
@ -551,7 +552,7 @@ function ValidationSanitizeProperties(C, item) {
}
});
}
// Block advanced vibrator modes if disabled
if (typeof property.Mode === "string" && C.ArousalSettings && C.ArousalSettings.DisableAdvancedVibes && !VibratorModeOptions[VibratorModeSet.STANDARD].includes(VibratorModeGetOption(property.Mode))) {
console.warn(`Removing invalid mode "${property.Mode}" from ${asset.Name}`);
@ -941,6 +942,7 @@ function ValidationResolveCyclicBlocks(appearance, diffMap) {
*/
function ValidationFindBlockCycles(appearance) {
const groups = appearance.map((item) => item.Asset.Group.Name);
/** @type {[string, string][]} */
const edges = [];
for (const item of appearance) {
const blockedGroups = ValidationGetBlockedGroups(item, groups);

View file

@ -498,7 +498,7 @@ function VibratorModeStateUpdateDeny(C, Arousal, TimeSinceLastChange, OldIntensi
// Here we give the fake orgasm, passing a special parameter that indicates we bypass the usual restriction on Edge
ActivityOrgasmPrepare(C, true);
}
// Set the vibrator to rest
State = VibratorModeState.REST;
Intensity = -1;

View file

@ -354,19 +354,29 @@ window.onload = function() {
MainRun(0);
};
// Main game running state, runs the drawing
/**
* Main game running state, runs the drawing
* @param {number} Timestamp
*/
function MainRun(Timestamp) {
DrawProcess();
TimerProcess(Timestamp);
}
// When the user presses a key, we send the KeyDown event to the current screen if it can accept it
/**
* When the user presses a key, we send the KeyDown event to the current screen if it can accept it
* @param {KeyboardEvent} event
*/
function KeyDown(event) {
if (event.repeat) return;
KeyPress = event.keyCode || event.which;
CommonKeyDown();
}
/**
* Handler for document-wide keydown event
* @param {KeyboardEvent} event
*/
function DocumentKeyDown(event) {
if (event.repeat) return;
if (event.key == "Escape") {
@ -383,7 +393,10 @@ function DocumentKeyDown(event) {
}
}
// When the user clicks, we fire the click event for other screens
/**
* When the user clicks, we fire the click event for other screens
* @param {MouseEvent} event
*/
function Click(event) {
if (!CommonIsMobile) {
MouseMove(event);
@ -391,31 +404,44 @@ function Click(event) {
}
}
// When the user touches the screen (mobile only), we fire the click event for other screens
function TouchStart(/** @type {TouchEvent} */ event) {
/**
* When the user touches the screen (mobile only), we fire the click event for other screens
* @param {TouchEvent} event
*/
function TouchStart(event) {
if (CommonIsMobile && MainCanvas) {
TouchMove(event.touches[0]);
CommonClick();
}
}
// When touch moves, we keep it's position for other scripts
function TouchMove(/** @type {Touch} */ touch) {
/**
* When touch moves, we keep it's position for other scripts
* @param {Touch} touch
*/
function TouchMove(touch) {
if (MainCanvas) {
MouseX = Math.round((touch.pageX - MainCanvas.canvas.offsetLeft) * 2000 / MainCanvas.canvas.clientWidth);
MouseY = Math.round((touch.pageY - MainCanvas.canvas.offsetTop) * 1000 / MainCanvas.canvas.clientHeight);
}
}
// When mouse move, we keep the mouse position for other scripts
function MouseMove(/** @type {MouseEvent} */ event) {
/**
* When mouse move, we keep the mouse position for other scripts
* @param {MouseEvent} event
*/
function MouseMove(event) {
if (MainCanvas) {
MouseX = Math.round(event.offsetX * 2000 / MainCanvas.canvas.clientWidth);
MouseY = Math.round(event.offsetY * 1000 / MainCanvas.canvas.clientHeight);
}
}
// When the mouse is away from the control, we stop keeping the coordinates, we also check for false positives with "relatedTarget"
/**
* When the mouse is away from the control, we stop keeping the coordinates,
* we also check for false positives with "relatedTarget"
* @param {MouseEvent} event
*/
function LoseFocus(event) {
if (event.relatedTarget || event.toElement) {
MouseX = -1;