use std::collections::{HashMap, HashSet};
use fmc::{
bevy::ecs::system::EntityCommands,
blocks::{BlockData, Blocks},
interfaces::{HeldInterfaceStack, InterfaceEvents, RegisterInterfaceNode},
items::{ItemStack, Items},
networking::Server,
players::Player,
prelude::*,
protocol::messages,
};
use serde::{Deserialize, Serialize};
use crate::{
items::crafting::{CraftingGrid, Recipes},
players::HandInteractions,
};
pub struct CraftingTablePlugin;
impl Plugin for CraftingTablePlugin {
fn build(&self, app: &mut App) {
app.insert_resource(CraftingTableRegistry::default())
.add_systems(Startup, setup)
.add_systems(
Update,
(handle_block_hits, handle_interface_events, handle_despawn),
);
}
}
#[derive(Component, Deref, DerefMut, Serialize, Deserialize)]
struct CraftingTable(CraftingGrid);
impl CraftingTable {
fn build_input_interface(&self, interface_update: &mut messages::InterfaceItemBoxUpdate) {
for (i, item_stack) in self.iter().enumerate() {
if let Some(item) = item_stack.item() {
interface_update.add_itembox(
"crafting_table/input",
i as u32,
item.id,
item_stack.size(),
None,
None,
);
} else {
interface_update.add_empty_itembox("crafting_table/input", i as u32);
}
}
}
fn build_output_interface(
&self,
recipes: &Recipes,
interface_update: &mut messages::InterfaceItemBoxUpdate,
) {
if let Some(output) = recipes.get("crafting").get_output(self) {
interface_update.add_itembox(
"crafting_table/output",
0,
output.item().unwrap().id,
output.capacity(),
None,
None,
);
} else {
interface_update.add_empty_itembox("crafting_table/output", 0);
}
}
}
#[derive(Resource, Default)]
struct CraftingTableRegistry {
table_to_players: HashMap<Entity, HashSet<Entity>>,
player_to_table: HashMap<Entity, Entity>,
}
impl CraftingTableRegistry {
fn remove_table(&mut self, crafting_table_entity: Entity) {
if let Some(player_entities) = self.table_to_players.remove(&crafting_table_entity) {
for entity in player_entities {
self.player_to_table.remove(&entity);
}
}
}
fn set_active_table(&mut self, player_entity: Entity, crafting_table_entity: Entity) {
if let Some(old_table_entity) = self.player_to_table.remove(&player_entity) {
self.table_to_players
.get_mut(&old_table_entity)
.unwrap()
.remove(&player_entity);
}
self.table_to_players
.entry(crafting_table_entity)
.or_default()
.insert(player_entity);
self.player_to_table
.insert(player_entity, crafting_table_entity);
}
}
fn setup(mut blocks: ResMut<Blocks>) {
let block_id = blocks.get_id("crafting_table");
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 crafting_table: CraftingTable = bincode::deserialize(&*block_data).unwrap();
commands.insert(crafting_table);
} else {
commands.insert(CraftingTable(CraftingGrid::with_size(9)));
}
commands.insert(HandInteractions::default());
}
fn handle_interface_events(
net: Res<Server>,
registry: Res<CraftingTableRegistry>,
recipes: Res<Recipes>,
mut player_query: Query<&mut HeldInterfaceStack, With<Player>>,
mut input_events: Query<
(Entity, &mut CraftingTable, &mut InterfaceEvents),
Changed<InterfaceEvents>,
>,
) {
for (crafting_table_entity, mut crafting_table, mut events) in input_events.iter_mut() {
for event in events.read() {
let mut held_item = player_query.get_mut(event.player_entity).unwrap();
let mut interface_update = messages::InterfaceItemBoxUpdate::default();
if let messages::InterfaceInteraction::TakeItem {
interface_path,
index,
quantity,
} = &*event
{
if interface_path.ends_with("input") {
let Some(item_stack) = crafting_table.get_mut(*index as usize) else {
continue;
};
item_stack.transfer_to(&mut held_item, *quantity);
crafting_table.build_output_interface(&recipes, &mut interface_update);
} else if interface_path.ends_with("output") {
let Some(output) = recipes.get("crafting").get_output(&crafting_table) else {
continue;
};
if held_item.is_empty() || held_item.item() == output.item() {
let amount = if held_item.is_empty() {
*quantity
} else {
std::cmp::min(held_item.remaining_capacity(), *quantity)
};
if let Some(mut item_stack) =
recipes.get("crafting").craft(&mut crafting_table, amount)
{
item_stack.transfer_to(&mut held_item, u32::MAX);
} else {
continue;
}
crafting_table.build_input_interface(&mut interface_update);
crafting_table.build_output_interface(&recipes, &mut interface_update);
}
}
} else if let messages::InterfaceInteraction::PlaceItem {
interface_path,
index,
quantity,
} = &*event
{
if !interface_path.ends_with("input") {
continue;
}
let Some(item_stack) = crafting_table.get_mut(*index as usize) else {
continue;
};
held_item.transfer_to(item_stack, *quantity);
crafting_table.build_output_interface(&recipes, &mut interface_update);
}
if !interface_update.updates.is_empty() {
net.send_many(
®istry.table_to_players[&crafting_table_entity],
interface_update,
);
}
}
}
}
fn handle_block_hits(
net: Res<Server>,
mut registry: ResMut<CraftingTableRegistry>,
recipes: Res<Recipes>,
mut block_hits: Query<
(Entity, &CraftingTable, &mut HandInteractions),
Changed<HandInteractions>,
>,
mut registration_events: EventWriter<RegisterInterfaceNode>,
) {
for (crafting_table_entity, crafting_table, mut block_hits) in block_hits.iter_mut() {
for player_entity in block_hits.read() {
registry.set_active_table(player_entity, crafting_table_entity);
registration_events.send(RegisterInterfaceNode {
player_entity,
node_path: String::from("crafting_table/input"),
node_entity: crafting_table_entity,
});
registration_events.send(RegisterInterfaceNode {
player_entity,
node_path: String::from("crafting_table/output"),
node_entity: crafting_table_entity,
});
let mut itembox_update = messages::InterfaceItemBoxUpdate::default();
crafting_table.build_input_interface(&mut itembox_update);
crafting_table.build_output_interface(&recipes, &mut itembox_update);
net.send_one(player_entity, itembox_update);
net.send_one(
player_entity,
messages::InterfaceVisibilityUpdate {
interface_path: "crafting_table".to_owned(),
visible: true,
},
);
}
}
}
fn save_state(mut table_query: Query<(&CraftingTable, &mut BlockData), Changed<CraftingTable>>) {
for (crafting_table, mut block_data) in table_query.iter_mut() {
*block_data = bincode::serialize(crafting_table).map(BlockData).unwrap();
}
}
fn handle_despawn(
mut registry: ResMut<CraftingTableRegistry>,
mut despawned_tables: RemovedComponents<CraftingTable>,
) {
for crafting_table_entity in despawned_tables.read() {
registry.remove_table(crafting_table_entity)
}
}