1use glam::IVec3;
5use rustc_hash::FxHashMap;
6use steel_registry::structure::LiquidSettingsData;
7use steel_registry::template_pool::TemplateData;
8use steel_utils::random::Random;
9use steel_utils::random::legacy_random::LegacyRandom;
10use steel_utils::{BoundingBox, Direction, Identifier, Rotation};
11
12use crate::structure::{
13 GenerationStub, Structure, StructureBlockIgnore, StructureGenerationContext, StructureMirror,
14 StructurePiece, StructurePiecePayload, TemplateMarkerHandling, TemplatePieceData,
15 TemplatePlacementAdjustment, TemplatePlacementClip, TemplatePostProcess, TemplateProcessorList,
16};
17use steel_registry::structure::StructureData;
18
19const MAX_GEN_DEPTH: i32 = 8;
20
21#[derive(Debug, Clone)]
23pub struct EndCityPiece {
24 pub template_name: String,
26 pub template_position: IVec3,
28 pub rotation: Rotation,
30 pub gen_depth: i32,
32 pub overwrite: bool,
34}
35
36type Templates = FxHashMap<Identifier, TemplateData>;
37
38fn template_size(templates: &Templates, name: &str) -> Option<IVec3> {
39 let id = Identifier::new("minecraft", format!("end_city/{name}"));
40 templates.get(&id).map(|t| IVec3::from(t.size))
41}
42
43fn piece_bb(templates: &Templates, piece: &EndCityPiece) -> BoundingBox {
44 let size = template_size(templates, &piece.template_name)
45 .unwrap_or_else(|| panic!("missing end_city template: {}", piece.template_name));
46 piece
47 .rotation
48 .get_bounding_box(piece.template_position, size)
49}
50
51fn add_piece(
55 parent: &EndCityPiece,
56 offset: IVec3,
57 template_name: &str,
58 rotation: Rotation,
59 overwrite: bool,
60) -> EndCityPiece {
61 let rotated = parent.rotation.transform_pos(offset, IVec3::ZERO);
62 EndCityPiece {
63 template_name: template_name.to_string(),
64 template_position: parent.template_position + rotated,
65 rotation,
66 gen_depth: 0,
67 overwrite,
68 }
69}
70
71struct SharedState {
73 ship_created: bool,
74}
75
76#[derive(Debug, Clone, Copy)]
78enum SectionKind {
79 HouseTower,
80 Tower,
81 TowerBridge,
82 FatTower,
83}
84
85#[expect(
86 clippy::too_many_arguments,
87 reason = "threads parent + rotation + shared state as in vanilla's recursive dispatch"
88)]
89fn recursive_children(
90 templates: &Templates,
91 kind: SectionKind,
92 gen_depth: i32,
93 parent: &EndCityPiece,
94 offset: IVec3,
95 pieces: &mut Vec<EndCityPiece>,
96 shared: &mut SharedState,
97 rng: &mut LegacyRandom,
98) -> bool {
99 if gen_depth > MAX_GEN_DEPTH {
100 return false;
101 }
102 let mut child_pieces: Vec<EndCityPiece> = Vec::new();
103 let ok = match kind {
104 SectionKind::HouseTower => generate_house_tower(
105 templates,
106 gen_depth,
107 parent,
108 offset,
109 &mut child_pieces,
110 shared,
111 rng,
112 ),
113 SectionKind::Tower => {
114 generate_tower(templates, gen_depth, parent, &mut child_pieces, shared, rng)
115 }
116 SectionKind::TowerBridge => {
117 generate_tower_bridge(templates, gen_depth, parent, &mut child_pieces, shared, rng)
118 }
119 SectionKind::FatTower => {
120 generate_fat_tower(templates, gen_depth, parent, &mut child_pieces, shared, rng)
121 }
122 };
123 if !ok {
124 return false;
125 }
126
127 let child_tag = rng.next_i32();
128 let parent_tag = parent.gen_depth;
129 for child in &mut child_pieces {
130 child.gen_depth = child_tag;
131 let child_bb = piece_bb(templates, child);
132 if pieces
133 .iter()
134 .filter(|e| e.gen_depth != parent_tag)
135 .any(|e| piece_bb(templates, e).intersects(child_bb))
136 {
137 return false;
138 }
139 }
140 pieces.extend(child_pieces);
141 true
142}
143
144fn generate_house_tower(
145 templates: &Templates,
146 gen_depth: i32,
147 parent: &EndCityPiece,
148 offset: IVec3,
149 pieces: &mut Vec<EndCityPiece>,
150 shared: &mut SharedState,
151 rng: &mut LegacyRandom,
152) -> bool {
153 if gen_depth > MAX_GEN_DEPTH {
154 return false;
155 }
156 let rotation = parent.rotation;
157 let mut last = add_piece(parent, offset, "base_floor", rotation, true);
158 pieces.push(last.clone());
159 let num_floors = rng.next_i32_bounded(3);
160
161 let mut push = |last: &mut EndCityPiece, off: IVec3, name, overwrite| {
162 let p = add_piece(last, off, name, rotation, overwrite);
163 pieces.push(p.clone());
164 *last = p;
165 };
166
167 if num_floors == 0 {
168 push(&mut last, IVec3::new(-1, 4, -1), "base_roof", true);
169 } else {
170 push(&mut last, IVec3::new(-1, 0, -1), "second_floor_2", false);
171 if num_floors == 1 {
172 push(&mut last, IVec3::new(-1, 8, -1), "second_roof", false);
173 } else if num_floors == 2 {
174 push(&mut last, IVec3::new(-1, 4, -1), "third_floor_2", false);
175 push(&mut last, IVec3::new(-1, 8, -1), "third_roof", true);
176 }
177 if num_floors >= 1 {
178 recursive_children(
179 templates,
180 SectionKind::Tower,
181 gen_depth + 1,
182 &last,
183 IVec3::ZERO,
184 pieces,
185 shared,
186 rng,
187 );
188 }
189 }
190 true
191}
192
193const TOWER_BRIDGES: [(Rotation, IVec3); 4] = [
194 (Rotation::None, IVec3::new(1, -1, 0)),
195 (Rotation::Clockwise90, IVec3::new(6, -1, 1)),
196 (Rotation::CounterClockwise90, IVec3::new(0, -1, 5)),
197 (Rotation::Clockwise180, IVec3::new(5, -1, 6)),
198];
199
200const FAT_TOWER_BRIDGES: [(Rotation, IVec3); 4] = [
201 (Rotation::None, IVec3::new(4, -1, 0)),
202 (Rotation::Clockwise90, IVec3::new(12, -1, 4)),
203 (Rotation::CounterClockwise90, IVec3::new(0, -1, 8)),
204 (Rotation::Clockwise180, IVec3::new(8, -1, 12)),
205];
206
207fn generate_tower(
208 templates: &Templates,
209 gen_depth: i32,
210 parent: &EndCityPiece,
211 pieces: &mut Vec<EndCityPiece>,
212 shared: &mut SharedState,
213 rng: &mut LegacyRandom,
214) -> bool {
215 let rotation = parent.rotation;
216 let x_off = 3 + rng.next_i32_bounded(2);
217 let z_off = 3 + rng.next_i32_bounded(2);
218 let mut last = add_piece(
219 parent,
220 IVec3::new(x_off, -3, z_off),
221 "tower_base",
222 rotation,
223 true,
224 );
225 pieces.push(last.clone());
226 let p = add_piece(&last, IVec3::new(0, 7, 0), "tower_piece", rotation, true);
227 pieces.push(p.clone());
228 last = p;
229
230 let mut bridge_piece: Option<EndCityPiece> =
231 (rng.next_i32_bounded(3) == 0).then(|| last.clone());
232 let tower_height = 1 + rng.next_i32_bounded(3);
233 for i in 0..tower_height {
234 let p = add_piece(&last, IVec3::new(0, 4, 0), "tower_piece", rotation, true);
235 pieces.push(p.clone());
236 last = p;
237 if i < tower_height - 1 && rng.next_bool() {
238 bridge_piece = Some(last.clone());
239 }
240 }
241
242 if let Some(bridge_anchor) = bridge_piece {
243 for (rot_offset, offset) in TOWER_BRIDGES {
244 if rng.next_bool() {
245 let child_rot = rotation.then(rot_offset);
246 let bridge_start = add_piece(&bridge_anchor, offset, "bridge_end", child_rot, true);
247 pieces.push(bridge_start.clone());
248 recursive_children(
249 templates,
250 SectionKind::TowerBridge,
251 gen_depth + 1,
252 &bridge_start,
253 IVec3::ZERO,
254 pieces,
255 shared,
256 rng,
257 );
258 }
259 }
260 pieces.push(add_piece(
261 &last,
262 IVec3::new(-1, 4, -1),
263 "tower_top",
264 rotation,
265 true,
266 ));
267 } else if gen_depth != 7 {
268 return recursive_children(
269 templates,
270 SectionKind::FatTower,
271 gen_depth + 1,
272 &last,
273 IVec3::ZERO,
274 pieces,
275 shared,
276 rng,
277 );
278 } else {
279 pieces.push(add_piece(
280 &last,
281 IVec3::new(-1, 4, -1),
282 "tower_top",
283 rotation,
284 true,
285 ));
286 }
287 true
288}
289
290fn generate_tower_bridge(
291 templates: &Templates,
292 gen_depth: i32,
293 parent: &EndCityPiece,
294 pieces: &mut Vec<EndCityPiece>,
295 shared: &mut SharedState,
296 rng: &mut LegacyRandom,
297) -> bool {
298 let rotation = parent.rotation;
299 let bridge_length = rng.next_i32_bounded(4) + 1;
300
301 let mut first = add_piece(parent, IVec3::new(0, 0, -4), "bridge_piece", rotation, true);
304 first.gen_depth = -1;
305 pieces.push(first.clone());
306
307 let mut next_y = 0;
308 let mut last = first;
309 for _ in 0..bridge_length {
310 if rng.next_bool() {
311 let p = add_piece(
312 &last,
313 IVec3::new(0, next_y, -4),
314 "bridge_piece",
315 rotation,
316 true,
317 );
318 pieces.push(p.clone());
319 last = p;
320 next_y = 0;
321 } else {
322 let (name, dz) = if rng.next_bool() {
323 ("bridge_steep_stairs", -4)
324 } else {
325 ("bridge_gentle_stairs", -8)
326 };
327 let p = add_piece(&last, IVec3::new(0, next_y, dz), name, rotation, true);
328 pieces.push(p.clone());
329 last = p;
330 next_y = 4;
331 }
332 }
333
334 if !shared.ship_created && rng.next_i32_bounded(10 - gen_depth) == 0 {
335 let ship_x = -8 + rng.next_i32_bounded(8);
336 let ship_z = -70 + rng.next_i32_bounded(10);
337 pieces.push(add_piece(
338 &last,
339 IVec3::new(ship_x, next_y, ship_z),
340 "ship",
341 rotation,
342 true,
343 ));
344 shared.ship_created = true;
345 } else if !recursive_children(
346 templates,
347 SectionKind::HouseTower,
348 gen_depth + 1,
349 &last,
350 IVec3::new(-3, next_y + 1, -11),
351 pieces,
352 shared,
353 rng,
354 ) {
355 return false;
356 }
357
358 let end_rot = rotation.then(Rotation::Clockwise180);
359 let mut end = add_piece(&last, IVec3::new(4, next_y, 0), "bridge_end", end_rot, true);
360 end.gen_depth = -1;
361 pieces.push(end);
362 true
363}
364
365fn generate_fat_tower(
366 templates: &Templates,
367 gen_depth: i32,
368 parent: &EndCityPiece,
369 pieces: &mut Vec<EndCityPiece>,
370 shared: &mut SharedState,
371 rng: &mut LegacyRandom,
372) -> bool {
373 let rotation = parent.rotation;
374 let mut last = add_piece(
375 parent,
376 IVec3::new(-3, 4, -3),
377 "fat_tower_base",
378 rotation,
379 true,
380 );
381 pieces.push(last.clone());
382 let p = add_piece(
383 &last,
384 IVec3::new(0, 4, 0),
385 "fat_tower_middle",
386 rotation,
387 true,
388 );
389 pieces.push(p.clone());
390 last = p;
391
392 for _ in 0..2 {
395 if rng.next_i32_bounded(3) == 0 {
396 break;
397 }
398 let p = add_piece(
399 &last,
400 IVec3::new(0, 8, 0),
401 "fat_tower_middle",
402 rotation,
403 true,
404 );
405 pieces.push(p.clone());
406 last = p;
407
408 for (rot_offset, offset) in FAT_TOWER_BRIDGES {
409 if rng.next_bool() {
410 let child_rot = rotation.then(rot_offset);
411 let bridge_start = add_piece(&last, offset, "bridge_end", child_rot, true);
412 pieces.push(bridge_start.clone());
413 recursive_children(
414 templates,
415 SectionKind::TowerBridge,
416 gen_depth + 1,
417 &bridge_start,
418 IVec3::ZERO,
419 pieces,
420 shared,
421 rng,
422 );
423 }
424 }
425 }
426 pieces.push(add_piece(
427 &last,
428 IVec3::new(-2, 8, -2),
429 "fat_tower_top",
430 rotation,
431 true,
432 ));
433 true
434}
435
436pub fn start_house_tower(
438 templates: &Templates,
439 origin: IVec3,
440 rotation: Rotation,
441 rng: &mut LegacyRandom,
442) -> Vec<EndCityPiece> {
443 let mut pieces: Vec<EndCityPiece> = Vec::new();
444 let mut shared = SharedState {
445 ship_created: false,
446 };
447
448 let mut last = EndCityPiece {
450 template_name: "base_floor".to_string(),
451 template_position: origin,
452 rotation,
453 gen_depth: 0,
454 overwrite: true,
455 };
456 pieces.push(last.clone());
457
458 for (off, name, overwrite) in [
459 (IVec3::new(-1, 0, -1), "second_floor_1", false),
460 (IVec3::new(-1, 4, -1), "third_floor_1", false),
461 (IVec3::new(-1, 8, -1), "third_roof", true),
462 ] {
463 let p = add_piece(&last, off, name, rotation, overwrite);
464 pieces.push(p.clone());
465 last = p;
466 }
467
468 recursive_children(
469 templates,
470 SectionKind::Tower,
471 1,
472 &last,
473 IVec3::ZERO,
474 &mut pieces,
475 &mut shared,
476 rng,
477 );
478 pieces
479}
480
481fn make_end_city_structure_piece(templates: &Templates, piece: EndCityPiece) -> StructurePiece {
482 let template_id = Identifier::new("minecraft", format!("end_city/{}", piece.template_name));
483 let size = templates
484 .get(&template_id)
485 .map_or(IVec3::ONE, |t| IVec3::from(t.size));
486 StructurePiece {
487 piece_type: Identifier::new_static("minecraft", "ecp"),
488 bounding_box: piece
489 .rotation
490 .get_bounding_box(piece.template_position, size),
491 gen_depth: piece.gen_depth,
492 orientation: Some(Direction::North),
493 payload: StructurePiecePayload::Template(TemplatePieceData {
494 template_id,
495 template_position: piece.template_position,
496 rotation: piece.rotation,
497 mirror: StructureMirror::None,
498 rotation_pivot: IVec3::ZERO,
499 block_ignore: if piece.overwrite {
500 StructureBlockIgnore::StructureBlock
501 } else {
502 StructureBlockIgnore::StructureAndAir
503 },
504 late_block_ignore: StructureBlockIgnore::None,
505 processors: TemplateProcessorList::Empty,
506 liquid_settings: LiquidSettingsData::ApplyWaterlogging,
507 marker_handling: TemplateMarkerHandling::EndCity,
508 placement_adjustment: TemplatePlacementAdjustment::None,
509 placement_clip: TemplatePlacementClip::CenterChunk,
510 post_process: TemplatePostProcess::None,
511 }),
512 ground_level_delta: 0,
513 junctions: Vec::new(),
514 projection: None,
515 }
516}
517
518pub struct EndCityStructure;
521
522impl Structure for EndCityStructure {
523 fn find_generation_point(
524 &self,
525 ctx: &mut dyn StructureGenerationContext,
526 structure: &StructureData,
527 rng: &mut LegacyRandom,
528 ) -> Option<GenerationStub> {
529 let rotation = Rotation::get_random(rng);
530 let off_xz = match rotation {
531 Rotation::None => IVec3::new(5, 0, 5),
532 Rotation::Clockwise90 => IVec3::new(-5, 0, 5),
533 Rotation::Clockwise180 => IVec3::new(-5, 0, -5),
534 Rotation::CounterClockwise90 => IVec3::new(5, 0, -5),
535 };
536 let (bx, bz) = (ctx.chunk_min_x() + 7, ctx.chunk_min_z() + 7);
537 let h0 = ctx.base_height_full(bx, bz, false) - 1;
540 let h1 = ctx.base_height_full(bx, bz + off_xz.z, false) - 1;
541 let h2 = ctx.base_height_full(bx + off_xz.x, bz, false) - 1;
542 let h3 = ctx.base_height_full(bx + off_xz.x, bz + off_xz.z, false) - 1;
543 let lowest = h0.min(h1).min(h2).min(h3);
544 if lowest < 60 {
545 return None;
546 }
547
548 let biome = ctx.biome_at(bx, lowest, bz);
549 if !structure.allowed_biomes.contains(&biome.key) {
550 return None;
551 }
552
553 let origin = IVec3::new(bx, lowest, bz);
554 Some(GenerationStub {
555 position: (origin.x, origin.y, origin.z),
556 pieces: start_house_tower(ctx.templates(), origin, rotation, rng)
557 .into_iter()
558 .map(|p| make_end_city_structure_piece(ctx.templates(), p))
559 .collect(),
560 })
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567
568 fn single_template(name: &str, size: IVec3) -> Templates {
569 let mut templates = FxHashMap::default();
570 templates.insert(
571 Identifier::new("minecraft", format!("end_city/{name}")),
572 TemplateData {
573 size: size.into(),
574 jigsaws: Vec::new(),
575 },
576 );
577 templates
578 }
579
580 #[test]
581 fn end_city_piece_uses_template_payload_and_overwrite_processor() {
582 let templates = single_template("third_roof", IVec3::new(6, 7, 8));
583 let runtime_piece = make_end_city_structure_piece(
584 &templates,
585 EndCityPiece {
586 template_name: "third_roof".to_owned(),
587 template_position: IVec3::new(10, 70, 20),
588 rotation: Rotation::Clockwise90,
589 gen_depth: 4,
590 overwrite: true,
591 },
592 );
593
594 assert_eq!(runtime_piece.piece_type, Identifier::vanilla_static("ecp"));
595 assert_eq!(runtime_piece.gen_depth, 4);
596 let StructurePiecePayload::Template(data) = runtime_piece.payload else {
597 panic!("end city should use template payload");
598 };
599 assert_eq!(
600 data.template_id,
601 Identifier::vanilla_static("end_city/third_roof")
602 );
603 assert_eq!(data.template_position, (10, 70, 20).into());
604 assert_eq!(data.rotation, Rotation::Clockwise90);
605 assert_eq!(data.block_ignore, StructureBlockIgnore::StructureBlock);
606 assert_eq!(data.processors, TemplateProcessorList::Empty);
607 assert_eq!(data.marker_handling, TemplateMarkerHandling::EndCity);
608 assert_eq!(data.placement_clip, TemplatePlacementClip::CenterChunk);
609
610 let runtime_piece = make_end_city_structure_piece(
611 &templates,
612 EndCityPiece {
613 template_name: "third_roof".to_owned(),
614 template_position: IVec3::new(10, 70, 20),
615 rotation: Rotation::Clockwise90,
616 gen_depth: 4,
617 overwrite: false,
618 },
619 );
620 let StructurePiecePayload::Template(data) = runtime_piece.payload else {
621 panic!("end city should use template payload");
622 };
623 assert_eq!(data.block_ignore, StructureBlockIgnore::StructureAndAir);
624 }
625}