use std::collections::{HashMap, HashSet};
use fmc::{
bevy::ecs::system::EntityCommands,
blocks::{BlockData, BlockPosition, Blocks},
interfaces::{HeldInterfaceStack, InterfaceEvents, RegisterInterfaceNode},
items::ItemStack,
networking::Server,
players::Player,
prelude::*,
protocol::messages,
world::BlockUpdate,
};
use serde::{Deserialize, Serialize};
use crate::players::HandInteractions;
pub struct ChestPlugin;
impl Plugin for ChestPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(ChestRegistry::default())
.add_systems(Startup, setup)
.add_systems(
Update,
(handle_block_hits, handle_interface_events, handle_despawn),
);
}
}
#[derive(Component, Serialize, Deserialize)]
struct Chest {
inventory: Vec<ItemStack>,
}
impl Chest {
fn new() -> Self {
Self {
inventory: vec![ItemStack::default(); 27],
}
}
fn build_interface(&self) -> messages::InterfaceItemBoxUpdate {
let mut item_box_update = messages::InterfaceItemBoxUpdate::default();
for (i, item_stack) in self.inventory.iter().enumerate() {
if !item_stack.is_empty() {
item_box_update.add_itembox(
"chest",
i as u32,
item_stack.item().unwrap().id,
item_stack.size(),
None,
None,
);
} else {
item_box_update.add_empty_itembox("chest", i as u32);
}
}
item_box_update
}
}
#[derive(Resource, Default)]
struct ChestRegistry {
chest_to_players: HashMap<Entity, HashSet<Entity>>,
player_to_chest: HashMap<Entity, Entity>,
}
impl ChestRegistry {
fn remove_chest(&mut self, crafting_table_entity: Entity) {
if let Some(player_entities) = self.chest_to_players.remove(&crafting_table_entity) {
for entity in player_entities {
self.player_to_chest.remove(&entity);
}
}
}
fn set_active_chest(&mut self, player_entity: Entity, crafting_table_entity: Entity) {
if let Some(old_table_entity) = self.player_to_chest.remove(&player_entity) {
self.chest_to_players
.get_mut(&old_table_entity)
.unwrap()
.remove(&player_entity);
}
self.chest_to_players
.entry(crafting_table_entity)
.or_default()
.insert(player_entity);
self.player_to_chest
.insert(player_entity, crafting_table_entity);
}
}
fn setup(mut blocks: ResMut<Blocks>) {
let block_id = blocks.get_id("chest");
let block = blocks.get_config_mut(&block_id);
block.set_spawn_function(spawn_function);
}
fn spawn_function(commands: &mut EntityCommands, block_data: Option<&BlockData>) {
if let Some(block_data) = block_data {
let chest: Chest = serde_json::from_slice(&block_data.0).unwrap();
commands.insert(chest);
} else {
commands.insert(Chest::new());
}
commands.insert(HandInteractions::default());
}
fn handle_interface_events(
net: Res<Server>,
registry: Res<ChestRegistry>,
mut player_query: Query<&mut HeldInterfaceStack, With<Player>>,
mut input_events: Query<
(Entity, &BlockPosition, &mut Chest, &mut InterfaceEvents),
Changed<InterfaceEvents>,
>,
mut block_update_writer: EventWriter<BlockUpdate>,
) {
for (chest_entity, block_position, mut chest, mut events) in input_events.iter_mut() {
for event in events.read() {
let mut held_item = player_query.get_mut(event.player_entity).unwrap();
if let messages::InterfaceInteraction::TakeItem {
quantity, index, ..
} = *event
{
let Some(item_stack) = chest.inventory.get_mut(index as usize) else {
continue;
};
item_stack.transfer_to(&mut held_item, quantity);
} else if let messages::InterfaceInteraction::PlaceItem {
quantity, index, ..
} = *event
{
let Some(item_stack) = chest.inventory.get_mut(index as usize) else {
continue;
};
held_item.transfer_to(item_stack, quantity);
}
block_update_writer.send(BlockUpdate::Data {
position: *block_position,
block_data: Some(serde_json::to_vec(&*chest).map(BlockData).unwrap()),
});
net.send_many(
®istry.chest_to_players[&chest_entity],
chest.build_interface(),
);
}
}
}
fn handle_block_hits(
net: Res<Server>,
mut registry: ResMut<ChestRegistry>,
mut block_hits: Query<(Entity, &Chest, &mut HandInteractions), Changed<HandInteractions>>,
mut registration_events: EventWriter<RegisterInterfaceNode>,
) {
for (chest_entity, chest, mut block_hits) in block_hits.iter_mut() {
for player_entity in block_hits.read() {
registry.set_active_chest(player_entity, chest_entity);
registration_events.send(RegisterInterfaceNode {
player_entity,
node_path: "chest".to_owned(),
node_entity: chest_entity,
});
net.send_one(player_entity, chest.build_interface());
net.send_one(
player_entity,
messages::InterfaceVisibilityUpdate {
interface_path: "chest".to_owned(),
visible: true,
},
);
}
}
}
fn handle_despawn(
mut registry: ResMut<ChestRegistry>,
mut despawned_tables: RemovedComponents<Chest>,
) {
for chest_entity in despawned_tables.read() {
registry.remove_chest(chest_entity)
}
}