Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update #35

Merged
merged 17 commits into from
Dec 3, 2024
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export { default as GameClient } from "./game.js";

export { default as PlayerMap } from "./players/map.js";

export { default as Block } from "./world/block.js";
export { default as Block, BlockId, BlockName } from "./world/block.js";
export { default as Layer } from "./world/layer.js";
export { default as Structure } from "./world/structure.js";
// export { default as World } from "./world/world.js";
Expand Down
40 changes: 40 additions & 0 deletions src/lobby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import RoomTypes from "./data/room-types.js";
import GameClient from "./game.js";

import PublicProfile from "./types/public-profile.js";
import PrivateWorld from "./types/private-world.js";
import PublicWorld from "./types/public-world.js";
import Friend, { FriendRequest } from "./types/friends.js";

Expand Down Expand Up @@ -150,6 +151,11 @@ export default class LobbyClient<Auth extends boolean = false> {
* Returns a Pocketbase [RecordService](https://github.com/pocketbase/js-sdk/blob/master/src/services/RecordService.ts).
* See usage at the [PocketBase](https://pocketbase.io/) website for [searching records](https://pocketbase.io/docs/api-records#listsearch-records).
* This method returns a collection handler that allows you to search through all public worlds.
*
* @note
*
* If you want to get a full list of worlds that you own and are not set to
* public, refer to the {@link LobbyClient.my_worlds} method.
*
* @example
*
Expand All @@ -169,11 +175,45 @@ export default class LobbyClient<Auth extends boolean = false> {
* "width": 200
* }
* ```
*
* @example
*
* If you want to get only **public** worlds that you yourself own, you can
* use the `owner='selfId'` filter (with selfId being your own connect user id).
*
* ```ts
* LobbyClient.withToken(process.env.token);
* .worlds()
* .getFullList({ filter: `owner='${client.selfId}'` })
* .then(console.log)
* ```
*/
public worlds(): RecordService<PublicWorld> {
return this.pocketbase.collection("public_worlds");
}

/**
* Returns a Pocketbase [RecordService](https://github.com/pocketbase/js-sdk/blob/master/src/services/RecordService.ts).
* See usage at the [PocketBase](https://pocketbase.io/) website for [searching records](https://pocketbase.io/docs/api-records#listsearch-records).
* This method returns a collection handler that allows you to search through
* all worlds owned by you.
*
* @example
*
* ```ts
* LobbyClient.withToken(process.env.token).
* .my_worlds()
* .getFullList()
* .then(console.log)
* ```
*
* @note To use this function you have to be logged in. (Login with
* an authorized constructor, i.e. not {@link LobbyClient.guest})
*/
public my_worlds(this: LobbyClient<true>): RecordService<PrivateWorld> {
return this.pocketbase.collection("worlds");
}

/**
* @note To use this function you have to be logged in. (Login with
* an authorized constructor, i.e. not {@link LobbyClient.guest})
Expand Down
24 changes: 24 additions & 0 deletions src/types/private-world.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { RecordModel } from "pocketbase";

/**
* A public world as retrieved by the Pocketbase API.
*/
export type PrivateWorld = RecordModel & {
collectionId: "omma7comumpv34j";
collectionName: "worlds";
id: string;
minimap: string;
minimapEnabled: boolean;
created: string;
updated: string;
visibility: 'public' | 'unlisted' | 'friends' | 'private';
data: string;
owner: string;
title: string;
description: string;
plays: number;
width: number;
height: number;
};

export default PrivateWorld;
22 changes: 22 additions & 0 deletions src/util/buffer-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,16 @@ export default class BufferReader {
// return tmp;
// }

/**
*
*/
public expectUInt8(value: number) {
const tmp = this.#buffer.readUInt8(this.#offset);
this.#offset += 1;
if (tmp !== value) throw new Error(`Expected ${value} but got ${tmp}`);
return tmp;
}

/**
*
*/
Expand Down Expand Up @@ -741,4 +751,16 @@ export default class BufferReader {

return arr;
}

[Symbol.for("nodejs.util.inspect.custom")]() {
let s = '<BufferReader';
let copy = BufferReader.from(this.#buffer);
copy.#offset = this.#offset;

for (let i = 0; i < 20 && this.#offset + i < this.length - 1; i++) {
s += ' ' + copy.readUInt8().toString(16).padStart(2, '0');
}

return s + '>';
}
}
25 changes: 17 additions & 8 deletions src/world/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import BufferReader, { ComponentTypeHeader } from "../util/buffer-reader.js";
* block.name; // 'empty'
* ```
*/
export type BlockId = keyof typeof BlockMappingsReverse;
export type BlockId = (typeof BlockMappings)[string];

/**
* This type represents the block mapping name that can be used in the game.
Expand All @@ -32,7 +32,12 @@ export type BlockName = keyof typeof BlockMappings;
* console.log(Block['EMPTY']); // Block[empty]
* ```
*/
export class Block<Index extends BlockId = BlockId> {
export class Block {
/**
* @todo
*/
public static Mappings = BlockMappings;

/**
* Retrieve the block argument based on the argument number.
*
Expand All @@ -48,7 +53,7 @@ export class Block<Index extends BlockId = BlockId> {
* The unique id of a block. Block ID changes accross updates.
* If you want to save persistant data, refer the block mapping.
*/
public readonly id: Index;
public readonly id: BlockId;

/**
* Block arguments are additional data that is sent with the block.
Expand All @@ -64,14 +69,14 @@ export class Block<Index extends BlockId = BlockId> {
/**
* Create a new block instance based on its' block id or block mapping.
*/
public constructor(id: Index | (typeof BlockMappingsReverse)[Index]) {
public constructor(id: BlockId | BlockName) {
switch (true) {
case id === undefined:
throw new Error("Block id is undefined");
// this.id = 0 as Index;
// break;
case typeof id === "string" && BlockMappings[id] !== undefined:
this.id = BlockMappings[id] as Index;
this.id = BlockMappings[id];
break;
case typeof id === "number":
this.id = id;
Expand All @@ -98,14 +103,14 @@ export class Block<Index extends BlockId = BlockId> {
/**
* Retrieves the mapping name of the block based on its' id.
*/
public get name(): (typeof BlockMappingsReverse)[Index] {
public get name(): BlockName {
return BlockMappingsReverse[this.id];
}

/**
* Returns if two blocks are equal based on their id and arguments.
*/
public equals(other: Block<number>): other is Block<Index> {
public equals(other: Block): boolean {
if ((this.id as number) !== other.id) return false;
if (this.data.length !== other.data.length) return false;

Expand Down Expand Up @@ -149,10 +154,14 @@ export class Block<Index extends BlockId = BlockId> {
/**
* Deserialize a block arguments from the buffer reader.
*/
public deserialize_args(buffer: BufferReader): Block {
public deserialize_args(buffer: BufferReader, flag = false): Block {
const format: ComponentTypeHeader[] = (BlockArgs as any)[this.name];

for (let i = 0; i < (format?.length ?? 0); i++) {
if (flag) {
buffer.expectUInt8(format[i]);
}

this.data[i] = buffer.read(format[i]);
}

Expand Down
103 changes: 70 additions & 33 deletions src/world/structure.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import YAML from "yaml";
import BufferReader from "../util/buffer-reader.js";
import Layer from "./layer.js";
import Block from "./block.js";
import Block, { BlockName } from "./block.js";

import structureMigrations from "./structure.migrations.js";

Expand Down Expand Up @@ -114,22 +113,49 @@ export default class Structure<Meta extends { [keys: number | string]: any } = {

private static LATEST_VERSION_ENCODING = 1;

// TODO TEST THIS
public static fromString(format: "json" | "yaml", value: string): Structure {
let data: any;
/**
* Read the structure from a parser implementation and
* return a Structure instance. *The default parser is JSON.*
*
* @example
*
* ```ts
* const value = fs.readFileSync("structure.json");
* const data = Structure.fromString(value);
* ```
*/
public static fromString(value: string): Structure;

switch (format) {
case "json": {
data = JSON.parse(data);
break;
}
case "yaml": {
data = YAML.parse(data);
break;
}
default:
throw new Error(`Unknown format: ${format}`);
}
/**
* Read the structure from a JSON parser implementation and
* return a Structure instance.
*
* @example
*
* ```ts
* const value = fs.readFileSync("structure.json");
* const data = Structure.fromString(value, JSON);
* ```
*/
public static fromString(value: string, format: JSON): Structure;

/**
* Read the structure from a custom parser implementation and
* return a Structure instance.
*
* @example
*
* ```ts
* import YAML from "yaml";
*
* const value = fs.readFileSync("structure.yaml");
* const data = Structure.fromString(value, YAML);
* ```
*/
public static fromString(value: string, parser: { parse(v: string): any }): Structure;

public static fromString(value: string, parser: { parse(v: string): any } = JSON): Structure {
let data = parser.parse(value);

if (data["file-version"] < 0 || data["file-version"] > Structure.LATEST_VERSION_ENCODING) {
throw new Error(`Unsupported file version: ${data["file-version"]}`);
Expand Down Expand Up @@ -162,17 +188,37 @@ export default class Structure<Meta extends { [keys: number | string]: any } = {
}

/**
* Write the structure into a writeable stream.
* Write the structure into a writeable stream and return an
* JSON string representation of the structure.
*
* @example
*
* ```ts
* const structure = ...;
* const data = structure.toString(JSON);
* fs.writeFileSync("structure.json", data);
* ```
*/
public toString(format: "json"): string;
public toString(format: JSON): string;

/**
* Write the structure into a writeable stream.
* Write the structure into a writeable stream and return a custom
* object encoding string.
*
* @example
*
* ```ts
* import YAML from "yaml";
*
* const structure = ...;
* const data = structure.toString(YAML);
* fs.writeFileSync("structure.yaml", data);
* ```
*/
public toString(format: "yaml"): string;
public toString(parser: { stringify(v: any): string }): string;

public toString(format: "json" | "yaml"): string {
const palette = ["empty"];
public toString(parser: { stringify(v: any): string }): string {
const palette: BlockName[] = ["empty"];
const layers: string[] = [];

const data = {
Expand Down Expand Up @@ -203,15 +249,6 @@ export default class Structure<Meta extends { [keys: number | string]: any } = {
layers.push(ENCODING);
}

switch (format) {
case "json": {
return JSON.stringify(data);
}
case "yaml": {
return YAML.stringify(data);
}
default:
throw new Error(`Unknown format: ${format}`);
}
return parser.stringify(data);
}
}
3 changes: 2 additions & 1 deletion src/world/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export default class World {
*/
connection.listen("worldBlockPlacedPacket", ({ playerId, isFillOperation, blockId, layer, extraFields, positions }) => {
const block = new Block(blockId);
block.deserialize_args(BufferReader.from(extraFields.buffer));
console.log(blockId, extraFields)
block.deserialize_args(BufferReader.from(extraFields), true);

for (const { x, y } of positions) {
this.structure![layer][x][y] = block;
Expand Down
Loading