diff --git a/assets/dat/items.dat b/assets/dat/items.dat index c5377e6..3b52537 100644 Binary files a/assets/dat/items.dat and b/assets/dat/items.dat differ diff --git a/package.json b/package.json index d29df25..513e58a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "migrate": "knex migrate:latest", "seed": "knex seed:run", "lint": "biome lint ./src", - "dev": "rimraf dist && rimraf assets/cache && npm run build && npm start", + "dev": "rimraf dist && rimraf assets/cache && biome lint ./src && swc ./src -d dist && node -r dotenv/config dist/src/app.js", "install": "(node scripts/setup.js) && npm run migrate && npm run seed && npm run finish", "finish": "echo \u001b[46mSetup Completed!\u001b[0m && echo \u001b[46mCheck out GrowServer github: https://github.com/JadlionHD/GrowServer\u001b[0m" }, diff --git a/src/abstracts/Action.ts b/src/abstracts/Action.ts index 2cec1c5..fb90569 100644 --- a/src/abstracts/Action.ts +++ b/src/abstracts/Action.ts @@ -4,12 +4,14 @@ import { BaseServer } from "../structures/BaseServer"; export abstract class Action { public config: ActionConfig; + public base: BaseServer; - constructor() { + constructor(base: BaseServer) { + this.base = base; this.config = { eventName: undefined }; } - public handle(base: BaseServer, peer: Peer, action: ActionType) {} + public handle(peer: Peer, action: ActionType) {} } diff --git a/src/abstracts/Command.ts b/src/abstracts/Command.ts index 693ec39..6d2694a 100644 --- a/src/abstracts/Command.ts +++ b/src/abstracts/Command.ts @@ -4,8 +4,10 @@ import { CommandOptions } from "../types/command"; export abstract class Command { public opt: CommandOptions; + public base: BaseServer; - constructor() { + constructor(base: BaseServer) { + this.base = base; this.opt = { name: "", description: "", @@ -18,5 +20,5 @@ export abstract class Command { }; } - public async execute(base: BaseServer, peer: Peer, text: string, args: string[]): Promise {} + public async execute(peer: Peer, text: string, args: string[]): Promise {} } diff --git a/src/abstracts/Dialog.ts b/src/abstracts/Dialog.ts index 49df9cc..d0187b3 100644 --- a/src/abstracts/Dialog.ts +++ b/src/abstracts/Dialog.ts @@ -4,12 +4,14 @@ import { DialogConfig, DialogReturnType } from "../types/dialog"; export abstract class Dialog { public config: DialogConfig; + public base: BaseServer; - constructor() { + constructor(base: BaseServer) { + this.base = base; this.config = { dialogName: undefined }; } - public handle(base: BaseServer, peer: Peer, action: DialogReturnType) {} + public handle(peer: Peer, action: DialogReturnType) {} } diff --git a/src/abstracts/Listener.ts b/src/abstracts/Listener.ts index fe06326..2e52531 100644 --- a/src/abstracts/Listener.ts +++ b/src/abstracts/Listener.ts @@ -3,9 +3,12 @@ import { BaseServer } from "../structures/BaseServer"; export abstract class Listener { public name: keyof ListenerEventTypes | undefined; - constructor() { + public base: BaseServer; + + constructor(base: BaseServer) { + this.base = base; this.name = undefined; } - public run(base: BaseServer, ...args: ListenerEventTypes[K]): void {} + public run(...args: ListenerEventTypes[K]): void {} } diff --git a/src/actions/DialogReturn.ts b/src/actions/DialogReturn.ts index be9904c..c7dfe7a 100644 --- a/src/actions/DialogReturn.ts +++ b/src/actions/DialogReturn.ts @@ -4,20 +4,20 @@ import { BaseServer } from "../structures/BaseServer"; import { ActionType } from "../types/action"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "dialog_return" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string; dialog_name: string }>): void { + public handle(peer: Peer, action: ActionType<{ action: string; dialog_name: string }>): void { const name = action.dialog_name; try { - if (!base.dialogs.has(name)) return; - base.dialogs.get(name)?.handle(base, peer, action); + if (!this.base.dialogs.has(name)) return; + this.base.dialogs.get(name)?.handle(peer, action); } catch (err) { - base.log.error(err); + this.base.log.error(err); } } } diff --git a/src/actions/Drop.ts b/src/actions/Drop.ts index bc398eb..23b7da5 100644 --- a/src/actions/Drop.ts +++ b/src/actions/Drop.ts @@ -6,16 +6,16 @@ import { DialogBuilder } from "../utils/builders/DialogBuilder"; import { Variant } from "growtopia.js"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "drop" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string; itemID: string }>): void { + public handle(peer: Peer, action: ActionType<{ action: string; itemID: string }>): void { const itemID = parseInt(action.itemID); - const item = base.items.metadata.items.find((v) => v.id === itemID); + const item = this.base.items.metadata.items.find((v) => v.id === itemID); const peerItem = peer.data?.inventory?.items.find((v) => v.id === itemID); const dialog = new DialogBuilder() diff --git a/src/actions/EnterGame.ts b/src/actions/EnterGame.ts index 498a105..082e999 100644 --- a/src/actions/EnterGame.ts +++ b/src/actions/EnterGame.ts @@ -6,27 +6,15 @@ import { DialogBuilder } from "../utils/builders/DialogBuilder"; import { ActionType } from "../types/action"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "enter_game" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string }>): void { - const tes = new DialogBuilder() - .defaultColor() - .addLabelWithIcon("Hello", "1000", "big") - .addSpacer("small") - .addTextBox("Welcome to GrowServer") - .raw("add_image_button||interface/large/news_banner1.rttex|bannerlayout|||\n") - .addQuickExit() - .endDialog("gazzette_end", "Cancel", "Ok") - .str(); - peer.send( - Variant.from("OnRequestWorldSelectMenu"), - Variant.from("OnConsoleMessage", `Welcome ${peer.name} Where would you like to go?`), - Variant.from({ delay: 100 }, "OnDialogRequest", tes) - ); + public handle(peer: Peer, action: ActionType<{ action: string }>): void { + const tes = new DialogBuilder().defaultColor().addLabelWithIcon("Hello", "1000", "big").addSpacer("small").addTextBox("Welcome to GrowServer").raw("add_image_button||interface/large/news_banner1.rttex|bannerlayout|||\n").addQuickExit().endDialog("gazzette_end", "Cancel", "Ok").str(); + peer.send(Variant.from("OnRequestWorldSelectMenu"), Variant.from("OnConsoleMessage", `Welcome ${peer.name} Where would you like to go?`), Variant.from({ delay: 100 }, "OnDialogRequest", tes)); } } diff --git a/src/actions/Input.ts b/src/actions/Input.ts index b9b1d33..7617f51 100644 --- a/src/actions/Input.ts +++ b/src/actions/Input.ts @@ -5,16 +5,16 @@ import { BaseServer } from "../structures/BaseServer"; import { ActionType } from "../types/action"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "input" }; } - public getCommand(base: BaseServer, commandName: string) { + public getCommand(commandName: string) { return ( - base.commands.get(commandName) || { + this.base.commands.get(commandName) || { execute: async () => {}, opt: { name: "", @@ -30,7 +30,7 @@ export default class extends Action { ); } - public async handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string; text: string }>): Promise { + public async handle(peer: Peer, action: ActionType<{ action: string; text: string }>): Promise { const text = action.text.trim(); if (!text || text.replace(/`.|`/g, "").length < 1) return; @@ -38,17 +38,17 @@ export default class extends Action { const args = text.slice("/".length).split(" "); const commandName = args.shift()?.toLowerCase() || ""; - if (!base.commands.has(commandName)) { + if (!this.base.commands.has(commandName)) { return peer.send(Variant.from("OnConsoleMessage", "`4Unknown command.`` Enter `$/help`` for a list of valid commands")); } peer.send(Variant.from("OnConsoleMessage", `\`6/${commandName} ${args.join(" ")}\`\``)); // Cooldown & Ratelimit - const cmd = this.getCommand(base, commandName); + const cmd = this.getCommand(commandName); - const cmdCd = base.cooldown.get(`${commandName}-netID-${peer.data?.netID}`); + const cmdCd = this.base.cooldown.get(`${commandName}-netID-${peer.data?.netID}`); if (!cmdCd) { - const cmdSet = base.cooldown.set(`${commandName}-netID-${peer.data?.netID}`, { + const cmdSet = this.base.cooldown.set(`${commandName}-netID-${peer.data?.netID}`, { limit: 1, time: Date.now() }); @@ -63,19 +63,19 @@ export default class extends Action { setTimeout( () => { - base.cooldown.delete(`${commandName}-netID-${peer.data?.netID}`); + this.base.cooldown.delete(`${commandName}-netID-${peer.data?.netID}`); }, cmd.opt.cooldown || 0 * 1000 ); try { if (cmd.opt.permission.some((perm) => perm === peer.data?.role)) { - await cmd.execute(base, peer, text, args); + await cmd.execute(peer, text, args); } else { peer.send(Variant.from("OnConsoleMessage", "You dont have permission to use this command.")); } } catch (err) { - base.log.error(err); + this.base.log.error(err); } return; } diff --git a/src/actions/JoinRequest.ts b/src/actions/JoinRequest.ts index f4b8d9d..75f29c2 100644 --- a/src/actions/JoinRequest.ts +++ b/src/actions/JoinRequest.ts @@ -5,14 +5,14 @@ import { Peer } from "../structures/Peer"; import { ActionType } from "../types/action"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "join_request" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string; name: string }>): void { + public handle(peer: Peer, action: ActionType<{ action: string; name: string }>): void { const worldName: string = action.name || ""; if (worldName.length <= 0) { peer.send(Variant.from("OnFailedToEnterWorld", 1), Variant.from("OnConsoleMessage", "That world name is uhh `9empty``")); diff --git a/src/actions/Quit.ts b/src/actions/Quit.ts index 547569d..45af7ec 100644 --- a/src/actions/Quit.ts +++ b/src/actions/Quit.ts @@ -4,14 +4,14 @@ import { BaseServer } from "../structures/BaseServer"; import { ActionType } from "../types/action"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "quit" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string }>): void { + public handle(peer: Peer, action: ActionType<{ action: string }>): void { peer.disconnect(); } } diff --git a/src/actions/QuitToExit.ts b/src/actions/QuitToExit.ts index 530cdad..b309230 100644 --- a/src/actions/QuitToExit.ts +++ b/src/actions/QuitToExit.ts @@ -4,14 +4,14 @@ import { BaseServer } from "../structures/BaseServer"; import { ActionType } from "../types/action"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "quit_to_exit" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string }>): void { + public handle(peer: Peer, action: ActionType<{ action: string }>): void { peer.leaveWorld(); } } diff --git a/src/actions/RefreshItemData.ts b/src/actions/RefreshItemData.ts index 925c4c5..4e4d593 100644 --- a/src/actions/RefreshItemData.ts +++ b/src/actions/RefreshItemData.ts @@ -6,17 +6,14 @@ import { TankTypes } from "../utils/enums/TankTypes"; import { ActionType } from "../types/action"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "refresh_item_data" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string }>): void { - peer.send( - Variant.from("OnConsoleMessage", "One moment. Updating item data..."), - TankPacket.from({ type: TankTypes.PEER_ITEMS_DAT, data: () => base.items.content }) - ); + public handle(peer: Peer, action: ActionType<{ action: string }>): void { + peer.send(Variant.from("OnConsoleMessage", "One moment. Updating item data..."), TankPacket.from({ type: TankTypes.PEER_ITEMS_DAT, data: () => this.base.items.content })); } } diff --git a/src/actions/Respawn.ts b/src/actions/Respawn.ts index 1449894..fc8beb0 100644 --- a/src/actions/Respawn.ts +++ b/src/actions/Respawn.ts @@ -4,14 +4,14 @@ import { BaseServer } from "../structures/BaseServer"; import { ActionType } from "../types/action"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "respawn" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string }>): void { + public handle(peer: Peer, action: ActionType<{ action: string }>): void { peer.respawn(); // TODO: respawn back to previous checkpoint } diff --git a/src/actions/RespawnSpike.ts b/src/actions/RespawnSpike.ts index a6c77bd..68377b4 100644 --- a/src/actions/RespawnSpike.ts +++ b/src/actions/RespawnSpike.ts @@ -4,14 +4,14 @@ import { BaseServer } from "../structures/BaseServer"; import { ActionType } from "../types/action"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "respawn_spike" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string }>): void { + public handle(peer: Peer, action: ActionType<{ action: string }>): void { peer.respawn(); // TODO: respawn back to previous checkpoint } diff --git a/src/actions/Trash.ts b/src/actions/Trash.ts index d50a195..42036cb 100644 --- a/src/actions/Trash.ts +++ b/src/actions/Trash.ts @@ -6,16 +6,16 @@ import { DialogBuilder } from "../utils/builders/DialogBuilder"; import { Variant } from "growtopia.js"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "trash" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string; itemID: string }>): void { + public handle(peer: Peer, action: ActionType<{ action: string; itemID: string }>): void { const itemID = parseInt(action.itemID); - const item = base.items.metadata.items.find((v) => v.id === itemID); + const item = this.base.items.metadata.items.find((v) => v.id === itemID); const peerItem = peer.data?.inventory?.items.find((v) => v.id === itemID); const dialog = new DialogBuilder() diff --git a/src/actions/Wrench.ts b/src/actions/Wrench.ts index 55024ec..69f5772 100644 --- a/src/actions/Wrench.ts +++ b/src/actions/Wrench.ts @@ -6,14 +6,14 @@ import { DialogBuilder } from "../utils/builders/DialogBuilder"; import { Variant } from "growtopia.js"; export default class extends Action { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { eventName: "wrench" }; } - public handle(base: BaseServer, peer: Peer, action: ActionType<{ action: string; netID: string }>): void { + public handle(peer: Peer, action: ActionType<{ action: string; netID: string }>): void { const dialog = new DialogBuilder() .defaultColor() .addLabelWithIcon(peer.data?.tankIDName || "", "32", "small") diff --git a/src/app.ts b/src/app.ts index e12d097..138c821 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,20 +2,15 @@ import { BaseServer } from "./structures/BaseServer"; import fs from "node:fs"; import { handleSaveAll } from "./utils/Utils"; -if (!fs.existsSync("./assets")) - throw new Error("Could not find 'assets' folder, please create new one."); +if (!fs.existsSync("./assets")) throw new Error("Could not find 'assets' folder, please create new one."); -if (!fs.existsSync("./assets/ssl")) - throw new Error("SSL certificate are required for https web server."); +if (!fs.existsSync("./assets/ssl")) throw new Error("SSL certificate are required for https web server."); -if (!fs.existsSync("./assets/ssl/server.crt")) - throw new Error("'assets/ssl/server.crt' are required for https web server."); +if (!fs.existsSync("./assets/ssl/server.crt")) throw new Error("'assets/ssl/server.crt' are required for https web server."); -if (!fs.existsSync("./assets/ssl/server.key")) - throw new Error("assets/ssl/server.key are required for https web server."); +if (!fs.existsSync("./assets/ssl/server.key")) throw new Error("assets/ssl/server.key are required for https web server."); -if (!fs.existsSync("./assets/dat/items.dat")) - throw new Error("items.dat not exist on 'assets/dat/items.dat'"); +if (!fs.existsSync("./assets/dat/items.dat")) throw new Error("items.dat not exist on 'assets/dat/items.dat'"); const server = new BaseServer(); diff --git a/src/commands/Find.ts b/src/commands/Find.ts index 49c9331..6682a3b 100644 --- a/src/commands/Find.ts +++ b/src/commands/Find.ts @@ -10,8 +10,8 @@ import { DataTypes } from "../utils/enums/DataTypes"; export default class extends Command { public opt: CommandOptions; - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.opt = { name: "find", description: "Find some items", @@ -24,7 +24,7 @@ export default class extends Command { }; } - public async execute(base: BaseServer, peer: Peer, text: string, args: string[]): Promise { + public async execute(peer: Peer, text: string, args: string[]): Promise { const dialog = new DialogBuilder().defaultColor().addLabelWithIcon("Find the item", "6016", "big").addCheckbox("seed_only", "Only seed", "not_selected").addInputBox("find_item_name", "", "", 30).addQuickExit().endDialog("find_item", "Cancel", "Find").str(); peer.send(Variant.from("OnDialogRequest", dialog)); diff --git a/src/commands/GiveGems.ts b/src/commands/GiveGems.ts index f6e84c1..7c4ea7b 100644 --- a/src/commands/GiveGems.ts +++ b/src/commands/GiveGems.ts @@ -9,8 +9,8 @@ import { find } from "../utils/Utils"; export default class extends Command { public opt: CommandOptions; - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.opt = { name: "givegems", description: "Give gems to someone or self", @@ -23,11 +23,11 @@ export default class extends Command { }; } - public async execute(base: BaseServer, peer: Peer, text: string, args: string[]): Promise { + public async execute(peer: Peer, text: string, args: string[]): Promise { if (!args[0]) return peer.send(Variant.from("OnConsoleMessage", "Gems amount are required.")); if (!/\d/.test(args[0])) return peer.send(Variant.from("OnConsoleMessage", "Gems amount are must be a number.")); if (args.length > 1) { - const targetPeer = find(base, base.cache.users, (user) => (user.data?.tankIDName || "").toLowerCase().includes(args[1].toLowerCase())); + const targetPeer = find(this.base, this.base.cache.users, (user) => (user.data?.tankIDName || "").toLowerCase().includes(args[1].toLowerCase())); if (!targetPeer) return peer.send(Variant.from("OnConsoleMessage", "Make sure that player is online.")); targetPeer.send(Variant.from("OnSetBux", parseInt(args[0]))); diff --git a/src/commands/Help.ts b/src/commands/Help.ts index 0027d8f..25fee53 100644 --- a/src/commands/Help.ts +++ b/src/commands/Help.ts @@ -9,8 +9,8 @@ import { Role } from "../utils/Constants"; export default class extends Command { public opt: CommandOptions; - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.opt = { name: "help", description: "Shows every available commands", @@ -23,10 +23,10 @@ export default class extends Command { }; } - public async execute(base: BaseServer, peer: Peer, text: string, args: string[]): Promise { + public async execute(peer: Peer, text: string, args: string[]): Promise { if (args.length > 0) { - if (!base.commands.has(args[0])) return peer.send(Variant.from("OnConsoleMessage", `It seems that commands doesn't exist.`)); - const cmd = base.commands.get(args[0]); + if (!this.base.commands.has(args[0])) return peer.send(Variant.from("OnConsoleMessage", `It seems that commands doesn't exist.`)); + const cmd = this.base.commands.get(args[0]); const dialog = new DialogBuilder() .defaultColor() @@ -45,7 +45,7 @@ export default class extends Command { const dialog = new DialogBuilder().defaultColor().addLabelWithIcon("Help", "32", "small").addSpacer("small"); - base.commands.forEach((cmd) => { + this.base.commands.forEach((cmd) => { dialog.addLabelWithIcon(cmd.opt.usage, "482", "small"); }); diff --git a/src/dialogs/AreaLockEdit.ts b/src/dialogs/AreaLockEdit.ts index fda61ff..fb35fb7 100644 --- a/src/dialogs/AreaLockEdit.ts +++ b/src/dialogs/AreaLockEdit.ts @@ -1,22 +1,21 @@ import { Dialog } from "../abstracts/Dialog"; import { BaseServer } from "../structures/BaseServer"; import { Peer } from "../structures/Peer"; -import { tileUpdate } from "../tanks/BlockPlacing"; +import { Place } from "../tanks/Place"; import { DialogReturnType } from "../types/dialog"; import { Floodfill } from "../structures/FloodFill"; import { Block, Lock } from "../types/world"; import { World } from "../structures/World"; export default class extends Dialog { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { dialogName: "area_lock_edit" }; } public handle( - base: BaseServer, peer: Peer, action: DialogReturnType<{ action: string; @@ -33,10 +32,10 @@ export default class extends Dialog { }> ): void { const world = peer.hasWorld(peer.data.world); - const pos = parseInt(action.tilex) + parseInt(action.tiley) * (world?.data.width || 100); + const pos = parseInt(action.tilex) + parseInt(action.tiley) * (world?.data.width as number); const block = world?.data.blocks[pos] as Block; - const mLock = base.locks.find((l) => l.id === parseInt(action.lockID)); - const itemMeta = base.items.metadata.items.find((i) => i.id === parseInt(action.lockID)); + const mLock = this.base.locks.find((l) => l.id === parseInt(action.lockID)); + const itemMeta = this.base.items.metadata.items.find((i) => i.id === parseInt(action.lockID)); if (block.lock?.ownerUserID !== peer.data?.id_user) return; const openToPublic = action.allow_break_build === "1" ? true : false; @@ -61,13 +60,13 @@ export default class extends Dialog { height: world?.data.height || 60, blocks: world?.data.blocks as Block[], s_block: block, - base: base, + base: this.base, noEmptyAir: ignoreEmpty }); algo.exec(); algo.apply(world as World, peer); } - tileUpdate(base, peer, itemMeta?.type || 0, block, world as World); + Place.tileUpdate(this.base, peer, itemMeta?.type || 0, block, world as World); } } diff --git a/src/dialogs/DoorEdit.ts b/src/dialogs/DoorEdit.ts index 5576f35..1dc48ed 100644 --- a/src/dialogs/DoorEdit.ts +++ b/src/dialogs/DoorEdit.ts @@ -2,21 +2,20 @@ import { TankPacket, TextPacket, Variant } from "growtopia.js"; import { Dialog } from "../abstracts/Dialog"; import { BaseServer } from "../structures/BaseServer"; import { Peer } from "../structures/Peer"; -import { tileUpdate } from "../tanks/BlockPlacing"; +import { Place } from "../tanks/Place"; import { DialogReturnType } from "../types/dialog"; import { Block } from "../types/world"; import { World } from "../structures/World"; export default class extends Dialog { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { dialogName: "door_edit" }; } public handle( - base: BaseServer, peer: Peer, action: DialogReturnType<{ action: string; @@ -30,9 +29,9 @@ export default class extends Dialog { }> ): void { const world = peer.hasWorld(peer.data.world); - const pos = parseInt(action.tilex) + parseInt(action.tiley) * (world?.data.width || 100); + const pos = parseInt(action.tilex) + parseInt(action.tiley) * (world?.data.width as number); const block = world?.data.blocks[pos] as Block; - const itemMeta = base.items.metadata.items.find((i) => i.id === parseInt(action.itemID)); + const itemMeta = this.base.items.metadata.items.find((i) => i.id === parseInt(action.itemID)); if (world?.data.owner) { if (world?.data.owner.id !== peer.data?.id_user) return; @@ -44,6 +43,6 @@ export default class extends Dialog { id: action.id?.toUpperCase() || "" }; - tileUpdate(base, peer, itemMeta?.type || 0, block, world as World); + Place.tileUpdate(this.base, peer, itemMeta?.type || 0, block, world as World); } } diff --git a/src/dialogs/DropEnd.ts b/src/dialogs/DropEnd.ts index 2bdf088..0cf0b16 100644 --- a/src/dialogs/DropEnd.ts +++ b/src/dialogs/DropEnd.ts @@ -8,15 +8,14 @@ import { DataTypes } from "../utils/enums/DataTypes"; import { TankTypes } from "../utils/enums/TankTypes"; export default class extends Dialog { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { dialogName: "drop_end" }; } public handle( - base: BaseServer, peer: Peer, action: DialogReturnType<{ action: string; diff --git a/src/dialogs/FindItem.ts b/src/dialogs/FindItem.ts index 4e25d14..b940714 100644 --- a/src/dialogs/FindItem.ts +++ b/src/dialogs/FindItem.ts @@ -6,15 +6,14 @@ import { DialogReturnType } from "../types/dialog"; import { DialogBuilder } from "../utils/builders/DialogBuilder"; export default class extends Dialog { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { dialogName: "find_item" }; } public handle( - base: BaseServer, peer: Peer, action: DialogReturnType<{ action: string; @@ -26,7 +25,7 @@ export default class extends Dialog { const isSeed = parseInt(action.seed_only) ? true : false; const dialog = new DialogBuilder().defaultColor().addQuickExit().addLabelWithIcon("Find the item", "6016", "big").addSpacer("small"); - const items = base.items.metadata.items.filter((v) => v.name?.toLowerCase().includes(action.find_item_name.toLowerCase())); + const items = this.base.items.metadata.items.filter((v) => v.name?.toLowerCase().includes(action.find_item_name.toLowerCase())); items.forEach((item) => { const itemID = item.id || 0; const itemName = item.name || ""; diff --git a/src/dialogs/FindItemEnd.ts b/src/dialogs/FindItemEnd.ts index cb57f27..d9b0a93 100644 --- a/src/dialogs/FindItemEnd.ts +++ b/src/dialogs/FindItemEnd.ts @@ -6,15 +6,14 @@ import { DialogReturnType } from "../types/dialog"; import { DialogBuilder } from "../utils/builders/DialogBuilder"; export default class extends Dialog { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { dialogName: "find_item_end" }; } public handle( - base: BaseServer, peer: Peer, action: DialogReturnType<{ action: string; @@ -25,7 +24,7 @@ export default class extends Dialog { ): void { const itemID = parseInt(action.buttonClicked); peer.data?.inventory?.items.push({ id: itemID, amount: 200 }); - peer.send(Variant.from("OnConsoleMessage", `Added \`6${base.items.metadata.items.find((v) => v.id === itemID)?.name}\`\` to your inventory.`)); + peer.send(Variant.from("OnConsoleMessage", `Added \`6${this.base.items.metadata.items.find((v) => v.id === itemID)?.name}\`\` to your inventory.`)); peer.inventory(); peer.saveToCache(); // peer.saveToDatabase(); diff --git a/src/dialogs/RegisterEnd.ts b/src/dialogs/RegisterEnd.ts index c19223b..4d4ddd5 100644 --- a/src/dialogs/RegisterEnd.ts +++ b/src/dialogs/RegisterEnd.ts @@ -6,15 +6,14 @@ import { DialogReturnType } from "../types/dialog"; import { DialogBuilder } from "../utils/builders/DialogBuilder"; export default class extends Dialog { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { dialogName: "register_end" }; } public async handle( - base: BaseServer, peer: Peer, action: DialogReturnType<{ action: string; @@ -54,7 +53,7 @@ export default class extends Dialog { return peer.send(Variant.from("OnDialogRequest", dialog.str())); } - const ifUserExist = await base.database.getUser(username); + const ifUserExist = await this.base.database.getUser(username); const userExist = ifUserExist?.name.toLowerCase(); if (username === userExist) { @@ -62,7 +61,7 @@ export default class extends Dialog { return peer.send(Variant.from("OnDialogRequest", dialog.str())); } - const result = await base.database.createUser(action.username, action.password); + const result = await this.base.database.createUser(action.username, action.password); if (result) { peer.send(Variant.from("OnConsoleMessage", "`2SUCCESS:`` Account has been created")); return peer.disconnect(); diff --git a/src/dialogs/SignEdit.ts b/src/dialogs/SignEdit.ts index 8a6e743..8404d9a 100644 --- a/src/dialogs/SignEdit.ts +++ b/src/dialogs/SignEdit.ts @@ -2,21 +2,20 @@ import { TankPacket, TextPacket, Variant } from "growtopia.js"; import { Dialog } from "../abstracts/Dialog"; import { BaseServer } from "../structures/BaseServer"; import { Peer } from "../structures/Peer"; -import { tileUpdate } from "../tanks/BlockPlacing"; +import { Place } from "../tanks/Place"; import { DialogReturnType } from "../types/dialog"; import { Block } from "../types/world"; import { World } from "../structures/World"; export default class extends Dialog { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { dialogName: "sign_edit" }; } public handle( - base: BaseServer, peer: Peer, action: DialogReturnType<{ action: string; @@ -28,9 +27,9 @@ export default class extends Dialog { }> ): void { const world = peer.hasWorld(peer.data.world); - const pos = parseInt(action.tilex) + parseInt(action.tiley) * (world?.data.width || 100); + const pos = parseInt(action.tilex) + parseInt(action.tiley) * (world?.data.width as number); const block = world?.data.blocks[pos] as Block; - const itemMeta = base.items.metadata.items.find((i) => i.id === parseInt(action.itemID)); + const itemMeta = this.base.items.metadata.items.find((i) => i.id === parseInt(action.itemID)); if (world?.data.owner) { if (world.data.owner.id !== peer.data?.id_user) return; @@ -40,6 +39,6 @@ export default class extends Dialog { label: action.label || "" }; - tileUpdate(base, peer, itemMeta?.type || 0, block, world as World); + Place.tileUpdate(this.base, peer, itemMeta?.type || 0, block, world as World); } } diff --git a/src/dialogs/TrashEnd.ts b/src/dialogs/TrashEnd.ts index 2caf115..6d13ee0 100644 --- a/src/dialogs/TrashEnd.ts +++ b/src/dialogs/TrashEnd.ts @@ -8,15 +8,14 @@ import { DataTypes } from "../utils/enums/DataTypes"; import { TankTypes } from "../utils/enums/TankTypes"; export default class extends Dialog { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.config = { dialogName: "trash_end" }; } public handle( - base: BaseServer, peer: Peer, action: DialogReturnType<{ action: string; diff --git a/src/events/Connect.ts b/src/events/Connect.ts index ff81ce3..66594fb 100644 --- a/src/events/Connect.ts +++ b/src/events/Connect.ts @@ -4,18 +4,18 @@ import { BaseServer } from "../structures/BaseServer"; import { Peer } from "../structures/Peer"; export default class extends Listener<"connect"> { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.name = "connect"; } - public run(base: BaseServer, netID: number): void { - base.log.debug("Peer", netID, "connected."); + public run(netID: number): void { + this.base.log.debug("Peer", netID, "connected."); - const peer = new Peer(base, netID); + const peer = new Peer(this.base, netID); const packet = TextPacket.from(0x1); peer.send(packet); - base.cache.users.setSelf(netID, peer.data); + this.base.cache.users.setSelf(netID, peer.data); } } diff --git a/src/events/Disconnect.ts b/src/events/Disconnect.ts index 18cd36c..45b9963 100644 --- a/src/events/Disconnect.ts +++ b/src/events/Disconnect.ts @@ -2,17 +2,17 @@ import { Listener } from "../abstracts/Listener"; import { BaseServer } from "../structures/BaseServer"; export default class extends Listener<"disconnect"> { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.name = "disconnect"; } - public run(base: BaseServer, netID: number): void { - base.log.debug("Peer", netID, "disconnected"); - const peer = base.cache.users.getSelf(netID); + public run(netID: number): void { + this.base.log.debug("Peer", netID, "disconnected"); + const peer = this.base.cache.users.getSelf(netID); peer?.leaveWorld(); peer?.saveToDatabase(); - base.cache.users.delete(netID); + this.base.cache.users.delete(netID); } } diff --git a/src/events/Raw.ts b/src/events/Raw.ts index b116988..0d657db 100644 --- a/src/events/Raw.ts +++ b/src/events/Raw.ts @@ -7,17 +7,17 @@ import { decrypt, find, parseAction } from "../utils/Utils"; import { Peer } from "../structures/Peer"; import { TankTypes } from "../utils/enums/TankTypes"; import { ActionTypes } from "../utils/enums/Tiles"; -import { handlePlace } from "../tanks/Place"; -import { handlePunch } from "../tanks/Punch"; import { ClothTypes } from "../utils/enums/ItemTypes"; -import { handleWrench } from "../tanks/BlockWrench"; import { DialogBuilder } from "../utils/builders/DialogBuilder"; import { World } from "../structures/World"; import { DroppedItem } from "../types/world"; +import { Place } from "../tanks/Place"; +import { Punch } from "../tanks/Punch"; +import { Wrench } from "../tanks/Wrench"; export default class extends Listener<"raw"> { - constructor() { - super(); + constructor(base: BaseServer) { + super(base); this.name = "raw"; } @@ -27,9 +27,8 @@ export default class extends Listener<"raw"> { peer.send(Variant.from("OnDialogRequest", dialog)); } - public async run(base: BaseServer, netID: number, data: Buffer): Promise { - // prettier-ignore - const peer = base.cache.users.has(netID) ? base.cache.users.getSelf(netID) : new Peer(base, netID); + public async run(netID: number, data: Buffer): Promise { + const peer = this.base.cache.users.has(netID) ? this.base.cache.users.getSelf(netID) : new Peer(this.base, netID); const dataType = data.readInt32LE(); switch (dataType) { @@ -37,7 +36,7 @@ export default class extends Listener<"raw"> { case DataTypes.ACTION: { const parsed = parseAction(data); - base.log.debug({ parsed, dataType }); + this.base.log.debug({ parsed, dataType }); // Guest if (parsed?.requestedName && !parsed?.tankIDName && !parsed?.tankIDPass) return this.sendGuest(peer, (parsed?.requestedName as string) || ""); @@ -46,7 +45,7 @@ export default class extends Listener<"raw"> { if (parsed?.requestedName && parsed?.tankIDName && parsed?.tankIDPass) { const username = parsed.tankIDName as string; const password = parsed.tankIDPass as string; - base.database.getUser(username).then((user) => { + this.base.database.getUser(username).then((user) => { if (!user || password !== decrypt(user?.password)) { peer.send(Variant.from("OnConsoleMessage", "`4Failed`` logging in to that account. Please make sure you've provided the correct info.")); peer.send(TextPacket.from(DataTypes.ACTION, "action|set_url", "url||https://127.0.0.1/recover", "label|`$Recover your Password``")); @@ -54,7 +53,7 @@ export default class extends Listener<"raw"> { } // Check if there's same account is logged in - const targetPeer = find(base, base.cache.users, (v) => v.data?.id_user === user.id_user); + const targetPeer = find(this.base, this.base.cache.users, (v) => v.data?.id_user === user.id_user); if (targetPeer) { peer.send(Variant.from("OnConsoleMessage", "`4Already Logged In?`` It seems that this account already logged in by somebody else.")); @@ -64,7 +63,7 @@ export default class extends Listener<"raw"> { peer.send( Variant.from( "OnSuperMainStartAcceptLogonHrdxs47254722215a", - base.items.hash, + this.base.items.hash, "www.growtopia1.com", "growtopia/cache/", "cc.cz.madkite.freedom org.aqua.gg idv.aqua.bulldog com.cih.gamecih2 com.cih.gamecih com.cih.game_cih cn.maocai.gamekiller com.gmd.speedtime org.dax.attack com.x0.strai.frep com.x0.strai.free org.cheatengine.cegui org.sbtools.gamehack com.skgames.traffikrider org.sbtoods.gamehaca com.skype.ralder org.cheatengine.cegui.xx.multi1458919170111 com.prohiro.macro me.autotouch.autotouch com.cygery.repetitouch.free com.cygery.repetitouch.pro com.proziro.zacro com.slash.gamebuster", @@ -123,10 +122,10 @@ export default class extends Listener<"raw"> { // Handle actions if (parsed?.action) { try { - const action = base.action.get(parsed.action as string); - action?.handle(base, peer.getSelfCache(), parsed as ActionType); + const action = this.base.action.get(parsed.action as string); + action?.handle(peer.getSelfCache(), parsed as ActionType); } catch (err) { - base.log.error(err); + this.base.log.error(err); } } break; @@ -140,7 +139,7 @@ export default class extends Listener<"raw"> { const tank = TankPacket.fromBuffer(data); switch (tank.data?.type) { default: { - base.log.debug("Unknown tank", tank); + this.base.log.debug("Unknown tank", tank); break; } case TankTypes.PEER_ICON: { @@ -156,7 +155,7 @@ export default class extends Listener<"raw"> { case TankTypes.PEER_CLOTH: { tank.data.state = peer.data?.rotatedLeft ? 16 : 0; - const item = base.items.metadata.items.find((v) => v.id === tank.data?.info); + const item = this.base.items.metadata.items.find((v) => v.id === tank.data?.info); const isAnces = (): boolean => { if (item?.type === ActionTypes.ANCES) { @@ -210,7 +209,7 @@ export default class extends Listener<"raw"> { } case ClothTypes.HAND: { if (isAnces()) break; - const handItem = base.items.metadata.items.find((item) => item.id === tank.data?.info); + const handItem = this.base.items.metadata.items.find((item) => item.id === tank.data?.info); if (peer.data.clothing.hand === tank.data.info) peer.data.clothing.hand = 0; else peer.data.clothing.hand = tank.data.info || 0; @@ -263,7 +262,7 @@ export default class extends Listener<"raw"> { peer.data.x = tank.data.xPos; peer.data.y = tank.data.yPos; - peer.data.rotatedLeft = Boolean(tank.data.state || 0x0 & 0x10); + peer.data.rotatedLeft = Boolean((tank.data.state as number) & 0x10); peer.saveToCache(); peer.everyPeer((p) => { @@ -279,13 +278,16 @@ export default class extends Listener<"raw"> { // Fist if (tank.data.info === 18) { - handlePunch(tank, peer, base, world); + const punch = new Punch(this.base, peer, tank, world); + punch.onPunch(); } else if (tank.data.info === 32) { - handleWrench(base, tank, peer, world); + const wrench = new Wrench(this.base, peer, tank, world); + wrench.onWrench(); } // Others else { - handlePlace(tank, peer, base, world); + const place = new Place(this.base, peer, tank, world); + place.onPlace(); } break; diff --git a/src/structures/BaseServer.ts b/src/structures/BaseServer.ts index a6c0883..5ffc452 100644 --- a/src/structures/BaseServer.ts +++ b/src/structures/BaseServer.ts @@ -87,8 +87,8 @@ export class BaseServer { #_loadEvents() { fs.readdirSync(`${__dirname}/../events`).forEach(async (event) => { const file = (await import(`../events/${event}`)).default; - const initFile = new file(); - this.server.on(initFile.name, (...args) => initFile.run(this, ...args)); + const initFile = new file(this); + this.server.on(initFile.name, (...args) => initFile.run(...args)); this.log.event(`Loaded "${initFile.name}" events`); }); } @@ -102,7 +102,7 @@ export class BaseServer { #_loadActions() { fs.readdirSync(`${__dirname}/../actions`).forEach(async (event) => { const file = (await import(`../actions/${event}`)).default; - const initFile = new file(); + const initFile = new file(this); this.action.set(initFile.config.eventName, initFile); this.log.action(`Loaded "${initFile.config.eventName}" actions`); }); @@ -111,7 +111,7 @@ export class BaseServer { #_loadDialogs() { fs.readdirSync(`${__dirname}/../dialogs`).forEach(async (event) => { const file = (await import(`../dialogs/${event}`)).default; - const initFile = new file(); + const initFile = new file(this); this.dialogs.set(initFile.config.dialogName, initFile); this.log.dialog(`Loaded "${initFile.config.dialogName}" dialogs`); }); @@ -120,7 +120,7 @@ export class BaseServer { #_loadCommands() { fs.readdirSync(`${__dirname}/../commands`).forEach(async (fileName) => { const file = (await import(`../commands/${fileName}`)).default; - const initFile = new file(); + const initFile = new file(this); this.commands.set(initFile.opt.name, initFile); this.log.command(`Loaded "${initFile.opt.name}" command`); }); diff --git a/src/structures/Peer.ts b/src/structures/Peer.ts index e507a83..885d0d8 100644 --- a/src/structures/Peer.ts +++ b/src/structures/Peer.ts @@ -136,8 +136,8 @@ export class Peer extends OldPeer { const extra = Math.random() * 6; - const x = this.data.x || 0 + (this.data.rotatedLeft ? -25 : +25) + extra; - const y = this.data.y || 0 + extra - Math.floor(Math.random() * (3 - -1) + -3); + const x = (this.data.x as number) + (this.data.rotatedLeft ? -25 : +25) + extra; + const y = (this.data.y as number) + extra - Math.floor(Math.random() * (3 - -1) + -3); world?.drop(this, x, y, id, amount); } diff --git a/src/structures/Tile.ts b/src/structures/Tile.ts new file mode 100644 index 0000000..eeff307 --- /dev/null +++ b/src/structures/Tile.ts @@ -0,0 +1,147 @@ +import { Action } from "../abstracts/Action"; +import { ActionTypes, Options, Flags, ExtraTypes } from "../utils/enums/Tiles"; +import { Block } from "../types/world"; +import { World } from "./World"; +import { BaseServer } from "./BaseServer"; +import { find } from "../utils/Utils"; + +export class Tile { + public base: BaseServer; + public world: World; + public block: Block; + + constructor(base: BaseServer, world: World, block: Block) { + this.base = base; + this.world = world; + this.block = block; + } + + public serialize(actionType: number) { + let buf: Buffer; + const lockPos = this.block.lock && !this.block.lock.isOwner ? (this.block.lock.ownerX as number) + (this.block.lock.ownerY as number) * this.world.data.width : 0; + + switch (actionType) { + case ActionTypes.PORTAL: + case ActionTypes.DOOR: + case ActionTypes.MAIN_DOOR: { + const label = this.block.door?.label || ""; + buf = Buffer.alloc(12 + label.length); + + buf.writeUInt32LE(this.block.fg | (this.block.bg << 16)); + buf.writeUint16LE(lockPos, 4); + buf.writeUint16LE(Flags.FLAGS_TILEEXTRA, 6); + + buf.writeUint8(ExtraTypes.DOOR, 8); + buf.writeUint16LE(label.length, 9); + buf.write(label, 11); + // first param locked/not (0x8/0x0) + buf.writeUint8(0x0, 11 + label.length); + + return buf; + } + + case ActionTypes.SIGN: { + let flag = 0x0; + const label = this.block.sign?.label || ""; + buf = Buffer.alloc(15 + label.length); + + if (this.block.rotatedLeft) flag |= Flags.FLAGS_ROTATED_LEFT; + + buf.writeUInt32LE(this.block.fg | (this.block.bg << 16)); + buf.writeUint16LE(lockPos, 4); + buf.writeUint16LE(Flags.FLAGS_TILEEXTRA, 6); + + buf.writeUint8(ExtraTypes.SIGN, 8); + buf.writeUint16LE(label.length, 9); + buf.write(label, 11); + buf.writeInt32LE(-1, 11 + label.length); + + return buf; + } + + case ActionTypes.HEART_MONITOR: { + // ET ID ID ID ID NL NL + + const name = this.block.heartMonitor?.name || ""; + const id = this.block.heartMonitor?.user_id || 0; + let flag = 0x0; + + // Check if the peer offline/online + const targetPeer = this.base.cache.users.findPeer((p) => p.data?.id_user === id); + + if (targetPeer) flag |= Flags.FLAGS_OPEN; + if (this.block.rotatedLeft) flag |= Flags.FLAGS_ROTATED_LEFT; + + buf = Buffer.alloc(15 + name.length); + + buf.writeUInt32LE(this.block.fg | (this.block.bg << 16)); + buf.writeUint16LE(lockPos, 4); + buf.writeUint16LE(flag, 6); + + buf.writeUint8(ExtraTypes.HEART_MONITOR, 8); + buf.writeUint32LE(id, 9); + buf.writeUint16LE(name.length, 13); + buf.write(name, 15); + + return buf; + } + + case ActionTypes.DISPLAY_BLOCK: { + buf = Buffer.alloc(13); + + buf.writeUInt32LE(this.block.fg | (this.block.bg << 16)); + buf.writeUint16LE(0x0, 4); + buf.writeUint16LE(Flags.FLAGS_TILEEXTRA, 6); + + buf.writeUint8(ExtraTypes.DISPLAY_BLOCK, 8); + buf.writeUint32LE(this.block.dblockID || 0, 9); + + return buf; + } + + case ActionTypes.LOCK: { + const owner = (this.block.lock ? this.block.lock.ownerUserID : this.world.data.owner?.id) as number; + + // 0 = admincount + buf = Buffer.alloc(26 + 4 * 0); + + buf.writeUInt32LE(this.block.fg | (this.block.bg << 16)); + buf.writeUint16LE(lockPos, 4); + buf.writeUint16LE(Flags.FLAGS_TILEEXTRA, 6); + + buf.writeUInt16LE(ExtraTypes.LOCK | (0x0 << 8), 8); + buf.writeUInt32LE(owner, 10); + buf.writeUInt32LE(0, 14); // admin count + buf.writeInt32LE(-1, 18); + + return buf; + } + + case ActionTypes.SEED: { + const flag = 0x0; + buf = Buffer.alloc(14); + + buf.writeUInt32LE(this.block.fg | (this.block.bg << 16)); + buf.writeUint16LE(lockPos, 4); + buf.writeUint16LE(flag, 6); + buf.writeUint8(ExtraTypes.SEED, 8); + + buf.writeUInt32LE(Math.floor((Date.now() - (this.block.tree?.plantedAt as number)) / 1000), 9); + buf.writeUInt8((this.block.tree?.fruitCount as number) > 4 ? 4 : (this.block.tree?.fruitCount as number), 13); + + return buf; + } + + default: { + const flag = 0x0; + buf = Buffer.alloc(8); + + buf.writeUInt32LE(this.block.fg | (this.block.bg << 16)); + buf.writeUint16LE(lockPos, 4); + buf.writeUint16LE(flag, 6); + + return buf; + } + } + } +} diff --git a/src/structures/TileExtra.ts b/src/structures/TileExtra.ts deleted file mode 100644 index 0a9e2cf..0000000 --- a/src/structures/TileExtra.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Action } from "../abstracts/Action"; -import { ActionTypes, Options, Flags, ExtraTypes } from "../utils/enums/Tiles"; -import { Block } from "../types/world"; -import { World } from "./World"; -import { BaseServer } from "./BaseServer"; -import { find } from "../utils/Utils"; - -export function HandleTile(base: BaseServer, block: Block, world: World, actionType?: number): Buffer { - let buf: Buffer; - const lockPos = block.lock && !block.lock.isOwner ? (block.lock.ownerX as number) + (block.lock.ownerY as number) * world.data.width : 0; - - switch (actionType) { - case ActionTypes.PORTAL: - case ActionTypes.DOOR: - case ActionTypes.MAIN_DOOR: { - const label = block.door?.label || ""; - buf = Buffer.alloc(12 + label.length); - - buf.writeUInt32LE(block.fg | (block.bg << 16)); - buf.writeUint16LE(lockPos, 4); - buf.writeUint16LE(Flags.FLAGS_TILEEXTRA, 6); - - buf.writeUint8(ExtraTypes.DOOR, 8); - buf.writeUint16LE(label.length, 9); - buf.write(label, 11); - // first param locked/not (0x8/0x0) - buf.writeUint8(0x0, 11 + label.length); - - return buf; - } - - case ActionTypes.SIGN: { - let flag = 0x0; - const label = block.sign?.label || ""; - buf = Buffer.alloc(15 + label.length); - - if (block.rotatedLeft) flag |= Flags.FLAGS_ROTATED_LEFT; - - buf.writeUInt32LE(block.fg | (block.bg << 16)); - buf.writeUint16LE(lockPos, 4); - buf.writeUint16LE(Flags.FLAGS_TILEEXTRA, 6); - - buf.writeUint8(ExtraTypes.SIGN, 8); - buf.writeUint16LE(label.length, 9); - buf.write(label, 11); - buf.writeInt32LE(-1, 11 + label.length); - - return buf; - } - - case ActionTypes.HEART_MONITOR: { - // ET ID ID ID ID NL NL - - const name = block.heartMonitor?.name || ""; - const id = block.heartMonitor?.user_id || 0; - let flag = 0x0; - - // Check if the peer offline/online - const targetPeer = base.cache.users.findPeer((p) => p.data?.id_user === id); - - if (targetPeer) flag |= Flags.FLAGS_OPEN; - if (block.rotatedLeft) flag |= Flags.FLAGS_ROTATED_LEFT; - - buf = Buffer.alloc(15 + name.length); - - buf.writeUInt32LE(block.fg | (block.bg << 16)); - buf.writeUint16LE(lockPos, 4); - buf.writeUint16LE(flag, 6); - - buf.writeUint8(ExtraTypes.HEART_MONITOR, 8); - buf.writeUint32LE(id, 9); - buf.writeUint16LE(name.length, 13); - buf.write(name, 15); - - return buf; - } - - case ActionTypes.DISPLAY_BLOCK: { - buf = Buffer.alloc(13); - - buf.writeUInt32LE(block.fg | (block.bg << 16)); - buf.writeUint16LE(0x0, 4); - buf.writeUint16LE(Flags.FLAGS_TILEEXTRA, 6); - - buf.writeUint8(ExtraTypes.DISPLAY_BLOCK, 8); - buf.writeUint32LE(block.dblockID || 0, 9); - - return buf; - } - - case ActionTypes.LOCK: { - const owner = (block.lock ? block.lock.ownerUserID : world.data.owner?.id) as number; - - // 0 = admincount - buf = Buffer.alloc(26 + 4 * 0); - - buf.writeUInt32LE(block.fg | (block.bg << 16)); - buf.writeUint16LE(lockPos, 4); - buf.writeUint16LE(Flags.FLAGS_TILEEXTRA, 6); - - buf.writeUInt16LE(ExtraTypes.LOCK | (0x0 << 8), 8); - buf.writeUInt32LE(owner, 10); - buf.writeUInt32LE(0, 14); // admin count - buf.writeInt32LE(-1, 18); - - return buf; - } - - case ActionTypes.SEED: { - const flag = 0x0; - buf = Buffer.alloc(14); - - buf.writeUInt32LE(block.fg | (block.bg << 16)); - buf.writeUint16LE(lockPos, 4); - buf.writeUint16LE(flag, 6); - buf.writeUint8(ExtraTypes.SEED, 8); - - buf.writeUInt32LE(Math.floor((Date.now() - (block.tree?.plantedAt as number)) / 1000), 9); - buf.writeUInt8((block.tree?.fruitCount as number) > 4 ? 4 : (block.tree?.fruitCount as number), 13); - - return buf; - } - - default: { - const flag = 0x0; - buf = Buffer.alloc(8); - - buf.writeUInt32LE(block.fg | (block.bg << 16)); - buf.writeUint16LE(lockPos, 4); - buf.writeUint16LE(flag, 6); - - return buf; - } - } -} diff --git a/src/structures/Webserver.ts b/src/structures/Webserver.ts index 08a3f7e..5373c5a 100644 --- a/src/structures/Webserver.ts +++ b/src/structures/Webserver.ts @@ -32,29 +32,23 @@ export async function WebServer(log: Logger, db: Database) { app.use(bodyparser.urlencoded({ extended: true })); app.use(bodyparser.json()); - app.set("view engine", "ejs"); - app.set("views", path.join(__dirname, "../../web/views")); - - if (existsSync("./assets/cache/cache")) app.use("/growtopia/cache", express.static(path.join(__dirname, "../../assets/cache/cache"))); - else app.use("/growtopia/cache", express.static(path.join(__dirname, "../../assets/cache"))); + if (existsSync("./assets/cache/cache")) { + app.use("/growtopia/cache", express.static(path.join(__dirname, "../../../assets/cache/cache"))); + } else { + console.log(path.join(__dirname, "../../../assets/cache")); + app.use("/growtopia/cache", express.static(path.join(__dirname, "../../../assets/cache"))); + } app.use("/growtopia/server_data.php", (req, res) => { res.send(`server|${process.env.WEB_ADDRESS}\nport|17091\ntype|1\n#maint|Maintenance woi\nmeta|lolwhat\nRTENDMARKERBS1001`); }); - // app.get("/register", (req, res) => { - // res.render("register.ejs"); - // }); - - // app.post("/api/register", apiLimiter, async (req, res) => { - // if (req.body && req.body.username && req.body.password) { - // let result = await db.createUser(req.body.username, req.body.password); - - // if (result) res.send("OK, Successfully creating account"); - // else res.send("Error"); - // } - // }); + app.use((req, res, next) => { + log.warn(`Growtopia Client requesting cache: ${req.originalUrl} not found. Redirecting to Growtopia Original CDN...`); + res.redirect(`https://ubistatic-a.akamaihd.net/0098/0214956/${req.originalUrl.replace("/growtopia/", "")}`); + next(); + }); if (process.env.WEB_ENV === "production") { app.listen(3000, () => { @@ -70,7 +64,6 @@ export async function WebServer(log: Logger, db: Database) { httpsServer.on("listening", () => { log.ready(`Starting web server on: http://${process.env.WEB_ADDRESS}:80`); - log.info(`To register account you need to register at: http://${process.env.WEB_ADDRESS}:80/register`); }); } } diff --git a/src/structures/World.ts b/src/structures/World.ts index 3792c2e..d13ea91 100644 --- a/src/structures/World.ts +++ b/src/structures/World.ts @@ -5,9 +5,9 @@ import { Block, EnterArg, Place, WorldData } from "../types/world"; import { BaseServer } from "./BaseServer"; import { WORLD_SIZE, Y_END_DIRT, Y_LAVA_START, Y_START_DIRT } from "../utils/Constants"; import { TankTypes } from "../utils/enums/TankTypes"; -import { HandleTile } from "./TileExtra"; import { Peer } from "./Peer"; import { DataTypes } from "../utils/enums/DataTypes"; +import { Tile } from "./Tile"; export class World { public data: WorldData = { @@ -174,7 +174,7 @@ export class World { this.data.blocks?.forEach((block) => { const item = this.base.items.metadata.items.find((i) => i.id === block.fg); - const blockBuf = HandleTile(this.base, block, this, item?.type); + const blockBuf = new Tile(this.base, this, block).serialize(item?.type as number); blockBuf.forEach((b) => blockBytes.push(b)); }); diff --git a/src/tanks/BlockPlacing.ts b/src/tanks/BlockPlacing.ts deleted file mode 100644 index 8b74f96..0000000 --- a/src/tanks/BlockPlacing.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { TankPacket, Variant } from "growtopia.js"; -import { BaseServer } from "../structures/BaseServer"; -import { Peer } from "../structures/Peer"; -import { HandleTile } from "../structures/TileExtra"; -import { World } from "../structures/World"; -import { Block } from "../types/world"; -import { TankTypes } from "../utils/enums/TankTypes"; -import { ActionTypes } from "../utils/enums/Tiles"; -import { Floodfill } from "../structures/FloodFill"; -import { BlockFlags } from "../utils/enums/ItemTypes"; - -interface Arg { - actionType: number; - peer: Peer; - world: World; - block: Block; - id: number; - isBg?: boolean; - base: BaseServer; - flags: number; -} - -export function handleBlockPlacing(p: Arg): boolean { - if (p.block.fg === 2946 && p.actionType !== ActionTypes.DISPLAY_BLOCK) return false; - - // prevent replace a block to others - if (p.block.fg && p.flags & BlockFlags.WRENCHABLE) return false; - if (p.block.fg && !p.block.bg) return false; - if (p.block.fg && p.actionType === ActionTypes.PLATFORM) return false; - - switch (p.actionType) { - case ActionTypes.SHEET_MUSIC: - case ActionTypes.BEDROCK: - case ActionTypes.LAVA: - case ActionTypes.PLATFORM: - case ActionTypes.FOREGROUND: - case ActionTypes.BACKGROUND: { - p.world.place({ - peer: p.peer, - x: p.block.x, - y: p.block.y, - isBg: p.isBg, - id: p.id - }); - - tileVisualUpdate(p.peer, p.block, 0x0, true); - return true; - } - - case ActionTypes.PORTAL: - case ActionTypes.DOOR: - case ActionTypes.MAIN_DOOR: { - p.block.door = { label: "", destination: "", id: "", locked: false }; - - p.world.place({ - peer: p.peer, - x: p.block.x, - y: p.block.y, - isBg: p.isBg, - id: p.id - }); - - return true; - } - - case ActionTypes.SIGN: { - p.block.sign = { label: "" }; - - p.world.place({ - peer: p.peer, - x: p.block.x, - y: p.block.y, - isBg: p.isBg, - id: p.id - }); - - tileUpdate(p.base, p.peer, p.actionType, p.block, p.world); - return true; - } - - case ActionTypes.HEART_MONITOR: { - p.block.heartMonitor = { - name: p.peer.data.tankIDName, - user_id: parseInt(p.peer.data?.id_user as string) - }; - - p.world.place({ - peer: p.peer, - x: p.block.x, - y: p.block.y, - isBg: p.isBg, - id: p.id - }); - - tileUpdate(p.base, p.peer, p.actionType, p.block, p.world); - - return true; - } - - case ActionTypes.LOCK: { - const mLock = p.base.locks.find((l) => l.id === p.id); - if (mLock) { - if (p.block.lock) { - p.peer.send(Variant.from("OnTalkBubble", p.peer.data.netID, "This area is `4already locked``", 0, 1)); - return false; - } - - if (typeof p.world.data.owner?.id === "number" && p.world.data.owner.id !== p.peer.data?.id_user) { - p.peer.send(Variant.from("OnTalkBubble", p.peer.data.netID, "The tile owner `2allows`` public building but `4not`` for this specific block.", 0, 1)); - return false; - } - - p.world.place({ - peer: p.peer, - x: p.block.x, - y: p.block.y, - isBg: p.isBg, - id: p.id - }); - - const algo = new Floodfill({ - s_node: { x: p.block.x, y: p.block.y }, - max: mLock.maxTiles, - width: p.world.data.width, - height: p.world.data.height, - blocks: p.world.data.blocks, - s_block: p.block, - base: p.base, - noEmptyAir: false - }); - - algo.exec(); - algo.apply(p.world, p.peer); - p.peer.send(Variant.from("OnTalkBubble", p.peer.data.netID, "Area locked.", 0, 1)); - - return true; - } - if (p.world.data.blocks?.find((b) => b.lock?.ownerUserID && b.lock.ownerUserID !== p.peer.data?.id_user)) { - p.peer.send(Variant.from("OnTalkBubble", p.peer.data.netID, `Can't put lock, there's other locks around here.`, 0, 1)); - return false; - } - - if (p.block.x === 0 && p.block.y === 0) { - p.peer.send(Variant.from("OnTalkBubble", p.peer.data.netID, "You `4cannot`` place locks over here!", 0, 1)); - return false; - } - - p.block.worldLock = true; - if (!p.block.lock) { - p.block.lock = { - ownerUserID: p.peer.data?.id_user as number - }; - } - p.world.data.owner = { - id: p.peer.data?.id_user as number, - name: p.peer.data?.tankIDName as string, - displayName: p.peer.name - }; - - p.world.data.bpm = 100; - - p.peer.everyPeer((pa) => { - if (pa.data?.world === p.peer.data?.world && pa.data?.world !== "EXIT") - pa.send( - Variant.from("OnTalkBubble", p.peer.data.netID, `\`3[\`w${p.world.worldName} \`ohas been World Locked by ${p.peer.name}\`3]`), - Variant.from("OnConsoleMessage", `\`3[\`w${p.world.worldName} \`ohas been World Locked by ${p.peer.name}\`3]`), - Variant.from({ netID: p.peer.data?.netID }, "OnPlayPositioned", "audio/use_lock.wav") - ); - }); - - p.world.place({ - peer: p.peer, - x: p.block.x, - y: p.block.y, - isBg: p.isBg, - id: p.id - }); - - tileUpdate(p.base, p.peer, p.actionType, p.block, p.world); - - return true; - } - - case ActionTypes.DISPLAY_BLOCK: { - p.block.dblockID = 0; - - p.world.place({ - peer: p.peer, - x: p.block.x, - y: p.block.y, - isBg: p.isBg, - id: p.id - }); - - tileUpdate(p.base, p.peer, p.actionType, p.block, p.world); - - return true; - } - - case ActionTypes.SEED: { - if (p.block.fg !== 0) return false; - - const item = p.base.items.metadata.items[p.id]; - const fruitCount = Math.floor(Math.random() * 10 * (1 - (item.rarity || 0) / 1000)) + 1; - const now = Date.now(); - - p.block.tree = { - fruit: p.id - 1, - fruitCount: fruitCount, - fullyGrownAt: now + (item.growTime || 0) * 1000, - plantedAt: now - }; - - p.world.place({ - peer: p.peer, - x: p.block.x, - y: p.block.y, - id: p.id, - fruit: fruitCount > 4 ? 4 : fruitCount - }); - - tileUpdate(p.base, p.peer, p.actionType, p.block, p.world); - - return true; - } - - default: { - p.base.log.debug("Unknown block placing", { actionType: p.actionType, block: p.block }); - return false; - } - } -} - -export function tileUpdate(base: BaseServer, peer: Peer, actionType: number, block: Block, world: World): void { - peer.everyPeer((p) => { - if (p.data?.world === peer.data?.world && p.data?.world !== "EXIT") { - p.send( - TankPacket.from({ - type: TankTypes.TILE_UPDATE, - xPunch: block.x, - yPunch: block.y, - data: () => HandleTile(base, block, world, actionType) - }) - ); - } - }); -} - -export function tileVisualUpdate(peer: Peer, block: Block, visualFlags: number, everyPeer = false): void { - const tank = TankPacket.from({ - type: TankTypes.TILE_UPDATE, - xPunch: block.x, - yPunch: block.y, - data: () => { - const buf = Buffer.alloc(8); - - buf.writeUInt32LE(block.fg | (block.bg << 16)); - buf.writeUint16LE(0x0, 4); - buf.writeUint16LE(visualFlags, 6); - - return buf; - } - }); - - if (everyPeer) { - peer.everyPeer((p) => { - if (p.data?.world === peer.data?.world && p.data?.world !== "EXIT") { - p.send(tank); - } - }); - } else { - peer.send(tank); - } -} diff --git a/src/tanks/BlockWrench.ts b/src/tanks/BlockWrench.ts deleted file mode 100644 index 0013ad4..0000000 --- a/src/tanks/BlockWrench.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Tank, TankPacket, Variant } from "growtopia.js"; -import { BaseServer } from "../structures/BaseServer"; -import { Peer } from "../structures/Peer"; -import { World } from "../structures/World"; -import { DialogBuilder } from "../utils/builders/DialogBuilder"; -import { ActionTypes } from "../utils/enums/Tiles"; - -export function handleWrench(base: BaseServer, tank: TankPacket, peer: Peer, world: World) { - const tankData = tank.data as Tank; - const pos = (tankData.xPunch as number) + (tankData.yPunch as number) * (world.data.width || 100); - const block = world.data.blocks[pos]; - const itemMeta = base.items.metadata.items[block.fg || block.bg]; - - switch (itemMeta.type) { - case ActionTypes.SIGN: { - if (world.data.owner) { - if (world.data.owner.id !== peer.data?.id_user) return; - } - const dialog = new DialogBuilder() - .defaultColor() - .addLabelWithIcon(`\`wEdit ${itemMeta.name}\`\``, itemMeta.id as number, "big") - .addTextBox("What would you like to write on this sign?") - .addInputBox("label", "", block.sign?.label, 100) - .embed("tilex", block.x) - .embed("tiley", block.y) - .embed("itemID", itemMeta.id) - .endDialog("sign_edit", "Cancel", "OK") - .str(); - - peer.send(Variant.from("OnDialogRequest", dialog)); - break; - } - - case ActionTypes.PORTAL: - case ActionTypes.DOOR: { - if (world.data.owner) { - if (world.data.owner.id !== peer.data?.id_user) return; - } - const dialog = new DialogBuilder() - .defaultColor() - .addLabelWithIcon(`\`wEdit ${itemMeta.name}\`\``, itemMeta.id as number, "big") - .addInputBox("label", "Label", block.door?.label, 100) - .addInputBox("target", "Destination", block.door?.destination, 24) - .addSmallText("Enter a Destination in this format: `2WORLDNAME:ID``") - .addSmallText("Leave `2WORLDNAME`` blank (:ID) to go to the door with `2ID`` in the `2Current World``.") - .addInputBox("id", "ID", block.door?.id, 11) - .addSmallText("Set a unique `2ID`` to target this door as a Destination from another!") - .embed("tilex", block.x) - .embed("tiley", block.y) - .embed("itemID", itemMeta.id) - .endDialog("door_edit", "Cancel", "OK") - .str(); - - peer.send(Variant.from("OnDialogRequest", dialog)); - break; - } - - case ActionTypes.LOCK: { - const mLock = base.locks.find((l) => l.id === itemMeta.id); - - if (mLock) { - if (block.lock?.ownerUserID !== peer.data?.id_user) return; - - const dialog = new DialogBuilder() - .defaultColor() - .addLabelWithIcon(`\`wEdit ${itemMeta.name}\`\``, itemMeta.id as number, "big") - .embed("lockID", mLock.id) - .embed("tilex", block.x) - .embed("tiley", block.y) - .addSmallText("Access list:") - // bikin list user disini nanti - .addSpacer("small") - .addTextBox("Currently, you're the only one with access.") - .raw("add_player_picker|playerNetID|`wAdd``|\n") - .addCheckbox("allow_break_build", "Allow anyone to Build and Break", block.lock?.openToPublic ? "selected" : "not_selected") - .addCheckbox("ignore_empty", "Ignore empty air", block.lock?.ignoreEmptyAir ? "selected" : "not_selected") - .addButton("reapply_lock", "`wRe-apply lock``"); - - if (itemMeta.id === 4994) { - dialog - .addSmallText(`This lock allows Building or Breaking.(ONLY if "Allow anyone to Build or Break" is checked above)!`) - .addSpacer("small") - .addSmallText("Leaving this box unchecked only allows Breaking.") - .addCheckbox("build_only", "Only Allow Building!", block.lock?.onlyAllowBuild ? "selected" : "not_selected") - .addSmallText("People with lock access can both build and break unless you check below. The lock owner can always build and break.") - .addCheckbox("limit_admin", "Admins Are Limited", block.lock?.adminLimited ? "selected" : "not_selected"); - } - - dialog.endDialog("area_lock_edit", "Cancel", "OK"); - - peer.send(Variant.from("OnDialogRequest", dialog.str())); - } - - break; - } - } -} diff --git a/src/tanks/Place.ts b/src/tanks/Place.ts index 74d3c17..c3ef528 100644 --- a/src/tanks/Place.ts +++ b/src/tanks/Place.ts @@ -5,73 +5,384 @@ import { World } from "../structures/World"; import { Role } from "../utils/Constants"; import { TankTypes } from "../utils/enums/TankTypes"; import { ActionTypes } from "../utils/enums/Tiles"; -import { handleBlockPlacing, tileUpdate } from "./BlockPlacing"; - -/** Handle Place */ -export function handlePlace(tank: TankPacket, peer: Peer, base: BaseServer, world: World): void { - const tankData = tank.data as Tank; - const pos = (tankData.xPunch as number) + (tankData.yPunch as number) * world.data.width; - const block = world.data.blocks[pos]; - //prettier-ignore - const isBg = base.items.metadata.items[tankData.info as number].type === ActionTypes.BACKGROUND || base.items.metadata.items[tankData.info as number].type === ActionTypes.SHEET_MUSIC; - const placedItem = base.items.metadata.items.find((i) => i.id === tank.data?.info); - const mLock = base.locks.find((l) => l.id === placedItem?.id); - const mainLock = block.lock ? world.data.blocks[(block.lock.ownerX as number) + (block.lock.ownerY as number) * world.data.width] : null; - - if (!placedItem || !placedItem.id) return; - if (tankData.info === 18 || tankData.info === 32) return; - - if (world.data.owner) { - // if (placedItem.id === 242 || placedItem.id === 4802) return; - if (!mLock && placedItem.type === ActionTypes.LOCK) { - peer.send(Variant.from("OnTalkBubble", peer.data.netID, "Uhh, world already locked.", 0, 1)); - return; +import { PlacedArg, Block } from "../types/world"; +import { Floodfill } from "../structures/FloodFill"; +import { BlockFlags } from "../utils/enums/ItemTypes"; +import { Tile } from "../structures/Tile"; + +export class Place { + public base: BaseServer; + public peer: Peer; + public tank: TankPacket; + public world: World; + + constructor(base: BaseServer, peer: Peer, tank: TankPacket, world: World) { + this.base = base; + this.peer = peer; + this.tank = tank; + this.world = world; + } + + public static tileUpdate(base: BaseServer, peer: Peer, actionType: number, block: Block, world: World) { + peer.everyPeer((p) => { + if (p.data?.world === peer.data?.world && p.data?.world !== "EXIT") { + p.send( + TankPacket.from({ + type: TankTypes.TILE_UPDATE, + xPunch: block.x, + yPunch: block.y, + data: () => new Tile(base, world, block).serialize(actionType) + }) + ); + } + }); + } + + public tileUpdate(actionType: number, block: Block) { + this.peer.everyPeer((p) => { + if (p.data?.world === this.peer.data?.world && p.data?.world !== "EXIT") { + p.send( + TankPacket.from({ + type: TankTypes.TILE_UPDATE, + xPunch: block.x, + yPunch: block.y, + data: () => new Tile(this.base, this.world, block).serialize(actionType) + }) + ); + } + }); + } + + public static tileVisualUpdate(peer: Peer, block: Block, visualFlags: number, everyPeer = false) { + const tank = TankPacket.from({ + type: TankTypes.TILE_UPDATE, + xPunch: block.x, + yPunch: block.y, + data: () => { + const buf = Buffer.alloc(8); + + buf.writeUInt32LE(block.fg | (block.bg << 16)); + buf.writeUint16LE(0x0, 4); + buf.writeUint16LE(visualFlags, 6); + + return buf; + } + }); + + if (everyPeer) { + peer.everyPeer((p) => { + if (p.data?.world === peer.data?.world && p.data?.world !== "EXIT") { + p.send(tank); + } + }); + } else { + peer.send(tank); + } + } + + public tileVisualUpdate(block: Block, visualFlags: number, everyPeer = false) { + const tank = TankPacket.from({ + type: TankTypes.TILE_UPDATE, + xPunch: block.x, + yPunch: block.y, + data: () => { + const buf = Buffer.alloc(8); + + buf.writeUInt32LE(block.fg | (block.bg << 16)); + buf.writeUint16LE(0x0, 4); + buf.writeUint16LE(visualFlags, 6); + + return buf; + } + }); + + if (everyPeer) { + this.peer.everyPeer((p) => { + if (p.data?.world === this.peer.data?.world && p.data?.world !== "EXIT") { + p.send(tank); + } + }); + } else { + this.peer.send(tank); } - if (world.data.owner.id !== peer.data?.id_user) { - if (peer.data?.role !== Role.DEVELOPER) { - peer.send(Variant.from({ netID: peer.data?.netID }, "OnPlayPositioned", "audio/punch_locked.wav")); + } + + public onPlace(): void { + const tankData = this.tank.data as Tank; + const pos = (tankData.xPunch as number) + (tankData.yPunch as number) * this.world.data.width; + const block = this.world.data.blocks[pos]; + //prettier-ignore + const isBg = this.base.items.metadata.items[tankData.info as number].type === ActionTypes.BACKGROUND || this.base.items.metadata.items[tankData.info as number].type === ActionTypes.SHEET_MUSIC; + const placedItem = this.base.items.metadata.items.find((i) => i.id === this.tank.data?.info); + const mLock = this.base.locks.find((l) => l.id === placedItem?.id); + const mainLock = block.lock ? this.world.data.blocks[(block.lock.ownerX as number) + (block.lock.ownerY as number) * this.world.data.width] : null; + + if (!placedItem || !placedItem.id) return; + if (tankData.info === 18 || tankData.info === 32) return; + + if (this.world.data.owner) { + // if (placedItem.id === 242 || placedItem.id === 4802) return; + if (!mLock && placedItem.type === ActionTypes.LOCK) { + this.peer.send(Variant.from("OnTalkBubble", this.peer.data.netID, "Uhh, world already locked.", 0, 1)); return; } + if (this.world.data.owner.id !== this.peer.data?.id_user) { + if (this.peer.data?.role !== Role.DEVELOPER) { + this.peer.send(Variant.from({ netID: this.peer.data?.netID }, "OnPlayPositioned", "audio/punch_locked.wav")); + return; + } + } + } else { + if (this.peer.data?.role !== Role.DEVELOPER) { + if (mainLock && mainLock.lock?.ownerUserID !== this.peer.data?.id_user) { + this.peer.send(Variant.from({ netID: this.peer.data?.netID }, "OnPlayPositioned", "audio/punch_locked.wav")); + return; + } + } } - } else { - if (peer.data?.role !== Role.DEVELOPER) { - if (mainLock && mainLock.lock?.ownerUserID !== peer.data?.id_user) { - peer.send(Variant.from({ netID: peer.data?.netID }, "OnPlayPositioned", "audio/punch_locked.wav")); + + if (placedItem.id === 8 || placedItem.id === 6 || placedItem.id === 1000 || placedItem.id === 3760 || placedItem.id === 7372) { + if (this.peer.data?.role !== Role.DEVELOPER) { + this.peer.send(Variant.from("OnTalkBubble", this.peer.data.netID, "Can't place that block."), Variant.from({ netID: this.peer.data?.netID }, "OnPlayPositioned", "audio/punch_locked.wav")); return; } } - } - if (placedItem.id === 8 || placedItem.id === 6 || placedItem.id === 1000 || placedItem.id === 3760 || placedItem.id === 7372) { - if (peer.data?.role !== Role.DEVELOPER) { - peer.send(Variant.from("OnTalkBubble", peer.data.netID, "Can't place that block."), Variant.from({ netID: peer.data?.netID }, "OnPlayPositioned", "audio/punch_locked.wav")); - return; + if (block.fg === 2946) { + block.dblockID = placedItem.id; + if (placedItem.collisionType === 1) { + this.peer.removeItemInven(this.tank.data?.info as number, 1); + this.tileUpdate(ActionTypes.DISPLAY_BLOCK, block); + this.peer.inventory(); + return; + } + this.tileUpdate(ActionTypes.DISPLAY_BLOCK, block); } + + const placed = this.onPlaced({ + actionType: placedItem.type as number, + flags: placedItem.flags as number, + block, + id: placedItem.id, + isBg + }); + + this.peer.removeItemInven(this.tank.data?.info as number, 1); + this.peer.inventory(); + this.peer.saveToCache(); + this.peer.saveToCache(); + return; } - if (block.fg === 2946) { - block.dblockID = placedItem.id; - if (placedItem.collisionType === 1) { - peer.removeItemInven(tank.data?.info as number); - tileUpdate(base, peer, ActionTypes.DISPLAY_BLOCK, block, world); - return; + public onPlaced(p: PlacedArg) { + if (p.block.fg === 2946 && p.actionType !== ActionTypes.DISPLAY_BLOCK) return false; + + // prevent replace a block to others + if (p.block.fg && p.flags & BlockFlags.WRENCHABLE) return false; + if (p.block.fg && !p.block.bg) return false; + if (p.block.fg && p.actionType === ActionTypes.PLATFORM) return false; + + switch (p.actionType) { + case ActionTypes.SHEET_MUSIC: + case ActionTypes.BEDROCK: + case ActionTypes.LAVA: + case ActionTypes.PLATFORM: + case ActionTypes.FOREGROUND: + case ActionTypes.BACKGROUND: { + this.world.place({ + peer: this.peer, + x: p.block.x, + y: p.block.y, + isBg: p.isBg, + id: p.id + }); + + this.tileVisualUpdate(p.block, 0x0, true); + + return true; + } + + case ActionTypes.PORTAL: + case ActionTypes.DOOR: + case ActionTypes.MAIN_DOOR: { + p.block.door = { label: "", destination: "", id: "", locked: false }; + + this.world.place({ + peer: this.peer, + x: p.block.x, + y: p.block.y, + isBg: p.isBg, + id: p.id + }); + + return true; + } + + case ActionTypes.SIGN: { + p.block.sign = { label: "" }; + + this.world.place({ + peer: this.peer, + x: p.block.x, + y: p.block.y, + isBg: p.isBg, + id: p.id + }); + + this.tileUpdate(p.actionType, p.block); + return true; + } + + case ActionTypes.HEART_MONITOR: { + p.block.heartMonitor = { + name: this.peer.data.tankIDName, + user_id: parseInt(this.peer.data?.id_user as string) + }; + + this.world.place({ + peer: this.peer, + x: p.block.x, + y: p.block.y, + isBg: p.isBg, + id: p.id + }); + + this.tileUpdate(p.actionType, p.block); + + return true; + } + + case ActionTypes.LOCK: { + const mLock = this.base.locks.find((l) => l.id === p.id); + if (mLock) { + if (p.block.lock) { + this.peer.send(Variant.from("OnTalkBubble", this.peer.data.netID, "This area is `4already locked``", 0, 1)); + return false; + } + + if (typeof this.world.data.owner?.id === "number" && this.world.data.owner.id !== this.peer.data?.id_user) { + this.peer.send(Variant.from("OnTalkBubble", this.peer.data.netID, "The tile owner `2allows`` public building but `4not`` for this specific block.", 0, 1)); + return false; + } + + this.world.place({ + peer: this.peer, + x: p.block.x, + y: p.block.y, + isBg: p.isBg, + id: p.id + }); + + const algo = new Floodfill({ + s_node: { x: p.block.x, y: p.block.y }, + max: mLock.maxTiles, + width: this.world.data.width, + height: this.world.data.height, + blocks: this.world.data.blocks, + s_block: p.block, + base: this.base, + noEmptyAir: false + }); + + algo.exec(); + algo.apply(this.world, this.peer); + this.peer.send(Variant.from("OnTalkBubble", this.peer.data.netID, "Area locked.", 0, 1)); + + return true; + } + if (this.world.data.blocks?.find((b) => b.lock?.ownerUserID && b.lock.ownerUserID !== this.peer.data?.id_user)) { + this.peer.send(Variant.from("OnTalkBubble", this.peer.data.netID, `Can't put lock, there's other locks around here.`, 0, 1)); + return false; + } + + if (p.block.x === 0 && p.block.y === 0) { + this.peer.send(Variant.from("OnTalkBubble", this.peer.data.netID, "You `4cannot`` place locks over here!", 0, 1)); + return false; + } + + p.block.worldLock = true; + if (!p.block.lock) { + p.block.lock = { + ownerUserID: this.peer.data?.id_user as number + }; + } + this.world.data.owner = { + id: this.peer.data?.id_user as number, + name: this.peer.data?.tankIDName as string, + displayName: this.peer.name + }; + + this.world.data.bpm = 100; + + this.peer.everyPeer((pa) => { + if (pa.data?.world === this.peer.data?.world && pa.data?.world !== "EXIT") + pa.send( + Variant.from("OnTalkBubble", this.peer.data.netID, `\`3[\`w${this.world.worldName} \`ohas been World Locked by ${this.peer.name}\`3]`), + Variant.from("OnConsoleMessage", `\`3[\`w${this.world.worldName} \`ohas been World Locked by ${this.peer.name}\`3]`), + Variant.from({ netID: this.peer.data?.netID }, "OnPlayPositioned", "audio/use_lock.wav") + ); + }); + + this.world.place({ + peer: this.peer, + x: p.block.x, + y: p.block.y, + isBg: p.isBg, + id: p.id + }); + + this.tileUpdate(p.actionType, p.block); + + return true; + } + + case ActionTypes.DISPLAY_BLOCK: { + p.block.dblockID = 0; + + this.world.place({ + peer: this.peer, + x: p.block.x, + y: p.block.y, + isBg: p.isBg, + id: p.id + }); + + this.tileUpdate(p.actionType, p.block); + + return true; + } + + case ActionTypes.SEED: { + if (p.block.fg !== 0) return false; + + const item = this.base.items.metadata.items[p.id]; + const fruitCount = Math.floor(Math.random() * 10 * (1 - (item.rarity || 0) / 1000)) + 1; + const now = Date.now(); + + p.block.tree = { + fruit: p.id - 1, + fruitCount: fruitCount, + fullyGrownAt: now + (item.growTime || 0) * 1000, + plantedAt: now + }; + + this.world.place({ + peer: this.peer, + x: p.block.x, + y: p.block.y, + id: p.id, + fruit: fruitCount > 4 ? 4 : fruitCount + }); + + this.tileUpdate(p.actionType, p.block); + + return true; + } + + default: { + this.base.log.debug("Unknown block placing", { actionType: p.actionType, block: p.block }); + return false; + } } - tileUpdate(base, peer, ActionTypes.DISPLAY_BLOCK, block, world); } - - const placed = handleBlockPlacing({ - actionType: placedItem.type as number, - flags: placedItem.flags as number, - peer, - world, - block, - id: placedItem.id, - isBg, - base - }); - - peer.removeItemInven(tank.data?.info as number); - world.saveToCache(); - peer.saveToCache(); - return; } diff --git a/src/tanks/Punch.ts b/src/tanks/Punch.ts index 8034b9d..ced9276 100644 --- a/src/tanks/Punch.ts +++ b/src/tanks/Punch.ts @@ -1,4 +1,4 @@ -import { Tank, TankPacket, Variant } from "growtopia.js"; +import { ItemDefinition, Tank, TankPacket, Variant } from "growtopia.js"; import { BaseServer } from "../structures/BaseServer"; import { Peer } from "../structures/Peer"; import { World } from "../structures/World"; @@ -6,42 +6,87 @@ import { Block } from "../types/world"; import { Role } from "../utils/Constants"; import { TankTypes } from "../utils/enums/TankTypes"; import { ActionTypes } from "../utils/enums/Tiles"; -import { tileUpdate, tileVisualUpdate } from "./BlockPlacing"; +import { Place } from "./Place"; + +export class Punch { + public base: BaseServer; + public peer: Peer; + public tank: TankPacket; + public world: World; + + constructor(base: BaseServer, peer: Peer, tank: TankPacket, world: World) { + this.base = base; + this.peer = peer; + this.tank = tank; + this.world = world; + } -/** Handle Punch */ -export function handlePunch(tank: TankPacket, peer: Peer, base: BaseServer, world: World): void { - const tankData = tank.data as Tank; - const pos = (tankData.xPunch as number) + (tankData.yPunch as number) * world.data.width; - const block = world.data.blocks[pos]; - const itemMeta = base.items.metadata.items[block.fg || block.bg]; - - if (!itemMeta.id) return; - if (typeof block.damage !== "number" || (block.resetStateAt as number) <= Date.now()) block.damage = 0; - - if (world.data.owner) { - if (world.data.owner.id !== peer.data?.id_user) { - if (peer.data?.role !== Role.DEVELOPER) { - if (itemMeta.id === 242) peer.send(Variant.from("OnTalkBubble", peer.data.netID, `\`#[\`0\`9World Locked by ${world.data.owner?.displayName}\`#]`)); - - peer.everyPeer((p) => { - if (p.data?.world === peer.data?.world && p.data?.world !== "EXIT") p.send(Variant.from({ netID: peer.data?.netID }, "OnPlayPositioned", "audio/punch_locked.wav")); + public onPunch() { + const tankData = this.tank.data as Tank; + const pos = (tankData.xPunch as number) + (tankData.yPunch as number) * this.world.data.width; + const block = this.world.data.blocks[pos]; + const itemMeta = this.base.items.metadata.items[block.fg || block.bg]; + + if (!itemMeta.id) return; + if (typeof block.damage !== "number" || (block.resetStateAt as number) <= Date.now()) block.damage = 0; + + if (this.world.data.owner) { + if (this.world.data.owner.id !== this.peer.data?.id_user) { + if (this.peer.data?.role !== Role.DEVELOPER) { + if (itemMeta.id === 242) this.peer.send(Variant.from("OnTalkBubble", this.peer.data.netID, `\`#[\`0\`9World Locked by ${this.world.data.owner?.displayName}\`#]`)); + + this.peer.everyPeer((p) => { + if (p.data?.world === this.peer.data?.world && p.data?.world !== "EXIT") p.send(Variant.from({ netID: this.peer.data?.netID }, "OnPlayPositioned", "audio/punch_locked.wav")); + }); + return; + } + } + } + + if (itemMeta.id === 8 || itemMeta.id === 6 || itemMeta.id === 3760 || itemMeta.id === 7372) { + if (this.peer.data?.role !== Role.DEVELOPER) { + this.peer.send(Variant.from("OnTalkBubble", this.peer.data.netID, "It's too strong to break.")); + this.peer.everyPeer((p) => { + if (p.data?.world === this.peer.data?.world && p.data?.world !== "EXIT") p.send(Variant.from({ netID: this.peer.data?.netID }, "OnPlayPositioned", "audio/punch_locked.wav")); }); return; } } + + if (block.damage >= (itemMeta.breakHits as number)) { + this.onDestroyed(block, itemMeta, tankData); + } else { + this.onDamaged(block, itemMeta, tankData); + } + + this.peer.send(this.tank); + + this.world.saveToCache(); + + this.peer.everyPeer((p) => { + if (p.data?.netID !== this.peer.data?.netID && p.data?.world === this.peer.data?.world && p.data?.world !== "EXIT") { + p.send(this.tank); + } + }); + return; } - if (itemMeta.id === 8 || itemMeta.id === 6 || itemMeta.id === 3760 || itemMeta.id === 7372) { - if (peer.data?.role !== Role.DEVELOPER) { - peer.send(Variant.from("OnTalkBubble", peer.data.netID, "It's too strong to break.")); - peer.everyPeer((p) => { - if (p.data?.world === peer.data?.world && p.data?.world !== "EXIT") p.send(Variant.from({ netID: peer.data?.netID }, "OnPlayPositioned", "audio/punch_locked.wav")); - }); - return; + private onDamaged(block: Block, itemMeta: ItemDefinition, tankData: Tank) { + tankData.type = TankTypes.TILE_DAMAGE; + tankData.info = (block.damage as number) + 5; + + block.resetStateAt = Date.now() + (itemMeta.resetStateAfter as number) * 1000; + (block.damage as number)++; + + switch (itemMeta.type) { + case ActionTypes.SEED: { + this.world.harvest(this.peer, block); + break; + } } } - if (block.damage >= (itemMeta.breakHits as number)) { + private onDestroyed(block: Block, itemMeta: ItemDefinition, tankData: Tank) { block.damage = 0; block.resetStateAt = 0; @@ -77,44 +122,22 @@ export function handlePunch(tank: TankPacket, peer: Peer, base: BaseServer, worl } case ActionTypes.LOCK: { - if (base.locks.find((l) => l.id === itemMeta.id)) { - world.data.blocks?.forEach((b) => { + if (this.base.locks.find((l) => l.id === itemMeta.id)) { + this.world.data.blocks?.forEach((b) => { if (b.lock && b.lock.ownerX === block.x && b.lock.ownerY === block.y) b.lock = undefined; }); } else { block.worldLock = undefined; block.lock = undefined; - world.data.owner = undefined; + this.world.data.owner = undefined; // tileUpdate(base, peer, itemMeta.type, block, world); - tileVisualUpdate(peer, block, 0x0, true); + Place.tileVisualUpdate(this.peer, block, 0x0, true); } break; } } - } else { - tankData.type = TankTypes.TILE_DAMAGE; - tankData.info = block.damage + 5; - - block.resetStateAt = Date.now() + (itemMeta.resetStateAfter as number) * 1000; - block.damage++; - - switch (itemMeta.type) { - case ActionTypes.SEED: { - world.harvest(peer, block); - break; - } - } } - - peer.send(tank); - - world.saveToCache(); - - peer.everyPeer((p) => { - if (p.data?.netID !== peer.data?.netID && p.data?.world === peer.data?.world && p.data?.world !== "EXIT") { - p.send(tank); - } - }); - return; } + +/** Handle Punch */ diff --git a/src/tanks/Wrench.ts b/src/tanks/Wrench.ts new file mode 100644 index 0000000..76c0722 --- /dev/null +++ b/src/tanks/Wrench.ts @@ -0,0 +1,111 @@ +import { Tank, TankPacket, Variant } from "growtopia.js"; +import { BaseServer } from "../structures/BaseServer"; +import { Peer } from "../structures/Peer"; +import { World } from "../structures/World"; +import { DialogBuilder } from "../utils/builders/DialogBuilder"; +import { ActionTypes } from "../utils/enums/Tiles"; + +export class Wrench { + public base: BaseServer; + public peer: Peer; + public tank: TankPacket; + public world: World; + + constructor(base: BaseServer, peer: Peer, tank: TankPacket, world: World) { + this.base = base; + this.peer = peer; + this.tank = tank; + this.world = world; + } + + public onWrench() { + const tankData = this.tank.data as Tank; + const pos = (tankData.xPunch as number) + (tankData.yPunch as number) * (this.world.data.width as number); + const block = this.world.data.blocks[pos]; + const itemMeta = this.base.items.metadata.items[block.fg || block.bg]; + + switch (itemMeta.type) { + case ActionTypes.SIGN: { + if (this.world.data.owner) { + if (this.world.data.owner.id !== this.peer.data?.id_user) return; + } + const dialog = new DialogBuilder() + .defaultColor() + .addLabelWithIcon(`\`wEdit ${itemMeta.name}\`\``, itemMeta.id as number, "big") + .addTextBox("What would you like to write on this sign?") + .addInputBox("label", "", block.sign?.label, 100) + .embed("tilex", block.x) + .embed("tiley", block.y) + .embed("itemID", itemMeta.id) + .endDialog("sign_edit", "Cancel", "OK") + .str(); + + this.peer.send(Variant.from("OnDialogRequest", dialog)); + break; + } + + case ActionTypes.PORTAL: + case ActionTypes.DOOR: { + if (this.world.data.owner) { + if (this.world.data.owner.id !== this.peer.data?.id_user) return; + } + const dialog = new DialogBuilder() + .defaultColor() + .addLabelWithIcon(`\`wEdit ${itemMeta.name}\`\``, itemMeta.id as number, "big") + .addInputBox("label", "Label", block.door?.label, 100) + .addInputBox("target", "Destination", block.door?.destination, 24) + .addSmallText("Enter a Destination in this format: `2WORLDNAME:ID``") + .addSmallText("Leave `2WORLDNAME`` blank (:ID) to go to the door with `2ID`` in the `2Current World``.") + .addInputBox("id", "ID", block.door?.id, 11) + .addSmallText("Set a unique `2ID`` to target this door as a Destination from another!") + .embed("tilex", block.x) + .embed("tiley", block.y) + .embed("itemID", itemMeta.id) + .endDialog("door_edit", "Cancel", "OK") + .str(); + + this.peer.send(Variant.from("OnDialogRequest", dialog)); + break; + } + + case ActionTypes.LOCK: { + const mLock = this.base.locks.find((l) => l.id === itemMeta.id); + + if (mLock) { + if (block.lock?.ownerUserID !== this.peer.data?.id_user) return; + + const dialog = new DialogBuilder() + .defaultColor() + .addLabelWithIcon(`\`wEdit ${itemMeta.name}\`\``, itemMeta.id as number, "big") + .embed("lockID", mLock.id) + .embed("tilex", block.x) + .embed("tiley", block.y) + .addSmallText("Access list:") + // bikin list user disini nanti + .addSpacer("small") + .addTextBox("Currently, you're the only one with access.") + .raw("add_player_picker|playerNetID|`wAdd``|\n") + .addCheckbox("allow_break_build", "Allow anyone to Build and Break", block.lock?.openToPublic ? "selected" : "not_selected") + .addCheckbox("ignore_empty", "Ignore empty air", block.lock?.ignoreEmptyAir ? "selected" : "not_selected") + .addButton("reapply_lock", "`wRe-apply lock``"); + + if (itemMeta.id === 4994) { + dialog + .addSmallText(`This lock allows Building or Breaking.(ONLY if "Allow anyone to Build or Break" is checked above)!`) + .addSpacer("small") + .addSmallText("Leaving this box unchecked only allows Breaking.") + .addCheckbox("build_only", "Only Allow Building!", block.lock?.onlyAllowBuild ? "selected" : "not_selected") + .addSmallText("People with lock access can both build and break unless you check below. The lock owner can always build and break.") + .addCheckbox("limit_admin", "Admins Are Limited", block.lock?.adminLimited ? "selected" : "not_selected"); + } + + dialog.endDialog("area_lock_edit", "Cancel", "OK"); + + this.peer.send(Variant.from("OnDialogRequest", dialog.str())); + } + + break; + } + } + } +} diff --git a/src/types/world.d.ts b/src/types/world.d.ts index 0564c28..bc9797d 100644 --- a/src/types/world.d.ts +++ b/src/types/world.d.ts @@ -1,4 +1,5 @@ -import { Peer } from "../structures/Peer"; +import type { Peer } from "../structures/Peer"; +import type { World } from "../structures/World"; export interface Place { peer: Peer; @@ -9,6 +10,14 @@ export interface Place { fruit?: number; } +export interface PlacedArg { + actionType: number; + block: Block; + id: number; + isBg?: boolean; + flags: number; +} + export interface WorldDB { name: string; ownedBy?: string | null; diff --git a/web/views/register.ejs b/web/views/register.ejs deleted file mode 100644 index 702eb5f..0000000 --- a/web/views/register.ejs +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - Register - - -
-

Create new account

-
- - -
-
- - -
-
- -
-
- -