Skip to content

Instantly share code, notes, and snippets.

@barryosull
Last active October 15, 2022 00:31
Show Gist options
  • Save barryosull/0185bfd1cd4580f0df2f86a39a940a6a to your computer and use it in GitHub Desktop.
Save barryosull/0185bfd1cd4580f0df2f86a39a940a6a to your computer and use it in GitHub Desktop.
DND prototype I made for my first DM session
<html>
<head>
<title>Dnd - Lost mines of Phandelver</title>
<style type="text/css">
body {
margin: 0 10px;
font-family: Arial;
background-color: darkgreen;
}
#background {
width: 100%;
height: 100%;
position: fixed;
top: 0px;
left: 0px;
z-index: 1;
background-position: center;
background-repeat: repeat;
background-size: cover;
filter: blur(16px);
}
#image {
height: 100%;
position: fixed;
left: 0;
right: 0;
z-index: 2;
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
#music {
position: fixed;
right: 0px;
bottom: 0px;
height: 150px;
width: 300px;
z-index: 3;
}
#controls {
position: fixed;
top: 0px;
right: 0px;
z-index: 4;
}
#controls select {
width: 90px;
}
#characters {
position: fixed;
top: 0px;
left: 0px;
z-index: 5;
}
.character {
position: absolute;
box-sizing: border-box;
width: 70px;
height: 70px;
border-radius: 35px;
border: 4px ridge #000;
cursor: pointer;
box-shadow: 1px 5px 15px -2px rgba(0, 0, 0, 0.75);
z-index: 1;
}
.player {
border-color: forestgreen;
}
.npc {
border-color: darkblue;
}
.enemy {
border-color: darkred;
}
</style>
</head>
<body style="position: relative;">
<div id="image">
</div>
<div id="background">
</div>
<div id="controls">
<select id="area"></select><br>
<select id="music-controls"></select><br>
</div>
<div id="characters">
</div>
<iframe id="music" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<script type="text/javascript">
let currImageIndex = 0;
const images = [
'title.jpeg',
'map.jpg',
'intro.jpeg',
'road.webp',
'cave-1.png',
'cave-2.png',
'cave-3.png',
'cave-4.png',
'cave-5.png',
'cave-6.png',
'cave-7.png',
'cave-8.png',
'phandalin.jpg',
];
const players = [
'fighter.png',
'wizard.png',
'rogue.png',
];
const music = {
intro: 'XbS3tPO9sUs',
forest: '6Em9tLXbhfo',
cave: 'E72yDpAfrgY',
combat: ['8Q7cioftmKs', 'H8n7K3jABhI', 'fq8OSrIUST4'],
};
const npcs = [];
function imageForward() {
const nextImageIndex = currImageIndex + 1;
if (!images[nextImageIndex]) {
return;
}
changeImage(nextImageIndex);
}
function imageBackward() {
const nextImageIndex = currImageIndex - 1;
if (!images[nextImageIndex]) {
return;
}
changeImage(nextImageIndex);
}
function changeImage(imageIndex) {
let selectElem = document.getElementById('area');
selectElem.value = imageIndex;
currImageIndex = imageIndex;
const image = images[imageIndex];
const backgroundElem = document.getElementById('background');
const imageElem = document.getElementById('image');
backgroundElem.style['background-image'] = 'url("images/' + image + ' ")';
imageElem.style['background-image'] = 'url("images/' + image + ' ")';
}
var preloadedImages = new Array()
function preloadImages() {
for (var i in images) {
preloadedImages[i] = new Image();
preloadedImages[i].src = 'images/' + images[i];
}
}
function prepareAreaControls() {
let selectElem = document.getElementById('area');
for (var i in images) {
const imageTitle = images[i].split('.')[0];
const optionHtml = '<option value="' + i + '">' + imageTitle + '</option>';
selectElem.innerHTML += optionHtml;
}
selectElem.onchange = function() {
changeImage(parseInt(this.value));
}
}
function prepareMusicControls() {
let selectElem = document.getElementById('music-controls');
for (var key in music) {
const optionHtml = '<option value="' + key + '">' + key + '</option>';
selectElem.innerHTML += optionHtml;
}
selectElem.onchange = function() {
playMusic(music[this.value]);
}
}
function prepareKeyboardShortcuts() {
document.onkeydown = function (e) {
e = e || window.event;
if (e.key == 'g') {
addGoblin();
}
if (e.key == 'b') {
addBugbear();
}
if (e.key == 's') {
addSildar();
}
if (e.key == 'w') {
addWolf();
}
if (e.key == 'c') {
clearNpcs();
}
if (e.code == 'ArrowLeft') {
imageBackward();
}
if (e.code == 'ArrowRight') {
imageForward();
}
}
}
function dragstartHandler(ev) {
ev.dataTransfer.setData("text/plain", ev.target.id);
}
function populatePlayers() {
for (var i in players) {
drawCharacter(players[i], 'player', players.length, i);
}
}
function addGoblin() {
const goblin = drawCharacter('goblin.png', 'enemy');
npcs.push(goblin);
}
function addBugbear() {
const bugbear = drawCharacter('bugbear.png', 'enemy');
npcs.push(bugbear);
}
function addWolf() {
const wolf = drawCharacter('wolf.png', 'enemy');
npcs.push(wolf);
}
function addSildar() {
const sildar = drawCharacter('sildar.png', 'npc');
npcs.push(sildar);
}
function clearNpcs() {
for (var i in npcs) {
npcs[i].remove();
}
}
characterId = 0;
function drawCharacter(image, type, batchSize, batchPosition) {
batchSize = batchSize ?? 1;
batchPosition = batchPosition ?? 0;
const charactersElem = document.getElementById('characters');
++characterId;
const characterElem = document.createElement("img");
characterElem.id = 'character_' + characterId;
characterElem.className = "character "+type;
characterElem.src = 'images/' + image;
const startX = ((batchSize-1)/2) * -80;
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
const offsetX = batchPosition * 80;
characterElem.style.left = (startX + (centerX - 35) + offsetX) + 'px';
characterElem.style.top = (centerY - 35) + 'px';
charactersElem.appendChild(characterElem);
makeDraggable(characterElem);
return characterElem;
}
var topCharacterZIndex = 1;
function makeDraggable(characterElem) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
const startDragging = function(e) {
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = stopDragging;
document.onmousemove = dragElement;
topCharacterZIndex++;
characterElem.style['z-index'] = topCharacterZIndex;
}
const dragElement = function(e) {
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
characterElem.style.left = (characterElem.offsetLeft - pos1) + "px";
characterElem.style.top = (characterElem.offsetTop - pos2) + "px";
}
const stopDragging = function() {
document.onmouseup = null;
document.onmousemove = null;
}
characterElem.addEventListener("mousedown", startDragging);
}
function playMusic(youtubeId) {
if (Array.isArray(youtubeId)) {
youtubeId = youtubeId[Math.floor(Math.random() * youtubeId.length)];
}
document.getElementById('music').src = "https://www.youtube.com/embed/" + youtubeId + "?autoplay=1&t=0";
}
// Do the work
preloadImages();
prepareAreaControls();
prepareKeyboardShortcuts();
populatePlayers();
changeImage(0);
prepareMusicControls();
playMusic(music.intro);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment