Skip to content

Commit f85fc5c

Browse files
committed
add spawners to custom dungeons
1 parent 629a9be commit f85fc5c

File tree

2 files changed

+153
-51
lines changed

2 files changed

+153
-51
lines changed

src/modules/places/dungeons/custom-dungeon.ts

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export class CustomDungeonRegion extends DungeonRegion {
2929
override ldb: CustomDungeonRegionDatabase = {
3030
chestLoot: [],
3131
chests: {},
32+
spawnerCooldowns: {},
3233
structureId: '',
3334
name: '',
3435
rotation: StructureRotation.None,
@@ -46,6 +47,8 @@ export class CustomDungeonRegion extends DungeonRegion {
4647
protected override structureFile: StructureFile = {
4748
chestPositions: [],
4849
enderChestPositions: [],
50+
structureVoidPoisitions: [],
51+
shulkers: [],
4952
size: { x: 10, y: 10, z: 10 },
5053
}
5154

@@ -72,60 +75,61 @@ export class CustomDungeonRegion extends DungeonRegion {
7275
this.createChest(location, lootTable, restoreTime)
7376
this.save()
7477
}
75-
}
76-
registerSaveableRegion('customDungeon', CustomDungeonRegion)
77-
registerRegionType(noI18n`Кастомный данж`, CustomDungeonRegion, false, true)
7878

79-
function eventHelper(player: Player, block: Block) {
80-
if (!is(player.id, 'techAdmin')) return false
79+
static {
80+
world.afterEvents.playerPlaceBlock.subscribe(({ player, block }) => {
81+
if (block.typeId !== MinecraftBlockTypes.Chest) return false
8182

82-
const region = CustomDungeonRegion.getAt(block)
83-
if (!region) return false
83+
const region = getDungeonRegionForTechAdmin(player, block)
84+
if (!region) return
8485

85-
return region
86-
}
87-
88-
world.afterEvents.playerPlaceBlock.subscribe(({ player, block }) => {
89-
if (block.typeId !== MinecraftBlockTypes.Chest) return false
86+
editChest(player, block, region)
87+
})
9088

91-
const region = eventHelper(player, block)
92-
if (!region) return
89+
world.afterEvents.playerBreakBlock.subscribe(({ player, block, brokenBlockPermutation }) => {
90+
if (brokenBlockPermutation.type.id !== MinecraftBlockTypes.Chest) return false
9391

94-
editChest(player, block, region)
95-
})
92+
const region = getDungeonRegionForTechAdmin(player, block)
93+
if (!region) return
9694

97-
world.afterEvents.playerBreakBlock.subscribe(({ player, block, brokenBlockPermutation }) => {
98-
if (brokenBlockPermutation.type.id !== MinecraftBlockTypes.Chest) return false
95+
const location = block.location
96+
const chest = getChest(region, location)
97+
if (chest) {
98+
region.ldb.chestLoot = region.ldb.chestLoot.filter(e => e !== chest)
99+
region.removeChest(location)
100+
player.success(i18n`Removed chest with loot ${chest.loot} and restore time ${chest.restoreTime / 60_000}min`)
101+
}
102+
})
99103

100-
const region = eventHelper(player, block)
101-
if (!region) return
104+
world.beforeEvents.playerInteractWithBlock.subscribe(event => {
105+
const { player, block } = event
106+
if (player.getGameMode() !== GameMode.Creative) return
107+
if (!player.isSneaking) return
102108

103-
const location = block.location
104-
const chest = getChest(region, location)
105-
if (chest) {
106-
region.ldb.chestLoot = region.ldb.chestLoot.filter(e => e !== chest)
107-
region.chests = region.chests.filter(e => !Vec.equals(e.location, location))
109+
const region = CustomDungeonRegion.getAt(block)
110+
if (!region) return
108111

109-
player.success(i18n`Removed chest with loot ${chest.loot} and restore time ${chest.restoreTime / 60_000}min`)
112+
const chest = getChest(region, block.location)
113+
if (chest) {
114+
event.cancel
115+
system.delay(() => {
116+
editChest(player, block.location, region, chest)
117+
})
118+
}
119+
})
110120
}
111-
})
121+
}
122+
registerSaveableRegion('customDungeon', CustomDungeonRegion)
123+
registerRegionType(noI18n`Кастомный данж`, CustomDungeonRegion, false, true)
112124

113-
world.beforeEvents.playerInteractWithBlock.subscribe(event => {
114-
const { player, block } = event
115-
if (player.getGameMode() !== GameMode.Creative) return
116-
if (!player.isSneaking) return
125+
function getDungeonRegionForTechAdmin(player: Player, block: Block) {
126+
if (!is(player.id, 'techAdmin')) return false
117127

118128
const region = CustomDungeonRegion.getAt(block)
119-
if (!region) return
129+
if (!region) return false
120130

121-
const chest = getChest(region, block.location)
122-
if (chest) {
123-
event.cancel
124-
system.delay(() => {
125-
editChest(player, block.location, region, chest)
126-
})
127-
}
128-
})
131+
return region
132+
}
129133

130134
function editChest(
131135
player: Player,

src/modules/places/dungeons/dungeon.ts

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Player, StructureRotation, StructureSaveMode, system, world } from '@minecraft/server'
1+
import { EntityTypes, Player, StructureRotation, StructureSaveMode, system, world } from '@minecraft/server'
22
import { MinecraftBlockTypes } from '@minecraft/vanilla-data'
33
import {
44
ActionForm,
@@ -13,14 +13,17 @@ import {
1313
} from 'lib'
1414
import { StructureDungeonsId, StructureFile, structureFiles } from 'lib/assets/structures'
1515
import { i18n, noI18n } from 'lib/i18n/text'
16+
import { anyPlayerNear } from 'lib/player-move'
1617
import { Area } from 'lib/region/areas/area'
1718
import { SphereArea } from 'lib/region/areas/sphere'
1819
import { Region, RegionCreationOptions, RegionPermissions } from 'lib/region/kinds/region'
20+
import { createLogger } from 'lib/utils/logger'
1921
import { structureLikeRotate, structureLikeRotateRelative, toAbsolute, toRelative } from 'lib/utils/structure'
2022
import { Dungeon } from './loot'
2123

2224
export interface DungeonRegionDatabase extends JsonObject {
2325
chests: Record<string, number | null>
26+
spawnerCooldowns: Record<string, number>
2427
structureId: string
2528
rotation: StructureRotation
2629
terrainStructureId: string
@@ -39,9 +42,20 @@ export interface DungeonChest {
3942
location: Vector3
4043
}
4144

45+
export interface DungeonSpawner {
46+
id: string
47+
restoreTime: number
48+
location: Vector3
49+
entities: { typeId: string; amount: number }[]
50+
}
51+
4252
export class DungeonRegion extends Region {
53+
static logger = createLogger('DungeonRegion')
54+
4355
static dungeons: DungeonRegion[] = []
4456

57+
static oldChestLogPositions = new Set<string>()
58+
4559
static {
4660
system.runInterval(
4761
() => {
@@ -53,19 +67,34 @@ export class DungeonRegion extends Region {
5367
}
5468
}
5569

70+
for (const spawner of dungeon.spawners) {
71+
const cooldown = dungeon.ldb.spawnerCooldowns[spawner.id]
72+
if (!cooldown || Cooldown.isExpired(cooldown, spawner.restoreTime)) {
73+
dungeon.spawn(spawner)
74+
}
75+
}
76+
77+
// Old chest
5678
for (const chest of Object.keys(dungeon.ldb.chests)) {
5779
const vector = Vec.parse(chest)
5880
if (!vector) continue
5981

6082
if (dungeon.chests.find(e => Vec.equals(e.location, vector))) continue
6183

62-
// Old chest
63-
console.log('Found old chest', Vec.string(vector, true))
64-
try {
65-
dungeon.dimension.setBlockType(vector, MinecraftBlockTypes.Air)
66-
Reflect.deleteProperty(dungeon.ldb.chests, chest)
67-
dungeon.save()
68-
} catch (e) {}
84+
const oldChestPosition = Vec.string(vector, true)
85+
if (!this.oldChestLogPositions.has(oldChestPosition)) {
86+
this.logger.info('Found old chest', oldChestPosition, 'removing once there is any player nearby...')
87+
this.oldChestLogPositions.add(oldChestPosition)
88+
}
89+
90+
if (anyPlayerNear(vector, dungeon.dimensionType, 20)) {
91+
try {
92+
this.logger.info('Trying to remove old chest', oldChestPosition)
93+
dungeon.dimension.setBlockType(vector, MinecraftBlockTypes.Air)
94+
Reflect.deleteProperty(dungeon.ldb.chests, chest)
95+
dungeon.save()
96+
} catch (e) {}
97+
}
6998
}
7099
}
71100
},
@@ -82,6 +111,7 @@ export class DungeonRegion extends Region {
82111

83112
ldb: DungeonRegionDatabase = {
84113
chests: {},
114+
spawnerCooldowns: {},
85115
structureId: '',
86116
terrainStructureId: '',
87117
terrainStructurePosition: { x: 0, z: 0, y: 0 },
@@ -96,14 +126,24 @@ export class DungeonRegion extends Region {
96126

97127
protected configureDungeon(): void {
98128
if (!this.structureFile) return
99-
const { chestPositions, enderChestPositions } = this.structureFile
129+
const { chestPositions, enderChestPositions, shulkers } = this.structureFile
100130
const toRotated = (f: Vector3[]) => this.rotate(f.map(e => this.fromRelativeToAbsolute(e)))
101131

102132
const loot = Dungeon.loot[this.structureId] ?? Dungeon.defaultLoot
103133
for (const f of toRotated(chestPositions)) this.createChest(f, loot)
104134

105135
const powerfullLoot = Dungeon.powerfullLoot[this.structureId] ?? Dungeon.defaultLoot
106136
for (const f of toRotated(enderChestPositions)) this.createChest(f, powerfullLoot)
137+
138+
for (const { loc, inv } of shulkers) {
139+
const [rotated] = this.rotate([this.fromRelativeToAbsolute(loc)])
140+
if (!rotated) {
141+
console.warn('Not rotated')
142+
continue
143+
}
144+
145+
this.createSpawnerFromShulkerInventory(inv, rotated)
146+
}
107147
}
108148

109149
private rotate(vectors: Vector3[]) {
@@ -144,7 +184,7 @@ export class DungeonRegion extends Region {
144184
}
145185

146186
customFormButtons(form: ActionForm, player: Player): void {
147-
form.button(noI18n`Установить структуру`, () => {
187+
form.button(noI18n`Снова установить структуру`, () => {
148188
this.placeStructure()
149189
})
150190
}
@@ -223,7 +263,65 @@ export class DungeonRegion extends Region {
223263
allowedAllItem: true,
224264
}
225265

226-
chests: DungeonChest[] = []
266+
private spawners: DungeonSpawner[] = []
267+
268+
protected createSpawnerFromShulkerInventory(
269+
inv: { amount: number; typeId: string }[],
270+
rotated: Vector3,
271+
restoreTime?: number,
272+
) {
273+
const entities: DungeonSpawner['entities'] = []
274+
for (const value of inv) {
275+
const entityTypeId = /^(?:minecraft:)(.+)_spawn_egg$/.exec(value.typeId)?.[1]
276+
if (!entityTypeId) {
277+
console.warn('No entity type id for', value.typeId)
278+
continue
279+
}
280+
281+
entities.push({ typeId: entityTypeId, amount: value.amount })
282+
}
283+
this.createSpawner(rotated, entities, restoreTime)
284+
}
285+
286+
protected createSpawner(location: Vector3, entities: DungeonSpawner['entities'], restoreTime = ms.from('min', 20)) {
287+
this.spawners.push({
288+
id: Vec.string(location),
289+
location,
290+
entities,
291+
restoreTime,
292+
})
293+
}
294+
295+
private static invalidSpawnerTypeIds = new Set<string>()
296+
297+
private spawn(spawner: DungeonSpawner) {
298+
if (!anyPlayerNear(spawner.location, this.dimensionType, 20)) return
299+
300+
for (const { typeId, amount } of spawner.entities) {
301+
const type = EntityTypes.get<string>(typeId)
302+
if (!type) {
303+
if (!DungeonRegion.invalidSpawnerTypeIds.has(typeId)) {
304+
console.warn('Dungeon spawner invalid typeId', typeId, spawner)
305+
DungeonRegion.invalidSpawnerTypeIds.add(typeId)
306+
}
307+
continue
308+
}
309+
310+
const entities = this.dimension.getEntities({ location: spawner.location, maxDistance: 20 })
311+
if (entities.length < amount) {
312+
const toSpawn = amount - entities.length
313+
for (let i = 0; i < toSpawn; i++) {
314+
this.dimension.spawnEntity(type, spawner.location)
315+
}
316+
}
317+
}
318+
}
319+
320+
private chests: DungeonChest[] = []
321+
322+
protected removeChest(location: Vector3) {
323+
this.chests = this.chests.filter(e => !Vec.equals(e.location, location))
324+
}
227325

228326
protected createChest(location: Vector3, loot: LootTable, restoreTime = ms.from('min', 20)) {
229327
// console.log(

0 commit comments

Comments
 (0)