class UIElement { static elements = []; constructor (x, y, w, h, layer = 0) { this.x = x; this.y = y; this.w = w; this.h = h; this.layer = layer; this.isVisible = true; this.clickable = false; this.acceptsItem = false; UIElement.elements.push(this); } get visible() { return this.isVisible; } set visible(visible) { this.isVisible = visible; } delete() { for (let i = 0; i < UIElement.elements.length; i++) { if (UIElement.elements[i] == this) { UIElement.elements.splice(i, 1); break; } } } draw() { } click() { } mouseUp() { } pointInBound(x, y) { return x >= this.x && y >= this.y && x <= this.x + this.w && y <= this.y + this.h; } static drawAll() { UIElement.elements.sort((a, b) => (a.layer > b.layer ? 1 : -1)); for (let i in UIElement.elements) { if (UIElement.elements[i].visible) UIElement.elements[i].draw(); } } } class Cursor extends UIElement { constructor(x, y) { super(x, y, 16, 16, 9999999); this.cursor = "default"; this.item = null; this.placingServer = false; } draw() { this.updateCursor(); if (this.item != null) { Game.c.drawImage(Assets.items[this.item], this.x, this.y); Game.c.globalAlpha = 0.5; } if (this.placingServer) { Game.c.globalAlpha = 0.5; let sx = Math.floor(this.x / Game.WIDTH * Game.TILES_X) * Game.TILESIZE; let sy = Math.floor(this.y / Game.HEIGHT * Game.TILES_Y) * Game.TILESIZE; Game.c.drawImage(Assets.server[0], sx, sy - 16); Game.c.globalAlpha = 0.3; if (Hitbox.tempCollidesAny(sx, sy, 16, 16)) { Game.c.fillStyle = "red"; } else { Game.c.fillStyle = "green"; } Game.c.fillRect(sx, sy - 8, 16, 24); Game.c.globalAlpha = 1; } Game.c.drawImage(Assets.ui.cursor[this.cursor], this.x, this.y); Game.c.globalAlpha = 1; } updateCursor() { this.cursor = "default"; for (let i = UIElement.elements.length - 1; i >= 0; i--) { if (UIElement.elements[i] != this && UIElement.elements[i].visible && UIElement.elements[i].pointInBound(this.x, this.y)) { this.cursor = "default"; return; } } for (let i = GameObject.gameObjects.length -1; i >= 0; i--) { if (GameObject.gameObjects[i].clickable && GameObject.gameObjects[i].hitbox != undefined && GameObject.gameObjects[i].hitbox.pointInBound(this.x, this.y)) { if (GameObject.gameObjects[i] instanceof Server && GameObject.gameObjects[i].failure) { this.cursor = "wrench"; return; } } } } click() { if (this.placingServer) { let sx = Math.floor(this.x / Game.WIDTH * Game.TILES_X) * Game.TILESIZE; let sy = Math.floor(this.y / Game.HEIGHT * Game.TILES_Y) * Game.TILESIZE; if (!Hitbox.tempCollidesAny(sx, sy, 16, 16)) { if (Game.gamestate == "tutorial") { Game.tutorialServers.push(new TutorialServer(sx, sy)); tutorialStep(); } else { new Server(sx, sy); } this.placingServer = false; } return; } for (let i = UIElement.elements.length - 1; i >= 0; i--) { if (UIElement.elements[i].clickable && UIElement.elements[i].visible) { if (UIElement.elements[i].pointInBound(this.x, this.y)) { if (this.item == null) { UIElement.elements[i].click(); return; } else if (UIElement.elements[i].acceptsItem) { if (UIElement.elements[i].passItem(this.item)) { this.item = null; return; } } } } } for (let i = GameObject.gameObjects.length -1; i >= 0; i--) { if (GameObject.gameObjects[i].clickable && GameObject.gameObjects[i].hitbox != undefined) { if ( GameObject.gameObjects[i].hitbox.pointInBound(this.x, this.y) || GameObject.gameObjects[i] == Game.player && pointInRect(this.x, this.y, Game.player.x, Game.player.y, Game.TILESIZE, Game.TILESIZE) ) { if (this.item == null) { GameObject.gameObjects[i].click(); return; } else if (GameObject.gameObjects[i].acceptsItem) { if (GameObject.gameObjects[i].passItem(this.item)) { this.item = null; return; } } } } } } mouseUp() { if (this.item != null) return; for (let i = UIElement.elements.length - 1; i >= 0; i--) { if (UIElement.elements[i].clickable && UIElement.elements[i].visible) { if (UIElement.elements[i].pointInBound(this.x, this.y)) { UIElement.elements[i].mouseUp(); return; } } } for (let i = GameObject.gameObjects.length -1; i >= 0; i--) { if (GameObject.gameObjects[i].clickable && GameObject.gameObjects[i].hitbox != undefined) { if (GameObject.gameObjects[i].hitbox.pointInBound(this.x, this.y)) { GameObject.gameObjects[i].mouseUp(); return; } } } } } class UIText extends UIElement { constructor (text, x, y, col, style = "small" , layer = 0) { super(x, y, 0, 0, layer); this.text = text; this.style = style; this.col = col; } draw() { Game.c.font = Assets.fonts[this.style].full; Game.c.fillStyle = this.col; Game.c.fillText(this.text, this.x, this.y); } } class ServerUI extends UIElement { constructor(server) { super( Math.floor(Game.WIDTH - 180) / 2, Math.floor(Game.HEIGHT - 100) / 2, 180, 100 ); this.server = server; this.closeButton = new CloseButton(this.x, this.y, () => { this.visible = false; Assets.sounds.click.play(); }, 1); this.closeButton.visible = false; this.connectors = []; for (let i = 0; i < this.server.numLines; i++) { for (let j = 0; j < this.server.numConnectors; j++) { this.connectors.push(new ConnectorUI(this.x + 30 + 25*j, this.y - 7 + this.h / 2 + (i - 0.5) * 50, this)); } } this.cables = []; let conn1 = Math.floor(Math.random() * this.connectors.length / 2); let conn2 = Math.floor(Math.random() * this.connectors.length / 2 + this.connectors.length / 2); this.requiredConnectors = [conn1, conn2]; this.cables.push(new CableUI( this.connectors[conn1].x, this.connectors[conn1].y, this.connectors[conn2].x, this.connectors[conn2].y, "red" )); } createNewCableFailure() { if (this.requiredConnectors.length >= 6) return; for (let i = 0; i < 2; i++) { let newConn; do { newConn = Math.floor(Math.random() * this.connectors.length); } while (this.requiredConnectors.includes(newConn)); this.requiredConnectors.push(newConn); } } createMoveCableFailure() { let numChanges = Math.floor(Math.random() * 2) + 1; // 50% one cable, 50% two cables for (let i = 0; i < numChanges; i++) { let newConn; do { newConn = Math.floor(Math.random() * this.connectors.length); // find free spot } while (this.requiredConnectors.includes(newConn)); let changeI = Math.floor(Math.random() * this.requiredConnectors.length); // find index to change this.requiredConnectors[i] = newConn; } } createBrokenCableFailure() { this.cables[Math.floor(Math.random() * this.cables.length)].broken = true; } createFailure() { if (this.requiredConnectors.length < 6 && Math.random() < 0.1) { // 10% chance new cable this.createNewCableFailure(); return; } else if (Math.random() > 0.3) { // otherwise 30% replace cable, 70% move cable this.createMoveCableFailure(); return; } else { this.createBrokenCableFailure(); return; } } set visible(visible) { this.isVisible = visible; this.closeButton.visible = visible; for (let i = 0; i < this.connectors.length; i++) { this.connectors[i].visible = visible; } for (let i = 0; i < this.cables.length; i++) { this.cables[i].visible = visible; } } get visible() { return this.isVisible; } checkFixed() { if (!this.server.failure) return; for (let i = 0; i < this.cables.length; i++) { if (this.cables[i].broken) { return; } } for (let i = 0; i < this.requiredConnectors.length; i++) { let found = false; for (let j = 0; j < this.cables.length; j++) { if (this.cables[j].connectors == null) continue; if ( this.cables[j].connectors[0].x == this.connectors[this.requiredConnectors[i]].x && this.cables[j].connectors[0].y == this.connectors[this.requiredConnectors[i]].y || this.cables[j].connectors[1].x == this.connectors[this.requiredConnectors[i]].x && this.cables[j].connectors[1].y == this.connectors[this.requiredConnectors[i]].y ) { found = true; break; } } if (!found) { return; } } this.server.fix(); } draw() { this.w = Assets.ui.server.background.width this.h = Assets.ui.server.background.height; this.x = Math.floor(Game.WIDTH - this.w) / 2; this.y = Math.floor(Game.HEIGHT - this.h) / 2; this.checkFixed(); for (let i = 0; i < this.connectors.length; i++) { this.connectors[i].required = this.requiredConnectors.includes(i); } this.closeButton.x = this.x + this.w - 15; this.closeButton.y = this.y + 6; Game.c.drawImage(Assets.ui.server.background, this.x, this.y); for (let i = 0; i < this.cables.length; i++) { if (this.cables[i].connectors == null) { this.cables.splice(i, 1); i--; if (Game.player.inventory.length >= Game.player.inventorySlots) { this.closeButton.click(); } } } } } class ConnectorUI extends UIElement { constructor(x, y, parent, layer = 1) { super(x, y, 13, 13, layer); this.acceptsItem = true; this.clickable = true; this.parent = parent; this.required = false; } draw() { this.w = Assets.ui.server.connector.width; this.h = Assets.ui.server.connector.height; if (this.required) { c.fillStyle = "red"; c.fillRect(this.x - 1, this.y - 1, this.w + 2, this.h + 2); } c.drawImage(Assets.ui.server.connector, this.x, this.y); } passItem(item) { if (item.startsWith("cable_")) { let cable = new CableUI(this.x, this.y, Game.cursor.x + 8, Game.cursor.y + 8, item.substr(6)); cable.connectors[1].dragging = true; this.parent.cables.push(cable); Assets.sounds.cable.disconnect.play(); return true; } else { return false; } } } class CableConnectorUI extends UIElement { constructor(x, y, parent, layer = 2) { super(x, y, 13, 13, layer); this.dragging = false; this.clickable = true; this.parent = parent; this.partner = null; } draw() { if (this.dragging) { this.x = Game.cursor.x - 7; this.y = Game.cursor.y - 7; } c.drawImage(Assets.ui.server.cableConnector, this.x, this.y); } click() { this.dragging = true; this.layer = 3; Assets.sounds.cable.disconnect.play(); } mouseUp() { if (this.dragging) { this.layer = 2; Assets.sounds.cable.connect.play(); this.dragging = false; if (this.partner.pointInBound(Game.cursor.x, Game.cursor.y)) { this.delete(); this.partner.delete(); this.parent.delete(); if (!this.parent.broken) { Game.cursor.item = "cable_" + this.parent.color; } else { if (Game.gamestate == "tutorial") { Game.player.inventory[0] = "broken_cable"; tutorialStep(); } else { Game.cursor.item = "broken_cable"; } } } for (let i = 0; i < UIElement.elements.length; i++) { if (UIElement.elements[i].visible && UIElement.elements[i] instanceof ConnectorUI) { if (UIElement.elements[i].pointInBound(Game.cursor.x, Game.cursor.y)) { this.x = UIElement.elements[i].x; this.y = UIElement.elements[i].y; return; } } } } } } class CableUI extends UIElement { constructor(x1, y1, x2, y2, color, layer=3) { super(0,0,0,0, layer); this.connectors = [ new CableConnectorUI(x1, y1, this), new CableConnectorUI(x2, y2, this) ]; this.color = color; this.connectors[0].partner = this.connectors[1]; this.connectors[1].partner = this.connectors[0]; this.broken = false; } draw() { Game.c.strokeStyle = "black"; Game.c.lineWidth = 4.5; line(this.connectors[0].x + 7, this.connectors[0].y + 7, this.connectors[1].x + 7, this.connectors[1].y + 7); if (!this.broken) { Game.c.strokeStyle = "red"; Game.c.lineWidth = 3; line(this.connectors[0].x + 7, this.connectors[0].y + 7, this.connectors[1].x + 7, this.connectors[1].y + 7); } } set visible(visible) { this.isVisible = visible; for (let i = 0; i < this.connectors.length; i++) { this.connectors[i].visible = visible; } } get visible() { return this.isVisible; } delete() { super.delete(); this.connectors = null; } } class Button extends UIElement { constructor(x, y, w, h, onclick, layer = 1) { super(x, y, w, h, layer); this.clickable = true; this.onclick = onclick; } click() { this.onclick(); } draw() { Game.c.fillStyle = "white"; Game.c.fillRect(this.x, this.y, this.w, this.h); } } class AnimatedTextButton extends Button { constructor(x, y, color, text, textcolor, textstyle, onclick, layer = 1) { super(x, y, 0, 0, onclick, layer); this.text = new UIText(text, x + 3, y + 1 + Assets.fonts[textstyle].size, textcolor, textstyle, this.layer + 1); this.color = color; this.clicked = false; } draw() { Game.c.font = Assets.fonts[this.text.style].full; this.w = c.measureText(this.text.text).width + 6; this.h = Assets.fonts[this.text.style].size + 6; if (!this.clicked) { Game.c.fillStyle = this.color; Game.c.font = Assets.fonts[this.text.style].full; Game.c.filter = "brightness(0.6)"; Game.c.fillRect(this.x, this.y, this.w, this.h + 3); Game.c.filter = "none"; Game.c.fillRect(this.x, this.y, this.w, this.h); } else { Game.c.fillStyle = this.color; Game.c.font = Assets.fonts[this.text.style].full; Game.c.fillRect(this.x, this.y + 3, this.w, this.h); } } click() { this.clicked = true; this.text.y += 3; Assets.sounds.click.play(); setTimeout(() => { this.onclick(); this.clicked = false; this.text.y -= 3; }, 100); } set visible(visible) { this.isVisible = visible; this.text.visible = visible; } get visible() { return this.isVisible; } delete() { this.text.delete(); super.delete(); } } class CloseButton extends Button { constructor(x, y, onclick, layer = 1) { super(x, y, 9, 9, onclick, layer); } draw() { c.drawImage(Assets.ui.buttons.close, this.x, this.y); } } class Inventory extends UIElement { constructor(x, y, layer = 1) { super(x, y, 18 * Game.player.inventorySlots, 16, layer); this.acceptsItem = true; this.clickable = true; } draw() { this.w = 18 * Game.player.inventorySlots; this.h = 16; Game.c.fillStyle = "white"; Game.c.strokeStyle = "black" Game.c.lineWidth = 1; for (let i = 0; i < Game.player.inventorySlots; i++) { Game.c.globalAlpha = 0.5; Game.c.fillRect(this.x + 18 * i, this.y, 16, 16); Game.c.strokeRect(this.x + 18 * i + 0.5, this.y + 0.5, 15.5, 15.5); Game.c.globalAlpha = 1; if (Game.player.inventory.length > i) { Game.c.drawImage(Assets.items[Game.player.inventory[i]], this.x + 18 * i, this.y); } } } passItem(item) { if (Game.player.inventory.length < Game.player.inventorySlots) { Assets.sounds.player.placeItem.play(); Game.player.inventory.push(item); console.log(Game.gamestate); if (Game.gamestate == "tutorial" && Game.tutorialStep == 17) { tutorialStep(); } return true; } else { return false; } } click() { for (let i = 0; i < Game.player.inventorySlots; i++) { if (Game.cursor.x < this.x + 18 * (i + 1)) { if (Game.player.inventory.length > i) { Game.cursor.item = Game.player.inventory[i]; Game.player.inventory.splice(i, 1); Assets.sounds.player.takeItem.play(); } return; } } } } class ProgressBar extends UIElement { constructor(x, y, w, h, bgcol, fgcol, align = "right", layer = 1) { super(x, y, w, h, layer); this.bgcol = bgcol; this.fgcol = fgcol; this.value = 0; this.align = align; } draw() { Game.c.fillStyle = this.bgcol; Game.c.fillRect(this.x, this.y, this.w, this.h); Game.c.fillStyle = this.fgcol; if (this.align == "right") { Game.c.filter = "brightness(0.6)"; Game.c.fillRect(this.x + 1, this.y + 1, this.w - 2 - Math.floor((this.w - 2) * (1 - this.value)), this.h - 2); Game.c.filter = "none"; Game.c.fillRect(this.x + 1, this.y + 1, this.w - 2 - Math.floor((this.w - 2) * (1 - this.value)), this.h - 3); } if (this.align == "left") { Game.c.filter = "brightness(0.6)"; Game.c.fillRect(this.x + 1 + Math.floor((this.w - 2) * (1- this.value)), this.y + 1, this.w - 2 - Math.floor((this.w - 2) * (1 - this.value)), this.h - 2 ); Game.c.filter = "none"; Game.c.fillRect(this.x + 1 + Math.floor((this.w - 2) * (1- this.value)), this.y + 1, this.w - 2 - Math.floor((this.w - 2) * (1 - this.value)), this.h - 3 ); } } } class EnergyBar extends ProgressBar { constructor(x, y, w, h, layer = 1) { super(x, y, w, h, "black", "yellow", "left", layer); } draw() { this.value = Game.player.energy / Game.player.maxEnergy; super.draw(); } } class ServerLoadBar extends ProgressBar { constructor(x, y, w, h, layer = 1) { super(x, y, w, h, "black", "red", "right", layer); } draw() { this.value = Game.serverLoad / Game.MAX_SERVER_LOAD; super.draw(); } } class PlayerCount extends UIText { constructor(x, y) { super("", x, y, "white", "small", 1); } draw() { this.text = "Players: " + Math.floor(Game.serverLoadGrowth); super.draw(); } } class MoneyLabel extends UIText { constructor(x, y) { super("$", x, y, "lime", "small", 1); this.moneyLabel = new UIText(Game.money, x + 8, y, "white", "small", 1); } draw() { super.draw(); this.moneyLabel.text = Math.floor(Game.money); } set visible(visible) { this.isVisible = visible; this.moneyLabel.visible = visible; } get visible() { return this.isVisible; } } class ShopItem extends UIText { constructor (name, icon, price, x, y, buttonx, buyAction, layer, priceIncrease = 0) { super(price + "$ - " + name, x + 20, y + 13, "white", "small", layer); this.icon = icon; this.price = price; this.priceIncrease = priceIncrease; this.buyButton = new AnimatedTextButton(buttonx, this.y - 13, "lime", "Buy", "black", "small", () => { if (Game.money >= this.price) { Game.money -= this.price; if (this.priceIncrease == 0) { this.buyButton.clickable = false; this.buyButton.color = "gray"; this.text = name; } else { this.price += this.priceIncrease; this.text = this.price + "$ - " + name; } Assets.sounds.buy.play(); buyAction(); } }, layer); } draw() { super.draw(); Game.c.drawImage(this.icon, this.x - 20, this.y - 13); } set visible(visible) { this.isVisible = visible; this.buyButton.visible = visible; } get visible() { return this.isVisible; } } class Shop extends UIElement { constructor() { super(0, 0, 0, 0, 2); this.w = 195; this.h = 5 * 23 + 10; this.x = Math.floor((Game.WIDTH - this.w) / 2); this.y = Math.floor((Game.HEIGHT - this.h) / 2); this.items = []; this.items.push(new ShopItem("Backpack", Assets.items.backpack, 200, this.x + 5, this.y + 5 + 23*1, this.x + 150, () => { Game.player.inventorySlots++; }, this.layer + 1)); this.items.push(new ShopItem("Big Backpack", Assets.items.big_backpack, 750, this.x + 5, this.y + 5 + 23*3, this.x + 150, () => { Game.player.inventorySlots++; }, this.layer + 1)); this.items.push(new ShopItem("Work Shoes", Assets.items.shoes, 150, this.x + 5, this.y + 5 + 23*0, this.x + 150, () => { Game.player.maxEnergy *= 1.75; Game.player.energy = Game.player.maxEnergy; if (Game.gamestate == "tutorial") { tutorialStep(); } }, this.layer + 1)); this.items.push(new ShopItem("Comfy Shoes", Assets.items.comfy_shoes, 600, this.x + 5, this.y + 5 + 23*2, this.x + 150, () => { Game.player.maxEnergy *= 1.75; Game.player.energy = Game.player.maxEnergy; }, this.layer + 1)); this.items.push(new ShopItem("Server", Assets.items.server, 300, this.x + 5, this.y + 5 + 23*4, this.x + 150, () => { this.closeButton.click(); Game.cursor.placingServer = true; }, this.layer + 1, 150)); this.closeButton = new CloseButton(this.x + 180, this.y + 6, () => { this.visible = false; Assets.sounds.click.play(); }, this.layer + 1); } draw() { Game.c.fillStyle = "#4C4C4C"; Game.c.fillRect(this.x, this.y, this.w, this.h); } set visible(visible) { this.isVisible = visible; for (let i = 0; i < this.items.length; i++) { this.items[i].visible = visible; } this.closeButton.visible = visible; } get visible() { return this.isVisible; } } class ServerLoadGauge extends UIElement { constructor(x, y, layer = 1) { super(x, y, 30, 30, layer); } draw() { this.value = (Game.serverLoad - Game.lastServerLoad) / 400; if (this.value > 1) { this.value = 1; } if (this.value < -1) { this.value = -1; } let maxAngle = 3.5 * Math.PI / 4; let angle = -Math.PI / 2 + maxAngle * this.value; Game.c.strokeStyle = "black"; Game.c.lineWidth = 3; let cx = this.x + 14.5; let cy = this.y + 14.5; Game.c.drawImage(Assets.ui.gauge, this.x, this.y); line(cx, cy, cx + Math.cos(angle) * 12, cy + Math.sin(angle) * 12); } } class Dialogue extends UIElement { constructor(text1, text2, onclose) { super(0, Game.HEIGHT - 32, Game.WIDTH, 32, 4); this.onclose = onclose; this.text1 = new UIText(text1, 5, this.y + 13, "white", "small", this.layer + 1); this.text2 = new UIText(text2, 5, this.y + 26, "white", "small", this.layer + 1); this.button = new AnimatedTextButton(Game.WIDTH - 26, this.y + 8, "#2E48DD", "OK", "white", "small", () => { this.delete(); this.onclose(); }, this.layer + 1); Assets.sounds.openDialogue.play(); } draw() { Game.c.fillStyle = "#242936"; Game.c.fillRect(this.x, this.y, this.w, this.h); } delete() { this.text1.delete(); this.text2.delete(); this.button.delete(); super.delete(); } }