@@ -3,7 +3,7 @@ | |||||
| Combat knife | Combat knife | - | | - | - | | | Combat knife | Combat knife | - | | - | - | | ||||
| Oriental sword | Katana | - | | - | - | | | Oriental sword | Katana | - | | - | - | | ||||
| Twin pistols | Beretta M9 | 9x19mm Parabellum | 15 | - | 381 | | | Twin pistols | Beretta M9 | 9x19mm Parabellum | 15 | - | 381 | | ||||
| Shotgun | Ithaca Model 37 | 12 gauge 2.75" shells | 8 | - | - | | |||||
| Shotgun | Ithaca Model 37 | 12 gauge 2.75" shells | 8 | - | 410 | | |||||
| Sub-machine gun | H&K MP5A3 | 9x19mm Parabellum | 30 | 800 (cyclic) | 400 | | | Sub-machine gun | H&K MP5A3 | 9x19mm Parabellum | 30 | 800 (cyclic) | 400 | | ||||
| Sub-machine gun | H&K MP5K | 9x19mm Parabellum | 30 | 900 (cyclic) | 375 | | | Sub-machine gun | H&K MP5K | 9x19mm Parabellum | 30 | 900 (cyclic) | 375 | | ||||
| Sub-machine gun | H&K MP7 | HK 4.6x30mm | 30 | 950 (cyclic) | 735 | | | Sub-machine gun | H&K MP7 | HK 4.6x30mm | 30 | 950 (cyclic) | 735 | | ||||
@@ -0,0 +1,60 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Audio Interval Test</title> | |||||
<style> | |||||
label { | |||||
position: relative; | |||||
} | |||||
label > span { | |||||
position: absolute; | |||||
left: -999999px; | |||||
} | |||||
label + input[type="checkbox"] { | |||||
position: absolute; | |||||
left: -999999px; | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<form id="formAudio"> | |||||
<fieldset> | |||||
<legend> | |||||
Repeating Sounds | |||||
</legend> | |||||
<div> | |||||
<label> | |||||
<span>Interval (bpm)</span> | |||||
<input | |||||
type="number" | |||||
name="interval" | |||||
value="750" | |||||
min="100" | |||||
max="1000" | |||||
/> | |||||
</label> | |||||
<input | |||||
type="checkbox" | |||||
name="play" | |||||
readonly | |||||
tabindex="-1" | |||||
checked | |||||
/> | |||||
</div> | |||||
<div> | |||||
<label> | |||||
<span>Interval (ms)</span> | |||||
<output>-</output> | |||||
</label> | |||||
</div> | |||||
<button type="submit"> | |||||
Toggle Sound | |||||
</button> | |||||
</fieldset> | |||||
</form> | |||||
<script src="index.js"></script> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,57 @@ | |||||
function AudioForm(el) { | |||||
const interval = el.elements['interval'] | |||||
const play = el.elements['play'] | |||||
const output = el.querySelector('output') | |||||
const audioPool = [] | |||||
let intervalHandle = null | |||||
let audioIndex = 0 | |||||
const items = 50 | |||||
for (let i = 0; i < items; i += 1) { | |||||
const newAudio = new Audio('./sfx/weapons/guns/smg-hkmp7/1_unload_var0.wav') | |||||
newAudio.volume = 0.2; | |||||
newAudio.addEventListener('canplaythrough', () => { | |||||
audioPool[i].ready = true | |||||
}) | |||||
newAudio.addEventListener('ended', () => { | |||||
newAudio.pause() | |||||
newAudio.currentTime = 0 | |||||
}) | |||||
audioPool[i] = { | |||||
audio: newAudio, | |||||
ended: true, | |||||
ready: false, | |||||
} | |||||
newAudio.load() | |||||
} | |||||
const playSound = () => { | |||||
for (let i = 0; i < items; i += 1) { | |||||
if (!audioPool[i].ready) { | |||||
return; | |||||
} | |||||
} | |||||
audioPool[audioIndex].audio.play(); | |||||
audioIndex = (audioIndex + 1) % items; | |||||
} | |||||
el.addEventListener('submit', (e) => { | |||||
e.preventDefault() | |||||
const ms = 60000 / Number(interval.value); | |||||
output.innerText = `${ms} ms` | |||||
if (play.checked) { | |||||
intervalHandle = window.setInterval(() => { | |||||
playSound() | |||||
}, ms) | |||||
} else { | |||||
window.clearInterval(intervalHandle); | |||||
} | |||||
play.checked = !play.checked | |||||
}) | |||||
} | |||||
const audioForm = new AudioForm(window.document.getElementById('formAudio')) |
@@ -0,0 +1 @@ | |||||
../../assets/sfx |
@@ -0,0 +1,180 @@ | |||||
<!DOCTYPE HTML> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<title>Websocket Client</title> | |||||
<style> | |||||
form { | |||||
margin: 2em 0; | |||||
} | |||||
fieldset { | |||||
display: contents; | |||||
} | |||||
legend { | |||||
position: absolute; | |||||
left: -999999px; | |||||
} | |||||
legend ~ div { | |||||
margin: 0.5em 0; | |||||
} | |||||
legend ~ div > div { | |||||
display: flex; | |||||
gap: 0.5em; | |||||
} | |||||
legend ~ div > div :first-child { | |||||
flex: auto; | |||||
} | |||||
legend ~ div > div :first-child > label { | |||||
display: block; | |||||
width: 100%; | |||||
} | |||||
legend ~ div > div :first-child > label > input { | |||||
display: block; | |||||
width: 100%; | |||||
box-sizing: border-box; | |||||
} | |||||
label { | |||||
position: relative; | |||||
} | |||||
label > span { | |||||
position: absolute; | |||||
left: -999999px; | |||||
} | |||||
label + input[type="checkbox"] { | |||||
position: absolute; | |||||
left: -999999px; | |||||
} | |||||
textarea { | |||||
width: 100%; | |||||
box-sizing: border-box; | |||||
display: block; | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<h1>Websocket Client</h1> | |||||
<form | |||||
id="formConnection" | |||||
> | |||||
<fieldset> | |||||
<legend>Server</legend> | |||||
<div> | |||||
<div> | |||||
<div> | |||||
<label> | |||||
<span>URL</span> | |||||
<input | |||||
type="url" | |||||
name="serverUrl" | |||||
autocomplete="off" | |||||
value="ws://localhost:42069" | |||||
required | |||||
> | |||||
</label> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div> | |||||
<div> | |||||
<div> | |||||
<label> | |||||
<span>Username</span> | |||||
<input | |||||
type="text" | |||||
name="username" | |||||
autocomplete="off" | |||||
value="Web" | |||||
required | |||||
> | |||||
</label> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<button | |||||
type="submit" | |||||
name="connect" | |||||
> | |||||
Connect | |||||
</button> | |||||
</fieldset> | |||||
</form> | |||||
<form | |||||
id="formMessage" | |||||
> | |||||
<fieldset | |||||
name="fieldset" | |||||
disabled | |||||
> | |||||
<legend>Message</legend> | |||||
<div> | |||||
<div> | |||||
<div> | |||||
<label> | |||||
<span>Message</span> | |||||
<input | |||||
type="text" | |||||
value="" | |||||
autocomplete="off" | |||||
name="message" | |||||
placeholder="Type your message" | |||||
> | |||||
</label> | |||||
<input | |||||
type="checkbox" | |||||
name="binary" | |||||
readonly | |||||
tabindex="-1" | |||||
/> | |||||
</div> | |||||
<button | |||||
type="submit" | |||||
> | |||||
Send as Text (Enter) | |||||
</button> | |||||
<button | |||||
name="submitBinary" | |||||
type="button" | |||||
> | |||||
Send as Binary (Shift + Enter) | |||||
</button> | |||||
</div> | |||||
</div> | |||||
</fieldset> | |||||
</form> | |||||
<form | |||||
id="formLogs" | |||||
> | |||||
<fieldset> | |||||
<legend>Logs</legend> | |||||
<div> | |||||
<label> | |||||
<span>Logs</span> | |||||
<textarea | |||||
rows="10" | |||||
cols="50" | |||||
name="logs" | |||||
readonly | |||||
></textarea> | |||||
</label> | |||||
</div> | |||||
<button | |||||
type="submit" | |||||
> | |||||
Clear Logs | |||||
</button> | |||||
</fieldset> | |||||
</form> | |||||
<script src="index.js"></script> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,149 @@ | |||||
const stringifyData = async value => { | |||||
const isString = typeof value === 'string' | |||||
const resolve = () => ( | |||||
isString | |||||
? Promise.resolve(value) | |||||
: value.arrayBuffer() | |||||
) | |||||
const a = await resolve() | |||||
if (isString) { | |||||
return `String:${a.length}(${a})` | |||||
} | |||||
const bytes = Array.from(new Uint8Array(a)).map(c => Number(c).toString(16).padStart(2, '0')).join(' ') | |||||
return `Binary:${a.byteLength}(${bytes})` | |||||
} | |||||
function ConnectionForm(el) { | |||||
let ws = null | |||||
el.addEventListener('submit', e => { | |||||
e.preventDefault() | |||||
if (!ws) { | |||||
const username = el.elements['username'].value | |||||
const baseUrl = el.elements['serverUrl'].value | |||||
ws = new WebSocket(`${baseUrl}?username=${username}`) | |||||
ws.addEventListener('open', () => { | |||||
this.messageForm.enable() | |||||
this.logsForm.append('Connection opened') | |||||
el.elements['serverUrl'].setAttribute('disabled', 'disabled') | |||||
el.elements['username'].setAttribute('disabled', 'disabled') | |||||
el.elements['connect'].innerText = 'Disconnect' | |||||
}) | |||||
ws.addEventListener('message', evt => { | |||||
stringifyData(evt.data).then(value => { | |||||
this.logsForm.append(`Recv: ${value}`) | |||||
}) | |||||
}) | |||||
ws.addEventListener('error', evt => { | |||||
console.error(evt) | |||||
}) | |||||
ws.addEventListener('close', event => { | |||||
this.messageForm.reset() | |||||
this.messageForm.disable() | |||||
this.logsForm.append(`Connection closed: wasClean: ${event.wasClean}, evCode: ${event.code}`) | |||||
el.elements['serverUrl'].removeAttribute('disabled') | |||||
el.elements['username'].removeAttribute('disabled') | |||||
el.elements['connect'].innerText = 'Connect' | |||||
ws = null | |||||
}) | |||||
return | |||||
} | |||||
ws.close() | |||||
}) | |||||
this.send = value => { | |||||
if (!ws) { | |||||
return | |||||
} | |||||
stringifyData(value).then(v => { | |||||
this.logsForm.append(`Send: ${v}`) | |||||
ws.send(value) | |||||
}) | |||||
} | |||||
} | |||||
function MessageForm(el) { | |||||
const inputMessage = el.elements['message'] | |||||
const binary = el.elements['binary'] | |||||
inputMessage.addEventListener('keydown', e => { | |||||
if (e.key !== 'Shift') { | |||||
return; | |||||
} | |||||
binary.checked = true; | |||||
}); | |||||
inputMessage.addEventListener('keyup', e => { | |||||
if (e.key !== 'Shift') { | |||||
return; | |||||
} | |||||
binary.checked = false; | |||||
}); | |||||
el.addEventListener('submit', e => { | |||||
e.preventDefault() | |||||
if (inputMessage.value.length < 1) { | |||||
return | |||||
} | |||||
this.connectionForm.send( | |||||
binary.checked | |||||
? new Blob(inputMessage.value.split('')) | |||||
: inputMessage.value, | |||||
); | |||||
inputMessage.value = '' | |||||
inputMessage.focus() | |||||
}) | |||||
el.elements['submitBinary'].addEventListener('click', e => { | |||||
e.preventDefault() | |||||
if (inputMessage.value.length < 1) { | |||||
return | |||||
} | |||||
this.connectionForm.send(new Blob(inputMessage.value.split(''))) | |||||
inputMessage.value = '' | |||||
inputMessage.focus() | |||||
}) | |||||
this.reset = () => { | |||||
inputMessage.value = '' | |||||
} | |||||
this.enable = () => { | |||||
el.elements['fieldset'].removeAttribute('disabled') | |||||
} | |||||
this.disable = () => { | |||||
el.elements['fieldset'].setAttribute('disabled', 'disabled') | |||||
} | |||||
} | |||||
function LogsForm(el) { | |||||
let logs = el.elements['logs'] | |||||
el.addEventListener('submit', e => { | |||||
e.preventDefault() | |||||
logs.value = '' | |||||
}) | |||||
this.append = data => { | |||||
logs.value += data + '\n' | |||||
logs.scrollTop = logs.scrollHeight | |||||
} | |||||
} | |||||
const logsForm = new LogsForm(window.document.getElementById('formLogs')) | |||||
const messageForm = new MessageForm(window.document.getElementById('formMessage')) | |||||
const connectionForm = new ConnectionForm(window.document.getElementById('formConnection')) | |||||
connectionForm.logsForm = logsForm | |||||
connectionForm.messageForm = messageForm | |||||
messageForm.connectionForm = connectionForm |