From b9ffbd810888becad28dc22494543ed6898fc486 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 21 Nov 2024 01:31:23 +0300 Subject: [PATCH] feat: add gamerules support: basic data parsing, now possible to control/change them via /gamerule only these gamerules effect implemented: doDaylightCycle fix: better messaging from output commands (now green) --- src/levelDat.ts | 14 +++++++++++- src/lib/command.ts | 4 ++-- src/lib/modules/commands.ts | 2 +- src/lib/modules/daycycle.ts | 5 ++++- src/lib/modules/gamerules.ts | 41 ++++++++++++++++++++++++++++++++++++ src/lib/modules/world.ts | 11 ++++------ 6 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 src/lib/modules/gamerules.ts diff --git a/src/levelDat.ts b/src/levelDat.ts index 00e10e07..88744b07 100644 --- a/src/levelDat.ts +++ b/src/levelDat.ts @@ -26,7 +26,7 @@ export const numberToLongArray = (num: bigint | number): [number, number] => { return [high, low] } -export async function writeLevelDat (path: string, value: LevelDatWrite & { time?: number }, oldLevelRaw?) { +export async function writeLevelDat (path: string, value: LevelDatWrite & { time?: number } & { GameRules }, oldLevelRaw?) { const nbt = { type: 'compound', name: '', @@ -35,6 +35,18 @@ export async function writeLevelDat (path: string, value: LevelDatWrite & { time type: 'compound', value: { ...oldLevelRaw?.value?.Data?.value, + GameRules: { + type: 'compound', + value: { + ...oldLevelRaw?.value?.Data?.value.GameRules?.value, + ...Object.fromEntries(Object.entries(value.GameRules ?? {}).map(([key, val]) => { + return [key, { + type: 'string', + value: val ? 'true' : 'false' + }] + })), + } + }, Version: { type: 'compound', value: { diff --git a/src/lib/command.ts b/src/lib/command.ts index a04909fa..fab49e26 100644 --- a/src/lib/command.ts +++ b/src/lib/command.ts @@ -1,10 +1,10 @@ import UserError from './user_error' -export type Ctx

= P extends true ? { +export type Ctx

= (P extends true ? { player: Player } : { player?: Player -} +}) type NonFalsey = T extends false ? never : NonNullable diff --git a/src/lib/modules/commands.ts b/src/lib/modules/commands.ts index 41d197fc..1c64e992 100644 --- a/src/lib/modules/commands.ts +++ b/src/lib/modules/commands.ts @@ -7,7 +7,7 @@ export const player = function (player: Player, serv: Server, { version }: Optio player.handleCommand = async (str) => { try { const res = await serv.commands.use(str, { player }, player.op) - if (res) player.chat(serv.color.red + res) + if (res) player.chat(serv.color.green + res) } catch (err) { if (err.userError) player.chat(serv.color.red + 'Error: ' + err.message) else setTimeout(() => { throw err }, 0) diff --git a/src/lib/modules/daycycle.ts b/src/lib/modules/daycycle.ts index 02fb8da8..d54e4cd8 100644 --- a/src/lib/modules/daycycle.ts +++ b/src/lib/modules/daycycle.ts @@ -12,7 +12,10 @@ export const server = function (serv: Server) { serv.time ??= 0 serv.on('tick', (delta, count) => { - if (!serv.doDaylightCycle) return + // TODO + // const disabledByGamerule = 'doDayLightCycle doDayLightcycle DayNightCycle' + const disabledByGamerule = 'doDayLightCycle'.split(' ').some(x => serv.levelData?.GameRules[x] === 'false') || !serv.gamerules.doDayLightCycle + if (!serv.doDaylightCycle || disabledByGamerule) return if (count % 20 === 0) { serv.behavior('changeTime', { old: serv.time, diff --git a/src/lib/modules/gamerules.ts b/src/lib/modules/gamerules.ts new file mode 100644 index 00000000..3db457c0 --- /dev/null +++ b/src/lib/modules/gamerules.ts @@ -0,0 +1,41 @@ +export const server = function (serv: Server, options: Options) { + serv.gamerules ??= {} + serv.on('asap', () => { + for (const [key, val] of Object.entries(serv.levelData?.GameRules ?? {})) { + serv.gamerules[key] = val === 'true' + } + }) + + const gameRules = { + 'doDayLightCycle': 'Wether to enable daylight cycle' + } + + serv.commands.add({ + base: 'gamerule', + info: '', + usage: '/gamerule ', + op: true, + parse (string, ctx) { + return string.split(' ') + }, + action (data, ctx) { + const [rule, newVal] = data + if (!rule) { + return `Available these gamerules: ${Object.entries(gameRules).map(([key, desc]) => `${key}: ${desc}`).join(', ')}` + } + if (newVal) { + serv.gameMode[rule] = newVal === 'false' ? false : true + return `set ${rule} to ${serv.gamerules[rule]}` + } else { + const gamerule = serv.gamerules[rule] + return `${rule} is ${gamerule ? 'Enabled' : 'Disabled'} (${gamerule})` + } + } + }) +} + +declare global { + interface Server { + gamerules: Record + } +} diff --git a/src/lib/modules/world.ts b/src/lib/modules/world.ts index 66ce8317..49585cf5 100644 --- a/src/lib/modules/world.ts +++ b/src/lib/modules/world.ts @@ -183,11 +183,7 @@ export const server: ServerModule = async function (serv, options) { const world = ctx.player?.world ?? serv.overworld // todo need concept of command output const message = `Seed: ${world.seed}` - if (ctx.player) { - ctx.player.chat(message) - } else { - console.log(message) - } + return message }, }) @@ -285,7 +281,8 @@ const levelDatWriter = (serv: Server, options: Options) => { generatorName: serv.levelData?.generatorName ?? serv.overworld.generatorName === 'superflat' ? 'flat' : serv.overworld.generatorName === 'diamond_square' ? 'default' : 'customized', LevelName: serv.levelData?.LevelName ?? options.levelName!, // todo fix typing allowCommands: serv.levelData?.allowCommands ?? 1, - time: serv.time + time: serv.time, + GameRules: serv.gamerules, }) } } @@ -539,7 +536,7 @@ declare global { /** Global spawn and respawn point for every player */ spawnPoint?: Vec3 /** Parsed level.dat of the loaded world (only if worldFolder is specificed) */ - levelData?: LevelDatFull + levelData?: LevelDatFull & { GameRules?} worlds: Record /** Contains the overworld world. This is where the default spawn point is */ "overworld": CustomWorld