This repository has been archived on 2025-04-27. You can view files and clone it, but cannot push or open issues or pull requests.
Servers-Under-Maintenance-G.../ui.js

795 lines
21 KiB
JavaScript

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();
}
}