Skip to content

Instantly share code, notes, and snippets.

@BOLL7708
Created May 1, 2023 10:35
Show Gist options
  • Save BOLL7708/69c5618afd6c34259eebabd8c676b3ae to your computer and use it in GitHub Desktop.
Save BOLL7708/69c5618afd6c34259eebabd8c676b3ae to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Scale Reward Source</title>
<style>
body { font-family: sans-serif; }
span[contenteditable] {
padding: 0.25em;
border: 2px solid #00000080;
border-radius: 0.5em;
background-color: #80808040;
line-height: 170%;
}
.password { color: transparent; }
.password:focus { color: black; }
em { background-color: lightyellow; }
li { padding: 0.25em; }
button {
font-size: 100%;
padding: 0.25em 1em;
border-radius: 0.25em;
}
button:hover { background-color: lightgreen; }
button:active { background-color: limegreen; }
#link {
background-color: lightgreen;
}
</style>
</head>
<body>
<h1>Scale Reward Source</h1>
<h2>Description</h2>
<p>This source will, from fragment input in the URL, set the world scale for the running application in SteamVR, and then reset it after a delay.</p>
<p>It is meant to be used as a browser source in <a href="https://obsproject.com/" target="_blank">OBS</a>, but you can use the link building form just opening this file in a browser.</p>
<h2>Instructions</h2>
<ol>
<li>This requires <a href="https://github.com/BOLL7708/OpenVR2WS/releases" target="_blank">OpenVR2WS</a> to run, launch it with SteamVR.</li>
<li>In the application, check <em>Enable remote setting updates</em> and click <em>Set password</em> to set a password.</li>
<li>Use the form below with said password and other information to build a link.</li>
<li>Load this link in an OBS Browser Source, set it to <em>Shutdown source when not visible</em> so you can trigger this when toggling the source off and on again.</li>
<li>To make this a dynamic reward, you would need to use a solution that can change the link in the browser source.</li>
<li>To make this invisible in OBS, just set the source size to be 1x1 or body opacity to 0 in the custom CSS field.</li>
</ol>
<h2>Build Link</h2>
<p title="Port set in OpenVR2WS.">OpenVR2WS port: <span contenteditable="true" id="port">7708</span></p>
<p title="Password set in OpenVR2WS.">OpenVR2WS password: <span contenteditable="true" id="password" class="password"></span></p>
<p title="The World Scale value to set in percent.">World scale: <span contenteditable="true" id="scale">50</span>%</p>
<p title="Duration of the scale change in seconds.">Scale duration: <span contenteditable="true" id="seconds">5</span> second(s), will not reset if set to 0.</p>
<p title="The minimum value to clamp the scale change to.">Minimum scale: <span contenteditable="true" id="min">10</span>%</p>
<p title="The maximum value to clamp the scale change to.">Maximum scale: <span contenteditable="true" id="max">1000</span>%</p>
<p><button id="button" onclick="handleLink()">Copy link</button> Link: <span id="link" contenteditable="true"></span></p>
<h2>Status Output</h2>
<ol id="status"></ol>
</body>
<script>
const
portSpan = document.querySelector('#port'),
passwordSpan = document.querySelector('#password'),
scaleSpan = document.querySelector('#scale'),
secondsSpan = document.querySelector('#seconds'),
minSpan = document.querySelector('#min'),
maxSpan = document.querySelector('#max'),
button = document.querySelector('#button'),
link = document.querySelector('#link')
const canCopy = (typeof navigator.clipboard.write == 'function')
if(!canCopy) button.innerHTML = 'Build Link'
function handleLink() {
const
port = portSpan.innerHTML,
password = passwordSpan.innerHTML,
scale = scaleSpan.innerHTML,
seconds = secondsSpan.innerHTML,
min = minSpan.innerHTML,
max = maxSpan.innerHTML,
documentURL = window.location.href,
fragmentVars = new URLSearchParams({port,password,scale,seconds,min,max}),
triggerURL = `${documentURL}#${fragmentVars}`
link.innerHTML = triggerURL
if(canCopy) setClipboard(triggerURL)
}
(async()=>{
const parsedHash = new URLSearchParams(
window.location.hash.substring(1) // any_hash_key=any_value
);
const
inScale = parsedHash.get('scale') ?? 50,
port = parsedHash.get('port') ?? 7708,
password = parsedHash.get('password') ?? '',
duration = parseInt(parsedHash.get('seconds') ?? '5'),
minScale = parseInt(parsedHash.get('min') ?? '10'),
maxScale = parseInt(parsedHash.get('max') ?? '1000'),
scale = Math.max(minScale, Math.min(maxScale, inScale)),
hash = await hashPassword(password)
let appId = ''
portSpan.innerHTML = port
passwordSpan.innerHTML = password
secondsSpan.innerHTML = duration.toString()
minSpan.innerHTML = minScale.toString()
maxSpan.innerHTML = maxScale.toString()
scaleSpan.innerHTML = scale.toString()
const status = document.querySelector('#status')
function printStatus(message) {
status.innerHTML += `<li>${message}</li>`
}
printStatus(`Will set scale to ${scale} for ${duration} second(s).`)
const socket = new WebSocket(`ws://localhost:${port}`)
socket.onopen = ()=>{
printStatus('Websocket connected.\n')
}
socket.onmessage = (event)=>{
try {
const response = JSON.parse(event.data)
switch(response?.key) {
case 'RemoteSetting':
printStatus(`Response: ${response?.data?.message}`)
break
case 'ApplicationInfo':
appId = response.data.id
printStatus(`Got running app ID: ${appId}`)
if(password.length > 0) run()
else printStatus('Problem: Password is not set.')
break
}
} catch(e) {
console.error(e.message)
}
}
socket.onclose = ()=>{
printStatus('Websocket closed.')
}
socket.onerror = (e)=>{
printStatus('Websocket error: '+e.message)
}
function run() {
const payload = {
key:'RemoteSetting',
value: hash,
value2: appId,
value3:"worldScale",
value4:scale/100
}
const payloadStr = JSON.stringify(payload)
printStatus('Sending first payload.')
socket.send(payloadStr)
printStatus('Waiting to send second payload.')
if(duration) setTimeout(()=>{
printStatus('Sending second payload.')
payload.value4 = 1 // Reset world scale
const payloadStr2 = JSON.stringify(payload)
socket.send(payloadStr2)
setTimeout(()=>{ // Terminate connection
socket.close()
}, 1000)
}, duration * 1000)
}
})()
async function hashPassword(password) {
const
encoder = new TextEncoder(),
data = encoder.encode(password),
hashBuffer = await crypto.subtle.digest('SHA-256', data)
return btoa(String.fromCharCode(...new Uint8Array(hashBuffer)), 'utf8')
}
function setClipboard(text) {
navigator.clipboard.writeText(text).then()
}
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment