diff --git a/LICENSE b/LICENSE index 369f2a2..7343f86 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 LAX1DUDE +Copyright (c) 2022 Calder Young Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md new file mode 100644 index 0000000..e6a8350 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Eaglercraft HTML5 Ping Embed + +### This allows you to easily embed your Eaglercraft server's ping, player list, and MOTD in a \ element in an HTML5 document using the same style as the game's multiplayer screen + +![Eaglercraft HTML5 Ping Embed](https://i.gyazo.com/3db6a206bca69da3545a6083bc8aa7eb.gif) + +### Demo link: [https://g.eags.us/eaglercraft/ping.html](https://g.eags.us/eaglercraft/ping.html) + +# How to install: + +1. Download 'embed.min.js' and 'icons.png' and upload them to your server + +2. Add the script: `` + +3. Create a \ like: `
` + +4. In your Javascript, create a new `ServerEmbed` object. Pass the \ object and a CSS width as parameters: +`var embed = new ServerEmbed(document.getElementById("embed"), "500px");` + +5. Call the `ping` function on the new object: `embed.ping("127.0.0.1:25565");` + + (Replace `127.0.0.1:25565` with the `ip:port` or `ws://` or `wss://` address of your server) + +6. You're done + +## Extra features: + +The `ping` function takes five arguments, four of which are optional: +```javascript +ping(addr, name, forceName, hideAddress, hideCracked) +``` + +- `addr` Is the `ip:port` or `ws://` or `wss://` address of your server + +- `name` *(Optional)* Sets the name to display for the server, if left undefined then the default name displayed for the server is the `server_name:` variable of config.yml of the server being pinged (once the server responds) + +- `forceName` *(Optional)* Sets if the `name` parameter should always be shown as the server's name instead of the pinged server's config.yml `server_name:` sent in the response to the ping. Default is false, so by default the optional `name` parameter is only shown until the server responds with it's configured `server_name:`, which is then displayed instead until the ping is sent again + +- `hideAddress` *(Optional)* Sets if the second line of the server's MOTD **should not display** the `addr` parameter on the canvas while it is still connecting, and then also if it shouldn't display the `addr` when the destination server's response does not specify a second line in it's MOTD. Default is false + +- `hideCracked` *(Optional)* Sets if the warning icon and padlock icon, which indicate if the server uses online mode or not, should be hidden from the embed so nobody can tell if your server is cracked or not. Default is false \ No newline at end of file diff --git a/embed.js b/embed.js new file mode 100644 index 0000000..02a2bdc --- /dev/null +++ b/embed.js @@ -0,0 +1,724 @@ +/* +MIT License + +Copyright (c) 2022 Calder Young + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +const CHAR_WIDTHS = [ + 1,9,9,8,8,8,8,7,9,8,9,9,8,9,9,9, + 8,8,8,8,9,9,8,9,8,8,8,8,8,9,9,9, + 4,2,5,6,6,6,6,3,5,5,5,6,2,6,2,6, + 6,6,6,6,6,6,6,6,6,6,2,2,5,6,5,6, + 7,6,6,6,6,6,6,6,6,4,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,4,6,4,6,6, + 3,6,6,6,6,6,5,6,6,2,6,5,3,6,6,6, + 6,6,6,6,4,6,6,6,6,6,6,5,2,5,7,6, + 6,6,6,6,6,6,6,6,6,6,6,4,6,3,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,4,6, + 6,3,6,6,6,6,6,6,6,7,6,6,6,2,6,6, + 8,9,9,6,6,6,8,8,6,8,8,8,8,8,6,6, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,6,9,9,9,5,9,9, + 8,7,7,8,7,8,8,8,7,8,8,7,9,9,6,7, + 7,7,7,7,9,6,7,8,7,6,6,9,7,6,7,1 +]; + +const CHAR_SHADOW_BRIGHTNESS = 0.247; + +const SERVER_ASPECT_RATIO = 38.0 / 256.0; + +class ServerEmbed { + + constructor(tag, width) { + this.containerTag = tag; + tag.style.width = width; + tag.style.height = Math.floor(tag.clientWidth * SERVER_ASPECT_RATIO) + "px"; + tag.style.backgroundColor = "black"; + this.canvas = document.createElement("canvas"); + this.canvas.width = tag.clientWidth; + this.canvas.height = tag.clientHeight; + tag.appendChild(this.canvas); + this.ctx = this.canvas.getContext("2d"); + if(!this.ctx) { + throw new Error("CanvasRenderingContext2D is not supported in this browser"); + } + this.ctx.imageSmoothingEnabled = false; + this.tmpCanvas = document.createElement("canvas"); + this.tmpCanvas.width = 64; + this.tmpCanvas.height = 64; + this.tmpCtx = this.tmpCanvas.getContext("2d"); + this.tmpCtx.imageSmoothingEnabled = false; + const self = this; + this.spriteSheet = document.createElement("img"); + this.spriteSheet.addEventListener("load", () => { + self.isSpriteSheetLoaded = true; + setInterval(() => self.redraw(), 50); + }); + this.spriteSheet.src = "icons.png"; + this.socket = null; + this.connected = false; + this.dirty = false; + this.connecting = 0; + this.currentStatus = { + serverName: "", + serverCracked: false, + pingSentTime: 0, + pingToServer: -1, + motdLine1: "", + motdLine2: "", + onlineCount: 0, + maxCount: 0, + players: [] + }; + this.showCracked = true; + this.tooltip = ""; + this.isShowingTooltip = false; + this.isTooltipEnabled = true; + this.tooltipCanvas = document.createElement("canvas"); + this.tooltipCanvas.width = 64; + this.tooltipCanvas.height = 64; + this.tooltipCanvas.style.display = "none"; + this.tooltipCanvas.style.position = "absolute"; + this.tooltipCanvas.style.zIndex = "100"; + this.tooltipCanvas.style.pointerEvents = "none"; + this.tooltipCtx = this.tooltipCanvas.getContext("2d"); + this.tooltipCtx.imageSmoothingEnabled = false; + document.body.appendChild(this.tooltipCanvas); + this.mouseOver = false; + this.mouseX = 0; + this.mouseY = 0; + this.mousePageX = 0; + this.mousePageY = 0; + var mouseMove = (e) => { + self.mouseOver = true; + self.mouseX = e.offsetX / self.canvas.width * 256.0; + self.mouseY = e.offsetY / self.canvas.height * 38.0; + self.mousePageX = e.pageX; + self.mousePageY = e.pageY; + }; + this.canvas.addEventListener("mouseover", mouseMove); + this.canvas.addEventListener("mousemove", mouseMove); + this.canvas.addEventListener("mouseout", (e) => { + self.mouseOver = false; + self.tooltipCanvas.style.display = "none"; + }); + this.lastWidth = 0; + this.redrawTimer = 0; + } + + setShowCracked(b) { + if(b != this.showCracked) { + this.showCracked = b; + this.dirty = true; + } + } + + setShowTooltip(b) { + this.isTooltipEnabled = b; + } + + getPixelX(x) { + return x * this.canvas.width / 256.0; + } + + getPixelY(y) { + return y * this.canvas.height / 38.0; + } + + drawIcon(x, y, w, h, dx, dy, dw, dh) { + if(this.isSpriteSheetLoaded) { + this.ctx.drawImage(this.spriteSheet, x + 0.05, y + 0.05, w - 0.1, h - 0.1, this.getPixelX(dx), + this.getPixelY(dy), this.getPixelX(dw||w), this.getPixelY(dh||h)); + } + } + + drawChar(x, y, ch, italic) { + if(ch >= CHAR_WIDTHS.length) { + ch = 176; + } + if(!italic) { + this.drawIcon(ch % 16 * 8, Math.floor(ch / 16.0) * 8.0, CHAR_WIDTHS[ch], 8, x, y); + }else { + if(this.isSpriteSheetLoaded) { + this.ctx.resetTransform(); + this.ctx.scale(this.canvas.width / 256.0, this.canvas.height / 38.0); + this.ctx.translate(x + 1.1, y); + this.ctx.transform(1, 0, -0.35, 1, 0, 0); + this.ctx.drawImage(this.spriteSheet, ch % 16 * 8, Math.floor(ch / 16.0) * 8.0, + CHAR_WIDTHS[ch] * 0.99, 8 * 0.99, 0, 0, CHAR_WIDTHS[ch], 8); + this.ctx.resetTransform(); + } + } + return CHAR_WIDTHS[ch]; + } + + drawColoredChar(x, y, ch, color, italic) { + if(ch >= CHAR_WIDTHS.length) { + ch = 176; + } + if(!this.isSpriteSheetLoaded) { + return CHAR_WIDTHS[ch]; + } + this.tmpCtx.globalCompositeOperation = "source-over"; + this.tmpCtx.clearRect(0, 0, 8, 8); + this.tmpCtx.imageSmoothingEnabled = false; + this.tmpCtx.drawImage(this.spriteSheet, ch % 16 * 8, Math.floor(ch / 16.0) * 8.0, + CHAR_WIDTHS[ch] * 0.99, 8 * 0.99, 0, 0, CHAR_WIDTHS[ch], 8); + this.tmpCtx.globalCompositeOperation = "source-in"; + this.tmpCtx.fillStyle = color; + this.tmpCtx.fillRect(0, 0, CHAR_WIDTHS[ch], 8); + + if(!italic) { + this.ctx.drawImage(this.tmpCanvas, 0, 0, CHAR_WIDTHS[ch] * 0.99, 8 * 0.99, this.getPixelX(x), + this.getPixelY(y), this.getPixelX(CHAR_WIDTHS[ch]), this.getPixelY(8)); + }else { + this.ctx.resetTransform(); + this.ctx.scale(this.canvas.width / 256.0, this.canvas.height / 38.0); + this.ctx.translate(x + 1.1, y); + this.ctx.transform(1, 0, -0.35, 1, 0, 0); + this.ctx.drawImage(this.tmpCanvas, 0, 0, CHAR_WIDTHS[ch] * 0.99, 8 * 0.99, 0, 0, CHAR_WIDTHS[ch], 8); + this.ctx.resetTransform(); + } + return CHAR_WIDTHS[ch]; + } + + drawCharLine(x, y, w, clr) { + this.ctx.fillStyle = clr; + this.ctx.fillRect(this.getPixelX(x), this.getPixelY(y), this.getPixelX(w + 0.02), this.getPixelY(1)); + } + + makeColor(r, g, b) { + return "rgba(" + Math.floor(r) + "," + Math.floor(g) + "," + Math.floor(b) + ",1.0)"; + } + + getStringWidth(txt, escapeChar) { + if(!escapeChar) { + escapeChar = 167; + }else { + escapeChar = escapeChar.charCodeAt(0); + } + var j = 0; + for(var i = 0; i < txt.length; ++i) { + var c = txt.charCodeAt(i); + if(c == escapeChar) { + i += 2; + }else { + if(c < CHAR_WIDTHS.length) { + j += CHAR_WIDTHS[c]; + }else { + j += 6; + } + } + } + return j; + } + + drawString(x, y, txt, r, g, b, shadow, randomSeed, escapeChar) { + var escapeCharChar = escapeChar; + if(!escapeChar) { + escapeChar = 167; + }else { + escapeChar = escapeChar.charCodeAt(0); + } + var startX = x; + var startY = y; + if(shadow) { + ++x; + ++y; + } + if(!randomSeed) { + randomSeed = 3492589035; + } + var readFormat = false; + var cr = r; + var cg = g; + var cb = b; + var obfuscate = false; var bold = false; var strikethrough = false; + var underline = false; var italic = false; + for(var i = 0; i < txt.length; ++i) { + var c = txt.charCodeAt(i); + if(c == escapeChar) { // color code symbol + readFormat = true; + continue; + } + if(readFormat) { + if(c == 48) { // black (0) + cr = cg = cb = 0; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 49) { // dark_blue (1) + cr = cg = 0; + cb = 170; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 50) { // dark_green (2) + cr = cb = 0; + cg = 170; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 51) { // dark_aqua (3) + cr = 0; + cg = cb = 170; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 52) { // dark_red (4) + cr = 170; + cg = cb = 0; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 53) { // dark_purple (5) + cr = cb = 170; + cg = 0; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 54) { // gold (6) + cr = 255; + cg = 170; + cb = 0; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 55) { // gray (7) + cr = cg = cb = 170; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 56) { // dark_gray (8) + cr = cg = cb = 85; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 57) { // blue (9) + cr = cg = 85; + cb = 255; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 97) { // green (a) + cr = cb = 85; + cg = 255; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 98) { // aqua (b) + cr = 85; + cg = cb = 255; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 99) { // red (c) + cg = cb = 85; + cr = 255; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 100) { // light_purple (d) + cr = cb = 255; + cg = 85; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 101) { // yellow (e) + cr = cg = 255; + cb = 85; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 102) { // white (f) + cr = cg = cb = 255; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + }else if(c == 107) { // obfuscated (k) + readFormat = false; + obfuscate = true; + continue; + }else if(c == 108) { // bold (l) + readFormat = false; + bold = true; + continue; + }else if(c == 109) { // strikethrough (m) + readFormat = false; + strikethrough = true; + continue; + }else if(c == 110) { // underline (n) + readFormat = false; + underline = true; + continue; + }else if(c == 111) { // italic (o) + readFormat = false; + italic = true; + continue; + }else if(c == 114) { // reset (r) + cr = r; + cg = g; + cb = b; + readFormat = obfuscate = bold = + strikethrough = underline = italic = false; + continue; + } + readFormat = false; + continue; + } + if(obfuscate) { + this.dirty = true; + var w = 6 + if(c < CHAR_WIDTHS.length) { + w = CHAR_WIDTHS[c]; + } + var j = 0; + var newChar = 0; + do { + var t = randomSeed += 0x6D2B79F5; + t = Math.imul(t ^ t >>> 15, t | 1); + t ^= t + Math.imul(t ^ t >>> 7, t | 61); + newChar = (((t ^ t >>> 14) >>> 0) / 429496) & 255; + }while((newChar == c || CHAR_WIDTHS[newChar] != w) && ++j < 1000); + c = newChar; + } + if(shadow) { + var ccc = this.makeColor(cr * CHAR_SHADOW_BRIGHTNESS, + cg * CHAR_SHADOW_BRIGHTNESS, cb * CHAR_SHADOW_BRIGHTNESS); + var ox = x; + var ow = 0; + if(bold) this.drawColoredChar(x + 1, y + 1, c, ccc, italic); + x += (ow = this.drawColoredChar(x, y, c, ccc, italic)); + if(bold) { + ++x; + ++ow; + } + if(strikethrough) this.drawCharLine(ox, y + 4, ow, ccc); + if(underline) this.drawCharLine(ox, y + 8, ow, ccc); + }else { + if(cr >= 250 && cg >= 250 && cb >= 250) { + var ox = x; + var ow = 0; + if(bold) this.drawChar(x + 0.5, y + 0.5, c, italic); + x += (ow = this.drawChar(x, y, c, italic)); + if(bold) { + ++x; + ++ow; + } + if(strikethrough) this.drawCharLine(ox, y + 4, ow, "#FFFFFF"); + if(underline) this.drawCharLine(ox, y + 8, ow, "#FFFFFF"); + }else { + var ccc = this.makeColor(cr,cg,cb); + var ox = x; + var ow = 0; + if(bold) this.drawColoredChar(x + 0.5, y + 0.5, c, ccc, italic); + x += (ow = this.drawColoredChar(x, y, c, ccc, italic)); + if(bold) { + ++x; + ++ow; + } + if(strikethrough) this.drawCharLine(ox, y + 4, ow, ccc); + if(underline) this.drawCharLine(ox, y + 8, ow, ccc); + } + } + if(x > 250.0) { + break; + } + } + if(shadow) { + this.drawString(startX, startY, txt, r, g, b, false, randomSeed, escapeCharChar); + } + } + + redraw() { + if(this.dirty || (this.mouseOver && ++this.redrawTimer % 6 == 0) || this.canvas.width != this.containerTag.clientWidth) { + this.dirty = false; + if(this.canvas.width != this.containerTag.clientWidth) { + this.containerTag.style.height = Math.floor(this.containerTag.clientWidth * SERVER_ASPECT_RATIO) + "px"; + this.canvas.width = this.containerTag.clientWidth; + this.canvas.height = this.containerTag.clientHeight; + } + var tooltipNewArray = []; + this.ctx.imageSmoothingEnabled = false; + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.ctx.fillStyle = "#101010"; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.ctx.strokeStyle = "#808080"; + this.ctx.lineWidth = this.getPixelX(1.0); + this.ctx.strokeRect(this.ctx.lineWidth / 2, this.ctx.lineWidth / 2,this.canvas.width - this.ctx.lineWidth,this.canvas.height - this.ctx.lineWidth); + + if(!this.iconCanvas) { + this.drawIcon(128, 0, 64, 64, 4, 4, 30, 30); + }else { + this.ctx.drawImage(this.iconCanvas, 0, 0, 64, 64, this.getPixelX(4), + this.getPixelY(4), this.getPixelX(30), this.getPixelY(30)); + } + + var v = Date.now(); + var drawAddress = false; + + this.drawString(38, 4, this.currentStatus.serverName, 255, 255, 255, true, v); + if(this.currentStatus.pingToServer >= 0) { + if(this.currentStatus.motdLine1.length > 0) { + this.drawString(38, 15, ""+this.currentStatus.motdLine1, 255, 255, 255, true, v); + } + if(this.currentStatus.motdLine2.length > 0) { + this.drawString(38, 27, ""+this.currentStatus.motdLine2, 255, 255, 255, true, v); + }else { + drawAddress = true; + } + }else if(v - this.connecting < 7000) { + this.dirty = true; + var dots = Math.floor(v / 300) % 4; + this.drawString(38, 15, "Connecting" + (dots > 0 ? "." : "") + (dots > 1 ? "." : "") + (dots > 2 ? "." : ""), 128, 128, 128, true, v); + drawAddress = true; + }else { + this.drawString(38, 15, "No Connection", 96, 96, 96, true, v); + drawAddress = true; + } + if(drawAddress && this.showAddress) { + this.drawString(38, 27, this.originalAddress, 48, 48, 48, true, v); + } + var mouseIsOverPing = this.mouseOver && this.mouseX >= 233 && this.mouseX <= 256 && this.mouseY >= 1 && this.mouseY <= 11; + var mouseIsOverPlayers = false; + if(this.currentStatus.pingToServer >= 0) { + if(mouseIsOverPing) tooltipNewArray = [""+this.currentStatus.pingToServer+"ms"]; + var i = 5; + if(this.currentStatus.pingToServer < 150) i = 0; + else if(this.currentStatus.pingToServer < 300) i = 1; + else if(this.currentStatus.pingToServer < 600) i = 2; + else if(this.currentStatus.pingToServer < 1000) i = 3; + else i = 4; + this.drawIcon(128, 80 + i * 8, 10, 7, 243, 3); + var onlineStr = "" + this.currentStatus.onlineCount + "/" + this.currentStatus.maxCount; + var xx = 253 - this.getStringWidth(onlineStr); + mouseIsOverPlayers = !mouseIsOverPing && this.mouseOver && this.mouseX >= xx - 5 && this.mouseX <= 256 + && this.mouseY >= 12 && this.mouseY <= 23; + this.drawString(xx, 14, onlineStr, 128, 128, 128, true); + if(mouseIsOverPlayers) { + tooltipNewArray = this.currentStatus.players; + } + }else { + if(v - this.connecting < 7000 || this.connected) { + this.dirty = true; + if(mouseIsOverPing) tooltipNewArray = ["Connecting..."]; + this.drawIcon(138, 80 + Math.abs(Math.floor(v / 100.0) % 8 - 4) * 8, 10, 7, 243, 3); + }else { + if(mouseIsOverPing) tooltipNewArray = ["No Connection"]; + this.drawIcon(128, 120, 10, 7, 243, 3); + } + } + if(this.showCracked && this.currentStatus.pingToServer >= 0) { + if(this.currentStatus.serverCracked) { + this.drawIcon(144, 64, 16, 16, 243, 25, 10, 10); + }else { + this.drawIcon(176, 96, 16, 16, 243, 25, 11, 11); + } + if(!mouseIsOverPlayers && this.mouseOver && this.mouseX >= 239 && this.mouseX <= 256 + && this.mouseY >= 22 && this.mouseY <= 36) { + if(this.currentStatus.serverCracked) { + tooltipNewArray = ["Server is cracked"]; + }else { + tooltipNewArray = ["Server has login"]; + } + } + } + if(!tooltipNewArray || !this.mouseOver || tooltipNewArray.length == 0) { + this.tooltipCanvas.style.display = "none"; + }else { + this.showTooltip(tooltipNewArray); + } + } + } + + ping(addr, name, forceName, hideAddress, hideCracked) { + if(!name) { + name = "Minecraft Server"; + this.showQueryName = (typeof forceName === "undefined") || !forceName; + }else { + this.showQueryName = (typeof forceName !== "undefined") && !forceName; + } + this.defaultName = name; + this.showAddress = !hideAddress; + this.originalAddress = addr; + if(typeof hideCracked !== "undefined") { + this.showCracked = !hideCracked; + } + if(addr.indexOf("ws://") != 0 && addr.indexOf("wss://") != 0) { + addr = (window.location.href.indexOf("https:") == 0 ? "wss" : "ws") + "://" + addr; + } + if(this.socket != null && this.connected) { + this.socket.close(); + } + this.currentStatus = { + serverName: name, + serverCracked: false, + pingSentTime: Date.now(), + pingToServer: -1, + motdLine1: "Connecting...", + motdLine2: "", + onlineCount: 0, + maxCount: 0, + players: [] + }; + this.dirty = true; + if(this.connected || this.connecting > 0) { + this.socket.close(); + this.connected = false; + this.connecting = 0; + this.redraw(); + } + this.connecting = Date.now(); + const self = this; + this.socket = new WebSocket(addr); + this.socket.binaryType = "arraybuffer"; + this.socket.onopen = () => { + self.socket.send("Accept: MOTD"); + self.connecting = 0; + self.connected = true; + self.dirty = true; + }; + this.socket.onmessage = (e) => { + if(e.data) { + if(typeof e.data === "string") { + try { + var dat = JSON.parse(e.data); + if(dat["type"].toLowerCase() === "motd") { + self.dirty = true; + if(self.currentStatus.pingSentTime > 0) { + self.currentStatus.pingToServer = Date.now() - self.currentStatus.pingSentTime; + self.currentStatus.pingSentTime = 0; + } + if(this.showQueryName) { + self.currentStatus.serverName = dat["name"]; + }else { + self.currentStatus.serverName = this.defaultName; + } + self.currentStatus.serverCracked = dat["cracked"]; + var lns = dat["data"]["motd"]; + if(lns && lns.length > 0) { + self.currentStatus.motdLine1 = lns[0]; + if(lns.length > 1) { + self.currentStatus.motdLine2 = lns[1]; + } + } + self.currentStatus.onlineCount = dat["data"]["online"]; + self.currentStatus.maxCount = dat["data"]["max"]; + self.currentStatus.players = dat["data"]["players"]||[]; + } + }catch(e) { + self.currentStatus.motdLine1 = "ERROR: " + e; + } + }else { + var dat = new Uint8Array(e.data); + if(dat.length == 64*64*4) { + this.updateIconFromBytes(dat); + this.dirty = true; + } + } + } + }; + this.socket.onclose = (e) => { + self.connecting = 0; + self.connected = false; + self.dirty = true; + }; + this.socket.onerror = (e) => { + if(self.connected || self.connecting > 0) { + self.socket.close(); + } + self.connecting = 0; + self.connected = false; + self.dirty = true; + }; + } + + isActive() { + return self.connecting > 0 || self.connected; + } + + setTooltip(ttList) { + if(this.isTooltipEnabled) { + var tst = ttList.join(); + if(tst !== this.tooltip || this.lastWidth !== this.canvas.width) { + var listWidth = 0; + var listHeight = ttList.length * 9; + for(var i = 0; i < ttList.length; ++i) { + listWidth = Math.max(listWidth, this.getStringWidth(ttList[i])); + } + listWidth += 6; + listHeight += 3; + this.tooltipCanvas.width = this.getPixelX(listWidth); + this.tooltipCanvas.height = this.getPixelY(listHeight); + var mainCtx = this.ctx; + this.ctx = this.tooltipCtx; + this.ctx.imageSmoothingEnabled = false; + this.ctx.clearRect(0, 0, this.tooltipCanvas.width, this.tooltipCanvas.height); + this.ctx.fillStyle = "#101010E0"; + this.ctx.fillRect(0, 0, this.tooltipCanvas.width, this.tooltipCanvas.height); + for(var i = 0; i < ttList.length; ++i) { + this.drawString(3, (2 + i*9), ttList[i], 256, 256, 256, true); + } + this.ctx = mainCtx; + this.tooltip = tst; + this.lastWidth = this.canvas.width; + } + } + } + + showTooltip(ttList) { + this.dirty = true; + if(!this.mouseOver) { + this.tooltipCanvas.style.display = "none"; + return; + } + this.setTooltip(ttList); + this.tooltipCanvas.style.display = "block"; + var x = this.mousePageX + this.getPixelX(4); + var y = this.mousePageY - this.getPixelY(9); + if(this.mousePageX + this.tooltipCanvas.width + 2 > window.innerWidth) { + x = window.innerWidth - this.tooltipCanvas.width - 2; + } + if(this.mousePageY + this.tooltipCanvas.height + 2 > window.innerHeight) { + y = window.innerHeight - this.tooltipCanvas.height - 2; + if(y < 0) { + y = 0; + } + } + this.tooltipCanvas.style.left = x + "px"; + this.tooltipCanvas.style.top = y + "px"; + } + + updateIconFromBytes(arr) { + if(!this.iconCanvas) { + this.iconCanvas = document.createElement("canvas"); + this.iconCanvas.width = 64; + this.iconCanvas.height = 64; + this.iconContext = this.iconCanvas.getContext("2d"); + } + var dat = this.iconContext.createImageData(64, 64); + for(var i = 0, l = 64*64*4; i < l; ++i) { + dat.data[i] = arr[i]; + } + this.iconContext.putImageData(dat, 0, 0); + } + +} + diff --git a/embed.min.js b/embed.min.js new file mode 100644 index 0000000..634d85f --- /dev/null +++ b/embed.min.js @@ -0,0 +1,30 @@ +// Copyright (c) 2022 Calder Young +var CHAR_WIDTHS=[1,9,9,8,8,8,8,7,9,8,9,9,8,9,9,9,8,8,8,8,9,9,8,9,8,8,8,8,8,9,9,9,4,2,5,6,6,6,6,3,5,5,5,6,2,6,2,6,6,6,6,6,6,6,6,6,6,6,2,2,5,6,5,6,7,6,6,6,6,6,6,6,6,4,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,4,6,4,6,6,3,6,6,6,6,6,5,6,6,2,6,5,3,6,6,6,6,6,6,6,4,6,6,6,6,6,6,5,2,5,7,6,6,6,6,6,6,6,6,6,6,6,6,4,6,3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,4,6,6,3,6,6,6,6,6,6,6,7,6,6,6,2,6,6,8,9,9,6,6,6,8,8,6,8,8,8,8,8,6,6,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,6,9,9,9,5,9,9,8,7,7,8,7,8,8,8,7,8,8,7,9,9,6,7,7,7, +7,7,9,6,7,8,7,6,6,9,7,6,7,1],CHAR_SHADOW_BRIGHTNESS=.247,SERVER_ASPECT_RATIO=.1484375,ServerEmbed=function(a,c){this.containerTag=a;a.style.width=c;a.style.height=Math.floor(a.clientWidth*SERVER_ASPECT_RATIO)+"px";a.style.backgroundColor="black";this.canvas=document.createElement("canvas");this.canvas.width=a.clientWidth;this.canvas.height=a.clientHeight;a.appendChild(this.canvas);this.ctx=this.canvas.getContext("2d");if(!this.ctx)throw Error("CanvasRenderingContext2D is not supported in this browser"); +this.ctx.imageSmoothingEnabled=!1;this.tmpCanvas=document.createElement("canvas");this.tmpCanvas.width=64;this.tmpCanvas.height=64;this.tmpCtx=this.tmpCanvas.getContext("2d");this.tmpCtx.imageSmoothingEnabled=!1;var b=this;this.spriteSheet=document.createElement("img");this.spriteSheet.addEventListener("load",function(){b.isSpriteSheetLoaded=!0;setInterval(function(){return b.redraw()},50)});this.spriteSheet.src="icons.png";this.socket=null;this.dirty=this.connected=!1;this.connecting=0;this.currentStatus= +{serverName:"",serverCracked:!1,pingSentTime:0,pingToServer:-1,motdLine1:"",motdLine2:"",onlineCount:0,maxCount:0,players:[]};this.showCracked=!0;this.tooltip="";this.isShowingTooltip=!1;this.isTooltipEnabled=!0;this.tooltipCanvas=document.createElement("canvas");this.tooltipCanvas.width=64;this.tooltipCanvas.height=64;this.tooltipCanvas.style.display="none";this.tooltipCanvas.style.position="absolute";this.tooltipCanvas.style.zIndex="100";this.tooltipCanvas.style.pointerEvents="none";this.tooltipCtx= +this.tooltipCanvas.getContext("2d");this.tooltipCtx.imageSmoothingEnabled=!1;document.body.appendChild(this.tooltipCanvas);this.mouseOver=!1;this.mousePageY=this.mousePageX=this.mouseY=this.mouseX=0;var g=function(e){b.mouseOver=!0;b.mouseX=e.offsetX/b.canvas.width*256;b.mouseY=e.offsetY/b.canvas.height*38;b.mousePageX=e.pageX;b.mousePageY=e.pageY};this.canvas.addEventListener("mouseover",g);this.canvas.addEventListener("mousemove",g);this.canvas.addEventListener("mouseout",function(e){b.mouseOver= +!1;b.tooltipCanvas.style.display="none"});this.redrawTimer=this.lastWidth=0};ServerEmbed.prototype.setShowCracked=function(a){a!=this.showCracked&&(this.showCracked=a,this.dirty=!0)};ServerEmbed.prototype.setShowTooltip=function(a){this.isTooltipEnabled=a};ServerEmbed.prototype.getPixelX=function(a){return a*this.canvas.width/256};ServerEmbed.prototype.getPixelY=function(a){return a*this.canvas.height/38}; +ServerEmbed.prototype.drawIcon=function(a,c,b,g,e,y,f,w){this.isSpriteSheetLoaded&&this.ctx.drawImage(this.spriteSheet,a+.05,c+.05,b-.1,g-.1,this.getPixelX(e),this.getPixelY(y),this.getPixelX(f||b),this.getPixelY(w||g))}; +ServerEmbed.prototype.drawChar=function(a,c,b,g){b>=CHAR_WIDTHS.length&&(b=176);g?this.isSpriteSheetLoaded&&(this.ctx.resetTransform(),this.ctx.scale(this.canvas.width/256,this.canvas.height/38),this.ctx.translate(a+1.1,c),this.ctx.transform(1,0,-.35,1,0,0),this.ctx.drawImage(this.spriteSheet,b%16*8,8*Math.floor(b/16),.99*CHAR_WIDTHS[b],7.92,0,0,CHAR_WIDTHS[b],8),this.ctx.resetTransform()):this.drawIcon(b%16*8,8*Math.floor(b/16),CHAR_WIDTHS[b],8,a,c);return CHAR_WIDTHS[b]}; +ServerEmbed.prototype.drawColoredChar=function(a,c,b,g,e){b>=CHAR_WIDTHS.length&&(b=176);if(!this.isSpriteSheetLoaded)return CHAR_WIDTHS[b];this.tmpCtx.globalCompositeOperation="source-over";this.tmpCtx.clearRect(0,0,8,8);this.tmpCtx.imageSmoothingEnabled=!1;this.tmpCtx.drawImage(this.spriteSheet,b%16*8,8*Math.floor(b/16),.99*CHAR_WIDTHS[b],7.92,0,0,CHAR_WIDTHS[b],8);this.tmpCtx.globalCompositeOperation="source-in";this.tmpCtx.fillStyle=g;this.tmpCtx.fillRect(0,0,CHAR_WIDTHS[b],8);e?(this.ctx.resetTransform(), +this.ctx.scale(this.canvas.width/256,this.canvas.height/38),this.ctx.translate(a+1.1,c),this.ctx.transform(1,0,-.35,1,0,0),this.ctx.drawImage(this.tmpCanvas,0,0,.99*CHAR_WIDTHS[b],7.92,0,0,CHAR_WIDTHS[b],8),this.ctx.resetTransform()):this.ctx.drawImage(this.tmpCanvas,0,0,.99*CHAR_WIDTHS[b],7.92,this.getPixelX(a),this.getPixelY(c),this.getPixelX(CHAR_WIDTHS[b]),this.getPixelY(8));return CHAR_WIDTHS[b]}; +ServerEmbed.prototype.drawCharLine=function(a,c,b,g){this.ctx.fillStyle=g;this.ctx.fillRect(this.getPixelX(a),this.getPixelY(c),this.getPixelX(b+.02),this.getPixelY(1))};ServerEmbed.prototype.makeColor=function(a,c,b){return"rgba("+Math.floor(a)+","+Math.floor(c)+","+Math.floor(b)+",1.0)"};ServerEmbed.prototype.getStringWidth=function(a,c){c=c?c.charCodeAt(0):167;for(var b=0,g=0;g>>15,v|1);v^=v+Math.imul(v^v>>>7,v|61);v=((v^v>>>14)>>>0)/429496&255}while((v==d||CHAR_WIDTHS[v]!=x)&&1E3>++z);d=v}f?(x=this.makeColor(p*CHAR_SHADOW_BRIGHTNESS,q*CHAR_SHADOW_BRIGHTNESS,r*CHAR_SHADOW_BRIGHTNESS),z=a,k&&this.drawColoredChar(a+1,c+1,d,x,l),a+=d=this.drawColoredChar(a,c,d,x,l),k&&(++a,++d),m&&this.drawCharLine(z,c+4,d,x),n&&this.drawCharLine(z,c+8,d,x)):250<=p&&250<=q&&250<=r?(z=a,k&&this.drawChar(a+.5,c+.5,d,l),a+=d=this.drawChar(a, +c,d,l),k&&(++a,++d),m&&this.drawCharLine(z,c+4,d,"#FFFFFF"),n&&this.drawCharLine(z,c+8,d,"#FFFFFF")):(x=this.makeColor(p,q,r),z=a,k&&this.drawColoredChar(a+.5,c+.5,d,x,l),a+=d=this.drawColoredChar(a,c,d,x,l),k&&(++a,++d),m&&this.drawCharLine(z,c+4,d,x),n&&this.drawCharLine(z,c+8,d,x));if(250c-this.connecting?(this.dirty=!0,b=Math.floor(c/300)%4,this.drawString(38,15,"Connecting"+(0=this.mouseX&&1<=this.mouseY&&11>=this.mouseY;var g=!1;if(0<=this.currentStatus.pingToServer){b&&(a=[""+this.currentStatus.pingToServer+"ms"]);this.drawIcon(128,80+8*(150>this.currentStatus.pingToServer?0:300>this.currentStatus.pingToServer?1:600>this.currentStatus.pingToServer?2:1E3>this.currentStatus.pingToServer?3:4),10,7,243,3);c=""+this.currentStatus.onlineCount+"/"+this.currentStatus.maxCount;var e= +253-this.getStringWidth(c);g=!b&&this.mouseOver&&this.mouseX>=e-5&&256>=this.mouseX&&12<=this.mouseY&&23>=this.mouseY;this.drawString(e,14,c,128,128,128,!0);g&&(a=this.currentStatus.players)}else 7E3>c-this.connecting||this.connected?(this.dirty=!0,b&&(a=["Connecting..."]),this.drawIcon(138,80+8*Math.abs(Math.floor(c/100)%8-4),10,7,243,3)):(b&&(a=["No Connection"]),this.drawIcon(128,120,10,7,243,3));this.showCracked&&0<=this.currentStatus.pingToServer&&(this.currentStatus.serverCracked?this.drawIcon(144, +64,16,16,243,25,10,10):this.drawIcon(176,96,16,16,243,25,11,11),!g&&this.mouseOver&&239<=this.mouseX&&256>=this.mouseX&&22<=this.mouseY&&36>=this.mouseY&&(a=this.currentStatus.serverCracked?["Server is cracked"]:["Server has login"]));a&&this.mouseOver&&0!=a.length?this.showTooltip(a):this.tooltipCanvas.style.display="none"}}; +ServerEmbed.prototype.ping=function(a,c,b,g,e){var y=this;c?this.showQueryName="undefined"!==typeof b&&!b:(c="Minecraft Server",this.showQueryName="undefined"===typeof b||!b);this.defaultName=c;this.showAddress=!g;this.originalAddress=a;"undefined"!==typeof e&&(this.showCracked=!e);0!=a.indexOf("ws://")&&0!=a.indexOf("wss://")&&(a=(0==window.location.href.indexOf("https:")?"wss":"ws")+"://"+a);null!=this.socket&&this.connected&&this.socket.close();this.currentStatus={serverName:c,serverCracked:!1, +pingSentTime:Date.now(),pingToServer:-1,motdLine1:"Connecting...",motdLine2:"",onlineCount:0,maxCount:0,players:[]};this.dirty=!0;if(this.connected||0window.innerWidth&&(a=window.innerWidth-this.tooltipCanvas.width-2);this.mousePageY+this.tooltipCanvas.height+2>window.innerHeight&&(c=window.innerHeight-this.tooltipCanvas.height-2,0>c&&(c=0));this.tooltipCanvas.style.left=a+"px";this.tooltipCanvas.style.top= +c+"px"}else this.tooltipCanvas.style.display="none"};ServerEmbed.prototype.updateIconFromBytes=function(a){this.iconCanvas||(this.iconCanvas=document.createElement("canvas"),this.iconCanvas.width=64,this.iconCanvas.height=64,this.iconContext=this.iconCanvas.getContext("2d"));for(var c=this.iconContext.createImageData(64,64),b=0;16384>b;++b)c.data[b]=a[b];this.iconContext.putImageData(c,0,0)}; \ No newline at end of file diff --git a/icons.png b/icons.png new file mode 100644 index 0000000..987ff85 Binary files /dev/null and b/icons.png differ diff --git a/icons_src/background.png b/icons_src/background.png new file mode 100644 index 0000000..929d3db Binary files /dev/null and b/icons_src/background.png differ diff --git a/icons_src/certificate_seal.ico b/icons_src/certificate_seal.ico new file mode 100644 index 0000000..a863cf5 Binary files /dev/null and b/icons_src/certificate_seal.ico differ diff --git a/icons_src/font.png b/icons_src/font.png new file mode 100644 index 0000000..0b5144d Binary files /dev/null and b/icons_src/font.png differ diff --git a/icons_src/icons.png b/icons_src/icons.png new file mode 100644 index 0000000..10f4373 Binary files /dev/null and b/icons_src/icons.png differ diff --git a/icons_src/key_padlock.ico b/icons_src/key_padlock.ico new file mode 100644 index 0000000..d5c7968 Binary files /dev/null and b/icons_src/key_padlock.ico differ diff --git a/icons_src/msg_warning.ico b/icons_src/msg_warning.ico new file mode 100644 index 0000000..90fcf18 Binary files /dev/null and b/icons_src/msg_warning.ico differ diff --git a/icons_src/unknown_pack_64x64.png b/icons_src/unknown_pack_64x64.png new file mode 100644 index 0000000..2323aa7 Binary files /dev/null and b/icons_src/unknown_pack_64x64.png differ diff --git a/test.html b/test.html new file mode 100644 index 0000000..11f9756 --- /dev/null +++ b/test.html @@ -0,0 +1,14 @@ + + +Eaglercraft Server Embed Test + + + + +
+ + \ No newline at end of file