a bit of refactor, and actually enforce player inventory condition rules on handle_item_change

This commit is contained in:
Edvin Bryntesson 2024-09-07 16:31:24 +02:00
parent 3b6fc48a0f
commit 59a950c606
No known key found for this signature in database
7 changed files with 80 additions and 74 deletions

View File

@ -52,20 +52,14 @@ pub enum WindowType {
CartographyTable,
Stonecutter,
}
pub struct ContainerStruct<const SLOTS: usize>([Option<ItemStack>; SLOTS]);
impl WindowType {
pub const fn default_title(&self) -> &'static str {
// TODO: Add titles here:
/*match self {
_ => "WINDOW TITLE",
}*/
"WINDOW TITLE"
}
}
// Container needs Sync + Send to be able to be in async Server
pub trait Container: Sync + Send {
fn window_type(&self) -> &'static WindowType;
fn window_name(&self) -> &'static str;
fn handle_item_change(
&mut self,
carried_item: &mut Option<ItemStack>,
@ -211,6 +205,13 @@ impl<'a> Container for OptionallyCombinedContainer<'a, 'a> {
}
}
fn window_name(&self) -> &'static str {
self.container
.as_ref()
.map(|container| container.window_name())
.unwrap_or(self.inventory.window_name())
}
fn all_slots(&mut self) -> Vec<&mut Option<ItemStack>> {
let slots = match &mut self.container {
Some(container) => {

View File

@ -59,6 +59,9 @@ impl Container for Chest {
&WindowType::Generic9x3
}
fn window_name(&self) -> &'static str {
"Chest"
}
fn all_slots(&mut self) -> Vec<&mut Option<ItemStack>> {
self.0.iter_mut().collect()
}

View File

@ -36,7 +36,6 @@ impl PlayerInventory {
total_opened_containers: 2,
}
}
/// Set the contents of an item in a slot
///
/// ## Slot
@ -48,48 +47,44 @@ impl PlayerInventory {
/// ## Item allowed override
/// An override, which when enabled, makes it so that invalid items, can be placed in slots they normally can't.
/// Useful functionality for plugins in the future.
pub fn set_slot(&mut self, slot: usize, item: Option<ItemStack>, item_allowed_override: bool) {
match slot {
0 => {
// TODO: Add crafting check here
self.crafting_output = item
pub fn set_slot(
&mut self,
slot: usize,
item: Option<ItemStack>,
item_allowed_override: bool,
) -> Result<(), InventoryError> {
if item_allowed_override {
if !(0..=45).contains(&slot) {
Err(InventoryError::InvalidSlot)?
}
1..=4 => self.crafting[slot - 1] = item,
5..=8 => {
match item {
None => self.armor[slot - 5] = None,
Some(item) => {
// TODO: Replace asserts with error handling
match slot - 5 {
0 => {
assert!(item.is_helmet() || item_allowed_override);
self.armor[0] = Some(item);
}
1 => {
assert!(item.is_chestplate() || item_allowed_override);
self.armor[1] = Some(item)
}
2 => {
assert!(item.is_leggings() || item_allowed_override);
self.armor[2] = Some(item);
}
3 => {
assert!(item.is_boots() || item_allowed_override);
self.armor[3] = Some(item)
}
_ => unreachable!(),
}
}
}
}
9..=44 => {
self.items[slot - 9] = item;
}
45 => {
self.offhand = item;
}
_ => unreachable!(),
*self.all_slots()[slot] = item;
return Ok(());
}
let slot_condition = self.slot_condition(slot)?;
if let Some(item) = item {
if slot_condition(&item) {
*self.all_slots()[slot] = Some(item);
}
}
Ok(())
}
#[allow(clippy::type_complexity)]
pub fn slot_condition(
&self,
slot: usize,
) -> Result<Box<dyn Fn(&ItemStack) -> bool>, InventoryError> {
if !(0..=45).contains(&slot) {
return Err(InventoryError::InvalidSlot);
}
Ok(Box::new(match slot {
0..=4 | 9..=45 => |_| true,
5 => |item: &ItemStack| item.is_helmet(),
6 => |item: &ItemStack| item.is_chestplate(),
7 => |item: &ItemStack| item.is_leggings(),
8 => |item: &ItemStack| item.is_boots(),
_ => unreachable!(),
}))
}
pub fn get_slot(&mut self, slot: usize) -> Result<&mut Option<ItemStack>, InventoryError> {
match slot {
@ -138,15 +133,26 @@ impl Container for PlayerInventory {
&WindowType::Generic9x1
}
fn window_name(&self) -> &'static str {
// We never send an OpenContainer with inventory, so it has no name.
""
}
fn handle_item_change(
&mut self,
carried_slot: &mut Option<ItemStack>,
slot: usize,
mouse_click: MouseClick,
) -> Result<(), InventoryError> {
let slot_condition = self.slot_condition(slot)?;
let item_slot = self.get_slot(slot)?;
handle_item_change(carried_slot, item_slot, mouse_click);
if let Some(item) = carried_slot {
if slot_condition(item) {
handle_item_change(carried_slot, item_slot, mouse_click);
}
} else {
handle_item_change(carried_slot, item_slot, mouse_click)
}
Ok(())
}

View File

@ -8,8 +8,8 @@ use pumpkin_inventory::container_click::{
};
use pumpkin_inventory::drag_handler::DragHandler;
use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait};
use pumpkin_inventory::Container;
use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer};
use pumpkin_inventory::{Container, WindowType};
use pumpkin_protocol::client::play::{
CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
};
@ -19,12 +19,7 @@ use pumpkin_world::item::ItemStack;
use std::sync::{Arc, Mutex};
impl Player {
pub fn open_container(
&mut self,
server: &mut Server,
minecraft_menu_id: &str,
window_title: Option<&str>,
) {
pub fn open_container(&mut self, server: &mut Server, minecraft_menu_id: &str) {
self.inventory.state_id = 0;
let total_opened_containers = self.inventory.total_opened_containers;
let mut container = self
@ -39,12 +34,11 @@ impl Player {
.get("protocol_id")
.unwrap())
.into();
let window_type = match &container {
Some(container) => container.window_type(),
None => &WindowType::Generic9x1,
}
.to_owned();
let title = TextComponent::text(window_title.unwrap_or(window_type.default_title()));
let window_title = container
.as_ref()
.map(|container| container.window_name())
.unwrap_or(self.inventory.window_name());
let title = TextComponent::text(window_title);
self.client.send_packet(&COpenScreen::new(
total_opened_containers.into(),

View File

@ -14,7 +14,7 @@ use pumpkin_core::{
GameMode,
};
use pumpkin_entity::EntityId;
use pumpkin_inventory::WindowType;
use pumpkin_inventory::{InventoryError, WindowType};
use pumpkin_protocol::server::play::{SCloseContainer, SSetPlayerGround, SUseItem};
use pumpkin_protocol::{
client::play::{
@ -517,15 +517,16 @@ impl Player {
self.inventory.set_selected(slot as usize);
}
pub fn handle_set_creative_slot(&mut self, _server: &mut Server, packet: SSetCreativeSlot) {
pub fn handle_set_creative_slot(
&mut self,
_server: &mut Server,
packet: SSetCreativeSlot,
) -> Result<(), InventoryError> {
if self.gamemode != GameMode::Creative {
self.kick(TextComponent::text(
"Invalid action, you can only do that if you are in creative",
));
return;
return Err(InventoryError::PermissionError);
}
self.inventory
.set_slot(packet.slot as usize, packet.clicked_item.to_item(), false);
.set_slot(packet.slot as usize, packet.clicked_item.to_item(), false)
}
// TODO:

View File

@ -21,7 +21,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
server.open_containers.insert(0, open_container);
}
}
player.open_container(server, "minecraft:generic_9x3", Some("Ender Chest"));
player.open_container(server, "minecraft:generic_9x3");
}
Ok(())

View File

@ -438,7 +438,8 @@ impl Player {
Ok(())
}
SSetCreativeSlot::PACKET_ID => {
self.handle_set_creative_slot(server, SSetCreativeSlot::read(bytebuf)?);
self.handle_set_creative_slot(server, SSetCreativeSlot::read(bytebuf)?)
.unwrap();
Ok(())
}
SPlayPingRequest::PACKET_ID => {