Skip to content

Commit

Permalink
feat: portals
Browse files Browse the repository at this point in the history
  • Loading branch information
leaftail1880 committed Oct 28, 2024
1 parent e9714ba commit db921b4
Show file tree
Hide file tree
Showing 21 changed files with 366 additions and 142 deletions.
4 changes: 2 additions & 2 deletions src/lib/crates/crate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { lootTablePreview } from 'lib/rpg/loot-table-preview'
import { Place } from 'lib/rpg/place'
import { PlaceAction } from '../action'
import { ItemLoreSchema } from '../database/item-stack'
import { SafeLocation, ValidLocation, location } from '../location'
import { ConfigurableLocation, ValidLocation, location } from '../location'
import { t } from '../text'
import CrateLootAnimation from './animation'

Expand All @@ -24,7 +24,7 @@ export class Crate {

static typeIds: string[] = [MinecraftBlockTypes.EnderChest, MinecraftBlockTypes.Chest]

private location: SafeLocation<Vector3>
private location: ConfigurableLocation<Vector3>

private floatingText: FloatingText

Expand Down
30 changes: 23 additions & 7 deletions src/lib/extensions/on-screen-display.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Player, RawMessage, ScreenDisplay, TicksPerSecond, system, world } from '@minecraft/server'
import { Player, RawMessage, ScreenDisplay, system, world } from '@minecraft/server'
import { ScreenDisplaySymbol } from 'lib/extensions/player'
import { fromMsToTicks, fromTicksToMs } from 'lib/utils/ms'
import { WeakPlayerMap } from 'lib/weak-player-storage'

export enum ActionbarPriority {
Expand Down Expand Up @@ -116,10 +117,27 @@ export const ScreenDisplayOverride: ScreenDisplayOverrideTypes & ScreenDisplayOv
if (screenDisplay.priority > priority) return
else screenDisplay.priority = priority

// Workaround to fix overriding title by other displays
if (prefix !== $title) {
const titleDispaly = playerScreenDisplay[$title]

if (titleDispaly && titleDispaly.expires) {
const { expires } = titleDispaly
playerScreenDisplay.actions.push(player => {
// @ts-expect-error AAAAAAAAAAAAAAA
player[ScreenDisplaySymbol].setTitle(`${$title}${titleDispaly.value as string}`, {
subtitle: titleDispaly.subtitle,
...defaultOptions,
stayDuration: fromMsToTicks(expires - Date.now()),
})
})
}
}

if (typeof options !== 'undefined' && type === 'title') {
const totalTicks = options.fadeInDuration + options.fadeOutDuration + options.stayDuration
if (totalTicks !== -1) {
screenDisplay.expires = Date.now() + totalTicks * TicksPerSecond
screenDisplay.expires = Date.now() + fromTicksToMs(totalTicks)
} else options.stayDuration = 0
}

Expand All @@ -131,11 +149,9 @@ export const ScreenDisplayOverride: ScreenDisplayOverrideTypes & ScreenDisplayOv
}

playerScreenDisplay.actions.push(player => {
if (!player.isValid()) return

try {
const title = `${prefix === $tipPrefix ? `${prefix}${n}` : prefix}${message}`
if (typeof options === 'undefined') options = { ...defaultTitleOptions }
if (typeof options === 'undefined') options = { ...defaultOptions }

// @ts-expect-error AAAAAAAAAAAAAAA
player[ScreenDisplaySymbol].setTitle(title, options)
Expand Down Expand Up @@ -216,7 +232,7 @@ system.run(() => {
titles.delete(id)
}

if (player) {
if (player?.isValid()) {
// Take first action and execute it
event.actions.shift()?.(player)
}
Expand All @@ -227,6 +243,6 @@ system.run(() => {
}
},
'title set',
1,
0,
)
})
15 changes: 9 additions & 6 deletions src/lib/form/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
type SettingsConfigParsed,
type SettingsDatabase,
} from 'lib/settings'
import { MaybeRawText } from 'lib/text'
import { rawTextToString } from 'lib/utils/lang'
import { stringSimilarity } from '../search'
import { util } from '../util'
import { ActionForm } from './action'
Expand All @@ -20,13 +22,13 @@ export declare namespace ArrayForm {
filters: F,
form: ActionForm,
back: VoidFunction,
) => [text: string, callback: NewFormCallback] | false
) => readonly [text: MaybeRawText, callback: NewFormCallback] | false
type Sort<T, F> = (array: T[], filters: F) => T[]
type AddCustomButtons<TH> = (this: TH, form: ActionForm, back: VoidFunction) => void

interface Options<T, C extends SettingsConfig, F extends SettingsConfigParsed<C> = SettingsConfigParsed<C>> {
filters: C
description?: Text
description?: MaybeRawText
button?: Button<T, F>
sort?: Sort<T, F>
addCustomButtonBeforeArray?: AddCustomButtons<this>
Expand All @@ -52,7 +54,7 @@ export class ArrayForm<
private array: readonly T[],
) {}

description(text: Text) {
description(text?: MaybeRawText) {
this.config.description = text
return this
}
Expand Down Expand Up @@ -100,7 +102,7 @@ export class ArrayForm<
const args = [filtersDatabase, filters, searchQuery] as const
const selfback = () => this.show(player, fromPage, ...args)
const paginator = util.paginate(
this.getSorted(filters, searchQuery, selfback),
this.getSorted(player, filters, searchQuery, selfback),
this.config.itemsPerPage,
fromPage,
this.config.minItemsForFilters,
Expand Down Expand Up @@ -199,7 +201,7 @@ export class ArrayForm<
}
}

private getSorted(filters: F, searchQuery = '', back: VoidFunction) {
private getSorted(player: Player, filters: F, searchQuery = '', back: VoidFunction) {
if (!this.config.button) throw new TypeError('No button modifier!')
if (searchQuery) {
// Search query overrides sort option
Expand All @@ -209,7 +211,8 @@ export class ArrayForm<
const button = this.config.button(item, filters, empty, back)

if (button) {
sorted.push({ button, search: stringSimilarity(searchQuery, button[0]), item })
const buttonText = typeof button[0] === 'string' ? button[0] : rawTextToString(button[0], player.lang)
sorted.push({ button, search: stringSimilarity(searchQuery, buttonText), item })
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/lib/form/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ModalFormData,
ModalFormResponse,
} from '@minecraft/server-ui'
import { WeakPlayerSet } from 'lib/weak-player-storage'
import { MessageForm } from './message'

interface BaseForm {
Expand Down Expand Up @@ -98,3 +99,15 @@ export const BUTTON = {
'search': 'textures/ui/custom/search',
'settings': 'textures/ui/custom/settings',
}

export function debounceMenu<T extends (player: Player, ...args: any[]) => Promise<any>>(menu: T): T {
const sent = new WeakPlayerSet()

return (async (player: Player, ...args) => {
if (sent.has(player)) return

sent.add(player)
await menu(player, ...(args as Parameters<T>))
system.runTimeout(() => sent.delete(player), 'debounceMenu reset', 40)
}) as T
}
4 changes: 2 additions & 2 deletions src/lib/game-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import {
} from '@minecraft/server'
import { MinecraftCameraPresetsTypes } from '@minecraft/vanilla-data'
import { dedupe } from 'lib/dedupe'
import { SafeLocation } from 'lib/location'
import { ConfigurableLocation } from 'lib/location'
import { Vector } from 'lib/vector'
import { PersistentSet } from './database/persistent-set'
import { getRole } from './roles'

/** Represents location in the specific dimension */
export interface LocationInDimension {
/** Location of the place */
location: Vector3 | SafeLocation<Vector3>
location: Vector3 | ConfigurableLocation<Vector3>
/** Dimension of the location */
dimensionId: Dimensions
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/lib.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ declare module '@minecraft/server' {
position?: number[]
stage?: number
}
unlockedPortals?: string[]
}
}

Expand Down
15 changes: 12 additions & 3 deletions src/lib/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type InvalidLocation<T extends Vector3> = {
valid: false
} & LocationCommon<T>

export type SafeLocation<T extends Vector3> = InvalidLocation<T> | ValidLocation<T>
export type ConfigurableLocation<T extends Vector3> = InvalidLocation<T> | ValidLocation<T>

class Location<T extends Vector3> {
/**
Expand Down Expand Up @@ -112,15 +112,24 @@ class Location<T extends Vector3> {
}
}

class LocationWithRotation extends Location<Vector3 & { xRot: number; yRot: number }> {
export type Vector3Rotation = Vector3 & {
xRot: number
yRot: number
}

class LocationWithRotation extends Location<Vector3Rotation> {
protected locationFormat = { x: 0, y: 0, z: 0, xRot: 0, yRot: 0 }

protected get teleportOptions(): TeleportOptions {
return { rotation: { x: this.location.xRot, y: this.location.yRot } }
}
}

class LocationWithRadius extends Location<Vector3 & { radius: number }> {
export type Vector3Radius = Vector3 & {
radius: number
}

class LocationWithRadius extends Location<Vector3Radius> {
protected locationFormat = { x: 0, y: 0, z: 0, radius: 0 }
}

Expand Down
33 changes: 20 additions & 13 deletions src/lib/portals.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { CameraFadeOptions, Player } from '@minecraft/server'
import { CameraFadeOptions, Player, TicksPerSecond } from '@minecraft/server'
import { LockAction, LockActionOptions, PlaceAction } from 'lib/action'
import { hexToRgb } from 'lib/util'
import { Vector } from 'lib/vector'
import { Core } from './extensions/core'

export class Portal {
static canTeleport(player: Player, lockActionOptions?: Parameters<(typeof LockAction)['locked']>[1]) {
return LockAction.locked(player, lockActionOptions)
return !LockAction.locked(player, lockActionOptions)
}

static showHudTitle(player: Player, place?: string) {
player.onScreenDisplay.setHudTitle(place ?? Core.name, {
fadeInDuration: 0,
stayDuration: 100,
fadeOutDuration: 0,
subtitle: '§2Перемещение...',
})
static showHudTitle(player: Player, place?: string, time = 5) {
if (place !== '') {
player.onScreenDisplay.setHudTitle(place ?? Core.name, {
fadeInDuration: 0,
stayDuration: time * TicksPerSecond,
fadeOutDuration: 0,
subtitle: '§2Перемещение...',
priority: 100,
})
}
}

private static readonly fadeOptions: CameraFadeOptions = {
Expand All @@ -30,17 +33,21 @@ export class Portal {
static teleport(
player: Player,
to: Vector3,
options: { lockAction?: LockActionOptions; fadeScreen?: boolean; title?: string } = {},
{
fadeScreen = true,
lockAction,
title,
}: { lockAction?: LockActionOptions; fadeScreen?: boolean; title?: string } = {},
updateHud?: VoidFunction,
) {
if (!this.canTeleport(player, options.lockAction)) return
if (!this.canTeleport(player, lockAction)) return

if (options.fadeScreen) this.fadeScreen(player)
if (fadeScreen) this.fadeScreen(player)
player.teleport(to)

updateHud?.()

this.showHudTitle(player, options.title)
this.showHudTitle(player, title)
}

static portals = new Map<string, Portal>()
Expand Down
6 changes: 3 additions & 3 deletions src/lib/rpg/boss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import { Entity, Player, system, world } from '@minecraft/server'
import { MinecraftEntityTypes } from '@minecraft/vanilla-data'
import { forceAllowSpawnInRegion } from 'lib/region/index'
import { table } from 'lib/database/abstract'
import { EventLoaderWithArg, EventSignal } from 'lib/event-signal'
import { Core } from 'lib/extensions/core'
import { isChunkUnloaded, LocationInDimension } from 'lib/game-utils'
import { location, SafeLocation } from 'lib/location'
import { ConfigurableLocation, location } from 'lib/location'
import { SphereArea } from 'lib/region/areas/sphere'
import { forceAllowSpawnInRegion } from 'lib/region/index'
import { BossArenaRegion } from 'lib/region/kinds/boss-arena'
import { LootTable } from 'lib/rpg/loot-table'
import { givePlayerMoneyAndXp } from 'lib/rpg/money'
Expand Down Expand Up @@ -70,7 +70,7 @@ export class Boss {

region?: BossArenaRegion

location: SafeLocation<Vector3>
location: ConfigurableLocation<Vector3>

static {
world.afterEvents.entityDie.subscribe(event => {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/rpg/npc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { MinecraftEntityTypes } from '@minecraft/vanilla-data'
import { developersAreWarned } from 'lib/assets/text'
import { Core } from 'lib/extensions/core'
import { location, SafeLocation } from 'lib/location'
import { ConfigurableLocation, location } from 'lib/location'
import { Temporary } from 'lib/temporary'
import { createLogger } from 'lib/utils/logger'
import { Vector } from 'lib/vector'
Expand All @@ -27,7 +27,7 @@ export class Npc {

static npcs: Npc[] = []

location: SafeLocation<Vector3>
location: ConfigurableLocation<Vector3>

private entity: Entity | undefined

Expand Down
4 changes: 2 additions & 2 deletions src/lib/shop/npc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Player, system } from '@minecraft/server'
import { PlaceAction } from 'lib/action'
import { Cooldown } from 'lib/cooldown'
import { SafeLocation, location } from 'lib/location'
import { ConfigurableLocation, location } from 'lib/location'
import { Npc } from 'lib/rpg/npc'
import { Place } from 'lib/rpg/place'
import { Shop } from './shop'
Expand Down Expand Up @@ -34,7 +34,7 @@ export class ShopNpc {
export class ShopBlock {
shop: Shop

location: SafeLocation<Vector3>
location: ConfigurableLocation<Vector3>

private cooldown = new Cooldown(1000, false)

Expand Down
8 changes: 4 additions & 4 deletions src/lib/shop/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type ProductOnBuy = (
successBuyText: MaybeRawText,
) => void | false

type ProductBackForm = (message?: MaybeRawText) => void
type BackFormWithMessage = (message?: MaybeRawText) => void

type OnProductCreate<T> = (product: Product) => T

Expand All @@ -23,12 +23,12 @@ export class Product<T extends Cost = any> {
let onCreateCallback: OnProductCreate<any> = s => s

return {
creator<P>(onCreate: OnProductCreate<P>, form: ProductBackForm) {
creator<P>(onCreate: OnProductCreate<P>, form: BackFormWithMessage) {
onCreateCallback = onCreate
return this.form<P>(form)
},
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
form: <P = Product<C>>(backForm: ProductBackForm) => ({
form: <P = Product<C>>(backForm: BackFormWithMessage) => ({
player: (player: Player) => ({
name: (name: ProductName) => ({
cost: (cost: C) => ({
Expand All @@ -49,7 +49,7 @@ export class Product<T extends Cost = any> {
private player: Player,
private onProductBuy: ProductOnBuy,
/** Texture represnting product */
private backForm: ProductBackForm,
private backForm: BackFormWithMessage,
) {}

private sell = false
Expand Down
2 changes: 1 addition & 1 deletion src/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export function isKeyof<O extends Record<Key, unknown>>(key: Key, object: O): ke
export function hexToRgb(hex: `#${string}`): RGB {
const rgb = hex
// Normalize hex
.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (_, r, g, b) => `${r}${r}${g}${g}${b}${b}`)
.replace(/^#?([a-f\d]{6})$/i, (_, r: string) => r)
.match(/.{2}/g)
?.map(x => parseInt(x, 16) / 256)

Expand Down
Loading

0 comments on commit db921b4

Please sign in to comment.