1use core::{fmt, ops};
4
5use arcstr::ArcStr;
6
7use crate::inv::InvInBlock;
8use crate::math::{Face6, GridRotation};
9use crate::op::Operation;
10use crate::universe::{HandleVisitor, VisitHandles};
11
12use crate::block::Modifier;
13use crate::time;
14#[cfg(doc)]
15use crate::{
16 block::{Block, BlockDef, Primitive},
17 space::Space,
18 time::TickSchedule,
19};
20
21macro_rules! derive_attribute_helpers {
24 ($(#[$_:meta])* pub struct BlockAttributes {
25 $(
26 $(#[doc = $field_doc:literal])*
27 #[custom(
28 arbitrary_type = $arbitrary_type:ty,
29 builder_param_style = $builder_param_style:tt,
30 )]
31 pub $field_name:ident: $field_type:ty,
32 )*
33 }) => {
34 impl fmt::Debug for BlockAttributes {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 if self == Self::DEFAULT_REF {
38 write!(f, "BlockAttributes {{}}")
41 } else {
42 let Self {
43 $($field_name,)*
44 } = self;
45
46 let mut debug_struct = f.debug_struct("BlockAttributes");
47 $(
48 if *$field_name != Self::DEFAULT_REF.$field_name {
49 debug_struct.field(stringify!($field_name), $field_name);
50 }
51 )*
52 debug_struct.finish()
53 }
54 }
55 }
56
57 impl VisitHandles for BlockAttributes {
58 fn visit_handles(&self, visitor: &mut dyn HandleVisitor) {
59 let Self { $( $field_name, )* } = self;
60 $(
61 <$field_type as VisitHandles>::visit_handles($field_name, visitor);
62 )*
63 }
64 }
65
66 #[cfg(feature = "arbitrary")]
67 #[mutants::skip]
68 impl<'a> arbitrary::Arbitrary<'a> for BlockAttributes {
69 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
70 Ok(BlockAttributes {
71 $(
72 $field_name:
73 <$arbitrary_type as arbitrary::Arbitrary>::arbitrary(u)?.into(),
74 )*
75 })
76 }
77
78 fn size_hint(depth: usize) -> (usize, Option<usize>) {
79 Self::try_size_hint(depth).unwrap_or_default()
80 }
81 fn try_size_hint(
82 depth: usize,
83 ) -> Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
84 arbitrary::size_hint::try_recursion_guard(depth, |depth| {
85 Ok(arbitrary::size_hint::and_all(&[
86 $(
87 <$arbitrary_type as arbitrary::Arbitrary>::try_size_hint(depth)?,
88 )*
89 ]))
90 })
91 }
92 }
93
94 impl<P, Txn> crate::block::Builder<'_, P, Txn> {
96 $(
97 attribute_builder_method!($(#[doc = $field_doc] )* $builder_param_style $field_name: $field_type);
98 )*
99 }
100 };
101}
102
103#[rustfmt::skip] macro_rules! attribute_builder_method {
105 ($(#[doc = $field_doc:literal] )* exact inventory: $field_type:ty) => {
107 #[doc = concat!(
108 "Sets the value for [`BlockAttributes::inventory`], which is:",
109 )]
110 #[doc = ""]
111 $(#[doc = concat!("> ", $field_doc)] )*
112 pub fn inventory_config(mut self, value: crate::inv::InvInBlock) -> Self {
113 self.attributes.inventory = value.into();
114 self
115 }
116 };
117
118 ($(#[doc = $field_doc:literal] )* exact $field_name:ident: $field_type:ty) => {
120 #[doc = concat!(
121 "Sets the value for [`BlockAttributes::",
122 stringify!($field_name),
123 "`], which is:",
124 )]
125 #[doc = ""]
126 $(#[doc = concat!("> ", $field_doc)] )*
127 pub fn $field_name(mut self, value: $field_type) -> Self {
128 self.attributes.$field_name = value;
129 self
130 }
131 };
132 ($(#[doc = $field_doc:literal] )* into $field_name:ident: $field_type:ty) => {
133 #[doc = concat!(
134 "Sets the value for [`BlockAttributes::",
135 stringify!($field_name),
136 "`], which is:",
137 )]
138 #[doc = ""]
139 $(#[doc = concat!("> ", $field_doc)] )*
140 pub fn $field_name(mut self, value: impl Into<$field_type>) -> Self {
141 self.attributes.$field_name = value.into();
142 self
143 }
144 };
145}
146
147#[derive(Clone, Eq, Hash, PartialEq)]
152#[non_exhaustive]
153#[macro_rules_attribute::derive(derive_attribute_helpers!)]
154pub struct BlockAttributes {
155 #[custom(
163 arbitrary_type = alloc::string::String,
164 builder_param_style = into,
165 )]
166 pub display_name: ArcStr,
167
168 #[custom(
172 arbitrary_type = bool,
173 builder_param_style = exact,
174 )]
175 pub selectable: bool,
176
177 #[custom(
180 arbitrary_type = InvInBlock,
181 builder_param_style = exact,
182 )]
183 pub inventory: InvInBlock,
184
185 #[custom(
187 arbitrary_type = crate::sound::Ambient,
188 builder_param_style = exact,
189 )]
190 pub ambient_sound: crate::sound::Ambient,
191
192 #[custom(
199 arbitrary_type = RotationPlacementRule,
200 builder_param_style = exact,
201 )]
202 pub rotation_rule: RotationPlacementRule,
203
204 #[custom(
209 arbitrary_type = Option<PlacementAction>,
210 builder_param_style = into,
211 )]
212 pub placement_action: Option<PlacementAction>,
213
214 #[custom(
216 arbitrary_type = Option<TickAction>,
217 builder_param_style = into,
218 )]
219 pub tick_action: Option<TickAction>,
220
221 #[custom(
223 arbitrary_type = Option<Operation>,
224 builder_param_style = into,
225 )]
226 pub activation_action: Option<Operation>,
227
228 #[custom(
234 arbitrary_type = AnimationHint,
235 builder_param_style = exact,
236 )]
237 pub animation_hint: AnimationHint,
238}
239
240impl BlockAttributes {
241 const DEFAULT: Self = BlockAttributes {
242 display_name: arcstr::literal!(""),
243 selectable: true,
244 inventory: InvInBlock::EMPTY,
245 ambient_sound: crate::sound::Ambient::SILENT,
246 rotation_rule: RotationPlacementRule::Never,
247 placement_action: None,
248 tick_action: None,
249 activation_action: None,
250 animation_hint: AnimationHint::UNCHANGING,
251 };
252 pub(crate) const DEFAULT_REF: &'static Self = &Self::DEFAULT;
256
257 pub const fn default() -> BlockAttributes {
262 Self::DEFAULT
263 }
264
265 #[mutants::skip] pub(crate) fn rotationally_symmetric(&self) -> bool {
267 let Self {
268 display_name: _,
269 selectable: _,
270 inventory,
271 ambient_sound: _,
272 rotation_rule,
273 placement_action,
274 tick_action,
275 activation_action,
276 animation_hint: _,
277 } = self;
278
279 inventory.rotationally_symmetric()
280 && rotation_rule.rotationally_symmetric()
281 && placement_action.as_ref().is_none_or(|a| a.rotationally_symmetric())
282 && tick_action.as_ref().is_none_or(|a| a.rotationally_symmetric())
283 && activation_action.as_ref().is_none_or(|a| a.rotationally_symmetric())
284 }
285
286 pub(crate) fn rotate(self, rotation: GridRotation) -> BlockAttributes {
287 let Self {
288 display_name,
289 selectable,
290 inventory,
291 ambient_sound,
292 rotation_rule,
293 placement_action,
294 tick_action,
295 activation_action,
296 animation_hint,
297 } = self;
298
299 Self {
300 display_name,
301 selectable,
302 inventory: inventory.rotate(rotation),
303 ambient_sound,
304 rotation_rule: rotation_rule.rotate(rotation),
305 placement_action: placement_action.map(|a| a.rotate(rotation)),
306 tick_action: tick_action.map(|a| a.rotate(rotation)),
307 activation_action: activation_action.map(|a| a.rotate(rotation)),
308 animation_hint,
309 }
310 }
311}
312
313impl Default for BlockAttributes {
314 fn default() -> BlockAttributes {
316 BlockAttributes::default()
318 }
319}
320
321impl From<BlockAttributes> for Modifier {
322 fn from(value: BlockAttributes) -> Self {
324 Modifier::Attributes(alloc::sync::Arc::new(value))
325 }
326}
327
328#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
333#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
334#[non_exhaustive]
335pub enum BlockCollision {
336 None,
339 Hard,
343 }
345
346impl BlockCollision {
347 pub(crate) const DEFAULT_FOR_FROM_COLOR: Self = Self::Hard;
352}
353
354#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
361#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
362#[non_exhaustive]
363pub enum RotationPlacementRule {
364 Never,
366 Attach {
368 by: Face6,
373 },
375}
376
377impl RotationPlacementRule {
378 pub(crate) fn rotationally_symmetric(self) -> bool {
379 match self {
380 RotationPlacementRule::Never => true,
381 RotationPlacementRule::Attach { by: _ } => false,
382 }
383 }
384
385 fn rotate(mut self, rotation: GridRotation) -> RotationPlacementRule {
386 match &mut self {
387 RotationPlacementRule::Attach { by } => *by = rotation.transform(*by),
388 RotationPlacementRule::Never => {}
389 }
390 self
391 }
392}
393
394impl VisitHandles for RotationPlacementRule {
395 fn visit_handles(&self, _: &mut dyn HandleVisitor) {}
396}
397
398#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
408#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
409#[non_exhaustive]
410pub struct AnimationHint {
411 pub redefinition: AnimationChange,
415
416 pub replacement: AnimationChange,
419}
420
421impl AnimationHint {
422 pub const UNCHANGING: Self = Self {
428 redefinition: AnimationChange::None,
429 replacement: AnimationChange::None,
430 };
431
432 pub const fn redefinition(change: AnimationChange) -> Self {
435 Self {
436 redefinition: change,
437 replacement: AnimationChange::None,
438 }
439 }
440
441 pub const fn replacement(change: AnimationChange) -> Self {
444 Self {
445 redefinition: AnimationChange::None,
446 replacement: change,
447 }
448 }
449
450 pub(crate) fn might_become_visible(self) -> bool {
453 self.redefinition.might_become_visible() || self.replacement.might_become_visible()
454 }
455}
456
457impl Default for AnimationHint {
458 fn default() -> Self {
459 Self::UNCHANGING
460 }
461}
462
463impl ops::BitOr for AnimationHint {
464 type Output = Self;
465 #[inline]
466 fn bitor(self, rhs: Self) -> Self::Output {
467 Self {
468 redefinition: self.redefinition | rhs.redefinition,
469 replacement: self.replacement | rhs.replacement,
470 }
471 }
472}
473impl ops::BitOrAssign for AnimationHint {
474 #[inline]
475 fn bitor_assign(&mut self, rhs: Self) {
476 *self = *self | rhs;
477 }
478}
479
480impl VisitHandles for AnimationHint {
481 fn visit_handles(&self, _: &mut dyn HandleVisitor) {}
482}
483
484#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
486#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
487#[non_exhaustive]
488pub enum AnimationChange {
489 None,
491 ColorSameCategory,
498 Shape,
505}
506
507impl AnimationChange {
508 fn might_become_visible(self) -> bool {
510 match self {
511 AnimationChange::None => false,
512 AnimationChange::ColorSameCategory => false,
514 AnimationChange::Shape => true,
515 }
516 }
517}
518
519impl ops::BitOr for AnimationChange {
520 type Output = Self;
521 #[inline]
522 fn bitor(self, rhs: Self) -> Self::Output {
523 use AnimationChange::*;
524 match (self, rhs) {
525 (Shape, _) | (_, Shape) => Shape,
526 (ColorSameCategory, _) | (_, ColorSameCategory) => ColorSameCategory,
527 (None, None) => None,
528 }
529 }
530}
531
532#[derive(Clone, Debug, Eq, Hash, PartialEq)]
536#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
537#[expect(clippy::exhaustive_structs, reason = "will deliberately break")]
538pub struct TickAction {
539 pub operation: Operation,
541 pub schedule: time::Schedule,
548}
549
550impl TickAction {
551 fn rotationally_symmetric(&self) -> bool {
552 let Self {
553 operation,
554 schedule: _,
555 } = self;
556 operation.rotationally_symmetric()
557 }
558
559 fn rotate(self, rotation: GridRotation) -> TickAction {
560 let Self {
561 operation,
562 schedule,
563 } = self;
564 let operation = operation.rotate(rotation);
565 Self {
566 operation,
567 schedule,
568 }
569 }
570}
571
572impl From<Operation> for TickAction {
573 fn from(operation: Operation) -> Self {
575 Self {
576 operation,
577 schedule: time::Schedule::EVERY_TICK,
578 }
579 }
580}
581
582impl VisitHandles for TickAction {
583 fn visit_handles(&self, visitor: &mut dyn HandleVisitor) {
584 let Self {
585 operation,
586 schedule: _,
587 } = self;
588 operation.visit_handles(visitor);
589 }
590}
591
592#[derive(Clone, Debug, Eq, Hash, PartialEq)]
595#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
596#[expect(clippy::exhaustive_structs, reason = "no better plan yet")]
597pub struct PlacementAction {
598 pub operation: Operation,
600
601 pub in_front: bool,
606}
607
608impl PlacementAction {
609 fn rotationally_symmetric(&self) -> bool {
610 let Self {
611 operation,
612 in_front: _,
613 } = self;
614 operation.rotationally_symmetric()
615 }
616
617 fn rotate(self, rotation: GridRotation) -> PlacementAction {
618 Self {
619 operation: self.operation.rotate(rotation),
620 in_front: self.in_front,
621 }
622 }
623}
624
625impl VisitHandles for PlacementAction {
626 fn visit_handles(&self, visitor: &mut dyn HandleVisitor) {
627 let Self {
628 operation,
629 in_front: _,
630 } = self;
631 operation.visit_handles(visitor);
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use alloc::string::String;
639
640 #[test]
641 fn size() {
642 let size = size_of::<BlockAttributes>();
643 assert!(
644 size <= 360,
645 "size_of::<BlockAttributes>() = {size} unexpectedly large"
646 );
647 }
648
649 #[test]
652 fn default_equivalent() {
653 assert_eq!(
654 BlockAttributes::default(),
655 <BlockAttributes as Default>::default()
656 );
657 }
658
659 #[test]
660 fn debug() {
661 let default = BlockAttributes::default;
662 #[expect(clippy::needless_pass_by_value)]
663 fn debug(a: BlockAttributes) -> String {
664 format!("{a:?}")
665 }
666 assert_eq!(&*debug(BlockAttributes::default()), "BlockAttributes {}",);
667 assert_eq!(
668 &*debug(BlockAttributes {
669 display_name: "x".into(),
670 ..default()
671 }),
672 "BlockAttributes { display_name: \"x\" }",
673 );
674 assert_eq!(
675 &*debug(BlockAttributes {
676 selectable: false,
677 ..default()
678 }),
679 "BlockAttributes { selectable: false }",
680 );
681 assert_eq!(
682 &*debug(BlockAttributes {
683 animation_hint: AnimationHint::replacement(AnimationChange::Shape),
684 ..default()
685 }),
686 "BlockAttributes { animation_hint: \
687 AnimationHint { redefinition: None, replacement: Shape } }",
688 );
689
690 assert_eq!(
692 &*debug(BlockAttributes {
693 display_name: "y".into(),
694 selectable: false,
695 ..default()
696 }),
697 "BlockAttributes { display_name: \"y\", selectable: false }",
698 );
699 }
700}