diff --git a/Game/src/KinkyDungeonTests.ts b/Game/src/KinkyDungeonTests.ts index 545b611fb..2f0921146 100644 --- a/Game/src/KinkyDungeonTests.ts +++ b/Game/src/KinkyDungeonTests.ts @@ -40,8 +40,7 @@ function KDTestFullRunthrough(GameLoops: number, Init: boolean, NGP: boolean): b if (Init) { KDSetWorldSlot(0, 1, 0, 0); MiniGameKinkyDungeonCheckpoint = "grv"; - KinkyDungeonInitialize(1); - KDInitPerks(); + KinkyDungeonInitialize(1) .then(() => KDInitPerks()); } for (let i = 0; i < KinkyDungeonMaxLevel * GameLoops; i++) { // Run through the stairs @@ -189,4 +188,4 @@ function KDGetMissingSpellNames() { } return ret; -} \ No newline at end of file +} diff --git a/Game/src/base/KinkyDungeon.ts b/Game/src/base/KinkyDungeon.ts index ba787a20c..3fe45f24f 100644 --- a/Game/src/base/KinkyDungeon.ts +++ b/Game/src/base/KinkyDungeon.ts @@ -966,7 +966,7 @@ function KDLoadToggles() { } KDConsentArray = (localStorage.getItem("KDConsentArray") ? JSON.parse(localStorage.getItem("KDConsentArray")) : {}) || {}; KDSeenConsents = (localStorage.getItem("KDSeenConsents") ? JSON.parse(localStorage.getItem("KDSeenConsents")) : []) || []; - + KDDefaultPalette = localStorage.getItem("KDDefaultPalette") || ""; let loaded = localStorage.getItem("KDToggles") ? JSON.parse(localStorage.getItem("KDToggles")) : {}; @@ -1127,44 +1127,47 @@ function KDReloadMainData(force: boolean) { } - KDReloadChallenge(); + (async () => { + KDReloadChallenge(); - KinkyDungeonNewDress = true; - KDCurrentOutfit = parseInt(localStorage.getItem("kdcurrentoutfit") || 0); - let appearance = DecompressB64(localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit)); - if (!appearance - // No appearance, or legacy - || (StandalonePatched && JSON.parse(appearance).length && JSON.parse(appearance)[0]?.Asset)) { - KinkyDungeonNewDress = false; - if (StandalonePatched) - appearance = - '[{"Model":"BanditBoots","Difficulty":0,"Color":"#ffffff","Filters":{"ShoeLeft":{"gamma":1.4000000000000001,"saturation":0.03333333333333333,"contrast":1.5833333333333333,"brightness":1.6833333333333333,"red":1,"green":1,"blue":1,"alpha":1},"ShoeRight":{"gamma":1.4000000000000001,"saturation":0.03333333333333333,"contrast":1.5833333333333333,"brightness":1.6833333333333333,"red":1,"green":1,"blue":1,"alpha":1}}},{"Model":"WitchBlouse","Difficulty":0,"Color":"#ffffff"},{"Model":"WitchCorset","Difficulty":0,"Color":"#ffffff","Filters":{"Corset":{"gamma":1.45,"saturation":0.4666666666666667,"contrast":1,"brightness":1,"red":1,"green":1,"blue":1,"alpha":1}}},{"Model":"MaidSkirt","Difficulty":0,"Color":"#ffffff","Filters":{"Skirt":{"gamma":1,"saturation":0.23333333333333334,"contrast":1,"brightness":1,"red":1,"green":1,"blue":1.55,"alpha":1.0166666666666666}}},{"Model":"MaidSocks","Difficulty":0,"Color":"#ffffff","Filters":{"SockRight":{"gamma":1,"saturation":0,"contrast":1,"brightness":1,"red":1,"green":1,"blue":1,"alpha":1},"SockLeft":{"gamma":1,"saturation":0,"contrast":1,"brightness":1,"red":1,"green":1,"blue":1,"alpha":1}}},{"Model":"StrappyBikini","Difficulty":0,"Color":"Default"},{"Model":"LatexBra","Difficulty":0,"Color":"Default"}]'; - - } + KinkyDungeonNewDress = true; + KDCurrentOutfit = parseInt(localStorage.getItem("kdcurrentoutfit") || 0); + let appearance = await KinkyDungeonDecompressSave (localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit), SaveType.Outfit); + if (!appearance + // No appearance, or legacy + || (StandalonePatched && JSON.parse(appearance).length && JSON.parse(appearance)[0]?.Asset)) + { + KinkyDungeonNewDress = false; + if (StandalonePatched) { + appearance = + '[{"Model":"BanditBoots","Difficulty":0,"Color":"#ffffff","Filters":{"ShoeLeft":{"gamma":1.4000000000000001,"saturation":0.03333333333333333,"contrast":1.5833333333333333,"brightness":1.6833333333333333,"red":1,"green":1,"blue":1,"alpha":1},"ShoeRight":{"gamma":1.4000000000000001,"saturation":0.03333333333333333,"contrast":1.5833333333333333,"brightness":1.6833333333333333,"red":1,"green":1,"blue":1,"alpha":1}}},{"Model":"WitchBlouse","Difficulty":0,"Color":"#ffffff"},{"Model":"WitchCorset","Difficulty":0,"Color":"#ffffff","Filters":{"Corset":{"gamma":1.45,"saturation":0.4666666666666667,"contrast":1,"brightness":1,"red":1,"green":1,"blue":1,"alpha":1}}},{"Model":"MaidSkirt","Difficulty":0,"Color":"#ffffff","Filters":{"Skirt":{"gamma":1,"saturation":0.23333333333333334,"contrast":1,"brightness":1,"red":1,"green":1,"blue":1.55,"alpha":1.0166666666666666}}},{"Model":"MaidSocks","Difficulty":0,"Color":"#ffffff","Filters":{"SockRight":{"gamma":1,"saturation":0,"contrast":1,"brightness":1,"red":1,"green":1,"blue":1,"alpha":1},"SockLeft":{"gamma":1,"saturation":0,"contrast":1,"brightness":1,"red":1,"green":1,"blue":1,"alpha":1}}},{"Model":"StrappyBikini","Difficulty":0,"Color":"Default"},{"Model":"LatexBra","Difficulty":0,"Color":"Default"}]'; + } + } - CharacterAppearanceRestore(KinkyDungeonPlayer, appearance, false, true); + await CharacterAppearanceRestore(KinkyDungeonPlayer, appearance, false, true); - CharacterReleaseTotal(KinkyDungeonPlayer); + CharacterReleaseTotal(KinkyDungeonPlayer); - CharacterRefresh(KinkyDungeonPlayer); + CharacterRefresh(KinkyDungeonPlayer); - KinkyDungeonInitializeDresses(); - KinkyDungeonDressSet(); + KinkyDungeonInitializeDresses(); + KinkyDungeonDressSet(); - CharacterNaked(KinkyDungeonPlayer); + CharacterNaked(KinkyDungeonPlayer); - - DrawCharacter(KinkyDungeonPlayer, 0, 0, 0.01); - - KDRefreshCharacter.set(KinkyDungeonPlayer, true); - KinkyDungeonDressPlayer(); - KDInitProtectedGroups(KinkyDungeonPlayer); + DrawCharacter(KinkyDungeonPlayer, 0, 0, 0.01); + + KDRefreshCharacter.set(KinkyDungeonPlayer, true); + KinkyDungeonDressPlayer(); + + KDInitProtectedGroups(KinkyDungeonPlayer); + })(); } if (localStorage.getItem("KinkyDungeonKeybindings") && JSON.parse(localStorage.getItem("KinkyDungeonKeybindings"))) { @@ -1297,8 +1300,10 @@ function KinkyDungeonLoad(): void { } else { KinkyDungeonState = "Game"; if (!KinkyDungeonGameData) { - KDSetWorldSlot(0, 1, 0, 0); - KinkyDungeonInitialize(1); + (async () => { + KDSetWorldSlot(0, 1, 0, 0); + await KinkyDungeonInitialize(1); + })(); } } @@ -1481,7 +1486,7 @@ function KinkyDungeonRun() { let ss = KDSaveSlot; KDSendMusicToast(TextGet("KDSaving")); let sd = JSON.stringify(KDSaveQueue.splice(0, 1)[0]); - KinkyDungeonCompressSave(sd).then( + KinkyDungeonCompressSave(sd, SaveType.Game).then( (data) => { try { localStorage.setItem('KinkyDungeonSave', data); @@ -1584,9 +1589,9 @@ function KinkyDungeonRun() { if (StandalonePatched && KDCurrentModels) { let refresh = false; if (CommonTime() > lastGlobalRefresh + GlobalRefreshInterval) { - + lastGlobalRefresh = CommonTime(); - + if (KinkyDungeonDrawState != "Game" && KinkyDungeonState != "Game") { refresh = true; KDGlobalFilterCacheRefresh = true; @@ -1859,28 +1864,35 @@ function KinkyDungeonRun() { KinkyDungeonState = "Name"; KDSaveSlot = (localStorage.getItem('KDLastSaveSlot') !== null) ? parseInt(localStorage.getItem('KDLastSaveSlot')) : 4; let emptySlot = undefined; + let code_z: string = null; for (var i = 1; i <= (saveSlotsPerPage*maxSaveSlotPages); i++) { let num = (i); - KinkyDungeonDBLoad(num).then((code) => { + KinkyDungeonDBLoad(num) + .then((code) => { loadedsaveslots[num - 1] = code; - let decoded = LZString.decompressFromBase64(code); - if (!decoded) return; - let parse = JSON.parse(decoded); - if (decoded && parse?.KDGameData?.PlayerName) - loadedsaveNames[num - 1] = - JSON.parse(decoded)?.KDGameData?.PlayerName; - - if (decoded && parse?.KDGameData?.HighestLevelCurrent) + code_z = code; + //let decoded = LZString.decompressFromBase64(code); + return KinkyDungeonDecompressSave (code, SaveType.Game); + }).then ((decoded) => { + if (!decoded) { + return; + } + const parse = JSON.parse(decoded); + + if (parse?.KDGameData?.PlayerName) + loadedsaveNames[num - 1] = parse?.KDGameData?.PlayerName; + + if (parse?.KDGameData?.HighestLevelCurrent) loadedsaveFloors[num - 1] = parse.KDGameData.HighestLevelCurrent; - - if (decoded && parse?.KDGameData?.Class) + + if (parse?.KDGameData?.Class) loadedsaveClasses[num - 1] = parse.KDGameData.Class; - if (decoded && (parse?.npp || parse?.stats?.npp)) + if (parse?.npp || parse?.stats?.npp) loadedsaveNG[num - 1] = (parse?.npp || parse?.stats?.npp); - if (!emptySlot && !code) { + if (!emptySlot && !code_z) { emptySlot = num; KDSaveSlot = emptySlot; } @@ -1900,13 +1912,19 @@ function KinkyDungeonRun() { }); for (var i = 1; i <= (saveSlotsPerPage*maxSaveSlotPages); i++) { let num = (i); - KinkyDungeonDBLoad(num).then((code) => { + KinkyDungeonDBLoad(num) + .then((code) => { loadedsaveslots[num - 1] = code; - let decoded = LZString.decompressFromBase64(code); - if (decoded && JSON.parse(decoded)?.KDGameData?.PlayerName) - loadedsaveNames[num - 1] = - JSON.parse(decoded)?.KDGameData?.PlayerName; + //let decoded = LZString.decompressFromBase64(code); + return KinkyDungeonDecompressSave (code, SaveType.Game); + }).then ((decoded) => { + if (decoded) { + const playerName = JSON.parse(decoded)?.KDGameData?.PlayerName; + if (playerName) { + loadedsaveNames[num - 1] = playerName; + } + } }); } @@ -1937,7 +1955,7 @@ function KinkyDungeonRun() { KDOptionFilter = ""; return true; }, true, 1000-350/2, 600, 350, 64, TextGet("GameToggles"), KDBaseWhite, ""); - + let ii = 680; if (KDExitButton) { @@ -2102,42 +2120,42 @@ function KinkyDungeonRun() { let newValue = ElementValue("saveInputField"); if (newValue != KDOldSaveCodeValue) { - - KDOldSaveCodeValue = newValue; - let itt = localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit); - let orig = itt ? - JSON.parse(LZString.decompressFromBase64(itt)).appearance - || itt : ""; - if (orig != ElementValue("saveInputField")) KDOriginalValue = orig; - let decompressed = DecompressB64(ElementValue("saveInputField")); - if (decompressed) { - let origAppearance = KinkyDungeonPlayer.Appearance; - try { - let decodeSave = JSON.parse(decompressed); - if (decodeSave?.saveStat?.appearance) { - if (decodeSave.saveStat.poses) { - KDCurrentModels.get(KinkyDungeonPlayer).Poses = decodeSave.saveStat.poses; + (async () => { + KDOldSaveCodeValue = newValue; + let itt = localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit); + let orig = itt ? JSON.parse (await KinkyDungeonDecompressSave (itt, SaveType.Outfit)).appearance || itt + : ""; + if (orig != ElementValue("saveInputField")) KDOriginalValue = orig; + //let decompressed = DecompressB64(ElementValue("saveInputField")); + const decompressed = await KinkyDungeonDecompressSave (ElementValue("saveInputField"), SaveType.Game); + if (decompressed) { + let origAppearance = KinkyDungeonPlayer.Appearance; + try { + let decodeSave = JSON.parse(decompressed); + if (decodeSave?.saveStat?.appearance) { + if (decodeSave.saveStat.poses) { + KDCurrentModels.get(KinkyDungeonPlayer).Poses = decodeSave.saveStat.poses; + } + let appearanceFromSave = JSON.stringify(decodeSave.saveStat.appearance); + await CharacterAppearanceRestore(KinkyDungeonPlayer, appearanceFromSave, false, false); + KinkyDungeonPlayer.Palette = decodeSave.saveStat.Palette; + KinkyDungeonPlayer.metadata = decodeSave.saveStat.metadata; + CharacterRefresh(KinkyDungeonPlayer); + UpdateModels(KinkyDungeonPlayer); + //KDInitProtectedGroups(KinkyDungeonPlayer); + //KinkyDungeonDressPlayer(KinkyDungeonPlayer, false); + + if (KinkyDungeonPlayer.Appearance.length == 0) + throw new DOMException(); } - let appearanceFromSave = JSON.stringify(decodeSave.saveStat.appearance); - CharacterAppearanceRestore(KinkyDungeonPlayer, appearanceFromSave, false, false); - KinkyDungeonPlayer.Palette = decodeSave.saveStat.Palette; - KinkyDungeonPlayer.metadata = decodeSave.saveStat.metadata; - CharacterRefresh(KinkyDungeonPlayer); - UpdateModels(KinkyDungeonPlayer); - //KDInitProtectedGroups(KinkyDungeonPlayer); - //KinkyDungeonDressPlayer(KinkyDungeonPlayer, false); - - if (KinkyDungeonPlayer.Appearance.length == 0) - throw new DOMException(); + } catch (e) { + console.log("Invalid outfit loaded from save"); + KinkyDungeonPlayer.Appearance = origAppearance; + /** breaks the link */ + KDRefreshSelectedModel(KinkyDungeonPlayer); } - - } catch (e) { - console.log("Invalid outfit loaded from save"); - KinkyDungeonPlayer.Appearance = origAppearance; - /** breaks the link */ - KDRefreshSelectedModel(KinkyDungeonPlayer); } - } + })(); } DrawButtonKDEx( @@ -2178,27 +2196,28 @@ function KinkyDungeonRun() { let newValue = ElementValue("saveInputField"); if (newValue != KDOldValue) { + (async () => { + let itt = localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit); + let orig = itt ? JSON.parse(await KinkyDungeonDecompressSave (itt, SaveType.Outfit)).appearance || itt + : ""; + if (orig != ElementValue("saveInputField")) KDOriginalValue = orig; + //let decompressed = DecompressB64(ElementValue("saveInputField")); + const decompressed = await KinkyDungeonDecompressSave (ElementValue("saveInputField"), SaveType.Outfit); + if (decompressed) { + try { + await CharacterAppearanceRestore(Char, decompressed, true, false); + CharacterRefresh(Char); + KDOldValue = newValue; + KDInitProtectedGroups(Char); + KinkyDungeonDressPlayer(Char, true); - let itt = localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit); - let orig = itt ? - JSON.parse(LZString.decompressFromBase64(itt)).appearance - || itt : ""; - if (orig != ElementValue("saveInputField")) KDOriginalValue = orig; - let decompressed = DecompressB64(ElementValue("saveInputField")); - if (decompressed) { - try { - CharacterAppearanceRestore(Char, decompressed, true, false); - CharacterRefresh(Char); - KDOldValue = newValue; - KDInitProtectedGroups(Char); - KinkyDungeonDressPlayer(Char, true); - - if (Char.Appearance.length == 0) - throw new DOMException(); - } catch (e) { - console.log("Invalid code."); + if (Char.Appearance.length == 0) + throw new DOMException(); + } catch (e) { + console.log("Invalid code."); + } } - } + })(); } ElementPosition("saveInputField", 1250, 350, 1000, 230); @@ -2221,7 +2240,7 @@ function KinkyDungeonRun() { KDDrawConsent(500); } - + else if (KinkyDungeonState == "Challenge") { //DrawTextKD(TextGet("KinkyDungeonChallenge"), 1250, 80, KDBaseWhite, KDTextGray1, 48); KDDrawGameSetupTabs(); @@ -2725,9 +2744,9 @@ function KinkyDungeonRun() { + (loadedsaveNG[slot-1] ? TextGet("KDNGSaveLabel") + loadedsaveNG[slot-1] : "") ) || TextGet("KDEmpty")); let textColor = (danger && (slot == KDSaveSlot)) ? dangerColor : (slot == KDSaveSlot ? selectedColor : defaultColor); - DrawTextFitKD(slotText, - ((danger && (slot == KDSaveSlot)) ? (Math.random() > 0.5 ? -1 : 1) : 0) + xOffsets[col % 2] + 10, - ((danger && (slot == KDSaveSlot)) ? (Math.random() > 0.5 ? -1 : 1) : 0) + yOffset + 25, + DrawTextFitKD(slotText, + ((danger && (slot == KDSaveSlot)) ? (Math.random() > 0.5 ? -1 : 1) : 0) + xOffsets[col % 2] + 10, + ((danger && (slot == KDSaveSlot)) ? (Math.random() > 0.5 ? -1 : 1) : 0) + yOffset + 25, 385, textColor, undefined, undefined, "left") DrawButtonKDEx("slot_" + slot + "prev", () => { // on click change save slot KDSaveSlot = slot; @@ -2735,11 +2754,11 @@ function KinkyDungeonRun() { }, true, ((danger && (slot == KDSaveSlot)) ? (Math.random() > 0.5 ? -1 : 1) : 0) + xOffsets[col % 2], ((danger && (slot == KDSaveSlot)) ? (Math.random() > 0.5 ? -1 : 1) : 0) + yOffset, - 400, 50, "", textColor, "", + 400, 50, "", textColor, "", undefined, undefined, true, KDButtonColor, undefined, true, { centerText: false, - + }); } } @@ -3291,7 +3310,7 @@ function KinkyDungeonRun() { KDForceAllCull = false; - + KDLastActiveElement = document.activeElement; } @@ -4547,6 +4566,7 @@ function KinkyDungeonDBOpen(): Promise { }); } + /** * Save a game to the database. */ @@ -4556,16 +4576,18 @@ function KinkyDungeonDBSave(saveslot: number, gamecode?: string) { console.error("Save slot is not defined"); return; // This is an invalid call or the save slot has not been set. } - if (gamecode == undefined) { - // We are going to use the current game state as a save code. - save = LZString.compressToBase64(JSON.stringify(KinkyDungeonGenerateSaveData())); - } - else { - save = gamecode; - } - - // Get the savegame database - KinkyDungeonDBOpen().then((db) => { + (async () => { + if (gamecode == undefined) { + // We are going to use the current game state as a save code. + save = await KinkyDungeonCompressSave (JSON.stringify (KinkyDungeonGenerateSaveData()), SaveType.Game); + } + else { + save = gamecode; + } + })().then (() => { + // Get the savegame database + return KinkyDungeonDBOpen(); + }).then ((db) => { // Create a transaction const transaction = db.transaction(KDGameSaveDBStoreName, "readwrite"); const store = transaction.objectStore(KDGameSaveDBStoreName); @@ -4713,35 +4735,35 @@ function KDDrawLoadMenu() { }, true, CombarXX + 210, YYstart - 5, 150, 45, TextGet("KDCloudSaves"), KDBaseWhite, undefined, "") } - + DrawCheckboxKDEx( "LoadoverrideOF", () => { KDToggles.OverrideOutfit = !KDToggles.OverrideOutfit; KDSaveToggles(); - + // Dress the KDPreviewModel ModelPreviewLoaded = false; KinkyDungeonDressModelPreview(); return true; - }, true, CombarXX + 20, YY + 165 + 375 + 19, 52, 52, + }, true, CombarXX + 20, YY + 165 + 375 + 19, 52, 52, TextGet("KDToggleOverrideOutfitAbbr"), KDToggles.OverrideOutfit, false, KDTextWhite, undefined, { }); - + DrawCheckboxKDEx( "LoadoverrideCo", () => { KDToggles.OverrideConsent = !KDToggles.OverrideConsent; KDSaveToggles(); - + // Dress the KDPreviewModel ModelPreviewLoaded = false; KinkyDungeonDressModelPreview(); return true; - }, true, CombarXX + 20, YY + 165 + 375 + 25 + 58, 52, 52, + }, true, CombarXX + 20, YY + 165 + 375 + 25 + 58, 52, 52, TextGet("KDToggleOverrideConsentAbbr"), KDToggles.OverrideConsent, false, KDTextWhite, undefined, { @@ -4771,14 +4793,13 @@ function KDDrawLoadMenu() { for (let i = startSaveSlot; i < endSaveSlot; i++) { let num = (i); // Slot button - DrawButtonKDEx(TextGet("KDSaveSlotButton") + num, () => { + DrawButtonKDEx(TextGet("KDSaveSlotButton") + num, async () => { console.log("Pressed button for save slot " + num); loadedSaveforPreview = null; LoadMenuCurrentSlot = num; LoadMenuCurrentSave = loadedsaveslots[num - 1]; - loadedSaveforPreview = KinkyDungeonLoadPreview(LoadMenuCurrentSave); + loadedSaveforPreview = await KinkyDungeonLoadPreview(LoadMenuCurrentSave); - let origSaveSlot = KDSaveSlot; KDSaveSlot = num; // @ts-ignore if (!loadedSaveforPreview.invalid) { @@ -4860,16 +4881,15 @@ function KDDrawLoadMenu() { else { for (let i = 1; i < 3; i++) { let num = (i); - DrawButtonKDEx(TextGet("KDSaveSlotButton") + num, () => { + DrawButtonKDEx(TextGet("KDSaveSlotButton") + num, async () => { // Slot button console.log("Pressed button for save slot " + num); loadedSaveforPreview = null; LoadMenuCurrentSlot = num; LoadMenuCurrentSave = loadedcloudsaveslots[num - 1]; - loadedSaveforPreview = KinkyDungeonLoadPreview(LoadMenuCurrentSave); - let origSaveSlot = KDSaveSlot; - KDSaveSlot = num; - // @ts-ignore + loadedSaveforPreview = await KinkyDungeonLoadPreview(LoadMenuCurrentSave); + KDSaveSlot = num; + // @ts-ignore if (!loadedSaveforPreview.invalid) { // Dress the KDPreviewModel ModelPreviewLoaded = false; @@ -4957,25 +4977,28 @@ function KDDrawLoadMenu() { ElementPosition("saveInputField", CombarXX + 215, YY + 145 - 33, 400, 300 - 85); let newValue = ElementValue("saveInputField"); // Load from Code button - DrawButtonKDEx("LoadFromCodeButton", () => { + DrawButtonKDEx("LoadFromCodeButton", async () => { KinkyDungeonKeybindingsTemp = Object.assign({}, KinkyDungeonKeybindingsTemp); LoadMenuCurrentSlot = undefined; - if (newValue) { - loadedSaveforPreview = KinkyDungeonLoadPreview(newValue); - if (loadedSaveforPreview) LoadMenuCurrentSlot = -1; - } else if (KDSlot0) { - loadedSaveforPreview = KinkyDungeonLoadPreview(KDSlot0); - if (loadedSaveforPreview) LoadMenuCurrentSlot = 0; - LoadMenuCurrentSave = KDSlot0; - } - else { + const decodeSrc = newValue || KDSlot0; + const slotidx = !!newValue ? -1 : 0; + + if (decodeSrc) { + loadedSaveforPreview = await KinkyDungeonLoadPreview (decodeSrc); + if (loadedSaveforPreview) { + LoadMenuCurrentSlot = slotidx; + } + if (slotidx == 0) { + LoadMenuCurrentSave = decodeSrc; + } + } else { loadedSaveforPreview = null; LoadMenuCurrentSlot = -1; } + if (loadedSaveforPreview) { - LoadMenuCurrentSave = newValue ? newValue : KDSlot0; + LoadMenuCurrentSave = newValue || KDSlot0; - let origSaveSlot = KDSaveSlot; KDSaveSlot = 0; // @ts-ignore if (!loadedSaveforPreview.invalid) { @@ -5343,14 +5366,15 @@ function KDDrawLoadMenu() { hotkeyPress: KinkyDungeonKeySkip[0], }); // Play Game with current save data! - DrawButtonKDEx("KDLoadGame", () => { + DrawButtonKDEx("KDLoadGame", async () => { if (LoadMenuCurrentSave != "") { KinkyDungeonKeybindingsTemp = Object.assign({}, KinkyDungeonKeybindingsTemp); KinkyDungeonNewGame = 0; KDMapData.Grid = ""; - KinkyDungeonInitialize(1, true); + await KinkyDungeonInitialize(1, true); MiniGameKinkyDungeonCheckpoint = "grv"; - if (KinkyDungeonLoadGame(LoadMenuCurrentSave, KDToggles.OverrideConsent)) { + const success = await KinkyDungeonLoadGame (LoadMenuCurrentSave, KDToggles.OverrideConsent); + if (success) { KDGenMapCallback = () => { if (KDMapData.Grid == "") KinkyDungeonCreateMap(KinkyDungeonMapParams[(KinkyDungeonMapIndex[MiniGameKinkyDungeonCheckpoint] || MiniGameKinkyDungeonCheckpoint)], @@ -5363,7 +5387,6 @@ function KDDrawLoadMenu() { return "Game"; }; KinkyDungeonState = "GenMap"; - } LoadMenuCurrentSave = undefined; LoadMenuCurrentSlot = undefined; @@ -5586,11 +5609,11 @@ function KinkyDungeonDressModelPreview() { KDRefreshSelectedModel(KDPreviewModel); if (KDCurrentModels.get(KDPreviewModel)) KDCurrentModels.get(KDPreviewModel).Poses = loadedSaveforPreview.saveStat.poses; - + UpdateModels(KDPreviewModel); } } - + //CharacterAppearanceRestore(KDPreviewModel, DecompressB64(localStorage.getItem(`kinkydungeonappearance${KDCurrentOutfit}`))) //setTimeout(() => { DrawCharacter(KDPreviewModel, PIXIWidth, PIXIHeight, 0.1); @@ -5610,7 +5633,7 @@ function KinkyDungeonDressModelPreview() { /** * Generate Preview data function */ -function KinkyDungeonLoadPreview(String: string): KinkyDungeonSave { +async function KinkyDungeonLoadPreview(String: string): Promise { if (!String) { return { // @ts-ignore @@ -5619,7 +5642,8 @@ function KinkyDungeonLoadPreview(String: string): KinkyDungeonSave { }; } try { - let str: string = DecompressB64(String.trim()); + let str: string = await KinkyDungeonDecompressSave (String.trim(), SaveType.Game); + let returndata: KinkyDungeonSave = null; // We do a little JS witchery here @@ -5838,48 +5862,51 @@ function KinkyDungeonLoadPreview(String: string): KinkyDungeonSave { } function KinkyDungeonStartNewGame(Load: boolean = false) { - KinkyDungeonSendEvent("beforeNewGame", {Load: Load}); - KinkyDungeonNewGame = 0; - let cp = KinkyDungeonMapIndex.grv; - KDUpdateHardMode(); - KinkyDungeonInitialize(1, Load); - MiniGameKinkyDungeonCheckpoint = "grv"; - KDMapData.Grid = ""; - if (Load) { - KinkyDungeonLoadGame(undefined, true); - KDSendEvent('loadGame'); - } else { - KDSendEvent('newGame'); - KDGameData.RoomType = "JourneyFloor";//KinkyDungeonStatsChoice.get("easyMode") ? "ShopStart" : "JourneyFloor"; - KDSetWorldSlot(0, 0, 0, 0); - KDInitializeJourney(""); + (async () => { + KinkyDungeonSendEvent("beforeNewGame", {Load: Load}); + KinkyDungeonNewGame = 0; + let cp = KinkyDungeonMapIndex.grv; + KDUpdateHardMode(); + await KinkyDungeonInitialize(1, Load); + MiniGameKinkyDungeonCheckpoint = "grv"; + KDMapData.Grid = ""; + if (Load) { + await KinkyDungeonLoadGame(undefined, true); + KDSendEvent('loadGame'); + } else { + KDSendEvent('newGame'); + KDGameData.RoomType = "JourneyFloor";//KinkyDungeonStatsChoice.get("easyMode") ? "ShopStart" : "JourneyFloor"; + KDSetWorldSlot(0, 0, 0, 0); + KDInitializeJourney(""); - if (KDTileToTest) { - KinkyDungeonMapIndex.grv = cp; - } + if (KDTileToTest) { + KinkyDungeonMapIndex.grv = cp; + } - KDGameData.PlayerName = localStorage.getItem("PlayerName") || "Ada"; - KinkyDungeonPlayer.Name = KDGameData.PlayerName; - } - if (!KDMapData.Grid) { - KinkyDungeonCreateMap(KinkyDungeonMapParams[(KinkyDungeonMapIndex[MiniGameKinkyDungeonCheckpoint] || MiniGameKinkyDungeonCheckpoint)], "JourneyFloor", "", MiniGameKinkyDungeonLevel, false, Load); - KDInitPerks(); - } - KinkyDungeonState = "Game"; + KDGameData.PlayerName = localStorage.getItem("PlayerName") || "Ada"; + KinkyDungeonPlayer.Name = KDGameData.PlayerName; + } + if (!KDMapData.Grid) { + KinkyDungeonCreateMap(KinkyDungeonMapParams[(KinkyDungeonMapIndex[MiniGameKinkyDungeonCheckpoint] || MiniGameKinkyDungeonCheckpoint)], "JourneyFloor", "", MiniGameKinkyDungeonLevel, false, Load); + KDInitPerks(); + } + KinkyDungeonState = "Game"; - if (KinkyDungeonKeybindings) { - KDCommitKeybindings(); - } - if (KDSoundEnabled()) AudioPlayInstantSoundKD(KinkyDungeonRootDirectory + "Audio/StoneDoor_Close.ogg"); + if (KinkyDungeonKeybindings) { + KDCommitKeybindings(); + } + if (KDSoundEnabled()) AudioPlayInstantSoundKD(KinkyDungeonRootDirectory + "Audio/StoneDoor_Close.ogg"); - KDModsAfterGameStart(); - if (!Load) - KinkyDungeonSendEvent("afterNewGame", {Load: Load}); - else KinkyDungeonSendEvent("afterModsLoadedAndLoadGame", {Load: Load}); + KDModsAfterGameStart(); + if (!Load) + KinkyDungeonSendEvent("afterNewGame", {Load: Load}); + else + KinkyDungeonSendEvent("afterModsLoadedAndLoadGame", {Load: Load}); + })(); } let KDConsentPerkTypes = ["Red", "Yellow", "Green"]; @@ -5985,7 +6012,7 @@ function KDUpdatePlugSettings(evalHardMode: boolean, allow_backport_consent?: bo KDUpdateHardMode(); } - + KDUpdateConsentSettings(allow_backport_consent); } @@ -6098,24 +6125,28 @@ function KinkyDungeonHandleClick(event: MouseEvent) { KDHandleTileEditor(); } else if (KinkyDungeonState == "Load"){ if (MouseIn(875, 750, 350, 64)) { - KinkyDungeonNewGame = 0; - KDMapData.Grid = ""; - if (!KDToggles.OverrideOutfit) - KinkyDungeonConfigAppearance = false; - KinkyDungeonInitialize(1, true); - MiniGameKinkyDungeonCheckpoint = "grv"; - if (KinkyDungeonLoadGame(ElementValue("saveInputField"), KDToggles.OverrideConsent)) { - KDSendEvent('loadGame'); - //KDInitializeJourney(KDJourney); - if (KDMapData.Grid == "") KinkyDungeonCreateMap(KinkyDungeonMapParams[(KinkyDungeonMapIndex[MiniGameKinkyDungeonCheckpoint] || MiniGameKinkyDungeonCheckpoint)], KDMapData.RoomType || "", KDMapData.MapMod || "", MiniGameKinkyDungeonLevel, false, true); - ElementRemove("saveInputField"); - KinkyDungeonState = "Game"; + (async () => { + KinkyDungeonNewGame = 0; + KDMapData.Grid = ""; + if (!KDToggles.OverrideOutfit) + KinkyDungeonConfigAppearance = false; + await KinkyDungeonInitialize(1, true); + MiniGameKinkyDungeonCheckpoint = "grv"; + //if (KinkyDungeonLoadGame(ElementValue("saveInputField"))) { + const success = await KinkyDungeonLoadGame (ElementValue("saveInputField"), KDToggles.OverrideConsent) + if (success) { + KDSendEvent('loadGame'); + //KDInitializeJourney(KDJourney); + if (KDMapData.Grid == "") KinkyDungeonCreateMap(KinkyDungeonMapParams[(KinkyDungeonMapIndex[MiniGameKinkyDungeonCheckpoint] || MiniGameKinkyDungeonCheckpoint)], KDMapData.RoomType || "", KDMapData.MapMod || "", MiniGameKinkyDungeonLevel, false, true); + ElementRemove("saveInputField"); + KinkyDungeonState = "Game"; - if (KinkyDungeonKeybindings) { - KDCommitKeybindings(); + if (KinkyDungeonKeybindings) { + KDCommitKeybindings(); + } + KDModsAfterGameStart(); } - KDModsAfterGameStart(); - } + })(); return true; } } else if (KinkyDungeonState == "LoadOutfit"){ @@ -6840,16 +6871,34 @@ function KinkyDungeonSaveGame(ToString: boolean = false): KinkyDungeonSave { } +enum SaveType { + Game = 'game', + Outfit = 'outfit', + Wardrobe = 'wardrobe', // Complete outfit collection; TBD +} + +interface SaveWorkerMsg { + op: 'cmp' | 'err' | 'cmp-legacy'; + type: SaveType; + data: string; +} + let KDSaveTimeout = 600000; // 10 minutes -async function KinkyDungeonCompressSave(save: string): Promise { +async function KinkyDungeonCompressSave(save: string, type = SaveType.Game): Promise { if (window.Worker) { + const workerMsg: SaveWorkerMsg = { + op: 'cmp', + type: type, + data: save + }; const myWorker = new Worker("out/saveworker.js"); - let pp = new Promise(function (resolve, reject) { + let pp = new Promise(function (resolve, reject) { myWorker.onmessage = function(e) { console.log('Compressed data received from worker'); resolve(e.data); } - myWorker.postMessage(save); + //myWorker.postMessage(save); + myWorker.postMessage(workerMsg); console.log('Save message posted to worker'); setTimeout(reject, KDSaveTimeout); // 10 min timeout }); @@ -6857,25 +6906,139 @@ async function KinkyDungeonCompressSave(save: string): Promise { .then((v) => { console.log('Yay'); myWorker.terminate(); - return v;}) - .catch((v) => { + return v.data;}) + .catch((_v) => { console.log('Nay'); myWorker.terminate(); return LZString.compressToBase64(save);}); } else { + /** + * IMPORTANT: Keep this block in sync with saveworker.js. + */ console.log('Your browser doesn\'t support web workers.'); - return LZString.compressToBase64(save); + const mime_type = `application/vnd.straightlaced.kinkydungeon.save.${type.toString()}+gzip;version=2`; + try { + // Blob. CompressionStream discards MIME-type; we'll add it later. + const save_b_js = new Blob ([save]); + const save_pipe = save_b_js.stream().pipeThrough (new CompressionStream ('gzip')); + + // Compressed blob. + const resp = new Response (save_pipe, { headers: [["Content-Type", mime_type ]]}); + const save_b_z = await resp.blob(); + + // Snarfed from MDN Web docs. + async function toBase64DataURL (blob: Blob): Promise { + return await new Promise ((resolve, reject) => { + const reader = Object.assign (new FileReader(), { + onload: () => resolve (reader.result as string), + onerror: () => reject (reader.error) + }); + reader.readAsDataURL (blob); + }); + } + + const save_z64 = await toBase64DataURL (save_b_z); + + return save_z64; + } catch (err) { + console.log (`Caught ${err}; falling back to LZString...`); + return LZString.compressToBase64 (save); + } + } +} + +/** + * Decompress a save code into JSON. + * + * @param save - Compressed save code, either legacy LZString, or new `data:` URL. + * @param expected_type - Expected type of save. + * @return - JSON string, suitable for parsing; or null if the expected_type doesn't match the code's type. + */ +async function KinkyDungeonDecompressSave (save: string, expected_type = SaveType.Game): Promise +{ + if (!save || !save.trim) { + return null; + } + + /* + * Trim leading/trailing whitespace, and join newline-separated + * lines into single string. + */ + save = "".concat (...save.trim().split ('\n')); + + if (!save.startsWith ('data:')) { + /* Legacy LSZtring save code. */ + console.log ("Decompressing legacy LZString save code..."); + return LZString.decompressFromBase64 (save); + } + + /* + * New style `data:` URL. + * Verify type and version number. (Don't need to match against the whole thing.) + */ + const save_head = save.slice (0, 256); + + /* + * Check save type. + * TODO: Test for `+gzip` and skip decompression if it's not there. + */ + let re = /vnd.straightlaced.kinkydungeon.save.(\w+)(?:\+gzip)?;/ + let res = save_head.match (re); + if (!res || res.length < 2) { + console.log ("Unrecognized save type."); + return null; + } + const save_type = res[1]; + if (save_type != expected_type) { + console.log (`Expected save code type ${expected_type}, got ${save_type}.`); + return null; + } + + /* + * Check version. + */ + re = /;version=(\d+);/ + res = save_head.match (re); + if (!res || res.length < 2) { + console.log ("Missing version in save code."); + return null; + } + const save_version = res[1]; + if (Number (save_version) != 2) { + console.log (`Unsupported save version ${save_version}`); + return null; + } + + /* + * Decode and decompress the data: URL. (This is fast enough that it + * doesn't need to be in a worker.) + */ + try { + const res = await fetch (save); + const save_b_z = new Blob ([await res.blob()]); + const save_pipe = save_b_z.stream().pipeThrough (new DecompressionStream ('gzip')); + + // Decompressed blob. + const resp = new Response (save_pipe); + const save_js = await resp.text(); + + return save_js; + } catch (err) { + console.log (`Save code decompression failed: ${err}`); } + return null; } +// LZString code // N4IgNgpgbhYgXARgDQgMYAsJoNYAcB7ASwDsAXBABlQCcI8FQBxDAgZwvgFoBWakAAo0ibAiQg0EvfgBkIAQzJZJ8fgFkIZeXFWoASgTwQqqAOpEwO/gFFIAWwjk2JkAGExAKwCudFwElLLzYiMSoAX1Q0djJneGAIkAIaACNYgG0AXUisDnSskAATOjZYkAARCAAzeS8wClQAcwIwApdCUhiEAGZUSBgwWNBbCAcnBBQ3Tx9jJFQAsCCQknGEtiNLPNRSGHIkgE8ENNAokjYvO3lkyEYQEnkHBEECMiW1eTuQBIBHL3eXsgOSAixzEZwuVxmoDuD3gTxeYgAylo7KR5J9UD8/kQAStkCDTudLtc4rd7jM4UsAGLCBpEVrfX7kbGAxDAkAAdwUhGWJOh5IA0iQiJVjGE2cUyDR5B0bnzHmUvGgyAAVeRGOQNZwJF4NDBkcQlca9Ai4R7o0ASqUy3lk+WKlVqiCUiCaNTnOwHbVEXX6iCG2bgE04M1hDJhIA= -function KinkyDungeonLoadGame(String: string = "", kdloadconsent = false) { +async function KinkyDungeonLoadGame(String: string = null, kdloadconsent = false): Promise { localStorage.setItem('KDLastSaveSlot', "" + KDSaveSlot); KinkyDungeonSendEvent("beforeLoadGame", {}); - let str = String ? DecompressB64(String.trim()) : - (localStorage.getItem('KinkyDungeonSave') ? DecompressB64(localStorage.getItem('KinkyDungeonSave')) - : (loadedsaveslots[KDSaveSlot-1] ? DecompressB64(loadedsaveslots[KDSaveSlot-1]) : "")); - if (str) { + const src_str = String ?? localStorage.getItem('KinkyDungeonSave') ?? loadedsaveslots[KDSaveSlot-1]; + + if (src_str) { + const str = await KinkyDungeonDecompressSave (src_str, SaveType.Game); + let saveData: KinkyDungeonSave = JSON.parse(str); if ( saveData && saveData.spells != undefined @@ -7408,7 +7571,7 @@ function KDDrawGameSetupTabs(_xOffset: number = 500, xpad: number = 10, num: num }); ii++; } - + DrawButtonKDEx("backButton", (_b) => { @@ -7420,7 +7583,7 @@ function KDDrawGameSetupTabs(_xOffset: number = 500, xpad: number = 10, num: num return true; } KinkyDungeonState = KinkyDungeonState != "Diff" ? "Diff" : "Name"; - + KDConsentFilter = ""; return true; }, true, 1075, 900, 350, 64, TextGet( @@ -7587,8 +7750,8 @@ async function KDLoadBackupDialog() { } else { KDSendMusicToast(TextGet("KDBackupLoadFail")); } - - + + }); reader.readAsText(file); } catch (err) { @@ -7687,7 +7850,7 @@ async function KDSaveBackupDialog(filename: string, text: string) { reject(null); } }); - + } function KDChangeZoom(change: number) { @@ -7732,7 +7895,7 @@ async function RunGenMapCallback() { function KDReloadChallenge() { KDSeenConsents = (localStorage.getItem("KDSeenConsents") ? JSON.parse(localStorage.getItem("KDSeenConsents")) : []) || []; - + KDConsentArray = (localStorage.getItem("KDConsentArray") ? JSON.parse(localStorage.getItem("KDConsentArray")) : {}) || {}; KinkyDungeonSexyMode = localStorage.getItem("KinkyDungeonSexyMode") != undefined ? localStorage.getItem("KinkyDungeonSexyMode") == "True" : true; KinkyDungeonClassMode = localStorage.getItem("KinkyDungeonClassMode") != undefined ? localStorage.getItem("KinkyDungeonClassMode") : "Mage"; @@ -7931,9 +8094,9 @@ function KDTogglesDraw() { } }) return true; - }, !KDBusySavingBackup, - PIXIWidth - 235, 900, 200, 64, - TextGet("KDFullBackup"), KDBusySavingBackup ? KDBaseLightGrey : KDBaseWhite, undefined, undefined, undefined, + }, !KDBusySavingBackup, + PIXIWidth - 235, 900, 200, 64, + TextGet("KDFullBackup"), KDBusySavingBackup ? KDBaseLightGrey : KDBaseWhite, undefined, undefined, undefined, undefined, undefined, undefined, undefined, { hoverData: { text: TextGet("KDFullBackupDesc") @@ -7942,13 +8105,13 @@ function KDTogglesDraw() { hotkey: KDHotkeyToText(KinkyDungeonKeyMenu[3]), hotkeyPress: KinkyDungeonKeyMenu[3], }); - + DrawButtonKDEx("kdtoggle_load", (b) => { KDLoadBackupDialog(); return true; - }, !KDBusyLoadingFile, - PIXIWidth - 450, 900, 200, 64, - TextGet("KDLoadBackup"), KDBusyLoadingFile ? KDBaseLightGrey : KDBaseWhite, undefined, undefined, undefined, + }, !KDBusyLoadingFile, + PIXIWidth - 450, 900, 200, 64, + TextGet("KDLoadBackup"), KDBusyLoadingFile ? KDBaseLightGrey : KDBaseWhite, undefined, undefined, undefined, undefined, undefined, undefined, undefined, { hoverData: { text: TextGet("KDLoadBackupDesc") @@ -7972,7 +8135,7 @@ function KDTogglesDraw() { for (let toggle of toggles.filter((tog) => {return KDToggleCategories[tog] == KDToggleTab || (!KDToggleCategories[tog] && KDToggleTab == "Main");})) { // Draw temp start screen let txt = KDOptionFilter ? TextGet("KDToggle" + toggle).toLocaleLowerCase() : ""; - + if (KDOptionFilter != "" && !(txt == KDOptionFilter.toLocaleLowerCase() || txt.includes(KDOptionFilter.toLocaleLowerCase())))continue; DrawCheckboxKDEx("toggle" + toggle, () => { KDToggles[toggle] = !KDToggles[toggle]; @@ -8100,7 +8263,7 @@ function KDTogglesDraw() { if (KinkyDungeonGameFlag) { KinkyDungeonState = "Game"; } else KinkyDungeonState = "Menu"; - + KDOptionFilter = ""; KDConsentFilter = ""; //ServerAccountUpdate.QueueData({ KinkyDungeonKeybindings: KinkyDungeonKeybindings }); @@ -8131,11 +8294,11 @@ function KDTextReplace(text: string, replacestrings: string[], FromSuff?: string + from.substring(1); to = to.substring(0, 1) + to.substring(1); - + str = str.replace( from, to); - + } return str; @@ -8144,7 +8307,7 @@ function KDTextReplace(text: string, replacestrings: string[], FromSuff?: string let KinkyDungeonPreviousState = ""; function KDDrawWardrobeButton() { - DrawButtonKDEx("GoToWardrobe", (_bdata) => { + DrawButtonKDEx("GoToWardrobe", async (_bdata) => { if (StandalonePatched) { KDSpeakerNPC = null; @@ -8163,13 +8326,12 @@ function KDDrawWardrobeButton() { KDUpdateModelList(); KDRefreshOutfitInfo(); let itt = localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit); - let orig = itt ? - JSON.parse(LZString.decompressFromBase64(itt)).appearance - || itt : ""; - let current = LZString.compressToBase64(AppearanceItemStringify(KinkyDungeonPlayer.Appearance)); + let orig = itt ? JSON.parse(await KinkyDungeonDecompressSave (itt, SaveType.Outfit)).appearance || itt + : ""; + let current = await KinkyDungeonCompressSave (AppearanceItemStringify(KinkyDungeonPlayer.Appearance), SaveType.Outfit); if (orig != current) KDOriginalValue = orig; } - let appearance = DecompressB64(localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit)); + let appearance = await KinkyDungeonDecompressSave (localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit), SaveType.Outfit); if (appearance) { CharacterAppearanceRestore(KinkyDungeonPlayer, appearance, false, true); let parsed = JSON.parse(appearance); @@ -8198,12 +8360,11 @@ function KDDrawWardrobeButton() { } return true; }, true, 30, 942, 440, 50, TextGet("KinkyDungeonDressPlayer"), KDBaseWhite, ""); - } function KDLoadConsentFromSave(saveData: KinkyDungeonSave, override) { if (override && saveData.saveStat) { - + let dontPopulate: Record = {}; for (let entry of Object.entries(KDConsentListBasic)) { @@ -8227,8 +8388,8 @@ function KDFirstRunMainmenu() { if (KDToggles.SkipIntro) { KinkyDungeonState = "Menu"; KDCheckedConsentAtStartup = false; - - + + } else { KDCheckedConsentAtStartup = false; KinkyDungeonState = "Intro"; @@ -8253,4 +8414,4 @@ function KDRunnewConsentCheck() { } } } -} \ No newline at end of file +} diff --git a/Game/src/base/KinkyDungeonErrors.ts b/Game/src/base/KinkyDungeonErrors.ts index bfc056569..05ae5c069 100644 --- a/Game/src/base/KinkyDungeonErrors.ts +++ b/Game/src/base/KinkyDungeonErrors.ts @@ -18,8 +18,8 @@ function KinkyDungeonTeardownCrashHandler(): void { * Error event handler for uncaught errors * @param event - The error event */ -function KinkyDungeonOnUncaughtError(event: ErrorEvent): void { - const report = KinkyDungeonGenerateErrorReport(event); +async function KinkyDungeonOnUncaughtError(event: ErrorEvent): Promise { + const report = await KinkyDungeonGenerateErrorReport(event); KinkyDungeonShowCrashReportModal(report); } @@ -28,13 +28,13 @@ function KinkyDungeonOnUncaughtError(event: ErrorEvent): void { * @param event - The error event * @returns The report */ -function KinkyDungeonGenerateErrorReport(event: ErrorEvent): string { +async function KinkyDungeonGenerateErrorReport(event: ErrorEvent): Promise { return [ KinkyDungeonCrashReportErrorDetails(event), KinkyDungeonCrashReportStateData(), KinkyDungeonCrashReportDiagnostics(), KinkyDungeonCrashReportDeviceDetails(), - KinkyDungeonCrashReportSaveData(), + await KinkyDungeonCrashReportSaveData(), ].join("\n\n"); } @@ -107,11 +107,11 @@ function KinkyDungeonCrashReportErrorDetails(event: ErrorEvent): string { * Generates a report string containing the current save state of the game * @returns The report */ -function KinkyDungeonCrashReportSaveData(): string { +async function KinkyDungeonCrashReportSaveData(): Promise { let saveData = localStorage.getItem("KinkyDungeonSave"); if (!saveData) { try { - saveData = LZString.compressToBase64(JSON.stringify(KinkyDungeonGenerateSaveData())); + saveData = await KinkyDungeonCompressSave (JSON.stringify (KinkyDungeonGenerateSaveData()), SaveType.Game); } catch (error) { saveData = "Could not locate or generate save data"; } diff --git a/Game/src/base/game/KinkyDungeonGame.ts b/Game/src/base/game/KinkyDungeonGame.ts index ca2041db2..9e465734f 100644 --- a/Game/src/base/game/KinkyDungeonGame.ts +++ b/Game/src/base/game/KinkyDungeonGame.ts @@ -462,7 +462,7 @@ function KDResetEventData(Data?: any) { -function KinkyDungeonInitialize(Level: number, Load?: any) { +async function KinkyDungeonInitialize(Level: number, Load?: any) { KDWorldMap = {}; KDMapData = KDDefaultMapData(0, 0); KDCurrentWorldSlot = {x: 0, y: 0}; @@ -497,9 +497,10 @@ function KinkyDungeonInitialize(Level: number, Load?: any) { KinkyDungeonDressSet(); // Refresh the character - CharacterAppearanceRestore(KinkyDungeonPlayer, CharacterAppearanceStringify(KinkyDungeonPlayer, - KDGetCharMetadata(KinkyDungeonPlayer) - ), false, true); + await CharacterAppearanceRestore (KinkyDungeonPlayer, + CharacterAppearanceStringify (KinkyDungeonPlayer, + KDGetCharMetadata(KinkyDungeonPlayer)), + false, true); KinkyDungeonDrawState = "Game"; KDResetAlternateInventoryRender(); KDRefreshCharacter.set(KinkyDungeonPlayer, true); @@ -2372,7 +2373,7 @@ function KinkyDungeonGameKeyDown() { //case KinkyDungeonKeyMenu[6]: KinkyDungeonDrawState = "Collection"; break; //case KinkyDungeonKeyMenu[7]: KinkyDungeonDrawState = "Facilities"; break; /*case KinkyDungeonKeyMenu[9]: { - KinkyDungeonDrawState = "JourneyMap"; + KinkyDungeonDrawState = "JourneyMap"; KDGameData.UseJourneyTarget = false; break;}*/ case KinkyDungeonKeySkip[0]: @@ -3738,8 +3739,8 @@ function KDAddAppearance ( C.Appearance.push(NA); return NA; } - - + + return null; } @@ -4155,7 +4156,7 @@ function KDSprintCost(sprintdata?: any, sprintCost?: number, accountForSlow: boo boost: 0, sprintCostOverride: sprintCost, }; - data.cost = (-KDSprintCostBase - KDSprintCostSlowLevel[Math.min(KDSprintCostSlowLevel.length-1, + data.cost = (-KDSprintCostBase - KDSprintCostSlowLevel[Math.min(KDSprintCostSlowLevel.length-1, Math.round(KinkyDungeonSlowLevel))] + ( (accountForSlow && KinkyDungeonSlowLevel > 1) ? -KDSprintAdjustSlowed : 0 )); @@ -4423,4 +4424,4 @@ let KDCustomKeyDown = [ }, ]; let KDCustomKeyUp = [ -]; \ No newline at end of file +]; diff --git a/Game/src/base/game/KinkyDungeonHUD.ts b/Game/src/base/game/KinkyDungeonHUD.ts index 6500b6d64..4d213dc0c 100644 --- a/Game/src/base/game/KinkyDungeonHUD.ts +++ b/Game/src/base/game/KinkyDungeonHUD.ts @@ -1941,11 +1941,12 @@ function KinkyDungeonHandleHUD() { return true; } if (MouseIn(1500, 320, 300, 64)) { - let saveData = LZString.compressToBase64(JSON.stringify(KinkyDungeonSaveGame(true))); - KinkyDungeonState = "Save"; - ElementCreateTextArea("saveDataField"); - ElementValue("saveDataField", saveData); - + (async () => { + let saveData = await KinkyDungeonCompressSave (JSON.stringify (KinkyDungeonSaveGame(true)), SaveType.Game); + KinkyDungeonState = "Save"; + ElementCreateTextArea("saveDataField"); + ElementValue("saveDataField", saveData); + })(); return true; } @@ -3121,7 +3122,7 @@ function KDDrawBuffIcons(minXX: number, minYY: number, statsDraw: Record minXX) || (KDMinBuffX && XX > KDMinBuffX)) && + if (((!KDMinBuffX && XX > minXX) || (KDMinBuffX && XX > KDMinBuffX)) && (KDStatsSkipLine[currCategory] || KDStatsSkipLineBefore[stat.category]) && currCategory != stat.category) { if (KDToggleShowAllBuffs) { diff --git a/Game/src/collection/KinkyDungeonCollection.ts b/Game/src/collection/KinkyDungeonCollection.ts index 65d791cf4..05055d28d 100644 --- a/Game/src/collection/KinkyDungeonCollection.ts +++ b/Game/src/collection/KinkyDungeonCollection.ts @@ -486,71 +486,72 @@ function KDDrawSelectedCollectionMember(value: KDCollectionEntry, x: number, y: DrawTextFitKD(TextGet("KDZoomNPC"), x + 220, y + 750, 500, KDBaseWhite, KDTextGray0); } if (KDGameData.Collection[value.id + ""] && DrawButtonKDEx("dressNPC", () => { - if (KDSoundEnabled()) - AudioPlayInstantSoundKD(KinkyDungeonRootDirectory + "Audio/" + "LightJingle" + ".ogg"); - //KDSpeakerNPC = null; - KinkyDungeonState = "Wardrobe"; - KDCanRevertFlag = value.customOutfit != undefined; - ForceRefreshModels(KDSpeakerNPC); - KDOriginalValue = ""; - CharacterReleaseTotal(KDSpeakerNPC); - KDWardrobeCallback = () => { - UpdateModels(KDSpeakerNPC); - KDShowCharacterPalette = false; KDWardrobePreviewRestraints = ""; - KDRefreshCharacter.set(KDSpeakerNPC, true); - KDDressWardrobeChar(KDSpeakerNPC, false); - - let value2 = value; - //if (KDOriginalValue) { - value2.customOutfit = LZString.compressToBase64(AppearanceItemStringify(KDSpeakerNPC.Appearance)); - value2.Palette = KDSpeakerNPC.Palette; - value2.metadata = KDSpeakerNPC.metadata; - - KDRefreshCharacter.set(KDSpeakerNPC, true); - KinkyDungeonCheckClothesLoss = true; - KinkyDungeonDressPlayer(KDSpeakerNPC, false, false, KDGameData.NPCRestraints ? KDGameData.NPCRestraints[value.id + ''] : undefined); - - //} - }; - if (value.customOutfit) { - let outfit = value.customOutfit; - KDWardrobeRevertCallback = () => { + (async () => { + if (KDSoundEnabled()) + AudioPlayInstantSoundKD(KinkyDungeonRootDirectory + "Audio/" + "LightJingle" + ".ogg"); + //KDSpeakerNPC = null; + KinkyDungeonState = "Wardrobe"; + KDCanRevertFlag = value.customOutfit != undefined; + ForceRefreshModels(KDSpeakerNPC); + KDOriginalValue = ""; + CharacterReleaseTotal(KDSpeakerNPC); + KDWardrobeCallback = async () => { + UpdateModels(KDSpeakerNPC); KDShowCharacterPalette = false; KDWardrobePreviewRestraints = ""; - if (outfit) - CharacterAppearanceRestore(KDSpeakerNPC, DecompressB64(outfit),false, true); - CharacterRefresh(KDSpeakerNPC); - KDInitProtectedGroups(KDSpeakerNPC); KDRefreshCharacter.set(KDSpeakerNPC, true); - KinkyDungeonDressPlayer(KDSpeakerNPC, true); - }; - KDWardrobeResetCallback = () => { - KDShowCharacterPalette = false; KDWardrobePreviewRestraints = ""; - delete value.customOutfit; - }; - } else { - KDWardrobeRevertCallback = () => { - KDShowCharacterPalette = false; KDWardrobePreviewRestraints = ""; - delete value.customOutfit; + KDDressWardrobeChar(KDSpeakerNPC, false); + + /* FIXME: After setting up `value2`, nothing is done with it? */ + let value2 = value; + //if (KDOriginalValue) { + value2.customOutfit = await KinkyDungeonCompressSave (AppearanceItemStringify(KDSpeakerNPC.Appearance), SaveType.Outfit); + value2.Palette = KDSpeakerNPC.Palette; + value2.metadata = KDSpeakerNPC.metadata; + KDRefreshCharacter.set(KDSpeakerNPC, true); - KinkyDungeonDressPlayer(KDSpeakerNPC, true); + KinkyDungeonCheckClothesLoss = true; + KinkyDungeonDressPlayer(KDSpeakerNPC, false, false, KDGameData.NPCRestraints ? KDGameData.NPCRestraints[value.id + ''] : undefined); + //} }; - KDWardrobeResetCallback = null; - } + if (value.customOutfit) { + let outfit = value.customOutfit; + KDWardrobeRevertCallback = async () => { + KDShowCharacterPalette = false; KDWardrobePreviewRestraints = ""; + if (outfit) + await CharacterAppearanceRestore(KDSpeakerNPC, await KinkyDungeonDecompressSave (outfit, SaveType.Outfit),false, true); + CharacterRefresh(KDSpeakerNPC); + KDInitProtectedGroups(KDSpeakerNPC); + KDRefreshCharacter.set(KDSpeakerNPC, true); + KinkyDungeonDressPlayer(KDSpeakerNPC, true); + }; + KDWardrobeResetCallback = () => { + KDShowCharacterPalette = false; KDWardrobePreviewRestraints = ""; + delete value.customOutfit; + }; + } else { + KDWardrobeRevertCallback = () => { + KDShowCharacterPalette = false; KDWardrobePreviewRestraints = ""; + delete value.customOutfit; + KDRefreshCharacter.set(KDSpeakerNPC, true); + KinkyDungeonDressPlayer(KDSpeakerNPC, true); + }; + KDWardrobeResetCallback = null; + } - KDPlayerSetPose = false; - KDInitCurrentPose(true,KDSpeakerNPC); - KinkyDungeonInitializeDresses(); - KDUpdateModelList(); - KDRefreshOutfitInfo(); - let itt = localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit); - let orig = itt ? - JSON.parse(LZString.decompressFromBase64(itt)).appearance - || itt : ""; - let current = LZString.compressToBase64(AppearanceItemStringify(KinkyDungeonPlayer.Appearance)); - if (orig != current) KDOriginalValue = orig; - ForceRefreshModelsAsync(KDSpeakerNPC); - KDShowCharacterPalette = false; KDWardrobePreviewRestraints = ""; - KDDressWardrobeChar(KDSpeakerNPC, true); + KDPlayerSetPose = false; + KDInitCurrentPose(true,KDSpeakerNPC); + KinkyDungeonInitializeDresses(); + KDUpdateModelList(); + KDRefreshOutfitInfo(); + let itt = localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit); + let orig = itt ? JSON.parse (await KinkyDungeonDecompressSave (itt, SaveType.Outfit)).appearance || itt + : ""; + let current = await KinkyDungeonCompressSave (AppearanceItemStringify (KinkyDungeonPlayer.Appearance), SaveType.Outfit); + if (orig != current) KDOriginalValue = orig; + ForceRefreshModelsAsync(KDSpeakerNPC); + KDShowCharacterPalette = false; KDWardrobePreviewRestraints = ""; + KDDressWardrobeChar(KDSpeakerNPC, true); + })(); return true; }, true, x - 90, y + 90, 80, 80, "", KDBaseWhite, KinkyDungeonRootDirectory + "UI/Dress.png", undefined, undefined, @@ -697,7 +698,7 @@ function KDDrawSelectedCollectionMember(value: KDCollectionEntry, x: number, y: NPCTags.set(KDSpeakerNPC, KinkyDungeonUpdateRestraints(KDSpeakerNPC, value.id, 0)); KDEntityRestraintMetadata.set(value.id, KDUpdateRestraintMetadata(value.id, 0)); } - KinkyDungeonDressPlayer(KDSpeakerNPC, false, false, + KinkyDungeonDressPlayer(KDSpeakerNPC, false, false, KDGameData.NPCRestraints ? KDGameData.NPCRestraints[value.id + ''] : undefined); DrawCharacter(KDSpeakerNPC, x + 20 + (KDToggleBigView ? 0 : 100), @@ -780,7 +781,7 @@ function KDDrawSelectedCollectionMember(value: KDCollectionEntry, x: number, y: } else { - + KDDraw(kdcanvas, kdpixisprites, value.name + "_coll," + value.id, KinkyDungeonRootDirectory + dir + sp + ".png", x + 20, y + 80, @@ -1157,8 +1158,8 @@ function KDDrawCollectionInventory(x: number, y: number, drawCallback?: (value: DrawCharacter(char, XX + size/2.7, YY + size*0.2, - size/1300, false, kdcanvas, undefined, - CHIBIMOD, 101, false, undefined, + size/1300, false, kdcanvas, undefined, + CHIBIMOD, 101, false, undefined, value.name + "_coll," + value.id, CHIBIMODEND); } @@ -1275,8 +1276,8 @@ function KDDrawCollectionInventory(x: number, y: number, drawCallback?: (value: DrawCharacter(char, XX + size/2.7, YY + size*0.2, - size/1300, false, kdcanvas, undefined, - CHIBIMOD, 101, false, undefined, + size/1300, false, kdcanvas, undefined, + CHIBIMOD, 101, false, undefined, value.name + "_coll," + value.id, CHIBIMODEND); } @@ -1938,5 +1939,5 @@ function KDGenCharForCollection(value: KDCollectionEntry, enemyType: enemy) { KDSpeakerNPC, true); } KDRefreshCharacter.set(KDSpeakerNPC, true); - } -} \ No newline at end of file + } +} diff --git a/Game/src/map/KDStairActions.ts b/Game/src/map/KDStairActions.ts index 4175e8e31..6cccd6293 100644 --- a/Game/src/map/KDStairActions.ts +++ b/Game/src/map/KDStairActions.ts @@ -133,7 +133,7 @@ function KDGoThruTile(x: number, y: number, suppressCheckPoint: boolean, force: if (KDGameData.PriorJailbreaks > 0) KDGameData.PriorJailbreaksDecay = (KDGameData.PriorJailbreaksDecay + 1) || 1; if (MiniGameKinkyDungeonLevel > 1) { - + KDAdvanceOneFloor(); } @@ -167,7 +167,7 @@ function KDGoThruTile(x: number, y: number, suppressCheckPoint: boolean, force: let movedUp = MiniGameKinkyDungeonLevel > KDGameData.HighestLevelCurrent; KDGameData.HighestLevelCurrent = Math.max(KDGameData.HighestLevelCurrent || 1, MiniGameKinkyDungeonLevel); KDGameData.HighestLevel = Math.max(KDGameData.HighestLevel || 1, MiniGameKinkyDungeonLevel); - + //if (KinkyDungeonTilesGet(KinkyDungeonPlayerEntity.x + "," + KinkyDungeonPlayerEntity.y)) { let MapMod = data.mapMod; @@ -264,15 +264,18 @@ function KDGoThruTile(x: number, y: number, suppressCheckPoint: boolean, force: function KDPostStairSave() { if (KDGameData.RoomType == "PerkRoom" && MiniGameKinkyDungeonLevel >= 1) { // && Math.floor(MiniGameKinkyDungeonLevel / 3) == MiniGameKinkyDungeonLevel / 3 - if ((!KinkyDungeonStatsChoice.get("saveMode"))) { - let saveData = LZString.compressToBase64(JSON.stringify(KinkyDungeonSaveGame(true))); + if ((!KinkyDungeonStatsChoice.get("saveMode"))) { + (async() => { + let saveData = await KinkyDungeonCompressSave (JSON.stringify (KinkyDungeonSaveGame (true)), SaveType.Game); KinkyDungeonState = "Save"; KDTextArea("saveDataField", 750, 100, 1000, 230); ElementValue("saveDataField", saveData); - } else KinkyDungeonSaveGame(); - } - else KinkyDungeonSaveGame(); - } + })(); + } else KinkyDungeonSaveGame(); + } + else KinkyDungeonSaveGame(); +} + function KinkyDungeonHandleStairs(toTile: string, suppressCheckPoint?: boolean) { if (KinkyDungeonFlags.get("stairslocked")) { KinkyDungeonSendActionMessage(10, TextGet("KDStairsLocked").replace("NMB", "" + KinkyDungeonFlags.get("stairslocked")), KDBaseWhite, 1); diff --git a/Game/src/map/KinkyDungeonAlt.ts b/Game/src/map/KinkyDungeonAlt.ts index 9c450cae4..2b865f5ca 100644 --- a/Game/src/map/KinkyDungeonAlt.ts +++ b/Game/src/map/KinkyDungeonAlt.ts @@ -3284,4 +3284,4 @@ function KDEnumerateShopVisitors() { options.AdaLovelock = 100; } return options; -} \ No newline at end of file +} diff --git a/Game/src/map/KinkyDungeonTiles.ts b/Game/src/map/KinkyDungeonTiles.ts index f98283a83..a38eb99d4 100644 --- a/Game/src/map/KinkyDungeonTiles.ts +++ b/Game/src/map/KinkyDungeonTiles.ts @@ -1028,7 +1028,7 @@ function KDAdvanceOneFloor() { KinkyDungeonChangeRep("Conjure", -1); KinkyDungeonChangeRep("Illusion", -1); } - + if (KDGameData.PrisonerState == "jail") { KDGameData.PrisonerState = ""; // Increase security if escaping jail @@ -1050,4 +1050,4 @@ function KDRoomUnwanderable(altType: string) { return true; } return !altRoom; // bc is main -} \ No newline at end of file +} diff --git a/Game/src/player/KinkyDungeonDress.ts b/Game/src/player/KinkyDungeonDress.ts index 3c12726b8..39267f14f 100644 --- a/Game/src/player/KinkyDungeonDress.ts +++ b/Game/src/player/KinkyDungeonDress.ts @@ -144,7 +144,7 @@ function KinkyDungeonDressPlayer ( customPlayerTags?: Map, customFaction?: string, noDressOutfit?: boolean, - forceUseOutfit?: boolean + forceUseOutfit?: boolean ) { if (!Character) Character = KinkyDungeonPlayer; @@ -162,32 +162,34 @@ function KinkyDungeonDressPlayer ( let restraintModels = {}; + let dressJob: Promise; let CurrentDress = Character == KinkyDungeonPlayer ? KinkyDungeonCurrentDress : (Character == KDPreviewModel ? KinkyDungeonCurrentDress : (KDCharacterDress.get(Character) || "Bandit")); let DressList = noDressOutfit ? [] : KDGetDressList()[CurrentDress]; if (!noDressOutfit && !forceUseOutfit && KDNPCStyle.get(Character)?.customOutfit) { - DressList = []; - for (let a of JSON.parse(DecompressB64(KDNPCStyle.get(Character)?.customOutfit))) { - if (a.Model && !KDModelIsProtected(a.Model) && !a.Model.Restraint && !a.Model.Cosplay) { - DressList.push({ - Item: a.Model.Name || a.Model, - Group: a.Model.Group || a.Model.Name || a.Model, - Color: KDBaseWhite, - Lost: false, - Filters: a.Model.Filters || a.Filters, - Properties: a.Model.Properties || a.Properties, - factionFilters: a.Model.factionFilters || a.factionFilters, - - },); + dressJob = (async() => { + DressList = []; + for (let a of JSON.parse(await KinkyDungeonDecompressSave (KDNPCStyle.get (Character)?.customOutfit, SaveType.Outfit))) { + if (a.Model && !KDModelIsProtected(a.Model) && !a.Model.Restraint && !a.Model.Cosplay) { + DressList.push({ + Item: a.Model.Name || a.Model, + Group: a.Model.Group || a.Model.Name || a.Model, + Color: KDBaseWhite, + Lost: false, + Filters: a.Model.Filters || a.Filters, + Properties: a.Model.Properties || a.Properties, + factionFilters: a.Model.factionFilters || a.factionFilters, + },); + } } - } + })(); } let forceCustomFaction = !!customFaction; if ((KinkyDungeonState != "Wardrobe" || KDShowCharacterPalette)) { - + if (Character == KinkyDungeonPlayer) { let outfit = KDOutfit({name: KinkyDungeonCurrentDress}); let restraintPalette = KDToggles.RestraintPalette ? KDGetRestraintsPalette(Character) : ""; @@ -201,18 +203,17 @@ function KinkyDungeonDressPlayer ( } } else { if (customFaction == undefined) { - let palette = + let palette = (Character.metadata?.palette || Character.Palette); if (palette) { customFaction = palette; } } } - + } try { - if (!KDGameData.NPCRestraints) KDGameData.NPCRestraints = {}; let data = { hideShrines: {}, @@ -222,30 +223,34 @@ function KinkyDungeonDressPlayer ( updateExpression: false, Character: Character, extraForceDress: undefined, - Wornitems: (npcRestraints || (KDGetCharacterID(Character) && KDGameData.NPCRestraints[KDGetCharacterID(Character)])) ? - Object.values(npcRestraints || KDGameData.NPCRestraints[KDGetCharacterID(Character)]) - .filter((rest) => {return rest.events}) - .map((rest) => {return rest.id;}) - : [], - NPCRestraintEvents: KDGetCharacterID(Character) ? - KDGameData.NPCRestraints[KDGetCharacterID(Character)] - : undefined, + Wornitems: (npcRestraints || (KDGetCharacterID(Character) && KDGameData.NPCRestraints[KDGetCharacterID(Character)])) + ? Object.values(npcRestraints || KDGameData.NPCRestraints[KDGetCharacterID(Character)]) + .filter((rest) => {return rest.events}) + .map((rest) => {return rest.id;}) + : [], + NPCRestraintEvents: KDGetCharacterID(Character) + ? KDGameData.NPCRestraints[KDGetCharacterID(Character)] + : undefined, }; - if (DressList) { - for (let clothes of DressList) { - if (clothes.Properties && !clothes.Lost) { - for (let p of Object.values(clothes.Properties)) { - if (p.HideRestraintsTags) { - for (let t of p.HideRestraintsTags) { - data.hideShrines[t] = true; + // Ensure the DressList has been populated before dancing on it. + if (dressJob) { + dressJob.then (() => { + if (DressList) { + for (let clothes of DressList) { + if (clothes.Properties && !clothes.Lost) { + for (let p of Object.values(clothes.Properties)) { + if (p.HideRestraintsTags) { + for (let t of p.HideRestraintsTags) { + data.hideShrines[t] = true; + } + } } } } } - } - + }); } @@ -545,7 +550,7 @@ function KinkyDungeonDressPlayer ( } - let item = KDInventoryWear(Character, clothes.Item, clothes.Group, undefined, + let item = KDInventoryWear(Character, clothes.Item, clothes.Group, undefined, clothes.Color, filters, clothes.Properties, clothes.factionFilters); alreadyClothed[clothes.Group || clothes.Item] = true; if (item) { @@ -614,10 +619,10 @@ function KinkyDungeonDressPlayer ( } } } - - KDInventoryWear(Character, clothes.Item, clothes.Group, undefined, - clothes.Color, filters, + + KDInventoryWear(Character, clothes.Item, clothes.Group, undefined, + clothes.Color, filters, clothes.Properties, clothes.factionFilters); alreadyClothed[clothes.Group || clothes.Item] = true; } @@ -845,7 +850,7 @@ function KinkyDungeonDressPlayer ( } if (!KDCurrentModels.get(Character)?.Poses?.Hair && KDModelHair[hairstyle]) { for (let hair of Object.values(KDModelHair[hairstyle])) { - KDInventoryWear(Character, hair.Item, undefined, undefined, undefined, hair.Filters, + KDInventoryWear(Character, hair.Item, undefined, undefined, undefined, hair.Filters, hair.Properties, hair.factionFilters); ReUpdate = true; } @@ -867,7 +872,7 @@ function KinkyDungeonDressPlayer ( } if (!KDCurrentModels.get(Character)?.Poses?.Cosplay && KDModelCosplay[cosplaystyle]) { for (let cosplay of Object.values(KDModelCosplay[cosplaystyle])) { - KDInventoryWear(Character, cosplay.Item, undefined, undefined, undefined, + KDInventoryWear(Character, cosplay.Item, undefined, undefined, undefined, cosplay.Filters, cosplay.Properties, cosplay.factionFilters); ReUpdate = true; @@ -960,8 +965,8 @@ function KinkyDungeonWearForcedClothes(C: Character, restraints?: item[], extraF } } } - KDInventoryWear(C, dress.Model, undefined, undefined, undefined, - dress.inheritFilters ? KDRestraint(inv).Filters : (filters), + KDInventoryWear(C, dress.Model, undefined, undefined, undefined, + dress.inheritFilters ? KDRestraint(inv).Filters : (filters), Properties); }); } @@ -1000,7 +1005,7 @@ function KDInventoryWear(Character: Character, AssetName: string, AssetGroup: st ): Item { const M = StandalonePatched ? ModelDefs[AssetName] : undefined; if (!M) return; - let item = KDAddModel(Character, AssetGroup, M, color || "Default", filters, undefined, + let item = KDAddModel(Character, AssetGroup, M, color || "Default", filters, undefined, Properties, factionFilters); //CharacterAppearanceSetItem(KinkyDungeonPlayer, AssetGroup, A, color || A.DefaultColor,0,-1, false); CharacterRefresh(Character, true); @@ -1056,7 +1061,7 @@ function KDApplyItem(C: Character, inv: item, tags: any, customFaction: string = let restraint = KDRestraint(inv); let AssetGroup = restraint.AssetGroup ? restraint.AssetGroup : restraint.Group; let faction = (inv.forceFaction != undefined) ? inv.forceFaction : (((forceCustomFaction || !inv.faction) && customFaction) ? customFaction : (inv.faction ? inv.faction : "")); - + // faction color system let filters = (restraint.Filters || (ModelDefs[restraint.Model || restraint.Asset])?.Filters) ? JSON.parse(JSON.stringify(restraint.Filters || (ModelDefs[restraint.Model || restraint.Asset])?.Filters)) @@ -1217,7 +1222,7 @@ function KDGetExtraPoses(C: Character): string[] { poses.push("Pulled"); } } - + } } return poses; @@ -1281,23 +1286,22 @@ function KDUpdateTempPoses(Character: Character) { } /** - * + * * @param C character to get palettes for (optional) * @param safe safe = deep copy, otherwise expecting reference only (no modify) */ function KDGetPalettes(C: Character, safe?: boolean, includeDefault: boolean = true, defaultOverride?: Record>): Record> { if (!defaultOverride) defaultOverride = KDDefaultWardrobePalettes; - + if (C?.metadata?.customColors) { let newPalettes: Record> = {}; for (let palette in KinkyDungeonFactionFilters) { newPalettes[palette] = KinkyDungeonFactionFilters[palette]; - } for (let palette in C.metadata.customColors) { newPalettes[palette] = C.metadata.customColors[palette]; } - + if (includeDefault) { for (let palette in defaultOverride) { if (newPalettes[palette]) continue; @@ -1316,7 +1320,7 @@ function KDGetPalettes(C: Character, safe?: boolean, includeDefault: boolean = t if (includeDefault) { for (let palette in defaultOverride) { if (newPalettes[palette]) continue; - newPalettes[palette] = defaultOverride[palette]; + newPalettes[palette] = defaultOverride[palette]; } } return safe ? structuredClone(newPalettes) : newPalettes; @@ -1425,5 +1429,5 @@ function KDGetPlayerPalette(C: Character) { } let DefaultStyles = [ - "BlueHair","GreenHair", "WhiteHair", "Ice", "Water", "Earth", "Air", "Fire", "RedHair", -]; \ No newline at end of file + "BlueHair","GreenHair", "WhiteHair", "Ice", "Water", "Earth", "Air", "Fire", "RedHair", +]; diff --git a/Game/src/player/wardrobe/KDWardrobe.ts b/Game/src/player/wardrobe/KDWardrobe.ts index cfe5c9a98..c2d1d9898 100644 --- a/Game/src/player/wardrobe/KDWardrobe.ts +++ b/Game/src/player/wardrobe/KDWardrobe.ts @@ -690,7 +690,7 @@ function KDDrawColorSliders(X: number, Y: number, C: Character, Model: Model): v bb}`); ElementValue("KDCopyFilter", JSON.stringify(Model.Filters[KDCurrentLayer])); ForceRefreshModels(C); - }, + }, (key) => { KDChangeWardrobe(C); if (!Model.Filters) Model.Filters = {}; @@ -731,7 +731,7 @@ function KDDrawColorSliders(X: number, Y: number, C: Character, Model: Model): v Model.Filters[KDCurrentLayer].green = g; Model.Filters[KDCurrentLayer].blue = b; Object.assign(KDCurrentModels.get(C).Models.get(Model.Name), JSON.parse(JSON.stringify(Model))); - + UpdateModels(C); }, (key, override, desaturate) => { KDChangeWardrobe(C); @@ -750,7 +750,7 @@ function KDDrawColorSliders(X: number, Y: number, C: Character, Model: Model): v } Model.factionFilters[KDCurrentLayer] = {color: key, override: override, desaturate: desaturate}; } - + Object.assign(KDCurrentModels.get(C).Models.get(Model.Name), JSON.parse(JSON.stringify(Model))); lastGlobalRefresh = CommonTime() - GlobalRefreshInterval + 10; ForceRefreshModels(C); @@ -770,16 +770,16 @@ function KDDrawColorSliders(X: number, Y: number, C: Character, Model: Model): v KDToggles.PaletteColorPicker = false; KDPropsSlider = false; return true; - }, true, X - 240, YY + 40, 140, 30, TextGet("KDColorPickerSimple"), + }, true, X - 240, YY + 40, 140, 30, TextGet("KDColorPickerSimple"), KDBaseWhite, undefined, undefined, undefined, KDToggles.PaletteColorPicker || KDPropsSlider || !KDToggles.SimpleColorPicker, KDButtonColor); DrawButtonKDEx("tab_ColorPickerPalette", (_b) => { KDToggles.PaletteColorPicker = true; KDPropsSlider = false; return true; - }, true, X - 240 + 290, YY + 40, 140, 30, + }, true, X - 240 + 290, YY + 40, 140, 30, TextGet("KDColorPickerPalette"), KDBaseWhite, undefined, undefined, undefined, - KDPropsSlider || !KDToggles.PaletteColorPicker, + KDPropsSlider || !KDToggles.PaletteColorPicker, (Model.factionFilters && Model.factionFilters[KDCurrentLayer]) ? KDTextGray3 : KDButtonColor); DrawButtonKDEx("tab_ColorPickerAdvanced", (_b) => { KDToggles.SimpleColorPicker = false; @@ -1271,7 +1271,7 @@ function KDDrawModelList(X: number, C: Character) { , true, X+220, 100 + buttonSpacing * i, 190, buttonHeight, !toplevel ? "" : TextGet("m_" + toplevel), (KDCurrentModels.get(C).Models.has(toplevel) || hasTopLevel[toplevel]) ? KDBaseWhite : faded, "", - undefined, undefined, + undefined, undefined, KDCategoryFilterSpecialTopNoBorder[mainCat] ? KDCategoryFilterSpecialTopNoBorder[mainCat](C, toplevel, index_top, toplevel) : index_top != KDModelList_Toplevel_index, KDButtonColor); @@ -1285,7 +1285,7 @@ function KDDrawModelList(X: number, C: Character) { : clickSublevel(sublevel, index_sub, sublevel), true, X+440, 100 + buttonSpacing * i, 190, buttonHeight, !sublevel ? "" : TextGet("m_" + sublevel), KDCurrentModels.get(C).Models.has(sublevel) ? KDBaseWhite : faded, "", - undefined, undefined, + undefined, undefined, KDCategoryFilterSpecialSubNoBorder[mainCat] ? KDCategoryFilterSpecialSubNoBorder[mainCat](C, sublevel, index_sub, sublevel) : index_sub != KDModelList_Sublevel_index, KDButtonColor); @@ -1427,7 +1427,7 @@ let KDDefaultWardrobePalettes: Record> = { let KDWardrobePreviewRestraints = ""; function KDDrawSubmeshEditor() { - let CF = KDTextField("KDSubmesh", 10, 60, 480, 30, + let CF = KDTextField("KDSubmesh", 10, 60, 480, 30, undefined, undefined, "300"); if (CF.Created) { SubmeshEditorBuffer = null; @@ -1455,7 +1455,7 @@ function KDDrawWardrobe(_screen: string, Character: Character) { let C = Character || KinkyDungeonPlayer; if (KDDebugMode) { - DrawButtonKDEx("togglewireframeeditor", + DrawButtonKDEx("togglewireframeeditor", () => { KDSubmeshEditor = !KDSubmeshEditor; SubmeshEditorBuffer = null; @@ -1489,7 +1489,7 @@ function KDDrawWardrobe(_screen: string, Character: Character) { KDDrawSubmeshEditor(); } if (SubmeshEditorBufferOrig && SubmeshEditorBuffer) - DrawButtonKDEx("exportwireframediff", + DrawButtonKDEx("exportwireframediff", () => { let data = [...SubmeshEditorBuffer].map((a, index) => { return a - KDTemplateEmptyMesh[index]; @@ -1520,16 +1520,15 @@ function KDDrawWardrobe(_screen: string, Character: Character) { ElementValue("KDOutfitName", KDOutfitInfo[KDCurrentOutfit]); } if (KDShowCharacterPalette) { - KDCurrentCharacterPalettes = KDGetPalettes(C, true, true, + KDCurrentCharacterPalettes = KDGetPalettes(C, true, true, C == KinkyDungeonPlayer ? KDDefaultWardrobePalettes : ( - (KinkyDungeonPlayer.metadata?.customColors) ? Object.assign(Object.assign({}, KDDefaultWardrobePalettes), + (KinkyDungeonPlayer.metadata?.customColors) ? Object.assign(Object.assign({}, KDDefaultWardrobePalettes), KinkyDungeonPlayer.metadata.customColors) : KDDefaultWardrobePalettes ) ); - - - + + let YY = 55 let size = 48; let spacing = 50; @@ -1560,7 +1559,7 @@ function KDDrawWardrobe(_screen: string, Character: Character) { KDDressWardrobeChar(C); return true; }, true, 1150, YY, size, size, - TextGet("KDVisualOpt_PreviewRestraints_" + preview), KDWardrobePreviewRestraints == preview, + TextGet("KDVisualOpt_PreviewRestraints_" + preview), KDWardrobePreviewRestraints == preview, false, KDBaseWhite, undefined, { maxWidth: 350, fontSize: Math.max(24, size/2), @@ -1592,9 +1591,9 @@ function KDDrawWardrobe(_screen: string, Character: Character) { pp[p._1] = p._2; } } - + KDDrawCustomPalettes(pp, pid, - 750, 200, KDPaletteWidth, 72, + 750, 200, KDPaletteWidth, 72, selectedPalette, (pal) => { C.Palette = pal; if (!C.metadata) { @@ -1627,19 +1626,19 @@ function KDDrawWardrobe(_screen: string, Character: Character) { if (palettetemp) { palette[palettelayer] = palettetemp[palettelayer]; } - + } } - + if (palette && selectedPalette) { - + let top = 100; let X = 1625; - let res = KDDrawColorPicker("Default", palettelayer, palette[palettelayer], palette, + let res = KDDrawColorPicker("Default", palettelayer, palette[palettelayer], palette, top, X, 300); - + DrawButtonKDEx("tab_ColorPickerSimple", (_b) => { KDToggles.SimpleColorPicker = true; KDPropsSlider = false; @@ -1652,38 +1651,38 @@ function KDDrawWardrobe(_screen: string, Character: Character) { return true; }, true, X - 200 + 250, res.YY + 20, 240, 30, TextGet("KDColorPickerAdvanced"), KDBaseWhite, undefined, undefined, undefined, KDPropsSlider || KDToggles.SimpleColorPicker, KDButtonColor); - + DrawButtonKDEx("KDResetAllLayers", (_bdata) => { if (C.metadata?.customColors[selectedPalette]) { delete C.metadata.customColors[selectedPalette]; delete KDCurrentCharacterPalettes[selectedPalette]; KDRefreshCharacter.set(C, true); KDDressWardrobeChar(C); - + } return true; - }, true, X + 300/2 + 10, top - 40, 300/2 - 10, 30, + }, true, X + 300/2 + 10, top - 40, 300/2 - 10, 30, TextGet("KDResetAllLayers"), KDTextWhite); DrawBoxKD(1625 - 225, top - 50, 350 + 200, res.YY - top + 125, KDButtonColor, false, 0.5, -10); - + let spacing = 64; let yyColor = top - 50 + (res.YY - top + 100) / 2 - spacing/2*GenericPaletteLayers.length; let xxColor = 1625 - 215; - + let ii = 0; for (let col of GenericPaletteLayers) { let sprite = GenericPaletteLayerSprites[col] || GenericPaletteLayerSprites.DarkNeutral; - DrawButtonKDEx("paletteLayer" + col, + DrawButtonKDEx("paletteLayer" + col, () => { KDSelectedPaletteLayer = col; return true; - }, - true, xxColor, yyColor, 200, 60, TextGet("KDPaletteLayer_" + col), - KDTextWhite, sprite, undefined, undefined, col != KDSelectedPaletteLayer, + }, + true, xxColor, yyColor, 200, 60, TextGet("KDPaletteLayer_" + col), + KDTextWhite, sprite, undefined, undefined, col != KDSelectedPaletteLayer, KDButtonColor, undefined, true, { // @ts-ignore filters: KDPIXIPaletteFilters.get(pid + selectedPalette) ? KDPIXIPaletteFilters.get(pid + selectedPalette)[ii++] : undefined, @@ -1691,20 +1690,19 @@ function KDDrawWardrobe(_screen: string, Character: Character) { yyColor += spacing; } - + if (CommonTime() > lastFastPaletteUpdate + 200) { lastFastPaletteUpdate = CommonTime(); if (KDPIXIPaletteFilters.has(pid + selectedPalette)) KDPIXIPaletteFilters.delete(pid + selectedPalette) - if (C.metadata?.customColors) for (let palette in C.metadata.customColors) { // finally update KDCurrentCharacterPalettes[palette] = C.metadata.customColors[palette]; } } - + if (res.updated) { if (KDPIXIPaletteFilters.has(pid + selectedPalette)) KDPIXIPaletteFilters.delete(pid + selectedPalette) @@ -1713,7 +1711,7 @@ function KDDrawWardrobe(_screen: string, Character: Character) { } if (palette && Object.values(palette).length > 0) { if (!C.metadata.customColors) C.metadata.customColors = {}; - if (!C.metadata.customColors[selectedPalette]) + if (!C.metadata.customColors[selectedPalette]) C.metadata.customColors[selectedPalette] = KDCurrentCharacterPalettes[selectedPalette] || {}; C.metadata.customColors[selectedPalette][palettelayer] = palette[palettelayer]; } else { @@ -1734,8 +1732,8 @@ function KDDrawWardrobe(_screen: string, Character: Character) { delete KDCurrentCharacterPalettes[selectedPalette]; } } - - + + } else { KDSelectedPaletteLayer = "Highlight"; KDCurrentCharacterPalettes = null; @@ -1795,10 +1793,12 @@ function KDDrawWardrobe(_screen: string, Character: Character) { DrawButtonKDEx("BackupOutfit", (_bdata) => { - downloadFile((ElementValue("savename") || KDOutfitInfo[KDCurrentOutfit] || "Outfit") + KDOUTFITBACKUP, - LZString.compressToBase64(CharacterAppearanceStringify(C || KinkyDungeonPlayer, - KDGetCharMetadata(C || KinkyDungeonPlayer) - ))); + (async () => { + downloadFile ((ElementValue("savename") || KDOutfitInfo[KDCurrentOutfit] || "Outfit") + KDOUTFITBACKUP, + await KinkyDungeonCompressSave (CharacterAppearanceStringify (C || KinkyDungeonPlayer, + KDGetCharMetadata (C || KinkyDungeonPlayer)), + SaveType.Outfit)); + })(); return true; }, true, 715, 930, 115, 50, TextGet("KDBackupOutfits"), KDBaseWhite, KinkyDungeonRootDirectory + "UI/Safe.png", "", false, false, @@ -1826,53 +1826,56 @@ function KDDrawWardrobe(_screen: string, Character: Character) { // Return anon function anonymously let clickButton = (index: number) => { return (_bdata: any) => { - KDSelectedModel = null; - if (C == KinkyDungeonPlayer) { - KDOutfitStore[KDCurrentOutfit] = LZString.compressToBase64(CharacterAppearanceStringify(C || KinkyDungeonPlayer, - KDGetCharMetadata(C || KinkyDungeonPlayer) - )); - KDOutfitOriginalStore[KDCurrentOutfit] = KDOriginalValue; - ElementValue("KDOutfitName", ""); - } - KDCurrentOutfit = index; - if (C == KinkyDungeonPlayer) - localStorage.setItem("kdcurrentoutfit", KDCurrentOutfit + ""); + (async () => { + KDSelectedModel = null; + if (C == KinkyDungeonPlayer) { + KDOutfitStore[KDCurrentOutfit] = + await KinkyDungeonCompressSave (CharacterAppearanceStringify (C || KinkyDungeonPlayer, + KDGetCharMetadata(C || KinkyDungeonPlayer)), + SaveType.Outfit); + KDOutfitOriginalStore[KDCurrentOutfit] = KDOriginalValue; + ElementValue("KDOutfitName", ""); + } + KDCurrentOutfit = index; + if (C == KinkyDungeonPlayer) + localStorage.setItem("kdcurrentoutfit", KDCurrentOutfit + ""); - let NewOutfit = KDOutfitStore[KDCurrentOutfit] || localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit); + let NewOutfit = KDOutfitStore[KDCurrentOutfit] || localStorage.getItem("kinkydungeonappearance" + KDCurrentOutfit); - if (NewOutfit) { - KDOriginalValue = KDOutfitOriginalStore[KDCurrentOutfit] || ""; - KinkyDungeonSetDress("None", "None", C, true); - KDRefreshCharacter.set(C, true); - KinkyDungeonDressPlayer(C, true, false, undefined, undefined, undefined, C.metadata?.palette || C.Palette, undefined, - true); - let newOut = DecompressB64(NewOutfit); - CharacterAppearanceRestore(C, newOut, C != KinkyDungeonPlayer, false); - let newParsed = JSON.parse(newOut); - C.metadata = newParsed?.metadata || DefaultOutfitMetadata(); - if (newParsed?.metadata) { - C.Palette = newParsed.metadata.palette; - } else C.Palette = ""; - CharacterRefresh(C); - KDInitProtectedGroups(C); - KDRefreshCharacter.set(C, true); - KinkyDungeonDressPlayer(C, true, undefined, undefined, undefined, - undefined, C.metadata?.palette || C.Palette, true - ); - } else if (C == KinkyDungeonPlayer) { - KDGetDressList().Default = KinkyDungeonDefaultDefaultDress; - CharacterAppearanceRestore(KinkyDungeonPlayer, - CharacterAppearanceStringify(DefaultPlayer, - KDGetCharMetadata(DefaultPlayer) - ), false, true); - CharacterReleaseTotal(KinkyDungeonPlayer); - KinkyDungeonSetDress("Default", "Default", C, true); - C.Palette = ""; - C.metadata = DefaultOutfitMetadata(); - KDRefreshCharacter.set(C, true); - KinkyDungeonDressPlayer(); - KDInitProtectedGroups(KinkyDungeonPlayer); - } + if (NewOutfit) { + KDOriginalValue = KDOutfitOriginalStore[KDCurrentOutfit] || ""; + KinkyDungeonSetDress("None", "None", C, true); + KDRefreshCharacter.set(C, true); + KinkyDungeonDressPlayer(C, true, false, undefined, undefined, undefined, C.metadata?.palette || C.Palette, undefined, + true); + let newOut = await KinkyDungeonDecompressSave (NewOutfit, SaveType.Outfit); + await CharacterAppearanceRestore(C, newOut, C != KinkyDungeonPlayer, false); + let newParsed = JSON.parse(newOut); + C.metadata = newParsed?.metadata || DefaultOutfitMetadata(); + if (newParsed?.metadata) { + C.Palette = newParsed.metadata.palette; + } else C.Palette = ""; + CharacterRefresh(C); + KDInitProtectedGroups(C); + KDRefreshCharacter.set(C, true); + KinkyDungeonDressPlayer(C, true, undefined, undefined, undefined, + undefined, C.metadata?.palette || C.Palette, true + ); + } else if (C == KinkyDungeonPlayer) { + KDGetDressList().Default = KinkyDungeonDefaultDefaultDress; + await CharacterAppearanceRestore(KinkyDungeonPlayer, + CharacterAppearanceStringify(DefaultPlayer, + KDGetCharMetadata(DefaultPlayer) + ), false, true); + CharacterReleaseTotal(KinkyDungeonPlayer); + KinkyDungeonSetDress("Default", "Default", C, true); + C.Palette = ""; + C.metadata = DefaultOutfitMetadata(); + KDRefreshCharacter.set(C, true); + KinkyDungeonDressPlayer(); + KDInitProtectedGroups(KinkyDungeonPlayer); + } + })(); return true; }; }; @@ -1940,7 +1943,7 @@ function KDDrawWardrobe(_screen: string, Character: Character) { KinkyDungeonSetDress("Bikini", "Bikini", C, true); } else KinkyDungeonSetDress("None", "None", C, true); - KinkyDungeonDressPlayer(C, true, false, undefined, undefined, + KinkyDungeonDressPlayer(C, true, false, undefined, undefined, undefined, C.metadata?.palette || C.Palette, undefined, true); if (C == KinkyDungeonPlayer) { KDInitProtectedGroups(C); @@ -1961,15 +1964,15 @@ function KDDrawWardrobe(_screen: string, Character: Character) { KDBaseWhite, KinkyDungeonRootDirectory + "UI/X.png", undefined, undefined, undefined, undefined, undefined, true); DrawButtonKDEx("LoadFromCode", (_bdata) => { - KinkyDungeonState = "LoadOutfit"; - KDSelectedModel = null; - + (async () => { + KinkyDungeonState = "LoadOutfit"; + KDSelectedModel = null; - CharacterReleaseTotal(C || KinkyDungeonPlayer); - ElementCreateTextArea("saveInputField"); - ElementValue("saveInputField", LZString.compressToBase64( - AppearanceItemStringify((C || KinkyDungeonPlayer).Appearance) - )); + CharacterReleaseTotal(C || KinkyDungeonPlayer); + ElementCreateTextArea("saveInputField"); + ElementValue ("saveInputField", + await KinkyDungeonCompressSave (AppearanceItemStringify((C || KinkyDungeonPlayer).Appearance), SaveType.Outfit)); + })(); return true; }, true,465, 875, 240, 50, TextGet("KinkyDungeonDressPlayerImport"), KDBaseWhite, KinkyDungeonRootDirectory + "UI/Load.png", undefined, undefined, undefined, @@ -2004,22 +2007,22 @@ function KDDrawWardrobe(_screen: string, Character: Character) { KDSaveOutfitInfo(); } KinkyDungeonReplaceConfirm = 0; - localStorage.setItem("kinkydungeonappearance" + KDCurrentOutfit, - LZString.compressToBase64( - CharacterAppearanceStringify(C || KinkyDungeonPlayer, - KDGetCharMetadata(C || KinkyDungeonPlayer) - ) - )); + KinkyDungeonCompressSave (CharacterAppearanceStringify (C || KinkyDungeonPlayer, + KDGetCharMetadata(C || KinkyDungeonPlayer)), + SaveType.Outfit) + .then ((compressed) => { + localStorage.setItem ("kinkydungeonappearance" + KDCurrentOutfit, compressed); + }); + //localStorage.setItem("kdcurrentoutfit", KDCurrentOutfit + ""); KinkyDungeonDressSet(); KDOriginalValue = ""; KDRefreshOutfitInfo(); - return true; } else { KDConfirmType = "save"; KinkyDungeonReplaceConfirm = 2; - return true; } + return true; }, true, 465, 930, 240, 50, TextGet((KinkyDungeonReplaceConfirm > 0 && KDConfirmType == 'save') ? "KDWardrobeSaveOutfitConfirm" : @@ -2031,20 +2034,22 @@ function KDDrawWardrobe(_screen: string, Character: Character) { if (KDConfirmType == "reset" && KinkyDungeonReplaceConfirm > 0) { KDSelectedModel = null; if (C == KinkyDungeonPlayer) { - KDChangeWardrobe(C); - KDGetDressList().Default = KinkyDungeonDefaultDefaultDress; - CharacterAppearanceRestore(KinkyDungeonPlayer, - CharacterAppearanceStringify(DefaultPlayer, - KDGetCharMetadata(KinkyDungeonPlayer) - ), false, false - ); - CharacterReleaseTotal(KinkyDungeonPlayer); - KinkyDungeonSetDress("Default", "Default", C, true); - KinkyDungeonDressPlayer(); - KDInitProtectedGroups(KinkyDungeonPlayer); - UpdateModels(KinkyDungeonPlayer); - KinkyDungeonConfigAppearance = true; - KinkyDungeonReplaceConfirm = 0; + (async () => { + KDChangeWardrobe(C); + KDGetDressList().Default = KinkyDungeonDefaultDefaultDress; + await CharacterAppearanceRestore(KinkyDungeonPlayer, + CharacterAppearanceStringify(DefaultPlayer, + KDGetCharMetadata(KinkyDungeonPlayer) + ), false, false + ); + CharacterReleaseTotal(KinkyDungeonPlayer); + KinkyDungeonSetDress("Default", "Default", C, true); + KinkyDungeonDressPlayer(); + KDInitProtectedGroups(KinkyDungeonPlayer); + UpdateModels(KinkyDungeonPlayer); + KinkyDungeonConfigAppearance = true; + KinkyDungeonReplaceConfirm = 0; + })(); } else if (C == KDSpeakerNPC) { let value = KDNPCStyle.get(KDSpeakerNPC); if (!value) return false; @@ -2245,42 +2250,45 @@ function KDSaveCodeOutfit(C: Character, clothesOnly: boolean = false): void { if (!C) C = KinkyDungeonPlayer; // Save outfit KDChangeWardrobe(C); - let decompressed = DecompressB64(ElementValue("saveInputField")); - if (decompressed) { + (async () => { + let decompressed = await KinkyDungeonDecompressSave (ElementValue ("saveInputField"), SaveType.Outfit); + if (decompressed) { - // Strips first - KDChangeWardrobe(C); - CharacterReleaseTotal(C); - CharacterNaked(C); - KDRefreshCharacter.set(C, true); - KinkyDungeonSetDress("None", "None", C, true); - KinkyDungeonDressPlayer(C, true); - KDInitProtectedGroups(C); - KinkyDungeonConfigAppearance = true; - KinkyDungeonReplaceConfirm = 0; - - // Then decompresses - CharacterAppearanceRestore(C, decompressed, clothesOnly, !clothesOnly); - CharacterRefresh(C); - KDInitProtectedGroups(C); - } + // Strips first + KDChangeWardrobe(C); + CharacterReleaseTotal(C); + CharacterNaked(C); + KDRefreshCharacter.set(C, true); + KinkyDungeonSetDress("None", "None", C, true); + KinkyDungeonDressPlayer(C, true); + KDInitProtectedGroups(C); + KinkyDungeonConfigAppearance = true; + KinkyDungeonReplaceConfirm = 0; - KDRefreshCharacter.set(C, true); - KinkyDungeonDressPlayer(C, true, undefined, undefined, undefined, - undefined, C.metadata?.palette || C.Palette, true); + // Then decompresses + await CharacterAppearanceRestore(C, decompressed, clothesOnly, !clothesOnly); + CharacterRefresh(C); + KDInitProtectedGroups(C); + } + KDRefreshCharacter.set(C, true); + KinkyDungeonDressPlayer(C, true, undefined, undefined, undefined, + undefined, C.metadata?.palette || C.Palette, true); - //KinkyDungeonNewDress = true; + //KinkyDungeonNewDress = true; + })(); } function KDRestoreOutfit() { - // Restore the original outfit - if (KDOriginalValue) { - CharacterAppearanceRestore(KinkyDungeonPlayer, DecompressB64(KDOriginalValue) || KDOriginalValue, false, true); - CharacterRefresh(KinkyDungeonPlayer); - KDInitProtectedGroups(KinkyDungeonPlayer); - KinkyDungeonDressPlayer(); - } + (async () => { + // Restore the original outfit + if (KDOriginalValue) { + await CharacterAppearanceRestore (KinkyDungeonPlayer, await KinkyDungeonDecompressSave (KDOriginalValue, SaveType.Outfit) || KDOriginalValue, false, true); + CharacterRefresh(KinkyDungeonPlayer); + KDInitProtectedGroups(KinkyDungeonPlayer); + KinkyDungeonDressPlayer(); + } + })(); } function KDSaveOutfitInfo() { @@ -2410,14 +2418,14 @@ function KDLoadOutfitDirect(files: File[], Char: Character) { KDSaveName = f.name; try { const reader = new FileReader(); - reader.addEventListener('load', (event) => { + reader.addEventListener('load', async (event) => { str = event.target.result.toString(); - let decompressed = DecompressB64(str); + let decompressed = await KinkyDungeonDecompressSave (str, SaveType.Outfit); if (decompressed) { let origAppearance = Char.Appearance; try { - CharacterAppearanceRestore(Char, decompressed, Char == KDSpeakerNPC, Char != KDSpeakerNPC); + await CharacterAppearanceRestore(Char, decompressed, Char == KDSpeakerNPC, Char != KDSpeakerNPC); let newParsed = JSON.parse(decompressed); if (newParsed && newParsed.metadata) { Char.metadata = newParsed.metadata; @@ -2426,7 +2434,7 @@ function KDLoadOutfitDirect(files: File[], Char: Character) { CharacterRefresh(Char); KDOldValue = str; KDInitProtectedGroups(Char); - KinkyDungeonDressPlayer(Char, true, undefined, undefined, + KinkyDungeonDressPlayer(Char, true, undefined, undefined, undefined, undefined, Char.metadata?.palette || Char.Palette, true); if (Char.Appearance.length == 0) @@ -3031,9 +3039,9 @@ function KDDrawColorPicker(id: string, currentLayerName: string, targetFilter: L KDRefreshProps = true; lastGlobalRefresh = CommonTime() - GlobalRefreshInterval + 10; } - + return true; - }, true, X + width/2 + 10, YY, width/2 - 10, 30, + }, true, X + width/2 + 10, YY, width/2 - 10, 30, TextGet("KDResetLayer"), KDBaseWhite) && MouseClicked) res.updated = true; @@ -3117,26 +3125,26 @@ function KDDrawColorPicker(id: string, currentLayerName: string, targetFilter: L let selectedLayer = factionFilterDef?.color || "None"; for (let key of ["None", ...GenericPaletteLayers]) { //DrawTextFitKD(TextGet("KDPaletteLayer_" + key), X + width/2, YY, width, KDBaseWhite, KDBaseBlack, 20); - + let sprite = GenericPaletteLayerSprites[key] || GenericPaletteLayerSprites.DarkNeutral; - - DrawButtonKDEx("paletteLayer" + key, + + DrawButtonKDEx("paletteLayer" + key, () => { lastFilterUpdate = CommonTime(); if (callback_palette) callback_palette(key, factionFilterDef?.override, factionFilterDef?.desaturate != undefined ? true : undefined); - + lastGlobalRefresh = CommonTime() - GlobalRefreshInterval + 10; return true; - }, - true, X, YY - 15, width, 60, TextGet("KDPaletteLayer_" + key), - KDTextWhite, sprite, undefined, undefined, key != selectedLayer, + }, + true, X, YY - 15, width, 60, TextGet("KDPaletteLayer_" + key), + KDTextWhite, sprite, undefined, undefined, key != selectedLayer, KDButtonColor, undefined, true, { // @ts-ignore filters: (ii >= 0 && KDPIXIPaletteFilters.get(pid + palette)) ? KDPIXIPaletteFilters.get(pid + palette)[ii++] : undefined, }); - + YY += 61; } YY += 15; @@ -3144,11 +3152,11 @@ function KDDrawColorPicker(id: string, currentLayerName: string, targetFilter: L DrawCheckboxKDEx("overridePaletteLayer", () => { lastFilterUpdate = CommonTime(); if (callback_palette) callback_palette(selectedLayer, !factionFilterDef?.override, factionFilterDef?.desaturate != undefined ? true : undefined); - + lastGlobalRefresh = CommonTime() - GlobalRefreshInterval + 10; return true; - }, selectedLayer != "None", - X, YY - 15, 50, 50, TextGet("KDWardrobeOverridePaletteLayer"), + }, selectedLayer != "None", + X, YY - 15, 50, 50, TextGet("KDWardrobeOverridePaletteLayer"), factionFilterDef?.override, selectedLayer == "None", KDTextWhite, undefined, { fontSize: 18, maxWidth: 200, @@ -3158,17 +3166,17 @@ function KDDrawColorPicker(id: string, currentLayerName: string, targetFilter: L DrawCheckboxKDEx("desaturatePaletteLayer", () => { lastFilterUpdate = CommonTime(); if (callback_palette) callback_palette(selectedLayer, factionFilterDef?.override, factionFilterDef?.desaturate != undefined ? undefined : true); - + lastGlobalRefresh = CommonTime() - GlobalRefreshInterval + 10; return true; - }, selectedLayer != "None", - X, YY - 15, 50, 50, TextGet("KDWardrobeDesaturatePaletteLayer"), + }, selectedLayer != "None", + X, YY - 15, 50, 50, TextGet("KDWardrobeDesaturatePaletteLayer"), !factionFilterDef?.desaturate, selectedLayer == "None", KDTextWhite, undefined, { fontSize: 18, maxWidth: 200, }); - + YY += 20; } @@ -3211,7 +3219,7 @@ function KDDrawColorPicker(id: string, currentLayerName: string, targetFilter: L bb}`); ElementValue("KDCopyFilter", JSON.stringify(targetFilters[currentLayerName])); } - + lastGlobalRefresh = CommonTime() - GlobalRefreshInterval + 10; } @@ -3391,7 +3399,7 @@ function KDDrawColorPicker(id: string, currentLayerName: string, targetFilter: L TF.Element.oninput = (_event: any) => { let value = ElementValue("KDSelectedColor"); let RegExp = /^#[0-9A-Fa-f]{6}$/i; - + if (RegExp.test(value)) { let hex = KDhexToRGB(value); if (hex) { @@ -3417,15 +3425,15 @@ function KDDrawColorPicker(id: string, currentLayerName: string, targetFilter: L }; } } else if (!palette) { - DrawTextFitKD(TextGet("KDPaletteColorPickerInfo"),X + width/2, YY - 10, + DrawTextFitKD(TextGet("KDPaletteColorPickerInfo"),X + width/2, YY - 10, 300, KDBaseWhite, KDTextGray0, 16, "center"); - + } else { - DrawTextFitKD(TextGet("KDPaletteColorPickerInfo2"),X + width/2, YY - 10, + DrawTextFitKD(TextGet("KDPaletteColorPickerInfo2"),X + width/2, YY - 10, 300, KDBaseWhite, KDTextGray0, 16, "center"); - + } - + res.YY = YY; @@ -3443,8 +3451,8 @@ function KDDressWardrobeChar(C: Character, forcedress?: boolean) { // show with preview restraints let selectedPalette = C.metadata?.palette || C.Palette || ""; let prevR = KDGetPreviewRestraints(KDWardrobePreviewRestraints); - KinkyDungeonDressPlayer(C, false, false, - prevR, undefined, + KinkyDungeonDressPlayer(C, false, false, + prevR, undefined, KDGetNPCRestraintTags(prevR, undefined, undefined, false, false), selectedPalette, true); return; @@ -3456,9 +3464,9 @@ function KDDressWardrobeChar(C: Character, forcedress?: boolean) { } else if (KDNPCChar_ID.get(KDSpeakerNPC)) { // show with restraints //let selectedPalette = C.metadata?.palette || C.Palette; - KinkyDungeonDressPlayer(KDSpeakerNPC, false, false, + KinkyDungeonDressPlayer(KDSpeakerNPC, false, false, KDGameData.NPCRestraints ? KDGameData.NPCRestraints[KDNPCChar_ID.get(KDSpeakerNPC) + ''] : undefined, - undefined, + undefined, KDGameData.NPCRestraints ? KDGetNPCRestraintTags(KDGameData.NPCRestraints[KDNPCChar_ID.get(KDSpeakerNPC) + ''], undefined, undefined, false, false) : undefined, undefined, !forcedress); diff --git a/Scripts/Appearance.ts b/Scripts/Appearance.ts index 6bf68c3f9..d16ca5db7 100644 --- a/Scripts/Appearance.ts +++ b/Scripts/Appearance.ts @@ -76,8 +76,9 @@ function AppearanceItemStringify(Item: Item[]): string { * @param backup - The serialised appearance to restore * @param clothesOnly - The serialised appearance to restore */ -function CharacterAppearanceRestore(C: Character, backup: string, clothesOnly: boolean = false, noProtected: boolean = false): void { - let parsed = JSON.parse(LZString.decompressFromBase64(backup) || backup); +async function CharacterAppearanceRestore(C: Character, backup: string, clothesOnly: boolean = false, noProtected: boolean = false): Promise +{ + let parsed = JSON.parse(await KinkyDungeonDecompressSave (backup, SaveType.Outfit) || backup); let newAppearance = AppearanceItemParse(parsed?.metadata ? parsed.appearance : backup); C.metadata = parsed.metadata; if (!clothesOnly) { @@ -149,4 +150,4 @@ function AppearanceCleanup(C: Character) { A -= 1; } } -} \ No newline at end of file +} diff --git a/out/saveworker.js b/out/saveworker.js index e49e577be..02706fdf9 100644 --- a/out/saveworker.js +++ b/out/saveworker.js @@ -1,8 +1,72 @@ // eslint-disable-next-line strict importScripts('../Scripts/lib/LZString.js'); -onmessage = (e) => { - console.log("Compressing save in worker thread..."); - postMessage(LZString.compressToBase64(e.data)); +// Included for reference. +//enum SaveType { +// Game = 'game', +// Outfit = 'outfit', +// Wardrobe = 'wardrobe', // Complete outfit collection; TBD +//} +// +//interface SaveWorkerMsg { +// op: 'cmp' | 'err'; +// type: SaveType; +// data: string; +//} + +onmessage = async (ev) => { + const msg = ev?.data; + if (!msg || !msg.op || !msg.type || !msg.data) { + if (!msg) { + console.log ("Missing worker MessageEvent payload."); + } else { + console.log (`Missing worker message field(s); only got ${msg}`); + } + postMessage ({ op: 'err', data: '' }); + return; + } + + if (msg.op != "cmp" && msg.op != "cmp-legacy") { + postMessage ({ op: 'err', data: '' }); + return; + } + + console.log ("Compressing save in worker thread..."); + const mime_type = `application/vnd.straightlaced.kinkydungeon.save.${msg.type}+gzip;version=2`; + try { + /* + * FIXME: Temporary. Delete when all compression call sites updated. + */ + if (msg.op === 'cmp-legacy') { + throw new Error ("Legacy compression requested"); + } + // Blob. CompressionStream discards MIME-type; we'll add it later. + const save_b_js = new Blob ([msg.data]); + const save_pipe = save_b_js.stream().pipeThrough (new CompressionStream ('gzip')); + + // Compressed blob. + const resp = new Response (save_pipe, { headers: [["Content-Type", mime_type ]]}); + const save_b_z = await resp.blob(); + + // Snarfed from MDN Web docs. + async function toBase64DataURL (blob) { + return await new Promise ((resolve, reject) => { + const reader = Object.assign (new FileReader(), { + onload: () => resolve (reader.result), + onerror: () => reject (reader.error) + }); + reader.readAsDataURL (blob); + }); + } + + const save_z64 = await toBase64DataURL (save_b_z); + + msg.data = save_z64; + } catch (err) { + console.log (`Caught ${err}; falling back to LZString...`); + msg.data = LZString.compressToBase64 (msg.data); + } + postMessage (msg); + // Is this necessary? close(); -}; \ No newline at end of file +};