use std::collections::{BTreeMap, VecDeque};
use bevy_ecs::prelude::*;
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use rig_core::observe::Emitter;
use serde::{Deserialize, Serialize};
use super::{EffectOutcome, Held, Issued};
#[derive(Component, Debug, Clone, Serialize, Deserialize, bevy_reflect::Reflect)]
#[reflect(opaque, Component, Debug, Serialize, Deserialize)]
pub struct HoldOwners(BTreeMap<String, Emitter>);
impl HoldOwners {
pub fn owners(&self) -> impl Iterator<Item = &Emitter> {
self.0.values()
}
}
#[derive(EntityEvent)]
pub struct HoldTransition {
pub entity: Entity,
pub owner: Emitter,
pub acquired: bool,
}
#[derive(Resource, Default)]
struct Transitions {
depth: usize,
publishing: bool,
pending: VecDeque<HoldTransition>,
}
pub fn forget_owner(world: &mut World, entity: Entity, owner: &str) {
let Some(mut owners) = world.get::<HoldOwners>(entity).cloned() else {
return;
};
let Some(emitter) = owners.0.remove(owner) else {
return;
};
begin(
world,
HoldTransition {
entity,
owner: emitter,
acquired: false,
},
);
if owners.0.is_empty() {
world.entity_mut(entity).remove::<HoldOwners>();
} else {
world.entity_mut(entity).insert(owners);
}
finish(world);
}
fn begin(world: &mut World, event: HoldTransition) {
let mut transitions = world.get_resource_or_init::<Transitions>();
transitions.depth += 1;
transitions.pending.push_back(event);
}
fn finish(world: &mut World) {
let mut transitions = world.resource_mut::<Transitions>();
transitions.depth -= 1;
if transitions.depth != 0 || transitions.publishing {
return;
}
transitions.publishing = true;
loop {
let event = world.resource_mut::<Transitions>().pending.pop_front();
let Some(event) = event else { break };
if world.get_entity(event.entity).is_ok() {
world.trigger(event);
}
}
world.resource_mut::<Transitions>().publishing = false;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HoldRefused {
Missing,
Settled,
Issued,
AlreadyHeld,
NotHeld,
}
impl std::fmt::Display for HoldRefused {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Missing => "the effect entity does not exist",
Self::Settled => "the effect already has an outcome",
Self::Issued => "the effect was already dispatched",
Self::AlreadyHeld => "this owner already holds the effect",
Self::NotHeld => "this owner does not hold the effect",
})
}
}
impl std::error::Error for HoldRefused {}
pub fn acquire_hold(world: &mut World, entity: Entity, owner: Emitter) -> Result<(), HoldRefused> {
let Ok(effect) = world.get_entity(entity) else {
return Err(HoldRefused::Missing);
};
if effect.contains::<EffectOutcome>() {
return Err(HoldRefused::Settled);
}
if effect.contains::<Issued>() {
return Err(HoldRefused::Issued);
}
let mut owners = effect.get::<HoldOwners>().cloned().unwrap_or_else(|| {
let mut owners = BTreeMap::new();
if effect.contains::<Held>() {
let unknown = Emitter::unknown();
owners.insert(unknown.name.clone(), unknown);
}
HoldOwners(owners)
});
if owners.0.contains_key(&owner.name) {
return Err(HoldRefused::AlreadyHeld);
}
owners.0.insert(owner.name.clone(), owner.clone());
begin(
world,
HoldTransition {
entity,
owner,
acquired: true,
},
);
world.entity_mut(entity).insert(owners);
if world
.get::<HoldOwners>(entity)
.is_some_and(|owners| !owners.0.is_empty())
{
world.entity_mut(entity).insert(Held);
}
finish(world);
Ok(())
}
pub fn release_hold(world: &mut World, entity: Entity, owner: &str) -> Result<(), HoldRefused> {
let mut owners = match world.get::<HoldOwners>(entity).cloned() {
Some(owners) => owners,
None if world.get::<Held>(entity).is_some() && owner == Emitter::unknown().name => {
let unknown = Emitter::unknown();
HoldOwners(BTreeMap::from([(unknown.name.clone(), unknown)]))
}
None => return Err(HoldRefused::NotHeld),
};
let Some(owner) = owners.0.remove(owner) else {
return Err(HoldRefused::NotHeld);
};
begin(
world,
HoldTransition {
entity,
owner,
acquired: false,
},
);
world.entity_mut(entity).insert(owners);
if world
.get::<HoldOwners>(entity)
.is_some_and(|owners| owners.0.is_empty())
{
world.entity_mut(entity).remove::<Held>();
if world
.get::<HoldOwners>(entity)
.is_some_and(|owners| owners.0.is_empty())
{
world.entity_mut(entity).remove::<HoldOwners>();
}
}
finish(world);
Ok(())
}