commit c4c299f701c546c019df447d25ced573eb45adf6 Author: Benjamin Kraft Date: Mon Mar 20 14:24:04 2023 +0100 Working setup diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/project.json b/project.json new file mode 100644 index 0000000..35c0908 --- /dev/null +++ b/project.json @@ -0,0 +1,6 @@ +{ + "display_name": "Global draw sheet", + "info_text": "A draw sheet for everyone.", + "visible": true, + "tags": ["Tool", "Multiplayer"] +} \ No newline at end of file diff --git a/public/changelog.txt b/public/changelog.txt new file mode 100644 index 0000000..ab00d57 --- /dev/null +++ b/public/changelog.txt @@ -0,0 +1,55 @@ +1.0.0 > - Started versioning + - Contents: + - ColorPicker + - Color pick from screen + - Thickness range input + - Thickness preview + - Minimap + - Two Modes: + - Draw Freely (lines) + - Pixel Art + - Disabled browser scrolling, added own scrolling via mousewheel + +1.0.1 > - Made changelog.txt public + +1.1.0 > - Changed Graphics resolution from 8000/8000 to 5000/5000 + - Changed pixel count for pixel art mode from 250 to 500 + - Added Skip Button for first drawing + - Inverted progress bar :D + - Added Button to request server save of drawings + +1.1.1 > - Enabled button usability feedback + +1.2.0 > - Disabled grid for pixel art + - Added hotkey for new alpha "grid" + +1.2.1 > - Changed pixel count for pixel art mode from 500 to 1000 + - Activated anti aliasing for free mode, deactivated for pixel mode + - Color copy ignores alpha grid + +1.2.2 > - Seperated settings for free mode and pixel mode + - Added hotkey set for pixel grid (defaults to 'G') + - Hotkey for pixel grid loads from session data + +1.2.3 > - ColorPicker displays a list of recently used colors + - Recently used colors are also loaded from site session data + +1.2.4 > - Recently used color defaults to white instead of black + +1.2.5 > - Made hotkey feature more understandable -_- + +1.2.6 > - 3-digit hex values for ColorPicker instantly convert to 6-digit values + +1.2.7 > - Enabled moving also with w,a,s,d + +1.2.8 > - Updated downloadable zip with newest contents + +1.2.9 > - Better Loading screen and bug fixes + +1.2.10 > - Nicer selection rectangle borders on minimap + +1.2.11 > - Zooming via mousewheel works much better now and is relative to mouse position + +1.2.12 > - Enabled user feedback :) + +1.2.13 > - Loading works much faster now!!! \ No newline at end of file diff --git a/public/data/images/favicon.ico b/public/data/images/favicon.ico new file mode 100644 index 0000000..3172667 Binary files /dev/null and b/public/data/images/favicon.ico differ diff --git a/public/data/lib/benjocraeft/collision.js b/public/data/lib/benjocraeft/collision.js new file mode 100644 index 0000000..3941d1d --- /dev/null +++ b/public/data/lib/benjocraeft/collision.js @@ -0,0 +1,53 @@ +'use strict'; + +/* + 2D Collision detection and performing +*/ + +class Collision{ + + static ellipseToEllipse(e1, e2){ + + //Colliding angle of ball 1 to ball 2 using arc tan of both x and y differences + let collisionAngle = Math.atan2((e2.pos.y - e1.pos.y), (e2.pos.x - e1.pos.x)); + + //Converting directions of velocity vector of balls into angles + let d1 = Math.atan2(e1.vel.y, e1.vel.x); + let d2 = Math.atan2(e2.vel.y, e2.vel.x); + + //Ignoring mass effects new velocites are simply magnitude multiplied with value of angle differences + let newXspeed1 = e1.vel.mag() * Math.cos(d1 - collisionAngle); + let newYspeed1 = e1.vel.mag() * Math.sin(d1 - collisionAngle); + let newXspeed2 = e2.vel.mag() * Math.cos(d2 - collisionAngle); + let newYspeed2 = e2.vel.mag() * Math.sin(d2 - collisionAngle); + + //According to the principle of linear momentum, kinetic energy stays the same after collision, so velocities are now related to masses + let finalXspeed1 = ((e1.radius - e2.radius) * newXspeed1 + e2.radius * 2 * newXspeed2) / (e1.radius + e2.radius); + let finalYspeed1 = newYspeed1; + let finalXspeed2 = (e1.radius * 2 * newXspeed1 + (e2.radius - e1.radius) * newXspeed2) / (e1.radius + e2.radius); + let finalYspeed2 = newYspeed2; + + //Values of collisionAngle + let cosAngle = Math.cos(collisionAngle); + let sinAngle = Math.sin(collisionAngle); + + //To also keep velocites relative to pure collisionAngle, subtract sin*x from cos*x and add sin*y to cos*y because coordSystem has y = 0 on the top + let u1x = cosAngle * finalXspeed1 - sinAngle * finalYspeed1; + let u1y = sinAngle * finalXspeed1 + cosAngle * finalYspeed1; + let u2x = cosAngle * finalXspeed2 - sinAngle * finalYspeed2; + let u2y = sinAngle * finalXspeed2 + cosAngle * finalYspeed2; + + //Set new velocities to both balls + e1.vel.x = u1x; + e1.vel.y = u1y; + e2.vel.x = u2x; + e2.vel.y = u2y; + + //Move balls one vx/vy forward to avoid double inverting collision detection + e1.pos.x += e1.vel.x; + e1.pos.y += e1.vel.y; + e2.pos.x += e2.vel.x; + e2.pos.y += e2.vel.y; + } + +} \ No newline at end of file diff --git a/public/data/lib/benjocraeft/colorPicker.js b/public/data/lib/benjocraeft/colorPicker.js new file mode 100644 index 0000000..328922b --- /dev/null +++ b/public/data/lib/benjocraeft/colorPicker.js @@ -0,0 +1,337 @@ +class ColorPicker { + + constructor(){ + this.movingObject = ""; + } + + updateFromGraphical(){ + this.h = (1 - $("#hue_picker").position().top / $("#hue").height()) * 360; + this.s = ($("#sb_picker").position().left + 8) / $("#saturation").width() * 100; + this.v = (1 - ($("#sb_picker").position().top + 8) / $("#value").height()) * 100; + + this.r = HSVtoRGB(this.h, this.s, this.v).r; + this.g = HSVtoRGB(this.h, this.s, this.v).g; + this.b = HSVtoRGB(this.h, this.s, this.v).b; + + this.hex = RGBtoHEX(this.r, this.g, this.b); + + this.updateInterface(); + } + + updateFromHSV(){ + this.h = $($("#color_picker_hsv input")[0]).val(); + this.s = $($("#color_picker_hsv input")[1]).val(); + this.v = $($("#color_picker_hsv input")[2]).val(); + + this.r = HSVtoRGB(this.h, this.s, this.v).r; + this.g = HSVtoRGB(this.h, this.s, this.v).g; + this.b = HSVtoRGB(this.h, this.s, this.v).b; + + this.hex = RGBtoHEX(this.r, this.g, this.b); + + this.updateFromHEX(null, true); + + this.updateInterface(); + } + + updateFromRGB(rgba){ + if (rgba){ + this.r = rgba[0]; + this.g = rgba[1]; + this.b = rgba[2]; + } else { + this.r = $($("#color_picker_rgb input")[0]).val(); + this.g = $($("#color_picker_rgb input")[1]).val(); + this.b = $($("#color_picker_rgb input")[2]).val(); + } + + + this.h = RGBtoHSV(this.r, this.g, this.b).h; + this.s = RGBtoHSV(this.r, this.g, this.b).s; + this.v = RGBtoHSV(this.r, this.g, this.b).v; + + this.hex = RGBtoHEX(this.r, this.g, this.b); + + this.updateFromHEX(null, true); + + this.updateInterface(); + } + + updateFromHEX(input, otf){ + if (!otf){ //Not on the fly + if ($(input).val().isValidHEX()) this.hex = $(input).val(); + else { + alert("Error!"); + return; + } + } + + this.r = HEXtoRGB(this.hex).r; + this.g = HEXtoRGB(this.hex).g; + this.b = HEXtoRGB(this.hex).b; + + this.h = RGBtoHSV(this.r, this.g, this.b).h; + this.s = RGBtoHSV(this.r, this.g, this.b).s; + this.v = RGBtoHSV(this.r, this.g, this.b).v; + + this.hex = RGBtoHEX(this.r, this.g, this.b); + + this.updateInterface(); + } + + setFromUsed(used){ + let usedColorsData = p.getItem('usedColors'); + let index = $('.used_color').toArray().reverse().indexOf(used); + let color = usedColorsData[index]; + this.hex = color; + this.updateFromHEX(null, true); + } + + setUsed(color){ + this.lastSetUsedColor = color; + let usedColors = $('.used_color').toArray().reverse(); + let usedColorsData = p.getItem('usedColors'); + if (usedColorsData.includes(color)){ + let index = usedColorsData.indexOf(color); + let swap = usedColorsData[index - 1]; + if (swap){ + usedColorsData[index - 1] = color; + usedColorsData[index] = swap; + } + } else { + set(color, 0); + function set(c, i){ + let oldColor = usedColorsData[i]; + usedColorsData[i] = c; + + if (usedColorsData.length > i + 1) + set(oldColor, i + 1); + } + } + + for (let c of usedColorsData){ + let dom = usedColors[usedColorsData.indexOf(c)]; + $(dom).css('background-color', c); + } + + //p.storeItem('usedColors', usedColorsData); + } + + updateInterface(){ + + let r = $($("#color_picker_rgb input")[0]), + g = $($("#color_picker_rgb input")[1]), + b = $($("#color_picker_rgb input")[2]), + h = $($("#color_picker_hsv input")[0]), + s = $($("#color_picker_hsv input")[1]), + v = $($("#color_picker_hsv input")[2]), + hex = $("#color_picker_hex"), + bgColor; + + r.val(round(this.r)); + g.val(round(this.g)); + b.val(round(this.b)); + h.val(round(this.h)); + s.val(round(this.s)); + v.val(round(this.v)); + bgColor = color(this.r, 0, 0); + r.css({ + "background-color": bgColor.toString(), + "color": fontColor(bgColor) + }); + bgColor = color(0, this.g, 0); + g.css({ + "background-color": bgColor.toString(), + "color": fontColor(bgColor) + }); + bgColor = color(0, 0, this.b); + b.css({ + "background-color": bgColor.toString(), + "color": fontColor(bgColor) + }); + colorMode(HSL); + bgColor = color(this.h, 100, 50); + h.css({ + "background-color": bgColor.toString(), + "color": fontColor(bgColor) + }); + bgColor = color(this.h, this.s, 100 - this.s / 2); + s.css({ + "background-color": bgColor.toString(), + "color": fontColor(bgColor) + }); + bgColor = color(this.h, 100, this.v / 2); + v.css({ + "background-color": bgColor.toString(), + "color": fontColor(bgColor) + }); + colorMode(RGB); + hex.val(this.hex); + hex.css({ + "background-color": this.hex, + "color": fontColor(color(this.hex)) + }); + + + let sRGB = HSVtoRGB(this.h, 100, 100); + let saturationBackground = "linear-gradient(to right, #FFF 0%, rgb(" + + sRGB.r + "," + + sRGB.g + "," + + sRGB.b + ") 100%)"; + $("#hue_picker").css("top", (1 - this.h / 360) * $("#hue").height()); + $("#sb_picker").css({ + "left": this.s / 100 * $("#saturation").width() - 8, + "top": (1 - this.v / 100) * $("#value").height() - 8 + }); + $("#saturation").css("background", saturationBackground); + $("#thickness_preview").css("background-color", this.hex); + } + + mousePressed(){ + let x = p.winMouseX - $("#saturation").offset().left; + let y = p.winMouseY - $("#value").offset().top; + if (x > 0 && x < $("#saturation").width() && y > 0 && y < $("#value").height()){ + this.movingObject = "sb"; + this.mouseDragged(); + } + if (x > $("#saturation").width() + 6 && x < $("#saturation").width() + 6 + $("#hue").width() && y > 0 && y < $("#hue").height()){ + this.movingObject = "hue"; + this.mouseDragged(); + } + } + + mouseDragged(){ + if (this.movingObject == "hue"){ + let objH = $("#hue"); + let picker = $("#hue_picker"); + let h = p.winMouseY - objH.offset().top; + if (h > 0 && h < objH.height()){ + picker.css("top", h - 1); + } else if (h > objH.height()){ + picker.css("top", objH.height() - 1); + } else if (h < 0){ + picker.css("top", -1); + } + + this.updateFromGraphical(); + } + + if (this.movingObject == "sb"){ + let objS = $("#saturation"); + let objV = $("#value"); + let picker = $("#sb_picker"); + let s = p.winMouseX - objS.offset().left; + let v = p.winMouseY - objV.offset().top; + if (s > 0 && s < objS.width()){ + picker.css("left", s - 8); + } else if (s < 0){ + picker.css("left", -8); + } else if (s < objS.width()){ + picker.css("left", objS.width() - 8); + } + if (v > 0 && v < objV.height()){ + picker.css("top", v - 8); + } else if (v < 0){ + picker.css("top", -8); + } else if (v > objV.height()){ + picker.css("top", objV.height() - 8); + } + + this.updateFromGraphical(); + } + } + + mouseReleased(){ + this.movingObject = ""; + } + + getColor(){ + return this.hex; + } +} + +function fontColor(bg){ + //http://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx + let o = (red(bg) * 299 + green(bg) * 587 + blue(bg) * 114) / 1000; + return (o > 125) ? "#000" : "#CCC"; +} + +//www.stackoverflow.com --> +function RGBtoHEX(r, g, b) { + let rgb = b | (g << 8) | (r << 16); + return '#' + (0x1000000 + rgb).toString(16).slice(1); +} + +function HEXtoRGB(hex) { + let shorthandRegex = /^#?([a-fA-F\d])([a-fA-F\d])([a-fA-F\d])$/i; + hex = hex.replace(shorthandRegex, function(m, r, g, b) { + return r + r + g + g + b + b; + }); + let result = /^#?([a-fA-F\d]{2})([a-fA-F\d]{2})([a-fA-F\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; +} + +function HSVtoRGB(h, s, v) { + let r, g, b, i, f, p, q, t; + if (arguments.length === 1) { + s = h.s, v = h.v, h = h.h; + } + h /= 360; + s /= 100; + v /= 100; + i = Math.floor(h * 6); + f = h * 6 - i; + p = v * (1 - s); + q = v * (1 - f * s); + t = v * (1 - (1 - f) * s); + switch (i % 6) { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + return { + r: r * 255, + g: g * 255, + b: b * 255 + }; +} + +function RGBtoHSV(r, g, b) { + if (arguments.length === 1) { + g = r.g, b = r.b, r = r.r; + } + let max = Math.max(r, g, b), min = Math.min(r, g, b), + d = max - min, + h, + s = (max === 0 ? 0 : d / max), + v = max / 255; + + switch (max) { + case min: h = 0; break; + case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break; + case g: h = (b - r) + d * 2; h /= 6 * d; break; + case b: h = (r - g) + d * 4; h /= 6 * d; break; + } + + return { + h: h * 360, + s: s * 100, + v: v * 100 + }; +} + +let round = (val) => p.round(val); +let color = (val) => p.color(val); +let red = (val) => p.red(val); +let green = (val) => p.green(val); +let blue = (val) => p.blue(val); +let colorMode = (val) => p.colorMode(val); +let HSL = p.HSL; +let RGB = p.RGB; \ No newline at end of file diff --git a/public/data/lib/benjocraeft/cookie.js b/public/data/lib/benjocraeft/cookie.js new file mode 100644 index 0000000..0ca3ed1 --- /dev/null +++ b/public/data/lib/benjocraeft/cookie.js @@ -0,0 +1,29 @@ +function setCookie(name, value, years){ + let expires = ""; + if (years){ + let date = new Date(); + date.setTime(date.getTime() + (years * 365 * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toUTCString(); + } + document.cookie = name + "=" + value + expires + "; path=/"; +} + +function getCookie(name){ + let nameEQ = name + "="; + let ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; i++){ + let c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); + } + return null; +} + +function deleteCookies(){ + for (let i = 0; i < arguments.length; i++) setCookie(arguments[i], "", -1); +} + +function deleteAllCookies(){ + let cookies = document.cookie.split(";"); + for (let i = 0; i < cookies.length; i++) deleteCookies(cookies[i].split("=")[0]); +} \ No newline at end of file diff --git a/public/data/lib/benjocraeft/loader.js b/public/data/lib/benjocraeft/loader.js new file mode 100644 index 0000000..8f6b4f2 --- /dev/null +++ b/public/data/lib/benjocraeft/loader.js @@ -0,0 +1,30 @@ +class Loader{ + + constructor(dom){ + this.dim = createVector($(dom).width(), $(dom).height()); + this.c = createGraphics(this.dim.x, this.dim.y); + this.c.parent(dom); + this.radius = min(this.dim.x, this.dim.y) * 0.4; + this.center = createVector(this.dim.x / 2, this.dim.y / 2); + $(dom).find('canvas').show(); + this.angle = 0; + } + + update(){ + this.angle += PI / 10; + } + + display(){ + let c = this.c; + c.clear(); + c.noFill(); + c.stroke(0); + c.strokeWeight(5); + c.arc(this.center.x, this.center.y, this.radius * 2, this.radius * 2, this.angle, this.angle + PI + HALF_PI); + } + + destroy(){ + this.c.remove(); + } + +} \ No newline at end of file diff --git a/public/data/lib/benjocraeft/prototypes.js b/public/data/lib/benjocraeft/prototypes.js new file mode 100644 index 0000000..be71cea --- /dev/null +++ b/public/data/lib/benjocraeft/prototypes.js @@ -0,0 +1,30 @@ +Array.prototype.shuffle = function(){ + let currentIndex = this.length, temporaryValue, randomIndex; + while (0 != currentIndex){ + randomIndex = floor(random() * currentIndex); + currentIndex -= 1; + temporaryValue = this[currentIndex]; + this[currentIndex] = this[randomIndex]; + this[randomIndex] = temporaryValue; + } +} + +Array.prototype.copy = function(){ + return this.slice(0); +}; + +//Divides big Array into big multidimensional Array +Array.prototype.partitiate = function(dimensions){ + if (!dimensions) return this; + let parts = []; + while(this.length) parts.push(this.splice(0, round(pow(this.length, 1 / (1 + 1 / dimensions)))).partitiate(dimensions - 1)); + return parts; +} + +String.prototype.isValidHEX = function(){ + return /(^#[0-9A-Fa-f]{6}$)|(^#[0-9A-Fa-f]{3}$)/i.test(this); +} + +String.prototype.capitalize = function(){ + return this.charAt(0).toUpperCase() + this.slice(1); +} \ No newline at end of file diff --git a/public/data/lib/benjocraeft/technical.js b/public/data/lib/benjocraeft/technical.js new file mode 100644 index 0000000..483207c --- /dev/null +++ b/public/data/lib/benjocraeft/technical.js @@ -0,0 +1,48 @@ +function toTimeString(time, hoursWanted){ + + time = floor(time / 10); + + let hs = String(floor(time % 100)); + let fs = String(floor((time / 100) % 60)); + + if (hoursWanted){ + let min = String(floor(((time / 100) / 60) % 60)); + let hr = String(floor(((time / 100) / 60) / 60)); + + if (hs.length < 2) hs = "0" + hs; + if (fs.length < 2) fs = "0" + fs; + if (min.length < 2) min = "0" + min; + if (hr.length < 2) hr = "0" + hr; + + return hr + ":" + min + ":" + fs + ":" + hs; + } else { + let min = String(floor(((time / 100) / 60) % 60)); + + if (hs.length < 2) hs = "0" + hs; + if (fs.length < 2) fs = "0" + fs; + if (min.length < 2) min = "0" + min; + + return min + ":" + fs + ":" + hs; + } +} + + + + +function debugInformation(x, y){ + push(); + textSize(12); + textStyle(NORMAL); + stroke(255); + strokeWeight(1); + fill(255); + text("FPS : " + round(frameRate()), 10 + x, 10 + textAscent("FPS : ") + y); + text("MouseX : " + round(mouseX + x), 10 + x, 10 + textAscent("FPS : ") + 10 + textAscent("MouseX : ") + y); + text("MouseY : " + round(-mouseY - y), 10 + x, 10 + textAscent("FPS : ") + 10 + textAscent("MouseX : ") + 10 + textAscent("MouseY : ") + y); + pop(); +} + +function ranBool(chance){ + if (!chance) chance = 2; + return Math.floor(Math.random() * chance + 1) == chance ? true : false; +} \ No newline at end of file diff --git a/public/data/scripts/drawer.js b/public/data/scripts/drawer.js new file mode 100644 index 0000000..53dc8dc --- /dev/null +++ b/public/data/scripts/drawer.js @@ -0,0 +1,395 @@ +class Drawer{ + + constructor(){ + + this.dim = {x: 5000, y: 5000}; + this.viewport = { + x: (this.dim.x - p.width) / 2, + y: (this.dim.y - p.height) / 2, + maxX: this.dim.x, + maxY: this.dim.y, + scroll: 10, + zoom: 1, + zoomV: 2 + }; + this.oldMouseX = this.mouseX; + this.oldMouseY = this.mouseY; + this.map = new MiniMap($("#minimap"), "#minimap > canvas", true); + + //Free draw + this.lines = []; + this.linesToDraw = []; + this.linesImage = p.createGraphics(this.dim.x, this.dim.y); + this.linesImage.background(255); + + //Pixel art + this.pixels = []; + this.pixelsToFill = []; + this.gridPixelsToFill = []; + this.gridActive = false; + this.pixelsImage = p.createGraphics(this.dim.x, this.dim.y); + this.pixelsImage.background(255); + this.gridImage = p.createGraphics(this.dim.x, this.dim.y); + this.gridImage.background(255, 0); + this.gridImage.noStroke(); + this.gridImage.fill(0, 255 * 0.1); + this.pixelCount = {x: 1000, y: 1000}; + this.pixelSize = {x: this.dim.x / this.pixelCount.x, y: this.dim.y / this.pixelCount.y}; + for (let x = 0; x < this.pixelCount.x; x++){ + let column = []; + for (let y = 0; y < this.pixelCount.y; y++){ + let pixel = {x: x, y: y, c: "#FFFFFF"}; + column.push(pixel); + if (x % 2 == 0 && (y + 1) % 2 == 0 + || y % 2 == 0 && (x + 1) % 2 == 0) + this.gridPixelsToFill.push(pixel); + } + this.pixels.push(column); + } + + this.isCopying = false; + this.drawType = null; + this.thickness = parseInt($("#thickness").val()); + + this.pixelsMap = new MiniMap($("#pixels_map_holder"), "#pixels_map_holder > canvas", false); + this.linesMap = new MiniMap($("#lines_map_holder"), "#lines_map_holder > canvas", false); + } + + get image(){ + if (this.type === 'free') return this.linesImage; + if (this.type === 'pixel') return this.pixelsImage; + } + + get scrollSpeed(){ + return this.viewport.scroll; + } + + get mouseX(){ + return p.mouseX * this.viewport.zoom + this.viewport.x; + } + + get mouseY(){ + return p.mouseY * this.viewport.zoom + this.viewport.y; + } + + sendDrawing(){ + if (this.isDrawing && this.hasDrawnAllLines && this.hasFilledAllPixels){ + + if (this.type === 'free'){ + let pos1 = {x: p.round(this.oldMouseX), y: p.round(this.oldMouseY)}; + let pos2 = {x: p.round(this.mouseX), y: p.round(this.mouseY)}; + let color = colorPicker.getColor(); + + let line = {p1: pos1, p2: pos2, c: color, t: this.thickness}; + socket.emit('add-line', line); + } + + if (this.type === 'pixel'){ + let x = p.floor(this.mouseX / (this.dim.x / this.pixelCount.x)); + x = p.constrain(x, 0, this.pixelCount.x); + + let y = p.floor(this.mouseY / (this.dim.y / this.pixelCount.y)); + y = p.constrain(y, 0, this.pixelCount.y); + + let color = colorPicker.getColor(); + if (color === this.pixels[x][y].c) + return; + + let pixel = {x: x, y: y, c: color}; + socket.emit('fill-pixel', pixel); + } + + let color = colorPicker.getColor(); + if (colorPicker.lastSetUsedColor != color) + colorPicker.setUsed(color); + } + } + + draw(){ + p.clear(); + + if (this.hasDrawnAllLines && this.hasFilledAllPixels){ + this.moveViewport(); + this.map.draw(this.image, this.dim, this.viewport); + + + let x = this.viewport.x; + let y = this.viewport.y; + let w = p.width * this.viewport.zoom; + let h = p.height * this.viewport.zoom; + p.image(this.image, 0, 0, p.width, p.height, x, y, w, h); + + if (this.type === 'pixel' && this.gridActive){ + p.image(this.gridImage, 0, 0, p.width, p.height, x, y, w, h); + } + } + else { + this.pixelsMap.draw(this.pixelsImage, this.dim, this.viewport); + this.linesMap.draw(this.linesImage, this.dim, this.viewport); + this.updateProgress(); + } + + let drawSpeed = 800; + + for (let line of this.linesToDraw.slice(0, drawSpeed)) + this.drawLine(line); + this.linesToDraw.splice(0, drawSpeed); + if (this.linesToDraw.length === 0 && !this.hasDrawnAllLines && this.receivedLines && this.receivedPixels){ + this.hasDrawnAllLines = true; + if (this.hasFilledAllPixels) + $("#loading_drawings").fadeOut(200); + } + + for (let pixel of this.pixelsToFill.slice(0, drawSpeed)) + this.drawPixel(pixel); + this.pixelsToFill.splice(0, drawSpeed); + if (this.pixelsToFill.length === 0 && !this.hasFilledAllPixels && this.receivedPixels && this.receivedLines){ + this.hasFilledAllPixels = true; + if (this.hasDrawnAllLines) + $("#loading_drawings").fadeOut(200); + } + + for (let pixel of this.gridPixelsToFill.slice(0, drawSpeed)) + this.drawGridPixel(pixel); + this.gridPixelsToFill.splice(0, drawSpeed); + + + this.oldMouseX = this.mouseX; + this.oldMouseY = this.mouseY; + } + + moveViewport(){ + if (givesFeedback) + return; + + let y = 0, x = 0; + if (p.keyIsDown(40) || p.keyIsDown(83)) + y += this.scrollSpeed; + if (p.keyIsDown(38) || p.keyIsDown(87)) + y -= this.scrollSpeed; + if (p.keyIsDown(37) || p.keyIsDown(65)) + x -= this.scrollSpeed; + if (p.keyIsDown(39) || p.keyIsDown(68)) + x += this.scrollSpeed; + + this.viewport.x += x; + this.viewport.y += y; + + let maxX = this.viewport.maxX - p.width * this.viewport.zoom; + let maxY = this.viewport.maxY - p.height * this.viewport.zoom; + if (this.viewport.x > maxX) this.viewport.x = maxX; + if (this.viewport.y > maxY) this.viewport.y = maxY; + if (this.viewport.x < 0) this.viewport.x = 0; + if (this.viewport.y < 0) this.viewport.y = 0; + + if (x != 0 || y != 0) + this.sendDrawing(); + } + + addAll(lines){ + for (let line of lines) + this.addLine(line); + } + + drawLine(line){ + this.linesImage.strokeWeight(line.t); + this.linesImage.stroke(line.c); + this.linesImage.line(line.p1.x, line.p1.y, line.p2.x, line.p2.y); + } + + addLine(line){ + this.lines.push(line); + this.linesToDraw.push(line); + } + + fillPixel(pixel){ + this.pixels[pixel.x][pixel.y].c = pixel.c + this.pixelsToFill.push(pixel); + } + + fillAll(pixels){ + pixels.forEach(c => { + if (!c) return; + c.forEach(p => { + if (!p) return; + this.fillPixel(p); + }); + }); + } + drawPixel(pixel){ + let px = pixel.x * this.pixelSize.x; + let py = pixel.y * this.pixelSize.y; + let w = this.pixelSize.x; + let h = this.pixelSize.y; + + this.pixelsImage.fill(pixel.c); + this.pixelsImage.strokeWeight(1); + this.pixelsImage.noStroke(); + this.pixelsImage.rect(px, py, w, h); + } + + drawGridPixel(pixel){ + let px = pixel.x * this.pixelSize.x; + let py = pixel.y * this.pixelSize.y; + let w = this.pixelSize.x; + let h = this.pixelSize.y; + + this.gridImage.rect(px, py, w, h); + } + + onLinesLoaded(lines){ + this.receivedLines = lines; + console.log('Received all lines from server'); + + if (this.receivedPixels) + this.startDrawing(); + } + onPixelsLoaded(pixels){ + this.receivedPixels = pixels; + console.log('Received all pixels from server'); + + if (this.receivedLines) + this.startDrawing(); + } + startDrawing(){ + $("#action").html('Drawing...'); + $('#loading_drawings > *:not(#action)').show(); + + this.receivedPixels.forEach((c, i, a) => { + a[i] = c.filter(p => p.c !== '#ffffff' && p.c !== '#FFFFFF'); + }); + + this.addAll(this.receivedLines); + this.fillAll(this.receivedPixels); + + console.log('Started drawing...'); + } + + updateProgress(){ + if (!(this.receivedLines && this.receivedPixels)) + return; + let pixelCountDraw = 0; + this.receivedPixels.forEach(c => pixelCountDraw += c.length); + let allThingsToDraw = this.receivedLines.length + pixelCountDraw; + let thingsToDraw = this.pixelsToFill.length + this.linesToDraw.length; + let progress = thingsToDraw / allThingsToDraw; + progress = progress * 100; + progress = isNaN(progress) ? 0 : progress; + $("#loading_drawings > progress").val(progress); + } + + isLineVisible(line){ + return this.isPointVisible(line.pos1) || this.isPointVisible(line.pos2); + } + isPointVisible(point){ + if (!point) return; + let viewX = this.viewport.x; + let viewY = this.viewport.y; + return point.x - viewX > 0 && point.x - viewX < p.width + && point.y - viewY > 0 && point.y - viewY < p.height + } + + onMouseDown(){ + if (p.mouseX > 0 && p.mouseX < p.width){ + if (this.isCopying){ + this.isCopying = false; + $("body").css('cursor', 'default'); + } else { + this.isDrawing = true; + this.sendDrawing(); + } + } + + this.map.onMouseDown(); + } + onMouseUp(){ + this.isDrawing = false; + this.map.onMouseUp(); + } + onMouseDragged(){ + if (this.isDrawing){ + this.sendDrawing(); + } + + + this.map.onMouseDragged(); + } + onMouseMoved(){ + if (this.isCopying){ + let vp = this.viewport; + let image = this.image.get(vp.x, vp.y, p.width * vp.zoom, p.height * vp.zoom); + let pixel = image.get(p.mouseX * vp.zoom, p.mouseY * vp.zoom); + colorPicker.updateFromRGB(pixel); + } + } + + zoom(delta){ + let oldZoom = this.viewport.zoom; + this.viewport.zoom += delta / 100 * this.viewport.zoomV; + if (p.width * this.viewport.zoom > this.dim.x) + this.viewport.zoom = this.dim.x / p.width; + if (p.height * this.viewport.zoom > this.dim.y) + this.viewport.zoom = this.dim.y / height; + if (this.viewport.zoom < 0.1) + this.viewport.zoom = 0.1; + + let addZoom = this.viewport.zoom - oldZoom; + this.viewport.x -= addZoom * p.mouseX; + this.viewport.y -= addZoom * p.mouseY; + } + + skipDrawing(html){ + if (!this.receivedLines || !this.receivedPixels) + return; + + $(html).attr('disabled', 'disabled'); + + setTimeout(() => { + for (let pixel of this.pixelsToFill){ + this.drawPixel(pixel); + } + this.pixelsToFill = []; + + for (let line of this.linesToDraw){ + this.drawLine(line); + } + this.linesToDraw = []; + }, 0); + } + + requestServerSave(html){ + $(html).attr('disabled', 'disabled'); + socket.emit('save-all'); + } + + answerServerSave(){ + console.log('Drawings successfully saved on server'); + $('#server_answer').fadeIn(200, 'swing', () => { + setTimeout(() => + $('#server_answer').fadeOut(200) + , 1000 * 5); + setTimeout(() => + $('#server_save').attr('disabled', false) + , 1000 * 60 * 5); + }); + } +} + +function updateDrawType(drawType){ + drawer.type = drawType; + if (drawType === 'pixel'){ + p.noSmooth(); + $("#free_settings").hide(); + $('#pixel_settings').show(); + } + if (drawType === 'free'){ + p.smooth(); + $("#free_settings").show(); + $('#pixel_settings').hide(); + } +} + +function startCopyColor(){ + drawer.isCopying = true; + $("body").css('cursor', 'crosshair'); +} \ No newline at end of file diff --git a/public/data/scripts/events.js b/public/data/scripts/events.js new file mode 100644 index 0000000..5185b5e --- /dev/null +++ b/public/data/scripts/events.js @@ -0,0 +1,81 @@ +'use strict'; + +let GRID_HOTKEY; + +p.keyPressed = () => { + if (givesFeedback) + return; + if (p.keyCode === GRID_HOTKEY && drawer) + drawer.gridActive = !drawer.gridActive; +} + +p.keyTyped = () => { + if (givesFeedback) + return; + if (settingHotkey){ + GRID_HOTKEY = p.keyCode; + //p.storeItem('gridHotkey', GRID_HOTKEY); + $('#grid_hotkey').html(String.fromCharCode(p.keyCode)); + settingHotkey = false; + } +} + +p.mouseMoved = () => { + if (givesFeedback) + return; + if (drawer) + drawer.onMouseMoved(); +} + +p.mouseDragged = () => { + if (givesFeedback) + return; + if (drawer) + if (!drawer.hasDrawnAllLines || !drawer.hasFilledAllPixels) + return; + if (colorPicker) + colorPicker.mouseDragged(); + if (drawer) + drawer.onMouseDragged(); +} + +p.mousePressed = () => { + if (drawer) + if (!drawer.hasDrawnAllLines || !drawer.hasFilledAllPixels) + return; + if (givesFeedback) + return; + if (drawer) + drawer.onMouseDown(); + if (colorPicker) + colorPicker.mousePressed(); +} + +p.mouseReleased = () => { + if (drawer) + if (!drawer.hasDrawnAllLines || !drawer.hasFilledAllPixels) + return; + if (givesFeedback) + return; + if (drawer) + drawer.onMouseUp(); + if (colorPicker) + colorPicker.mouseReleased(); +} + +p.mouseWheel = (e) => { + if (drawer) + if (drawer.hasDrawnAllLines && drawer.hasFilledAllPixels) + drawer.zoom(e.delta); + e.preventDefault(); +} + +window.onresize = () => { + p.resizeCanvas($("#canvas-holder").width(), $("#canvas-holder").height()); +} + +let settingHotkey = false; +function setGridHotkey(dom){ + $(dom).blur(); + settingHotkey = true; +} diff --git a/public/data/scripts/init.js b/public/data/scripts/init.js new file mode 100644 index 0000000..688b324 --- /dev/null +++ b/public/data/scripts/init.js @@ -0,0 +1,125 @@ +'use strict'; + +let projectName = "project_pattern"; + +let debug = false, + font, + localSettings, + loader; + +let p; +let processor = new p5((p5Instance) => p = p5Instance); + +//Only for online games +let socket; + +let drawer; +let colorPicker; +let givesFeedback = false; + +p.preload = () => { + localSettings = p.loadJSON('data/settings/settings.json', (json) => { + console.log('Local settings loaded: ', json) + }, (error) => { + console.log('Local settings failed: ', error) + }); + + font = p.loadFont('data/styles/font.ttf', (json) => { + console.log('Local font loaded: ', json) + }, (error) => { + console.log('Local font failed: ', error) + }); + + p.loadJSON('data/settings/libraries.json', json => { + loadScripts(json) + console.log('BenjoCraeft library scripts loaded: ', json) + }); +} + +p.setup = () => { + canvasSetup(localSettings.frameWork); + interfaceSetup(); + + drawer = new Drawer(); + + socketConnect(localSettings.project); +} + +p.draw = () => { + + if (loader){ + loader.update(); + loader.display(); + } + + if (drawer) + drawer.draw(); + + if (debug) debugInformation(); +} + +function canvasSetup(frameWork){ + p.setFrameRate(frameWork.frameRate); + let w = frameWork.width, + h = frameWork.height; + if (w == null) + w = $("#canvas-holder").width(); + if (h == null) + h = $("#canvas-holder").height(); + let canvas = p.createCanvas(w, h); + canvas.parent('canvas-holder'); + p.textFont(font); + p.noSmooth(); +} + +function interfaceSetup(){ + updateThicknessPreview(); + $('input[type=radio][name=type]').change(function() { + updateDrawType(this.value); + $(this).blur(); + }); + + GRID_HOTKEY = p.getItem('gridHotkey'); + if (!GRID_HOTKEY) + GRID_HOTKEY = 'G'.charCodeAt(0); + //p.storeItem('gridHotkey', GRID_HOTKEY); + $('#grid_hotkey').html(String.fromCharCode(GRID_HOTKEY)); + + let usedColors = $('.used_color').toArray().reverse(); + let usedColorsData = p.getItem('usedColors'); + if (!usedColorsData) + usedColorsData = []; + for (let used of usedColors){ + let index = usedColors.indexOf(used); + let color = usedColorsData[index]; + if (!color) + color = '#FFFFFF'; + $(used).css('background-color', color); + usedColorsData[index] = color; + } + //p.storeItem('usedColors', usedColorsData); +} + +function updateThicknessPreview(){ + let size = $("#thickness").val(); + if (drawer) + drawer.thickness = parseInt(size); + $("#thickness_preview").css({width: size, height: size}); +} + +function loadScripts(libs){ + for (let script in libs){ + if (libs[script]){ + let url = 'data/lib/benjocraeft/' + script + '.js' + $.getScript(url, () => { + console.log('Successfully loaded script: ', url) + if (script === 'colorPicker'){ + colorPicker = new ColorPicker(null); + colorPicker.hex = "#000"; + colorPicker.updateFromHEX(null, true); + console.log('ColorPicker script loaded'); + } + }); + } + } +} \ No newline at end of file diff --git a/public/data/scripts/minimap.js b/public/data/scripts/minimap.js new file mode 100644 index 0000000..05425ee --- /dev/null +++ b/public/data/scripts/minimap.js @@ -0,0 +1,67 @@ +class MiniMap{ + + constructor(htmlHolder, htmlCanvas, isMinimap){ + let w = $(htmlHolder).width(); + let h = $(htmlHolder).height(); + this.dim = {x: w, y: h}; + this.p = p.createGraphics(w, h); + this.p.parent(htmlHolder.get(0)); + this.isMinimap = isMinimap; + $(htmlCanvas).show(); + } + + get mouseX(){ + return p.winMouseX - $("#minimap > canvas").offset().left; + } + + get mouseY(){ + return p.winMouseY - $("#minimap > canvas").offset().top; + } + + draw(image, dim, vp){ + this.p.clear(); + + this.p.image(image, 0, 0, this.dim.x, this.dim.y, 0, 0, dim.x, dim.y); + + if (this.isMinimap){ + let x = p.map(vp.x, 0, vp.maxX, 0, this.dim.x); + let y = p.map(vp.y, 0, vp.maxY, 0, this.dim.y); + let w = p.map(p.width * vp.zoom, 0, vp.maxX, 0, this.dim.x); + let h = p.map(p.height * vp.zoom, 0, vp.maxY, 0, this.dim.y); + let r = (w + h) / 2 * 0.02; + this.p.noFill(); + this.p.rect(x, y, w, h, r); + } + } + + onMouseDown(){ + if (this.mouseX > 0 && this.mouseX < this.p.width && + this.mouseY > 0 && this.mouseY < this.p.height){ + this.isDrawing = true; + this.moveViewport(); + } + } + + onMouseUp(){ + this.isDrawing = false; + } + + onMouseDragged(){ + if (this.isDrawing) + this.moveViewport(); + } + + moveViewport(){ + if (!this.isMinimap) + return; + + let vp = drawer.viewport; + let mapX = this.mouseX - p.map(p.width * vp.zoom / 2, 0, drawer.dim.x, 0, this.dim.x); + let mapY = this.mouseY - p.map(p.height * vp.zoom / 2, 0, drawer.dim.y, 0, this.dim.y); + let x = p.map(mapX, 0, this.dim.x, 0, vp.maxX); + let y = p.map(mapY, 0, this.dim.y, 0, vp.maxY); + drawer.viewport.x = x; + drawer.viewport.y = y; + } + +} \ No newline at end of file diff --git a/public/data/scripts/online.js b/public/data/scripts/online.js new file mode 100644 index 0000000..eb2e600 --- /dev/null +++ b/public/data/scripts/online.js @@ -0,0 +1,82 @@ +'use strict'; + +function socketConnect(project, name = "noone"){ + let urlQueries = '?game=' + project.name + '&name=' + name; + $.get('data/settings/get_port.php', port => { + let url = 'https://' + location.hostname + ':' + port + urlQueries; + + socket = io.connect(url); + socket.on('connect', () => { + console.log('Connected to ', url); + + socket.emit('join-lobby', 'global-draw-room'); + + socket.on('add-line', (lobby, line) => drawer.addLine(line)); + socket.on('fill-pixel', (lobby, pixel) => drawer.fillPixel(pixel)); + socket.on('add-all', (lines) => drawer.onLinesLoaded(lines)); + socket.on('fill-all', (pixels) => drawer.onPixelsLoaded(pixels)); + socket.on('member-joined', (lobby, clientId) => { + if (clientId !== socket.id) + return; + if (drawer) + if (drawer.lines.length !== 0) + return; + + socket.emit('request-all-lines'); + socket.emit('request-all-pixels'); + $("#action").html("Downloading..."); + }); + + socket.on('all-saved', (_lobby) => drawer.answerServerSave()); + + updateDrawType($("input[type=radio][name=type]:checked").val()); + }); + }); +} + +function sendFeedback(){ + $.post('/php/post_feedback.php', {content: $('#user_feedback > textarea').val(), projectName: localSettings.project.name}); +} + +function createLobby(dom){ + if (inputIsValid('create')){ + onlineRequestFrontend(dom); + //TODO + } +} + +function joinLobby(dom){ + if (inputIsValid('join')){ + onlineRequestFrontend(dom); + //TODO + } +} + +function onlineRequestFrontend(dom){ + $(dom).blur(); + $(dom).attr('disabled', 'disabled'); + if (loader) loader.destroy(); + loader = new Loader($('#loader').get(0)); +} + +function inputIsValid(type){ + let valid = true; + $('.error-label').html(''); + if (type === 'create'){ + if ($('#player-name > input').val() === ''){ + valid = false; + $('#player-name > .error-label').html('Please enter a name!'); + } + } + if (type === 'join'){ + if ($('#player-name > input').val() === ''){ + valid = false; + $('#player-name > .error-label').html('Please enter a name!'); + } + if ($('#lobby-code > input').val() === ''){ + valid = false; + $('#lobby-code > .error-label').html('Please enter your code!'); + } + } + return valid; +} \ No newline at end of file diff --git a/public/data/settings/get_port.php b/public/data/settings/get_port.php new file mode 100644 index 0000000..8de971e --- /dev/null +++ b/public/data/settings/get_port.php @@ -0,0 +1,3 @@ + input, #color_picker_hsv > input{ + width: 80px; + float: left; + height: 35px; + font-size: 25px; + color: #000; + margin-top: 5px; + border-radius: 10px; +} +#color_picker_rgb > input:nth-child(1), #color_picker_hsv > input:nth-child(1){ + margin-right: 10%; + margin-left: 3%; +} +#color_picker_rgb > input:nth-child(3), #color_picker_hsv > input:nth-child(3){ + margin-left: 10%; +} +#color_picker_hex{ + width: calc(100% - 5px); + height: 30px; + font-size: 25px; + border-radius: 10px; +} +#saturation{ + position: relative; + width: calc(100% - 33px); + height: 100%; + background: linear-gradient(to right, #FFF 0%, #F00 100%); + float: left; + margin-right: 6px; +} +#value { + width: 100%; + height: 100%; + background: linear-gradient(to top, #000 0%, rgba(255,255,255,0) 100%); +} +#sb_picker{ + border: 2px solid; + border-color: #FFF; + position: absolute; + width: 14px; + height: 14px; + border-radius: 10px; + bottom: -7px; + left: -8px; + box-sizing: border-box; + z-index: 10; +} +#hue { + width: 27px; + height: 100%; + position: relative; + float: left; + background: linear-gradient(to bottom, #F00 0%, #F0F 17%, #00F 34%, #0FF 50%, #0F0 67%, #FF0 84%, #F00 100%); +} +#hue_picker { + position: absolute; + background: #000; + border-bottom: 1px solid #000; + top: 0; + width: 27px; + height: 2px; +} \ No newline at end of file diff --git a/public/data/styles/font.ttf b/public/data/styles/font.ttf new file mode 100644 index 0000000..199cf40 Binary files /dev/null and b/public/data/styles/font.ttf differ diff --git a/public/data/styles/range_input.css b/public/data/styles/range_input.css new file mode 100644 index 0000000..80b6507 --- /dev/null +++ b/public/data/styles/range_input.css @@ -0,0 +1,86 @@ +input[type=range] { + -webkit-appearance: none; + margin: 18px 0; + width: 100%; + background-color: inherit; +} +input[type=range]:focus { + outline: none; +} +input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 8.4px; + cursor: pointer; + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; + background: #3071a9; + border-radius: 1.3px; + border: 0.2px solid #010101; +} +input[type=range]::-webkit-slider-thumb { + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; + border: 1px solid #000000; + height: 36px; + width: 16px; + border-radius: 3px; + background: none; + cursor: pointer; + -webkit-appearance: none; + margin-top: -14px; +} +input[type=range]:focus::-webkit-slider-runnable-track { + background: #367ebd; +} +input[type=range]::-moz-range-track { + width: 100%; + height: 8.4px; + cursor: pointer; + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; + background: #3071a9; + border-radius: 1.3px; + border: 0.2px solid #010101; +} +input[type=range]::-moz-range-thumb { + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; + border: 1px solid #000000; + height: 36px; + width: 16px; + border-radius: 3px; + background: #264eff; + cursor: pointer; +} +input[type=range]::-ms-track { + width: 100%; + height: 8.4px; + cursor: pointer; + background: transparent; + border-color: transparent; + border-width: 16px 0; + color: transparent; +} +input[type=range]::-ms-fill-lower { + background: #2a6495; + border: 0.2px solid #010101; + border-radius: 2.6px; + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; +} +input[type=range]::-ms-fill-upper { + background: #3071a9; + border: 0.2px solid #010101; + border-radius: 2.6px; + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; +} +input[type=range]::-ms-thumb { + box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; + border: 1px solid #000000; + height: 36px; + width: 16px; + border-radius: 3px; + background: none; + cursor: pointer; +} +input[type=range]:focus::-ms-fill-lower { + background: #3071a9; +} +input[type=range]:focus::-ms-fill-upper { + background: #367ebd; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..1ac9f7b --- /dev/null +++ b/public/index.html @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + Global Draw + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+ + + +
+ +
+
+
+
+ + + + + + + +
+ +
+
+ +
+
+
+
+
+
+ Grid Toggle: [] + +
+
+
+
+ +
+ +
+ + + +
+
+
+ Connecting... + + +
+ +
+ +
+ +
+ + \ No newline at end of file diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 0000000..97e73dc --- /dev/null +++ b/public/styles.css @@ -0,0 +1,226 @@ +a:link, a:hover, a:active, a:visited{color: #000;} + +html, body{margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden;} + +canvas{margin: 0; padding: 0; border: none; display: block;} + +button:hover{cursor: pointer;} + +@font-face{ + font-family: "Rametto"; + src: url("data/styles/font.ttf"); +} + +*{ + font-family: "Rametto"; + color: #000; + font-size: 17px; +} + +:root{ + --if-background-color: rgb(0, 100, 131); + --if-width: 300px; + --if-border-width: 5px; + --if-padding: 25px; + --if-content-width: calc(var(--if-width) - var(--if-padding) * 2); +} + +/** + * Standard styles + */ + +#content{ + display: grid; + grid-template-columns: calc(var(--if-width) + var(--if-border-width)) calc(100% - var(--if-width)); + grid-template-rows: 100%; + width: 100%; + height: 100%; + overflow: hidden; +} + +button{ + background-color: rgb(67, 202, 67); + margin: 5px; + border-radius: 5px; +} +button:hover:not(.used_color):not([disabled]){ + background-color: rgb(49, 150, 49); +} +button:disabled{ + background-color: rgb(121, 121, 121); +} + + +#loading_drawings{ + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(50, 182, 138, 0.699); + text-align: center; +} +#action{ + font-size: 50px; +} +#loading_drawings > progress{ + width: 700px; + height: 30px; +} +#warning{ + font-size: 10px; +} + +#p5_loading{ + display: none; +} +#loading_maps{ + width: 100%; + display: flex; + justify-content: center; +} +#pixels_map_holder, #lines_map_holder{ + width: 500px; + height: 500px; + border-radius: 5px; + border: 3px solid #000; + background-color: #FFF; + margin: 50px; +} + + +#user_feedback{ + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(50, 182, 138, 0.699); + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +#user_feedback > *{ + width: 50%; +} + + +#interface{ + background-color: var(--if-background-color); + border: var(--if-border-width) solid #000; + border-width: 0 5px 0 0; +} + +#version{ + position: absolute; + bottom: 55px; +} +#changelog{ + position: absolute; + bottom: 30px; +} +#start_user_feedback{ + position: absolute; + bottom: 0; + height: 25px; + font-size: 15px; +} + +#color_picker{ + width: var(--if-content-width); + height: var(--if-content-width); +} +#color_picker_numeric_holder{ + margin: 10px var(--if-padding); + width: var(--if-content-width); + background-color: none; +} +#color_picker_numeric{ + background-color: none; +} +#color_picker_rgb, #color_picker_hsv{ + display: none; +} +#used_colors{ + width: var(--if-content-width); + margin: 0 var(--if-padding); + display: flex; + justify-content: space-between; +} +.used_color{ + width: 30px; + height: 30px; + margin: 0; + background-color: white; +} +#get_color{ + width: calc(var(--if-content-width)); + margin: 0 var(--if-padding) 15px; + font-size: 11px; + border-radius: 3px; + background-color: white; +} +#get_color:hover{ + background-color: rgb(150, 150, 150); +} + +#free_settings, #pixel_settings{ + margin: 0px var(--if-padding); + width: var(--if-content-width); + height: 80px; +} + +#thickness_holder{ + display: flex; + align-items: center; +} +#thickness{ + width: calc(var(--if-content-width) - 50px); +} +#thickness_preview_holder{ + position: relative; + margin: 0 0 0 10px; + width: 40px; + height: 40px; +} +#thickness_preview{ + border: 1px solid #000; + position: absolute; + top: 50%; left: 50%; + transform: translate(-50%, -50%); + border-radius: 50%; +} + +#set_hotkey{ + float: right; + margin: 0; + font-size: 12px; + height: 28px; +} + +#minimap{ + width: var(--if-content-width); + height: var(--if-content-width); + margin: 22px; + border-radius: 5px; + border: 3px solid #000; +} +#minimap > canvas{ + border-radius: inherit; +} + +#draw_type{ + width: var(--if-content-width); + margin: 0 var(--if-padding); + text-align: center; +} + +#server_save{ + width: calc(var(--if-content-width) * 0.6); + margin: 10px calc(var(--if-padding) + var(--if-content-width) * 0.2) +} +#server_answer{ + display: block; + width: var(--if-content-width); + font-size: 15px; + text-align: center; + margin: 0 var(--if-padding); +} \ No newline at end of file diff --git a/public/thumbnail.png b/public/thumbnail.png new file mode 100644 index 0000000..2c6aba9 Binary files /dev/null and b/public/thumbnail.png differ diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..7f2c5d6 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,5 @@ +out +json_data +logs +node_modules +.env \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..2a59cb2 --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,424 @@ +{ + "name": "global-draw-sheet", + "version": "2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "global-draw-sheet", + "version": "2.0", + "dependencies": { + "dotenv": "^16.0.3", + "https": "^1.0.0", + "socket.io": "^4.4.1", + "typescript": "^5.0.2" + }, + "devDependencies": { + "@types/node": "^18.15.3" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "18.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", + "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/engine.io": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", + "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/socket.io": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/typescript": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + }, + "dependencies": { + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "18.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", + "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==" + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, + "engine.io": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + } + }, + "engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==" + }, + "https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", + "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "socket.io": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" + } + }, + "socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "requires": { + "ws": "~8.11.0" + } + }, + "socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, + "typescript": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "requires": {} + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..7d1a8d4 --- /dev/null +++ b/server/package.json @@ -0,0 +1,17 @@ +{ + "name": "global-draw", + "version": "2.0", + "private": true, + "scripts": { + "start": "node out/index.js" + }, + "dependencies": { + "dotenv": "^16.0.3", + "https": "^1.0.0", + "socket.io": "^4.4.1", + "typescript": "^5.0.2" + }, + "devDependencies": { + "@types/node": "^18.15.3" + } +} diff --git a/server/src/client.ts b/server/src/client.ts new file mode 100644 index 0000000..b1cd58c --- /dev/null +++ b/server/src/client.ts @@ -0,0 +1,169 @@ +import {Room} from "./room.js" +import {ConnectionManager, serializeObject} from "./manager.js" +import {log} from "./logger.js"; +import * as SocketIO from "socket.io"; +import {Settings} from "./definitions/settings"; + +export class Client { + + socket: SocketIO.Socket; + name: string; + game: string; + id: string; + isReady: boolean; + isPlayer: boolean; + isSpectator: boolean; + + constructor(socket: SocketIO.Socket, manager: ConnectionManager) { + this.socket = socket; + // @ts-ignore + this.name = socket.handshake.query.name; + // @ts-ignore + this.game = socket.handshake.query.game; + this.id = socket.id; + this.setEvents(manager) + } + + get serialized(): Serialized.Client { + return { + id: this.id, + name: this.name, + game: this.game, + isReady: this.isReady, + isPlayer: this.isPlayer, + isSpectator: this.isSpectator + }; + } + + setEvents(mng: ConnectionManager): void { + let s = this.socket; + s.on('room-list', () => this.sendRoomList()); + s.on('client-list', () => this.sendClientList()); + s.on('set-ready', ready => this.setReady(ready)); + s.on('game-settings', settings => this.setGameSettings(settings)); + s.on('create-lobby', (settings, name) => this.createRoom(settings, name)); + s.on('join-lobby', roomId => this.joinRoom(roomId)); + s.on('leave-lobby', roomId => this.leaveRoom(roomId)); + s.on('join-spectators', () => this.joinSpectators()); + s.on('join-players', () => this.joinPlayers()); + s.on('start-game', lobbyId => mng.startGame(this, lobbyId)); + s.on('stop-game', lobbyId => mng.stopGame(this, lobbyId)); + s.on('feedback', content => mng.saveFeedbackToFile(this, content)); + s.on('disconnect', () => mng.disconnected(this)); + + this.send('connected') + } + + sendRoomList(): void { + let rooms = ConnectionManager.RoomListByGame(this.game); + this.send('room-list', rooms) + } + + sendClientList(): void { + let clients = ConnectionManager.ClientListByClientId(this.id); + this.send('client-list', clients) + } + + setReady(ready: boolean): void { + let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms); + + if (room) { + this.isReady = ready; + room.toAll('client-list', room.clients) + } + } + + setGameSettings(settings: any): void { + let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms); + + if (room) { + room.gameSettings = settings; + room.toAll('game-settings', settings) + } + } + + createRoom(settings: Settings.Global, name: string): void { + let room = ConnectionManager.Instance.createRoom(settings, name); + + room.add(this); + this.send('created-lobby', room); + + log('lobby-created', this, room) + } + + joinRoom(roomId: string): Room { + let room = Room.getByRoomId(roomId, ConnectionManager.Instance.rooms); + + if (!room) { + this.send('join-failed', 'Room does not exist!'); + log('join-non-existent', this, new Room('not-existent', roomId)) + } else if (room.hasStarted && !room.settings.spectators) { + this.send('join-failed', 'Game has started yet!'); + log('join-started', this, room) + } else { + room.add(this); + log('member-joined', this, room) + } + return room + } + + leaveRoom(_roomId: string): void { + let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms); + + if (!room) + return; + + this.leave(room.id); + if (room.runningGame) + room.runningGame.removeClient(this); + room.clients.splice(room.clients.indexOf(this), 1); + room.toAll('member-left', this.id, this.name); + room.toAll('client-list', room.clients); + + this.send('left-lobby'); + + log('member-left', this, room); + + if (room.isEmpty && !room.settings.always) { + ConnectionManager.Instance.deleteRoom(room) + } + } + + joinSpectators() { + let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms); + if (!room) + return; + + this.isSpectator = true; + this.isPlayer = false; + + room.toAll('client-list', room.clients) + } + + joinPlayers() { + let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms); + if (!room) + return; + + if (room.hasStarted) + return; + + this.isSpectator = false; + this.isPlayer = true; + + room.toAll('client-list', room.clients) + } + + send(event: string, ...args: any[]): void { + this.socket.emit(event, ...serializeObject(args)) + } + + join(roomId: string): void { + this.socket.join(roomId) + } + + leave(roomId: string): void { + this.socket.leave(roomId) + } + +} \ No newline at end of file diff --git a/server/src/definitions/serialized.d.ts b/server/src/definitions/serialized.d.ts new file mode 100644 index 0000000..b7b6a04 --- /dev/null +++ b/server/src/definitions/serialized.d.ts @@ -0,0 +1,20 @@ +declare namespace Serialized { + + interface Lobby { + id: string + name: string + game: string + clientCounts: number[] + clients: Client[] + hasStarted: boolean + } + + interface Client { + id: string + name: string + game: string + isReady: boolean + isPlayer: boolean + isSpectator: boolean + } +} \ No newline at end of file diff --git a/server/src/definitions/settings.d.ts b/server/src/definitions/settings.d.ts new file mode 100644 index 0000000..55d1d03 --- /dev/null +++ b/server/src/definitions/settings.d.ts @@ -0,0 +1,29 @@ +import {ServerGame} from "../game_standard"; + +declare module Settings { + interface Global { + project: Project + frameWork: FrameWork + game: any + always: boolean + spectators: boolean + } + + interface Project { + name: string + author: string + playerCounts: number[] + } + + interface FrameWork { + frameRate: number + updateRate: number + width: number + height: number + } + + interface Server { + useP2P: boolean + gameClass: typeof ServerGame + } +} \ No newline at end of file diff --git a/server/src/game_standard.ts b/server/src/game_standard.ts new file mode 100644 index 0000000..cedb658 --- /dev/null +++ b/server/src/game_standard.ts @@ -0,0 +1,38 @@ +import {Room} from "./room.js" +import {Client} from "./client.js" +import {Settings} from "./definitions/settings"; + +export class ServerGame { + + room: Room; + settings: Settings.Global; + game: any; + + constructor(room: Room, settings: Settings.Global) { + this.settings = settings; + this.room = room; + this.room.clients.forEach(c => this.addClient(c)) + } + + addClient(client: Client): void { + this.setEvents(client) + } + + removeClient(client: Client): void { + this.removeEvents(client) + } + + gameAction(action: string, ...args: any[]): void { + } + + setEvents(client: Client): void { + let socket = client.socket; + socket.on('game-action', (action, ...args) => this.gameAction(action, ...args)) + } + + removeEvents(client: Client): void { + let socket = client.socket; + socket.removeAllListeners('game-action') + } + +} \ No newline at end of file diff --git a/server/src/global_draw.ts b/server/src/global_draw.ts new file mode 100644 index 0000000..345e545 --- /dev/null +++ b/server/src/global_draw.ts @@ -0,0 +1,149 @@ +import {ServerGame} from "./game_standard" +import {Room} from "./room"; +import {Settings} from "./definitions/settings"; +import {log} from "./logger"; +import {Client} from "./client"; +import * as fs from "fs"; + +export class GlobalDraw extends ServerGame { + + lines: any[]; + pixels: any[][]; + + linesPath = "json_data/global_draw/lines.json"; + pixelsPath = "json_data/global_draw/pixels.json"; + + pixelCount = 1000; + + constructor(lobby: Room, settings: Settings.Global) { + super(lobby, settings); + + this.lines = []; + this.pixels = []; + + for (let x = 0; x < this.pixelCount; x++) { + let column = []; + for (let y = 0; y < this.pixelCount; y++) { + column.push({x: x, y: y, c: "#ffffff"}); + } + this.pixels.push(column); + } + + let linesLoaded = false; + let pixelsLoaded = false; + + this.loadDrawingsFromFile(this.linesPath, (data: any[]) => { + this.lines = data; + }, () => { + linesLoaded = true; + if (pixelsLoaded) { + this.startSaveInterval(); + } + }); + this.loadDrawingsFromFile(this.pixelsPath, (data: any[]) => { + for (let x = 0; x < this.pixelCount; x++) { + for (let y = 0; y < this.pixelCount; y++) { + if (data[x]) + if (data[x][y]) + this.pixels[x][y].c = data[x][y].c + } + } + }, () => { + pixelsLoaded = true; + if (linesLoaded) { + this.startSaveInterval(); + } + }); + } + + startSaveInterval() { + this.saveAllDrawingsToFile(); + + //Saves once every day + setInterval(() => this.saveAllDrawingsToFile(), 1000 * 60 * 60 * 24); + } + + addLine(line: any) { + this.lines.push(line); + this.room.toAll('add-line', line) + } + + fillPixel(pixel: any) { + this.pixels[pixel.x][pixel.y].c = pixel.c; + this.room.toAll('fill-pixel', pixel) + } + + loadDrawingsFromFile(drawingsPath: string, successs: (data: any[]) => void, done: () => void) { + fs.readFile(drawingsPath, 'utf8', (err, data) => { + if (err) + log('load-error', null, this.room, err.message); + else { + try { + let parsed = JSON.parse(data); + log('load-success', null, this.room); + successs(parsed); + } catch (e) { + log('parse-error', null, this.room, e.message); + } + } + done(); + }); + } + + saveDrawingsToFile(drawings: any[], drawingsPath: string, callback: (err: any) => void) { + let splits = drawingsPath.split('/'); + let path = splits.slice(0, splits.length - 1).reduce((prev, curr) => prev + '/' + curr); + let name = splits[splits.length - 1]; + if (!fs.existsSync(path)) { + fs.mkdirSync(path, {recursive: true}); + } + fs.writeFile(drawingsPath, JSON.stringify(drawings), callback); + } + + saveAllDrawingsToFile() { + let linesSaved = false; + let pixelsSaved = false; + + this.saveDrawingsToFile(this.lines, this.linesPath, (err) => { + if (err) + log('save-error', null, this.room, err.message); + else { + linesSaved = true; + if (pixelsSaved) { + this.room.toAll('all-saved'); + linesSaved = false; + pixelsSaved = false + } + log('save-success', null, this.room, 'Successfully saved lines to file') + } + }); + this.saveDrawingsToFile(this.pixels, this.pixelsPath, (err) => { + if (err) + log('save-error', null, this.room, err.message); + else { + pixelsSaved = true; + if (linesSaved) { + this.room.toAll('all-saved'); + pixelsSaved = false; + linesSaved = false + } + log('save-success', null, this.room, 'Successfully saved pixels to file') + } + }); + } + + addClient(client: Client): void { + this.setEvents(client); + } + + setEvents(client: Client): void { + super.setEvents(client); + let socket = client.socket; + socket.on('add-line', (line) => this.addLine(line)); + socket.on('fill-pixel', (pixel) => this.fillPixel(pixel)); + socket.on('request-all-lines', () => socket.emit('add-all', this.lines)); + socket.on('request-all-pixels', () => socket.emit('fill-all', this.pixels)); + socket.on('save-all', () => this.saveAllDrawingsToFile()); + } + +} \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts new file mode 100644 index 0000000..1eee09a --- /dev/null +++ b/server/src/index.ts @@ -0,0 +1,7 @@ +import {GlobalDraw} from "./global_draw"; +import {StartServer} from "./start"; + +StartServer({ + useP2P: false, + gameClass: GlobalDraw +}); \ No newline at end of file diff --git a/server/src/logger.ts b/server/src/logger.ts new file mode 100644 index 0000000..2b4e936 --- /dev/null +++ b/server/src/logger.ts @@ -0,0 +1,105 @@ +import {Room} from "./room.js" +import {Client} from "./client.js" + +import * as fs from "fs"; +import * as util from "util"; + +let logFolder = "./logs"; + +if (!fs.existsSync(logFolder)) { + fs.mkdirSync(logFolder); +} + +let logFile = fs.createWriteStream(logFolder + '/' + new Date().getTime() + '.log', {flags: 'a'}); +let logStdout = process.stdout; + +console.log = function () { + logFile.write(util.format.apply(null, arguments) + '\n'); + logStdout.write(util.format.apply(null, arguments) + '\n'); +}; + +console.error = console.log; + +process.on('uncaughtException', err => { + console.error('Uncaught error: ', err); + process.exit(1); +}); + +process.stdin.pipe(logFile); + + +export function log(type: string, client: Client, lobby?: Room, msg?: string) { + let now = new Date(Date.now()).toString(), message, name, game; + let date = '[' + now.substring(0, now.indexOf('GMT') - 1) + ']'; + + if (client) { + game = '[' + client.game + ']'; + let short = client.id.substring(0, Math.round(client.id.length / 3)); + name = '"' + client.name + '(' + short + '...)"'; + } else { + if (type === 'lobby-deleted') { + game = '[' + lobby.gameName + ']'; + } else { + game = '[undefined]'; + } + name = 'UNKNOWN'; + } + if (lobby) { + game = '[' + lobby.gameName + ']'; + } + switch (type) { + case 'join-non-existent': + message = name + ' tried to join non-existent lobby "' + lobby.id + '"'; + break; + case 'join-started': + message = name + ' tried to join the started game "' + lobby.id + '"'; + break; + case 'lobby-created': + message = name + ' created new lobby: "' + lobby.id + '"'; + break; + case 'game-started': + message = name + ' started the game: "' + lobby.id + '"'; + break; + case 'game-stopped': + message = name + ' stopped the game: "' + lobby.id + '"'; + break; + case 'member-joined': + message = name + ' joined the lobby "' + lobby.id + '"'; + break; + case 'member-left': + message = name + ' left the lobby "' + lobby.id + '"'; + break; + case 'lobby-deleted': + message = 'Lobby "' + lobby.id + '" was deleted'; + break; + case 'save-success': + message = msg; + break; + case 'save-error': + message = 'Failed to save contents to file: ' + msg; + break; + case 'load-success': + message = 'Successfully loaded and parsed file contents'; + break; + case 'load-error': + message = 'Failed to load file: ' + msg; + break; + case 'parse-error': + message = 'Failed to parse contents: ' + msg; + break; + case 'feedback': + message = 'Saved feedback to file: ' + msg; + break; + case 'connection': + message = name + ' connected'; + break; + case 'disconnection': + message = name + ' disconnected'; + break; + case 'startup': + message = msg; + break; + } + + console.log(date + game + ' ---> {' + message + '}'); +} \ No newline at end of file diff --git a/server/src/manager.ts b/server/src/manager.ts new file mode 100644 index 0000000..6d50d8b --- /dev/null +++ b/server/src/manager.ts @@ -0,0 +1,149 @@ +import {Room} from "./room.js" +import {Client} from "./client.js" +import {log} from "./logger.js" +import * as fs from "fs"; +import * as SocketIO from "socket.io" +import {Settings} from "./definitions/settings"; + +export class ConnectionManager { + + static Instance: ConnectionManager; + io: SocketIO.Server; + rooms: Room[]; + + constructor(io: SocketIO.Server) { + ConnectionManager.Instance = this; + + this.io = io; + this.rooms = []; + + let drawSettings = { + project: { + name: 'global-draw', + playerCounts: null + }, + always: true, + spectators: true + }; + let drawRoom = this.createRoom(drawSettings, ''); + drawRoom.id = 'global-draw-room'; + drawRoom.startGame(); + this.rooms.push(drawRoom); + } + + static RoomListByGame(game: string): Room[] { + return this.Instance.rooms.filter(l => l.gameName === game) + } + + static ClientListByClientId(clientId: string): Client[] { + let room = Room.getByClientId(clientId, this.Instance.rooms); + + return room.clients + } + + newSocket(socket: SocketIO.Socket): void { + let client = new Client(socket, this); + log('connection', client) + } + + roomListUpdate(): void { + this.io.sockets.emit('room-list', serializeObject(this.rooms)) + } + + createRoom(settings: Settings.Global | any, name: string): Room { + let roomId = Room.generateCode(10); + let room = new Room(name, roomId, settings, this.io); + + this.rooms.push(room); + this.roomListUpdate(); + + return room + } + + deleteRoom(room: Room): void { + this.rooms.splice(this.rooms.indexOf(room), 1); + this.roomListUpdate(); + + log('lobby-deleted', null, room) + } + + //Starts the game of a room with given id + startGame(client: Client, _roomId: string): void { + let lobby = Room.getByClientId(client.id, this.rooms); + if (!lobby) return; + + if (!lobby.hasStarted) { + lobby.startGame(); + log('game-started', client, lobby) + } + + this.io.sockets.emit('room-list', serializeObject(this.rooms)) + } + + //Stops the game of a lobby with given id + stopGame(client: Client, lobbyId: string): void { + let lobby = Room.getByRoomId(lobbyId, this.rooms); + if (!lobby) return; + + lobby.stopGame(client); + log('game-stopped', client, lobby) + } + + + //Saves user feedback to a file + saveFeedbackToFile(client: Client, content: string): void { + let date = new Date(Date.now()).toString(); + let path = "feedback/" + client.game + '.txt'; + let saveToFile = (content: string) => { + fs.writeFile(path, content, (err: any) => { + if (err) + log('save-error', client, null, err.message); + else + log('feedback', client, null, path) + }); + }; + if (fs.existsSync(path)) { + fs.readFile(path, 'utf8', (err, data) => { + if (err) + log('load-error', client, null, err.message); + else { + log('load-success', client, null); + let newContent = data + '\n\n\n\n' + date + '\n\n' + content; + saveToFile(newContent) + } + }) + } else { + saveToFile(date + '\n' + content) + } + } + + //Removes a disconnected client from all references + disconnected(client: Client): void { + let room = Room.getByClientId(client.id, this.rooms); + + if (room) + client.leaveRoom(room.id); + + log('disconnection', client) + } + +} + +export function serializeObject(object: any): any { + function serialize(obj: any) { + if (!obj) + return obj; + if (obj.serialized) + return obj.serialized; + else if (obj instanceof Array) { + let content = []; + obj.forEach(o => { + content.push(serialize(o)) + }); + return content + } + return obj + } + + return serialize(object) +} \ No newline at end of file diff --git a/server/src/room.ts b/server/src/room.ts new file mode 100644 index 0000000..caf3fea --- /dev/null +++ b/server/src/room.ts @@ -0,0 +1,136 @@ +import {Client} from "./client.js" +import {ServerGame} from "./game_standard.js" +import {serializeObject} from "./manager.js"; +import {Server} from "socket.io"; +import {Settings} from "./definitions/settings"; + +export class Room { + + id: string; + gameName: string; + clientCounts: number[]; + io: Server; + clients: Client[]; + runningGame: ServerGame; + settings: Settings.Global; + gameSettings: any; + name: string; + + static GameClass: typeof ServerGame + + constructor(name: string, id: string, settings?: Settings.Global, io?: Server) { + this.id = id; + this.name = name; + if (!io || !settings) return; + this.settings = settings; + this.gameName = settings.project.name; + this.clientCounts = settings.project.playerCounts; + this.io = io; + this.clients = []; + this.gameSettings = {} + } + + get leader(): Client { + return this.players[0] + } + + get players(): Client[] { + return this.clients.filter(c => c.isPlayer) + } + + get spectators(): Client[] { + return this.clients.filter(c => c.isSpectator) + } + + get serialized(): Serialized.Lobby { + return { + id: this.id, + name: this.name, + game: this.gameName, + clientCounts: this.clientCounts, + clients: serializeObject(this.clients), + hasStarted: this.hasStarted + }; + } + + get isEmpty(): boolean { + return !(this.clients.length) + } + + get hasStarted(): boolean { + return this.runningGame != null + } + + static getByRoomId(id: string, lobbies: Room[]): Room { + for (let l of lobbies) { + if (l.id === id) + return l + } + return null; + } + + static getByClientId(id: string, lobbies: Room[]): Room { + for (let l of lobbies) { + for (let c of l.clients) { + if (c.id === id) + return l + } + } + return null; + } + + static generateCode(elements: number): string { + let code = ''; + let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + while (elements--) { + code += possible.charAt(Math.floor(Math.random() * possible.length)) + } + return code + } + + startGame(): void { + let seed = Math.random() * 10000; + this.toAll('start-game', seed); + this.runGame() + } + + stopGame(client: Client): void { + this.toAll('stop-game', client); + this.runningGame = null + } + + add(client: Client): void { + this.clients.push(client); + + let isPlayer = !this.hasStarted && this.hasValidPlayerCount(); + client.isPlayer = isPlayer; + client.isSpectator = !isPlayer; + client.isReady = false; + client.join(this.id); + + this.toAll('member-joined', client.id, client.name); + this.toAll('client-list', this.clients); + this.toAll('game-settings', this.gameSettings); + + if (this.hasStarted) + this.runningGame.addClient(client) + } + + hasValidPlayerCount(): boolean { + let valid = false; + this.clientCounts.forEach(c => { + if (c === this.clients.length) + valid = true + }); + return valid + } + + runGame(): void { + this.runningGame = new Room.GameClass(this, this.settings); + } + + toAll(event: string, ...args: any[]): void { + this.io.to(this.id).emit(event, serializeObject(this), ...serializeObject(args)) + } + +} \ No newline at end of file diff --git a/server/src/start.ts b/server/src/start.ts new file mode 100644 index 0000000..82293ed --- /dev/null +++ b/server/src/start.ts @@ -0,0 +1,39 @@ +import {ConnectionManager} from "./manager.js"; +import {log} from "./logger.js"; + +import {Server} from 'socket.io'; +import {Room} from "./room.js"; +import * as https from "https"; +import * as fs from "fs"; + +export function StartServer(settings: any){ + + require("dotenv").config(); + const httpsPort = parseInt(process.env.HTTPS_PORT); + + let cert = fs.readFileSync(`${process.env.SSL_PATH}/cert.pem`); + let key = fs.readFileSync(`${process.env.SSL_PATH}/key.pem`); + + let httpsServer = https.createServer({key: key, cert: cert}); + + let sIO = new Server(httpsServer, { + cors: { + origin: ["https://play.benjamin-kraft.local", "https://play.benjamin-kraft.eu"] + } + }); + if (settings.useP2P){ + const p2p = require('socket.io-p2p-server').Server; + sIO.use(p2p); + } + + httpsServer.listen(httpsPort); + + Room.GameClass = settings.gameClass; + + let connectionManager = new ConnectionManager(sIO); + + // On new connection + sIO.on('connection', socket => connectionManager.newSocket(socket)); + + log('startup', null, null, 'Server is listening on port ' + httpsPort); +} \ No newline at end of file diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..87d4284 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "alwaysStrict": true, + "sourceMap": true, + "outDir": "./out" + }, + "include": [ + "./src" + ] +} \ No newline at end of file