Skip to content

Commit

Permalink
Add warden
Browse files Browse the repository at this point in the history
  • Loading branch information
leaftail1880 committed Jan 4, 2025
1 parent aba5ed2 commit 86b943b
Show file tree
Hide file tree
Showing 27 changed files with 275 additions and 89 deletions.
4 changes: 2 additions & 2 deletions src/lib/cooldown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export class Cooldown {
}

/**
* Checks if cooldown for player is expired and returns true, otherwise tells player about it if {@link this.tell} is
* true and returns false
* Checks if cooldown for player is expired, if so updates last cooldown time in db and returns true, otherwise tells
* player about it if {@link this.tell} is true and returns false
*
* @param player - Player to check
* @returns - Whenether cooldown is expired or not
Expand Down
17 changes: 6 additions & 11 deletions src/lib/extensions/enviroment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable no-var */

import { MinecraftEntityTypes } from '@minecraft/vanilla-data'
import { stringify } from 'lib/utils/inspect'
import { util } from '../util'
import { expand } from './extend'

Expand Down Expand Up @@ -140,28 +139,24 @@ declare global {
}
}

function format(args: unknown[]) {
return args.map(e => util.toTerminalColors(stringify(e))).join(' ')
}

expand(console, {
error(...args: unknown[]) {
super.error(format(args))
super.error(util.format(args))
},
warn(...args: unknown[]) {
super.warn(format(args))
super.warn(util.format(args))
},
info(...args: unknown[]) {
super.info(format(args))
super.info(util.format(args))
},
log(...args: unknown[]) {
super.log(format(args))
super.log(util.format(args))
},
debug(...args: unknown[]) {
super.log(format(args))
super.log(util.format(args))
},
verbose(...args: unknown[]) {
if (verbose) super.log(format(args))
if (verbose) super.log(util.format(args))
},
})

Expand Down
20 changes: 19 additions & 1 deletion src/lib/player-move.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Player, system, world } from '@minecraft/server'
import { Player, ShortcutDimensions, system, world } from '@minecraft/server'
import type { Region } from 'lib/region'
import { Vector } from 'lib/vector'
import { EventSignal } from './event-signal'
import { WeakPlayerMap } from './weak-player-storage'

Expand All @@ -14,6 +16,22 @@ export const onPlayerMove = new EventSignal<PlayerPosition>()
/** Cache used by {@link onPlayerMove}. Updates every job run */
export const playerPositionCache = new WeakPlayerMap<PlayerPosition>()

export function anyPlayerNear(location: Vector3, dimensionType: ShortcutDimensions, radius: number) {
for (const player of playerPositionCache.values()) {
if (dimensionType !== player.dimensionType) continue
if (Vector.distance(player.vector, location) < radius) return true
}

return false
}

export function anyPlayerNearRegion(region: Region, radius: number) {
for (const player of playerPositionCache.values()) {
if (region.area.isNear(player, radius)) return true
}
return false
}

jobInterval()

function jobInterval() {
Expand Down
50 changes: 32 additions & 18 deletions src/lib/region/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Region } from 'lib/region/kinds/region'
import { t, textTable } from 'lib/text'
import { inspect } from 'lib/util'
import { Vector } from 'lib/vector'
import { RectangleArea } from './areas/rectangle'
import { SphereArea } from './areas/sphere'

export const createableRegions: { name: string; region: typeof Region }[] = []
Expand Down Expand Up @@ -47,24 +48,37 @@ function regionForm(player: Player) {
function regionList(player: Player, RegionType: typeof Region, back = () => regionForm(player)) {
const form = new ActionForm('Список ' + RegionType.name)

form.addButton(ActionForm.backText, back).addButton('Создать', BUTTON['+'], () => {
const form = new ModalForm('Создать ' + RegionType.name)

form
.addTextField('Центр', '~~~', '~~~')
.addSlider('Радиус', 1, 100, 1)
.show(player, (ctx, rawCenter, radius) => {
let center = parseLocationFromForm(ctx, rawCenter, player)
if (!center) return
center = Vector.floor(center)

editRegion(player, RegionType.create(new SphereArea({ radius, center }, player.dimension.type)), () =>
regionList(player, RegionType, back),
)
})
})
form
.addButton(ActionForm.backText, back)
.addButton('Создать Сферический', BUTTON['+'], () => {
new ModalForm('Создать Сферический ' + RegionType.name)
.addTextField('Центр', '~~~', '~~~')
.addSlider('Радиус', 1, 100, 1)
.show(player, (ctx, rawCenter, radius) => {
const center = parseLocationFromForm(ctx, rawCenter, player)
if (!center) return

editRegion(player, RegionType.create(new SphereArea({ radius, center }, player.dimension.type)), () =>
regionList(player, RegionType, back),
)
})
})
.addButton('Создать Кубический', BUTTON['+'], () => {
new ModalForm('Создать Кубический' + RegionType.name)
.addTextField('От', '~~~', '~~~')
.addTextField('До', '~~~', '~~~')
.show(player, (ctx, rawFrom, rawTo) => {
const from = parseLocationFromForm(ctx, rawFrom, player)
const to = parseLocationFromForm(ctx, rawTo, player)
if (!from || !to) return

editRegion(player, RegionType.create(new RectangleArea({ from, to }, player.dimension.type)), () =>
regionList(player, RegionType, back),
)
})
})

for (const region of RegionType.instances()) {
for (const region of RegionType.getAll()) {
form.addButton(region.name, () => editRegion(player, region, () => regionList(player, RegionType, back)))
}
form.show(player)
Expand Down Expand Up @@ -127,7 +141,7 @@ function parseLocationFromForm(ctx: FormCallback<ModalForm>, location: string, p
const parsed = parseLocationArguments([x, y, z], player)
if (!parsed) return ctx.error('Неправильная локация: ' + inspect(location))

return parsed
return Vector.floor(parsed)
}

export function editRegionPermissions(
Expand Down
6 changes: 3 additions & 3 deletions src/lib/region/kinds/region.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ describe('Region', () => {
expect(region).toBeInstanceOf(TestRegion)
expectTypeOf(region).toEqualTypeOf<TestRegion>()

expectTypeOf(TestRegion.instances()).toEqualTypeOf<TestRegion[]>()
expect(TestRegion.instances().length).toBe(1)
expect(TestRegion.instances()[0]).toBe(region)
expectTypeOf(TestRegion.getAll()).toEqualTypeOf<TestRegion[]>()
expect(TestRegion.getAll().length).toBe(1)
expect(TestRegion.getAll()[0]).toBe(region)

expectTypeOf(TestRegion.getAt(createPoint(0, 0, 0))).toEqualTypeOf<TestRegion | undefined>()
expect(TestRegion.getAt(createPoint(0, 0, 0))).toBe(region)
Expand Down
6 changes: 3 additions & 3 deletions src/lib/region/kinds/region.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface RegionCreationOptions {
}

interface RegionConstructor<I extends Region>
extends Pick<typeof Region, 'regions' | 'instances' | 'getAt' | 'getManyAt'> {
extends Pick<typeof Region, 'regions' | 'getAll' | 'getAt' | 'getManyAt'> {
new (...args: any[]): I
}

Expand Down Expand Up @@ -93,7 +93,7 @@ export class Region {
*
* @returns Array of instances that match the specified type `R` of Region.
*/
static instances<I extends Region>(this: RegionConstructor<I>) {
static getAll<I extends Region>(this: RegionConstructor<I>) {
return this.regions.filter((e => e instanceof this) as (e: Region) => e is I)
}

Expand All @@ -112,7 +112,7 @@ export class Region {
* @param point - Represents point in the world
*/
static getManyAt<I extends Region>(this: RegionConstructor<I> | typeof Region, point: AbstractPoint): I[] {
const regions = this === Region ? this.regions : this.instances()
const regions = this === Region ? this.regions : this.getAll()
point = toPoint(point)

return regions.filter(region => region.area.isIn(point)).sort((a, b) => b.priority - a.priority) as I[]
Expand Down
2 changes: 1 addition & 1 deletion src/lib/region/kinds/with-structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export abstract class RegionWithStructure extends Region {

constructor(...args: ConstructorParameters<typeof Region>) {
super(...args)
this.structure = new RegionStructure(this)
this.structure = new RegionStructure(this, this.id)
}
}
22 changes: 19 additions & 3 deletions src/lib/region/structure.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import { BlockPermutation, Dimension, StructureSaveMode, world } from '@minecraft/server'
import { BlockPermutation, Dimension, StructureSaveMode, system, world } from '@minecraft/server'
import { Vector } from 'lib/vector'
import { Region } from './kinds/region'

system.delay(() => {
// Due to a bug there is a ton of randomly saved structures
world.structureManager
.getWorldStructureIds()
.filter(e => /region:undefined-s-\d+-\d+\/.+/.exec(e))
.forEach(e => {
console.log('Deleted unused structure', e)
world.structureManager.delete(e)
})
})

export class RegionStructure {
constructor(private region: Region) {}
constructor(
private region: Region,
private regionId: string,
) {
this.id = `region:${this.regionId.replaceAll(':', '|')}`
}

protected id = `region:${this.region.id.replaceAll(':', '|')}`
protected id: string

/** Used when structure was saved with bigger area radius, for example in BaseRegion */
offset = 0
Expand Down
15 changes: 5 additions & 10 deletions src/lib/rpg/floating-text.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Entity, ShortcutDimensions, world } from '@minecraft/server'
import { CustomEntityTypes } from 'lib/assets/custom-entity-types'
import { anyPlayerNear } from 'lib/player-move'
import { createLogger } from 'lib/utils/logger'
import { Vector } from 'lib/vector'

Expand All @@ -10,7 +11,7 @@ export class FloatingText {

constructor(
private id: string,
private dimensionId: ShortcutDimensions,
private dimensionType: ShortcutDimensions,
) {}

private entity: Entity | undefined
Expand All @@ -21,13 +22,7 @@ export class FloatingText {
}

update(location: Vector3, nameTag: string) {
if (
!world
.getAllPlayers()
.map(e => e.location)
.some(e => Vector.distance(location, e) < 30)
)
return
if (!anyPlayerNear(location, this.dimensionType, 30)) return

location = Vector.add(location, { x: 0.5, y: 0.7, z: 0.5 })

Expand All @@ -50,12 +45,12 @@ export class FloatingText {
}

private create(location: Vector3) {
this.entity = world[this.dimensionId].spawnEntity(FloatingText.typeId, location)
this.entity = world[this.dimensionType].spawnEntity(FloatingText.typeId, location)
this.entity.setDynamicProperty(FloatingText.dynamicProperty, this.id)
}

private find() {
return world[this.dimensionId]
return world[this.dimensionType]
.getEntities({ type: FloatingText.typeId })
.find(e => e.getDynamicProperty(FloatingText.dynamicProperty) === this.id)
}
Expand Down
7 changes: 3 additions & 4 deletions src/lib/rpg/npc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { MinecraftEntityTypes } from '@minecraft/vanilla-data'
import { developersAreWarned } from 'lib/assets/text'
import { Core } from 'lib/extensions/core'
import { ConfigurableLocation, location } from 'lib/location'
import { anyPlayerNear } from 'lib/player-move'
import { Temporary } from 'lib/temporary'
import { createLogger } from 'lib/utils/logger'
import { Vector } from 'lib/vector'
import { Place } from './place'

export declare namespace Npc {
Expand Down Expand Up @@ -122,10 +122,9 @@ export class Npc {
Core.afterEvents.worldLoad.subscribe(() => {
system.runInterval(
() => {
const playerPositions = world.getAllPlayers().map(e => e.location)
this.npcs.forEach(npc => {
if (npc.entity) return // Entity already loaded
if (!playerPositions.some(e => npc.location.valid && Vector.distance(e, npc.location) < 10)) return
if (npc.entity || !npc.location.valid) return // Entity already loaded
if (!anyPlayerNear(npc.location, npc.dimensionId, 10)) return

const npcs = world[npc.dimensionId].getEntities({ type: Npc.type }).map(e => ({
entity: e,
Expand Down
11 changes: 11 additions & 0 deletions src/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ export const util = {
TerminalColors.r
: text.replace(/§(.)/g, '')
},

fromTerminalColorsToMinecraft(string: string) {
for (const [mc, terminal] of Object.entries(TerminalColors)) {
string = string.replaceAll(terminal, ${mc}`)
}
return string
},

format(args: unknown[]) {
return args.map(e => util.toTerminalColors(stringify(e))).join(' ')
},
}

/**
Expand Down
33 changes: 33 additions & 0 deletions src/lib/utils/logger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { util } from 'lib'
import { TEST_createPlayer } from 'test/utils'
import { createLogger } from './logger'

describe('Logger', () => {
it('should create logger that prints debug info', () => {
const mock = vi.fn()
const player = TEST_createPlayer()
const logger = createLogger('name')
vi.spyOn(console, 'debug').mockImplementation((...args) =>
mock(util.fromTerminalColorsToMinecraft(util.format(args))),
)

logger.debug('Message Common')
logger.debug('Message Common with arguments', [{ someObject: true }])
logger.debug`Message Template`
logger.player(player).debug('Player message Common')
logger.player(player).debug`Message Template`

expect(mock.mock.calls.flat().join('\n')).toMatchInlineSnapshot(`
"§9name§r Message Common§r
§9name§r Message Common with arguments§r [
{
someObject: §6true§r
}
]§r
§9name§r §fMessage Template§f§r
§9name §f§lTest player name§r §r Player message Common§r
§9name §f§lTest player name§r §r §fMessage Template§f§r"
`)
})
})

4 changes: 2 additions & 2 deletions src/lib/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { type Text, t } from '../text'

/** Creates new logger that will use name as prefix and will output info to the console */
export function createLogger(name: string) {
name = name.startsWith('§9') ? name : `§9${name} `
name = name.startsWith('§9') ? name : `§9${name}`
return {
debug: createLevel(name, 'debug', debug),
info: createLevel(name, 'info', info),
Expand Down Expand Up @@ -36,7 +36,7 @@ const error = t.error
function createLevel(name: string, level: 'debug' | 'error' | 'info' | 'warn', text: Text.Static | Text.Function) {
return (t: unknown, ...args: unknown[]) => {
if (isTemplateStringsArray(t)) {
console[level](name + (text as unknown as Text.Function)(t, ...args))
console[level](name, (text as unknown as Text.Function)(t, ...args))
} else {
console[level](name, t, ...args)
}
Expand Down
8 changes: 5 additions & 3 deletions src/modules/anticheat/whitelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ system.delay(() => {
}
})

if (whitelist.enabled) {
logger.info('To disable, use /scriptevent whitelist:disable')
}
system.delay(() => {
if (whitelist.enabled) {
logger.info('To disable, use /scriptevent whitelist:disable')
}
})

system.afterEvents.scriptEventReceive.subscribe(
event => {
Expand Down
2 changes: 1 addition & 1 deletion src/modules/features/break-place-outside-of-region.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ actionGuard((player, region, ctx) => {
const interactable = INTERACTABLE.includes(ctx.event.block.typeId) || interactableItem
if (interactable && region instanceof BaseRegion) return youCannot(player)

const scheduled = !!isScheduledToPlace(ctx.event.block, ctx.event.block.dimension.type)
const scheduled = isScheduledToPlace(ctx.event.block, ctx.event.block.dimension.type)
return scheduled || !interactable
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (!region && ctx.type === 'interactWithEntity') {
Expand Down
Loading

0 comments on commit 86b943b

Please sign in to comment.