1use crate::cores::LawTemplateId;
34use crate::prelude::*;
35
36use strum::IntoEnumIterator;
37use strum_macros::{Display, EnumIter, EnumString};
38
39#[declare]
40pub type ArtifactTemplateId = Uuid;
41
42#[declare]
43pub type ArtifactStoneTemplateId = Uuid;
44
45#[derive(
52 Clone,
53 Copy,
54 Debug,
55 Default,
56 Serialize,
57 Deserialize,
58 PartialEq,
59 Eq,
60 Hash,
61 JsonSchema,
62 Tsify,
63 Display,
64 EnumString,
65 EnumIter,
66)]
67#[tsify(from_wasm_abi, into_wasm_abi)]
68pub enum ArtifactSocketColumn {
69 #[default]
71 Aspect,
72 Law,
76}
77
78#[derive(
96 Clone,
97 Copy,
98 Debug,
99 Default,
100 Serialize,
101 Deserialize,
102 PartialEq,
103 Eq,
104 Hash,
105 JsonSchema,
106 Tsify,
107 Display,
108 EnumString,
109 EnumIter,
110)]
111#[tsify(from_wasm_abi, into_wasm_abi)]
112pub enum ArtifactSocketSlot {
113 #[default]
114 VisibleAspect,
115 HiddenAspect,
116 FlipAspect,
117 VisibleLaw,
118 HiddenLaw,
119 BridgeLaw,
120}
121
122impl ArtifactSocketSlot {
123 pub const fn column(self) -> ArtifactSocketColumn {
126 match self {
127 Self::VisibleAspect | Self::HiddenAspect | Self::FlipAspect => {
128 ArtifactSocketColumn::Aspect
129 }
130 Self::VisibleLaw | Self::HiddenLaw | Self::BridgeLaw => ArtifactSocketColumn::Law,
131 }
132 }
133
134 pub const fn unlock_order(self) -> u8 {
138 match self {
139 Self::VisibleAspect => 0,
140 Self::HiddenAspect => 1,
141 Self::FlipAspect => 2,
142 Self::VisibleLaw => 3,
143 Self::HiddenLaw => 4,
144 Self::BridgeLaw => 5,
145 }
146 }
147
148 pub fn all() -> Vec<Self> {
150 let mut slots: Vec<Self> = Self::iter().collect();
151 slots.sort_by_key(|slot| slot.unlock_order());
152 slots
153 }
154}
155
156#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
163#[tsify(from_wasm_abi, into_wasm_abi)]
164pub struct Artifact {
165 pub template_id: ArtifactTemplateId,
166 pub level: i64,
167 pub copies: i64,
170}
171
172impl Artifact {
173 pub const fn new(template_id: ArtifactTemplateId) -> Self {
174 Self {
175 template_id,
176 level: 1,
177 copies: 0,
178 }
179 }
180}
181
182#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
189#[tsify(from_wasm_abi, into_wasm_abi)]
190pub struct ArtifactStone {
191 pub template_id: ArtifactStoneTemplateId,
192 pub level: i64,
193 pub copies: i64,
195 pub socket: Option<ArtifactSocketSlot>,
197 pub law_target: Option<LawTemplateId>,
214}
215
216impl ArtifactStone {
217 pub const fn new(template_id: ArtifactStoneTemplateId) -> Self {
218 Self {
219 template_id,
220 level: 1,
221 copies: 0,
222 socket: None,
223 law_target: None,
224 }
225 }
226
227 pub const fn is_socketed(&self) -> bool {
228 self.socket.is_some()
229 }
230}
231
232#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
234#[tsify(from_wasm_abi, into_wasm_abi)]
235pub struct ArtifactCollection {
236 pub artifacts: Vec<Artifact>,
237 pub equipped: Option<ArtifactTemplateId>,
241 pub stones: Vec<ArtifactStone>,
244}
245
246#[derive(Debug, thiserror::Error, PartialEq, Eq)]
249pub enum ArtifactError {
250 #[error("the player owns no artifact of template {0}")]
251 UnknownArtifact(ArtifactTemplateId),
252 #[error("the player owns no artifact stone of template {0}")]
253 UnknownStone(ArtifactStoneTemplateId),
254 #[error("socket {socket} takes a {expected} stone")]
255 WrongSocket {
256 socket: ArtifactSocketSlot,
257 expected: ArtifactSocketSlot,
258 },
259 #[error("socket {0} is not unlocked yet")]
260 SocketLocked(ArtifactSocketSlot),
261 #[error("socket {0} already holds a stone")]
262 SocketOccupied(ArtifactSocketSlot),
263 #[error("artifact stone {0} is already socketed — a template holds at most one socket")]
264 AlreadySocketed(ArtifactStoneTemplateId),
265 #[error("artifact stone {0} is not socketed")]
266 NotSocketed(ArtifactStoneTemplateId),
267 #[error("artifact stone {0} sits in an Aspect socket and has no law to choose")]
268 NotALawStone(ArtifactStoneTemplateId),
269 #[error("law {0} is not slotted on a core, so no artifact stone may point at it")]
270 LawNotSlotted(LawTemplateId),
271 #[error(
272 "upgrading artifact stone {stone} to level {level} needs {required} copies, {held} banked"
273 )]
274 NotEnoughCopies {
275 stone: ArtifactStoneTemplateId,
276 level: i64,
277 required: i64,
278 held: i64,
279 },
280 #[error("artifact stone {0} is already at the maximum level {1}")]
281 AlreadyMaxLevel(ArtifactStoneTemplateId, i64),
282 #[error(
283 "upgrading artifact {artifact} to level {level} needs {required} copies, {held} banked"
284 )]
285 NotEnoughArtifactCopies {
286 artifact: ArtifactTemplateId,
287 level: i64,
288 required: i64,
289 held: i64,
290 },
291 #[error("artifact {0} is already at the maximum level {1}")]
292 ArtifactAlreadyMaxLevel(ArtifactTemplateId, i64),
293 #[error("the artifact stone upgrade ladder has no cost for level {0}")]
296 NoUpgradeStep(i64),
297 #[error("artifacts and their stones may only be changed out of combat")]
298 InCombat,
299}
300
301impl ArtifactCollection {
302 pub fn get(&self, template_id: ArtifactTemplateId) -> Option<&Artifact> {
303 self.artifacts
304 .iter()
305 .find(|artifact| artifact.template_id == template_id)
306 }
307
308 fn get_mut(&mut self, template_id: ArtifactTemplateId) -> Option<&mut Artifact> {
309 self.artifacts
310 .iter_mut()
311 .find(|artifact| artifact.template_id == template_id)
312 }
313
314 pub fn stone(&self, template_id: ArtifactStoneTemplateId) -> Option<&ArtifactStone> {
315 self.stones
316 .iter()
317 .find(|stone| stone.template_id == template_id)
318 }
319
320 fn stone_mut(&mut self, template_id: ArtifactStoneTemplateId) -> Option<&mut ArtifactStone> {
321 self.stones
322 .iter_mut()
323 .find(|stone| stone.template_id == template_id)
324 }
325
326 pub fn socketed(&self, socket: ArtifactSocketSlot) -> Option<&ArtifactStone> {
328 self.stones
329 .iter()
330 .find(|stone| stone.socket == Some(socket))
331 }
332
333 pub fn grant_artifact(&mut self, template_id: ArtifactTemplateId) {
346 if self.equipped.is_none() {
347 self.equipped = Some(template_id);
348 }
349
350 let Some(artifact) = self.get_mut(template_id) else {
351 self.artifacts.push(Artifact::new(template_id));
352 return;
353 };
354 artifact.copies += 1;
355 }
356
357 pub fn upgrade_artifact(
362 &mut self,
363 template_id: ArtifactTemplateId,
364 required: i64,
365 max_level: i64,
366 ) -> Result<i64, ArtifactError> {
367 let artifact = self
368 .get_mut(template_id)
369 .ok_or(ArtifactError::UnknownArtifact(template_id))?;
370
371 if artifact.level >= max_level {
372 return Err(ArtifactError::ArtifactAlreadyMaxLevel(
373 template_id,
374 max_level,
375 ));
376 }
377 let next_level = artifact.level + 1;
378 if artifact.copies < required {
379 return Err(ArtifactError::NotEnoughArtifactCopies {
380 artifact: template_id,
381 level: next_level,
382 required,
383 held: artifact.copies,
384 });
385 }
386
387 artifact.copies -= required;
388 artifact.level = next_level;
389 Ok(next_level)
390 }
391
392 pub fn equip(&mut self, template_id: ArtifactTemplateId) -> Result<(), ArtifactError> {
399 if self.get(template_id).is_none() {
400 return Err(ArtifactError::UnknownArtifact(template_id));
401 }
402 self.equipped = Some(template_id);
403 Ok(())
404 }
405
406 pub fn grant_stone(&mut self, template_id: ArtifactStoneTemplateId) {
411 if let Some(stone) = self.stone_mut(template_id) {
412 stone.copies += 1;
413 return;
414 }
415 self.stones.push(ArtifactStone::new(template_id));
416 self.stones.sort_by_key(|stone| stone.template_id);
421 }
422
423 pub fn insert_stone(
430 &mut self,
431 template_id: ArtifactStoneTemplateId,
432 socket: ArtifactSocketSlot,
433 expected: ArtifactSocketSlot,
434 unlocked: bool,
435 ) -> Result<(), ArtifactError> {
436 if socket != expected {
437 return Err(ArtifactError::WrongSocket { socket, expected });
438 }
439 if !unlocked {
440 return Err(ArtifactError::SocketLocked(socket));
441 }
442 if self.stone(template_id).is_none() {
443 return Err(ArtifactError::UnknownStone(template_id));
444 }
445 if self.socketed(socket).is_some() {
446 return Err(ArtifactError::SocketOccupied(socket));
447 }
448 let stone = self
449 .stone_mut(template_id)
450 .ok_or(ArtifactError::UnknownStone(template_id))?;
451 if stone.is_socketed() {
452 return Err(ArtifactError::AlreadySocketed(template_id));
453 }
454 stone.socket = Some(socket);
455 Ok(())
456 }
457
458 pub fn set_law_target(
473 &mut self,
474 template_id: ArtifactStoneTemplateId,
475 law: Option<LawTemplateId>,
476 is_law_socket: bool,
477 slotted: bool,
478 ) -> Result<(), ArtifactError> {
479 if !is_law_socket {
480 return Err(ArtifactError::NotALawStone(template_id));
481 }
482 if let Some(law) = law
483 && !slotted
484 {
485 return Err(ArtifactError::LawNotSlotted(law));
486 }
487 let stone = self
488 .stone_mut(template_id)
489 .ok_or(ArtifactError::UnknownStone(template_id))?;
490 stone.law_target = law;
491 Ok(())
492 }
493
494 pub fn forget_law_target(&mut self, law: LawTemplateId) -> bool {
499 let mut changed = false;
500 for stone in &mut self.stones {
501 if stone.law_target == Some(law) {
502 stone.law_target = None;
503 changed = true;
504 }
505 }
506 changed
507 }
508
509 pub fn remove_stone(
512 &mut self,
513 template_id: ArtifactStoneTemplateId,
514 ) -> Result<ArtifactSocketSlot, ArtifactError> {
515 let stone = self
516 .stone_mut(template_id)
517 .ok_or(ArtifactError::UnknownStone(template_id))?;
518 stone
519 .socket
520 .take()
521 .ok_or(ArtifactError::NotSocketed(template_id))
522 }
523
524 pub fn upgrade_stone(
530 &mut self,
531 template_id: ArtifactStoneTemplateId,
532 required: i64,
533 max_level: i64,
534 ) -> Result<i64, ArtifactError> {
535 let stone = self
536 .stone_mut(template_id)
537 .ok_or(ArtifactError::UnknownStone(template_id))?;
538
539 if stone.level >= max_level {
540 return Err(ArtifactError::AlreadyMaxLevel(template_id, max_level));
541 }
542 let next_level = stone.level + 1;
543 if stone.copies < required {
544 return Err(ArtifactError::NotEnoughCopies {
545 stone: template_id,
546 level: next_level,
547 required,
548 held: stone.copies,
549 });
550 }
551
552 stone.copies -= required;
553 stone.level = next_level;
554 Ok(next_level)
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use super::*;
561
562 const EMPTY_FRAME: u128 = 900;
563 const SECOND_ARTIFACT: u128 = 901;
564 const ASPECT_STONE: u128 = 910;
565 const OTHER_ASPECT_STONE: u128 = 911;
566 const LAW_STONE: u128 = 920;
567 const OTHER_LAW_STONE: u128 = 921;
568 const CHOSEN_LAW: u128 = 930;
569 const OTHER_LAW: u128 = 931;
570
571 fn id(n: u128) -> Uuid {
572 Uuid::from_u128(n)
573 }
574
575 fn ladder(level: i64) -> Option<i64> {
577 match level {
578 2 => Some(1),
579 3 => Some(2),
580 _ => None,
581 }
582 }
583
584 #[test]
585 fn the_six_sockets_open_left_column_first() {
586 let all = ArtifactSocketSlot::all();
587 assert_eq!(all.len(), 6);
588 let columns: Vec<_> = all.iter().map(|slot| slot.column()).collect();
589 assert_eq!(
590 columns,
591 vec![
592 ArtifactSocketColumn::Aspect,
593 ArtifactSocketColumn::Aspect,
594 ArtifactSocketColumn::Aspect,
595 ArtifactSocketColumn::Law,
596 ArtifactSocketColumn::Law,
597 ArtifactSocketColumn::Law,
598 ],
599 "the whole Aspect column comes before the Law column"
600 );
601 }
602
603 #[test]
605 fn exactly_one_artifact_is_worn_while_many_are_owned() {
606 let mut collection = ArtifactCollection::default();
607 collection.grant_artifact(id(EMPTY_FRAME));
608 collection.grant_artifact(id(SECOND_ARTIFACT));
609 assert_eq!(collection.artifacts.len(), 2);
610 assert_eq!(
611 collection.equipped,
612 Some(id(EMPTY_FRAME)),
613 "the first artifact owned is worn; the second does not take over"
614 );
615
616 collection.equip(id(EMPTY_FRAME)).unwrap();
617 assert_eq!(collection.equipped, Some(id(EMPTY_FRAME)));
618
619 collection.equip(id(SECOND_ARTIFACT)).unwrap();
620 assert_eq!(
621 collection.equipped,
622 Some(id(SECOND_ARTIFACT)),
623 "wearing a second artifact replaces the first, never adds to it"
624 );
625 assert_eq!(collection.artifacts.len(), 2, "both are still owned");
626 }
627
628 #[test]
632 fn the_first_granted_artifact_is_worn_and_later_grants_do_not_take_over() {
633 let mut collection = ArtifactCollection::default();
634
635 collection.grant_artifact(id(SECOND_ARTIFACT));
636 assert_eq!(collection.equipped, Some(id(SECOND_ARTIFACT)));
637
638 collection.grant_artifact(id(EMPTY_FRAME));
639 assert_eq!(
640 collection.equipped,
641 Some(id(SECOND_ARTIFACT)),
642 "a later grant does not silently swap what the player wears"
643 );
644
645 collection.equip(id(EMPTY_FRAME)).unwrap();
646 collection.grant_artifact(id(SECOND_ARTIFACT));
647 assert_eq!(
648 collection.equipped,
649 Some(id(EMPTY_FRAME)),
650 "a duplicate never overrides an explicit choice"
651 );
652 }
653
654 #[test]
655 fn an_unowned_artifact_cannot_be_worn() {
656 let mut collection = ArtifactCollection::default();
657 assert_eq!(
658 collection.equip(id(SECOND_ARTIFACT)),
659 Err(ArtifactError::UnknownArtifact(id(SECOND_ARTIFACT)))
660 );
661 assert_eq!(collection.equipped, None);
662 }
663
664 #[test]
667 fn duplicates_bank_until_explicit_upgrade_including_at_the_cap() {
668 let mut collection = ArtifactCollection::default();
669
670 collection.grant_artifact(id(SECOND_ARTIFACT));
671 let artifact = collection.get(id(SECOND_ARTIFACT)).unwrap();
672 assert_eq!((artifact.level, artifact.copies), (1, 0));
673
674 collection.grant_artifact(id(SECOND_ARTIFACT));
675 let artifact = collection.get(id(SECOND_ARTIFACT)).unwrap();
676 assert_eq!((artifact.level, artifact.copies), (1, 1));
677 assert_eq!(
678 collection.upgrade_artifact(id(SECOND_ARTIFACT), ladder(2).unwrap(), 3),
679 Ok(2)
680 );
681 assert_eq!(
682 (
683 collection.get(id(SECOND_ARTIFACT)).unwrap().level,
684 collection.get(id(SECOND_ARTIFACT)).unwrap().copies
685 ),
686 (2, 0)
687 );
688
689 collection.grant_artifact(id(SECOND_ARTIFACT));
690 collection.grant_artifact(id(SECOND_ARTIFACT));
691 assert_eq!(
692 collection.upgrade_artifact(id(SECOND_ARTIFACT), ladder(3).unwrap(), 3),
693 Ok(3)
694 );
695
696 collection.grant_artifact(id(SECOND_ARTIFACT));
697 let artifact = collection.get(id(SECOND_ARTIFACT)).unwrap();
698 assert_eq!((artifact.level, artifact.copies), (3, 1));
699 assert_eq!(collection.artifacts.len(), 1, "still one instance");
700 }
701
702 #[test]
703 fn refused_artifact_upgrades_are_atomic() {
704 let mut collection = ArtifactCollection::default();
705 assert_eq!(
706 collection.upgrade_artifact(id(SECOND_ARTIFACT), 1, 3),
707 Err(ArtifactError::UnknownArtifact(id(SECOND_ARTIFACT)))
708 );
709
710 collection.grant_artifact(id(SECOND_ARTIFACT));
711 let before = collection.clone();
712 assert_eq!(
713 collection.upgrade_artifact(id(SECOND_ARTIFACT), 1, 3),
714 Err(ArtifactError::NotEnoughArtifactCopies {
715 artifact: id(SECOND_ARTIFACT),
716 level: 2,
717 required: 1,
718 held: 0,
719 })
720 );
721 assert_eq!(collection, before);
722
723 let artifact = collection.get_mut(id(SECOND_ARTIFACT)).unwrap();
724 artifact.level = 3;
725 artifact.copies = 4;
726 let before = collection.clone();
727 assert_eq!(
728 collection.upgrade_artifact(id(SECOND_ARTIFACT), 2, 3),
729 Err(ArtifactError::ArtifactAlreadyMaxLevel(
730 id(SECOND_ARTIFACT),
731 3
732 ))
733 );
734 assert_eq!(collection, before);
735 }
736
737 #[test]
739 fn a_stone_only_fits_the_socket_its_catalog_entry_names() {
740 let mut collection = ArtifactCollection::default();
741 collection.grant_stone(id(ASPECT_STONE));
742
743 let err = collection
744 .insert_stone(
745 id(ASPECT_STONE),
746 ArtifactSocketSlot::HiddenAspect,
747 ArtifactSocketSlot::VisibleAspect,
748 true,
749 )
750 .unwrap_err();
751 assert_eq!(
752 err,
753 ArtifactError::WrongSocket {
754 socket: ArtifactSocketSlot::HiddenAspect,
755 expected: ArtifactSocketSlot::VisibleAspect,
756 }
757 );
758
759 let err = collection
761 .insert_stone(
762 id(ASPECT_STONE),
763 ArtifactSocketSlot::VisibleLaw,
764 ArtifactSocketSlot::VisibleAspect,
765 true,
766 )
767 .unwrap_err();
768 assert!(matches!(err, ArtifactError::WrongSocket { .. }));
769 assert!(collection.stone(id(ASPECT_STONE)).unwrap().socket.is_none());
770
771 collection
772 .insert_stone(
773 id(ASPECT_STONE),
774 ArtifactSocketSlot::VisibleAspect,
775 ArtifactSocketSlot::VisibleAspect,
776 true,
777 )
778 .unwrap();
779 assert_eq!(
780 collection
781 .socketed(ArtifactSocketSlot::VisibleAspect)
782 .map(|stone| stone.template_id),
783 Some(id(ASPECT_STONE))
784 );
785 }
786
787 #[test]
790 fn a_locked_socket_refuses_the_stone() {
791 let mut collection = ArtifactCollection::default();
792 collection.grant_stone(id(ASPECT_STONE));
793
794 assert_eq!(
795 collection.insert_stone(
796 id(ASPECT_STONE),
797 ArtifactSocketSlot::VisibleAspect,
798 ArtifactSocketSlot::VisibleAspect,
799 false,
800 ),
801 Err(ArtifactError::SocketLocked(
802 ArtifactSocketSlot::VisibleAspect
803 ))
804 );
805 assert!(collection.stone(id(ASPECT_STONE)).unwrap().socket.is_none());
806 }
807
808 #[test]
809 fn a_socket_holds_one_stone_at_a_time() {
810 let mut collection = ArtifactCollection::default();
811 collection.grant_stone(id(ASPECT_STONE));
812 collection.grant_stone(id(OTHER_ASPECT_STONE));
813
814 collection
815 .insert_stone(
816 id(ASPECT_STONE),
817 ArtifactSocketSlot::VisibleAspect,
818 ArtifactSocketSlot::VisibleAspect,
819 true,
820 )
821 .unwrap();
822 assert_eq!(
823 collection.insert_stone(
824 id(OTHER_ASPECT_STONE),
825 ArtifactSocketSlot::VisibleAspect,
826 ArtifactSocketSlot::VisibleAspect,
827 true,
828 ),
829 Err(ArtifactError::SocketOccupied(
830 ArtifactSocketSlot::VisibleAspect
831 ))
832 );
833 }
834
835 #[test]
838 fn stones_survive_an_artifact_swap() {
839 let mut collection = ArtifactCollection::default();
840 collection.grant_artifact(id(EMPTY_FRAME));
841 collection.grant_artifact(id(SECOND_ARTIFACT));
842 collection.grant_stone(id(ASPECT_STONE));
843 collection.grant_stone(id(ASPECT_STONE)); collection.equip(id(EMPTY_FRAME)).unwrap();
845 collection
846 .insert_stone(
847 id(ASPECT_STONE),
848 ArtifactSocketSlot::FlipAspect,
849 ArtifactSocketSlot::FlipAspect,
850 true,
851 )
852 .unwrap();
853 let before = collection.stone(id(ASPECT_STONE)).unwrap().clone();
854
855 collection.equip(id(SECOND_ARTIFACT)).unwrap();
856
857 assert_eq!(collection.stones.len(), 1);
858 assert_eq!(
859 collection.stone(id(ASPECT_STONE)).unwrap(),
860 &before,
861 "level, copies and socket all carried over to the new artifact"
862 );
863 assert_eq!(
864 collection
865 .socketed(ArtifactSocketSlot::FlipAspect)
866 .map(|stone| stone.template_id),
867 Some(id(ASPECT_STONE))
868 );
869 }
870
871 #[test]
872 fn a_removed_stone_keeps_its_level_and_copies() {
873 let mut collection = ArtifactCollection::default();
874 collection.grant_stone(id(ASPECT_STONE));
875 collection.grant_stone(id(ASPECT_STONE));
876 collection
877 .insert_stone(
878 id(ASPECT_STONE),
879 ArtifactSocketSlot::HiddenAspect,
880 ArtifactSocketSlot::HiddenAspect,
881 true,
882 )
883 .unwrap();
884
885 assert_eq!(
886 collection.remove_stone(id(ASPECT_STONE)).unwrap(),
887 ArtifactSocketSlot::HiddenAspect
888 );
889 let stone = collection.stone(id(ASPECT_STONE)).unwrap();
890 assert_eq!((stone.level, stone.copies, stone.socket), (1, 1, None));
891 }
892
893 #[test]
896 fn upgrading_a_stone_only_moves_its_level() {
897 let mut collection = ArtifactCollection::default();
898 collection.grant_stone(id(ASPECT_STONE));
899 for _ in 0..3 {
900 collection.grant_stone(id(ASPECT_STONE));
901 }
902 collection
903 .insert_stone(
904 id(ASPECT_STONE),
905 ArtifactSocketSlot::VisibleAspect,
906 ArtifactSocketSlot::VisibleAspect,
907 true,
908 )
909 .unwrap();
910
911 assert_eq!(collection.upgrade_stone(id(ASPECT_STONE), 2, 5), Ok(2));
912 let stone = collection.stone(id(ASPECT_STONE)).unwrap();
913 assert_eq!(stone.level, 2);
914 assert_eq!(stone.copies, 1);
915 assert_eq!(
916 stone.socket,
917 Some(ArtifactSocketSlot::VisibleAspect),
918 "an upgrade never disturbs the socket"
919 );
920 }
921
922 #[test]
923 fn a_short_bank_or_a_capped_stone_spends_nothing() {
924 let mut collection = ArtifactCollection::default();
925 collection.grant_stone(id(ASPECT_STONE));
926
927 assert_eq!(
928 collection.upgrade_stone(id(ASPECT_STONE), 2, 5),
929 Err(ArtifactError::NotEnoughCopies {
930 stone: id(ASPECT_STONE),
931 level: 2,
932 required: 2,
933 held: 0,
934 })
935 );
936 assert_eq!(collection.stone(id(ASPECT_STONE)).unwrap().copies, 0);
937
938 collection.stone_mut(id(ASPECT_STONE)).unwrap().level = 5;
939 collection.stone_mut(id(ASPECT_STONE)).unwrap().copies = 9;
940 assert_eq!(
941 collection.upgrade_stone(id(ASPECT_STONE), 2, 5),
942 Err(ArtifactError::AlreadyMaxLevel(id(ASPECT_STONE), 5))
943 );
944 assert_eq!(collection.stone(id(ASPECT_STONE)).unwrap().copies, 9);
945 }
946
947 #[test]
950 fn only_a_law_stone_carries_a_chosen_law_and_only_a_slotted_one() {
951 let mut collection = ArtifactCollection::default();
952 collection.grant_stone(id(ASPECT_STONE));
953 collection.grant_stone(id(LAW_STONE));
954 let law = id(CHOSEN_LAW);
955
956 assert_eq!(
957 collection.set_law_target(id(ASPECT_STONE), Some(law), false, true),
958 Err(ArtifactError::NotALawStone(id(ASPECT_STONE))),
959 "an Aspect stone has no law to choose"
960 );
961 assert_eq!(
962 collection.set_law_target(id(LAW_STONE), Some(law), true, false),
963 Err(ArtifactError::LawNotSlotted(law)),
964 "a law off the core is not a legal target"
965 );
966 assert_eq!(collection.stone(id(LAW_STONE)).unwrap().law_target, None);
967
968 collection
969 .set_law_target(id(LAW_STONE), Some(law), true, true)
970 .unwrap();
971 assert_eq!(
972 collection.stone(id(LAW_STONE)).unwrap().law_target,
973 Some(law)
974 );
975
976 collection
978 .set_law_target(id(LAW_STONE), None, true, false)
979 .unwrap();
980 assert_eq!(collection.stone(id(LAW_STONE)).unwrap().law_target, None);
981 }
982
983 #[test]
986 fn forgetting_a_law_clears_every_stone_that_chose_it() {
987 let mut collection = ArtifactCollection::default();
988 collection.grant_stone(id(LAW_STONE));
989 collection.grant_stone(id(OTHER_LAW_STONE));
990 let law = id(CHOSEN_LAW);
991 let other = id(OTHER_LAW);
992
993 collection
994 .set_law_target(id(LAW_STONE), Some(law), true, true)
995 .unwrap();
996 collection
997 .set_law_target(id(OTHER_LAW_STONE), Some(other), true, true)
998 .unwrap();
999
1000 assert!(collection.forget_law_target(law));
1001 assert_eq!(collection.stone(id(LAW_STONE)).unwrap().law_target, None);
1002 assert_eq!(
1003 collection.stone(id(OTHER_LAW_STONE)).unwrap().law_target,
1004 Some(other),
1005 "only the unslotted law is forgotten"
1006 );
1007 assert!(
1008 !collection.forget_law_target(law),
1009 "a second pass has nothing to clear and reports so"
1010 );
1011 }
1012
1013 #[test]
1016 fn unsocketing_a_law_stone_keeps_its_chosen_law() {
1017 let mut collection = ArtifactCollection::default();
1018 collection.grant_stone(id(LAW_STONE));
1019 collection
1020 .insert_stone(
1021 id(LAW_STONE),
1022 ArtifactSocketSlot::VisibleLaw,
1023 ArtifactSocketSlot::VisibleLaw,
1024 true,
1025 )
1026 .unwrap();
1027 collection
1028 .set_law_target(id(LAW_STONE), Some(id(CHOSEN_LAW)), true, true)
1029 .unwrap();
1030
1031 collection.remove_stone(id(LAW_STONE)).unwrap();
1032 let stone = collection.stone(id(LAW_STONE)).unwrap();
1033 assert_eq!(stone.socket, None);
1034 assert_eq!(stone.law_target, Some(id(CHOSEN_LAW)));
1035 }
1036
1037 #[test]
1041 fn a_player_without_artifacts_has_an_inert_collection() {
1042 let collection = ArtifactCollection::default();
1043 assert!(collection.artifacts.is_empty());
1044 assert!(collection.stones.is_empty());
1045 assert_eq!(collection.equipped, None);
1046 for socket in ArtifactSocketSlot::all() {
1047 assert!(collection.socketed(socket).is_none());
1048 }
1049 }
1050}