// // 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"; // 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"; final LUNCH = "lunch", SHOPPING = "shopping", PARTY = "party", SLEEPING = "sleeping"; final BEDTIME = "bedtime", WAKEUP = "wakeup", BEDTIMECHECK = "bedtimeCheck"; final BEGINPLAN = "beginPlan", ENDPLAN = "endPlan"; final FRIENDS = [ "Tori", "Sophie", "Krystal" ]; // toy.position final KNEELING = "kneeling", ALLFOURS = "allfours", STANDING = "standing"; def OWNER = null; def DOMME = null; final SITTER = "sarah-james"; final SOFT = "soft"; 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"; final IMAGEDATA = { return new File("$DATAFOLDER/images/toy") .listFiles() .findAll { d -> d.isDirectory(); } .collectEntries { d -> [ d.getName(), [ domme: d.getName(), sets: d.listFiles() .findAll { s -> s.isDirectory(); } .collect { s -> [ sub: s, f: new File(s.getPath() + "/tags")] } .findAll { s -> s.f.canRead() } .collectEntries { s -> [ s.sub.getName(), [ set: s.sub.getName(), images: s.f .readLines() .collect { l -> def fields = l.split(":"); return [ image: fields[0], tags: fields[1] .split(",") .findAll { t -> !t.isEmpty() } ]; } ] ]} ] ] }; }(); 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); }; return [ "$DATAFOLDER/images/toy/domme.groovy", "$DATAFOLDER/images/toy/$domme/person.groovy", "$DATAFOLDER/images/toy/$domme/$set/set.groovy" ] .collect { readDomme(it) } .sum() }; def selectImage = { domme, set, spec -> def meets = { i -> return spec.every({ s -> return (s[0] == "!") ? i.tags.indexOf(s.drop(1)) == -1 : i.tags.indexOf(s) != -1; }); }; def matches = IMAGEDATA[domme]; if (!matches) return null; matches = matches.sets[set]; if (!matches) return null; matches = matches .images.findAll({ i -> meets(i) }); if (matches.isEmpty()) return null; return matches[getRandom(matches.size())]; }; def showImage = { spec -> def outfit = loadString("toy.owner.outfit"); def image = selectImage(OWNER, outfit, spec); if (image) { setImage("toy/$OWNER/$outfit/${image.image}.jpg"); return image.tags; } else { setImage(null); return null; } }; def showLounge = { setImage("toy/$OWNER/lounge.jpg"); }; def selectImageSet = { domme, specs -> def matches = IMAGEDATA[domme]; if (!matches) return null; matches = matches.sets.values().findAll({ s -> specs.every({ spec -> selectImage(domme, s.set, spec)})}); return matches[getRandom(matches.size())]; }; def dress = { specs -> def outfit = loadString("toy.owner.outfit"); def outfitTime = loadInteger("toy.owner.outfitTime"); if (outfitTime > getTime() - 7200) { // Recent, check def prev = IMAGEDATA[OWNER]; if (prev) { prev = prev.sets[outfit]; if (prev && specs.every({ spec -> selectImage(OWNER, prev.set, spec)})) { DOMME = loadDomme(OWNER, outfit); return outfit; } } } outfit = selectImageSet(OWNER, specs); if (!outfit) { showPopup("No outfit for $OWNER : $specs"); save("toy.owner.outfit", null); save("toy.owner.outfitTime", null); DOMME = loadDomme(OWNER, outfit); return; } save("toy.owner.outfit", outfit.set); save("toy.owner.outfitTime", getTime()); DOMME = loadDomme(OWNER, outfit.set); }; // 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 (getTime() / DAY) * DAY; } public T getProp(String i, java.util.function.Function f, T d = null) { final T v = f("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", "") } boolean loadB(String p) { loadBoolean(p) } int loadI(String p) { loadInteger(p) } String loadS(String p ) { loadString(p) } 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) } def loadEvents = { return (loadMap("toy.events") ?: [:]); } def saveEvents = { events -> save("toy.events", events); } def nextEvent = { events -> def first = null; events.each{ k, v -> if (!first || first.event.time > v.time) { first = [ name: k, event: v ]; } }; return first; }; def setEvent = { events, name, time, func, arg = null -> if (namedEvents.containsKey(func)) { events[name] = [ time: time, func: func, arg: arg ]; saveEvents(events); return true; } else { showPopup("No such event $func"); return false; } }; def addEvent = { name, time, func, arg = null -> def events = loadEvents(); return setEvent(events, name, time, func, arg); } def addEventIfMissing = { name, time, func, arg = null -> def events = loadEvents(); if (!events.containsKey(name)) { return setEvent(events, name, time, func, arg); } }; def removeEvent = { name -> def events = loadEvents(); events.remove(name); saveEvents(events); }; def execEvents = { 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 imageTagsComment = { tags -> if (!tags || tags.isEmpty()) return null; tags = tags.intersect([ASS, CROP, BOOTS, PUSSY, SSSH]) if (tags.isEmpty()) return null; Collections.shuffle(tags); switch (tags[0]) { case ASS: if (!stateIs(GAGGED)) return compose([ ["Come closer,", "Don't be shy,"], ["kiss it!", "two kisses on each cheek."]]); else return compose([ ["Too bad you can't kiss it like that.", "Maybe I should ungag you so you can kiss it?"]]); break; case CROP: return compose([ ["You know what this is for?", "Good boys get rewards, bad ones getting a beating.", "Would you like your ass a darker shade?"]]); break; case BOOTS: if (stateIs(GAGGED)) return compose([ ["I know you'd like to worship them.", "Too bad that tongue can't be put to good use."]]); else return compose([ ["Get down there, worship my boots.", "See any dirt on my boots? Maybe you should lick them clean!"]]); case PUSSY: if (stateIs(GAGGED)) return compose([ ["I know you'd like to pleasure me.", "Too bad that tongue can't be put to good use."]]); else return compose([ ["How about you start licking it?", "How much would like your tongue in there?"]]); break; case SSSH: return compose([ ["Quiet now.", "Sssssh.", "Patience."]]); break; } return null; }; def harden = { imageSpec -> present(imageSpec, [ ["I want to see you hard.", "I need you hard now, very hard."], ["Stroke it, slowly...", "Slow worship strokes..."], ["don't edge...", "no edging..."], ["and do NOT cum.", "and definitely no cumming."], ["Not yet.", "Maybe soon."]]); playBackgroundSound("toy/90bpm.mp3", 2); //45sec if (showButtonG("Hard, ${dommeTitle()}", "hard", 60) == 60) { playBackgroundSound(null); present(imageSpec, [ ["What's taking so long!?", "Come on!", "Don't disappoint me."], ["Slap it about a bit!.", "Get it hard, now!", "Pinch your nipples."]]); playBackgroundSound("toy/165bpm.mp3"); if (showButtonG("Hard, ${dommeTitle()}", "hard", 30) == 30) { playBackgroundSound("toy/180bpm.mp3"); if (showButtonG("Hard, ${dommeTitle()}", "hard", 30) == 30) { sessionAborted = SOFT; present(imageSpec, [ ["Bah!", "Pathetic!"], ["That's no good to me!", "How do I have fun with that!?"]]); adjustPunish(100); showButtonG("Sorry, ${dommeTitle()}", "sorry"); } } } playBackgroundSound(null); }; def expose = { imageSpec -> if (!stateIs(NAKED)) { present(imageSpec, [ ["Get that cock out, toy;", "Let's see that cock of mine."], ["I want to torment it.", "It's playtime!"]]); wait(10); } }; // Return whether toy came or not def mightCum = { time, lenient = false, reason = "without permission" -> final taken = showButtonG("Sorry, ${dommeTitle()}, I'm cumming $reason", "cumming", time); if (taken < time) { playBackgroundSound(null); if (lenient && taken < 2) { present(null, [ ["Awww.", "Damn."], ["That was close;", "I'll let you have that one;"], ["And I was looking forward to punishing you.", "No punishment this time."]]); } else { present(null, [ ["Bah!", "Pfft. I'm very disappointed in you."], ["Ruin it.", "Don't touch it."]]); sessionAborted = CUM; adjustPunish(80); } adjustPunish(taken * 10); wait(10); present([DRESSED], [["Clean up your mess!"]]); showButtonG("Yes, ${dommeTitle()}", "ok"); wait(10); showButtonG("Cleaned up, ${dommeTitle()}, back", "back"); return true; } return false; }; // Return whether toy came or not, despite being denied def cumChanceDenied = { lenient = false -> present([TEASE], [ ["Stop!", "Nope!", "Haha! No!"], ["Let go.", "Hands off!", "Hands behind your back."], ["Not this time.", "Maaaaybe next time, toy."]]); return mightCum(15, lenient); }; def cumChanceCountdown = { present([TEASE], [ ["OK, toy, I'm going to count you down.", "Would you like a countdown, toy?"], ["Get stroking!", "Jerk it!"]]); if (mightCum(5 + getRandom(5))) return true; present([TITS], []); def numbers = ""; for (def n = 10; n > 0; n--) { numbers += "${n}... "; show(numbers); mightCum(1, false, "too soon"); if (getRandom(100) > 85 || !can(CUM)) { return cumChanceDenied(); } } present([TITS], [ ["Cum, cum", "Cum for me"], ["you little slut.", "my lucky toy.", "you dirty boy."]]); wait(10); showButtonG("Thank you, ${dommeTitle()}.", "ok"); return true; }; def cumChanceWindow = { present([TEASE], [ ["OK, toy, I'm going to give you a chance to cum.", "Would you like a small chance to cum, toy?"], ["Get stroking!", "Stroke it, you'll need to be close!"], ["You won't get long.", "You might not get long.", "Wait for my say so..."]]); if (mightCum(15 + getRandom(60), false, "too soon")) return true; if (getRandom(5) > 0 || !can(CUM)) { return cumChanceDenied(); } present([TITS], [ ["Cum, cum", "Cum for me"], ["you little slut.", "my lucky toy.", "you dirty boy."]]); def w = 4 + getRandom(4); if (showButtonG("Cumming, ${dommeTitle()}", "cumming", w) == w) { w = 3; present(null, [ ["Quickly now!", "Hurry!"]]); if (showButtonG("Cumming, ${dommeTitle()}", "cumming", w) == w) { present([TEASE], [ ["Stop!", "Let go.", "Hands off!"], ["Time's up!", "You had your chance."]]); return mightCum(15, true, "too late"); } } wait(10); showButtonG("Thank you, ${dommeTitle()}.", "ok"); return true; }; def cumChanceRuin = { present([TEASE], [ ["Stroke!", "Jerk it."], ["Get close to the edge, toy...", "Get yourself close..."]]); if (mightCum(5 + getRandom(10), false, "too soon")) return true; if (getRandom(5) > 0 || !can(CUM)) { return cumChanceDenied(); } for (def n = 3 + getRandom(6); n >= 0; n--) { present([TITS], [ ["Try to cum!"]]); def w = 3 + getRandom(8); if (showButtonG("Cumming, ${dommeTitle()}", "cumming", w) < w) { present([TEASE], [ ["Hands off!", "Leave it."], ["Let it all ooze out.", "You're not getting a proper release."]]); wait(10); showButtonG("Thank you, ${dommeTitle()}.", "ok"); return true; } if (n > 0) { present([TITS], [ ["Stop!", "Not now!", "Wait!"], ["Hands off!", "Hands by your side."]]); w = (w - 2) + getRandom(8); if (mightCum(w, true, "but it's ruined")) return true; } } return cumChanceDenied(true); }; def edge = { amount, imageSpec -> def allowedTime = 128; (getRandom(amount) + 2).times { if (sessionAborted) return; present(imageSpec, [ ["Stroke to edge now, toy...", "Edge!"], ["Don't cum.", "No cumming.", "No accidents though."]]); showButtonGT("Edging, ${dommeTitle()}", "edging", allowedTime + 20, 1); if (getRandom(2) == 1) { present(imageSpec, [ ["Hold it...", "And hold...", "Keeping going..."]]); if (mightCum(getRandom(20) + 5)) return; } present(imageSpec, [ ["Hands off!", "Stop!"]]); if (mightCum(getRandom(5) + 5)) return; allowedTime /= 2; }; }; def strokes = { amount, imageSpec -> def tags = present(imageSpec, [ ["Stroke to the beat, toy.", "Follow the beat.", "Beat it!"], ["No cumming unless I say so...", "Don't cum without my say so,"], ["Tell me if you get too close.", "so tell me if you're edging.", "I don't want any messes."]]); final BEATS = [ // bpm:30, len:60 ], [ bpm:90, len:45 ], [ bpm:165, len:30 ], [ bpm:180, len:30 ], [ bpm:240, len:20 ], ]; def sto = loadInteger("toy.strokeTeaseOffset"); def edges = 0; def itr = 0; (getRandom(amount) + 5).times { if (sessionAborted) return; switch (itr) { case 2: final cmt = imageTagsComment(tags); if (cmt) { show(cmt); } break; case 4: present(null, [ ["Don't stop stroking!", "Keep jerking.", "Keep stroking."]]); break; } itr += 1; def beatNo = getRandom(BEATS.size()); def beat = BEATS[beatNo]; playBackgroundSound("toy/${beat.bpm}bpm.mp3", 10); // Lots, to cover offset def len = getRandom(Math.max(5, beat.len + (sto * beatNo))); if (showButtonG("Edging, ${dommeTitle()}", "edging", len) < len) { sto -= 1; edges += 1; itr = 0; playBackgroundSound(null); switch (getRandom(3)) { case 0: present(imageSpec, [ ["Stop!", "Let go!", "Hands off!"]]); if (mightCum(getRandom(8) + 10)) return; break; case 1: present(imageSpec, [ ["Careful now...", "No accidents..."], ["slow it down...", "slowly now..."]]); playBackgroundSound("toy/30bpm.mp3"); if (mightCum(getRandom(20) + 5)) { playBackgroundSound(null); return; } case 2: switch(getSelectedValue("Would you like to cum, toy?", [ gagText("Please, ${dommeTitle()}, may I cum!?", "yes"), gagText("No, ${dommeTitle()}, please torment me more!", "no") ])) { case 0: if (can(CUM)) { present(imageSpec, [ ["Maybe...", "Perhaps.", "We'll see."]]); } else { present(imageSpec, [ ["No you may not!", "Nope.", "Not a chance."]]); adjustPunish(5); } break; case 1: present(imageSpec, [ ["Good boy.", "Very well.", "OK then!"]]); adjustPunish(-5); break; } if (mightCum(getRandom(8) + 5)) { playBackgroundSound(null); return; } playBackgroundSound(null); } tags = present(imageSpec, [ ["Back to stroking, toy...", "Get to it again...."], ["follow the beat..."], ["no accidents.", "and concentrate."]]); } else { playBackgroundSound(null); } } if (edges == 0) { sto += 1; } save("toy.strokeTeaseOffset", sto); } 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 preRelease = { if (!stateIs(CHASTE)) return; if (!stateIs(NAKED)) { present([DRESSED], [ ["Reach down... and through your clothes...", "Stay clothed,"], ["rub that chastity device...", "run your hand around that locked cock..."], ["imagine if that was me doing it.", "that looks frustrating."]]); wait(15); present(null, [["Show me.", "Let me see it."]]); wait(10); } else { present([DRESSED], [ ["Look what we have here.", "Hnmm, look at that."], ["Locked away and useless.", "Under lock and key as it should be."], ["Too bad I have the keys.", "And I have the keys somewhere safe."]]); wait(10); } present([DRESSED], [ ["Play with your balls,", "Tease those balls a little,"], ["I want to see how horny you are", "see how hard you can get"], ["before I let you out.", "before I hand over the keys."], ["You should be", "When you're"], ["practically breaking out,", "about to break it for me,"], ["we'll continue.", "then I'll see about letting it out."]]); showButtonG("Hard, ${dommeTitle()}, please let me out", "hard"); present([DRESSED,TEASE], [ ["OK then.", "Fine, fine."], ["I'm feeling generous, toy...", "Well, it's no good to me like that..."], ["I have the keys right here,", "I have the keys here somewhere... OK,"], ["unlock yourself and", "take your chastity device off"], ["let me see my cock.", "let's see it."]]); showButtonG("Yes, ${dommeTitle()}", "ok"); wait(15); set(CHASTE, false); showButtonGT("Thank you, ${dommeTitle()}", "ok", 80, 1); }; def preEdge = { if (stateIs(CHASTE)) return; expose([DRESSED]); harden([DRESSED]); edge(4, [DRESSED]); present([DRESSED], [ ["And relax...", "Hands off..."], ["cool down a little.", "but keep it hard for me!", "for now!"]]); wait(getRandom(5) + 5); }; def preGag = { if (stateIs(GAGGED)) return 1.2; if (!has(BALLGAG)) return; present([DRESSED], [ ["Go fetch your gag, toy...", "Bring me your gag, toy..."], ["Quickly!", "Hurry back!"]]); showButton("Yes, ${dommeTitle()}"); show(null); wait(10); showButtonGT("Back, ${dommeTitle()}", "back", 50, 2); present([DRESSED], [ ["Good boy.", "Very good."], ["Put it on..."], ["Nice and tight.", "Tight!"], ["I don't want to a hear a word from you.", "A quiet toy is a good toy."]]); set(GAGGED, true); showButtonGT("Gagged, ${dommeTitle()}", "gagged", 20, 2); sessionToys[BALLGAG] = getTime(); return 1.2; }; def clamps = { if (stateIs(CLAMPED)) return; if (!has(CLAMPS)) return; present([DRESSED], likes(PAIN) ? [ ["We both like to see you suffer.", "Pain is fun."], ["Isn't that right, toy?", "Don't you agree?"]] : [ ["I'm sorry, toy,"], ["but seeing you suffer is too much fun.", "but I need to find my amusement somewhere."]]); wait(10); present([DRESSED],[ ["Go put your nipple clamps on...", "I want those nipples clamped..."], ["but on your way back...", "no walking though..."], ["crawl, down on all fours.", "on your knees."]]); showButtonG("Yes, ${dommeTitle()}", "ok"); show(null); wait(10); set(CLAMPED, true); showButtonGT("Back, ${dommeTitle()}", "back", 60, 1); sessionToys[CLAMPS] = getTime(); present([DRESSED], [ ["On your knees,", "Kneel before me"], ["let me see.", "hands behind your back."]]); wait(getRandom(10) + 5); }; def clampsShow = { present([DRESSED,TEASE], [ ["Let me see", "Show me"], ["those nipple clamps,", "those nasty clamps,"], ["they look painful.", "and jiggle them about for me!"]]); wait(10); }; def preClamps = { if (!has(CLAMPS)) return; if (stateIs(CLAMPED)) { clampsShow(); } else { clamps(); } clampPulls(getRandom(4)); return 1.8; }; def preCollar = { if (stateIs(COLLARED)) return 1.1; if (!has(COLLAR)) return; present([DRESSED], [ ["You look underdressed, toy.", "A toy without a collar doesn't look right."], ["Go put your collar on", "I want a collar around your neck"], ["and crawl back like the slutty puppy dog you are.", "and kneel before me."]]); showButtonG("Yes, ${dommeTitle()}", "ok"); show(null); wait(10); set(COLLARED, true); showButtonGT("Back, ${dommeTitle()}", "back", 60, 1); sessionToys[COLLAR] = getTime(); return 1.1; }; 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); return 1.1; }; // Post def postEdge = { if (stateIs(CHASTE)) return; if (!can(EDGE)) return; edge(6, [TEASE]); }; def postCum = { if (stateIs(CHASTE)) return; present([TEASE], [ ["How about I let you cum?", "Maybe I should let you cum."], ["Would you like that?", "Let some of that frustration out?"]]); wait(getRandom(5) + 5); def ways = [cumChanceRuin, cumChanceWindow, cumChanceCountdown]; if (ways[getRandom(ways.size())]()) { // Once toy has cum, revoke permission for a while revokePermission(CUM); } }; def chastity = { pre -> if (stateIs(CHASTE)) return; if (!has(CHASTITY)) return; if (pre) { present([DRESSED,TEASE], [ ["Fun time is over for you.", "You'd have enough pleasure lately, my turn."]]); } else { present([TEASE], [ ["I can't keep an eye on you all time.", "It's not that I don't trust you."], ["But...", "I just like to be sure."]]); } wait(getRandom(5) + 10); if (sessionToys.containsKey(CHASTITY)) { present([DRESSED,TEASE], [ ["Time to", "Get your chastity device and"], ["lock my cock away again now.", "get that cock back in its cage."], ["And of course", "And then"], ["hand over the key.", "give me the key back."]]); } else { present([DRESSED,TEASE], [ ["Go to your room,", "Run along now,"], ["get your chastity device and"], ["lock it on.", "get that cock secured away."], ["Bring back the key.", "I want the key of course."]]); } wait(10); show(null); showButtonGT("Locked, ${dommeTitle()}. Here's the key.", "locked", 180, 1); set(CHASTE, true); present([DRESSED,TEASE], [ ["Good boy.", "Thank you, toy."]]); wait(5); } def preChastity = { chastity(true); } def postChastity = { chastity(false); if (stateIs(CHASTE) && (getPermission(PERM_CHASTE) == null || (getPermission(PERM_CHASTE) == false && getProp(PERM_CHASTE_ASK, loadI, 0) < getTime() - (DAY * 30)))) { present([DRESSED,TEASE], [ ["Now I have you locked up,", "Now my cock is secured away,"], ["would you let me", "can I"], ["keep you like that"], ["all the time?", "permanently?"]]); def sh = getSelectedValue(null, [ gagText("Yes, ${dommeTitle()}", "yes"), gagText("Please, ${dommeTitle()}, no", "no")]); if (sh == 0) { present([DRESSED,TEASE], [ ["Good boy.", "Thank you, toy."]]); setPermission(PERM_CHASTE, true); setProp(PERM_CHASTE_ASK, null); } else { present([DRESSED,nTEASE], [ ["Awwww.", "Shame."], ["One day..."]]); setPermission(PERM_CHASTE, false); setProp(PERM_CHASTE_ASK, getTime()); } wait(5); } }; // Play def playStrokes = { preRelease(); harden([TITS]); strokes(15, [TITS]); }; def playEdges = { preRelease(); harden([TITS]); edge(6, [TITS]); }; 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 playBeatBalls = { final BEATS = [ // bpm:30, len:60 ], [ bpm:90, len:45 ], [ bpm:165, len:30 ], [ bpm:180, len:30 ], [ bpm:240, len:20 ], ]; getSpoon(); present([DRESSED], [ ["Good.", "Right."], ["Take those balls in one hand", "In one hand, hold your balls"], ["and in the other, get ready to beat them.", "and get ready to beat them with other."]]); showButtonGT("Yes, ${dommeTitle()}", "ok", 10, 1); present([DRESSED], [ ["You concentrate on beating,", "Don't worry,"], ["I'll be keeping count.", "I'll count them for you."]]); wait(3); present(null, [["Ready"]]); wait(1); present(null, [["Set"]]); wait(1); present(null, [["Go!"]]); def beats = 0; (4 + getRandom(7)).times({ def beatNo = getRandom(BEATS.size()); def beat = BEATS[beatNo]; def len = getRandom(beat.len - 5) + 5; playBackgroundSound("toy/${beat.bpm}bpm.mp3"); wait(len); playBackgroundSound(null); beats += (int)((beat.bpm * len) / 60.0); present(null, [ ["$beats.", "That's $beats.", "$beats so far."], ["Keep going.", "Don't stop!", "Just a few more.", "Harder!"]]); }); present([DRESSED], [ ["Stop!", "Ok, you can stop now."], ["They look a little sore.", "Is that a nice tingly feeling?"]]); showButtonG("Thank you, ${dommeTitle()}.", "ok"); show(null); wait(5); return beats / 10.0; }; def playBeatCock = { preRelease(); harden([DRESSED,nTITS]); getSpoon(); present([DRESSED], [ ["Good.", "Right."], ["When I crack my whip... you beat the head my little cock."], ["Don't hold back,", "Be brave for me,"], ["it's meant to hurt!", "it's fun for me to watch."]]); wait(5); def count = 0; def n = (2 + getRandom((int)(getPunish() / 150))); n.times({ randRange(10, 20).times({ playBackgroundSound("shortwhip.wav"); count += 1; wait(randRange(3, 5)); }); if (it < n - 1) { def sh = getSelectedValue("Still hard?", [ gagText("Yes, ${dommeTitle()}", "yes"), gagText("No, ${dommeTitle()}", "no")]); if (sh == 1) { harden([DRESSED, nTITS]); } else { present([DRESSED], [ ["Good", "Good boy", "Excellent"]]); wait(randRange(3, 5)); } present([DRESSED], [ ["Again!", "More!"]]); wait(1); } }) return count * 5; }; 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 playBondage = { if (!has(HANDCUFFS)) return; def prep = [ preStrip, preCollar, preClamps, preGag ]; Collections.shuffle(prep); prep.collect { p -> p(); }; present([DRESSED, nTEASE], [ ["Go fetch your handcuffs"]]); showButtonG("Yes, ${dommeTitle()}", "ok"); wait(10); showButtonGT("Back, ${dommeTitle()}", "back", 60, 1); sessionToys[HANDCUFFS] = getTime(); present([DRESSED, nTEASE], [ ["Good."], ["Down on the floor.", "On your knees.", "Down there, where you are."]]); wait(15); present([DRESSED, nTEASE], [ ["You know what's next.", "Take your cuffs."], ["Cuff yourself,", "Hands cuffed,"], ["behind your back of course.", "behind you."]]); wait(15); present([DRESSED, TEASE], [ ["Don't go anywhere!", "Do not move!"]]); wait(15); present([STOOD], [ ["I'll be back for you soon.", "I won't leave you too long."]]); wait(15); setImage(null); show(null); def waitTime = 300 + getRandom(Math.min(1500, 1 + getPunish() * 10)); wait(waitTime); playBackgroundSound("bell.wav"); present([STOOD], [ ["Hi slave!", "Hello again."], ["You don't look very comfortable.", "That looks quite awkward."], ["Maybe you've been there long enough.", "It's been a while now."]]); wait(20); present([DRESSED, TEASE], [ ["OK,", "Mmmm,"], ["soon.", "just a while longer."]]); wait(60 + getRandom(60)); playBackgroundSound("bell.wav"); present([DRESSED, TEASE], [ ["Go find the keys,", "Very well,"], ["let yourself out,", "unlock the cuffs,"], ["and come back.", "and hurry back."]]); wait(10); setImage(null); showButtonGT("Freed, ${dommeTitle()}", "freed", 120, 1); return waitTime; }; 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)); }; 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); present([TEASE], [ ["Get ready"]]); showButtonGT("Ready, ${dommeTitle()}", "ready", 10, 1); } show(null); final target = loadInteger("toy.deepthroat.goal") ?: 5; def completed = loadInteger("toy.deepthroat.completed") ?: 0; 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 (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 playClamps = { if (!has(CLAMPS)) return; if (!stateIs(CLAMPED)) clamps(); clampsShow(); return clampPulls(3 + getRandom(10)); }; def intClamps = { if (!has(CLAMPS)) return; if (!stateIs(CLAMPED)) return; clampsShow(); return clampPulls(getRandom(5)); }; def intSqueeze = { (2 + getRandom(4)).times { n -> if (n > 0) { present([DRESSED,TEASE], [ ["And again,", "Again,", "Once more,"], ["harder!", "tighter!"]]); } else { present([DRESSED,TEASE], [ ["Squeeze yours balls", "Grab your balls and squeeze them"], ["good and tight", "firm and hard"], ["until they hurt a little.", "as if I was doing it."]]); } wait(3 + getRandom(6)); present(null, [ ["Let them go.", "Hands off."]]); wait(3 + getRandom(6)); }; }; 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 = [ 'intSqueeze': intSqueeze, 'intClamps': intClamps, 'preRelease': preRelease, 'preChastity': preChastity, 'preEdge': preEdge, 'preGag': preGag, 'preStrip': preStrip, 'preCollar': preCollar, 'preClamps': preClamps, 'playStrokes': playStrokes, 'playEdges': playEdges, 'playKneel': playKneel, 'playBeatBalls': playBeatBalls, 'playBeatCock': playBeatCock, 'playClamps': playClamps, 'playNothing': playNothing, 'playBondage': playBondage, 'playSuck': playSuck, 'postCum': postCum, 'postPermitCum': { addEventIfMissing(CUM, getTime() + (DAY * 2) + getRandom(DAY * 2), PERMIT, CUM); // 2 - 4 days from now }, 'postChastity': postChastity ]; 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); } 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 sleepSchedule = { def t = (int)(getDay() - localTimeOffset() + (25.1 * HOUR) + getRandom((int)HOUR)); addEventIfMissing(SLEEPING, t, BEDTIME); }; def bedtime = { rt, schedTime -> dress([[LINGERIE,nTITS]]); if (rt && sessionSummon([LINGERIE,nTITS])) { preStrip([LINGERIE,nTITS]); def toys = TOYTOYS.findAll { t -> stateIs(t) }; Collections.shuffle(toys); toys.each { removeToy(it, [LINGERIE,nTITS]) }; present([LINGERIE,nTITS], [ ["Very good, toy.", "OK."], ["Bedtime for you!", "Off to bed with you!", "Bed! Now!"]]); showButtonG("Good night, ${dommeTitle()}", "night"); addEvent(BEDTIMECHECK, getTime() + 300 + getRandom(600), BEDTIMECHECK); showLounge(); } // Assume toy will return dressed tomorrow removeEvent(REDRESS); set(NAKED, false); // Assume toy will release himself TOYTOYS.findAll { t -> stateIs(t) }.each { removeEvent("$RELEASEFROM-$it"); set(it, false); }; setAway(SLEEPING); def t = schedTime + (6 * (int)HOUR) + getRandom(2 * (int)HOUR); addEvent(SLEEPING, t, WAKEUP); removeEvent(PLAY); }; def bedtimeCheck = { rt -> dress([[LINGERIE,nTITS]]); if (rt && sessionSummon([LINGERIE,nTITS])) { present([LINGERIE,nTITS], [ ["Hey!", "Ah ha!"], ["What are you doing still up?", "Didn't I send you to bed?"]]); showButtonG("Sorry, ${dommeTitle()}", "sorry"); present([LINGERIE,nTITS], [ ["Get yourself to bed", "In bed"], ["right now!", "this instant!"], ["I'll deal with you later."]]); showButtonG("Yes, ${dommeTitle()}", "ok"); adjustPunish(75); showLounge(); } }; def wakeup = { rt -> // Unset outfit, stops bedtime wear always being selected first save("toy.owner.outfit", null); dress([]); setAway(null); playSchedule(); sleepSchedule(); }; 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(); }; // Confession def confessChastityRelease = { adjustPunish(75); present([DRESSED, nTEASE], [ ["Bad toy!", "Naughty little slut."], ["I don't keep it locked for nothing.", "It's locked for a reason."], ["It better have been a good reason.", "I'll presume you had a valid excuse."], ["Is it locked again now?", "Are you back in chastity now?"]]); if (getBoolean(null)) { present([DRESSED, nTEASE], [ ["Good!", "There's that at least."]]); showButtonG("Yes, ${dommeTitle()}", "ok"); } else { adjustPunish(300); present([DRESSED, nTEASE], [ ["disobedient", "dirty", "cheeky"], ["little"], ["slut!", "whore!", "brat!"], ["Get it locked back up", "Back in chastity"], ["right now!", "this instant!"]]); showButtonGT("Locked, ${dommeTitle()}. Here's the key.", "locked", 180, 1); } if (getBoolean("Did you get erect whilst out?")) { adjustPunish(50); if (getBoolean("Did you cum!?")) { adjustPunish(200); } } adjustPunish(getInteger("How many hours were you out?", 1) * 10); }; def confessStroked = { adjustPunish(40); present([DRESSED,nTEASE], [ ["Awww, poor little toy.", "Naught little toy!"], ["You know you aren't allowed.", "You get to stroke only when I say."]]); showButtonG("Sorry, ${dommeTitle()}", "sorry"); if (getBoolean("Did you get to the edge?")) { adjustPunish(40); adjustPunish(getInteger("How many times?", 1) * 5); if (getBoolean("Did you cum!?")) { adjustPunish(200); } } }; def confess = { // 5 points just for feeling the need to confess def op = getPunish(); adjustPunish(5); dress([[DRESSED,nTEASE], [MEAN]]); while (true) { present([DRESSED, nTEASE], [ ["I'm not going to like this, am I?", "There is no way this ends well for you."], ["Out with it.", "Let me hear it."]]); def opts = [ ]; if (stateIs(CHASTE)) { opts.push([lbl: "Freed from chastity", act: confessChastityRelease]); } if (!stateIs(CHASTE)) { opts.push([lbl: "Stroked", act: confessStroked]); } opts.push([ lbl: "Nothing listed (phew!)", act: null ]); def opt = getSelectedValue(null, opts.collect { it.lbl }); def act = opts[opt].act; if (!act) { showLounge(); return; } act(); if (!getBoolean("Anything else?")) break; } // If sufficient points accrued, punish immediately.. with a bonus if (getPunish() > 150 + op) { present([MEAN], [ ["I'm very disappointed in you.", "You've been a very bad toy!"], ["Immediate punishment!", "So you will suffer... right now!"]]); adjustPunish(100); showButtonG("Yes, ${dommeTitle()}", "ok"); removeEvent(PLAY); sessionPlay(); sessionRelease(sessionAborted == null); playSchedule(); } else { present([DRESSED, nTEASE], [ ["Good!", "There better not be!"]]); showButtonG("Yes, ${dommeTitle()}", "ok"); showLounge(); } }; // Setup // Plans - cannot overlap def setupPlan = { plan, float startmin, float startmax, float endmin, float endmax -> def timeWindow = { float min, float max -> (int)((min * HOUR) - localTimeOffset() + getRandom((int)((max - min) * HOUR))) }; final friendName = FRIENDS[getRandom(FRIENDS.size())]; def day = getDay() + ((getRandom(3) + 1) * DAY); addEventIfMissing(plan, day + timeWindow(startmin, startmax), BEGINPLAN, [friendName: friendName, returnTime: day + timeWindow(endmin, endmax)]); } def setupPlans = { save("toy.plan", null); setupPlan(LUNCH, 11.5, 12.5, 13.0, 14.0) setupPlan(SHOPPING, 14.5, 15.5, 16.0, 18.0) setupPlan(PARTY, 19, 20, 22.0, 25.0) }; def setupEvents = { playSchedule(); sleepSchedule(); setupPlans(); }; def setDefault = { prop, val -> if (loadString(prop) == null) { save(prop, val); } }; def leaveNote = { msg, instr -> save("toy.note", msg); save("toy.noteInstr", instr); }; def readNote = { leaveNote(null, null); }; def plannedCheck = { plan -> return (getTime() > loadInteger("toy.plan.${plan}.${START}") && getTime() < loadInteger("toy.plan.${plan}.${END}")); } def beginPlan = { rt, plan, args -> addEvent(plan, args.returnTime, ENDPLAN, args.friendName); dress([[DRESSED,nTEASE], [DRESSED,TEASE]]); if (rt && sessionSummon([DRESSED,nTEASE])) { present([DRESSED,nTEASE], [ [ "I'm going out now, toy.", "$plan time!" ], [ "Will you be good while I'm gone?", "You will behave as I expect?" ]]); wait(10); postChastity(); def toys = TOYTOYS.findAll { t -> stateIs(t) }; Collections.shuffle(toys); toys.each { removeToy(it, [DRESSED,nTEASE]) }; present([DRESSED,TEASE], [ [ "Back later!", "See you soon, toy" ]]); wait(5); } else if (has(CHASTITY) && !stateIs(CHASTE)) { leaveNote("I want you in chastity until I return!", CHASTE); } DOMME = loadDomme(OWNER); setAway(plan); removeEvent(PLAY); showLounge(); }; def endPlan = { rt, plan -> save("toy.owner.outfitTime", getTime()); setAway(null); if (rt && sessionSummon([DRESSED])) { present([DRESSED], [["Hello, toy!"], ["I'm back home now."]]); wait(10); showLounge(); } readNote(); playSchedule(); setupPlans(); }; // Initial - basic checks and defaults def namedEvents = [ bedtime: { name, arg, schedTime, rt -> bedtime(rt, schedTime) }, bedtimeCheck: { name, arg, schedTime, rt -> bedtimeCheck(rt) }, wakeup: { name, arg, schedTime, rt -> wakeup(rt) }, beginPlan: { name, arg, schedTime, rt -> beginPlan(rt, name, arg) }, endPlan: { name, arg, schedTime, rt -> endPlan(rt, name) }, 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 setupInitial = { if (loadInteger("toy.version") == null) { // Welcome dress([[DRESSED,nTEASE], [DRESSED,TEASE], [TITS]]); def name = loadString("intro.name"); present([DRESSED,nTEASE], [ ["Welcome, $name, to your new life as my plaything, my toy."]]); showButton("Thank you, ${dommeTitle()}"); present([DRESSED,nTEASE], [ ["My rules are simple, do what I say, when I say, and you'll be rewarded... eventually."]]); showButton("Thank you, ${dommeTitle()}"); present([DRESSED,nTEASE], [ ["But if you disappointment me, disobey me or disrespect me, then you'll be punished."]]); showButton("Yes, ${dommeTitle()}"); present([DRESSED,TEASE], [ ["I will tease you... and I will torment you..."]]); showButton("Yes, ${dommeTitle()}"); present([TITS], [ ["... and I will break... and then you will suffer!"]]); showButton("Thank you, ${dommeTitle()}"); present([TITS], [ ["Now, your devotion is important to me."], ["I have expectations my toy be available to play with as much as possible."]]); showButton("Yes, ${dommeTitle()}"); present([TITS], [ ["You can indicate your availability by running this script."], ["I will summon you when I wish to play."]]); showButton("Understood, ${dommeTitle()}"); present([DRESSED,TEASE], [ ["Would you like to set this script as default?"]]); if (getBoolean(null)) { save("intro.start_script", "toy"); present([DRESSED,nTEASE], [ ["Good boy."]]); showButton("Thank you, ${dommeTitle()}"); } present([DRESSED,nTEASE], [ ["Run along now, toy."], ["I'll summon you with a bell when I want you."], ["Lisen carefully!"]]); showButton("Yes, ${dommeTitle()}"); present(null, [ ["All images are the property of their respective owners, see watermarks (OnlyTease, ForeverVamp).\n"], ["Don't do anything you aren't 100% comfortable doing.\n"], ["Be sure to have configured your toys!"]]); showButton("OK"); } save("toy.version", VERSION); setDefault("toy.strokeTeaseOffset", 0); setDefault("toy.punishment", 0); execEvents(false); setupEvents(); }; def setupShowState = { 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/"); }; def setupShowCalendar = { final events = loadEvents(); show( [LUNCH, SHOPPING, PARTY].collect { final event = events[it]; if (!event) return ""; def s = event.time; def f = event.arg.friendName; def timeStr = localTimeOf(s).format(soonFormatter); return "$it with $f on $timeStr.\n".capitalize(); }.join()); showButton("OK"); }; String main() { // GO! setDefault("toy.owner", "ancilla"); OWNER = loadString("toy.owner"); DOMME = loadDomme(OWNER); 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); }; setupInitial(); 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 = [ [ lbl: "FAQ (webpage)", act: showFaq ], [ lbl: "Calendar", act: setupShowCalendar ], [ lbl: "Confess", act: confess ], ]; 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 * 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 */