aboutsummaryrefslogtreecommitdiffstats
path: root/desktop
diff options
context:
space:
mode:
Diffstat (limited to 'desktop')
-rw-r--r--desktop/Prefs.qml13
-rw-r--r--desktop/companion/Companion.qml2
-rw-r--r--desktop/companion/Mood.js11
-rw-r--r--desktop/companion/test_mood.js18
-rw-r--r--desktop/modules/companion/CompanionModule.qml44
5 files changed, 74 insertions, 14 deletions
diff --git a/desktop/Prefs.qml b/desktop/Prefs.qml
index 03ac980..80515a4 100644
--- a/desktop/Prefs.qml
+++ b/desktop/Prefs.qml
@@ -56,6 +56,15 @@ Singleton {
root.write(Object.assign({}, root.doc, { companion: on }));
}
+ // Seconds the companion stays in her leotard after the self-destruct,
+ // 0 for until she is hidden and shown again.
+ property int companionSkirtless: 60
+ function setCompanionSkirtless(s) {
+ if (s === root.companionSkirtless) return;
+ root.companionSkirtless = s;
+ root.write(Object.assign({}, root.doc, { companionSkirtless: s }));
+ }
+
// The companion's wallpaper list, "" for the default in
// companion/Admired.qml.
property string companionBlushFile: ""
@@ -125,6 +134,8 @@ Singleton {
root.settingsSidebar = typeof w === "number" && w >= root.sidebarMin && w <= root.sidebarMax
? Math.round(w) : 200;
root.companion = parsed.companion !== false;
+ const sk = parsed.companionSkirtless;
+ root.companionSkirtless = Number.isInteger(sk) && sk >= 0 ? sk : 60;
root.companionBlushFile = typeof parsed.companionBlushFile === "string" ? parsed.companionBlushFile : "";
root.ready = true;
}
@@ -141,6 +152,6 @@ Singleton {
onFileChanged: reload()
onLoaded: root.reparse()
// Missing file: the defaults, and nothing written.
- onLoadFailed: { root.doc = {}; root.pinnedIp = ""; root.presets = null; root.settingsSidebar = 200; root.assistantModel = ""; root.companion = true; root.companionBlushFile = ""; root.ready = true; }
+ onLoadFailed: { root.doc = {}; root.pinnedIp = ""; root.presets = null; root.settingsSidebar = 200; root.assistantModel = ""; root.companion = true; root.companionBlushFile = ""; root.companionSkirtless = 60; root.ready = true; }
}
}
diff --git a/desktop/companion/Companion.qml b/desktop/companion/Companion.qml
index b9ae9fb..3102c59 100644
--- a/desktop/companion/Companion.qml
+++ b/desktop/companion/Companion.qml
@@ -244,7 +244,7 @@ PanelWindow {
clicks: root.clicks, admireAt: root.admireAt, mockAt: root.mockAt, typingAt: root.typingAt, wakeAt: root.wakeAt,
monologue: root.monologue, idle: root.idle,
})
- readonly property string outfit: Mood.outfit(root.destructAt, root.now)
+ readonly property string outfit: Mood.outfit(root.destructAt, root.now, Prefs.companionSkirtless * 1000)
// Anything above the monologue cuts it short.
onMoodChanged: if (root.monologue && root.mood.state !== "muse") root.stopMonologue()
diff --git a/desktop/companion/Mood.js b/desktop/companion/Mood.js
index c4df50b..409a958 100644
--- a/desktop/companion/Mood.js
+++ b/desktop/companion/Mood.js
@@ -31,7 +31,6 @@ var T = {
admireLook: 1500, // turned to the wallpaper, before the blush
admireBlush: 3000,
admireCooldown: 180000,
- skirtless: 60000,
};
function st(state, frame) { return { state: state, frame: frame }; }
@@ -79,12 +78,14 @@ function pick(s) {
}
// The self-destruct takes the skirt off once the flash is over, for the
-// scorched moment and the skirtless minute after it. Showing her again
-// sets destructAt back to 0, which restores it early.
-function outfit(destructAt, now) {
+// scorched moment and skirtless ms after it (Prefs.companionSkirtless),
+// or with 0 until she is shown again, which sets destructAt back to 0 and
+// also restores it early.
+function outfit(destructAt, now, skirtless) {
if (!destructAt) return "dress";
const since = now - destructAt;
- return since >= T.destruct && since < T.destruct + T.scorched + T.skirtless ? "leotard" : "dress";
+ if (since < T.destruct) return "dress";
+ return skirtless === 0 || since < T.destruct + T.scorched + skirtless ? "leotard" : "dress";
}
function mockAllowed(lastMockAt, now) { return now - lastMockAt >= T.mockCooldown; }
diff --git a/desktop/companion/test_mood.js b/desktop/companion/test_mood.js
index f24340c..37af2ea 100644
--- a/desktop/companion/test_mood.js
+++ b/desktop/companion/test_mood.js
@@ -99,12 +99,18 @@ assert.deepStrictEqual([1, 2, 3, 4, 5, 6].map(M.clickFrame),
// Outfit: the dress until the flash is over, then the leotard for
// scorched + skirtless, then the dress again.
-assert.strictEqual(M.outfit(0, NOW), "dress");
-assert.strictEqual(M.outfit(NOW - 10, NOW), "dress");
-assert.strictEqual(M.outfit(NOW - M.T.destruct, NOW), "leotard");
-const bare = M.T.destruct + M.T.scorched + M.T.skirtless;
-assert.strictEqual(M.outfit(NOW - bare + 10, NOW), "leotard");
-assert.strictEqual(M.outfit(NOW - bare, NOW), "dress");
+const SK = 60000;
+assert.strictEqual(M.outfit(0, NOW, SK), "dress");
+assert.strictEqual(M.outfit(NOW - 10, NOW, SK), "dress");
+assert.strictEqual(M.outfit(NOW - M.T.destruct, NOW, SK), "leotard");
+const bare = M.T.destruct + M.T.scorched + SK;
+assert.strictEqual(M.outfit(NOW - bare + 10, NOW, SK), "leotard");
+assert.strictEqual(M.outfit(NOW - bare, NOW, SK), "dress");
+// A skirtless time of 0 keeps the leotard until she is shown again, which
+// resets destructAt.
+assert.strictEqual(M.outfit(NOW - 10, NOW, 0), "dress");
+assert.strictEqual(M.outfit(NOW - 3600000, NOW, 0), "leotard");
+assert.strictEqual(M.outfit(0, NOW, 0), "dress");
// Cooldowns.
assert.strictEqual(M.mockAllowed(0, NOW), true);
diff --git a/desktop/modules/companion/CompanionModule.qml b/desktop/modules/companion/CompanionModule.qml
index abd852a..6ed36ec 100644
--- a/desktop/modules/companion/CompanionModule.qml
+++ b/desktop/modules/companion/CompanionModule.qml
@@ -24,7 +24,7 @@ Module {
icon: "\u{F0899}"
settings: true
group: "Desktop"
- keywords: ["companion", "2b", "mascot", "character", "show", "hide", "wallpaper", "blush"]
+ keywords: ["companion", "2b", "mascot", "character", "show", "hide", "wallpaper", "blush", "leotard", "timeout"]
page: Component {
Page {
@@ -58,6 +58,48 @@ Module {
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5 }
}
}
+ // After the self-destruct. Off keeps the leotard until she is
+ // hidden and shown again; turning it back on starts from the
+ // seconds in the field.
+ Item {
+ width: parent.width
+ height: 34
+ Text {
+ id: lblSk
+ anchors { left: parent.left; verticalCenter: parent.verticalCenter }
+ width: 160
+ text: "Leotard timeout"
+ color: Theme.subtext
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ }
+ Switch {
+ id: swSk
+ anchors { left: lblSk.right; leftMargin: 8; verticalCenter: parent.verticalCenter }
+ checked: Prefs.companionSkirtless > 0
+ onToggled: on => Prefs.setCompanionSkirtless(on ? (parseInt(fSk.value) || 60) : 0)
+ }
+ Field {
+ id: fSk
+ anchors { left: swSk.right; leftMargin: 10; verticalCenter: parent.verticalCenter }
+ width: 60
+ enabled: swSk.checked
+ opacity: enabled ? 1 : 0.4
+ value: String(Prefs.companionSkirtless || 60)
+ placeholder: "60"
+ onEdited: {
+ const n = parseInt(value);
+ if (n >= 1 && String(n) === value.trim()) Prefs.setCompanionSkirtless(n);
+ else value = String(Prefs.companionSkirtless || 60);
+ }
+ }
+ Text {
+ anchors { left: fSk.right; leftMargin: 10; right: parent.right; verticalCenter: parent.verticalCenter }
+ elide: Text.ElideRight
+ text: swSk.checked ? "seconds" : "until she is hidden and shown again"
+ color: Theme.overlay
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5 }
+ }
+ }
Item {
width: parent.width
height: 34