Improvement: Use TypeScript for assetcheck ()

This commit is contained in:
jomshir98 2021-07-27 00:09:45 +02:00 committed by GitHub
parent 164f0286d6
commit 1657fa4dbb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1977 additions and 473 deletions

3
.gitattributes vendored
View file

@ -1,2 +1,3 @@
# Auto detect text files and perform LF normalization
* text=auto
* text=auto
BondageClub/Tools/Node/package-lock.json linguist-generated -diff

View file

@ -17,7 +17,13 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 14.x
- run: npm ci
- name: Custom matcher
run: echo "::remove-matcher owner=tsc::"
- name: Run TypeScript
run: |
npm run -s assetcheck-typescript -- --pretty
shell: bash
- name: Run Asset check
run: npm run -s assetcheck

View file

@ -17,7 +17,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 14.x
- run: npm ci
- name: Custom matcher
run: echo "##[add-matcher].github/eslint-matcher.json"

View file

@ -31,6 +31,7 @@
"no-trailing-spaces": "warn",
"semi": "warn",
"indent": ["warn", "tab", { "SwitchCase": 1, "ignoredNodes": ["ConditionalExpression"] }],
"unicode-bom": ["error", "never"],
// Until globals are properly documented
"no-undef": "off",
"no-var": "off"

View file

@ -1,3 +1,4 @@
//@ts-check
"use strict";
// *** Item value guidelines ***
// First, check if there's a similar item and use that price. If there isn't, use the real price in US dollars
@ -20,12 +21,19 @@
// Don't create anything that could be viewed by lots of players as racist, sexist, anti-LGBT, pedophilic, religious or political
// If you change an item or a piece of code made by someone else, make sure to get their approval first
// Spanking Toys Asset
/**
* Spanking Toys Asset
* @type {AssetDefinition}
*/
var AssetSpankingToys = {
Name: "SpankingToys", Random: false, Wear: false, BuyGroup: "SpankingToys",
DynamicAllowInventoryAdd: C => InventoryIsWorn(Player, "SpankingToys", "ItemHands") && InventorySpankingToysActivityAllowed(C),
DynamicDescription: C => InventorySpankingToysGetDescription(C),
DynamicExpressionTrigger: () => InventoryItemHandsSpankingToysOptions.find(x => x.Name == InventorySpankingToysGetType(Player)).ExpressionTrigger,
DynamicExpressionTrigger: () => {
const Type = InventorySpankingToysGetType(Player);
const Option = InventoryItemHandsSpankingToysOptions.find(x => x.Name === Type);
return Option && Option.ExpressionTrigger;
},
DynamicPreviewImage: () => InventorySpankingToysGetType(Player),
DynamicName: C => "SpankingToys" + InventorySpankingToysGetType(C),
DynamicGroupName: "ItemHands",
@ -38,10 +46,15 @@ var AssetSpankingToys = {
};
// Alpha mask regions based on Appearance.js CanvasUpperOverflow and CanvasLowerOverflow values
/** @type {[number, number, number, number]} */
const AssetUpperOverflowAlpha = [0, -700, 500, 700];
/** @type {[number, number, number, number]} */
const AssetLowerOverflowAlpha = [0, 1000, 500, 1000 + 150];
// 3D Custom Girl based assets
/**
* 3D Custom Girl based assets
* @type {AssetGroupDefinition[]}
*/
var AssetFemale3DCG = [
// Appearance specific
@ -2011,7 +2024,7 @@ var AssetFemale3DCG = [
} else if (InventoryItemPelvisLoveChastityBeltLastAction == "Shock") {
return [{ Name: "Medium", Group: "Blush", Timer: 10 }];
} else if (InventoryItemPelvisLoveChastityBeltLastAction == "ShockTriggered") {
var belt = InventoryGet(CharacterGetCurrent(), "ItemPelvis");
var belt = InventoryGet(C, "ItemPelvis");
var intensity = belt && belt.Property && belt.Property.Intensity;
if (intensity == 0) {
return [{ Name: "Low", Group: "Blush", Timer: 10 }];
@ -5217,7 +5230,10 @@ var ActivityFemale3DCG = [
}
];
// 3D Custom Girl based fetishes
/**
* 3D Custom Girl based fetishes
* @type {{Name: string; GetFactor(C: Character): number; }[]}
*/
var FetishFemale3DCG = [
{
Name: "Bondage",

View file

@ -1,3 +1,4 @@
//@ts-check
"use strict";
/**
@ -13,7 +14,7 @@
/**
* An enum encapsulating the available extended item archetypes
* MODULAR - Indicates that this item is modular, with several independently configurable modules
* @enum {string}
* @type {Record<"MODULAR"|"TYPED", ExtendedArchetype>}
* @see {@link ModularItemConfig}
* @see {@link TypedItemConfig}
*/
@ -278,63 +279,60 @@ var AssetFemale3DCGExtended = {
Config: {
ChatTags: [CommonChatTags.SOURCE_CHAR, CommonChatTags.TARGET_CHAR],
Options: [
{
Name: "WristTie",
BondageLevel: null,
Property: { Type: "WristTie", Effect: ["Block", "Prone"], SetPose: ["BackBoxTie"], Difficulty: 1 },
Expression: [{ Group: "Blush", Name: "Low", Timer: 5 }]
}, {
Name: "BoxTie",
BondageLevel: null,
Property: { Type: null, Effect: ["Block", "Prone"], SetPose: ["BackBoxTie"], Difficulty: 1 }
}, {
Name: "ChainCuffs",
BondageLevel: null,
Property: { Type: "ChainCuffs", Effect: ["Block", "Prone"], SetPose: ["BackCuffs"], Difficulty: 1, OverridePriority: 29 },
Expression: [{ Group: "Blush", Name: "Low", Timer: 5 }]
}, {
Name: "WristElbowTie",
BondageLevel: 2,
Property: { Type: "WristElbowTie", Effect: ["Block", "Prone", "NotSelfPickable"], SetPose: ["BackElbowTouch"], Difficulty: 2 },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 5 }]
}, {
Name: "WristElbowHarnessTie",
BondageLevel: 3,
Property: { Type: "WristElbowHarnessTie", Effect: ["Block", "Prone", "NotSelfPickable"], SetPose: ["BackElbowTouch"], Difficulty: 3 },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 5 }]
}, {
Name: "KneelingHogtie",
BondageLevel: 4,
Prerequisite: ["NotMounted", "NotSuspended"],
Property: { Type: "KneelingHogtie", Effect: ["Block", "Freeze", "Prone", "NotSelfPickable"], Block: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots", "ItemDevices"], AllowActivityOn: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots"], SetPose: ["Kneel", "BackElbowTouch"], Difficulty: 3 },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 10 }],
SelfBlockCheck: true,
Random: false,
}, {
Name: "Hogtied",
BondageLevel: 4,
Prerequisite: ["NotMounted", "NotSuspended", "CannotBeHogtiedWithAlphaHood"],
Property: { Type: "Hogtied", Effect: ["Block", "Freeze", "Prone", "NotSelfPickable"], Block: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots", "ItemDevices"], AllowActivityOn: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots"], SetPose: ["Hogtied"], Difficulty: 3 },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 10 }],
SelfBlockCheck: true,
Random: false,
}, {
Name: "AllFours",
BondageLevel: 6,
Prerequisite: ["NotMounted", "NotSuspended", "CannotBeHogtiedWithAlphaHood"],
Property: { Type: "AllFours", Effect: ["ForceKneel", "NotSelfPickable"], Block: ["ItemLegs", "ItemFeet", "ItemBoots", "ItemDevices"], AllowActivityOn: ["ItemLegs", "ItemFeet", "ItemBoots"], SetPose: ["AllFours"], Difficulty: 3 },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 10 }],
SelfBlockCheck: true,
Random: false,
}, {
Name: "SuspensionHogtied",
BondageLevel: 8,
Prerequisite: ["NotMounted", "NotChained", "NotSuspended", "CannotBeHogtiedWithAlphaHood"],
Property: { Type: "SuspensionHogtied", Effect: ["Block", "Freeze", "Prone", "NotSelfPickable"], Block: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots"], AllowActivityOn: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots"], SetPose: ["Hogtied", "SuspensionHogtied"], Difficulty: 6,
OverrideHeight: { Height: 0, Priority: 51, HeightRatioProportion: 0 } },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 10 }],
Random: false,
}
{
Name: "WristTie",
Property: { Type: "WristTie", Effect: ["Block", "Prone"], SetPose: ["BackBoxTie"], Difficulty: 1 },
Expression: [{ Group: "Blush", Name: "Low", Timer: 5 }]
}, {
Name: "BoxTie",
Property: { Type: null, Effect: ["Block", "Prone"], SetPose: ["BackBoxTie"], Difficulty: 1 }
}, {
Name: "ChainCuffs",
Property: { Type: "ChainCuffs", Effect: ["Block", "Prone"], SetPose: ["BackCuffs"], Difficulty: 1, OverridePriority: 29 },
Expression: [{ Group: "Blush", Name: "Low", Timer: 5 }]
}, {
Name: "WristElbowTie",
BondageLevel: 2,
Property: { Type: "WristElbowTie", Effect: ["Block", "Prone", "NotSelfPickable"], SetPose: ["BackElbowTouch"], Difficulty: 2 },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 5 }]
}, {
Name: "WristElbowHarnessTie",
BondageLevel: 3,
Property: { Type: "WristElbowHarnessTie", Effect: ["Block", "Prone", "NotSelfPickable"], SetPose: ["BackElbowTouch"], Difficulty: 3 },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 5 }]
}, {
Name: "KneelingHogtie",
BondageLevel: 4,
Prerequisite: ["NotMounted", "NotSuspended"],
Property: { Type: "KneelingHogtie", Effect: ["Block", "Freeze", "Prone", "NotSelfPickable"], Block: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots", "ItemDevices"], AllowActivityOn: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots"], SetPose: ["Kneel", "BackElbowTouch"], Difficulty: 3 },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 10 }],
SelfBlockCheck: true,
Random: false,
}, {
Name: "Hogtied",
BondageLevel: 4,
Prerequisite: ["NotMounted", "NotSuspended", "CannotBeHogtiedWithAlphaHood"],
Property: { Type: "Hogtied", Effect: ["Block", "Freeze", "Prone", "NotSelfPickable"], Block: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots", "ItemDevices"], AllowActivityOn: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots"], SetPose: ["Hogtied"], Difficulty: 3 },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 10 }],
SelfBlockCheck: true,
Random: false,
}, {
Name: "AllFours",
BondageLevel: 6,
Prerequisite: ["NotMounted", "NotSuspended", "CannotBeHogtiedWithAlphaHood"],
Property: { Type: "AllFours", Effect: ["ForceKneel", "NotSelfPickable"], Block: ["ItemLegs", "ItemFeet", "ItemBoots", "ItemDevices"], AllowActivityOn: ["ItemLegs", "ItemFeet", "ItemBoots"], SetPose: ["AllFours"], Difficulty: 3 },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 10 }],
SelfBlockCheck: true,
Random: false,
}, {
Name: "SuspensionHogtied",
BondageLevel: 8,
Prerequisite: ["NotMounted", "NotChained", "NotSuspended", "CannotBeHogtiedWithAlphaHood"],
Property: { Type: "SuspensionHogtied", Effect: ["Block", "Freeze", "Prone", "NotSelfPickable"], Block: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots"], AllowActivityOn: ["ItemHands", "ItemLegs", "ItemFeet", "ItemBoots"], SetPose: ["Hogtied", "SuspensionHogtied"], Difficulty: 6,
OverrideHeight: { Height: 0, Priority: 51, HeightRatioProportion: 0 } },
Expression: [{ Group: "Blush", Name: "Medium", Timer: 10 }],
Random: false,
}
],
Dialog: {
Load: "SelectChainBondage",

View file

@ -0,0 +1,168 @@
interface AssetGroupDefinition {
Asset: (AssetDefinition | string)[];
Group: string;
ParentGroup?: string;
Category?: string;
Default?: boolean;
IsRestraint?: boolean;
AllowNone?: boolean;
AllowColorize?: boolean;
AllowCustomize?: boolean;
Random?: boolean;
Color?: string[];
ParentSize?: string;
ParentColor?: string;
Clothing?: boolean;
Underwear?: boolean;
BodyCosplay?: boolean;
Activity?: string[];
AllowActivityOn?: string[];
Hide?: string[];
Block?: string[];
Zone?: [number, number, number, number][];
SetPose?: string[];
AllowPose?: string[];
AllowExpression?: string[];
Effect?: string[];
MirrorGroup?: string;
RemoveItemOnRemove?: { Group: string, Name: string, Type?: string }[];
Priority?: number;
Left?: number;
Top?: number;
FullAlpha?: boolean;
Blink?: boolean;
InheritColor?: string;
FreezeActivePose?: string[];
PreviewZone?: [number, number, number, number];
DynamicGroupName?: string;
}
interface AssetDefinition {
Name: string,
ParentItem?: string;
ParentGroup?: string | null;
Enable?: boolean;
Visible?: boolean;
Wear?: boolean;
Activity?: string | string[];
AllowActivity?: string[];
AllowActivityOn?: string[];
BuyGroup?: string;
PrerequisiteBuyGroups?: string[];
Effect?: string[];
Bonus?: string;
Block?: string[];
Expose?: string[];
Hide?: string[];
HideItem?: string[];
HideItemExclude?: string[];
Require?: string[];
SetPose?: string[];
AllowPose?: string[];
HideForPose?: string[];
AllowActivePose?: string[];
WhitelistActivePose?: string[];
Value?: number;
Difficulty?: number;
SelfBondage?: number;
SelfUnlock?: boolean;
ExclusiveUnlock?: boolean;
Random?: boolean;
RemoveAtLogin?: boolean;
Time?: number;
LayerVisibility?: boolean;
RemoveTime?: number;
RemoveTimer?: number;
MaxTimer?: number;
Priority?: number;
Left?: number;
Top?: number;
Height?: number;
Zoom?: number;
Alpha?: { Group?: string[], Pose?: string[], Masks: [number, number, number, number][] }[];
Prerequisite?: string | string[];
Extended?: boolean;
AlwaysExtend?: boolean;
AlwaysInteract?: boolean;
AllowLock?: boolean;
IsLock?: boolean;
PickDifficulty?: number | null;
OwnerOnly?: boolean;
LoverOnly?: boolean;
ExpressionTrigger?: { Name: string, Group: string, Timer: number }[];
RemoveItemOnRemove?: { Name: string, Group: string, Type?: string }[];
AllowEffect?: string[];
AllowBlock?: string[];
AllowType?: string[];
DefaultColor?: string | string[];
Opacity?: number;
MinOpacity?: number;
MaxOpacity?: number;
Audio?: string;
Category?: string[];
Fetish?: string[];
ArousalZone?: string;
IsRestraint?: boolean;
BodyCosplay?: boolean;
OverrideBlinking?: boolean;
DialogSortOverride?: number;
DynamicDescription?: (C: Character) => string;
DynamicPreviewImage?: (C: Character) => string;
DynamicAllowInventoryAdd?: (C: Character) => boolean;
DynamicExpressionTrigger?: (C: Character) => ExpressionTrigger[] | null | undefined;
DynamicName?: (C: Character) => string;
DynamicGroupName?: string;
DynamicActivity?: (C: Character) => string[] | string | null | undefined;
DynamicAudio?: (C: Character) => string;
CharacterRestricted?: boolean;
AllowRemoveExclusive?: boolean;
InheritColor?: string;
DynamicBeforeDraw?: boolean;
DynamicAfterDraw?: boolean;
DynamicScriptDraw?: boolean;
HasType?: boolean;
AllowLockType?: string[];
AllowColorizeAll?: boolean;
AvailableLocations?: string[];
OverrideHeight?: { Height: number; Priority: number; HeightRatioProportion?: number };
FreezeActivePose?: string[];
DrawLocks?: boolean;
AllowExpression?: string[];
MirrorExpression?: string;
FixedPosition?: boolean;
CustomBlindBackground?: any;
Layer?: AssetLayerDefinition[];
Archetype?: string;
FuturisticRecolor?: boolean;
FuturisticRecolorDisplay?: boolean;
Attribute?: string[];
HideItemAttribute?: string[];
PreviewIcons?: string[];
}
interface AssetLayerDefinition {
Name?: string;
AllowColorize?: boolean;
CopyLayerColor?: string;
ColorGroup?: string;
HideColoring?: boolean;
AllowTypes?: string[];
HasType?: boolean;
Visibility?: string;
ParentGroup?: string | null,
AllowPose?: string[];
Priority?: number;
InheritColor?: string;
Alpha?: { Group?: string[], Pose?: string[], Masks: [number, number, number, number][] }[],
Left?: number;
Top?: number;
HideAs?: { Group: string, Asset: string };
HasImage?: boolean;
Opacity?: number;
MinOpacity?: number;
MaxOpacity?: number;
LockLayer?: boolean;
MirrorExpression?: string;
HideForPose?: string[];
AllowModuleTypes?: string[];
}

View file

@ -101,6 +101,10 @@ function AssetsItemDevicesKennelScriptDraw({ C, PersistentData, Item }) {
}
}
/**
* @param {Character} C
* @returns {string}
*/
function InventoryItemDevicesKennelGetAudio(C) {
let wasWorn = InventoryGet(C, "ItemDevices") && InventoryGet(C, "ItemDevices").Asset.Name === "Kennel";
let isSelf = C.ID == 0;

View file

@ -1,4 +1,5 @@
"use strict";
/** @type { (ExtendedItemOption & { ExpressionTrigger: ExpressionTrigger[] } )[]} */
const InventoryItemHandsSpankingToysOptions = [
{
Name: "Crop",
@ -171,21 +172,27 @@ const InventoryItemHandsSpankingToysOptions = [
},
];
// Loads the item extension properties
/** Loads the item extension properties */
function InventoryItemHandsSpankingToysLoad() {
ExtendedItemLoad(InventorySpankingToysAvailableToys(CharacterGetCurrent()), "SelectSpankingToysType");
}
// Draw the item extension screen
/** Draw the item extension screen */
function InventoryItemHandsSpankingToysDraw() {
ExtendedItemDraw(InventorySpankingToysAvailableToys(CharacterGetCurrent()), "SpankingToysType");
}
// Catches the item extension clicks
/** Catches the item extension clicks */
function InventoryItemHandsSpankingToysClick() {
ExtendedItemClick(InventorySpankingToysAvailableToys(CharacterGetCurrent()));
}
/**
* Publishes the message to the chat
* @param {Character} C - The target character
* @param {ExtendedItemOption} Option - The currently selected Option
* @returns {void} - Nothing
*/
function InventoryItemHandsSpankingToysPublishAction(C, Option) {
var msg = C.ID == 0 ? "SpankingToysSetPlayer" : "SpankingToysSetOthers";
var Dictionary = [];
@ -195,6 +202,15 @@ function InventoryItemHandsSpankingToysPublishAction(C, Option) {
ChatRoomPublishCustomAction(msg, true, Dictionary);
}
/**
* The NPC dialog is for what the NPC says to you when you make a change to their restraints - the dialog lookup is on a
* per-NPC basis. You basically put the "AssetName" + OptionName in there to allow individual NPCs to override their default
* "GroupName" dialog if for example we ever wanted an NPC to react specifically to having the restraint put on them.
* That could be done by adding an "AssetName" entry (or entries) to that NPC's dialog CSV
* @param {Character} C - The NPC to whom the restraint is applied
* @param {ExtendedItemOption} Option - The chosen option for this extended item
* @returns {void} - Nothing
*/
function InventoryItemHandsSpankingToysNpcDialog(C, Option) {
C.CurrentDialog = DialogFind(C, "SpankingToys" + Option.Name, "ItemHands");
}
@ -223,7 +239,11 @@ function InventorySpankingToysGetType(C) {
else return "Crop";
}
// Get the description of the spanking toy that the character is holding
/**
* Get the description of the spanking toy that the character is holding
* @param {Character} C
* @returns {string}
*/
function InventorySpankingToysGetDescription(C) {
var ToyDescription = null;
var Toy = InventoryGet(C, "ItemHands");
@ -237,14 +257,22 @@ function InventorySpankingToysGetDescription(C) {
return ToyDescription || "Handheld Toy";
}
// Get the activity of the spanking toy that the character is holding
/**
* Get the activity of the spanking toy that the character is holding
* @param {Character} C
* @returns {string | string[] | null}
*/
function InventorySpankingToysGetActivity(C) {
var Type = InventorySpankingToysGetType(C);
var A = AssetGet(C.AssetFamily, "ItemHands", "SpankingToys" + Type);
return A && A.Activity || null;
}
// Determine whether an item activity is allowed on the selected region
/**
* Determine whether an item activity is allowed on the selected region
* @param {Character} C
* @returns {boolean}
*/
function InventorySpankingToysActivityAllowed(C) {
var Type = InventorySpankingToysGetType(Player);
var A = AssetGet(C.AssetFamily, "ItemHands", "SpankingToys" + Type);

View file

@ -33,7 +33,7 @@ var CommonVersionUpdated = false;
* Additionally, sending the following tags will ensure that asset names in messages are correctly translated by
* recipients:
* ASSET_NAME: (substituted with the localized name of the asset, if available)
* @enum {string}
* @type {Record<"SOURCE_CHAR"|"DEST_CHAR"|"DEST_CHAR_NAME"|"TARGET_CHAR"|"TARGET_CHAR_NAME"|"ASSET_NAME", CommonChatTags>}
*/
const CommonChatTags = {
SOURCE_CHAR: "SourceCharacter",

View file

@ -27,7 +27,7 @@ var DialogItemPermissionMode = false;
var DialogExtendedMessage = "";
var DialogActivityMode = false;
var DialogActivity = [];
/** @enum {number} */
/** @type {Record<"Enabled" | "Equipped" | "BothFavoriteUsable" | "TargetFavoriteUsable" | "PlayerFavoriteUsable" | "Usable" | "TargetFavoriteUnusable" | "PlayerFavoriteUnusable" | "Unusable" | "Blocked", DialogSortOrder>} */
var DialogSortOrder = {
Enabled: 1, Equipped: 2, BothFavoriteUsable: 3,
TargetFavoriteUsable: 4, PlayerFavoriteUsable: 5, Usable: 6,

View file

@ -360,7 +360,7 @@ function InventoryAllow(C, Prerequisite, SetDialog) {
* Gets the current item / cloth worn a specific area (AssetGroup)
* @param {Character} C - The character on which we must check the appearance
* @param {String} AssetGroup - The name of the asset group to scan
* @returns {Item} - Returns the appearance which is the item / cloth asset, color and properties
* @returns {Item|null} - Returns the appearance which is the item / cloth asset, color and properties
*/
function InventoryGet(C, AssetGroup) {
for (let A = 0; A < C.Appearance.length; A++)

View file

@ -61,7 +61,7 @@ const ModularItemDataLookup = {};
* the same)
* - PER_OPTION - The item has one chatroom message per option (for finer granularity - each individual option within a
* module can have its own chatroom message)
* @enum {string}
* @type {Record<"PER_MODULE"|"PER_OPTION", ModularItemChatSetting>}
*/
const ModularItemChatSetting = {
PER_MODULE: "perModule",

View file

@ -1,4 +1,4 @@
"use strict";
"use strict";
/**
* An enum for the events in the game that notifications can be raised for
@ -15,7 +15,7 @@ const NotificationEventType = {
/**
* An enum for the types of notifications that can be raised
* @enum {number}
* @type {Record<"NONE"|"TITLEPREFIX"|"FAVICON"|"POPUP",NotificationAlertType>}
*/
const NotificationAlertType = {
NONE: 0,
@ -26,7 +26,7 @@ const NotificationAlertType = {
/**
* An enum for the audio settings for notifications
* @enum {number}
* @type {Record<"NONE"|"FIRST"|"REPEAT", NotificationAlertType>}
*/
const NotificationAudioType = {
NONE: 0,

View file

@ -36,8 +36,7 @@ const TypedItemDataLookup = {};
* - TO_ONLY - The item has one chatroom message per type (indicating that the type has been selected)
* - FROM_TO - The item has a chatroom message for from/to type pairing
* - SILENT - The item doesn't publish an action when a type is selected.
* @type {{TO_ONLY: string, FROM_TO: string, SILENT: string}}
* @enum {string}
* @type {Record<"TO_ONLY"|"FROM_TO"|"SILENT", TypedItemChatSetting>}
*/
const TypedItemChatSetting = {
TO_ONLY: "toOnly",

View file

@ -38,6 +38,25 @@ interface HTMLCanvasElement {
//#endregion
//#region Enums
type ExtendedArchetype = "modular" | "typed";
type TypedItemChatSetting = "toOnly" | "fromTo" | "silent";
type ModularItemChatSetting = "perModule" | "perOption";
type CommonChatTags =
| "SourceCharacter"
| "DestinationCharacter"
| "DestinationCharacterName"
| "TargetCharacter"
| "TargetCharacterName"
| "AssetName";
type NotificationAudioType = 0 | 1 | 2;
type NotificationAlertType = 0 | 1 | 3 | 2;
type DialogSortOrder = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
//#endregion
//#region index.html
/**
@ -277,10 +296,10 @@ interface Asset {
DynamicDescription: (C: Character) => string;
DynamicPreviewImage: (C: Character) => string;
DynamicAllowInventoryAdd: (C: Character) => boolean;
DynamicExpressionTrigger: (C: Character) => ExpressionTrigger[];
DynamicName: (C?: Character) => string;
DynamicExpressionTrigger: (C: Character) => ExpressionTrigger[] | null | undefined;
DynamicName: (C: Character) => string;
DynamicGroupName: string;
DynamicActivity: (C: Character) => string[] | string | undefined;
DynamicActivity: (C: Character) => string[] | string | null | undefined;
DynamicAudio: ((C: Character) => string) | null;
CharacterRestricted: boolean;
AllowRemoveExclusive: boolean;

View file

@ -1,10 +1,6 @@
"use strict";
const vm = require("vm");
const fs = require("fs");
const typeCheck = require("type-check").typeCheck;
// Load the type definitions
const types = require("./AssetCheck_Types");
const BASE_PATH = "../../";
// Files needed to check the Female3DCG assets
@ -101,58 +97,6 @@ function loadCSV(path, expectedWidth) {
return data;
}
/**
* Check if obj is generic object (not null, not array)
* @param {any} obj - THe object to check
* @returns {boolean}
*/
function isObject(obj) {
return obj && typeof obj === "object" && !Array.isArray(obj);
}
/**
* Validates object, checking for existing, missing and extra keys
* @param {Record<string, string>} definition - The expected definition of the object
* @param {any} obj - The object to check
* @param {string} description - The description to use for errors
* @param {boolean} [allowMissing=false] - If keys missing in definition should be ignored
*/
function validateObject(definition, obj, description, allowMissing=false) {
if (!isObject(obj)) {
error(`${description}: Object expected`);
return;
}
const definitionKeys = Object.keys(definition);
for (const k of definitionKeys) {
if (allowMissing && obj[k] === undefined) continue;
if (!typeCheck(definition[k], obj[k])) {
error(`${description}: expected ${k} to be "${definition[k]}" found ${typeof obj[k]}`);
}
}
for (const k of Object.keys(obj)) {
if (!definitionKeys.includes(k)) {
error(`${description}: unexpected key ${k}`);
}
}
}
/**
* Validates array, checking definition against each element
* @param {Record<string, string>} definition - The expected definition of the object
* @param {any} obj - The object to check
* @param {string} description - The description to use for errors
* @param {boolean} [allowMissing=false] - If keys missing in definition should be ignored
*/
function validateArray(definition, obj, description, allowMissing=false) {
if (!Array.isArray(obj)) {
error(`${description}: Array expected`);
return;
}
for (let i = 0; i < obj.length; i++) {
validateObject(definition, obj[i], description + `[${i}]`, allowMissing);
}
}
(function () {
const context = vm.createContext({ OuterArray: Array, Object: Object });
for (const file of neededFiles) {
@ -164,6 +108,7 @@ function validateArray(definition, obj, description, allowMissing=false) {
// We need to strigify and parse the asset array to have correct Array and Object prototypes, because VM uses different ones
// This unfortunately results in Functions being lost and replaced with undefined
/** @type {AssetGroupDefinition[]} */
const AssetFemale3DCG = JSON.parse(JSON.stringify(context.AssetFemale3DCG));
const AssetFemale3DCGExtended = JSON.parse(JSON.stringify(context.AssetFemale3DCGExtended));
@ -180,95 +125,70 @@ function validateArray(definition, obj, description, allowMissing=false) {
}
// Arrays of type-validated groups and assets
/** @type {AssetGroupDefinition[]} */
const Groups = [];
/** @type {Record<string, AssetDefinition[]>} */
const Assets = {};
// Check all groups
for (const Group of AssetFemale3DCG) {
localError = false;
validateObject(types.AssetGroupType, Group, `Group ${Group.Group}`);
// Don't validate group further, if it had bad data
if (localError) {
continue;
}
Groups.push(Group);
/** @type {AssetDefinition[]} */
const GroupAssets = (Assets[Group.Group] = []);
// Check all assets in groups
if (Array.isArray(Group.Asset)) {
for (const Asset of Group.Asset) {
if (typeof Asset === "string") {
GroupAssets.push({
Name: Asset
});
continue;
}
localError = false;
validateObject(types.AssetType, Asset, `Asset ${Group.Group}:${Asset.Name}`, true);
if (Asset.Name === undefined) {
error(`Asset ${Group.Group}:${Asset.Name}: Missing Name`);
}
for (const Asset of Group.Asset) {
if (typeof Asset === "string") {
GroupAssets.push({
Name: Asset
});
continue;
}
localError = false;
// Check any extended item config
if (Asset.Extended) {
const groupConfig = AssetFemale3DCGExtended[Group.Group] || {};
let assetConfig = groupConfig[Asset.Name];
if (assetConfig) {
validateObject(types.ExtendedItemAssetConfig, assetConfig, `Extended asset archetype for ${Group.Group}:${Asset.Name}`);
const Config = assetConfig.Config;
if (assetConfig && assetConfig.CopyConfig) {
const Overrides = assetConfig.Config;
const { GroupName, AssetName } = assetConfig.CopyConfig;
assetConfig = (AssetFemale3DCGExtended[GroupName || Group.Group] || {} )[AssetName];
if (!assetConfig) {
error(`Asset ${Group.Group}:${Asset.Name}: CopyConfig target not found!`);
assetConfig = groupConfig[Asset.Name];
} else if (Overrides) {
const MergedConfig = Object.assign({}, assetConfig.Config, Overrides);
assetConfig = Object.assign({}, assetConfig, {Config: MergedConfig});
}
// Check any extended item config
if (Asset.Extended) {
const groupConfig = AssetFemale3DCGExtended[Group.Group] || {};
let assetConfig = groupConfig[Asset.Name];
if (assetConfig) {
if (assetConfig && assetConfig.CopyConfig) {
const Overrides = assetConfig.Config;
const { GroupName, AssetName } = assetConfig.CopyConfig;
assetConfig = (AssetFemale3DCGExtended[GroupName || Group.Group] || {} )[AssetName];
if (!assetConfig) {
error(`Asset ${Group.Group}:${Asset.Name}: CopyConfig target not found!`);
assetConfig = groupConfig[Asset.Name];
} else if (Overrides) {
const MergedConfig = Object.assign({}, assetConfig.Config, Overrides);
assetConfig = Object.assign({}, assetConfig, {Config: MergedConfig});
}
if (assetConfig.Config) {
if (assetConfig.Archetype === "modular") {
validateObject(types.ModularItemConfig, assetConfig.Config, `Extended asset config for ${Group.Group}:${Asset.Name}`);
validateArray(types.ModularItemModule, assetConfig.Config.Modules, `Extended asset config for ${Group.Group}:${Asset.Name} Modules`);
for (let i = 0; !localError && i < assetConfig.Config.Modules.length; i++) {
validateArray(types.ModularItemOption, assetConfig.Config.Modules[i].Options, `Extended asset config for ${Group.Group}:${Asset.Name} Modules[${i}].Options`);
}
if (assetConfig.Config) {
if (assetConfig.Archetype === "typed") {
const HasSubscreen = !localError && assetConfig.Config.Options.some(option => !!option.HasSubscreen);
if (!HasSubscreen) {
if (Asset.AllowEffect !== undefined) {
error(`Asset ${Group.Group}:${Asset.Name}: Assets using "typed" archetype should NOT set AllowEffect (unless they use subscreens)`);
}
} else if (assetConfig.Archetype === "typed") {
validateObject(types.TypedItemConfig, assetConfig.Config, `Extended asset config for ${Group.Group}:${Asset.Name}`);
validateArray(types.ExtendedItemOption, assetConfig.Config.Options, `Extended asset config for ${Group.Group}:${Asset.Name} Options`);
const HasSubscreen = !localError && assetConfig.Config.Options.some(option => !!option.HasSubscreen);
if (!HasSubscreen) {
if (Asset.AllowEffect !== undefined) {
error(`Asset ${Group.Group}:${Asset.Name}: Assets using "typed" archetype should NOT set AllowEffect (unless they use subscreens)`);
}
if (Asset.AllowBlock !== undefined) {
error(`Asset ${Group.Group}:${Asset.Name}: Assets using "typed" archetype should NOT set AllowBlock (unless they use subscreens)`);
}
if (Asset.AllowBlock !== undefined) {
error(`Asset ${Group.Group}:${Asset.Name}: Assets using "typed" archetype should NOT set AllowBlock (unless they use subscreens)`);
}
}
}
if (assetConfig.Archetype === "typed" && Asset.AllowType !== undefined) {
error(`Asset ${Group.Group}:${Asset.Name}: Assets using "typed" archetype should NOT set AllowType`);
}
if (!["modular", "typed"].includes(assetConfig.Archetype)) {
error(`Extended asset archetype for ${Group.Group}:${Asset.Name}: Unknown Archetype ${assetConfig.Archetype}`);
}
}
if (assetConfig.Archetype === "typed" && Asset.AllowType !== undefined) {
error(`Asset ${Group.Group}:${Asset.Name}: Assets using "typed" archetype should NOT set AllowType`);
}
if (!["modular", "typed"].includes(assetConfig.Archetype)) {
error(`Extended asset archetype for ${Group.Group}:${Asset.Name}: Unknown Archetype ${assetConfig.Archetype}`);
}
}
}
// Check all layers of assets
if (Array.isArray(Asset.Layer)) {
for (const Layer of Asset.Layer) {
validateObject(types.AssetLayerType, Layer, `Layer ${Group.Group}:${Asset.Name}:${Layer.Name}`, true);
}
}
if (!localError) {
GroupAssets.push(Asset);
}
if (!localError) {
GroupAssets.push(Asset);
}
}
}

View file

@ -1,234 +0,0 @@
"use strict";
const AssetGroupType = {
Asset: "[Object | String]",
Group: "String",
ParentGroup: "Maybe String",
Category: "Maybe String",
Default: "Maybe Boolean",
IsRestraint: "Maybe Boolean",
AllowNone: "Maybe Boolean",
AllowColorize: "Maybe Boolean",
AllowCustomize: "Maybe Boolean",
Random: "Maybe Boolean",
Color: "Maybe [String]",
ParentSize: "Maybe String",
ParentColor: "Maybe String",
Clothing: "Maybe Boolean",
Underwear: "Maybe Boolean",
BodyCosplay: "Maybe Boolean",
Activity: "Maybe [String]",
AllowActivityOn: "Maybe [String]",
Hide: "Maybe [String]",
Block: "Maybe [String]",
Zone: "Maybe [(Number, Number, Number, Number)]",
SetPose: "Maybe [String]",
AllowPose: "Maybe [String]",
AllowExpression: "Maybe [String]",
Effect: "Maybe [String]",
MirrorGroup: "Maybe String",
RemoveItemOnRemove: "Maybe [{ Group: String, Name: String, Type: Maybe String }]",
Priority: "Maybe Number",
Left: "Maybe Number",
Top: "Maybe Number",
FullAlpha: "Maybe Boolean",
Blink: "Maybe Boolean",
InheritColor: "Maybe String",
FreezeActivePose: "Maybe [String]",
PreviewZone: "Maybe (Number, Number, Number, Number)",
DynamicGroupName: "Maybe String",
};
const AssetType = {
Name: "String",
ParentItem: "String",
ParentGroup: "Maybe String",
Enable: "Boolean",
Visible: "Boolean",
Wear: "Boolean",
Activity: "String | [String]",
AllowActivity: "[String]",
AllowActivityOn: "[String]",
BuyGroup: "String",
PrerequisiteBuyGroups: "[String]",
Effect: "[String]",
Bonus: "String",
Block: "[String]",
Expose: "[String]",
Hide: "[String]",
HideItem: "[String]",
HideItemExclude: "[String]",
Require: "[String]",
SetPose: "[String]",
AllowPose: "[String]",
HideForPose: "[String]",
AllowActivePose: "[String]",
WhitelistActivePose: "[String]",
Value: "Number",
Difficulty: "Number",
SelfBondage: "Number",
SelfUnlock: "Boolean",
ExclusiveUnlock: "Boolean",
Random: "Boolean",
RemoveAtLogin: "Boolean",
Time: "Number",
LayerVisibility: "Boolean",
RemoveTime: "Number",
RemoveTimer: "Number",
MaxTimer: "Number",
Priority: "Number",
Left: "Number",
Top: "Number",
Height: "Number",
Zoom: "Number",
Alpha: "[{ Group: Maybe [String], Pose: Maybe [String], Masks: [(Number, Number, Number, Number)] }]",
Prerequisite: "String | [String]",
Extended: "Boolean",
AlwaysExtend: "Boolean",
AlwaysInteract: "Boolean",
AllowLock: "Boolean",
IsLock: "Boolean",
PickDifficulty: "Maybe Number",
OwnerOnly: "Boolean",
LoverOnly: "Boolean",
ExpressionTrigger: "[{ Name: String, Group: String, Timer: Number }]",
RemoveItemOnRemove: "[{ Name: String, Group: String, Type: Maybe String }]",
AllowEffect: "[String]",
AllowBlock: "[String]",
AllowType: "[String]",
DefaultColor: "String | [String]",
Opacity: "Number",
MinOpacity: "Number",
MaxOpacity: "Number",
Audio: "String",
Category: "[String]",
Fetish: "[String]",
ArousalZone: "String",
IsRestraint: "Boolean",
BodyCosplay: "Boolean",
OverrideBlinking: "Boolean",
DialogSortOverride: "Number",
// DynamicDescription: "Function",
// DynamicPreviewImage: "Function",
// DynamicAllowInventoryAdd: "Function",
// DynamicExpressionTrigger: "Function",
// DynamicName: "Function",
DynamicGroupName: "String",
// DynamicActivity: "Function",
// DynamicAudio: "Function",
CharacterRestricted: "Boolean",
AllowRemoveExclusive: "Boolean",
InheritColor: "String",
DynamicBeforeDraw: "Boolean",
DynamicAfterDraw: "Boolean",
DynamicScriptDraw: "Boolean",
HasType: "Boolean",
AllowLockType: "[String]",
AllowColorizeAll: "Boolean",
AvailableLocations: "[String]",
OverrideHeight: "{ Height: Number, Priority: Number, HeightRatioProportion: Maybe Number }",
FreezeActivePose: "[String]",
DrawLocks: "Boolean",
AllowExpression: "[String]",
MirrorExpression: "String",
FixedPosition: "Boolean",
CustomBlindBackground: "Object",
Layer: "[Object]",
Archetype: "String",
FuturisticRecolor: "Boolean",
FuturisticRecolorDisplay: "Boolean",
Attribute: "[String]",
HideItemAttribute: "[String]",
PreviewIcons: "[String]",
};
const AssetLayerType = {
Name: "String",
AllowColorize: "Boolean",
CopyLayerColor: "String",
ColorGroup: "String",
HideColoring: "Boolean",
AllowTypes: "[String]",
HasType: "Boolean",
Visibility: "String",
ParentGroup: "Maybe String",
AllowPose: "[String]",
Priority: "Number",
InheritColor: "String",
Alpha: "[{ Group: Maybe [String], Pose: Maybe [String], Masks: [(Number, Number, Number, Number)] }]",
Left: "Number",
Top: "Number",
HideAs: "{ Group: String, Asset: String }",
HasImage: "Boolean",
Opacity: "Number",
MinOpacity: "Number",
MaxOpacity: "Number",
LockLayer: "Boolean",
MirrorExpression: "String",
HideForPose: "[String]",
AllowModuleTypes: "[String]",
};
const ExtendedItemAssetConfig = {
Archetype: "String",
Config: "Maybe Object", // ModularItemConfig | TypedItemConfig
CopyConfig: "Maybe {GroupName: Maybe String, AssetName: String}",
};
const ModularItemConfig = {
Modules: "[Object]", // ModularItemModule[]
ChatSetting: "Maybe String",
ChangeWhenLocked: "Maybe Boolean",
}
const ModularItemModule = {
Name: "String",
Key: "String",
Options: "[Object]", // ModularItemOption[]
}
const ModularItemOption = {
Difficulty: "Maybe Number",
BondageLevel: "Maybe Number",
SelfBondageLevel: "Maybe Number",
Block: "Maybe [String]",
Hide: "Maybe [String]",
HideItem: "Maybe [String]",
Property: "Maybe Object",
ChangeWhenLocked: "Maybe Boolean",
HasSubscreen: "Maybe Boolean",
}
const TypedItemConfig = {
Options: "[Object]", // ExtendedItemOption
Dialog: "Maybe { Load: Maybe String, TypePrefix: Maybe String, ChatPrefix: Maybe String, NpcPrefix: Maybe String }",
ChatTags: "Maybe [String]",
ChatSetting: "Maybe String",
DrawImages: "Maybe Boolean",
ChangeWhenLocked: "Maybe Boolean",
// Validate: "Maybe Function",
}
const ExtendedItemOption = {
Name: "String",
BondageLevel: "Maybe Number",
SelfBondageLevel: "Maybe Number",
Prerequisite: "Undefined | String | [String]",
SelfBlockCheck: "Maybe Boolean",
ChangeWhenLocked: "Maybe Boolean",
Property: "Maybe Object",
Expression: "Maybe [{ Name: String, Group: String, Timer: Number }]",
HasSubscreen: "Maybe Boolean",
Random: "Maybe Boolean",
}
module.exports = {
AssetGroupType,
AssetType,
AssetLayerType,
ExtendedItemAssetConfig,
ModularItemConfig,
ModularItemModule,
ModularItemOption,
TypedItemConfig,
ExtendedItemOption
};

View file

@ -6,5 +6,8 @@
"strict": true,
"noImplicitAny": false
},
"include": ["AssetCheck.js"]
"include": [
"../../Assets/Female3DCG/Female3DCG_Types.d.ts",
"AssetCheck.js"
]
}

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@
"lint:fix": "eslint --fix --config ./.eslintrc.js ../../Assets/Female3DCG/Female3DCG.js",
"lintscripts": "eslint --quiet \"../../Scripts/**/*.js\" \"../../Screens/**/*.js\" \"../../Assets/**/*.js\" \"../../Tools/**/*.js\" \"../../Backgrounds/Backgrounds.js\"",
"assetcheck": "node AssetCheck",
"assetcheck-typescript": "cd ../../../ && tsc -p BondageClub/Tools/Node/tsconfig-assetcheck.json",
"test": "echo \\\"Error: no test specified\\\" && exit 1"
},
"repository": {
@ -24,9 +25,7 @@
"cheerio": "^1.0.0-rc.3",
"eslint": "^6.8.0",
"eslint-plugin-sort-keys-custom": "^2.0.0",
"marked": "^2.0.0"
},
"dependencies": {
"type-check": "^0.4.0"
"marked": "^2.0.0",
"typescript": "^4.3.5"
}
}

View file

@ -0,0 +1,16 @@
{
"compilerOptions": {
"noEmit": true,
"module": "commonjs",
"lib": ["es2018", "dom"],
"allowJs": true,
"strict": true,
"noImplicitAny": true
},
"include": [
"../../Scripts/**/*",
"../../Screens/**/*",
"../../Assets/**/*",
"../../Backgrounds/**/*.js",
],
}