Merge branch 'craft-difficulty' into 'master'

ENH: Allow crafted items access to the tighten/loosen screen

See merge request 
This commit is contained in:
BondageProjects 2024-11-08 19:55:12 +00:00
commit 2b00c8ad4f
10 changed files with 265 additions and 219 deletions

View file

@ -8863,10 +8863,10 @@ var AssetFemale3DCG = [
ParentItem: "RightAnketHeelBinders",
DefaultColor: ["#808080"],
PoseMapping: {
LegsClosed: "LegsClosed",
Spread: "Spread",
Kneel: PoseType.HIDE,
KneelingSpread: PoseType.HIDE,
LegsClosed: "LegsClosed",
Spread: "Spread",
Kneel: PoseType.HIDE,
KneelingSpread: PoseType.HIDE,
},
Layer: [
{ Name: "Cuffs", AllowColorize: true },
@ -9109,10 +9109,10 @@ var AssetFemale3DCG = [
ParentItem: "LeftAnketHeelBinders",
DefaultColor: ["#808080"],
PoseMapping: {
LegsClosed: "LegsClosed",
Spread: "Spread",
Kneel: PoseType.HIDE,
KneelingSpread: PoseType.HIDE,
LegsClosed: "LegsClosed",
Spread: "Spread",
Kneel: PoseType.HIDE,
KneelingSpread: PoseType.HIDE,
},
Layer: [
{ Name: "Cuffs", AllowColorize: true },
@ -9217,10 +9217,10 @@ var AssetFemale3DCG = [
BuyGroup: "HeelBinders",
DefaultColor: ["#808080"],
PoseMapping: {
LegsClosed: "LegsClosed",
Spread: "Spread",
Kneel: PoseType.HIDE,
KneelingSpread: PoseType.HIDE,
LegsClosed: "LegsClosed",
Spread: "Spread",
Kneel: PoseType.HIDE,
KneelingSpread: PoseType.HIDE,
},
Layer: [
{ Name: "Cuffs", AllowColorize: true },
@ -9323,10 +9323,10 @@ var AssetFemale3DCG = [
BuyGroup: "HeelBinders",
DefaultColor: ["#808080"],
PoseMapping: {
LegsClosed: "LegsClosed",
Spread: "Spread",
Kneel: PoseType.HIDE,
KneelingSpread: PoseType.HIDE,
LegsClosed: "LegsClosed",
Spread: "Spread",
Kneel: PoseType.HIDE,
KneelingSpread: PoseType.HIDE,
},
Layer: [
{ Name: "Cuffs", AllowColorize: true },
@ -17441,30 +17441,30 @@ var AssetFemale3DCG = [
Layer: [
{
Name: "GlossyBase",
AllowTypes: {Style : 0}
AllowTypes: { Style: 0 },
},
{
Name: "TameBase",
CopyLayerColor: "GlossyBase",
AllowTypes: {Style: 1}
AllowTypes: { Style: 1 },
},
{
Name: "Tone"
Name: "Tone",
},
{
Name: "Strands",
AllowTypes: {Strands: 0}
AllowTypes: { Strands: 0 },
},
{
Name: "Braids",
AllowTypes: {Extra: 0}
AllowTypes: { Extra: 0 },
},
{
Name: "Extra",
CopyLayerColor: "Braids",
AllowTypes: {Extra: 1}
}
]
AllowTypes: { Extra: 1 },
},
],
},
{
Name: "HairFront62b",
@ -17475,31 +17475,31 @@ var AssetFemale3DCG = [
Layer: [
{
Name: "GlossyBase",
AllowTypes: {Style : 0}
AllowTypes: { Style: 0 },
},
{
Name: "TameBase",
CopyLayerColor: "GlossyBase",
AllowTypes: {Style: 1}
AllowTypes: { Style: 1 },
},
{
Name: "Tone"
Name: "Tone",
},
{
Name: "Strands",
AllowTypes: {Strands: 0}
AllowTypes: { Strands: 0 },
},
{
Name: "Braids",
AllowTypes: {Extra: 0}
AllowTypes: { Extra: 0 },
},
{
Name: "Extra",
CopyLayerColor: "Braids",
AllowTypes: {Extra: 1}
}
]
}
AllowTypes: { Extra: 1 },
},
],
},
],
Color: [
"#6a3628",
@ -19559,7 +19559,7 @@ var AssetFemale3DCG = [
AllowActivePose: ["LegsClosed", "Kneel", "Hogtied"],
RemoveItemOnRemove: [{ Name: "X-Cross", Group: "ItemDevices" }],
Layer: [
{
{
Name: "Cuffs",
PoseMapping: {
...AssetPoseMapping.ItemFeet,
@ -19569,7 +19569,7 @@ var AssetFemale3DCG = [
},
AllowColorize: true,
},
{
{
Name: "Chain",
PoseMapping: {
...AssetPoseMapping.ItemFeet,
@ -19579,7 +19579,7 @@ var AssetFemale3DCG = [
},
AllowColorize: true,
},
{
{
Name: "Back",
Priority: 6,
PoseMapping: {
@ -19590,7 +19590,7 @@ var AssetFemale3DCG = [
},
CopyLayerColor: "Cuffs",
},
{
{
Name: "LCChain",
Priority: 5,
ParentGroup: null,
@ -19622,7 +19622,7 @@ var AssetFemale3DCG = [
Block: ["Shoes", "ItemBoots"],
RemoveItemOnRemove: [{ Name: "X-Cross", Group: "ItemDevices" }],
Layer: [
{
{
Name: "Cuffs",
PoseMapping: {
...AssetPoseMapping.ItemFeet,
@ -19633,7 +19633,7 @@ var AssetFemale3DCG = [
DefaultColor: ["#808080"],
AllowColorize: true,
},
{
{
Name: "Strap",
Priority: 6,
PoseMapping: {
@ -19644,7 +19644,7 @@ var AssetFemale3DCG = [
},
CopyLayerColor: "Cuffs",
},
{
{
Name: "Chain",
Priority: 22,
ParentGroup: null,
@ -19659,7 +19659,7 @@ var AssetFemale3DCG = [
DefaultColor: ["#AAAAAA"],
AllowColorize: true,
},
{
{
Name: "ShortChain",
Priority: 26,
PoseMapping: {
@ -19672,7 +19672,7 @@ var AssetFemale3DCG = [
AllowTypes: { r: 2 },
CopyLayerColor: "Chain",
},
{
{
Name: "CuffLink",
Priority: 22,
ParentGroup: null,
@ -19687,7 +19687,7 @@ var AssetFemale3DCG = [
DefaultColor: ["#606060"],
AllowColorize: true,
},
{
{
Name: "SpreadBar",
Priority: 22,
PoseMapping: {
@ -19697,11 +19697,11 @@ var AssetFemale3DCG = [
Kneel: PoseType.HIDE,
},
CreateLayerTypes: ["r"],
AllowTypes: { r: [ 4, 5, 6, 7 ] },
AllowTypes: { r: [4, 5, 6, 7] },
DefaultColor: ["#AAAAAA"],
AllowColorize: true,
},
{
{
Name: "ChainSusp",
Priority: 22,
ParentGroup: null,
@ -19715,7 +19715,7 @@ var AssetFemale3DCG = [
AllowTypes: { r: 6 },
CopyLayerColor: "Chain",
},
{
{
Name: "ChainGround",
Priority: 22,
ParentGroup: null,
@ -19729,7 +19729,7 @@ var AssetFemale3DCG = [
AllowTypes: { r: 7 },
CopyLayerColor: "Chain",
},
{
{
Name: "Lock",
Priority: 26,
ParentGroup: null,
@ -19754,7 +19754,7 @@ var AssetFemale3DCG = [
Value: 31,
Time: 12,
RemoveTime: 10,
DefaultColor: ["#505050","#BBBBBB"],
DefaultColor: ["#505050", "#BBBBBB"],
Extended: true,
AllowLock: true,
DrawLocks: true,
@ -19771,27 +19771,27 @@ var AssetFemale3DCG = [
Hogtied: PoseType.HIDE,
},
Alpha: [
{
Group: [
"Socks",
"SocksRight",
"SocksLeft",
"SuitLower",
"ItemBoots",
],
Masks: [[195, 746, 110, 28]],
},
{
Group: [
"Socks",
"SocksRight",
"SocksLeft",
"SuitLower",
"ItemBoots",
],
Masks: [[195, 818, 110, 28]],
},
{
Group: [
"Socks",
"SocksRight",
"SocksLeft",
"SuitLower",
"ItemBoots",
],
Masks: [[195, 746, 110, 28]],
},
{
Group: [
"Socks",
"SocksRight",
"SocksLeft",
"SuitLower",
"ItemBoots",
],
Masks: [[195, 818, 110, 28]],
},
],
Layer: [
{ Name: "Straps", AllowColorize: true },
{ Name: "Details", ParentGroup: null, AllowColorize: true },
@ -31565,33 +31565,16 @@ var AssetFemale3DCG = [
AllowTighten: true,
Extended: true,
SetPose: ["TapedHands"],
DefaultColor: [
"#454545",
"#707070",
"#8D908F",
"#404040",
"#AAB2AF",
],
Effect: [
E.Block,
E.BlockWardrobe,
E.MergedFingers,
E.CuffedArms,
],
DefaultColor: ["#454545", "#707070", "#8D908F", "#404040", "#AAB2AF"],
Effect: [E.Block, E.BlockWardrobe, E.MergedFingers, E.CuffedArms],
RemoveItemOnRemove: [
{
{
Name: "X-Cross",
Group: "ItemDevices",
},
],
ExpressionTrigger: [{ Name: "Soft", Group: "Eyebrows", Timer: 10 }],
Hide: [
"Gloves",
"LeftHand",
"RightHand",
"Bracelet",
"ItemHandheld",
],
Hide: ["Gloves", "LeftHand", "RightHand", "Bracelet", "ItemHandheld"],
PoseMapping: {
...AssetPoseMapping.ItemHands,
BackBoxTie: "BackBoxTie",
@ -31637,7 +31620,7 @@ var AssetFemale3DCG = [
},
AllowColorize: true,
CreateLayerTypes: ["r"],
AllowTypes: { r: [ 1, 2 ] },
AllowTypes: { r: [1, 2] },
},
{
Name: "Detail",
@ -31655,7 +31638,7 @@ var AssetFemale3DCG = [
},
CopyLayerColor: "Main",
CreateLayerTypes: ["r"],
AllowTypes: { r: [ 1, 2 ] },
AllowTypes: { r: [1, 2] },
},
{
Name: "Strait",
@ -31709,7 +31692,7 @@ var AssetFemale3DCG = [
},
AllowColorize: true,
CreateLayerTypes: ["r"],
AllowTypes: { r: [ 4, 5, 6 ] },
AllowTypes: { r: [4, 5, 6] },
},
{
Name: "BDetail",
@ -31727,7 +31710,7 @@ var AssetFemale3DCG = [
},
AllowColorize: true,
CreateLayerTypes: ["r"],
AllowTypes: { r: [ 4, 5, 6 ] },
AllowTypes: { r: [4, 5, 6] },
},
{
Name: "Armbinder",
@ -31745,7 +31728,7 @@ var AssetFemale3DCG = [
},
CopyLayerColor: "Main",
CreateLayerTypes: ["r"],
AllowTypes: { r: [ 5, 6 ]},
AllowTypes: { r: [5, 6] },
},
{
Name: "ADetail",
@ -37193,10 +37176,10 @@ var AssetFemale3DCG = [
"#1B1B1B",
"#D3D3D3",
"#6C0B0B",
"#7B1D1D"
"#7B1D1D",
],
Layer: [
{ Name: "Main"},
{ Name: "Main" },
{
Name: "VertStrap",
AllowTypes: { typed: [0, 1] },
@ -37242,7 +37225,7 @@ var AssetFemale3DCG = [
Prerequisite: "AccessMouth",
Effect: ["BlockMouth"],
ExpressionTrigger: [{ Name: "Soft", Group: "Eyebrows", Timer: 10 }],
DefaultColor: ["#890000","#B41414","#929292"],
DefaultColor: ["#890000", "#B41414", "#929292"],
Layer: [
{
Name: "Panties",
@ -37251,14 +37234,14 @@ var AssetFemale3DCG = [
{
Name: "RolledWad",
AllowTypes: { Stuffing: 1 },
CopyLayerColor: "Panties"
CopyLayerColor: "Panties",
},
{
Name: "Mouth",
AllowTypes: { Stuffing: [0, 1] },
AllowColorize: false,
},
{ Name: "Clothmask", AllowTypes: { Gagtype: 0}},
{ Name: "Clothmask", AllowTypes: { Gagtype: 0 } },
{ Name: "Tape1", AllowTypes: { Gagtype: 1 } },
{
Name: "Tape2",
@ -37271,7 +37254,7 @@ var AssetFemale3DCG = [
AllowTypes: { Gagtype: 3 },
},
],
}
},
],
Color: [
"Default",

View file

@ -1075,14 +1075,14 @@ var AssetFemale3DCGExtended = {
{
Name: "Strands",
Key: "Strands",
Options: [{},{}], // Strands, No Strands
Options: [{}, {}], // Strands, No Strands
},
{
Name: "Extra",
Key: "Extra",
Options: [{},{},{}], // Braids, Extra, None
}
]
Options: [{}, {}, {}], // Braids, Extra, None
},
],
},
HairFront62b: {
Archetype: ExtendedArchetype.MODULAR,
@ -1097,14 +1097,14 @@ var AssetFemale3DCGExtended = {
{
Name: "Strands",
Key: "Strands",
Options: [{},{}], // Strands, No Strands
Options: [{}, {}], // Strands, No Strands
},
{
Name: "Extra",
Key: "Extra",
Options: [{},{},{}], // Braids, Extra, None
}
]
Options: [{}, {}, {}], // Braids, Extra, None
},
],
},
},
HairBack: {
@ -12446,7 +12446,7 @@ var AssetFemale3DCGExtended = {
Effect: [E.GagHeavy],
},
},
{
Property: {
Effect: [E.GagHeavy],
@ -12470,7 +12470,8 @@ var AssetFemale3DCGExtended = {
Effect: [E.GagMedium],
Fetish: ["Lingerie"],
},
}], // Panties, Wad
},
], // Panties, Wad
},
],
DialogPrefix: {
@ -12479,7 +12480,7 @@ var AssetFemale3DCGExtended = {
Option: "OverfilledGagOption",
Chat: "OverfilledGagSet",
},
} // Overfilled Gag
}, // Overfilled Gag
}, // ItemMouth
ItemMouth2: {
ClothGag: {
@ -14235,7 +14236,7 @@ var AssetFemale3DCGExtended = {
Property: {
Difficulty: 7,
Effect: [E.Freeze],
Random: false,
Random: false,
},
},
],
@ -14266,10 +14267,7 @@ var AssetFemale3DCGExtended = {
Property: {
SetPose: ["LegsClosed"],
AllowActivePose: ["Kneel"],
Effect: [
E.Slow,
E.BlockWardrobe,
],
Effect: [E.Slow, E.BlockWardrobe],
Difficulty: 6,
},
},
@ -14283,10 +14281,7 @@ var AssetFemale3DCGExtended = {
"AllFours",
"Hogtied",
],
Effect: [
E.Slow,
E.BlockWardrobe,
],
Effect: [E.Slow, E.BlockWardrobe],
Difficulty: 5,
},
},
@ -14295,11 +14290,7 @@ var AssetFemale3DCGExtended = {
Property: {
SetPose: ["LegsOpen"],
AllowActivePose: ["Kneel"],
Effect: [
E.BlockWardrobe,
E.Freeze,
E.MapImmobile,
],
Effect: [E.BlockWardrobe, E.Freeze, E.MapImmobile],
Difficulty: 7,
},
},
@ -14308,11 +14299,7 @@ var AssetFemale3DCGExtended = {
Property: {
SetPose: ["Spread"],
AllowActivePose: ["KneelingSpread"],
Effect: [
E.BlockWardrobe,
E.Freeze,
E.MapImmobile,
],
Effect: [E.BlockWardrobe, E.Freeze, E.MapImmobile],
Difficulty: 8,
},
},
@ -14320,12 +14307,7 @@ var AssetFemale3DCGExtended = {
// r6 - Spread Bar Sus.Inverted
Property: {
SetPose: ["Spread", "Suspension"],
Effect: [
E.BlockWardrobe,
E.Freeze,
E.Tethered,
E.MapImmobile,
],
Effect: [E.BlockWardrobe, E.Freeze, E.Tethered, E.MapImmobile],
OverrideHeight: { Height: -20, Priority: 41 },
Difficulty: 9,
Expression: [{ Group: "Blush", Name: "High", Timer: 12 }],
@ -14335,12 +14317,7 @@ var AssetFemale3DCGExtended = {
// r7 - Spread Bar Spread.Ground
Property: {
SetPose: ["Spread"],
Effect: [
E.BlockWardrobe,
E.Freeze,
E.Tethered,
E.MapImmobile,
],
Effect: [E.BlockWardrobe, E.Freeze, E.Tethered, E.MapImmobile],
Difficulty: 9,
},
},
@ -17176,11 +17153,7 @@ var AssetFemale3DCGExtended = {
Property: {
SetPose: ["BackBoxTie"],
Block: ["ItemArms"],
Effect: [
E.Block,
E.BlockWardrobe,
E.NotSelfPickable,
],
Effect: [E.Block, E.BlockWardrobe, E.NotSelfPickable],
Difficulty: 8,
},
},
@ -17188,11 +17161,7 @@ var AssetFemale3DCGExtended = {
// r4 - Restrict Belts
Property: {
SetPose: ["BackElbowTouch"],
Effect: [
E.Block,
E.BlockWardrobe,
E.NotSelfPickable,
],
Effect: [E.Block, E.BlockWardrobe, E.NotSelfPickable],
Difficulty: 6,
},
},
@ -17201,11 +17170,7 @@ var AssetFemale3DCGExtended = {
Property: {
SetPose: ["BackElbowTouch"],
Block: ["ItemArms"],
Effect: [
E.Block,
E.BlockWardrobe,
E.NotSelfPickable,
],
Effect: [E.Block, E.BlockWardrobe, E.NotSelfPickable],
Difficulty: 9,
},
},
@ -17214,12 +17179,7 @@ var AssetFemale3DCGExtended = {
Property: {
SetPose: ["BackElbowTouch"],
Block: ["ItemArms"],
Effect: [
E.Block,
E.BlockWardrobe,
E.Slow,
E.NotSelfPickable,
],
Effect: [E.Block, E.BlockWardrobe, E.Slow, E.NotSelfPickable],
Difficulty: 12,
},
},

View file

@ -179,6 +179,7 @@
}
#crafting-extended-button,
#crafting-tighten-button,
#crafting-layering-button,
#crafting-colors-button,
#crafting-private-checkbox,
@ -191,6 +192,10 @@
background-image: url("../Icons/Use.png");
}
#crafting-tighten-button {
background-image: url("../Icons/TightenLoosen.png");
}
#crafting-layering-button {
background-image: url("../Icons/Layering.png");
}
@ -259,6 +264,7 @@
#crafting-private-label,
#crafting-extended-label,
#crafting-tighten-label,
#crafting-ascii-description-label {
grid-template-columns: min-content auto;
}

View file

@ -213,12 +213,12 @@
.button-styling:disabled,
.button-styling[aria-disabled="true"] {
background-color: var(--disabled-color);
background-color: var(--disabled-color) !important;
}
.button-styling:disabled > .button-label,
.button-styling[aria-disabled="true"] > .button-label {
background-color: color-mix(in srgb, var(--disabled-color) 50%, transparent);
background-color: color-mix(in srgb, var(--disabled-color) 50%, transparent) !important;
}
.button-styling:disabled::before,

View file

@ -189,6 +189,8 @@ const CraftingID = /** @type {const} */({
privateLabel: "crafting-private-label",
extendedButton: "crafting-extended-button",
extendedLabel: "crafting-extended-label",
tightenButton: "crafting-tighten-button",
tightenLabel: "crafting-tighten-label",
asciiDescriptionCheckbox: "crafting-ascii-description-checkbox",
asciidescriptionLabel: "crafting-ascii-description-label",
});
@ -442,6 +444,30 @@ var CraftingEventListeners = {
}
},
/**
* @private
* @type {(this: HTMLButtonElement, ev: Event) => void}
*/
_ClickTighten: function _ClickTighten() {
if (!CraftingPreview || !CraftingSelectedItem?.Asset.AllowTighten) {
return;
}
const item = InventoryGet(CraftingPreview, CraftingSelectedItem.Asset.DynamicGroupName);
if (item) {
// Make sure that all expected modifiers are present so that the difficulty factor can easily be extracted afterwards by extracting a deterministic value
item.Craft.Property = CraftingSelectedItem.Property;
item.Difficulty = (
item.Asset.Difficulty
+ SkillGetLevel(Player, "Bondage")
+ (item.Craft?.Property === "Secure" ? 4 : 0)
+ CraftingSelectedItem.DifficultyFactor
);
DialogSetTightenLoosenItem(item);
CraftingModeSet("Tighten");
}
},
/**
* @private
* @type {(this: HTMLButtonElement, ev: Event) => void}
@ -658,12 +684,14 @@ var CraftingEventListeners = {
}
// Disable the extended item config button for non-extended items
const [extendedButton, colorButton, layeringButton] = /** @type {HTMLButtonElement[]} */([
const [extendedButton, colorButton, layeringButton, tightenButton] = /** @type {HTMLButtonElement[]} */([
document.getElementById(CraftingID.extendedButton),
document.getElementById(CraftingID.colorsButton),
document.getElementById(CraftingID.layeringButton),
document.getElementById(CraftingID.tightenButton),
]);
extendedButton.disabled = !CraftingSelectedItem.Asset.Extended;
tightenButton.disabled = !CraftingSelectedItem.Asset.AllowTighten;
colorButton.disabled = false;
colorsInput.disabled = false;
layeringButton.disabled = false;
@ -1120,6 +1148,15 @@ function CraftingLoad() {
{ tag: "span", children: [TextGet("EnterType")] },
],
},
{
tag: "label",
attributes: { id: CraftingID.tightenLabel },
classList: ["crafting-label"],
children: [
ElementButton.Create(CraftingID.tightenButton, CraftingEventListeners._ClickTighten, { disabled: true }),
{ tag: "span", children: [TextGet("EnterTighten")] },
],
},
{
tag: "label",
attributes: { id: CraftingID.asciidescriptionLabel },
@ -1270,6 +1307,12 @@ function CraftingRun() {
DrawCharacter(CraftingPreview, 500, 100, 0.9, false);
}
if (CraftingMode == "Tighten" && DialogTightenLoosenItem) {
TightenLoosenItemDraw();
DrawButton(1885, 25, 90, 90, "", "White", "Icons/Exit.png");
DrawCharacter(CraftingPreview, 500, 100, 0.9, false);
}
if (CraftingMode == "OverridePriority") {
DrawCharacter(CraftingPreview, 500, 100, 0.9, false);
}
@ -1418,6 +1461,7 @@ function CraftingSerialize(craft) {
"", // Old field as used by the deprecated `OverridePriority` crafted craft property, DO NOT REMOVE!
(craft.ItemProperty == null) ? "" : JSON.stringify(craft.ItemProperty),
(craft.TypeRecord == null) ? "" : JSON.stringify(craft.TypeRecord),
(!craft.DifficultyFactor) ? "" : craft.DifficultyFactor.toString(),
];
return stringData.map(i => i.replace(CraftingSerializeSanitize, "")).join(CraftingSerializeFieldSep);
}
@ -1455,6 +1499,7 @@ function CraftingDeserialize(craftString) {
OverridePriority,
ItemProperty,
TypeRecord,
DifficultyFactor,
] = craftString.split(CraftingSerializeFieldSep);
/** @type {CraftingItem} */
@ -1469,6 +1514,7 @@ function CraftingDeserialize(craftString) {
ItemProperty: ItemProperty ? CommonJSONParse(ItemProperty) : {},
Type: Type || null,
TypeRecord: TypeRecord ? CommonJSONParse(TypeRecord) : null,
DifficultyFactor: DifficultyFactor ? Number.parseInt(DifficultyFactor, 10) : undefined,
};
const priority = Number.parseInt(OverridePriority);
@ -1679,6 +1725,7 @@ function CraftingClick(event) {
CraftingSelectedItem = {
Name: "",
Description: "",
DifficultyFactor: 0,
Color: "Default",
Assets: [],
get Asset() {
@ -1723,6 +1770,10 @@ function CraftingClick(event) {
CommonCallFunctionByNameWarn(`Inventory${DialogFocusItem.Asset.Group.Name}${DialogFocusItem.Asset.Name}Click`);
}
if (CraftingMode == "Tighten" && DialogTightenLoosenItem) {
TightenLoosenItemClick();
}
if (CraftingMode == "OverridePriority") {
return;
}
@ -1754,6 +1805,7 @@ function CraftingConvertSelectedToItem() {
Color: CraftingSelectedItem.Color,
Private: CraftingSelectedItem.Private,
TypeRecord: CraftingSelectedItem.TypeRecord || null,
DifficultyFactor: CraftingSelectedItem.DifficultyFactor || undefined,
ItemProperty: CraftingSelectedItem.ItemProperty,
};
}
@ -1767,6 +1819,7 @@ function CraftingConvertItemToSelected(Craft) {
return {
Name: Craft.Name,
Description: Craft.Description,
DifficultyFactor: Craft.DifficultyFactor ?? 0,
Color: Craft.Color,
Private: Craft.Private,
TypeRecord: Craft.TypeRecord || null,
@ -1833,12 +1886,14 @@ function CraftingExitResetElements() {
}
// Disable all buttons that _must_ have an asset selected
const [extendedButton, colorButton, layeringButton] = /** @type {(HTMLButtonElement)[]} */([
const [extendedButton, colorButton, layeringButton, tightenButton] = /** @type {(HTMLButtonElement)[]} */([
document.getElementById(CraftingID.extendedButton),
document.getElementById(CraftingID.colorsButton),
document.getElementById(CraftingID.layeringButton),
document.getElementById(CraftingID.tightenButton),
]);
extendedButton.disabled = true;
tightenButton.disabled = true;
colorButton.disabled = true;
colorsInput.disabled = true;
layeringButton.disabled = true;
@ -1871,6 +1926,7 @@ function CraftingExit(allowPanelClose=true) {
case "Color":
ItemColorExitClick();
return;
case "Tighten":
case "Extended":
DialogLeaveFocusItem();
return;
@ -1963,6 +2019,15 @@ const CraftingValidationRecord = {
GetDefault: (c, a) => "",
StatusCode: CraftingStatusType.ERROR,
},
DifficultyFactor: {
Validate: function (c, a) {
return (c.DifficultyFactor == null || CommonIsInteger(c.DifficultyFactor, -100, 4)) ? true : false;
},
GetDefault: function (c, a) {
return CommonIsInteger(c.DifficultyFactor) ? CommonClamp(c.DifficultyFactor, -100, 4) : undefined;
},
StatusCode: CraftingStatusType.ERROR,
},
Disabled: {
Validate: function (c, a) {
return c.Disabled == null || typeof c.Disabled === "boolean";

View file

@ -28,6 +28,7 @@ EnterExtendedDescription,Enable longer (alphanumeric-only) descriptions
EnterColor,Enter or pick the colors
EnterPriority,Item drawing priority
EnterPrivate,Private (only you can use it)
EnterTighten,Tighten or loosen item
EnterType,Configure extended item
EmptySlot,Empty
PropertyNormal,Normal

1 Exit Return to the main hall
28 EnterColor Enter or pick the colors
29 EnterPriority Item drawing priority
30 EnterPrivate Private (only you can use it)
31 EnterTighten Tighten or loosen item
32 EnterType Configure extended item
33 EmptySlot Empty
34 PropertyNormal Normal

View file

@ -224,7 +224,19 @@ var DialogLeaveFocusItemHandlers = {
* Screen setup callbacks for after exiting the tighten/loosen menu; screen names are used as keys.
* @type {Record<string, (item: Item) => void>}
*/
DialogTightenLoosenItem: {},
DialogTightenLoosenItem: {
Crafting: (item) => {
// Subtract deterministic modifiers so that only the difficulty factor remains
CraftingSelectedItem.DifficultyFactor = (
item.Difficulty
- SkillGetLevel(Player, "Bondage")
- item.Asset.Difficulty
- (item.Craft?.Property === "Secure" ? 4 : 0)
);
item.Difficulty = item.Asset.Difficulty;
CraftingModeSet("Name");
},
},
/**
* Screen setup callbacks for after exiting the extended item menu; screen names are used as keys.

View file

@ -652,82 +652,81 @@ function InventoryGet(C, AssetGroup) {
* @param {AssetGroupItemName} GroupName - The name of the asset group to scan
* @param {CraftingItem} Craft - The crafted properties to apply
* @param {Boolean} Refresh - TRUE if we must refresh the character
* @param {Boolean} ApplyColor - TRUE if the items color must be (re-)applied
* @param {Boolean} PreConfigureItem - TRUE if the default, pre-configured item state of the crafted item must be (re-)applied
* @param {Boolean} CraftWarn - Whether a warning should logged whenever the crafting validation fails
* @returns {void}
*/
function InventoryCraft(Source, Target, GroupName, Craft, Refresh, ApplyColor=true, CraftWarn=true) {
function InventoryCraft(Source, Target, GroupName, Craft, Refresh, PreConfigureItem=true, CraftWarn=true) {
// Gets the item first
if ((Source == null) || (Target == null) || (GroupName == null)) return;
let Item = InventoryGet(Target, GroupName);
if ((Item == null) || !CraftingValidate(Craft, Item.Asset, CraftWarn, Source.IsPlayer())) return;
if (Item.Craft == null) Item.Craft = Craft;
if (Item.Property == null) Item.Property = {};
// Applies the color schema, separated by commas
if (ApplyColor) {
Item.Color = Craft.Color.replace(" ", "").split(",");
}
// Applies a lock to the item
if (Craft.Lock != "")
InventoryLock(Target, Item, Craft.Lock, Source.MemberNumber, false);
const hasCraft = Item.Craft != null;
Item.Craft ??= Craft;
Item.Property ??= {};
Item.Difficulty ??= Item.Asset.Difficulty;
// Sets the crafter name and ID
if (Item.Craft.MemberNumber == null) Item.Craft.MemberNumber = Source.MemberNumber;
if (Item.Craft.MemberName == null) Item.Craft.MemberName = CharacterNickname(Source);
Item.Craft.MemberNumber ??= Source.MemberNumber;
Item.Craft.MemberName ??= CharacterNickname(Source);
// Abort; the properties below are pre-configured by crafted items the first time they're applied,
// but should otherwise not be touched by this function lest they undo any further item customization applied after the fact
if (hasCraft && !PreConfigureItem) {
return;
}
// Applies the color schema, separated by commas
Item.Color = Craft.Color.replace(" ", "").split(",");
// Applies a lock to the item
if (Craft.Lock != "") {
InventoryLock(Target, Item, Craft.Lock, Source.MemberNumber, false);
}
// Set the item priority
if (Craft.ItemProperty) {
Object.assign(Item.Property, CommonCloneDeep(Craft.ItemProperty));
}
// The properties are only applied on self or NPCs to prevent duplicating the effect
if (Target.IsPlayer() || Target.IsNpc()) {
// Update the item's difficulty
Item.Difficulty += Craft.DifficultyFactor ?? 0;
// The secure property adds 5 to the difficulty rating to struggle out
if (Craft.Property === "Secure") {
if (Item.Difficulty == null) Item.Difficulty = 4;
else Item.Difficulty = Item.Difficulty + 4;
}
// The loose property removes 5 to the difficulty rating to struggle out
if (Craft.Property === "Loose") {
if (Item.Difficulty == null) Item.Difficulty = -4;
else Item.Difficulty = Item.Difficulty - 4;
}
// The decoy property makes it always possible to struggle out
if (Craft.Property === "Decoy") Item.Difficulty = -50;
// Expressions cannot be changed if the settings doesn't allow it for the player
if (!Target.IsPlayer() || (Player.OnlineSharedSettings == null) || Player.OnlineSharedSettings.ItemsAffectExpressions) {
// The painful property triggers an expression change
if (Craft.Property === "Painful") {
// Apply Property-specific effects such as difficulty modifiers and facial expressions
switch (Craft.Property) {
case "Secure":
Item.Difficulty += 4;
break;
case "Loose":
Item.Difficulty -= 4;
break;
case "Decoy":
Item.Difficulty = -50;
break;
case "Painful":
if (Target.OnlineSharedSettings?.ItemsAffectExpressions ?? true) {
CharacterSetFacialExpression(Target, "Blush", "ShortBreath", 10);
CharacterSetFacialExpression(Target, "Eyes", "Angry", 10);
CharacterSetFacialExpression(Target, "Eyes2", "Angry", 10);
CharacterSetFacialExpression(Target, "Eyebrows", "Angry", 10);
Item.Property.Fetish = CommonArrayConcatDedupe(Item.Property.Fetish || [], ["Masochism"]);
}
// The comfy property triggers an expression change
if (Craft.Property === "Comfy") {
Item.Property.Fetish = CommonArrayConcatDedupe(Item.Property.Fetish || [], ["Masochism"]);
break;
case "Comfy":
if (Target.OnlineSharedSettings?.ItemsAffectExpressions ?? true) {
CharacterSetFacialExpression(Target, "Blush", "Low", 10);
CharacterSetFacialExpression(Target, "Eyes", "Horny", 10);
CharacterSetFacialExpression(Target, "Eyes2", "Horny", 10);
CharacterSetFacialExpression(Target, "Eyebrows", "Raised", 10);
}
}
break;
}
// Refreshes the character if needed
if (Refresh)
if (Refresh) {
CharacterRefresh(Target, true);
}
}
/**

View file

@ -13,10 +13,13 @@ const TightenLoosenScreenBlacklist = new Set([
* @returns {void} Nothing
*/
function TightenLoosenItemLoad() {
if (DialogTightenLoosenItem.Difficulty == null) DialogTightenLoosenItem.Difficulty = 0;
TightenLoosenItemMaximumDifficulty = SkillGetLevel(Player, "Bondage") + 4;
if (DialogTightenLoosenItem.Asset.Difficulty != null) TightenLoosenItemMaximumDifficulty = TightenLoosenItemMaximumDifficulty + DialogTightenLoosenItem.Asset.Difficulty;
if ((DialogTightenLoosenItem.Craft != null) && (DialogTightenLoosenItem.Craft.Property === "Secure")) TightenLoosenItemMaximumDifficulty = TightenLoosenItemMaximumDifficulty + 4;
DialogTightenLoosenItem.Difficulty ??= DialogTightenLoosenItem.Asset.Difficulty;
TightenLoosenItemMaximumDifficulty = (
SkillGetLevel(Player, "Bondage")
+ 4
+ DialogTightenLoosenItem.Asset.Difficulty
+ (DialogTightenLoosenItem.Craft?.Property === "Secure" ? 4 : 0)
);
}
/**
@ -53,6 +56,10 @@ function TightenLoosenItemDraw() {
* @returns {void} Nothing
*/
function TightenLoosenFacialExpression(C, Blush, Eyes, Eyebrows) {
if (!(C.OnlineSharedSettings?.ItemsAffectExpressions ?? true)) {
return;
}
if ((Blush != "") && ((InventoryGet(C, "Blush") == null) || (InventoryGet(C, "Blush").Property == null) || (InventoryGet(C, "Blush").Property.Expression == null))) CharacterSetFacialExpression(C, "Blush", Blush, 7);
if ((Eyes != "") && ((InventoryGet(C, "Eyes") == null) || (InventoryGet(C, "Eyes").Property == null) || (InventoryGet(C, "Eyes").Property.Expression == null))) CharacterSetFacialExpression(C, "Eyes", Eyes, 3);
if ((Eyebrows != "") && ((InventoryGet(C, "Eyebrows") == null) || (InventoryGet(C, "Eyebrows").Property == null) || (InventoryGet(C, "Eyebrows").Property.Expression == null))) CharacterSetFacialExpression(C, "Eyebrows", Eyebrows, 5);
@ -111,7 +118,7 @@ function TightenLoosenItemClick() {
// If the difficulty changed and we must refresh, improves the skill a little if done in a multiplayer
if (Action != "") {
CharacterRefresh(C, true);
CharacterRefresh(C, true, false);
if (CurrentScreen == "ChatRoom") {
if (((Action == "LoosenLot") || (Action == "LoosenLittle")) && C.IsPlayer()) SkillProgress(Player, "Evasion", 25);
if (((Action == "LoosenLot") || (Action == "LoosenLittle")) && !C.IsPlayer()) SkillProgress(Player, "Bondage", 25);

View file

@ -956,7 +956,7 @@ interface Asset {
readonly AllowHideItem?: readonly string[];
/** @deprecated */
readonly AllowTypes?: never;
readonly AllowTighten?: boolean;
readonly AllowTighten: boolean;
/**
* The default color of the item: an array of length {@link Asset.ColorableLayerCount} consisting of {@link AssetGroup.DefaultColor} and/or valid color hex codes.
*/
@ -3599,6 +3599,7 @@ type CraftingMode = (
| "Name"
| "Color"
| "Extended"
| "Tighten"
| "OverridePriority"
);
@ -3655,6 +3656,12 @@ interface CraftingItem {
* Only relevant if the player is the craft's owner but do they not own the underlying item (_e.g._ due to an inventory wipe).
*/
Disabled?: boolean;
/**
* A tighten-/loosen-specific modifier to-be added to the base difficulty[1] of the item
*
* [1] The sum of the asset difficulty, `Bondage` skill modifier and crafted property `Secure`/`Loose` modifiers.
*/
DifficultyFactor?: number;
}
/**
@ -3666,6 +3673,12 @@ interface CraftingItemSelected {
Name: string;
/** The custom item description. */
Description: string;
/**
* A tighten-/loosen-specific modifier to-be added to the base difficulty[1] of the item
*
* [1] The sum of the asset difficulty, `Bondage` skill modifier and crafted property `Secure`/`Loose` modifiers.
*/
DifficultyFactor: number;
/** The comma-separated color(s) of the item. */
Color: string;
/** The names of the crafted item's supported assets. */