1use glam::IVec3;
7use steel_registry::structure::StructureData;
8use steel_utils::random::Random;
9use steel_utils::random::legacy_random::LegacyRandom;
10use steel_utils::{BlockPos, BoundingBox, Direction, Identifier};
11
12use crate::structure::{
13 GenerationStub, ProceduralPieceData, Structure, StructureGenerationContext, StructurePiece,
14 StructurePiecePayload, make_oriented_piece_bounding_box, random_horizontal_direction,
15};
16
17const WIDTH: i32 = 58;
18const HEIGHT: i32 = 23;
19const DEPTH: i32 = 58;
20const MIN_Y: i32 = 39;
21const BIOME_RANGE_CHECK: i32 = 29;
22const GRID_WIDTH: i32 = 5;
23const GRID_DEPTH: i32 = 5;
24const GRID_FLOOR_COUNT: i32 = GRID_WIDTH * GRID_DEPTH;
25const GRID_SIZE: usize = 75;
26const GRIDROOM_SOURCE_INDEX: i32 = get_room_index(2, 0, 0);
27const GRIDROOM_TOP_CONNECT_INDEX: i32 = get_room_index(2, 2, 0);
28const GRIDROOM_LEFTWING_CONNECT_INDEX: i32 = get_room_index(0, 1, 0);
29const GRIDROOM_RIGHTWING_CONNECT_INDEX: i32 = get_room_index(4, 1, 0);
30const LEFTWING_INDEX: i32 = 1001;
31const RIGHTWING_INDEX: i32 = 1002;
32const PENTHOUSE_INDEX: i32 = 1003;
33
34const SURROUNDING_BIOMES: &[&str] = &[
36 "deep_frozen_ocean",
37 "deep_cold_ocean",
38 "deep_ocean",
39 "deep_lukewarm_ocean",
40 "frozen_ocean",
41 "cold_ocean",
42 "ocean",
43 "lukewarm_ocean",
44 "warm_ocean",
45 "river",
46 "frozen_river",
47];
48
49#[derive(Debug, Clone, PartialEq)]
51pub struct OceanMonumentPieceData {
52 pub child_pieces: Vec<OceanMonumentChildPiece>,
54}
55
56#[derive(Debug, Clone, PartialEq)]
58pub struct OceanMonumentChildPiece {
59 pub bounding_box: BoundingBox,
61 pub kind: OceanMonumentChildPieceKind,
63}
64
65#[derive(Debug, Clone, PartialEq)]
67pub enum OceanMonumentChildPieceKind {
68 EntryRoom {
70 room: OceanMonumentRoomData,
72 },
73 CoreRoom,
75 DoubleXRoom {
77 west: OceanMonumentRoomData,
79 east: OceanMonumentRoomData,
81 },
82 DoubleXYRoom {
84 west: OceanMonumentRoomData,
86 east: OceanMonumentRoomData,
88 west_up: OceanMonumentRoomData,
90 east_up: OceanMonumentRoomData,
92 },
93 DoubleYRoom {
95 room: OceanMonumentRoomData,
97 above: OceanMonumentRoomData,
99 },
100 DoubleYZRoom {
102 south: OceanMonumentRoomData,
104 north: OceanMonumentRoomData,
106 south_up: OceanMonumentRoomData,
108 north_up: OceanMonumentRoomData,
110 },
111 DoubleZRoom {
113 south: OceanMonumentRoomData,
115 north: OceanMonumentRoomData,
117 },
118 SimpleRoom {
120 room: OceanMonumentRoomData,
122 main_design: i32,
124 },
125 SimpleTopRoom {
127 room: OceanMonumentRoomData,
129 },
130 WingRoom {
132 main_design: i32,
134 },
135 Penthouse,
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub struct OceanMonumentRoomData {
142 pub index: i32,
144 pub has_opening: [bool; 6],
146 pub has_up_connection: bool,
148}
149
150impl OceanMonumentRoomData {
151 #[must_use]
153 pub fn count_openings(self) -> i32 {
154 self.has_opening.iter().filter(|&&open| open).count() as i32
155 }
156}
157
158pub struct OceanMonumentStructure;
160
161impl Structure for OceanMonumentStructure {
162 fn find_generation_point(
163 &self,
164 ctx: &mut dyn StructureGenerationContext,
165 structure: &StructureData,
166 rng: &mut LegacyRandom,
167 ) -> Option<GenerationStub> {
168 let check_x = ctx.chunk_min_x() + 9;
169 let check_z = ctx.chunk_min_z() + 9;
170 let check_y = ctx.sea_level();
171
172 let x_range = ((check_x - BIOME_RANGE_CHECK) >> 2)..=((check_x + BIOME_RANGE_CHECK) >> 2);
173 let z_range = ((check_z - BIOME_RANGE_CHECK) >> 2)..=((check_z + BIOME_RANGE_CHECK) >> 2);
174 let y_range = ((check_y - BIOME_RANGE_CHECK) >> 2)..=((check_y + BIOME_RANGE_CHECK) >> 2);
175
176 for qz in z_range {
177 for qx in x_range.clone() {
178 for qy in y_range.clone() {
179 let biome = ctx.biome_at(qx << 2, qy << 2, qz << 2);
180 if !SURROUNDING_BIOMES
181 .iter()
182 .any(|&b| biome.key == Identifier::vanilla_static(b))
183 {
184 return None;
185 }
186 }
187 }
188 }
189
190 let surface_y = ctx.surface_y();
191 let biome = ctx.biome_at(ctx.center_block_x(), surface_y, ctx.center_block_z());
192 if !structure.allowed_biomes.contains(&biome.key) {
193 return None;
194 }
195
196 let west = ctx.chunk_min_x() - 29;
197 let north = ctx.chunk_min_z() - 29;
198 let orientation = random_horizontal_direction(rng);
199 let piece = monument_building_piece(west, north, orientation, rng);
200 Some(GenerationStub {
201 position: (ctx.center_block_x(), surface_y, ctx.center_block_z()),
202 pieces: vec![piece],
203 })
204 }
205}
206
207fn monument_building_piece(
208 west: i32,
209 north: i32,
210 orientation: Direction,
211 rng: &mut LegacyRandom,
212) -> StructurePiece {
213 let bounding_box =
214 make_oriented_piece_bounding_box(west, MIN_Y, north, orientation, WIDTH, HEIGHT, DEPTH);
215 StructurePiece {
216 piece_type: Identifier::new_static("minecraft", "omb"),
217 bounding_box,
218 gen_depth: 0,
219 orientation: Some(orientation),
220 payload: StructurePiecePayload::Procedural(ProceduralPieceData::OceanMonument(
221 OceanMonumentPieceData {
222 child_pieces: generate_child_pieces(bounding_box, orientation, rng),
223 },
224 )),
225 ground_level_delta: 0,
226 junctions: Vec::new(),
227 projection: None,
228 }
229}
230
231fn generate_child_pieces(
232 building_box: BoundingBox,
233 orientation: Direction,
234 rng: &mut LegacyRandom,
235) -> Vec<OceanMonumentChildPiece> {
236 let mut graph = generate_room_graph(rng);
237 let mut child_pieces = Vec::new();
238
239 graph.rooms[graph.source_room].claimed = true;
240 push_room_child(
241 &mut child_pieces,
242 OceanMonumentChildPieceKind::EntryRoom {
243 room: graph.snapshot(graph.source_room),
244 },
245 orientation,
246 graph.rooms[graph.source_room].index,
247 (1, 1, 1),
248 );
249 push_room_child(
250 &mut child_pieces,
251 OceanMonumentChildPieceKind::CoreRoom,
252 orientation,
253 graph.rooms[graph.core_room].index,
254 (2, 2, 2),
255 );
256
257 for order_index in 0..graph.room_order.len() {
258 let room_index = graph.room_order[order_index];
259 if graph.rooms[room_index].claimed || graph.rooms[room_index].is_special() {
260 continue;
261 }
262
263 if fit_double_xy_room(&mut graph, &mut child_pieces, orientation, room_index) {
264 continue;
265 }
266 if fit_double_yz_room(&mut graph, &mut child_pieces, orientation, room_index) {
267 continue;
268 }
269 if fit_double_z_room(&mut graph, &mut child_pieces, orientation, room_index) {
270 continue;
271 }
272 if fit_double_x_room(&mut graph, &mut child_pieces, orientation, room_index) {
273 continue;
274 }
275 if fit_double_y_room(&mut graph, &mut child_pieces, orientation, room_index) {
276 continue;
277 }
278 if fit_simple_top_room(&mut graph, &mut child_pieces, orientation, room_index) {
279 continue;
280 }
281 fit_simple_room(&mut graph, &mut child_pieces, orientation, room_index, rng);
282 }
283
284 let offset = world_pos(building_box, Some(orientation), 9, 0, 22);
285 for child in &mut child_pieces {
286 child.bounding_box = child.bounding_box.translate(offset.0);
287 }
288
289 let left_wing = BoundingBox::from_corners(
290 world_pos(building_box, Some(orientation), 1, 1, 1),
291 world_pos(building_box, Some(orientation), 23, 8, 21),
292 );
293 let right_wing = BoundingBox::from_corners(
294 world_pos(building_box, Some(orientation), 34, 1, 1),
295 world_pos(building_box, Some(orientation), 56, 8, 21),
296 );
297 let penthouse = BoundingBox::from_corners(
298 world_pos(building_box, Some(orientation), 22, 13, 22),
299 world_pos(building_box, Some(orientation), 35, 17, 35),
300 );
301 let wing_random = rng.next_i32();
302 child_pieces.push(OceanMonumentChildPiece {
303 bounding_box: left_wing,
304 kind: OceanMonumentChildPieceKind::WingRoom {
305 main_design: wing_random & 1,
306 },
307 });
308 child_pieces.push(OceanMonumentChildPiece {
309 bounding_box: right_wing,
310 kind: OceanMonumentChildPieceKind::WingRoom {
311 main_design: wing_random.wrapping_add(1) & 1,
312 },
313 });
314 child_pieces.push(OceanMonumentChildPiece {
315 bounding_box: penthouse,
316 kind: OceanMonumentChildPieceKind::Penthouse,
317 });
318
319 child_pieces
320}
321
322struct RoomGraph {
323 rooms: Vec<RoomDefinition>,
324 room_order: Vec<usize>,
325 source_room: usize,
326 core_room: usize,
327}
328
329impl RoomGraph {
330 fn snapshot(&self, room: usize) -> OceanMonumentRoomData {
331 let definition = &self.rooms[room];
332 OceanMonumentRoomData {
333 index: definition.index,
334 has_opening: definition.has_opening,
335 has_up_connection: definition.connections[direction_index(Direction::Up)].is_some(),
336 }
337 }
338
339 fn connection(&self, room: usize, direction: Direction) -> usize {
340 let Some(connection) = self.rooms[room].connections[direction_index(direction)] else {
341 panic!(
342 "ocean monument room {} missing {:?} connection",
343 self.rooms[room].index, direction
344 );
345 };
346 connection
347 }
348}
349
350#[derive(Debug, Clone)]
351struct RoomDefinition {
352 index: i32,
353 connections: [Option<usize>; 6],
354 has_opening: [bool; 6],
355 claimed: bool,
356 is_source: bool,
357 scan_index: i32,
358}
359
360impl RoomDefinition {
361 const fn new(index: i32) -> Self {
362 Self {
363 index,
364 connections: [None; 6],
365 has_opening: [false; 6],
366 claimed: false,
367 is_source: false,
368 scan_index: 0,
369 }
370 }
371
372 const fn is_special(&self) -> bool {
373 self.index >= GRID_SIZE as i32
374 }
375
376 fn update_openings(&mut self) {
377 for i in 0..6 {
378 self.has_opening[i] = self.connections[i].is_some();
379 }
380 }
381}
382
383#[expect(
384 clippy::too_many_lines,
385 reason = "direct port of vanilla MonumentBuilding.generateRoomGraph"
386)]
387fn generate_room_graph(rng: &mut LegacyRandom) -> RoomGraph {
388 let mut rooms = Vec::new();
389 let mut room_grid = [None; GRID_SIZE];
390
391 for x in 0..5 {
392 for z in 0..4 {
393 let pos = get_room_index(x, 0, z);
394 room_grid[pos as usize] = Some(add_room(&mut rooms, pos));
395 }
396 }
397
398 for x in 0..5 {
399 for z in 0..4 {
400 let pos = get_room_index(x, 1, z);
401 room_grid[pos as usize] = Some(add_room(&mut rooms, pos));
402 }
403 }
404
405 for x in 1..4 {
406 for z in 0..2 {
407 let pos = get_room_index(x, 2, z);
408 room_grid[pos as usize] = Some(add_room(&mut rooms, pos));
409 }
410 }
411
412 let source_room = room_at(&room_grid, GRIDROOM_SOURCE_INDEX);
413
414 for x in 0..5 {
415 for z in 0..5 {
416 for y in 0..3 {
417 let pos = get_room_index(x, y, z);
418 let Some(room_index) = room_grid[pos as usize] else {
419 continue;
420 };
421
422 for direction in VANILLA_DIRECTIONS {
423 let (step_x, step_y, step_z) = direction.offset();
424 let neigh_x = x + step_x;
425 let neigh_y = y + step_y;
426 let neigh_z = z + step_z;
427 if !(0..5).contains(&neigh_x)
428 || !(0..5).contains(&neigh_z)
429 || !(0..3).contains(&neigh_y)
430 {
431 continue;
432 }
433
434 let neigh_pos = get_room_index(neigh_x, neigh_y, neigh_z);
435 let Some(neighbor) = room_grid[neigh_pos as usize] else {
436 continue;
437 };
438 let connection_direction = if neigh_z == z {
439 direction
440 } else {
441 direction.opposite()
442 };
443 set_connection(&mut rooms, room_index, connection_direction, neighbor);
444 }
445 }
446 }
447 }
448
449 let roof_room = add_room(&mut rooms, PENTHOUSE_INDEX);
450 let left_wing = add_room(&mut rooms, LEFTWING_INDEX);
451 let right_wing = add_room(&mut rooms, RIGHTWING_INDEX);
452 set_connection(
453 &mut rooms,
454 room_at(&room_grid, GRIDROOM_TOP_CONNECT_INDEX),
455 Direction::Up,
456 roof_room,
457 );
458 set_connection(
459 &mut rooms,
460 room_at(&room_grid, GRIDROOM_LEFTWING_CONNECT_INDEX),
461 Direction::South,
462 left_wing,
463 );
464 set_connection(
465 &mut rooms,
466 room_at(&room_grid, GRIDROOM_RIGHTWING_CONNECT_INDEX),
467 Direction::South,
468 right_wing,
469 );
470 rooms[roof_room].claimed = true;
471 rooms[left_wing].claimed = true;
472 rooms[right_wing].claimed = true;
473 rooms[source_room].is_source = true;
474
475 let core_room = room_at(&room_grid, get_room_index(rng.next_i32_bounded(4), 0, 2));
476 rooms[core_room].claimed = true;
477 let core_east = connected_room(&rooms, core_room, Direction::East);
478 let core_north = connected_room(&rooms, core_room, Direction::North);
479 let core_east_north = connected_room(&rooms, core_east, Direction::North);
480 let core_up = connected_room(&rooms, core_room, Direction::Up);
481 let core_east_up = connected_room(&rooms, core_east, Direction::Up);
482 let core_north_up = connected_room(&rooms, core_north, Direction::Up);
483 let core_east_north_up = connected_room(&rooms, core_east_north, Direction::Up);
484 for room in [
485 core_east,
486 core_north,
487 core_east_north,
488 core_up,
489 core_east_up,
490 core_north_up,
491 core_east_north_up,
492 ] {
493 rooms[room].claimed = true;
494 }
495
496 let mut room_order = Vec::new();
497 for room in room_grid.into_iter().flatten() {
498 rooms[room].update_openings();
499 room_order.push(room);
500 }
501 rooms[roof_room].update_openings();
502 vanilla_shuffle(&mut room_order, rng);
503
504 let mut scan_index = 1;
505 for room in room_order.iter().copied() {
506 let mut close_count = 0;
507 let mut attempt_count = 0;
508
509 while close_count < 2 && attempt_count < 5 {
510 attempt_count += 1;
511 let direction = rng.next_i32_bounded(6) as usize;
512 if !rooms[room].has_opening[direction] {
513 continue;
514 }
515
516 let Some(connection) = rooms[room].connections[direction] else {
517 continue;
518 };
519 let opposite = direction_index(direction_from_index(direction).opposite());
520 rooms[room].has_opening[direction] = false;
521 rooms[connection].has_opening[opposite] = false;
522
523 let room_scan = scan_index;
524 scan_index += 1;
525 let connection_scan = scan_index;
526 scan_index += 1;
527 if find_source(&mut rooms, room, room_scan)
528 && find_source(&mut rooms, connection, connection_scan)
529 {
530 close_count += 1;
531 } else {
532 rooms[room].has_opening[direction] = true;
533 rooms[connection].has_opening[opposite] = true;
534 }
535 }
536 }
537
538 room_order.push(roof_room);
539 room_order.push(left_wing);
540 room_order.push(right_wing);
541
542 RoomGraph {
543 rooms,
544 room_order,
545 source_room,
546 core_room,
547 }
548}
549
550fn add_room(rooms: &mut Vec<RoomDefinition>, index: i32) -> usize {
551 let room = rooms.len();
552 rooms.push(RoomDefinition::new(index));
553 room
554}
555
556fn room_at(room_grid: &[Option<usize>; GRID_SIZE], room_index: i32) -> usize {
557 let Some(room) = room_grid[room_index as usize] else {
558 panic!("ocean monument missing generated room {room_index}");
559 };
560 room
561}
562
563fn connected_room(rooms: &[RoomDefinition], room: usize, direction: Direction) -> usize {
564 let Some(connection) = rooms[room].connections[direction_index(direction)] else {
565 panic!(
566 "ocean monument room {} missing {:?} connection",
567 rooms[room].index, direction
568 );
569 };
570 connection
571}
572
573const fn set_connection(
574 rooms: &mut [RoomDefinition],
575 room: usize,
576 direction: Direction,
577 connection: usize,
578) {
579 rooms[room].connections[direction_index(direction)] = Some(connection);
580 rooms[connection].connections[direction_index(direction.opposite())] = Some(room);
581}
582
583fn find_source(rooms: &mut [RoomDefinition], start: usize, scan_index: i32) -> bool {
584 let mut stack = vec![start];
585 while let Some(room) = stack.pop() {
586 if rooms[room].is_source {
587 return true;
588 }
589 if rooms[room].scan_index == scan_index {
590 continue;
591 }
592 rooms[room].scan_index = scan_index;
593
594 for direction in 0..6 {
595 let Some(connection) = rooms[room].connections[direction] else {
596 continue;
597 };
598 if rooms[room].has_opening[direction] && rooms[connection].scan_index != scan_index {
599 stack.push(connection);
600 }
601 }
602 }
603 false
604}
605
606fn fit_double_xy_room(
607 graph: &mut RoomGraph,
608 child_pieces: &mut Vec<OceanMonumentChildPiece>,
609 orientation: Direction,
610 room: usize,
611) -> bool {
612 if !graph.rooms[room].has_opening[direction_index(Direction::East)]
613 || !graph.rooms[room].has_opening[direction_index(Direction::Up)]
614 {
615 return false;
616 }
617
618 let east = graph.connection(room, Direction::East);
619 let up = graph.connection(room, Direction::Up);
620 if graph.rooms[east].claimed || graph.rooms[up].claimed {
621 return false;
622 }
623
624 if !graph.rooms[east].has_opening[direction_index(Direction::Up)] {
625 return false;
626 }
627 let east_up = graph.connection(east, Direction::Up);
628 if graph.rooms[east_up].claimed {
629 return false;
630 }
631
632 for claimed in [room, east, up, east_up] {
633 graph.rooms[claimed].claimed = true;
634 }
635 push_room_child(
636 child_pieces,
637 OceanMonumentChildPieceKind::DoubleXYRoom {
638 west: graph.snapshot(room),
639 east: graph.snapshot(east),
640 west_up: graph.snapshot(up),
641 east_up: graph.snapshot(east_up),
642 },
643 orientation,
644 graph.rooms[room].index,
645 (2, 2, 1),
646 );
647 true
648}
649
650fn fit_double_yz_room(
651 graph: &mut RoomGraph,
652 child_pieces: &mut Vec<OceanMonumentChildPiece>,
653 orientation: Direction,
654 room: usize,
655) -> bool {
656 if !graph.rooms[room].has_opening[direction_index(Direction::North)]
657 || !graph.rooms[room].has_opening[direction_index(Direction::Up)]
658 {
659 return false;
660 }
661
662 let north = graph.connection(room, Direction::North);
663 let up = graph.connection(room, Direction::Up);
664 if graph.rooms[north].claimed || graph.rooms[up].claimed {
665 return false;
666 }
667
668 if !graph.rooms[north].has_opening[direction_index(Direction::Up)] {
669 return false;
670 }
671 let north_up = graph.connection(north, Direction::Up);
672 if graph.rooms[north_up].claimed {
673 return false;
674 }
675
676 for claimed in [room, north, up, north_up] {
677 graph.rooms[claimed].claimed = true;
678 }
679 push_room_child(
680 child_pieces,
681 OceanMonumentChildPieceKind::DoubleYZRoom {
682 south: graph.snapshot(room),
683 north: graph.snapshot(north),
684 south_up: graph.snapshot(up),
685 north_up: graph.snapshot(north_up),
686 },
687 orientation,
688 graph.rooms[room].index,
689 (1, 2, 2),
690 );
691 true
692}
693
694fn fit_double_z_room(
695 graph: &mut RoomGraph,
696 child_pieces: &mut Vec<OceanMonumentChildPiece>,
697 orientation: Direction,
698 room: usize,
699) -> bool {
700 if !graph.rooms[room].has_opening[direction_index(Direction::North)]
701 || graph.rooms[graph.connection(room, Direction::North)].claimed
702 {
703 return false;
704 }
705
706 let north = graph.connection(room, Direction::North);
707 graph.rooms[room].claimed = true;
708 graph.rooms[north].claimed = true;
709 push_room_child(
710 child_pieces,
711 OceanMonumentChildPieceKind::DoubleZRoom {
712 south: graph.snapshot(room),
713 north: graph.snapshot(north),
714 },
715 orientation,
716 graph.rooms[room].index,
717 (1, 1, 2),
718 );
719 true
720}
721
722fn fit_double_x_room(
723 graph: &mut RoomGraph,
724 child_pieces: &mut Vec<OceanMonumentChildPiece>,
725 orientation: Direction,
726 room: usize,
727) -> bool {
728 if !graph.rooms[room].has_opening[direction_index(Direction::East)] {
729 return false;
730 }
731
732 let east = graph.connection(room, Direction::East);
733 if graph.rooms[east].claimed {
734 return false;
735 }
736
737 graph.rooms[room].claimed = true;
738 graph.rooms[east].claimed = true;
739 push_room_child(
740 child_pieces,
741 OceanMonumentChildPieceKind::DoubleXRoom {
742 west: graph.snapshot(room),
743 east: graph.snapshot(east),
744 },
745 orientation,
746 graph.rooms[room].index,
747 (2, 1, 1),
748 );
749 true
750}
751
752fn fit_double_y_room(
753 graph: &mut RoomGraph,
754 child_pieces: &mut Vec<OceanMonumentChildPiece>,
755 orientation: Direction,
756 room: usize,
757) -> bool {
758 if !graph.rooms[room].has_opening[direction_index(Direction::Up)] {
759 return false;
760 }
761
762 let above = graph.connection(room, Direction::Up);
763 if graph.rooms[above].claimed {
764 return false;
765 }
766
767 graph.rooms[room].claimed = true;
768 graph.rooms[above].claimed = true;
769 push_room_child(
770 child_pieces,
771 OceanMonumentChildPieceKind::DoubleYRoom {
772 room: graph.snapshot(room),
773 above: graph.snapshot(above),
774 },
775 orientation,
776 graph.rooms[room].index,
777 (1, 2, 1),
778 );
779 true
780}
781
782fn fit_simple_top_room(
783 graph: &mut RoomGraph,
784 child_pieces: &mut Vec<OceanMonumentChildPiece>,
785 orientation: Direction,
786 room: usize,
787) -> bool {
788 let definition = &graph.rooms[room];
789 if definition.has_opening[direction_index(Direction::West)]
790 || definition.has_opening[direction_index(Direction::East)]
791 || definition.has_opening[direction_index(Direction::North)]
792 || definition.has_opening[direction_index(Direction::South)]
793 || definition.has_opening[direction_index(Direction::Up)]
794 {
795 return false;
796 }
797
798 graph.rooms[room].claimed = true;
799 push_room_child(
800 child_pieces,
801 OceanMonumentChildPieceKind::SimpleTopRoom {
802 room: graph.snapshot(room),
803 },
804 orientation,
805 graph.rooms[room].index,
806 (1, 1, 1),
807 );
808 true
809}
810
811fn fit_simple_room(
812 graph: &mut RoomGraph,
813 child_pieces: &mut Vec<OceanMonumentChildPiece>,
814 orientation: Direction,
815 room: usize,
816 rng: &mut LegacyRandom,
817) {
818 graph.rooms[room].claimed = true;
819 push_room_child(
820 child_pieces,
821 OceanMonumentChildPieceKind::SimpleRoom {
822 room: graph.snapshot(room),
823 main_design: rng.next_i32_bounded(3),
824 },
825 orientation,
826 graph.rooms[room].index,
827 (1, 1, 1),
828 );
829}
830
831fn push_room_child(
832 child_pieces: &mut Vec<OceanMonumentChildPiece>,
833 kind: OceanMonumentChildPieceKind,
834 orientation: Direction,
835 room_index: i32,
836 size: (i32, i32, i32),
837) {
838 child_pieces.push(OceanMonumentChildPiece {
839 bounding_box: make_room_bounding_box(orientation, room_index, size.0, size.1, size.2),
840 kind,
841 });
842}
843
844fn make_room_bounding_box(
845 orientation: Direction,
846 room_index: i32,
847 room_width: i32,
848 room_height: i32,
849 room_depth: i32,
850) -> BoundingBox {
851 let room_x = room_index % GRID_WIDTH;
852 let room_z = room_index / GRID_WIDTH % GRID_DEPTH;
853 let room_y = room_index / GRID_FLOOR_COUNT;
854 let bounding_box = make_oriented_piece_bounding_box(
855 0,
856 0,
857 0,
858 orientation,
859 room_width * 8,
860 room_height * 4,
861 room_depth * 8,
862 );
863
864 match orientation {
865 Direction::North => bounding_box.translate(IVec3::new(
866 room_x * 8,
867 room_y * 4,
868 -(room_z + room_depth) * 8 + 1,
869 )),
870 Direction::South => bounding_box.translate(IVec3::new(room_x * 8, room_y * 4, room_z * 8)),
871 Direction::West => bounding_box.translate(IVec3::new(
872 -(room_z + room_depth) * 8 + 1,
873 room_y * 4,
874 room_x * 8,
875 )),
876 Direction::East => bounding_box.translate(IVec3::new(room_z * 8, room_y * 4, room_x * 8)),
877 Direction::Down | Direction::Up => panic!("ocean monument room has vertical orientation"),
878 }
879}
880
881const fn world_pos(
882 bounding_box: BoundingBox,
883 orientation: Option<Direction>,
884 x: i32,
885 y: i32,
886 z: i32,
887) -> BlockPos {
888 let world_y = if orientation.is_some() {
889 y + bounding_box.min_y()
890 } else {
891 y
892 };
893 let (world_x, world_z) = match orientation {
894 None | Some(Direction::Up | Direction::Down) => (x, z),
895 Some(Direction::North) => (bounding_box.min_x() + x, bounding_box.max_z() - z),
896 Some(Direction::South) => (bounding_box.min_x() + x, bounding_box.min_z() + z),
897 Some(Direction::West) => (bounding_box.max_x() - z, bounding_box.min_z() + x),
898 Some(Direction::East) => (bounding_box.min_x() + z, bounding_box.min_z() + x),
899 };
900 BlockPos::new(world_x, world_y, world_z)
901}
902
903const VANILLA_DIRECTIONS: [Direction; 6] = [
904 Direction::Down,
905 Direction::Up,
906 Direction::North,
907 Direction::South,
908 Direction::West,
909 Direction::East,
910];
911
912const fn direction_index(direction: Direction) -> usize {
913 match direction {
914 Direction::Down => 0,
915 Direction::Up => 1,
916 Direction::North => 2,
917 Direction::South => 3,
918 Direction::West => 4,
919 Direction::East => 5,
920 }
921}
922
923const fn direction_from_index(index: usize) -> Direction {
924 match index {
925 0 => Direction::Down,
926 1 => Direction::Up,
927 2 => Direction::North,
928 3 => Direction::South,
929 4 => Direction::West,
930 _ => Direction::East,
931 }
932}
933
934const fn get_room_index(room_x: i32, room_y: i32, room_z: i32) -> i32 {
935 room_y * GRID_FLOOR_COUNT + room_z * GRID_WIDTH + room_x
936}
937
938fn vanilla_shuffle<T>(items: &mut [T], rng: &mut LegacyRandom) {
939 for i in (1..items.len()).rev() {
940 let j = rng.next_i32_bounded((i + 1) as i32) as usize;
941 items.swap(i, j);
942 }
943}
944
945#[cfg(test)]
946mod tests {
947 use glam::IVec3;
948
949 use super::*;
950
951 #[test]
952 fn monument_building_uses_full_procedural_payload() {
953 let mut rng = LegacyRandom::from_seed(1234);
954 let piece = monument_building_piece(16, 32, Direction::West, &mut rng);
955
956 assert_eq!(piece.piece_type, Identifier::new_static("minecraft", "omb"));
957 assert_eq!(piece.gen_depth, 0);
958 assert_eq!(piece.orientation, Some(Direction::West));
959 assert_eq!(
960 piece.bounding_box,
961 BoundingBox::new(IVec3::new(16, 39, 32), IVec3::new(73, 61, 89))
962 );
963 let StructurePiecePayload::Procedural(ProceduralPieceData::OceanMonument(data)) =
964 piece.payload
965 else {
966 panic!("ocean monument should use its procedural payload");
967 };
968 assert!(!data.child_pieces.is_empty());
969 assert!(matches!(
970 data.child_pieces[0].kind,
971 OceanMonumentChildPieceKind::EntryRoom { .. }
972 ));
973 assert!(matches!(
974 data.child_pieces[1].kind,
975 OceanMonumentChildPieceKind::CoreRoom
976 ));
977 assert!(matches!(
978 data.child_pieces.last().expect("penthouse child").kind,
979 OceanMonumentChildPieceKind::Penthouse
980 ));
981 }
982
983 #[test]
984 fn generated_child_order_captures_vanilla_fixed_children() {
985 let mut rng = LegacyRandom::from_seed(9876);
986 let piece = monument_building_piece(-29, -29, Direction::South, &mut rng);
987 let StructurePiecePayload::Procedural(ProceduralPieceData::OceanMonument(data)) =
988 piece.payload
989 else {
990 panic!("ocean monument should use its procedural payload");
991 };
992
993 assert!(data.child_pieces.len() >= 5);
994 assert!(matches!(
995 data.child_pieces[data.child_pieces.len() - 3].kind,
996 OceanMonumentChildPieceKind::WingRoom { .. }
997 ));
998 assert!(matches!(
999 data.child_pieces[data.child_pieces.len() - 2].kind,
1000 OceanMonumentChildPieceKind::WingRoom { .. }
1001 ));
1002 assert!(matches!(
1003 data.child_pieces[data.child_pieces.len() - 1].kind,
1004 OceanMonumentChildPieceKind::Penthouse
1005 ));
1006 }
1007}