diff options
| author | Dan Goodliffe <dan@randomdan.homeip.net> | 2018-11-27 20:03:53 +0000 | 
|---|---|---|
| committer | Dan Goodliffe <dan@randomdan.homeip.net> | 2018-11-27 21:57:18 +0000 | 
| commit | 9a2ef23cedb65c1947f17d28f847d6b8d183170f (patch) | |
| tree | 6a298f8ed6270fc9d9fd4e79710f61e08d987703 /scripts/toy.groovy | |
| download | toy-9a2ef23cedb65c1947f17d28f847d6b8d183170f.zip | |
Initial commit
Decent image set, basically usable
Diffstat (limited to 'scripts/toy.groovy')
| -rw-r--r-- | scripts/toy.groovy | 694 | 
1 files changed, 694 insertions, 0 deletions
diff --git a/scripts/toy.groovy b/scripts/toy.groovy new file mode 100644 index 0000000..fcb1b2e --- /dev/null +++ b/scripts/toy.groovy @@ -0,0 +1,694 @@ +// Toy main + +setInfos(9, "Toy", "Your frustration is my amusement.", "rascalDan", "WIP", 0xFFFFFF, "en", ["", "", ""]); +final VERSION = 1; +final DAY = 86400; +final HOUR = 3600.0; +final soonFormatter = java.time.format.DateTimeFormatter.ofPattern("EEEE 'at' h:mma"); +// Notes +// toys.<name> 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"; +// fetish.<name> +final BONDAGE = "bondage", CBT = "cbt", CHORES = "chores", PAIN = "pain"; +// toy.permission.<name> +final CUM = "cum", EDGE = "edge" +// toy.state.<name> +final CHASTE = "chaste", COLLARED = "collared", CUFFED = "cuffed", CLAMPED = "clamped", GAGGED = "gagged", NAKED = "naked"; +// toy.plan.<name>.{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"; +final OWNER = "ancilla"; +final SITTER = "sarah-james"; + +// Imagery +final DRESSED = "dressed", TITS = "tits", PUSSY = "pussy", LINGERIE = "lingerie", TEASE = "tease", SIT = "sit", BOOTS = "boots", KNEEL = "kneel", STOOD = "stood", MEAN = "mean", CROP = "crop", ASS = "ass", SQUAT = "squat", SSSH = "sssh"; +final nDRESSED = "!$DRESSED", nTITS = "!$TITS", nTEASE = "!$TEASE"; +def IMAGEDATA = { [ +	[ domme: "ancilla", sets: [ +		[ set: "corset", images: [ +			[ image: 1, tags: [DRESSED, STOOD] ], +			[ image: 2, tags: [DRESSED, STOOD] ], +			[ image: 3, tags: [DRESSED, STOOD] ], +			[ image: 4, tags: [DRESSED, STOOD] ], +			[ image: 5, tags: [DRESSED, STOOD] ], +			[ image: 6, tags: [DRESSED, STOOD] ], +			[ image: 7, tags: [DRESSED, STOOD, TEASE] ], +			[ image: 8, tags: [DRESSED, STOOD, ASS] ], +			[ image: 9, tags: [DRESSED, STOOD, TEASE] ], +			[ image: 10, tags: [DRESSED, STOOD, TEASE] ], +			[ image: 11, tags: [KNEEL, TITS] ], +			[ image: 12, tags: [KNEEL, TITS, ASS] ], +			[ image: 13, tags: [KNEEL, TITS, LINGERIE] ], +			[ image: 14, tags: [KNEEL, TITS, LINGERIE] ], +			[ image: 15, tags: [SIT, TITS, LINGERIE] ], +			[ image: 16, tags: [STOOD, TITS, LINGERIE] ], +		] ], +		[ set: "crop", images: [ +			[ image: 1, tags: [TITS, KNEEL, SSSH] ], +			[ image: 2, tags: [TITS, KNEEL, MEAN, CROP] ], +			[ image: 3, tags: [TITS, KNEEL, CROP] ], +			[ image: 4, tags: [TEASE, KNEEL] ], +			[ image: 5, tags: [STOOD, TITS] ], +			[ image: 6, tags: [KNEEL, TITS] ], +			[ image: 7, tags: [KNEEL, TITS] ], +			[ image: 8, tags: [DRESSED, STOOD, CROP] ], +			[ image: 9, tags: [DRESSED, STOOD, CROP] ], +			[ image: 10, tags: [DRESSED, STOOD, CROP] ], +			[ image: 11, tags: [DRESSED, STOOD, CROP] ], +			[ image: 12, tags: [DRESSED, STOOD, CROP] ], +			[ image: 13, tags: [DRESSED, STOOD, CROP] ], +			[ image: 14, tags: [DRESSED, SIT, CROP] ], +			[ image: 15, tags: [TITS, SIT] ], +			[ image: 16, tags: [TITS, STOOD] ], +		] ], +		[ set: "dress", images: [ +			[ image: 1, tags: [TITS, STOOD] ], +			[ image: 2, tags: [TITS, STOOD] ], +			[ image: 3, tags: [TITS, SIT] ], +			[ image: 4, tags: [TITS, SIT] ], +			[ image: 5, tags: [TITS, STOOD] ], +			[ image: 6, tags: [TITS, STOOD, ASS] ], +			[ image: 7, tags: [DRESSED, STOOD] ], +			[ image: 8, tags: [DRESSED, STOOD] ], +			[ image: 9, tags: [DRESSED, STOOD, ASS] ], +			[ image: 10, tags: [DRESSED, STOOD, ASS] ], +			[ image: 11, tags: [DRESSED, SQUAT] ], +			[ image: 12, tags: [DRESSED, STOOD, TEASE] ], +			[ image: 13, tags: [DRESSED, STOOD, TEASE] ], +			[ image: 14, tags: [TITS, STOOD] ], +			[ image: 15, tags: [TITS, STOOD] ], +		] ], +		[ set: "pink", images: [ +			[ image: 1, tags: [TITS, KNEEL, LINGERIE] ], +			[ image: 2, tags: [TITS, KNEEL, LINGERIE] ], +			[ image: 3, tags: [TITS, KNEEL, LINGERIE] ], +			[ image: 4, tags: [TITS, KNEEL, LINGERIE] ], +			[ image: 5, tags: [TITS, KNEEL, LINGERIE] ], +			[ image: 6, tags: [TITS, KNEEL] ], +			[ image: 7, tags: [DRESSED, KNEEL] ], +			[ image: 8, tags: [DRESSED, STOOD] ], +			[ image: 9, tags: [DRESSED, STOOD] ], +			[ image: 10, tags: [DRESSED, STOOD, TEASE] ], +			[ image: 11, tags: [DRESSED, STOOD, TEASE] ], +			[ image: 12, tags: [KNEEL, LINGERIE] ], +			[ image: 13, tags: [TITS, KNEEL, LINGERIE] ], +			[ image: 14, tags: [TITS, KNEEL, LINGERIE] ], +			[ image: 15, tags: [TITS, KNEEL, LINGERIE] ], +		] ], +	] +	] +] }; +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 }) +		.sets.find({ s -> s.set == set }) +		.images.findAll({ i -> meets(i) }); +	return matches[getRandom(matches.size())]; +}; +def showImage = { spec -> +	def outfit = loadString("toy.owner.outfit"); +	def image = selectImage(OWNER, outfit, spec); +	setImage("toy/$OWNER/$outfit/${image.image}.jpg"); +	return image.tags; +}; +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 (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 is = {i -> loadBoolean("toy.state.$i") == true}; +def set = {i, s -> save("toy.state.$i", s)}; +def positioned = { i -> loadString("toy.position") == i }; +def gagText = { t -> +	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(); +}; +def showButtonG = { s, t = null -> +	return t != null ? showButton(gagText(s), t) : showButton(gagText(s)); +}; +def present = { imageSpec, texts -> +	if (texts) { +		show(texts.collect { +			t -> t[getRandom(t.size())] +		}.join(" ").capitalize()); +	} +	if (imageSpec) { +		return showImage(imageSpec); +	} +	return null; +}; + +def harden = { imageSpec -> +	present([DRESSED], [ +			["Take that cock...", "Hand on penis..."], +			["stroke it, slowly...", "slow strokes..."], +			["don't edge...", "no edging..."], +			["and do NOT cum.", "and definitely no cumming."]]); +	if (showButtonG("Hard, mistress", 60) == 60) { +		present(null, [ +				["What's taking so long!?", "Come on!", "Don't disappoint me."], +				["Slap it about a bit!.", "Get it hard, now!", "Pinch your nipples."]]); +		showButtonG("Hard, mistress"); +	} +}; +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); +	} +}; +def edge = { amount, imageSpec -> +	(getRandom(amount) + 2).times { +		present(imageSpec, [ +				["Stroke to edge now, toy...", "Edge!"], +				["Don't cum.", "No cumming.", "No accidents though."]]); +		showButtonG("Edging, mistress"); // TODO: time check +		if (getRandom(2) == 1) { +			present(imageSpec, [ +					["Hold it...", "And hold...", "Keeping going..."]]); +			wait(getRandom(20) + 5); +		} +		present(imageSpec, [ +				["Hands off!", "Stop!"]]); +		wait(getRandom(5) + 5); +	}; +}; +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 { +		def beatNo = getRandom(BEATS.size()); +		def beat = BEATS[beatNo]; +		playBackgroundSound("toy/${beat.bpm}bpm.mp3", 10); // Lots, to cover offset +		def len = getRandom(java.lang.Math.max(1, beat.len + (sto * beatNo))); +		if (showButtonG("Edging, mistress", len) < len) { +			sto -= 1; +			edges += 1; +			playBackgroundSound(null); +			switch (getRandom(3)) { +				case 0: +					present(imageSpec, [ +							["Stop!", "Let go!", "Hands off!"]]); +					wait(getRandom(8) + 10); +					break; +				case 1: +					present(imageSpec, [ +							["Careful now...", "No accidents..."], +							["slow it down...", "slowly now..."]]); +					playBackgroundSound("toy/30bpm.mp3"); +					wait(getRandom(20) + 5); +					playBackgroundSound(null); +				case 2: +					switch(getSelectedValue("Would you like to cum, toy?", [ +								gagText("Please, mistress, may I cum!?"), +								gagText("No, mistress, please torment me more!") +					])) { +						case 0: +							present(imageSpec, [ +									["No you may not!", "Nope.", "Not a chance."]]); +							break; +						case 1: +							present(imageSpec, [ +									["Good boy.", "Very well.", "OK then!"]]); +							break; +					} +					wait(getRandom(8) + 5); +			} +			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); +} + +// Pre-tease +def preRelease = { +	if (!is(CHASTE)) return; +	present([DRESSED], [ +			["I'm feeling generous, toy...", "Well, that's not good to me..."], +			["let's get that chastity device off.", "take your chastity device off."]]); +	showButtonG("Yes, mistress"); +	wait(15); +	set(CHASTE, false); +	showButtonG("Thank you, mistress"); +}; +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; +	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); +	showButton("Back, mistress"); // TODO: time check +	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); +	showButtonG("Gagged, mistress"); // TODO: time check +}; +def preClamps = { +	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"); +	show(null); +	wait(10); +	set(CLAMPED, true); +	showButtonG("Back, mistress"); // TODO: time check +	present([DRESSED], [ +			["On your knees,", "Kneel before me"], +			["let me see.", "hands behind your back."]]); +	wait(getRandom(10) + 5); +	getRandom(4).times { +		present([DRESSED], [ +				["Pull them tight.", "Pull them!"]]); +		wait(getRandom(10) + 5); +		if (getRandom(5) == 0) { +			present(null, [ +					["Tighter!", "Harder!"]]); +			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); +	}; +}; +def preCollar = { +	if (is(COLLARED)) return; +	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"); +	show(null); +	wait(10); +	set(COLLARED, true); +	showButtonG("Back, mistress"); // TODO: time check +}; +def preStrip = { imageSpec = [DRESSED] -> +	if (is(NAKED)) return; +	present(imageSpec, [ +			["Clothes off!", "Get naked, slut!"]]); +	wait(10); +	present([DRESSED], [ +			["All of them off!", "And the rest!", "Keep going."]]); +	set(NAKED, true); +	showButtonG("Naked, mistress"); +	(getRandom(2) + 1).times { +		switch (getRandom(2)) { +			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, [ +						["Turn around,", "Spin"], +						["slowly", "nice and slow."]]); +				break; +			case 2: +				present(null, [ +						["Very good. Now...", "I see..."], +						["on your knees before me,", "kneel,"], +						["don't move those hands.", "hands still."]]); +				break; +		} +		wait(10); +	}; +}; + +// 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 postChastity = { +	if (is(CHASTE)) return; +	if (!has(CHASTITY)) return; +	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([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); +	showButtonG("Locked, mistress. Here's the key."); +	set(CHASTE, true); +	present([DRESSED,TEASE], [ +			["Good boy.", "Thank you, toy."]]); +}; + +// Play +def playStrokes = { +	preRelease(); +	harden([TITS]); +	strokes(15, [TITS]); +}; +def playEdges = { +	preRelease(); +	harden([TITS]); +	edge(6, [TITS]); +}; +def playNothing = { +	(getRandom(5) + 4).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); +	}; +}; + +// 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!"]]); +				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; +		}; +	} +	return false; +}; +def sessionPlay = { +	def play = { amount, activities -> +		(getRandom(amount) + 1).times { +			activities[getRandom(activities.size())](); +		}; +	}; +	play(3, [preRelease, preEdge, preGag, preClamps, preCollar, preStrip]); +	play(3, [playStrokes, playEdges, playNothing]); +	play(2, [postEdge, postCum, postChastity]); +}; +def sessionRelease = { +	present([DRESSED], [ +			["Ahhh,", "OK,"], +			["I'm done with you for now.", "That was fun... for me at least."]]); +	wait(getRandom(10) + 5); +	if (is(CUFFED) || is(COLLARED) || is(CLAMPED) || is(GAGGED)) { +		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)) { +		present([DRESSED], [ +				["Put some clothes back on.", "Cover yourself up!"]]); +		wait(getRandom(10) + 15); +		set(NAKED, false); +	} +	present([DRESSED], [ +			["Off you go.", "You may leave."]]); +	wait(10); +	setImage(null); +	show(null); +}; +def playtime = { +	dress([[DRESSED,nTEASE],[DRESSED,TEASE],[TITS]]); +	if (sessionSummon([DRESSED,nTEASE])) { +		sessionPlay(); +		sessionRelease(); +	} +} +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"); +	} +	exit(); +}; + +// 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(7) + 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 setDefault = { prop, val -> +	if (loadString(prop) == null) { +		save(prop, val); +	} +}; +// Initial - basic checks and defaults +def setupInitial = { +	if (loadInteger("toy.version") == null) { +		// Welcome +	} +	save("toy.version", VERSION); +	setDefault("toy.strokeTeaseOffset", 0); +	setupPlans(); +}; +def setupShowState = { +	show( +			[CHASTE, COLLARED, CUFFED, CLAMPED, GAGGED, NAKED].collect { +				def v = is(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! +setupInitial(); +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(); +} +final cycleTime = 6; +while (true) { +	if (showButton("Please, Miss?", cycleTime) < cycleTime) { +		switch (getSelectedValue("Yes, toy?", [ +					"Status", +					"Calendar", +					"Play", +					"Back" +		])) { +			case 0: setupShowState(); break; +			case 1: setupShowCalendar(); break; +			case 2: playtime(); break; +			case 3: return "toy"; +		} +	} +	else if (localTime() > 25.75) { // 1:45am +		bedtime(); +	} +	else if (getRandom(1000) == 0) { +		playtime(); +	} +	addAvail(cycleTime); +} +  | 
