diff --git a/solitaire_engine/src/card_plugin.rs b/solitaire_engine/src/card_plugin.rs index 546e074..2c1573b 100644 --- a/solitaire_engine/src/card_plugin.rs +++ b/solitaire_engine/src/card_plugin.rs @@ -1487,6 +1487,7 @@ fn update_drag_shadow( drag: Res, layout: Option>, card_entities: Query<(&CardEntity, &Transform)>, + card_index: Res, mut shadow: Local>, ) { if drag.is_idle() { @@ -1503,9 +1504,9 @@ fn update_drag_shadow( // Find the world position of the first (top) dragged card. let top_pos = drag.cards.first().and_then(|first_card| { - card_entities - .iter() - .find(|(marker, _)| marker.card == *first_card) + card_index + .get(first_card) + .and_then(|entity| card_entities.get(entity).ok()) .map(|(_, t)| t.translation) }); diff --git a/solitaire_engine/src/input_plugin.rs b/solitaire_engine/src/input_plugin.rs index 5477b2c..e3be4ac 100644 --- a/solitaire_engine/src/input_plugin.rs +++ b/solitaire_engine/src/input_plugin.rs @@ -33,7 +33,9 @@ use solitaire_core::game_state::GameState; use crate::auto_complete_plugin::AutoCompleteState; use crate::card_animation::tuning::AnimationTuning; use crate::card_animation::{CardAnimation, MotionCurve}; -use crate::card_plugin::{CardEntity, HintHighlight, HintHighlightTimer, STACK_FAN_FRAC}; +use crate::card_plugin::{ + CardEntity, CardEntityIndex, HintHighlight, HintHighlightTimer, STACK_FAN_FRAC, +}; use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL; use crate::events::{ DrawRequestEvent, ForfeitRequestEvent, HintVisualEvent, InfoToastEvent, MoveRejectedEvent, @@ -121,6 +123,10 @@ impl Plugin for InputPlugin { .init_resource::() .init_resource::() .init_resource::() + // The drag systems resolve cards via `CardEntityIndex`; `CardPlugin` + // owns and rebuilds it, but init here too so `InputPlugin` is + // self-sufficient in tests (idempotent if already registered). + .init_resource::() .add_message::() .add_message::() .add_message::() @@ -674,6 +680,7 @@ fn follow_drag( layout: Option>, tuning: Res, mut card_transforms: Query<(&CardEntity, &mut Transform, &mut Sprite)>, + card_index: Res, ) { // Skip if idle or if a touch drag is running. if drag.is_idle() || drag.active_touch_id.is_some() { @@ -704,9 +711,8 @@ fn follow_drag( // Elevate cards: push to DRAG_Z and dim slightly so the board // beneath stays readable. for (i, card) in drag.cards.iter().enumerate() { - if let Some((_, mut transform, mut sprite)) = card_transforms - .iter_mut() - .find(|(ce, _, _)| ce.card == *card) + if let Some(entity) = card_index.get(card) + && let Ok((_, mut transform, mut sprite)) = card_transforms.get_mut(entity) { transform.translation.z = dragged_card_z(i); sprite.color.set_alpha(0.85); @@ -719,9 +725,8 @@ fn follow_drag( let fan = -layout.0.card_size.y * layout.0.tableau_fan_frac; for (i, card) in drag.cards.iter().enumerate() { - if let Some((_, mut transform, _)) = card_transforms - .iter_mut() - .find(|(ce, _, _)| ce.card == *card) + if let Some(entity) = card_index.get(card) + && let Ok((_, mut transform, _)) = card_transforms.get_mut(entity) { transform.translation.x = bottom_pos.x; transform.translation.y = bottom_pos.y + fan * i as f32; @@ -743,6 +748,7 @@ fn end_drag( mut changed: MessageWriter, mut commands: Commands, card_entities: Query<(Entity, &CardEntity, &Transform)>, + card_index: Res, ) { if paused.is_some_and(|p| p.0) { drag.clear(); @@ -830,9 +836,8 @@ fn end_drag( continue; }; let target_pos = card_position(&game.0, &layout.0, &origin, stack_index); - if let Some((entity, _, transform)) = card_entities - .iter() - .find(|(_, ce, _)| ce.card == *card) + if let Some(entity) = card_index.get(card) + && let Ok((_, _, transform)) = card_entities.get(entity) { let drag_pos = transform.translation.truncate(); let drag_z = transform.translation.z; @@ -930,6 +935,7 @@ fn touch_follow_drag( layout: Option>, tuning: Res, mut card_transforms: Query<(&CardEntity, &mut Transform, &mut Sprite)>, + card_index: Res, ) { let Some(active_id) = drag.active_touch_id else { return; // Mouse drag or idle. @@ -957,9 +963,8 @@ fn touch_follow_drag( drag.committed = true; for (i, card) in drag.cards.iter().enumerate() { - if let Some((_, mut transform, mut sprite)) = card_transforms - .iter_mut() - .find(|(ce, _, _)| ce.card == *card) + if let Some(entity) = card_index.get(card) + && let Ok((_, mut transform, mut sprite)) = card_transforms.get_mut(entity) { transform.translation.z = dragged_card_z(i); sprite.color.set_alpha(0.85); @@ -971,9 +976,8 @@ fn touch_follow_drag( let fan = -layout.0.card_size.y * layout.0.tableau_fan_frac; for (i, card) in drag.cards.iter().enumerate() { - if let Some((_, mut transform, _)) = card_transforms - .iter_mut() - .find(|(ce, _, _)| ce.card == *card) + if let Some(entity) = card_index.get(card) + && let Ok((_, mut transform, _)) = card_transforms.get_mut(entity) { transform.translation.x = bottom_pos.x; transform.translation.y = bottom_pos.y + fan * i as f32; @@ -998,6 +1002,7 @@ fn touch_end_drag( mut changed: MessageWriter, mut commands: Commands, card_entities: Query<(Entity, &CardEntity, &Transform)>, + card_index: Res, ) { let Some(active_id) = drag.active_touch_id else { return; // Mouse drag or idle. @@ -1070,9 +1075,8 @@ fn touch_end_drag( continue; }; let target_pos = card_position(&game.0, &layout.0, &origin, stack_index); - if let Some((entity, _, transform)) = card_entities - .iter() - .find(|(_, ce, _)| ce.card == *card) + if let Some(entity) = card_index.get(card) + && let Ok((_, _, transform)) = card_entities.get(entity) { let drag_pos = transform.translation.truncate(); let drag_z = transform.translation.z;