ipc.p.into_inner().delete_hook(&gpc, gs);
}
if let Some(occilk) = ipc.occilk {
- ig.ioccults.ilks.dispose(occilk);
+ ig.ioccults.ilks.dispose_iilk(occilk);
}
(U{ pcs: vec![(piece, PieceUpdateOp::Delete())],
log: vec![ LogEntry {
let p = IPieceTraitObj::new(p);
(||{
let ilks = &mut ig.ioccults.ilks;
- let occilk = occultable.map(|(ilkname, p_occ)| {
- ilks.create(ilkname, OccultIlkData { p_occ })
+ let occilk = occultable.map(|(lilk, p_occ)| {
+ let data = OccultIlkData { p_occ };
+ match lilk {
+ LOI::Distinct => IOI::Distinct(data),
+ LOI::Mix(ilkname) => IOI::Mix(ilks.create(ilkname, data)),
+ }
});
ig.ipieces.as_mut(modperm).insert(piece, IPiece {
p, occilk,
throw!(SpecError::UnoccultableButRichImageForOccultation)
}
let occ_label = occ_label(occ);
- Some((image.into(), "bad-ilk-mixing-todo".into(), occ_label))
+ Some((image.into(), occ_label))
},
- (Some((image_occ_ilk, image_occ_image)), occ) => {
+ (Some((_, image_occ_image)), occ) => {
let default_occ = default();
let occ = occ.as_ref().unwrap_or(&default_occ);
let occ_label = occ_label(occ);
-
- let our_ilk =
- // We need to invent an ilk to allow coalescing of similar
- // objects. Here "similar" includes dice with the same
- // occulted image, but possibly different sets of faces
- // (ie, different labels).
- //
- // We also disregard the cooldown timer parameters, so
- // similar-looking dice with different cooldowns can be
- // mixed. Such things are pathological anyway.
- //
- // But we don't want to get mixed up with some other things
- // that aren't dice. That would be mad even if they look a
- // bit like us.
- format!("die.{}.{}", nfaces, &image_occ_ilk);
-
- Some((image_occ_image, our_ilk, occ_label))
+ Some((image_occ_image, occ_label))
},
- }.map(|(occ_image, occ_ilk, occ_label)| {
- let occ_ilk = GoodItemName::try_from(occ_ilk)
- .map_err(|e| internal_error_bydebug(&e))?
- .into();
-
+ }.map(|(occ_image, occ_label)| {
+ let occ_ilk = LOI::Distinct;
let our_occ_image = Arc::new(Die {
nfaces, cooldown_time, cooldown_radius, surround_outline,
itemname: itemname.clone(),
}
pub type PieceSpecLoaded = SpecLoaded<dyn PieceTrait>;
pub type PieceSpecLoadedOccultable =
- Option<(OccultIlkName, Arc<dyn InertPieceTrait>)>;
+ Option<(LOccultIlk, Arc<dyn InertPieceTrait>)>;
#[typetag::serde(tag="type")]
pub trait PieceSpec: Debug + Sync + Send + 'static {
#[derive(Debug,Serialize,Deserialize)]
pub struct IPiece {
pub p: IPieceTraitObj,
- pub occilk: Option<OccultIlkOwningId>,
+ pub occilk: Option<IOccultIlk>,
}
deref_to_field!{IPiece, IPieceTraitObj, p}
#[derive(Clone,Copy,Debug,Serialize,Deserialize,Eq,PartialEq)]
struct Passive {
occid: OccId,
- notch: Notch,
+ // If None, this piece does not participate in mixing (IOI::Distinct)
+ permute_notch: Option<Notch>,
}
#[derive(Clone,Debug,Serialize,Deserialize)]
pub fn passive_delete_hook(&self, goccults: &mut GameOccults,
piece: PieceId) {
if_chain! {
- if let Some(Passive { occid, notch }) = self.passive;
+ if let Some(Passive { occid, permute_notch }) = self.passive;
+ if let Some(notch) = permute_notch;
if let Some(occ) = goccults.occults.get_mut(occid);
then {
occ.notches.remove(piece, notch)
{
let mut occk_dbg = None;
let occulted = if_chain! {
- if let Some(Passive { occid, notch }) = gpc.occult.passive;
+ if let Some(Passive { occid, permute_notch }) = gpc.occult.passive;
if let Some(occ) = occults.occults.get(occid);
- if let Some(zg) = occ.notch_zg(notch);
- then {
- let occk = occ.views.get_kind(player)
- .map_displaced(|(displace, z)| {
- let notch: NotchNumber = notch.into();
- let pos = displace.place(occ.ppiece_use_size, notch);
- let z = z.plus_offset(notch)
- .unwrap_or_else(|e| { // eek!
- error!("z coordinate overflow ({:?}), bodging! {:?} {:?}",
- e, piece, &z);
- z.clone()
- });
- (pos, ZLevel { z, zg })
- });
-
- occk_dbg = Some(occk.clone());
- match occk.pri_occulted() {
- Some(o) => o,
- None => {
- trace_dbg!("piece_pri", player, piece, occk_dbg, gpc);
- return None;
+ then { if_chain!{
+ if let Some(notch) = permute_notch;
+ if let Some(zg) = occ.notch_zg(notch);
+ then {
+ let occk = occ.views.get_kind(player)
+ .map_displaced(|(displace, z)| {
+ let notch: NotchNumber = notch.into();
+ let pos = displace.place(occ.ppiece_use_size, notch);
+ let z = z.plus_offset(notch)
+ .unwrap_or_else(|e| { // eek!
+ error!("z coordinate overflow ({:?}), bodging! {:?} {:?}",
+ e, piece, &z);
+ z.clone()
+ });
+ (pos, ZLevel { z, zg })
+ });
+
+ occk_dbg = Some(occk.clone());
+ match occk.pri_occulted() {
+ Some(o) => o,
+ None => {
+ trace_dbg!("piece_pri", player, piece, occk_dbg, gpc);
+ return None;
+ }
}
}
- }
+ else { // No notch, not mixing (so not displacing)
+ match occ.views.get_kind(player) {
+ OccKG::Invisible => {
+ trace_dbg!("piece_pri", player, piece, gpc);
+ return None;
+ }
+ OccKG::Visible => PriOG::Visible(ShowUnocculted(())),
+ OccKG::Scrambled => PriOG::Occulted,
+ OccKG::Displaced(_) => PriOG::Occulted,
+ }
+ }
+ } }
else {
PriOG::Visible(ShowUnocculted(()))
}
impl IPiece {
#[throws(IE)]
- pub fn show_or_instead<'p>(&self, ioccults: &'p IOccults,
+ pub fn show_or_instead<'p>(&'p self, ioccults: &'p IOccults,
y: Option<ShowUnocculted>)
-> Either<ShowUnocculted, /*occulted*/ &'p dyn InertPieceTrait> {
match y {
let occilk = self.occilk.as_ref()
.ok_or_else(|| internal_logic_error(format!(
"occulted non-occultable {:?}", self)))?;
- let occ_data = ioccults.ilks.get(occilk)
+ let occ_data = ioccults.ilks.from_iilk(occilk)
.ok_or_else(|| internal_logic_error(format!(
"occulted ilk vanished {:?} {:?}", self, occilk)))?;
occ_data.p_occ.as_ref()
let piece: Option<PieceId> = if_chain! {
if let Some(p) = piece;
if let Some(gpc) = gs.pieces.get(p);
- if let Some(Passive { occid, notch:_ }) = gpc.occult.passive;
+ if let Some(Passive { occid, permute_notch:_ }) = gpc.occult.passive;
if let Some(occ) = gs.occults.occults.get(occid);
let kind = occ.views.get_kind(player);
if ! kind.at_all_visible();
{
#[derive(Debug,Copy,Clone)]
struct OldNewOcculteds<O> {
- old: Option<(O, Notch)>,
+ old: Option<(O, Option<Notch>)>,
new: Option<O>,
}
impl<O> OldNewOcculteds<O> {
let occulteds = OldNewOcculteds {
old:
- gpc.occult.passive.map(|Passive { occid, notch }| Ok::<_,IE>((
+ gpc.occult.passive.map(|Passive { occid, permute_notch }| Ok::<_,IE>((
Occulted {
occid,
occ: goccults.occults.get(occid).ok_or_else(
|| internal_logic_error("uccultation vanished"))?,
},
- notch,
+ permute_notch,
))).transpose()?,
new:
to_recalculate.mark_dirty(occid);
goccults.occults.get_mut(occid).unwrap()
};
- if let Some((occid, old_notch)) = occulteds.old {
+ if let Some((occid, Some(old_notch))) = occulteds.old {
occultation(goccults, occid)
.notches
.remove(piece, old_notch)
let occ = occultation(goccults, occid);
if let Some(ilk) = wants!( ipc.occilk.as_ref() );
then {
- if_chain!{
- if occ.notches.is_empty();
- if let Some(ilk) = wants!( ioccults.ilks.get(ilk) );
- if let Some(bbox) = want!( Ok = ilk.p_occ.bbox_approx() );
- if let Some(size) = want!( Ok = bbox.br() - bbox.tl(), ?(bbox) );
- then { occ.ppiece_use_size = size; }
+ let permute_notch = match ilk {
+ IOI::Distinct(_) => None,
+ IOI::Mix(ilk) => {
+ if_chain!{
+ if occ.notches.is_empty();
+ if let Some(ilk) = wants!( ioccults.ilks.get(ilk) );
+ if let Some(bbox) = want!( Ok = ilk.p_occ.bbox_approx() );
+ if let Some(size) = want!( Ok = bbox.br() - bbox.tl(), ?(bbox) );
+ then { occ.ppiece_use_size = size; }
+ };
+
+ let notch = occ.notches.insert(zg, piece);
+ Some(notch)
+ }
};
- let notch = occ.notches.insert(zg, piece);
- Some(Passive { occid, notch })
+ Some(Passive { occid, permute_notch })
}
else {
None
pub p_occ: Arc<dyn InertPieceTrait>,
}
+#[derive(Debug,Clone)]
+pub enum LOccultIlk {
+ /// Pieces does not participate in ilk-based mixing.
+ ///
+ /// Such a piece remains distinguishable from all other pieces, and
+ /// trackable, by all players, even when occulted (unless made
+ /// totally invisible, in which case it will still be trackable
+ /// when it returns).
+ Distinct,
+
+ /// Ilk-based mixing
+ ///
+ /// Pieces with the same OccultIlkName will be mixed together when
+ /// occulted, so that players who see the occulted view cannot track
+ /// the individual piece identities. This supports deck-shuffling
+ /// for pickup decks (and for hands etc. hides what the player is
+ /// doing with their own cards, from the other players).
+ Mix(OccultIlkName),
+}
+
+macro_rules! serde_with_compat { {
+ [ #[ $($attrs:meta)* ] ] [ $vis:vis ] [ $($intro:tt)* ]
+ $main:ident=$main_s:literal $new:ident $compat_s:literal
+ [ $($body:tt)* ]
+} => {
+ $(#[ $attrs ])*
+ #[serde(try_from=$compat_s)]
+ $vis $($intro)* $main $($body)*
+
+ #[allow(non_camel_case_types)]
+ $(#[ $attrs ])*
+ #[serde(remote=$main_s)]
+ $($intro)* $new $($body)*
+} }
+
+serde_with_compat!{
+ [ #[derive(Debug,Serialize,Deserialize)] ]
+ [ pub ][ enum ] IOccultIlk="IOccultIlk" IOccultIlk_New "IOccultIlk_Compat" [
+ {
+ Distinct(OccultIlkData),
+ Mix(OccultIlkOwningId),
+ }
+ ]
+}
+
+#[derive(Debug,Deserialize)]
+#[serde(untagged)]
+#[allow(non_camel_case_types)]
+enum IOccultIlk_Compat {
+ V1(OccultIlkOwningId), // Otter 1.0.0
+ V2(#[serde(with="IOccultIlk_New")] IOccultIlk),
+}
+
type Id = OccultIlkId;
type OId = OccultIlkOwningId;
type K = OccultIlkName;
refcount: Refcount,
}
+impl TryFrom<IOccultIlk_Compat> for IOccultIlk {
+ type Error = Infallible;
+ #[throws(Infallible)]
+ fn try_from(compat: IOccultIlk_Compat) -> IOccultIlk { match compat {
+ IOccultIlk_Compat::V1(oioi) => IOI::Mix(oioi),
+ IOccultIlk_Compat::V2(ioi) => ioi,
+ } }
+}
+
impl OccultIlks {
#[throws(as Option)]
pub fn get<I: Borrow<Id>>(&self, id: &I) -> &V {
&self.table.get(*id.borrow())?.v
}
+ #[throws(as Option)]
+ pub fn from_iilk<'r>(&'r self, iilk: &'r IOccultIlk) -> &'r V { match iilk {
+ IOI::Distinct(data) => data,
+ IOI::Mix(id) => self.get(id)?,
+ } }
+ pub fn dispose_iilk(&mut self, iilk: IOccultIlk) { match iilk {
+ IOI::Distinct(_data) => { },
+ IOI::Mix(id) => self.dispose(id),
+ } }
+
pub fn create(&mut self, k: K, v: V) -> OId {
let OccultIlks { lookup, table } = self;
let id = *lookup
pub use OccultationKindGeneral as OccKG;
pub use OccultationKindAlwaysOk as OccKA;
+// occultilks.rs
+pub type LOI = LOccultIlk;
+pub type IOI = IOccultIlk;
+
// pcrender.rs
pub use PriOccultedGeneral as PriOG;
OccData::Back(ilk) => {
if let Some(back) = &back {
let back = back.clone();
- Some((ilk.clone(), back))
+ Some((LOI::Mix(ilk.clone()), back))
} else {
None // We got AliasNotFound, ah well
}
desc: occ.desc.clone(),
outline: occ.outline.clone(),
}) as Arc<dyn InertPieceTrait>;
- Some((occ_name.into_inner(), it))
+ Some((LOI::Mix(occ_name.into_inner()), it))
},
};
error!("{}", internal_error_bydebug(&(occid, &occ, &nr, piece)));
continue;
}}
+ if_let!{ IOI::Mix(occilk) = occilk; else continue; }
let (notches, pieces) = ilks.entry(*occilk.borrow()).or_default();
notches.push(notch);
pieces.push(piece);
new_notches[notch] = NR::Piece(new_piece);
gpieces.get_mut(new_piece).unwrap()
.occult.passive.as_mut().unwrap()
- .notch
- = notch;
+ .permute_notch
+ = Some(notch);
}
}
assert_eq!(&gpc.occult.passive, &None);
}
- if let Some(Passive { occid, notch }) = gpc.occult.passive {
+ if let Some(Passive { occid, permute_notch }) = gpc.occult.passive {
let occ = goccults.occults.get(occid).unwrap();
- assert_eq!(occ.notches.table[notch], NR::Piece(piece));
+ if let Some(notch) = permute_notch {
+ assert_eq!(occ.notches.table[notch], NR::Piece(piece));
+ }
}
}
let pgpc = gpieces.get(ppiece).unwrap();
let passive = pgpc.occult.passive.as_ref().unwrap();
assert_eq!(passive.occid, occid);
- assert_eq!(passive.notch, notch);
+ assert_eq!(passive.permute_notch.unwrap(), notch);
}
let nfree1 = occ.notches.table.iter()