// // Toy // // by rascalDan // This script is a WIP and is always likely to be so. // All polite suggestions are welcomed :) // // This script, from https://ss.deviatenow.com, is protected by this licence : // CC by-NC, see http://creativecommons.org/licenses/by-nc/3.0/ // setInfos(9, "Toy", "Become my new plaything.", "rascalDan", "WIP", 0xFFFFFF, "en", ["bondage", "femaledom", "formale", "long", "pain", "toys", "joi"]); return new Object() { final VERSION = 1; final DAY = 86400; final HOUR = 3600.0; final soonFormatter = java.time.format.DateTimeFormatter.ofPattern("EEEE 'at' h:mma"); // Notes // toys. ankle_cuffs, ballgag, blindfold, chastity_belt, clothespins, cockring, dog_collar, handcuffs, hood, nipple_clamps, ring_gag final BALLGAG = "ballgag", COLLAR = "dog_collar", CLAMPS = "nipple_clamps", CHASTITY = "chastity_belt", HANDCUFFS = "handcuffs", DILDO = "dildo"; final SPOON = "spoon"; final COCK = "cock"; // fetish. final BONDAGE = "bondage", CBT = "cbt", CHORES = "chores", PAIN = "pain"; // toy.permission. final CUM = "cum", EDGE = "edge" final PERMIT = "permit"; final PLAY = "play"; final PERM_CHASTE = "perm_chaste", PERM_CHASTE_ASK = "perm_chaste_ask"; // toy.state. final CHASTE = "chaste", COLLARED = "collared", CUFFED = "cuffed", CLAMPED = "clamped", GAGGED = "gagged", NAKED = "naked"; final REDRESS = "redress"; final TOYTOYS = [CUFFED, COLLARED, CLAMPED, GAGGED]; final RELEASEFROM = "releaseFrom"; // toy.position final KNEELING = "kneeling", ALLFOURS = "allfours", STANDING = "standing"; def OWNER = null; def DOMME = null; final SITTER = "sarah-james"; final SOFT = "soft"; final DISAPPOINT = "disappoint"; final DATAFOLDER = getDataFolder(); // Imagery final DRESSED = "dressed", TITS = "tits", PUSSY = "pussy", LINGERIE = "lingerie", TEASE = "tease", SIT = "sit", BOOTS = "boots", KNEEL = "kneel", STOOD = "stood", MEAN = "mean", CROP = "crop", ASS = "ass", SQUAT = "squat", SSSH = "sssh"; final nDRESSED = "!$DRESSED", nTITS = "!$TITS", nTEASE = "!$TEASE"; def loadDomme = { domme, set = null -> final readDomme = { path -> if (!path) return [:]; final f = new File(path); if (!f.exists()) return [:]; return Eval.me(f.text); }; DOMME = [ "$DATAFOLDER/images/toy/domme.groovy", "$DATAFOLDER/images/toy/$domme/person.groovy", "$DATAFOLDER/images/toy/$domme/$set/set.groovy" ] .collect { readDomme(it) } .sum() }; def loadModules = { toy -> new File("$DATAFOLDER/scripts/toy") .listFiles() .each({s -> Eval.me(s.text)(toy); }); }; // Utils int localTimeOffset() { return java.time.ZoneId.systemDefault() .getRules() .getOffset(java.time.Instant.now()) .getTotalSeconds(); } java.time.LocalDateTime localTimeOf(int s) { return java.time.LocalDateTime.ofInstant( java.time.Instant.ofEpochSecond(s), java.time.ZoneId.systemDefault()); } float localTime() { // A float between 4.0 (4am) and 28.0 (4am) def wallclock = ((getTime() + localTimeOffset()) % DAY) / HOUR; if (wallclock < 4) { wallclock += 24; } return wallclock; } int getDay() { return ((int)(getTime() / DAY) * DAY); } public T getProp(String i, java.util.function.Function f, T d = null) { final T v = f.apply("toy.$i".toString()); if (v == null) return d; return v; } Object getDommeProp = { String i, Object d = null -> if (DOMME == null) return d; final v = DOMME[i]; if (v == null) return d; return v; } String dommeTitle() { getDommeProp("title", "Mistress") } String dommeName() { getDommeProp("name", "") } final java.util.function.Function loadB = this.&loadBoolean; final java.util.function.Function loadI = this.&loadInteger; final java.util.function.Function loadS = this.&loadString; void setProp(String i, Object v) { save("toy.$i".toString(), v) } boolean has(String i) { loadBoolean("toys.$i") == true } boolean likes(String i) { loadBoolean("fetish.$i") == true } boolean can(String i) { loadBoolean("toy.permission.$i") == true } boolean perm(String i) { can(it) } boolean getPermission(String i) { getProp("permission.$i", loadB) } void setPermission(String i, boolean v) { setProp("permission.$i", v) } void givePermission(String i) { setPermission(i, true) } void revokePermission(String i) { setPermission(i, false) } boolean stateIs(String i) { loadBoolean("toy.state.$i") == true } void set(String i, boolean s) { save("toy.state.$i", s) } boolean positioned(String i) { ("toy.position") == i } int getPunish() { loadInteger("toy.punishment") ?: 0 } void adjustPunish(int p) { save("toy.punishment", Math.max(getPunish() + p, 0)) } void adjustPunish(double p) { adjustPunish((int)p) } String getAway() { loadString("toy.owner.away") } void setAway(String a) { save("toy.owner.away", a) } int randRange(int min, int max) { min + getRandom(1 + max - min) } int randRange(double min, double max) { randRange((int)min, (int)max) } // Events static class Event { int time; String func; Object arg; def map = { [ time: time, func: func, arg: arg ] } } static class NamedEvent { String name; Event event; } Map loadEvents() { return (loadMap("toy.events") ?: [:]) .collectEntries { k, v -> [ k, new Event(v) ] }; } void saveEvents(Map events) { save("toy.events", events.collectEntries { k, v -> [ k, v.map() ] }); } NamedEvent nextEvent(Map events) { NamedEvent first = null; events.each{ String k, Event v -> if (!first || first.event.time > v.time) { first = new NamedEvent(name: k, event: v); } }; return first; } boolean setEvent(Map events, String name, int time, String func, Object arg = null) { if (namedEvents.containsKey(func)) { events[name] = new Event( time: time, func: func, arg: arg ); saveEvents(events); return true; } else { showPopup("No such event $func"); return false; } } boolean addEvent(String name, int time, String func, Object arg = null) { def events = loadEvents(); return setEvent(events, name, time, func, arg); } boolean addEventIfMissing(String name, int time, String func, Object arg = null) { def events = loadEvents(); if (!events.containsKey(name)) { return setEvent(events, name, time, func, arg); } return false; } void removeEvent(String name) { def events = loadEvents(); events.remove(name); saveEvents(events); } void execEvents(boolean rt) { for (def e = nextEvent(loadEvents()); e && e.event.time <= getTime(); e = nextEvent(loadEvents())) { def f = namedEvents[e.event.func]; removeEvent(e.name); if (f) { f(e.name, e.event.arg, e.event.time, rt); } } } def sessionAborted = null; def sessionToys = [:]; def gagText = { t, p -> if (!stateIs(GAGGED)) return t.toString(); return t.split(/\s+/) .collect { 'm' * getRandom((int)Math.max(1.0, it.length() * 1.0)) + 'p' * getRandom((int)Math.max(1.0, it.length() * 0.4)) + 'h' * getRandom((int)Math.max(1.0, it.length() * 0.8)) } .join(" ") .capitalize() + " ($p)"; }; def showButtonG = { s, p, t = null -> return t != null ? showButton(gagText(s, p), t) : showButton(gagText(s, p)); }; def compose = { texts -> if (!texts || texts.isEmpty()) return null; return texts.collect { t -> t[getRandom(t.size())] }.join(" ").capitalize(); }; def present = { imageSpec, texts -> if (texts) { show(compose(texts)); } if (imageSpec) { return showImage(imageSpec); } return null; }; def showButtonGT = { s, p, t, pc -> def tt = showButton(gagText(s, p), t); if (tt == t) { present(null, [ ["Don't keep me waiting!", "Hurry up!", "Quicker, toy!"]]); def pt = showButton(gagText(s, p)); adjustPunish(pt * pc); return pt + tt; } return tt; }; def clampPulls = { amount -> getRandom(1 + amount).times { if (getRandom(2)) { present([DRESSED], [ ["Take them off.", "Remove them."]]); wait(6 + getRandom(8)); if (getRandom(2)) { present([DRESSED], [ ["And put them back on", "Put them back"], ["right where they came from.", "where they were."]]); } else { present([DRESSED], [ ["Flip them", "Turn them"], ["90 degrees", "around"], ["and put them back.", "and replace them."]]); } wait(10 + getRandom(8)); } else if (getRandom(2)) { present([DRESSED], [ ["Twist them"], ["for me.", "... twist those nipples."]]); wait(6 + getRandom(5)); if (getRandom(2)) { present(null, [ ["More!", "A little more!", "Further!"]]); wait(getRandom(10) + 2); } } present([DRESSED], [ ["Pull them tight.", "Pull them!"]]); wait(getRandom(10) + 5); if (getRandom(5) == 0) { present(null, [ ["Tighter!", "Harder!", "Further!"]]); wait(getRandom(10) + 2); present(null, [ ["Haha!", "Ooo, they must hurt.", "Do they hurt?"]]); wait(3); } present(null, [ ["Release them.", "Let them go."]]); wait(getRandom(5) + 3); }; return amount; }; // Pre-tease def preStrip = { imageSpec = [DRESSED] -> if (stateIs(NAKED)) return 1.1; present(imageSpec, [ ["Clothes off!", "Get naked, slut!"]]); wait(10); present(imageSpec, [ ["All of them off!", "And the rest!", "Keep going."]]); set(NAKED, true); showButtonGT("Naked, ${dommeTitle()}", "naked", 60, 5); present(null, [ ["Very good. Now...", "I see..."], ["on your knees before me,", "kneel,", "stand up straight,", "on your feet,"], ["hands behind your head,", "arms up,", "hands high up,"], ["let me see you properly.", "let's see what we have.", "I want a good view."]]); wait(10); sessionToys[COCK] = getTime(); return 1.1; }; // Post // Play def playNothing = { (getRandom(3) + 2).times { show(imageTagsComment(showImage([TITS]))); wait(getRandom(10) + 10); }; }; def getSpoon = { if (sessionToys.containsKey(SPOON)) { present([DRESSED], [ ["Pick up", "Grab"], ["your", "that"], ["wooden spoon"], ["again."]]); wait(10); } else { present([DRESSED], [ ["Go to the kitchen...", "From the kitchen..."], ["bring me", "get yourself"], ["a wooden spoon or similar."]]); showButtonG("Yes, ${dommeTitle()}", "ok"); wait(5); showButtonGT("Back, ${dommeTitle()}", "back", 20, 1); sessionToys[SPOON] = getTime(); } }; def playKneel = { present([DRESSED], [ ["Right, slut.", "Come here, toy."], ["On the floor, right there.", "Kneel at my feet."]]); showButtonGT("Yes, ${dommeTitle()}.", "ok", 20, 1); present([DRESSED], [ ["When I say go,", "In a moment,"], ["you'll crawl", "turn and crawl"], ["away until I ring my bell."], ["You'll stay there,", "And then you won't move,"], ["head bowed", "face down"], ["until I call you back.", "until you're summoned."]]); showButtonG("Yes, ${dommeTitle()}.", "ok"); present(null, [["Wait..."]]); wait(getRandom(10) + 10); present([DRESSED], [["Ok, go!", "Now! Off you go."]]); wait(getRandom(5) + 5); playBackgroundSound("bell.wav"); show(null); def kneelTime = 60 + getRandom(240); wait(kneelTime); while (playBackgroundSound("bell.wav") || true) { if (showButtonG("Back, ${dommeTitle()}", "back", 20) != 20) break; } present([DRESSED], [ ["Good boy.", "Very good."], ["Stay down there.", "Don't move."]]); wait(getRandom(5) + 5); return kneelTime / 20.0; }; def playSuck = { def playWait = { file, time -> playBackgroundSound(file); wait(time); playBackgroundSound(null); }; def suck = { file, n, delay -> n.times { playWait(file, delay); }; }; def suckBeat = { beat, n -> suck("toy/${beat}bpm.mp3", n, 2 * (60 / beat)); }; def completed = loadInteger("toy.deepthroat.completed") ?: 0; if (completed >= 4) { preStrip(); preCollar(); preClamps(); getCuffs(); } if (sessionToys.containsKey(DILDO)) { present([DRESSED], [ ["Back to your dildo.", "Amuse me some more with that dildo."], ["Same as before, listen for my bell, slut."]]); showButtonG("Yes, ${dommeTitle()}", "ok"); } else { present([DRESSED], [ ["Time for you to amuse me, you little slut."], ["Get your dildo and secure it somewhere nearby, about eye level when kneeling."]]); wait(10); showButtonGT("Done, ${dommeTitle()}", "done", 60, 1); sessionToys[DILDO] = getTime(); present([DRESSED], [ ["The rules are simple:\n"], ["On my first bell, you take the head in your mouth and start to pleasure it.\n"], ["When you hear the beat, you suck the shaft, one beat in, one beat out again.\n"], ["Lastly, on the crack of my whip, you take the whole thing in and suck from the base.\n"], ["I know you'll enjoy it, don't be shy.\n"], ["One more thing..."], ["That cock doesn't doesn't leave your mouth until I say so, listen for second bell and then return to me."]]); showButtonGT("Understood, ${dommeTitle()}", "understood", 30, 1); } if (sessionToys[HANDCUFFS]) { present([DRESSED, nTEASE], [ ["Cuff yourself,", "Hands cuffed,"], ["behind your back of course.", "behind you."]]); wait(20); set(CUFFED, true); } present([TEASE], [ ["Get ready"]]); showButtonGT("Ready, ${dommeTitle()}", "ready", 10, 1); show(null); final target = loadInteger("toy.deepthroat.goal") ?: 5; def session = 0; wait(randRange(10, 20)); playWait("bell.wav", randRange(10, 40)); // Head while (session < target) { suckBeat(30, randRange(5, 15)); // Slow suckBeat(90, randRange(10, 30)); // Quick wait(0.5); final deep = randRange(target / 4, target / 2); suck("shortwhip.wav", deep, 2.5); // Deep session += deep; } suckBeat(30, randRange(5, 15)); // Slow wait(randRange(10, 20)); // Head playBackgroundSound("bell.wav"); // End showButtonGT("Back, ${dommeTitle()}", "back", 15, 1); if (stateIs(CUFFED)) { present([DRESSED, TEASE], [ ["Let yourself out.", "Unlock the cuffs."]]); wait(10); showButtonGT("Freed, ${dommeTitle()}", "freed", 120, 1); set(CUFFED, false); } if (getBoolean("Did you perform as ordered?", "Yes, ${dommeTitle()}", "No, ${dommeTitle()}")) { present([DRESSED], [ ["Good boy"]]); adjustPunish(-4 * target); completed += 1; if (completed >= 5) { save("toy.deepthroat.goal", target + 1); completed = 0; } } else { final missed = getInteger("How many deep sucks did you miss?", 1); if (missed > 5) { present([DRESSED], [ ["Very disappointing."]]); } else { present([DRESSED], [ ["Mmm"], ["More practice required."]]); } adjustPunish(10 * missed); completed -= 1; if (completed <= -5) { save("toy.deepthroat.goal", target - 1); completed = 0; } } save("toy.deepthroat.completed", completed); wait(10); }; def stateToyName = { state -> switch (state) { case CUFFED: return "cuffs"; case COLLARED: return "collar"; case CLAMPED: return "nipple clamps"; case GAGGED: return "gag"; } return null; }; def removeToy = { toy, imageSpec -> if (!stateIs(toy)) return; def toyName = stateToyName(toy); present(imageSpec, [ ["OK,"], ["you may remove your $toyName.", "the $toyName can come off.", "take the $toyName off."]]); showButtonG("Thank you, ${dommeTitle()}", "ok"); wait(5); set(toy, false); removeEvent("$RELEASEFROM-$toy"); showButtonGT("Removed, ${dommeTitle()}", "removed", 20, 1); }; // Session def sessionSummon = { imageSpec -> final limit = 5; final timeMax = 10; for (def n = 1; n <= limit; n++) { switch (n) { case limit: present(imageSpec, [ ["TOY!", "SLAVE!", "SLUT!"], ["Last chance!", "Final warning!"]]); adjustPunish(5); break; case limit - 1: case limit - 2: present(imageSpec, [ ["Toy!", "Slave!", "Slut!"], ["Where are you!?", "Get yourself here, now!"]]); break; default: present(imageSpec, [ ["Toy!", "Slave!", "Slut!"]]); break; } playBackgroundSound(n == limit ? "shortwhip.wav" : "bell.wav"); if (showButton("Yes, ${dommeTitle()}?", timeMax) < timeMax) { return true; }; } showLounge(); show(null); adjustPunish(5); return false; }; final activityList = [ 'preStrip': preStrip, 'playKneel': playKneel, 'playNothing': playNothing, 'playSuck': playSuck, ]; final addActivity = { String name, func -> activityList[name] = func; } def sessionPlay = { if (stateIs(CHASTE)) sessionToys[CHASTITY] = getTime(); def playScope = [ 'randRange': { min, max -> randRange(min, max) }, 'currentPunishment': { getPunish() }, 'sessionAborted': { return sessionAborted }, 'can': { return can(it) }, 'has': { return has(it) }, 'is': { return stateIs(it) }, 'likes': { return likes(it) }, 'punishMultiple': (float)1.0, 'prop': load('toy') ?: [:] ]; def apply = { activitity, use = null -> if (!activitity) return; def val = activitity(); playBackgroundSound(null); if (use && val instanceof Number) { use(val); } }; def interval = { activities -> if (activities) { apply(activities[getRandom(activities.size())], null); } }; def funcMap = [ 'use': [ 'punishMultiply': { val -> if (val) { playScope.punishMultiple *= val; } }, 'punishApply': { val -> if (val) { adjustPunish(-val * playScope.punishMultiple); } } ], 'select': [ 'repeat': { amount, activities, intervals, use = null -> amount.times { if (!sessionAborted) { apply(activities[getRandom(activities.size())], use); interval(intervals); } }; }, 'take': { amount, activities, intervals, use = null -> Collections.shuffle(activities); activities.take(amount).each { if (!sessionAborted) { apply(it, use); interval(intervals); } }; } ], 'activities': activityList ]; final eval = { expr -> Eval.me('toy', playScope, expr.toString()) }; if (stateIs("DEBUG")) { // Check everything in DOMME evaluates and resolves final checkIsFunc = { group, name -> if (!name) return; show("checking $group:$name is a function"); if (!(funcMap[group][name] instanceof Closure)) { showPopup("$group:$name is not a function"); } } final checkEval = { expr -> show("checking $expr evaluates"); eval(expr); }; DOMME.sessions.forEach { session -> checkEval(session.require ?: true); checkEval(session.probability); session.phases.forEach { phase -> checkEval(phase.require ?: true); checkIsFunc("select", phase.select); checkEval(phase.number ?: 1); phase.activities.forEach { a -> checkIsFunc("activities", a); } (phase.intervals ?: []).forEach { i -> checkIsFunc("activities", i); } checkIsFunc("use", phase.use); }; }; } final sessions = DOMME.sessions.findAll { s -> eval(s.require ?: true) }; final probabilities = sessions.withIndex().collect { s, idx -> [idx] * s.probability }.sum(); final sessionIdx = probabilities[getRandom(probabilities.size)]; sessions[sessionIdx].phases.forEach { phase -> if (eval(phase.require ?: true)) { funcMap.select[phase.select ?: 'take'](eval(phase.number ?: 1), (phase.activities ?: []).collect { f -> funcMap.activities[f] }, (phase.intervals ?: []).collect { f -> funcMap.activities[f] }, funcMap.use[phase.use]); } }; }; def sessionRelease = { goodToy -> if (goodToy) { present([DRESSED], [ ["Ahhh,", "OK,"], ["I'm done with you for now.", "That was fun... for me at least."]]); } else { present([DRESSED], [ ["I don't ask much of you, toy."]]); wait(getRandom(10) + 5); postChastity(); } wait(getRandom(10) + 5); def toys = TOYTOYS.findAll { t -> stateIs(t) }; Collections.shuffle(toys); if (toys) { if (!goodToy) { present([DRESSED], [ ["I should leave you like that.", "I suppose you still want letting free?"], ["Maybe.", "Let me ponder on it."]]); wait(getRandom(20) + 20); } toys.each { toy -> if (getRandom(100) > ((goodToy && (getPunish() < 100)) ? 10 : 80)) { removeToy(toy, [DRESSED]); } else { addEvent("$RELEASEFROM-$toy", getTime() + 100 + getRandom(10 * (getPunish() + 1)), RELEASEFROM, toy); adjustPunish(-5); } }; } if (stateIs(NAKED)) { if (goodToy && getRandom(1)) { present([DRESSED], [ ["Put some clothes back on.", "Cover yourself up!"]]); wait(getRandom(10) + 15); set(NAKED, false); } else { present([DRESSED], [ ["No clothes now for a while,", "Keep those clothes off,"], ["you don't get clothes until I say.", "I like seeing you naked."]]); addEvent(REDRESS, getTime() + 600 + getRandom(1200), "redress"); showButtonG("Yes, ${dommeTitle()}", "ok"); } } if (goodToy) { present([DRESSED], [ ["Off you go.", "You may leave."]]); } else { present([DRESSED], [ ["Get out of my sight.", "Go!"]]); removeEvent(CUM); revokePermission(CUM); } wait(10); save("toy.lastPlay", getTime()); showLounge(); sessionAborted = null; sessionToys = [:]; if (goodToy) { adjustPunish(-20); } else { setProp(DISAPPOINT, getProp(DISAPPOINT, loadI, 0) + 1); } show(null); }; def playActivity = { final keys = new ArrayList(activityList.keySet()); final act = getSelectedValue("Choose activity", keys); activityList[keys[act]](); }; def playWait = { present([DRESSED,nTEASE], [ ["Hello,", "Hi,", "Hey there,"], ["toy.", "slut.", "slave."], ["On your knees,", "On the floor,", "Down... at my feet,",], ["wait there.", "wait patiently.", "don't move."], ["I'll be with you shortly.", "I won't be a moment."]]); showButtonGT("Yes, ${dommeTitle()}", "ok", 10, 1); showLounge(); show(null); wait(20 + getRandom(20)); }; def playSchedule = { if (getAway()) return; final lastPlay = loadInteger("toy.lastPlay") ?: 0; if (lastPlay < getTime() - (4 * HOUR)) { addEventIfMissing(PLAY, getTime() + 120 + getRandom(480), PLAY, true); // 2-10mins } else { addEventIfMissing(PLAY, getTime() + 1200 + getRandom(5400), PLAY, false); // 20-90mins } }; def playEvent = { rt = true, first = true -> if (!rt || getAway()) return; dress([[DRESSED,nTEASE],[DRESSED,TEASE],[TITS],[STOOD]]); if (sessionSummon([DRESSED,nTEASE])) { if (first.asBoolean()) { playWait(); } sessionPlay(); sessionRelease(sessionAborted == null); } else { adjustPunish(25); } playSchedule(); showLounge(); } def redress = { rt -> if (!rt || !sessionSummon([DRESSED])) { addEvent(REDRESS, getTime() + 300, "redress"); return; } present([DRESSED], [ ["OK, toy,"], ["you can put some clothes back on now.", "get yourself covered up."]]); wait(getRandom(10) + 15); showButtonG("Thank you, ${dommeTitle()}", "ok"); set(NAKED, false); showLounge(); }; def releaseFrom = { rt, arg, name -> if (!rt || !sessionSummon([DRESSED])) { addEvent(name, getTime() + 300, RELEASEFROM, arg); return; } removeToy(arg, [DRESSED]); showLounge(); }; def requestClothes = { adjustPunish(5); present([DRESSED], [ ["Really?", "Disappointing."], ["You want to clothes back on?", "You wish to cover up?"]]); if (getBoolean(null)) { present([DRESSED], [["Hmm, very well.", "If you must."]]); adjustPunish(50); set(NAKED, false); removeEvent(REDRESS); } else { present([DRESSED], [["Good boy."]]); } showButtonG("Thank you, ${dommeTitle()}", "ok"); showLounge(); }; def requestRelease = { toy -> adjustPunish(5); present([DRESSED], [ ["Really?", "Disappointing."], ["You wish to be un-$toy?"]]); if (getBoolean(null)) { adjustPunish(10); if (getRandom(100) > getPunish()) { present([DRESSED], [["No.", "You may not."], ["Not yet.", "Not until later."]]); } else { present([DRESSED], [["Hmm, very well.", "If you must."]]); set(toy, false); removeEvent("$RELEASEFROM-$toy"); } } else { present([DRESSED], [["Good boy."]]); } showButtonG("Thank you, ${dommeTitle()}", "ok"); showLounge(); }; // Setup def setupEvents = { playSchedule(); }; def setDefault = { prop, val -> if (loadString(prop) == null) { save(prop, val); } }; def namedEvents = [ play: { name, arg, schedTime, rt -> playEvent(rt, arg) }, releaseFrom: { name, arg, schedTime, rt -> releaseFrom(rt, arg, name) }, redress: { name, arg, schedTime, rt -> redress(rt) }, permit: { name, arg, schedTime, rt -> givePermission(arg) } ]; def setupShowState = { def localTimeStr = localTimeOf(getTime()).format(soonFormatter); show("localTime: $localTimeStr\noffsetSet: ${localTimeOffset()}\ntime_t: ${getTime()}"); showButton("OK"); show( [CHASTE, COLLARED, CUFFED, CLAMPED, GAGGED, NAKED].collect { def v = stateIs(it); return "$it: $v\n".capitalize() }.join()); showButton("OK"); show( [BALLGAG, COLLAR, CLAMPS, CHASTITY, HANDCUFFS, DILDO].collect { def v = has(it); return "$it: $v\n".capitalize() }.join()); showButton("OK"); show( [BONDAGE, CBT, CHORES, PAIN].collect { def v = likes(it); return "$it: $v\n".capitalize() }.join()); showButton("OK"); }; def showFaq = { useUrl("http://toy.randomdan.homeip.net/"); }; final addNamedEvent = { String name, func -> namedEvents[name] = func; } def requestables = [ faq: [ lbl: "FAQ (webpage)", act: showFaq ], ]; final addRequestable = { String name, String label, func -> requestables[name] = [lbl: label, act: func]; }; final removeRequestable = { String name -> requestables.remove(name); }; // This exists because calling .wait(t) in a metaClass method // calls it on the metaClass, not on the original object void pause(int t) { wait(t); } String main() { // GO! setDefault("toy.owner", "ancilla"); OWNER = loadString("toy.owner"); loadDomme(OWNER); loadModules(this); def getDayNum = { Math.floorDiv(getTime() - localTimeOffset() - 14400, 86400) }; def getAvail = { dayNum -> loadInteger("toy.availability.$dayNum") ?: 0 }; def addAvail = { amount -> def dayNum = getDayNum(); save("toy.availability.$dayNum", (int)(getAvail(dayNum) + amount)); }; def tidyAvail = { def p = load("toy.availability"); if (!p) return; def p2 = [:]; final firstDay = getDayNum() - 20; p.eachWithIndex{ avail, day -> if (day >= firstDay && avail > 0) { p2["$day"] = avail; } } save("toy.availability", p2); }; setDefault("toy.punishment", 0); execEvents(false); setupEvents(); tidyAvail(); final cycleTime = 60; showLounge(); while (true) { def away = getAway(); def e = nextEvent(loadEvents()); if (!e) return "toy"; // Force restart, should create some events if (away) { def note = loadString("toy.note"); def noteInstr = loadString("toy.noteInstr"); if (note) { show("${dommeTitle()} ${dommeName()} is away ($away)... but you find a note:\n$note"); showButton("OK"); if (noteInstr) { set(noteInstr, true); } readNote(); } else { show("${dommeTitle()} ${dommeName()} is away ($away)..."); } if (e && e.event && e.event.time > getTime()) { wait(e.event.time - getTime()); } } else { if (stateIs("DEBUG")) { def timeStr = localTimeOf(e.event.time).format(soonFormatter); show("$e.name: $timeStr -> ${e.event.func}(${e.event.arg})"); } else { show("${dommeTitle()} ${dommeName()} will summon you when required."); } final waitTime = Math.min(cycleTime, e.event.time - getTime()); final clickTime = showButton("Please, ${dommeTitle()}?", waitTime); if (clickTime < waitTime) { def opts = requestables.values().toList(); if (stateIs("DEBUG")) { opts.push([ lbl: "Status", act: setupShowState ]); opts.push([ lbl: "Play", act: playEvent, arg: true ]); opts.push([ lbl: "Activity", act: playActivity ]); } if (stateIs(NAKED)) { opts.push([ lbl: "May I wear clothes", act: requestClothes ]); } TOYTOYS.findAll { stateIs(it) }.each { opts.push([ lbl: "May I be un-$it".toString(), act: requestRelease, arg: it ]); }; opts.push([ lbl: "Back", act: null ]); def opt = getSelectedValue(stateIs("DEBUG") ? DOMME : "Yes, toy?", opts.collect { it.lbl }); def act = opts[opt].act; if (act) act(opts[opt].arg); else if (stateIs("DEBUG")) return "toy"; } addAvail(clickTime); } def eStart = getTime(); execEvents(true); addAvail(getTime() - eStart); } } }.main(); /* * Resources * scripts/toy.groovy * scripts/toy/bondage.groovy * scripts/toy/cbt.groovy * scripts/toy/chastity.groovy * scripts/toy/confession.groovy * scripts/toy/imagery.groovy * scripts/toy/intro.groovy * scripts/toy/orgasmControl.groovy * scripts/toy/pain.groovy * scripts/toy/sleep.groovy * scripts/toy/social.groovy * scripts/toy/tease.groovy * images/toy/ancilla/corset/1.jpg * images/toy/ancilla/corset/10.jpg * images/toy/ancilla/corset/11.jpg * images/toy/ancilla/corset/12.jpg * images/toy/ancilla/corset/13.jpg * images/toy/ancilla/corset/14.jpg * images/toy/ancilla/corset/15.jpg * images/toy/ancilla/corset/16.jpg * images/toy/ancilla/corset/2.jpg * images/toy/ancilla/corset/3.jpg * images/toy/ancilla/corset/4.jpg * images/toy/ancilla/corset/5.jpg * images/toy/ancilla/corset/6.jpg * images/toy/ancilla/corset/7.jpg * images/toy/ancilla/corset/8.jpg * images/toy/ancilla/corset/9.jpg * images/toy/ancilla/corset/tags * images/toy/ancilla/crop/1.jpg * images/toy/ancilla/crop/10.jpg * images/toy/ancilla/crop/11.jpg * images/toy/ancilla/crop/12.jpg * images/toy/ancilla/crop/13.jpg * images/toy/ancilla/crop/14.jpg * images/toy/ancilla/crop/15.jpg * images/toy/ancilla/crop/16.jpg * images/toy/ancilla/crop/2.jpg * images/toy/ancilla/crop/3.jpg * images/toy/ancilla/crop/4.jpg * images/toy/ancilla/crop/5.jpg * images/toy/ancilla/crop/6.jpg * images/toy/ancilla/crop/7.jpg * images/toy/ancilla/crop/8.jpg * images/toy/ancilla/crop/9.jpg * images/toy/ancilla/crop/tags * images/toy/ancilla/dress/1.jpg * images/toy/ancilla/dress/10.jpg * images/toy/ancilla/dress/11.jpg * images/toy/ancilla/dress/12.jpg * images/toy/ancilla/dress/13.jpg * images/toy/ancilla/dress/14.jpg * images/toy/ancilla/dress/15.jpg * images/toy/ancilla/dress/2.jpg * images/toy/ancilla/dress/3.jpg * images/toy/ancilla/dress/4.jpg * images/toy/ancilla/dress/5.jpg * images/toy/ancilla/dress/6.jpg * images/toy/ancilla/dress/7.jpg * images/toy/ancilla/dress/8.jpg * images/toy/ancilla/dress/9.jpg * images/toy/ancilla/dress/tags * images/toy/ancilla/pink/1.jpg * images/toy/ancilla/pink/10.jpg * images/toy/ancilla/pink/11.jpg * images/toy/ancilla/pink/12.jpg * images/toy/ancilla/pink/13.jpg * images/toy/ancilla/pink/14.jpg * images/toy/ancilla/pink/15.jpg * images/toy/ancilla/pink/2.jpg * images/toy/ancilla/pink/3.jpg * images/toy/ancilla/pink/4.jpg * images/toy/ancilla/pink/5.jpg * images/toy/ancilla/pink/6.jpg * images/toy/ancilla/pink/7.jpg * images/toy/ancilla/pink/8.jpg * images/toy/ancilla/pink/9.jpg * images/toy/ancilla/pink/tags * images/toy/ancilla/lounge.jpg * images/toy/ancilla/person.groovy * images/toy/domme.groovy * sounds/toy/165bpm.mp3 * sounds/toy/180bpm.mp3 * sounds/toy/240bpm.mp3 * sounds/toy/30bpm.mp3 * sounds/toy/90bpm.mp3 */