// // 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"]); 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"; // fetish. final BONDAGE = "bondage", CBT = "cbt", CHORES = "chores", PAIN = "pain"; // toy.permission. final CUM = "cum", EDGE = "edge" final PERMIT = "permit"; final PLAY = "play"; // toy.state. final CHASTE = "chaste", COLLARED = "collared", CUFFED = "cuffed", CLAMPED = "clamped", GAGGED = "gagged", NAKED = "naked"; final REDRESS = "redress"; // toy.plan..{start|end|friend} UTC times final LUNCH = "lunch", SHOPPING = "shopping", PARTY = "party"; final START = "start", END = "end", FRIEND = "friend"; final FRIENDS = [ "Tori", "Sophie", "Krystal" ]; // toy.position final KNEELING = "kneeling", ALLFOURS = "allfours", STANDING = "standing"; def OWNER = null; final SITTER = "sarah-james"; final SOFT = "soft"; // 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(getDataFolder() + "/images/toy") .listFiles() .findAll { d -> d.isDirectory(); } .collect { d -> [ domme: d.getName(), sets: d.listFiles() .findAll { s -> s.isDirectory(); } .collect { s -> [ set: s.getName(), images: new File(s.getPath() + "/tags") .readLines() .collect { l -> def fields = l.split(":"); return [ image: fields[0], tags: fields[1] .split(",") .findAll { t -> !t.isEmpty() } ]; } ] } ] }; }(); 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 .find({ d -> d.domme == domme }); if (!matches) return null; matches = matches .sets.find({ s -> s.set == 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 .find({ d -> d.domme == domme }) .sets.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 .find({ d -> d.domme == OWNER }) .sets.find({ s -> s.set == outfit }); if (prev && specs.every({ spec -> selectImage(OWNER, prev.set, spec)})) { return outfit; } } outfit = selectImageSet(OWNER, specs); if (!outfit) { showPopup("No outfit for $OWNER : $specs"); } save("toy.owner.outfit", outfit.set); save("toy.owner.outfitTime", getTime()); }; // Utils def localTimeOffset = { return java.time.ZoneId.systemDefault() .getRules() .getOffset(java.time.Instant.now()) .getTotalSeconds(); }; def localTimeOf = { s -> java.time.LocalDateTime.ofInstant( java.time.Instant.ofEpochSecond(s), java.time.ZoneId.systemDefault()) } def localTime = { // A float between 4.0 (4am) and 28.0 (4am) def wallclock = ((getTime() + localTimeOffset()) % DAY) / HOUR; if (wallclock < 4) { wallclock += 24; } return wallclock; }; def getDay = { (int)(getTime() / DAY) * DAY }; def has = {i -> loadBoolean("toys.$i") == true}; def likes = {i -> loadBoolean("fetish.$i") == true}; def can = {i -> loadBoolean("toy.permission.$i") == true}; def givePermission = {i -> save("toy.permission.$i", true)}; def revokePermission = {i -> save("toy.permission.$i", false)}; def is = {i -> loadBoolean("toy.state.$i") == true}; def set = {i, s -> save("toy.state.$i", s)}; def positioned = { i -> loadString("toy.position") == i }; def getPunish = { loadInteger("toy.punishment") ?: 0; }; def adjustPunish = { p -> save("toy.punishment", Math.max(getPunish() + (int)p, 0)); }; def namedEvents; 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 -> def events = loadEvents(); events .findAll({ e -> e.value.time <= getTime() }) .each({ e -> def f = namedEvents[e.value.func]; events.remove(e.key); saveEvents(events); if (f) { f(e.value.arg, e.value.time, rt); } }) }; def sessionAborted = null; def gagText = { t, p -> if (!is(GAGGED)) return t; 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 present = { imageSpec, texts -> if (texts) { show(texts.collect { t -> t[getRandom(t.size())] }.join(" ").capitalize()); } 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 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, mistress", "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, mistress", "hard", 30) == 30) { playBackgroundSound("toy/180bpm.mp3"); if (showButtonG("Hard, mistress", "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, mistress", "sorry"); } } } playBackgroundSound(null); }; def expose = { imageSpec -> if (!is(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, reason = "without permission" -> if (showButtonG("Sorry, mistress, I'm cumming $reason", "cumming", time) < time) { playBackgroundSound(null); sessionAborted = CUM; present(null, [ ["Bah!", "Pfft. I'm very disappointed in you."], ["Ruin it.", "Don't touch it."]]); adjustPunish(100); wait(10); present([DRESSED], [["Clean up your mess!"]]); showButtonG("Yes, mistress", "ok"); wait(10); showButtonG("Cleaned up, mistress, back", "back"); return true; } return false; }; // Return whether toy came or not, despite being denied def cumChanceDenied = { present([TEASE], [ ["Stop!", "Nope!", "Haha! No!"], ["Let go.", "Hands off!", "Hands behind your back."], ["Not this time.", "Maaaaybe next time, toy."]]); return mightCum(15); }; 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, "too soon"); if (getRandom(100) > 85) { return cumChanceDenied(); } } present([TITS], [ ["Cum, cum", "Cum for me"], ["you little slut.", "my lucky toy.", "you dirty boy."]]); wait(10); showButtonG("Thank you, mistress.", "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), "too soon")) return true; if (getRandom(5) > 0) { 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, mistress", "cumming", w) == w) { w = 3; present(null, [ ["Quickly now!", "Hurry!"]]); if (showButtonG("Cumming, mistress", "cumming", w) == w) { present([TEASE], [ ["Stop!", "Let go.", "Hands off!"], ["Time's up!", "You had your chance."]]); return mightCum(15, "too late"); } } wait(10); showButtonG("Thank you, mistress.", "ok"); return true; }; def cumChanceRuin = { present([TEASE], [ ["Stroke!", "Jerk it."], ["Get close to the edge, toy...", "Get yourself close..."]]); if (mightCum(5 + getRandom(10), "too soon")) return true; for (def n = 3 + getRandom(6); n >= 0; n--) { present([TITS], [ ["Try to cum!"]]); def w = 3 + getRandom(8); if (showButtonG("Cumming, mistress", "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, mistress.", "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, "but it's ruined")) return true; } } return cumChanceDenied(); }; 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, mistress", "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 -> 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; (getRandom(amount) + 5).times { if (sessionAborted) return; 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(1, beat.len + (sto * beatNo))); if (showButtonG("Edging, mistress", "edging", len) < len) { sto -= 1; edges += 1; 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, mistress, may I cum!?", "yes"), gagText("No, mistress, 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); } 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(3 + 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(3 + getRandom(8)); } else if (getRandom(2)) { present([DRESSED], [ ["Twist them"], ["for me.", "... twist those nipples."]]); wait(3 + getRandom(8)); 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, [ ["And release.", "Let go."]]); wait(getRandom(5) + 3); }; return amount; }; // Pre-tease def preRelease = { if (!is(CHASTE)) return; if (!is(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 you 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, mistress, 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, mistress", "ok"); wait(15); set(CHASTE, false); showButtonGT("Thank you, mistress", "ok", 80, 1); }; def preEdge = { if (is(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 (is(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, mistress"); show(null); wait(10); showButtonGT("Back, mistress", "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, mistress", "gagged", 20, 2); return 1.2; }; def clamps = { if (is(CLAMPED)) 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, mistress", "ok"); show(null); wait(10); set(CLAMPED, true); showButtonGT("Back, mistress", "back", 60, 1); present([DRESSED], [ ["On your knees,", "Kneel before me"], ["let me see.", "hands behind your back."]]); wait(getRandom(10) + 5); }; def preClamps = { clamps(); clampPulls(getRandom(4)); return 1.8; }; def preCollar = { if (is(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, mistress", "ok"); show(null); wait(10); set(COLLARED, true); showButtonGT("Back, mistress", "back", 60, 1); return 1.1; }; def preStrip = { imageSpec = [DRESSED] -> if (is(NAKED)) return 1.1; present(imageSpec, [ ["Clothes off!", "Get naked, slut!"]]); wait(10); present([DRESSED], [ ["All of them off!", "And the rest!", "Keep going."]]); set(NAKED, true); showButtonGT("Naked, mistress", "naked", 60, 5); (getRandom(2) + 1).times { switch (getRandom(1)) { case 0: present(null, [ ["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."]]); break; case 1: present(null, [ ["Very good. Now...", "I see..."], ["on your knees before me,", "kneel,"], ["don't move those hands.", "hands still."]]); break; } wait(10); }; return 1.1; }; // Post def postEdge = { if (is(CHASTE)) return; if (!can(EDGE)) return; edge(6, [TEASE]); }; def postCum = { if (is(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); if (!can(CUM)) { present([TEASE], [ ["Or not.", "No, not right now.", "Maybe later.", "Not yet."]]); wait(getRandom(5) + 5); return; } 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 (is(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); 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, mistress. 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); }; // Play def playStrokes = { preRelease(); harden([TITS]); strokes(15, [TITS]); }; def playEdges = { preRelease(); harden([TITS]); edge(6, [TITS]); }; def playNothing = { (getRandom(3) + 2).times { def tags = showImage([TITS]).intersect( [ASS, CROP, BOOTS, PUSSY, SSSH]); Collections.shuffle(tags); if (tags.isEmpty()) { show(null); } else { switch (tags[0]) { case ASS: if (!is(GAGGED)) present(null, [ ["Come closer,", "Don't be shy,"], ["kiss it!", "two kisses on each cheek."]]); break; case CROP: present(null, [ ["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 (is(GAGGED)) present(null, [ ["I know you'd like to worship them.", "Too bad that tongue can't be put to good use."]]); else break; case PUSSY: if (is(GAGGED)) present(null, [ ["I know you'd like to pleasure me.", "Too bad that tongue can't be put to good use."]]); else present(null, [ ["How about you start licking it?", "How much would like your tongue in there?"]]); break; case SSSH: present(null, [ ["Quiet now.", "Sssssh.", "Patience."]]); break; default: show(null); } } wait(getRandom(10) + 10); }; }; def playCbt = { present([DRESSED], [ ["Go to the kitchen...", "From the kitchen..."], ["bring me", "get yourself"], ["a wooden spoon or similar."]]); showButtonG("Yes, mistress", "ok"); wait(5); final BEATS = [ // bpm:30, len:60 ], [ bpm:90, len:45 ], [ bpm:165, len:30 ], [ bpm:180, len:30 ], [ bpm:240, len:20 ], ]; showButtonGT("Back, mistress", "back", 20, 1); 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, mistress", "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, mistress.", "ok"); show(null); wait(5); return beats / 10.0; }; def playKneel = { present([DRESSED], [ ["Right, slut.", "Come here, toy."], ["On the floor, right there.", "Kneel at my feet."]]); showButtonGT("Yes, mistress.", "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, mistress.", "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); waitWithGauge(kneelTime); while (playBackgroundSound("bell.wav") || true) { if (showButtonG("Back, mistress", "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, mistress", "ok"); wait(10); showButtonGT("Back, mistress", "back", 60, 1); 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, TITS], [ ["I'll be back for you soon.", "I won't leave you too long."]]); wait(15); setImage(null); show(null); def waitTime = 500 + getRandom(1000); waitWithGauge(waitTime); playBackgroundSound("bell.wav"); present([DRESSED, nTEASE], [ ["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, mistress", "freed", 120, 1); return waitTime; }; def playClamps = { if (!has(CLAMPS)) return; if (!is(CLAMPED)) clamps(); present([DRESSED], [ ["Let me see", "Show me"], ["those nipple clamps", "those nasty clamps"], ["they look painful.", "and jiggle them about for me!"]]); wait(10); return clampPulls(3 + getRandom(10)); }; // 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, Miss?", timeMax) < timeMax) { return true; }; } showLounge(); show(null); adjustPunish(5); return false; }; def sessionPlay = { def ph = (float)1.0; def pm = { val -> if (val) { ph *= val; } }; def ap = { val -> if (val) { adjustPunish(-val * ph); } }; def apply = { activitity, use = null -> if (!activitity) return; def val = (Float)activitity(); if (use) { use(val); } }; def playRepeat = { amount, activities, use = null -> amount.times { if (!sessionAborted) { apply(activities[getRandom(activities.size())], use); } }; }; def playTake = { amount, activities, use = null -> Collections.shuffle(activities); activities.take(amount); activities.each { if (!sessionAborted) { apply(it, use); } }; }; def cp = getPunish(); if (cp > 50) { // Punish only: bad toy preChastity(); def pr = 1 + getRandom(2 + (int)(cp / 100)); playRepeat(pr, [preGag, preClamps, preCollar, preStrip], pm); playRepeat(pr, [playKneel, playCbt, playClamps, playNothing], ap); playTake(pr, [playBondage, playCbt, playClamps]); } else { // Sub pleasure: good toy playRepeat(1 + getRandom(3), [preRelease, preEdge, preGag, preClamps, preCollar, preStrip]); playRepeat(1 + getRandom(3), [playStrokes, playEdges]); playTake(getRandom(3), [postCum, postChastity]); if (!sessionAborted) { addEventIfMissing(CUM, getTime() + (DAY * 2) + getRandom(DAY * 2), PERMIT, CUM); // 2 - 4 days from now } } }; 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."]]); postChastity(); } wait(getRandom(10) + 5); if (is(CUFFED) || is(COLLARED) || is(CLAMPED) || is(GAGGED)) { 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); } present([DRESSED], [ ["You may release yourself", "Free yourself"]]); wait(getRandom(5) + 5); set(CUFFED, false); set(COLLARED, false); set(CLAMPED, false); set(GAGGED, false); } if (is(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, mistress", "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); showLounge(); sessionAborted = null; if (goodToy) { adjustPunish(-20); } show(null); }; 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, mistress", "ok", 10, 1); showLounge(); show(null); wait(20 + getRandom(20)); }; def playSchedule = { lastPlay -> 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) return; dress([[DRESSED,nTEASE],[DRESSED,TEASE],[TITS]]); if (sessionSummon([DRESSED,nTEASE])) { if (first.asBoolean()) { playWait(); } sessionPlay(); sessionRelease(sessionAborted == null); save("toy.lastPlay", getTime()); } else { adjustPunish(25); } playSchedule(getTime()); showLounge(); } def bedtime = { dress([[LINGERIE,nTITS]]); if (sessionSummon([LINGERIE,nTITS])) { preStrip([LINGERIE,nTITS]); present([LINGERIE,nTITS], [ ["Very good, toy.", "OK"], ["Bedtime!", "Off to bed with you!", "Bed! Now!"]]); showButtonG("Good night, mistress", "night"); } // Assume toy with return dressed tomorrow removeEvent(REDRESS); set(NAKED, false); exit(); }; 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, mistress", "ok"); set(NAKED, false); 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, mistress", "ok"); }; // 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, mistress", "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, mistress. 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, mistress", "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,CROP]]); 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 (is(CHASTE)) { opts.push([lbl: "Freed from chastity", act: confessChastityRelease]); } if (!is(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) return; act(); if (!getBoolean("Anything else?")) break; } // If sufficient points accrued, punish immediately.. with a bonus if (getPunish() > 150 + op) { present([MEAN,CROP], [ ["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, mistress", "ok"); sessionPlay(); sessionRelease(sessionAborted == null); } else { present([DRESSED, nTEASE], [ ["Good!", "There better not be!"]]); showButtonG("Yes, mistress", "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) + getRandom((int)((max - min) * HOUR))) }; // Unplanned or past if (getTime() > loadInteger("toy.plan.${plan}.${END}")) { def day = getDay() + ((getRandom(3) + 1) * DAY); save("toy.plan.${plan}.${FRIEND}", FRIENDS[getRandom(FRIENDS.size())]); save("toy.plan.${plan}.${START}", day + timeWindow(startmin, startmax)); save("toy.plan.${plan}.${END}", day + timeWindow(endmin, endmax)); } } def setupPlans = { 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(loadInteger("toy.lastPlay") ?: 0); }; 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 = { plan -> dress([[DRESSED,nTEASE], [DRESSED,TEASE]]); save("toy.owner.outfitTime", loadInteger("toy.plan.${plan}.${END}")); if (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(); present([DRESSED,TEASE], [ [ "Back later!", "See you soon, toy" ]]); wait(5); exit(); } if (has(CHASTITY)) { leaveNote("I want you in chastity until I return!", CHASTE); } adjustPunish(5); exit(); }; // Initial - basic checks and defaults namedEvents = [ // name: func(arg, schedTime rt) play: { arg, schedTime, rt -> playEvent(rt, arg) }, redress: { arg, schedTime, rt -> redress(rt) }, permit: { 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, mistress"); present([DRESSED,nTEASE], [ ["My rules are simple, do what I say, when I say, and you'll be rewarded... eventually."]]); showButton("Thank you, mistress"); present([DRESSED,nTEASE], [ ["But if you disappointment me, disobey me or disrespect me, then you'll be punished."]]); showButton("Yes, mistress"); present([DRESSED,TEASE], [ ["I will tease you... and I will torment you..."]]); showButton("Yes, mistress"); present([TITS], [ ["... and I will break... and then you will suffer!"]]); showButton("Thank you, mistress"); 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, mistress"); present([TITS], [ ["You can indicate your availability by running this script."], ["I will summon you when I wish to play."]]); showButton("Understood, mistress"); 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, mistress"); } present([DRESSED,nTEASE], [ ["Run along now, toy."], ["I'll summon you with a bell when I want you."], ["Lisen carefully!"]]); showButton("Yes, mistress"); 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); setupPlans(); execEvents(false); setupEvents(); }; def setupShowState = { show( [CHASTE, COLLARED, CUFFED, CLAMPED, GAGGED, NAKED].collect { def v = is(it); return "$it: $v\n".capitalize() }.join()); showButton("OK"); show( [BALLGAG, COLLAR, CLAMPS, CHASTITY, HANDCUFFS].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 setupShowCalendar = { show( [LUNCH, SHOPPING, PARTY].collect { def s = loadInteger("toy.plan.${it}.${START}"); def f = loadString("toy.plan.${it}.${FRIEND}"); def timeStr = localTimeOf(s).format(soonFormatter); return "$it with $f on $timeStr.\n".capitalize(); }.join()); showButton("OK"); }; // GO! setDefault("toy.owner", "ancilla"); OWNER = loadString("toy.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", getAvail(dayNum) + amount); }; if (localTime() > 25.75 || localTime() < 7.5) { // 1:45am - 7:30am show("Mistress is sleeping..."); wait(10); exit(); } if (plannedCheck(LUNCH) || plannedCheck(SHOPPING) || plannedCheck(PARTY)) { showLounge(); def note = loadString("toy.note"); def noteInstr = loadString("toy.noteInstr"); if (note) { show("Mistress is out... but you find a note:\n$note"); showButton("OK"); if (noteInstr) { set(noteInstr, true); } readNote(); } else { show("Mistress is out..."); wait(10); } while (plannedCheck(LUNCH) || plannedCheck(SHOPPING) || plannedCheck(PARTY)) { wait(5); } execEvents(false); if (sessionSummon([DRESSED])) { present([DRESSED], [["Hello, toy!"], ["I'm back home now."]]); wait(10); showLounge(); } } else { leaveNote(null, null); } setupInitial(); final cycleTime = 6; showLounge(); while (true) { show("Mistress will summon you when required."); if (showButton("Please, Miss?", cycleTime) < cycleTime) { def opts = [ [ lbl: "Status", act: setupShowState ], [ lbl: "Calendar", act: setupShowCalendar ], [ lbl: "Confess", act: confess ], [ lbl: "Back", act: null ], ]; if (is("DEBUG")) { opts.push([ lbl: "Play", act: playEvent, arg: true ]); opts.push([ lbl: "Begin plan", act: beginPlan, arg: LUNCH ]); } if (is(NAKED)) { opts.push([ lbl: "May I wear clothes", act: requestClothes ]); } def opt = getSelectedValue("Yes, toy?", opts.collect { it.lbl }); def act = opts[opt].act; if (act) act(opts[opt].arg); else return "toy"; } else if (localTime() > 25.75) { // 1:45am bedtime(); } else if (plannedCheck(LUNCH)) { beginPlan(LUNCH); } else if (plannedCheck(SHOPPING)) { beginPlan(SHOPPING); } else if (plannedCheck(PARTY)) { beginPlan(PARTY); } execEvents(true); addAvail(cycleTime); } /* * 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 * sounds/toy/165bpm.mp3 * sounds/toy/180bpm.mp3 * sounds/toy/240bpm.mp3 * sounds/toy/30bpm.mp3 * sounds/toy/90bpm.mp3 */