diff --git a/src/lib/assets/generated.ts b/src/lib/assets/generated.ts index b1c0dd6c..fba6e873 100644 --- a/src/lib/assets/generated.ts +++ b/src/lib/assets/generated.ts @@ -321,7 +321,6 @@ export const playerJson = { }, "deals_damage": false }, - { "on_damage": { "filters": { diff --git a/src/lib/command/utils.ts b/src/lib/command/utils.ts index 80e4859d..4929f230 100644 --- a/src/lib/command/utils.ts +++ b/src/lib/command/utils.ts @@ -49,7 +49,7 @@ export function commandNotFound(player: Player, command: string): void { suggestCommand(player, command) player.tell('§cСписок всех доступных вам команд: §f.help') - Command.logger.player(player).error`Unknown command: ${command}` + Command.logger.player(player).warn`Unknown command: ${command}` } /** diff --git a/src/lib/quest/step.ts b/src/lib/quest/step.ts index fbdf7ee5..93cdb462 100644 --- a/src/lib/quest/step.ts +++ b/src/lib/quest/step.ts @@ -212,8 +212,8 @@ export abstract class QS extends Temporary { return this.proxies.system } - subscribe>(eventSignal: T, callback: EventSignal.Callback) { - eventSignal.subscribe(callback) + subscribe>(eventSignal: T, callback: EventSignal.Callback, position?: number) { + eventSignal.subscribe(callback, position) this.cleaners.push(() => eventSignal.unsubscribe(callback)) return callback } diff --git a/src/lib/region/config.ts b/src/lib/region/config.ts index 559fd246..906970d2 100644 --- a/src/lib/region/config.ts +++ b/src/lib/region/config.ts @@ -4,12 +4,12 @@ import { CustomEntityTypes } from 'lib/assets/config' /** All doors and switches in minecraft */ export const DOORS = BlockTypes.getAll() - .filter(e => e.id.endsWith('_door')) + .filter(e => e.id.endsWith('door')) .map(e => e.id) /** All doors and switches in minecraft */ export const TRAPDOORS = BlockTypes.getAll() - .filter(e => e.id.endsWith('_trapdoor')) + .filter(e => e.id.endsWith('trapdoor')) .map(e => e.id) /** All doors and switches in minecraft */ @@ -35,7 +35,12 @@ export const BLOCK_CONTAINERS = [ ] /** With this entities player can interact (e.g. npc, custom buttons, etc) */ -export const INTERACTABLE_ENTITIES: string[] = [MinecraftEntityTypes.Npc] +export const INTERACTABLE_ENTITIES: string[] = [ + MinecraftEntityTypes.Npc, + MinecraftEntityTypes.Horse, + MinecraftEntityTypes.Mule, + MinecraftEntityTypes.Donkey, +] /** * System entities like database, floating text, sit and other which are not affected by health bar display, region diff --git a/src/lib/region/index.ts b/src/lib/region/index.ts index 67eca89f..81ee537b 100644 --- a/src/lib/region/index.ts +++ b/src/lib/region/index.ts @@ -46,8 +46,19 @@ export enum ActionGuardOrder { Anticheat = 10, Feature = 9, Permission = 8, + Lowest = 7, } +actionGuard((player, region, ctx) => { + if (ctx.type !== 'interactWithBlock') return + const { event } = ctx + + if (region?.permissions.switches && SWITCHES.includes(event.block.typeId)) return true // allow + if (region?.permissions.doors && DOORS.includes(event.block.typeId)) return true // allow + if (region?.permissions.trapdoors && TRAPDOORS.includes(event.block.typeId)) return true // allow + if (region?.permissions.openContainers && BLOCK_CONTAINERS.includes(event.block.typeId)) return true // allow +}, ActionGuardOrder.Lowest) + const allowed: InteractionAllowed = (player, region, context, regions) => { if (isBuilding(player)) return true @@ -70,6 +81,7 @@ const allowed: InteractionAllowed = (player, region, context, regions) => { } } + // for (const [fn] of EventSignal.sortSubscribers(ACTION_GUARD)) { const result = fn(player, region, context, regions) if (typeof result !== 'undefined') return result @@ -87,11 +99,6 @@ world.beforeEvents.playerInteractWithBlock.subscribe(event => { const { regions, region } = getRegions(event.block, event.player.dimension.type) if (allowed(event.player, region, { type: 'interactWithBlock', event }, regions)) return - if (region?.permissions.switches && SWITCHES.includes(event.block.typeId)) return // allow - if (region?.permissions.doors && DOORS.includes(event.block.typeId)) return // allow - if (region?.permissions.trapdoors && TRAPDOORS.includes(event.block.typeId)) return // allow - if (region?.permissions.openContainers && BLOCK_CONTAINERS.includes(event.block.typeId)) return // allow - event.cancel = true }) diff --git a/src/modules/commands/wipe.ts b/src/modules/commands/wipe.ts index 0948e87c..a624b56c 100644 --- a/src/modules/commands/wipe.ts +++ b/src/modules/commands/wipe.ts @@ -3,6 +3,7 @@ import { Airdrop, Compass, Join, prompt } from 'lib' import { Quest } from 'lib/quest' import { Anarchy } from 'modules/places/anarchy/anarchy' import { Spawn } from 'modules/places/spawn' +import { enterNewbieMode } from 'modules/pvp/newbie' import { updateBuilderStatus } from 'modules/world-edit/builder' new Command('wipe') @@ -31,7 +32,8 @@ new Command('wipe') Spawn.portal?.teleport(ctx.player) ctx.player.scores.money = 0 ctx.player.scores.anarchyOnlineTime = 0 - ctx.player.database.survival.newbie = 1 + + enterNewbieMode(ctx.player) for (let i = 0; i <= 26; i++) { ctx.player.runCommand(`replaceitem entity @s slot.enderchest ${i} air`) diff --git a/src/modules/features/axe.ts b/src/modules/features/axe.ts deleted file mode 100644 index 604ae6fe..00000000 --- a/src/modules/features/axe.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { world } from '@minecraft/server' - -import { MinecraftBlockTypes } from '@minecraft/vanilla-data' -import { Region, ms } from 'lib' -import { isBuilding } from 'lib/game-utils' -import { actionGuard, ActionGuardOrder } from 'lib/region/index' -import { scheduleBlockPlace } from 'modules/survival/scheduled-block-place' - -export class Axe { - static breaks: string[] = Object.entries(MinecraftBlockTypes) - .filter(e => /log/i.exec(e[0])) - .map(e => e[1]) - - static allowBreakInRegions: Region[] = [] -} - -actionGuard((_, region, ctx) => { - if ( - ctx.type === 'break' && - region && - Axe.allowBreakInRegions.includes(region) && - Axe.breaks.includes(ctx.event.block.typeId) - ) { - return true - } -}, ActionGuardOrder.Permission) - -world.afterEvents.playerBreakBlock.subscribe(({ block, brokenBlockPermutation: broken, dimension, player }) => { - if (!Axe.breaks.includes(broken.type.id)) return - if (isBuilding(player)) return - - scheduleBlockPlace({ - dimension: dimension.type, - location: block.location, - typeId: broken.type.id, - states: broken.getAllStates(), - restoreTime: ms.from('min', 1), - }) -}) diff --git a/src/modules/features/break-place-outside-of-region.ts b/src/modules/features/break-place-outside-of-region.ts index 7deafc3f..9ef632c6 100644 --- a/src/modules/features/break-place-outside-of-region.ts +++ b/src/modules/features/break-place-outside-of-region.ts @@ -9,6 +9,7 @@ const INTERACTABLE = DOORS.concat(SWITCHES, TRAPDOORS, BLOCK_CONTAINERS) const INTERACTABLEITEMS = Object.values(MinecraftItemTypes) .filter(e => e.includes('axe')) .concat(MinecraftItemTypes.FlintAndSteel) as (undefined | string)[] + const youCannot = (player: Player) => { if (textCooldown.isExpired(player)) player.fail(`Вы не можете ломать непоставленные блоки вне баз, шахт или других зон добычи`) @@ -19,6 +20,7 @@ const textCooldown = new Cooldown(ms.from('sec', 2), false) actionGuard((player, region, ctx) => { if (ctx.type === 'place') { if (region instanceof BaseRegion) return youCannot(player) + else if (region) return scheduleBlockPlace({ dimension: ctx.event.block.dimension.type, @@ -29,11 +31,14 @@ actionGuard((player, region, ctx) => { }) return true } else if (ctx.type === 'break') { + if (region) return + // Break if (!isScheduledToPlace(ctx.event.block, ctx.event.block.dimension.type)) return youCannot(player) else return true } else if (ctx.type === 'interactWithBlock') { - const interactable = - INTERACTABLE.includes(ctx.event.block.typeId) || INTERACTABLEITEMS.includes(ctx.event.itemStack?.typeId) + // Interact + const interactableItem = INTERACTABLEITEMS.includes(ctx.event.itemStack?.typeId) + 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) diff --git a/src/modules/places/minearea/minearea-region.ts b/src/modules/places/minearea/minearea-region.ts index 6146fe17..73b7555b 100644 --- a/src/modules/places/minearea/minearea-region.ts +++ b/src/modules/places/minearea/minearea-region.ts @@ -101,7 +101,7 @@ actionGuard((player, region, ctx) => { return true } } -}, ActionGuardOrder.Permission) +}, ActionGuardOrder.Lowest) onScheduledBlockPlace.subscribe(({ block, schedules, schedule }) => { const regions = Region.nearestRegions(block, block.dimension.type) diff --git a/src/modules/places/mineshaft/algo.ts b/src/modules/places/mineshaft/algo.ts index d4b9134c..c8505372 100644 --- a/src/modules/places/mineshaft/algo.ts +++ b/src/modules/places/mineshaft/algo.ts @@ -57,7 +57,10 @@ export function placeOre(brokenLocation: Block, brokenTypeId: string, dimension: brokenOre?.isDeepslate ?? (brokenTypeId === MinecraftBlockTypes.Deepslate || brokenLocation.y < -3) const place = (block: Block, oreTypeId: string) => { - if (block.isValid() && !block.isAir && oreTypeId) block.setType(oreTypeId) + if (block.isValid() && !block.isAir && oreTypeId) { + block.setType(oreTypeId) + return true + } else return false } for (const [action] of EventSignal.sortSubscribers(OrePlace)) { @@ -72,7 +75,7 @@ interface OrePlaceEvent { brokenOre: undefined | OreEntry brokenLocation: Block possibleBlocks: Block[] - place: (block: Block, oreTypeId: string) => void + place: (block: Block, oreTypeId: string) => boolean } export const OrePlace = new EventSignal() diff --git a/src/modules/pvp/newbie.ts b/src/modules/pvp/newbie.ts index 3a6d4653..5b2878cc 100644 --- a/src/modules/pvp/newbie.ts +++ b/src/modules/pvp/newbie.ts @@ -45,7 +45,7 @@ export function exitNewbieMode(player: Player, reason: Text) { logger.player(player).info`Exited newbie mode because ${reason}` } -function enterNewbieMode(player: Player) { +export function enterNewbieMode(player: Player) { player.database.survival.newbie = 1 player.scores.anarchyOnlineTime = 0 player.setProperty(property, true) diff --git a/src/modules/quests/learning/learning.ts b/src/modules/quests/learning/learning.ts index cf4687ff..ccade5e8 100644 --- a/src/modules/quests/learning/learning.ts +++ b/src/modules/quests/learning/learning.ts @@ -19,6 +19,7 @@ import { stoneQuarryInvestigating } from 'modules/places/stone-quarry/quests/inv import { StoneQuarry } from 'modules/places/stone-quarry/stone-quarry' import { VillageOfMiners } from 'modules/places/village-of-miners/village-of-miners' import airdropTable from './airdrop' +import { WeakPlayerMap } from 'lib/weak-player-storage' // TODO Write second quests for investigating other places // TODO Add catscenes @@ -159,29 +160,32 @@ class Learning { .isItem(item => item.typeId === MinecraftItemTypes.StonePickaxe) .place(crafting) - q.counter(i => (i === 0 ? `§6Вновь вскопайте камень в шахте §f0/1` : `§6Добыто железной руды: §f${i}/5`), 6) + q.counter(i => (i === 0 ? `§6Вновь вскопайте камень в шахте §f0/1` : `§6Добыто железной руды: §f${i - 1}/3`), 3 + 1) .description('Вернитесь в шахту и вскопайте камень. Кажется, за ним прячется железо!') .activate(ctx => { // Force iron ore generation - ctx.subscribe(OrePlace, ({ player, isDeepslate, possibleBlocks, place }) => { - if (player.id !== player.id) return false - - ctx.db ??= { count: ctx.value } - ;(ctx.db as { iron?: number }).iron ??= 0 - - if ('iron' in ctx.db && typeof ctx.db.iron === 'number') { - if (ctx.db.iron >= ctx.end) return false - - for (const block of possibleBlocks) { - if (ctx.db.iron >= ctx.end) break - place(block, isDeepslate ? b.DeepslateIronOre : b.IronOre) - ctx.db.iron++ - } - return true - } - - return false - }) + ctx.subscribe( + OrePlace, + ({ player, isDeepslate, possibleBlocks, place }) => { + if (player.id !== player.id) return false + + ctx.db ??= { count: ctx.value } + ;(ctx.db as { iron?: number }).iron ??= 0 + + if ('iron' in ctx.db && typeof ctx.db.iron === 'number') { + if (ctx.db.iron >= ctx.end - 1) return true + + for (const block of possibleBlocks) { + if (ctx.db.iron >= ctx.end - 1) break + if (place(block, isDeepslate ? b.DeepslateIronOre : b.IronOre)) { + ctx.db.iron++ + } + } + return true + } else return false + }, + 10, + ) // Check if it breaks ctx.world.afterEvents.playerBreakBlock.subscribe( @@ -234,9 +238,17 @@ class Learning { miner = new Jeweler(this.quest.group, this.quest.group.point('miner').name('Шахтер')) + blockedOre = new WeakPlayerMap() + constructor() { actionGuard((player, region, ctx) => { if (ctx.type !== 'break') return + if (ctx.event.dimension.type !== 'overworld') return + if ([...this.blockedOre.values()].flat().includes(Vector.string(ctx.event.block))) { + player.fail('Вы не можете ломать руду новичка.') + return false + } + if (region !== VillageOfMiners.safeArea) return if (this.quest.getPlayerStep(player)) { const isOre = diff --git a/src/modules/survival/death-quest-and-gravestone.ts b/src/modules/survival/death-quest-and-gravestone.ts index 11337fd7..202dd686 100644 --- a/src/modules/survival/death-quest-and-gravestone.ts +++ b/src/modules/survival/death-quest-and-gravestone.ts @@ -1,5 +1,5 @@ import { Player, system, world } from '@minecraft/server' -import { Cooldown, Settings, Vector, actionGuard, inventoryIsEmpty, ms } from 'lib' +import { actionGuard, Cooldown, inventoryIsEmpty, ms, Settings, Vector } from 'lib' import { CustomEntityTypes } from 'lib/assets/config' import { Quest } from 'lib/quest/quest' import { ActionGuardOrder, ALLOW_SPAWN_PROP } from 'lib/region' @@ -119,14 +119,17 @@ actionGuard((player, _, ctx) => { if (typeof owner !== 'string') return true if (owner === player.id) return true - if (Player.database[owner].survival.newbie) return false + if (Player.database[owner].survival.newbie) { + ctx.event.player.fail('Вы не можете залутать могилу новичка!') + return false + } }, ActionGuardOrder.Feature) const quest = new Quest('restoreInventory', 'Вернуть вещи', 'Верните вещи после смерти!', (q, player) => { const { deadAt } = player.database.survival if (!deadAt) return q.failed('Ваше место смерти потерялось!') - q.dynamic(`§d!!!`) + q.dynamic(Vector.string(deadAt, true)) .description( `Верните свои вещи${ player.database.survival.newbie ? ', никто кроме вас их забрать не может' : ''