From d61341e9db6dd9cf3d06508b0f26c9aa33ae0154 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Sun, 17 Apr 2022 18:11:44 +0100 Subject: [PATCH] ilk mixing: Support non-mixing pieces Allow us to handle an pieces which are occultable but which don't participate in shuffling and displacement. I. Introduce IOccultIlk enum. The old approach is the variant Mix. This allows us to introduce a new approach Distinct. II. Make Notch optional in Passive Replace the unconditional OccultIlkOwningId in IPiece with a new enum IOccultIlk, and the OccultIlkName in the return value from PieceSpec::load with a new enum LOccultIlk. III. Immediately use the new approach for dice, dropping all handling of ilks. (Combining this into this commit avoids writing a bunch of daft code that will be deleted right away.) Consequences and ancillary changes: * We need a new compatibility arrangement so we can load old savefiles. This is a bit fiddly and involves some macro trickery (which we will be moved to another file in due course). * OccultIlks gets two new methods from_iilk and dispose_iilk for handling IOccultIlk, rather than merely OccultIlkName. (We're going to move creation in here too, shortly.) * All existing pieces other than dice are still mixed by ilk. Allowing this not to be the case ia a question for the future. * Rename Passive.notch to Passive.permute_notch, for clarity of its purpose. Signed-off-by: Ian Jackson --- daemon/cmdlistener.rs | 10 ++-- src/dice.rs | 29 ++---------- src/gamestate.rs | 2 +- src/global.rs | 2 +- src/hidden.rs | 103 ++++++++++++++++++++++++++---------------- src/occultilks.rs | 72 +++++++++++++++++++++++++++++ src/prelude.rs | 4 ++ src/shapelib.rs | 4 +- src/vpid.rs | 13 ++++-- 9 files changed, 163 insertions(+), 76 deletions(-) diff --git a/daemon/cmdlistener.rs b/daemon/cmdlistener.rs index ebf38ee0..c55116d7 100644 --- a/daemon/cmdlistener.rs +++ b/daemon/cmdlistener.rs @@ -1042,7 +1042,7 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>( 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 { @@ -1122,8 +1122,12 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>( 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, diff --git a/src/dice.rs b/src/dice.rs index 9101e520..1e05dea1 100644 --- a/src/dice.rs +++ b/src/dice.rs @@ -194,35 +194,16 @@ impl PieceSpec for Spec { 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(), diff --git a/src/gamestate.rs b/src/gamestate.rs index cf92b985..e8c7fffb 100644 --- a/src/gamestate.rs +++ b/src/gamestate.rs @@ -289,7 +289,7 @@ pub struct SpecLoaded { } pub type PieceSpecLoaded = SpecLoaded; pub type PieceSpecLoadedOccultable = - Option<(OccultIlkName, Arc)>; + Option<(LOccultIlk, Arc)>; #[typetag::serde(tag="type")] pub trait PieceSpec: Debug + Sync + Send + 'static { diff --git a/src/global.rs b/src/global.rs index 5b70f336..18cccd6d 100644 --- a/src/global.rs +++ b/src/global.rs @@ -79,7 +79,7 @@ pub struct IPlayer { // usual variable: ipl #[derive(Debug,Serialize,Deserialize)] pub struct IPiece { pub p: IPieceTraitObj, - pub occilk: Option, + pub occilk: Option, } deref_to_field!{IPiece, IPieceTraitObj, p} diff --git a/src/hidden.rs b/src/hidden.rs index 5a0e6877..a8926434 100644 --- a/src/hidden.rs +++ b/src/hidden.rs @@ -44,7 +44,8 @@ pub struct PieceOccult { #[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, } #[derive(Clone,Debug,Serialize,Deserialize)] @@ -151,7 +152,8 @@ impl PieceOccult { 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) @@ -287,32 +289,46 @@ pub fn piece_pri( { 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(())) } @@ -401,7 +417,7 @@ impl IPieceTraitObj { 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) -> Either { match y { @@ -410,7 +426,7 @@ impl IPiece { 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() @@ -471,7 +487,7 @@ pub fn vpiece_decode( let piece: Option = 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(); @@ -508,7 +524,7 @@ fn recalculate_occultation_general< { #[derive(Debug,Copy,Clone)] struct OldNewOcculteds { - old: Option<(O, Notch)>, + old: Option<(O, Option)>, new: Option, } impl OldNewOcculteds { @@ -541,13 +557,13 @@ fn recalculate_occultation_general< 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: @@ -686,7 +702,7 @@ fn recalculate_occultation_general< 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) @@ -698,15 +714,22 @@ fn recalculate_occultation_general< 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 diff --git a/src/occultilks.rs b/src/occultilks.rs index f7ac81a7..bdce5f68 100644 --- a/src/occultilks.rs +++ b/src/occultilks.rs @@ -18,6 +18,59 @@ pub struct OccultIlkData { pub p_occ: Arc, } +#[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; @@ -37,12 +90,31 @@ pub struct Data { refcount: Refcount, } +impl TryFrom 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>(&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 diff --git a/src/prelude.rs b/src/prelude.rs index ef2c9a6a..c9ea63c4 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -218,6 +218,10 @@ pub type OccK = OccultationKind; 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; diff --git a/src/shapelib.rs b/src/shapelib.rs index b38ee293..b075722c 100644 --- a/src/shapelib.rs +++ b/src/shapelib.rs @@ -583,7 +583,7 @@ impl Contents { 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 } @@ -604,7 +604,7 @@ impl Contents { desc: occ.desc.clone(), outline: occ.outline.clone(), }) as Arc; - Some((occ_name.into_inner(), it)) + Some((LOI::Mix(occ_name.into_inner()), it)) }, }; diff --git a/src/vpid.rs b/src/vpid.rs index bb5f3d19..d463908b 100644 --- a/src/vpid.rs +++ b/src/vpid.rs @@ -301,6 +301,7 @@ pub fn permute(occid: OccId, 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); @@ -327,8 +328,8 @@ pub fn permute(occid: OccId, new_notches[notch] = NR::Piece(new_piece); gpieces.get_mut(new_piece).unwrap() .occult.passive.as_mut().unwrap() - .notch - = notch; + .permute_notch + = Some(notch); } } @@ -400,9 +401,11 @@ pub fn consistency_check( 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)); + } } } @@ -416,7 +419,7 @@ pub fn consistency_check( 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() -- 2.30.2