commit
c4c299f701
38 changed files with 3167 additions and 0 deletions
@ -0,0 +1 @@ |
||||
.idea |
@ -0,0 +1,6 @@ |
||||
{ |
||||
"display_name": "Global draw sheet", |
||||
"info_text": "A draw sheet for everyone.", |
||||
"visible": true, |
||||
"tags": ["Tool", "Multiplayer"] |
||||
} |
@ -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!!! |
After Width: | Height: | Size: 318 B |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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; |
@ -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]); |
||||
} |
@ -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(); |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
@ -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; |
||||
} |
@ -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'); |
||||
} |
@ -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; |
||||
} |
@ -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'); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
@ -0,0 +1,3 @@ |
||||
<?php |
||||
|
||||
echo parse_ini_file("../../../server/.env")["HTTPS_PORT"]; |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"collision": false, |
||||
"colorPicker": true, |
||||
"cookie": true, |
||||
"loader": true, |
||||
"prototypes": true, |
||||
"technical": true |
||||
} |
@ -0,0 +1,22 @@ |
||||
{ |
||||
"project": { |
||||
"name": "global_draw", |
||||
"author": "BenjoCraeft", |
||||
"playerCounts": [], |
||||
"online": { |
||||
"iceServers": [ |
||||
{"urls": "stun:stun.l.google.com:19302"}, |
||||
{ |
||||
"urls": "turn:numb.viagenie.ca", |
||||
"credential": "muazkh", |
||||
"username": "webrtc@live.com" |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"frameWork": { |
||||
"frameRate": 60, |
||||
"width": null, |
||||
"height": null |
||||
} |
||||
} |
@ -0,0 +1,76 @@ |
||||
#color_picker{ |
||||
margin: 20px; |
||||
border: 5px solid #000; |
||||
background-color: #000; |
||||
-webkit-user-select: none; |
||||
-moz-user-select: none; |
||||
-ms-user-select: none; |
||||
user-select: none; |
||||
position: relative; |
||||
} |
||||
#color_picker_numeric{ |
||||
border-radius: 10px; |
||||
overflow: hidden; |
||||
} |
||||
#color_picker_rgb > 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; |
||||
} |
Binary file not shown.
@ -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; |
||||
} |
@ -0,0 +1,109 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<script src="https://cdn.socket.io/4.4.1/socket.io.min.js" type="text/javascript"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js" type="text/javascript"></script> |
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js" type="text/javascript"></script> |
||||
<script src="data/scripts/init.js" type="text/javascript"></script> |
||||
<script src="data/scripts/events.js" type="text/javascript"></script> |
||||
<script src="data/scripts/online.js" type="text/javascript"></script> |
||||
<script src="data/scripts/drawer.js" type="text/javascript"></script> |
||||
<script src="data/scripts/minimap.js" type="text/javascript"></script> |
||||
<link href="styles.css" rel="stylesheet"> |
||||
<link href="data/styles/color_picker.css" rel="stylesheet"> |
||||
<link href="data/styles/range_input.css" rel="stylesheet"> |
||||
<link href="data/images/favicon.ico" rel="icon" type="image/x-icon"> |
||||
<title>Global Draw</title> |
||||
</head> |
||||
<body> |
||||
<div id="p5_loading"></div> |
||||
<div id="content"> |
||||
<div id="interface"> |
||||
<div id="color_picker"> |
||||
<div id="saturation"> |
||||
<div id="value"> |
||||
<div id="sb_picker"></div> |
||||
</div> |
||||
</div> |
||||
<div id="hue"> |
||||
<div id="hue_picker"></div> |
||||
</div> |
||||
</div> |
||||
<div id="color_picker_numeric_holder"> |
||||
<div id="color_picker_numeric"> |
||||
<div id="color_picker_rgb"> |
||||
<input max="255" min="0" onchange="colorPicker.updateFromRGB()" oninput="colorPicker.updateFromRGB()" type="number"/> |
||||
<input max="255" min="0" onchange="colorPicker.updateFromRGB()" oninput="colorPicker.updateFromRGB()" type="number"/> |
||||
<input max="255" min="0" onchange="colorPicker.updateFromRGB()" oninput="colorPicker.updateFromRGB()" type="number"/> |
||||
</div> |
||||
<div id="color_picker_hsv"> |
||||
<input max="360" min="0" onchange="colorPicker.updateFromHSV()" oninput="colorPicker.updateFromHSV()" type="number"/> |
||||
<input max="100" min="0" onchange="colorPicker.updateFromHSV()" oninput="colorPicker.updateFromHSV()" type="number"/> |
||||
<input max="100" min="0" onchange="colorPicker.updateFromHSV()" oninput="colorPicker.updateFromHSV()" type="number"/> |
||||
</div> |
||||
<input id="color_picker_hex" onchange="colorPicker.updateFromHEX(this, false)" style="background-color: #000000;" style="background-color: #000000;" type="text"/> |
||||
<div id="color_picker_final"></div> |
||||
</div> |
||||
</div> |
||||
<div id="used_colors"> |
||||
<button class="used_color" onclick="colorPicker.setFromUsed(this)"></button> |
||||
<button class="used_color" onclick="colorPicker.setFromUsed(this)"></button> |
||||
<button class="used_color" onclick="colorPicker.setFromUsed(this)"></button> |
||||
<button class="used_color" onclick="colorPicker.setFromUsed(this)"></button> |
||||
<button class="used_color" onclick="colorPicker.setFromUsed(this)"></button> |
||||
<button class="used_color" onclick="colorPicker.setFromUsed(this)"></button> |
||||
<button class="used_color" onclick="colorPicker.setFromUsed(this)"></button> |
||||
</div> |
||||
<button id="get_color" onclick="startCopyColor();">Pick color from screen</button> |
||||
<div id="free_settings"> |
||||
<div id="thickness_holder"> |
||||
<input id="thickness" max="25" min="1" oninput="updateThicknessPreview();" type="range" value="2"/> |
||||
<div id="thickness_preview_holder"> |
||||
<div id="thickness_preview"></div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div id="pixel_settings"> |
||||
<span>Grid Toggle: [</span><span id="grid_hotkey"></span><span>]</span> |
||||
<button id="set_hotkey" onclick="setGridHotkey(this);">Change</button> |
||||
</div> |
||||
<div id="minimap"> |
||||
</div> |
||||
<form id="draw_type"> |
||||
<label> |
||||
<input checked name="type" type="radio" value="free"> |
||||
Draw Freely |
||||
</label> |
||||
<br> |
||||
<label> |
||||
<input name="type" type="radio" value="pixel"> |
||||
Pixel Art |
||||
</label> |
||||
</form> |
||||
<button id="server_save" onclick="drawer.requestServerSave(this);">Server save</button> |
||||
<span id="server_answer" style="display: none;">Drawings saved!</span> |
||||
<button id="start_user_feedback" onclick="$('#user_feedback').show(); givesFeedback = true;">Feedback</button> |
||||
</div> |
||||
<div id="canvas-holder"></div> |
||||
<div id="loading_drawings"> |
||||
<span id="action">Connecting...</span> |
||||
<div id="loading_maps" style="display: none;"> |
||||
<div id="pixels_map_holder"></div> |
||||
<div id="lines_map_holder"></div> |
||||
</div> |
||||
<progress max="100" style="display: none;" value="100"></progress> |
||||
<br> |
||||
<button id="skip_drawing" onclick="if (drawer) drawer.skipDrawing(this)" style="display: none;">Skip Drawing</button> |
||||
<br> |
||||
<span id="warning" style="display: none;">Warning: Do not click this unless you have a proper Graphic Processor!</span> |
||||
</div> |
||||
<div id="user_feedback" style="display: none;"> |
||||
<span>Write me feedback!</span> |
||||
<textarea cols="50" rows="10"></textarea> |
||||
<button onclick="sendFeedback();$('#user_feedback').hide();givesFeedback = false;">Submit</button> |
||||
<button onclick="$('#user_feedback').hide(); givesFeedback = false;">Cancel</button> |
||||
</div> |
||||
</div> |
||||
</body> |
||||
</html> |
@ -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); |
||||
} |
After Width: | Height: | Size: 171 KiB |
@ -0,0 +1,5 @@ |
||||
out |
||||
json_data |
||||
logs |
||||
node_modules |
||||
.env |
@ -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": {} |
||||
} |
||||
} |
||||
} |
@ -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" |
||||
} |
||||
} |
@ -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) |
||||
} |
||||
|
||||
} |
@ -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 |
||||
} |
||||
} |
@ -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 |
||||
} |
||||
} |
@ -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') |
||||
} |
||||
|
||||
} |
@ -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()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,7 @@ |
||||
import {GlobalDraw} from "./global_draw"; |
||||
import {StartServer} from "./start"; |
||||
|
||||
StartServer({ |
||||
useP2P: false, |
||||
gameClass: GlobalDraw |
||||
}); |
@ -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 + '}'); |
||||
} |
@ -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) |
||||
} |
@ -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)) |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
@ -0,0 +1,11 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"module": "CommonJS", |
||||
"alwaysStrict": true, |
||||
"sourceMap": true, |
||||
"outDir": "./out" |
||||
}, |
||||
"include": [ |
||||
"./src" |
||||
] |
||||
} |
Loading…
Reference in new issue