yog-event 0.8.0

Yog event system — pub/sub for game/server events.
Documentation
//! Handles: one entity, player or dimension held by its key, each method
//! the action of that domain with the key put in first (generated by
//! `actions!` from the actions themselves — nothing here repeats them).
//!
//! ```ignore
//! let me = Player::new(&e.player_name);
//! me.give_item("minecraft:diamond", 1u32);
//! if let Some(body) = me.entity() {
//!     body.teleport((0.5, 100.0, 0.5));
//! }
//! ```

use yog_core::{BlockPos, join_state, split_state};
use yog_ids::sep::{PROPERTY_IS, PROPERTY_SEP};

/// An entity, by its uuid: every action whose first field is `uuid`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Entity {
    pub uuid: String,
}

impl Entity {
    pub fn new(uuid: impl Into<String>) -> Self {
        Self { uuid: uuid.into() }
    }
}

/// An online player, by name: every action whose first field is `player`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Player {
    pub name: String,
}

impl Player {
    pub fn new(name: impl Into<String>) -> Self {
        Self { name: name.into() }
    }

    /// The player as an entity (its uuid asked of the server); `None` offline.
    pub fn entity(&self) -> Option<Entity> {
        self.info().map(|i| Entity::new(i.uuid))
    }
}

/// A dimension (`minecraft:overworld`): every action whose first field is `dim`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct World {
    pub dim: String,
}

impl World {
    pub fn new(dim: impl Into<String>) -> Self {
        Self { dim: dim.into() }
    }

    pub fn overworld() -> Self {
        Self::new(yog_ids::game::OVERWORLD)
    }

    /// Property `name` of the block at `pos` (`"energized"` → `"true"`).
    pub fn property(&self, pos: BlockPos, name: &str) -> Option<String> {
        let state = self.state(pos)?;
        split_state(&state)
            .1
            .into_iter()
            .find(|(k, _)| *k == name)
            .map(|(_, v)| v.to_owned())
    }

    /// Sets some properties of the block at `pos` (`"energized=true,powered=false"`),
    /// keeping the rest; `false` if there's no block or the state doesn't take.
    pub fn set_properties(&self, pos: BlockPos, properties: &str) -> bool {
        let Some(state) = self.state(pos) else {
            return false;
        };
        let (id, mut props) = split_state(&state);
        for pair in properties.split(PROPERTY_SEP).filter(|p| !p.is_empty()) {
            let Some((k, v)) = pair.split_once(PROPERTY_IS) else {
                return false;
            };
            match props.iter_mut().find(|(n, _)| *n == k) {
                Some(p) => p.1 = v,
                None => props.push((k, v)),
            }
        }
        self.set_state(pos, join_state(id, &props))
    }
}