Page 2 of 3

Re: PC Vibration

Posted: Tue Mar 07, 2023 12:24 am
by modnar
I'm not sure I dare try this with my "Nobra twincharger". Mainly because 0-100% isn't really a thing on the nobra. The lower end is just not what you'd expect in terms of 0-100.

Interested to know what toys others have been using.

Re: PC Vibration

Posted: Tue Mar 07, 2023 7:38 am
by wellrested
I used a Lovense Gush.

Re: PC Vibration

Posted: Tue Mar 07, 2023 7:02 pm
by Faroe
I've had confirmation of:

Gush / Domi 2 / Edge 2 / Handy (with some issues & on a development version, not current release)

Someone has a testing version for Kiiroo Onyx.

I haven't tried the Nobra (a scary thought), as I haven't been able to get it to work with buttplug-io.

F

Re: PC Vibration

Posted: Wed Mar 08, 2023 8:04 am
by LegalizeRanch
Very interesting tease, great! Would love to see the edging stuff. I have handy and lovense lush.
Thanks!

Re: PC Vibration

Posted: Thu Mar 09, 2023 3:19 pm
by 47dahc
Not sure what I'm doing wrong but after installing TamperMonkey and ButtEOS, the webtease won't load and I get an error saying that "This content is blocked. Contact the site owner to fix the issue." If I disable TM it loads fine but I'm assuming the script isn't loading since I'm not getting the icon in the lower right corner. This is on Chrome. After some testing, no EOS tease will load with ButtEOS enabled. Disable it, teases load. So what am I doing wrong?

Re: PC Vibration

Posted: Thu Mar 09, 2023 3:33 pm
by LegalizeRanch
47dahc wrote: Thu Mar 09, 2023 3:19 pm Not sure what I'm doing wrong but after installing TamperMonkey and ButtEOS, the webtease won't load and I get an error saying that "This content is blocked. Contact the site owner to fix the issue." If I disable TM it loads fine but I'm assuming the script isn't loading since I'm not getting the icon in the lower right corner. This is on Chrome. After some testing, no EOS tease will load with ButtEOS enabled. Disable it, teases load. So what am I doing wrong?
Use Violentmonkey instead, I had the same problem with tampermonkey.

Re: PC Vibration

Posted: Fri Mar 10, 2023 1:14 am
by 47dahc
LegalizeRanch wrote: Thu Mar 09, 2023 3:33 pm
47dahc wrote: Thu Mar 09, 2023 3:19 pm Not sure what I'm doing wrong but after installing TamperMonkey and ButtEOS, the webtease won't load and I get an error saying that "This content is blocked. Contact the site owner to fix the issue." If I disable TM it loads fine but I'm assuming the script isn't loading since I'm not getting the icon in the lower right corner. This is on Chrome. After some testing, no EOS tease will load with ButtEOS enabled. Disable it, teases load. So what am I doing wrong?
Use Violentmonkey instead, I had the same problem with tampermonkey.
Guess I'm not supposed to try this tease. ViolentMonkey says "no scripts found" and if I go to GreasyFork (link in the tease), same thing.

Re: PC Vibration

Posted: Fri Mar 10, 2023 6:06 am
by LegalizeRanch
47dahc wrote: Fri Mar 10, 2023 1:14 am
LegalizeRanch wrote: Thu Mar 09, 2023 3:33 pm
47dahc wrote: Thu Mar 09, 2023 3:19 pm Not sure what I'm doing wrong but after installing TamperMonkey and ButtEOS, the webtease won't load and I get an error saying that "This content is blocked. Contact the site owner to fix the issue." If I disable TM it loads fine but I'm assuming the script isn't loading since I'm not getting the icon in the lower right corner. This is on Chrome. After some testing, no EOS tease will load with ButtEOS enabled. Disable it, teases load. So what am I doing wrong?
Use Violentmonkey instead, I had the same problem with tampermonkey.
Guess I'm not supposed to try this tease. ViolentMonkey says "no scripts found" and if I go to GreasyFork (link in the tease), same thing.
Spoiler: show
// ==UserScript==
// @name ButtEOS
// @namespace Violentmonkey Scripts
// @grant none
// @match *://milovana.com/webteases/showtease.php
// @match *://milovana.com/eos/editor/*
// @match *://eosscript.com/*
// @license BSD
// @version 1.1
// @author cfs6t08p
// @description 2/21/2022, 9:26:31 PM
// ==/UserScript==

/* jshint esversion: 8 */

function mod(a, b) {
return ((a % b) + b) % b;
}

function actionIndex(pattern, time) {
for(let a = 0; a < pattern.numActions; a++) {
if(pattern.actions[a].at > time) {
return a;
}
}
}

function positionAt(pattern, time, index) {
if(pattern.actions[index].at > time) {
let a1 = pattern.actions[mod((index - 1), pattern.numActions)];
let a2 = pattern.actions[index];

let a1Wrap = mod(a1.at, pattern.patternLength);

let dp = a2.pos - a1.pos;
let dt = a2.at - a1Wrap;

let alpha = (time - a1Wrap) / dt;

return 99 - (a1.pos + alpha * dp);
}
}

function vibe(level) {
window.parent.postMessage({buttEOS: true, vib: {level: level}}, "https://milovana.com/webteases/*");
}

function linear(position, duration) {
window.parent.postMessage({buttEOS: true, linear: {position: position, duration: duration }}, "https://milovana.com/webteases/*");
}

if(document.getElementById("eosContainer")) {
let eos = document.getElementById("eosContainer");
let bod = document.body;

let div = document.createElement("div");

div.style = "position: absolute; left: 20px; top: 40px; width: 160px; z-index: 100000";

bod.append(div);

let bar = document.createElement("div");
let fill = document.createElement("div");
let arrow = document.createElement("div");
let line = document.createElement("div");
let text = document.createElement("div");

bar.style = "position: absolute; left: 20px; height: 80%; bottom: 10%; width: 50px; border-top-left-radius: 25px; border-top-right-radius: 25px; background-color: #ffffff20; visibility: hidden;";
fill.style = "position: absolute; left: 7px; width: 36px; bottom: 0px; background-color: #bb55bbcc;";
arrow.style = "position: absolute; left: 7px; width: 36px; height: 18px; border-top-left-radius: 18px; border-top-right-radius: 18px; background-color: #bb55bbcc;";
line.style = "position: absolute; width: 100%; height: 4px; bottom: 15%; background-color: #ffff00cc;";
text.style = "position: absolute; width: 100%; height: 10%; bottom: 0px; color: white; padding-top: 5px;";

bar.append(fill);
bar.append(arrow);
bar.append(line);

div.append(bar);
div.append(text);

let currentPattern = {};
let lastPatternName;
let lastBPM;
let bpmPattern;
let patternStart;
let prevActionIndex;

let newPatterns = 0;

let vibeLevel;

let patterns = {};

setInterval(() => {
let xpath = ".//p[contains(text(),'Load pattern:')]";
let result = document.evaluate(xpath, eos, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

if(result.snapshotLength == 0) {
newPatterns = 0;
}

for(let i = 0; i < result.snapshotLength; i++) {
let node = result.snapshotItem(i);
let name = node.textContent.slice(13).trim();

let data = node.parentNode.childNodes;

if(data.length >= 3) {
let text = data[1].textContent;
let funscript = "";

for(let l = 2; l < data.length; l++) {
funscript = funscript + data[l].textContent;
}

if(patterns[name] === undefined) {
patterns[name] = {};

try {
let pattern = JSON.parse(funscript);

pattern.valid = true;
pattern.text = text;
pattern.numActions = pattern.actions.length;
pattern.patternLength = pattern.actions[pattern.numActions - 1].at;

let time = 0;

for(let a = 0; a < pattern.numActions; a++) {
let at = pattern.actions[a].at;

pattern.actions[a].dur = at - time;

time = at;
}

patterns[name] = pattern;

newPatterns++;

console.log(pattern);
} catch(error) {
console.error("Failed to load pattern \"" + name + "\"");
console.error(error);
}
}
}
}

if(newPatterns > 0) {
text.innerText = "Loaded " + newPatterns + " pattern(s)";
}
}, 100);

setInterval(() => {
let now = Date.now();
let h = (eos.clientHeight - 40) * 0.7;

div.style.height = h + "px";

let vibePath = ".//div[contains(text(),'Vibrator:')]";
let vibeNotification = document.evaluate(vibePath, eos, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

let newVibe = vibeLevel;

if(vibeNotification) {
newVibe = parseInt(vibeNotification.textContent.slice(9));
} else {
newVibe = 0;
}

if((newVibe != vibeLevel) && !(Number.isNaN(newVibe) && Number.isNaN(vibeLevel))) {
vibeLevel = newVibe;

if(Number.isNaN(vibeLevel) || vibeLevel > 100 || vibeLevel < 0) {
console.error("Invalid vibrator level: \"" + vibeLevel + "\"");

vibe(0);
} else {
vibe(vibeLevel);
}
}

let pattern;

let patternPath = ".//div[contains(text(),'Pattern:')]";
let patternNotification = document.evaluate(patternPath, eos, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

if(patternNotification) {
let name = patternNotification.textContent.slice(8).trim();
pattern = patterns[name];

if(lastPatternName != name) {
lastPatternName = name;

if(!pattern) {
console.error("Pattern \"" + name + "\" not found");
}
}
}

let bpmPath = ".//div[contains(text(),'BPM:')]";
let bpmNotification = document.evaluate(bpmPath, eos, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

if(bpmNotification) {
let bpm = parseInt(bpmNotification.textContent.slice(4));

if((bpm != lastBPM) && !(Number.isNaN(bpm) && Number.isNaN(lastBPM))) {
lastBPM = bpm;

if(Number.isNaN(bpm) || bpm <= 0 || bpm > 600) {
console.error("Invalid BPM: \"" + bpm + "\"");

bpmPattern = undefined;
} else {
let period = (60 * 1000) / bpm;

pattern = {valid: true, numActions: 2, patternLength: period, text: "", actions: [{at: period / 2, pos: 0, dur: period / 2},{at: period, pos: 100, dur: period / 2}]};

bpmPattern = pattern;
}
} else {
pattern = bpmPattern;
}
}

if(pattern != currentPattern) {
currentPattern = pattern;
patternStart = now;
prevActionIndex = -1;

if(pattern) {
text.innerText = pattern.text;

bar.style.visibility = "visible";
} else {
text.innerText = "";

bar.style.visibility = "hidden";
}

newPatterns = 0;
}

if(currentPattern !== undefined && currentPattern.valid) {
let patternTime = mod(now - patternStart, currentPattern.patternLength);
let index = actionIndex(currentPattern, patternTime);

if(index != prevActionIndex) {
linear(currentPattern.actions[index].pos, currentPattern.actions[index].dur);

prevActionIndex = index;
}

let fillHeight = ((bar.clientHeight - 25) * positionAt(currentPattern, patternTime, index)) / 100;

fill.style.height = fillHeight + "px";
arrow.style.bottom = fillHeight + "px";
}
}, 10);
}

if(document.querySelector(".eosTopBody")) {
window.addEventListener("message", (event) => {
if(event.data.buttEOS) {
if(window.buttplug_devices) {
if(event.data.vib) {
window.buttplug_devices.forEach((device) => {
if(device.messageAttributes(Buttplug.ButtplugDeviceMessageType.VibrateCmd)) {
device.vibrate(event.data.vib.level / 100);
}
});
}

if(event.data.linear) {
window.buttplug_devices.forEach((device) => {
if(device.messageAttributes(Buttplug.ButtplugDeviceMessageType.LinearCmd)) {
device.linear(event.data.linear.position / 100, event.data.linear.duration);
}
});
}
}

event.stopImmediatePropagation();
}
});

let bpscript= document.createElement("script");
bpscript.src = "https://cdn.jsdelivr.net/npm/buttplug@1 ... lug.min.js";
document.body.append(bpscript);

window.addEventListener("load", function (e) {
let style = document.createElement("style");
style.innerHTML = `
#buttplug-top-container h3, li {
font-family:Arial;
font-size:15px;
}
#buttplug-top-container ul {
list-style-type: none;
column-count: 2;
}
.buttplug-button {
box-shadow:inset 0px 1px 3px 0px #91b8b3;
background:linear-gradient(to bottom, #768d87 5%, #6c7c7c 100%);
background-color:#768d87;
border-radius:5px;
border:1px solid #566963;
display:inline-block;
cursor:pointer;
color:#ffffff;
font-family:Arial;
font-size:15px;
font-weight:bold;
padding:11px 23px;
text-decoration:none;
text-shadow:0px -1px 0px #2b665e;
margin: 5px;
}
.buttplug-button:hover {
background:linear-gradient(to bottom, #6c7c7c 5%, #768d87 100%);
background-color:#6c7c7c;
}
.buttplug-button:active {
position:relative;
top:1px;
}

#buttplug-top-container {
position: fixed;
top: 0;
right: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: rgba(0, 0, 0, 0.7);
display: none;
}

#buttplug-dialog {
width: 50%;
min-height: 200px;
position: absolute;
top: 10%;
left: 0;
left: 0;
right: 0;
margin: auto;
background: #888888cc;
border-radius: 5px;
padding: 20px;
}

.close {
background: #000;
cursor: pointer;
width: 20px;
height: 20px;
border-radius: 2px;
text-align: center;
color: white;
}

#close-bottom-right {
position: absolute;
bottom: 0;
right: 0;
}

body {
width: 100%;
height: 100%;
}

.open {
width: 50px;
height: 50px;
background-image: url("data:image/svg+xml,%3Csvg id='Layer_1' data-name='Layer 1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 290.56 293.08'%3E%3Cdefs%3E%3Cstyle%3E.cls-1,.cls-3%7Bfill:none;%7D.cls-1%7Bstroke:%23fff;stroke-miterlimit:10;%7D.cls-2%7Bfill:%23fff;%7D%3C/style%3E%3C/defs%3E%3Ctitle%3Ebuttplug-logo-1%3C/title%3E%3Crect x='0.5' y='0.5' width='289.56' height='292.08' rx='32' ry='32'/%3E%3Crect class='cls-1' x='0.5' y='0.5' width='289.56' height='292.08' rx='32' ry='32'/%3E%3Crect class='cls-2' x='10.63' y='10.72' width='269.29' height='271.63' rx='25' ry='25'/%3E%3Crect class='cls-1' x='10.63' y='10.72' width='269.29' height='271.63' rx='25' ry='25'/%3E%3Crect x='17.37' y='17.51' width='255.83' height='258.05' rx='20' ry='20'/%3E%3Crect class='cls-1' x='17.37' y='17.51' width='255.83' height='258.05' rx='20' ry='20'/%3E%3Cline class='cls-3' x1='156.1' y1='152.66' x2='142.44' y2='162.32'/%3E%3Cpath class='cls-2' d='M325.32,383.36a3.07,3.07,0,0,1-1.71-5.64,107.76,107.76,0,0,1,14.2-9.47l2.32-1.36c2.57-1.54,5.24-3,7.83-4.36a95,95,0,0,0,13.73-8.38c1.9-1.49,2.33-6.94,2.59-10.2v-.12c.86-10.76,1-22.09-7.83-32-9.93-11.24-8.63-25.63-6.06-38.22,3-14.72,5.94-29.72,8.78-44.22,3.34-17.09,6.8-34.76,10.41-52.11,1.82-8.76,6.31-14.55,12.3-15.88a20.85,20.85,0,0,1,6.58,0c6,1.33,10.48,7.12,12.3,15.88,3.61,17.35,7.07,35,10.41,52.12,2.83,14.5,5.77,29.49,8.78,44.21,2.58,12.59,3.87,27-6.06,38.22-8.79,10-8.69,21.29-7.83,32v.12c.26,3.26.69,8.71,2.6,10.2a95.08,95.08,0,0,0,13.73,8.38c2.58,1.39,5.26,2.82,7.83,4.36l2.32,1.36a108,108,0,0,1,14.2,9.47,3.07,3.07,0,0,1-1.81,5.64H325.32Zm2.69-4H442.34a109.85,109.85,0,0,0-11.81-7.65l-2.37-1.39c-2.48-1.49-5.11-2.9-7.66-4.26a98.21,98.21,0,0,1-14.31-8.76c-3.28-2.57-3.75-8.37-4.12-13v-.12c-.93-11.61-1-23.88,8.83-35,8.74-9.9,7.63-22.56,5.14-34.77-3-14.73-5.95-29.74-8.79-44.25-3.34-17.08-6.79-34.75-10.4-52.07-1.49-7.15-4.86-11.81-9.25-12.79a9.39,9.39,0,0,0-2.27-.17H385a9.32,9.32,0,0,0-2.27.17c-4.39,1-7.76,5.64-9.25,12.79-3.61,17.32-7.06,35-10.4,52.06-2.84,14.51-5.77,29.52-8.79,44.25-2.5,12.21-3.61,24.87,5.14,34.77,9.83,11.13,9.75,23.4,8.82,35v.12c-.37,4.66-.83,10.46-4.12,13a98.14,98.14,0,0,1-14.31,8.76c-2.54,1.36-5.17,2.77-7.66,4.26l-2.37,1.39A109.88,109.88,0,0,0,328,379.35Z' transform='translate(-239.9 -125.68)'/%3E%3C/svg%3E%0A");
display: none;
z-index:999;
}

#open-bottom-right {
position: fixed;
bottom: 0;
right: 0;
display: block;
}
`;

document.body.append(style);

let open_element = document.createElement('div');
open_element.id = `open-bottom-right`;
open_element.className = "open";
document.body.append(open_element);

let container_div = document.createElement('div');
container_div.innerHTML = `
<div id="buttplug-dialog">
<div id="close-bottom-right" class="close">V</div>
<div id="buttplug-container" style="margin: 10px; display: flex;">
<div id="buttplug-connector" style="display: block;">
<a href="#" class="buttplug-button" id="buttplug-connect-browser">Connect in Browser</a>
<br/>
<a href="#" class="buttplug-button" id="buttplug-connect-intiface">Connect to Intiface Desktop</a>
<br/>
</div>
<div id="buttplug-enumeration" style="display: none;">
<a href="#" class="buttplug-button" id="buttplug-scanning">Start Scanning</a>
<a href="#" class="buttplug-button" id="buttplug-disconnect">Disconnect</a>
<br/>
<h3>Devices</h3>
<ul id="buttplug-device-list">
<li>
</li>
</ul>
</div>
</div>
</div>`;
container_div.id = "buttplug-top-container";
document.body.append(container_div);

// We need the buttplug_devices to be global, so that tampermonkey user
// scripts can work with it. Hang it off window.
window.buttplug_devices = [];

setTimeout(() =>
(async function () {
// Set up Buttplug
await Buttplug.buttplugInit();

const buttplug_client = new Buttplug.ButtplugClient("ButtEOS Client");
const dialog_div = document.getElementById("buttplug-dialog");
const connector_div = document.getElementById("buttplug-connector");
const enumeration_div = document.getElementById("buttplug-enumeration");
const scanning_button = document.getElementById("buttplug-scanning");
const connect_browser_button = document.getElementById("buttplug-connect-browser");
const connect_intiface_button = document.getElementById("buttplug-connect-intiface");
const disconnect_button = document.getElementById("buttplug-disconnect");
const device_list = document.getElementById("buttplug-device-list");
buttplug_client.addListener('deviceadded', async (device) => {
const element_id = `buttplug-device-${device.Index}`;
const input = document.createElement("li");
input.id = element_id;
const checkbox = document.createElement("input");
const checkbox_id = `${element_id}-checkbox`;
checkbox.type = "checkbox";
checkbox.id = checkbox_id;
input.addEventListener("click", async (event) => {
const index = window.buttplug_devices.indexOf(device);

if (index > -1) {
await device.stop();
window.buttplug_devices.splice(index, 1);
checkbox.checked = false;
} else {
window.buttplug_devices.push(device);
checkbox.checked = true;
}
});
let label = document.createElement("label");
label.for = `${element_id}-checkbox`;
label.innerHTML = device.Name;
input.appendChild(checkbox);
input.appendChild(label);
device_list.appendChild(input);
});

buttplug_client.addListener('deviceremoved', async (device) => {
const element_id = `buttplug-device-${device.Index}`;
var element = document.getElementById(element_id);
element.parentNode.removeChild(element);
});

connect_browser_button.addEventListener("click", async (event) => {
const connector = new Buttplug.ButtplugEmbeddedConnectorOptions();
await buttplug_client.connect(connector);
connector_div.style.display = "none";
enumeration_div.style.display = "block";
}, false);

connect_intiface_button.addEventListener("click", async (event) => {
const connector = new Buttplug.ButtplugWebsocketConnectorOptions("ws://localhost:12345/");
await buttplug_client.connect(connector);
connector_div.style.display = "none";
enumeration_div.style.display = "block";
}, false);

disconnect_button.addEventListener("click", async (event) => {
await buttplug_client.disconnect();
enumeration_div.style.display = "none";
connector_div.style.display = "block";
}, false);

scanning_button.addEventListener('click', async () => {
await buttplug_client.startScanning();
});

let container = document.querySelector("#buttplug-top-container");

let close = document.getElementById(`close-bottom-right`);
let open = document.getElementById(`open-bottom-right`);
close.addEventListener("click", () => {
container.style.display = "none";
open.style.display = "block";
}, false);

container_div.addEventListener("click", () => {
container.style.display = "none";
open.style.display = "block";
}, false);

dialog_div.addEventListener("click", (ev) => {
ev.stopPropagation();
}, false);

open.addEventListener("click", () => {
open.style.display = "none";
container.style.display = "block";
}, false);
})(), 0);

}, false);
}
Create new script and paste this

Re: PC Vibration

Posted: Fri Mar 10, 2023 6:02 pm
by Faroe
You can get ButtEos script it from here

https://sleazyfork.org/en/scripts/440634-butteos

Re: PC Vibration

Posted: Sun Mar 12, 2023 9:23 pm
by Faroe
Hey

I've made some updates - nothing interesting; but made it easier for it to be rated (you don't actually have to get to the end to rate - which has probably inflated the rating so far).

Changed menu's / hopefully updated instructions on set up.

Let me know if I've broken it.

F

Re: PC Vibration

Posted: Tue Mar 14, 2023 4:51 pm
by 47dahc
Faroe wrote: Sun Feb 26, 2023 6:54 pm Hey

Cheers! This is my first tease; when you get a more compatible toy let me know. Check the list of buttplug-io compatible toys.

There's also functionality built into the tease to make it work with supported stokers, but as I do not have one, it's hard for me to test. So if anyone wants to test for me - send me a message.

F
I was able to get the tease, TamperMonkey, ButtEOS script, Intiface Desktop, and my OSR2 all connected and visible in the tease and got really excited when it worked on the test page. However, it did not work when I started the tease. Whatever code you used on the test page worked great but not so much in the tease. So if you a tester still, I can test with the OSR2.

Re: PC Vibration

Posted: Tue Mar 14, 2023 6:02 pm
by Faroe
Hey

Yes the 'test' bit has the code for stroker support. But it isn't in the released version.

I've sent you a PM to a version where I've put in some basic stroker support.

As I can't test it myself - I'd suggest seeing if it works before trying anything! (I've kept it on probably low settings to be safe as well).

F

Re: PC Vibration

Posted: Sun Mar 19, 2023 9:12 pm
by Faroe
Hey

Made some updates:

1) New pics!
2) Some different sessions! (Random Original: this rolls a chance to change the state of the toy
(on/off) rather than just change it, Up, starts at 0, and has a chance to increase the strength of toy - the chance reduces as time progresses; Down: similar to Up, but in reverse).
3) Stats page; to see what you went through!

F

Re: PC Vibration

Posted: Sun Mar 26, 2023 7:01 pm
by Faroe
Hey

Added some things:

1) Ability to select which session you want (although stats won't come up on this option, that's only for the real tease!) [In the real tease the toy will gradually pick higher vibrating levels, here it will just be using the ones available in the first session]
2) 10% chance of hitting a bonus round after a session (it won't count as a session, but it may trigger another bonus round if you are lucky/unlucky).
3) Changed the ending round; it's not 100% on now
4) Ending round and bonus round share new pics.
5) Added description of what each session does (in about).

I think it all works, but let me know if something breaks.

F

Re: PC Vibration

Posted: Tue Apr 11, 2023 12:03 am
by Faroe
Fixed some bugs,

Added a challenge mode , where what will happen is ... (just do it to see..!!).

Anyway, I think I've done most of what I wanted to do. But I've an experiment tease in progress, with something like this tease + Stroker + estim options. Send me a PM if you're interested!! (early days though)

F