Add Items (#92)

Adding all the items, just like the blocks are. This will adress 1. and 2. bullet from issue #53
This will also make it easier to convert between block <-> item.
This commit is contained in:
EmperialDev 2022-10-05 18:28:08 +02:00 committed by GitHub
parent e985bae469
commit 1f996f7549
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 12175 additions and 27 deletions

View file

@ -36,8 +36,9 @@ place. Here are some noteworthy achievements:
- [x] JSON Text API - [x] JSON Text API
- [x] A Fabric mod for extracting data from the game into JSON files. These files are processed by a build script to - [x] A Fabric mod for extracting data from the game into JSON files. These files are processed by a build script to
generate Rust code for the project. The JSON files can be used in other projects as well. generate Rust code for the project. The JSON files can be used in other projects as well.
- [x] Items
- [ ] Inventory
- [ ] Block entities - [ ] Block entities
- [ ] Inventory and items
- [ ] Proxy support - [ ] Proxy support
- [ ] Sounds, particles, etc. - [ ] Sounds, particles, etc.
- [ ] Utilities for continuous collision detection - [ ] Utilities for continuous collision detection

View file

@ -17,6 +17,7 @@ struct TopLevel {
struct Block { struct Block {
#[allow(unused)] #[allow(unused)]
id: u16, id: u16,
item_id: u16,
translation_key: String, translation_key: String,
name: String, name: String,
properties: Vec<Property>, properties: Vec<Property>,
@ -313,6 +314,31 @@ pub fn build() -> anyhow::Result<TokenStream> {
}) })
.collect::<TokenStream>(); .collect::<TokenStream>();
let block_kind_to_item_kind_arms = blocks
.iter()
.map(|block| {
let name = ident(block.name.to_pascal_case());
let item_id = block.item_id;
quote! {
BlockKind::#name => #item_id,
}
})
.collect::<TokenStream>();
let block_kind_from_item_kind_arms = blocks
.iter()
.filter(|block| block.item_id != 0)
.map(|block| {
let name = ident(block.name.to_pascal_case());
let item_id = block.item_id;
quote! {
#item_id => Some(BlockKind::#name),
}
})
.collect::<TokenStream>();
let block_kind_count = blocks.len(); let block_kind_count = blocks.len();
let prop_names = blocks let prop_names = blocks
@ -535,7 +561,7 @@ pub fn build() -> anyhow::Result<TokenStream> {
/// Construct a block kind from its snake_case name. /// Construct a block kind from its snake_case name.
/// ///
/// Returns `None` if the name is invalid. /// Returns `None` if the name is invalid.
pub fn from_str(name: &str) -> Option<BlockKind> { pub fn from_str(name: &str) -> Option<Self> {
match name { match name {
#block_kind_from_str_arms #block_kind_from_str_arms
_ => None _ => None
@ -568,6 +594,35 @@ pub fn build() -> anyhow::Result<TokenStream> {
} }
} }
/// Converts a block kind to its corresponding item kind.
///
/// [`ItemKind::Air`] is used to indicate the absence of an item.
pub const fn to_item_kind(self) -> ItemKind {
let id = match self {
#block_kind_to_item_kind_arms
};
// TODO: unwrap() is not const yet.
match ItemKind::from_raw(id) {
Some(k) => k,
None => unreachable!(),
}
}
/// Constructs a block kind from an item kind.
///
/// If the given item does not have a corresponding block, `None` is returned.
pub const fn from_item_kind(item: ItemKind) -> Option<Self> {
// The "default" blocks are ordered before the other variants.
// For instance, `torch` comes before `wall_torch` so this match
// should do the correct thing.
#[allow(unreachable_patterns)]
match item.to_raw() {
#block_kind_from_item_kind_arms
_ => None,
}
}
/// An array of all block kinds. /// An array of all block kinds.
pub const ALL: [Self; #block_kind_count] = [#(Self::#block_kind_variants,)*]; pub const ALL: [Self; #block_kind_count] = [#(Self::#block_kind_variants,)*];
} }

301
build/item.rs Normal file
View file

@ -0,0 +1,301 @@
use anyhow::Ok;
use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::quote;
use serde::Deserialize;
use crate::ident;
#[derive(Deserialize, Clone, Debug)]
struct Item {
id: u16,
name: String,
translation_key: String,
max_stack: u8,
max_durability: u16,
enchantability: u8,
fireproof: bool,
food: Option<FoodComponent>,
}
#[derive(Deserialize, Clone, Debug)]
struct FoodComponent {
hunger: u16,
saturation: f32,
always_edible: bool,
meat: bool,
snack: bool,
// TODO: effects
}
pub fn build() -> anyhow::Result<TokenStream> {
let items = serde_json::from_str::<Vec<Item>>(include_str!("../extracted/items.json"))?;
let item_kind_count = items.len();
let item_kind_from_raw_id_arms = items
.iter()
.map(|item| {
let id = &item.id;
let name = ident(item.name.to_pascal_case());
quote! {
#id => Some(Self::#name),
}
})
.collect::<TokenStream>();
let item_kind_to_raw_id_arms = items
.iter()
.map(|item| {
let id = &item.id;
let name = ident(item.name.to_pascal_case());
quote! {
Self::#name => #id,
}
})
.collect::<TokenStream>();
let item_kind_from_str_arms = items
.iter()
.map(|item| {
let str_name = &item.name;
let name = ident(str_name.to_pascal_case());
quote! {
#str_name => Some(Self::#name),
}
})
.collect::<TokenStream>();
let item_kind_to_str_arms = items
.iter()
.map(|item| {
let str_name = &item.name;
let name = ident(str_name.to_pascal_case());
quote! {
Self::#name => #str_name,
}
})
.collect::<TokenStream>();
let item_kind_to_translation_key_arms = items
.iter()
.map(|item| {
let name = ident(item.name.to_pascal_case());
let translation_key = &item.translation_key;
quote! {
Self::#name => #translation_key,
}
})
.collect::<TokenStream>();
let item_kind_variants = items
.iter()
.map(|item| ident(item.name.to_pascal_case()))
.collect::<Vec<_>>();
let item_kind_to_max_stack_arms = items
.iter()
.map(|item| {
let name = ident(item.name.to_pascal_case());
let max_stack = item.max_stack;
quote! {
Self::#name => #max_stack,
}
})
.collect::<TokenStream>();
let item_kind_to_food_component_arms = items
.iter()
.map(|item| match &item.food {
Some(food_component) => {
let name = ident(item.name.to_pascal_case());
let hunger = food_component.hunger;
let saturation = food_component.saturation;
let always_edible = food_component.always_edible;
let meat = food_component.meat;
let snack = food_component.snack;
quote! {
Self::#name => Some(FoodComponent {
hunger: #hunger,
saturation: #saturation,
always_edible: #always_edible,
meat: #meat,
snack: #snack,
}
),
}
}
None => quote! {},
})
.collect::<TokenStream>();
let item_kind_to_max_durability_arms = items
.iter()
.filter(|item| item.max_durability != 0)
.map(|item| {
let name = ident(item.name.to_pascal_case());
let max_durability = item.max_durability;
quote! {
Self::#name => #max_durability,
}
})
.collect::<TokenStream>();
let item_kind_to_enchantability_arms = items
.iter()
.filter(|item| item.enchantability != 0)
.map(|item| {
let name = ident(item.name.to_pascal_case());
let ench = item.enchantability;
quote! {
Self::#name => #ench,
}
})
.collect::<TokenStream>();
let item_kind_to_fireproof_arms = items
.iter()
.filter(|item| item.fireproof)
.map(|item| {
let name = ident(item.name.to_pascal_case());
quote! {
Self::#name => true,
}
})
.collect::<TokenStream>();
Ok(quote! {
/// Represents an item from the game
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u16)]
pub enum ItemKind {
#(#item_kind_variants,)*
}
/// Contains food information about an item.
///
/// Only food items have a food component.
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub struct FoodComponent {
pub hunger: u16,
pub saturation: f32,
pub always_edible: bool,
pub meat: bool,
pub snack: bool,
}
impl ItemKind {
/// Constructs a item kind from a raw item ID.
///
/// If the given ID is invalid, `None` is returned.
pub const fn from_raw(id: u16) -> Option<Self> {
match id {
#item_kind_from_raw_id_arms
_ => None
}
}
/// Gets the raw item ID from the item kind
pub const fn to_raw(self) -> u16 {
match self {
#item_kind_to_raw_id_arms
}
}
/// Construct an item kind for its snake_case name.
///
/// Returns `None` if the name is invalid.
#[allow(clippy::should_implement_trait)]
pub fn from_str(name: &str) -> Option<ItemKind> {
match name {
#item_kind_from_str_arms
_ => None
}
}
/// Gets the snake_case name of this item kind.
pub const fn to_str(self) -> &'static str {
match self {
#item_kind_to_str_arms
}
}
/// Gets the translation key of this item kind.
pub const fn translation_key(self) -> &'static str {
match self {
#item_kind_to_translation_key_arms
}
}
/// Returns the maximum stack count.
pub const fn max_stack(self) -> u8 {
match self {
#item_kind_to_max_stack_arms
}
}
/// Returns a food component which stores hunger, saturation etc.
///
/// If the item kind can't be eaten, `None` will be returned.
pub const fn food_component(self) -> Option<FoodComponent> {
match self {
#item_kind_to_food_component_arms
_ => None
}
}
/// Returns the maximum durability before the item will break.
///
/// If the item doesn't have durability, `0` is returned.
pub const fn max_durability(self) -> u16 {
match self {
#item_kind_to_max_durability_arms
_ => 0,
}
}
/// Returns the enchantability of the item kind.
///
/// If the item doesn't have durability, `0` is returned.
pub const fn enchantability(self) -> u8 {
match self {
#item_kind_to_enchantability_arms
_ => 0,
}
}
/// Returns if the item can survive in fire/lava.
pub const fn fireproof(self) -> bool {
#[allow(clippy::match_like_matches_macro)]
match self {
#item_kind_to_fireproof_arms
_ => false
}
}
/// Constructs an item kind from a block kind.
///
/// [`ItemKind::Air`] is used to indicate the absence of an item.
pub const fn from_block_kind(kind: BlockKind) -> Self {
kind.to_item_kind()
}
/// Constructs a block kind from an item kind.
///
/// If the given item kind doesn't have a corresponding block kind, `None` is returned.
pub const fn to_block_kind(self) -> Option<BlockKind> {
BlockKind::from_item_kind(self)
}
/// An array of all item kinds.
pub const ALL: [Self; #item_kind_count] = [#(Self::#item_kind_variants,)*];
}
})
}

View file

@ -9,6 +9,7 @@ mod block;
mod enchant; mod enchant;
mod entity; mod entity;
mod entity_event; mod entity_event;
mod item;
pub fn main() -> anyhow::Result<()> { pub fn main() -> anyhow::Result<()> {
println!("cargo:rerun-if-changed=extracted/"); println!("cargo:rerun-if-changed=extracted/");
@ -17,6 +18,7 @@ pub fn main() -> anyhow::Result<()> {
(entity::build as fn() -> _, "entity.rs"), (entity::build as fn() -> _, "entity.rs"),
(entity_event::build, "entity_event.rs"), (entity_event::build, "entity_event.rs"),
(block::build, "block.rs"), (block::build, "block.rs"),
(item::build, "item.rs"),
(enchant::build, "enchant.rs"), (enchant::build, "enchant.rs"),
]; ];

File diff suppressed because it is too large Load diff

10749
extracted/items.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -2,11 +2,7 @@ package dev._00a.valence_extractor;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import dev._00a.valence_extractor.extractors.Blocks; import dev._00a.valence_extractor.extractors.*;
import dev._00a.valence_extractor.extractors.Entities;
import dev._00a.valence_extractor.extractors.EntityData;
import dev._00a.valence_extractor.extractors.Packets;
import dev._00a.valence_extractor.extractors.Enchants;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -40,8 +36,8 @@ public class Main implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
LOGGER.info("Starting extractors..."); LOGGER.info("Starting extractors...");
var extractors = new Extractor[]{new Blocks(), new Entities(), new EntityData(), new Packets(), new Enchants()}; var extractors = new Extractor[]{new Blocks(), new Entities(), new EntityData(), new Packets(), new Items(), new Enchants()};
Path outputDirectory; Path outputDirectory;
try { try {

View file

@ -35,8 +35,7 @@ public class Blocks implements Main.Extractor {
blockJson.addProperty("id", Registry.BLOCK.getRawId(block)); blockJson.addProperty("id", Registry.BLOCK.getRawId(block));
blockJson.addProperty("name", Registry.BLOCK.getId(block).getPath()); blockJson.addProperty("name", Registry.BLOCK.getId(block).getPath());
blockJson.addProperty("translation_key", block.getTranslationKey()); blockJson.addProperty("translation_key", block.getTranslationKey());
// blockJson.addProperty("min_state_id", stateIdCounter); blockJson.addProperty("item_id", Registry.ITEM.getRawId(block.asItem()));
// blockJson.addProperty("max_state_id", stateIdCounter + block.getStateManager().getStates().size() - 1);
var propsJson = new JsonArray(); var propsJson = new JsonArray();
for (var prop : block.getStateManager().getProperties()) { for (var prop : block.getStateManager().getProperties()) {

View file

@ -0,0 +1,66 @@
package dev._00a.valence_extractor.extractors;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import dev._00a.valence_extractor.Main;
import net.minecraft.util.registry.Registry;
public class Items implements Main.Extractor {
public Items() {
}
@Override
public String fileName() {
return "items.json";
}
@Override
public JsonElement extract() throws Exception {
var itemsJson = new JsonArray();
for (var item : Registry.ITEM) {
var itemJson = new JsonObject();
itemJson.addProperty("id", Registry.ITEM.getRawId(item));
itemJson.addProperty("name", Registry.ITEM.getId(item).getPath());
itemJson.addProperty("translation_key", item.getTranslationKey());
itemJson.addProperty("max_stack", item.getMaxCount());
itemJson.addProperty("max_durability", item.getMaxDamage());
itemJson.addProperty("enchantability", item.getEnchantability());
itemJson.addProperty("fireproof", item.isFireproof());
if (item.getFoodComponent() != null) {
var foodJson = new JsonObject();
var foodComp = item.getFoodComponent();
foodJson.addProperty("hunger", foodComp.getHunger());
foodJson.addProperty("saturation", foodComp.getSaturationModifier());
foodJson.addProperty("always_edible", foodComp.isAlwaysEdible());
foodJson.addProperty("meat", foodComp.isMeat());
foodJson.addProperty("snack", foodComp.isSnack());
itemJson.add("food", foodJson);
var effectsJson = new JsonArray();
for (var pair : foodComp.getStatusEffects()) {
var effectJson = new JsonObject();
var effect = pair.getFirst();
var chance = pair.getSecond();
effectJson.addProperty("chance", chance);
effectJson.addProperty("translation_key", effect.getEffectType().getTranslationKey());
// TODO: more effect information.
effectsJson.add(effectJson);
}
foodJson.add("effects", effectsJson);
}
itemsJson.add(itemJson);
}
return itemsJson;
}
}

View file

@ -9,6 +9,7 @@ use std::iter::FusedIterator;
use anyhow::Context; use anyhow::Context;
pub use crate::block_pos::BlockPos; pub use crate::block_pos::BlockPos;
use crate::item::ItemKind;
use crate::protocol::{Decode, Encode, VarInt}; use crate::protocol::{Decode, Encode, VarInt};
include!(concat!(env!("OUT_DIR"), "/block.rs")); include!(concat!(env!("OUT_DIR"), "/block.rs"));

View file

@ -7,7 +7,7 @@ use crate::block_pos::BlockPos;
use crate::config::Config; use crate::config::Config;
use crate::entity::types::Pose; use crate::entity::types::Pose;
use crate::entity::{Entity, EntityEvent, EntityId, TrackedData}; use crate::entity::{Entity, EntityEvent, EntityId, TrackedData};
use crate::itemstack::ItemStack; use crate::item::ItemStack;
use crate::protocol::packets::c2s::play::ClickContainerMode; use crate::protocol::packets::c2s::play::ClickContainerMode;
pub use crate::protocol::packets::c2s::play::{ pub use crate::protocol::packets::c2s::play::{
BlockFace, ChatMode, DisplayedSkinParts, Hand, MainHand, ResourcePackC2s as ResourcePackStatus, BlockFace, ChatMode, DisplayedSkinParts, Hand, MainHand, ResourcePackC2s as ResourcePackStatus,

55
src/item.rs Normal file
View file

@ -0,0 +1,55 @@
//! Items and ItemStacks
use anyhow::Context;
use crate::block::BlockKind;
use crate::nbt::Compound;
use crate::protocol::{Decode, Encode, VarInt};
include!(concat!(env!("OUT_DIR"), "/item.rs"));
#[derive(Debug, Clone, PartialEq)]
pub struct ItemStack {
pub item: ItemKind,
pub item_count: u8,
pub nbt: Option<Compound>,
}
impl Encode for ItemKind {
fn encode(&self, w: &mut impl std::io::Write) -> anyhow::Result<()> {
VarInt(self.to_raw() as i32).encode(w)
}
}
impl Decode for ItemKind {
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
let id = VarInt::decode(r)?.0;
let errmsg = "invalid item ID";
ItemKind::from_raw(id.try_into().context(errmsg)?).context(errmsg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn item_kind_to_block_kind() {
assert_eq!(
ItemKind::Cauldron.to_block_kind(),
Some(BlockKind::Cauldron)
);
}
#[test]
fn block_state_to_item() {
assert_eq!(BlockKind::Torch.to_item_kind(), ItemKind::Torch);
assert_eq!(BlockKind::WallTorch.to_item_kind(), ItemKind::Torch);
assert_eq!(BlockKind::Cauldron.to_item_kind(), ItemKind::Cauldron);
assert_eq!(BlockKind::LavaCauldron.to_item_kind(), ItemKind::Cauldron);
assert_eq!(BlockKind::NetherPortal.to_item_kind(), ItemKind::Air);
}
}

View file

@ -1,9 +0,0 @@
use crate::nbt::Compound;
use crate::protocol::VarInt;
#[derive(Debug, Clone, PartialEq)]
pub struct ItemStack {
pub item_id: VarInt,
pub item_count: u8,
pub nbt: Option<Compound>,
}

View file

@ -110,10 +110,9 @@ pub mod dimension;
pub mod enchant; pub mod enchant;
pub mod entity; pub mod entity;
pub mod ident; pub mod ident;
pub mod itemstack; pub mod item;
pub mod player_list; pub mod player_list;
pub mod player_textures; pub mod player_textures;
#[allow(dead_code)]
#[doc(hidden)] #[doc(hidden)]
pub mod protocol; pub mod protocol;
pub mod server; pub mod server;

View file

@ -2,9 +2,9 @@ use std::io::Write;
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
use crate::itemstack::ItemStack; use crate::item::{ItemKind, ItemStack};
use crate::nbt::Compound; use crate::nbt::Compound;
use crate::protocol::{Decode, Encode, VarInt}; use crate::protocol::{Decode, Encode};
pub type SlotId = i16; pub type SlotId = i16;
@ -22,7 +22,7 @@ impl Encode for Slot {
Slot::Empty => false.encode(w), Slot::Empty => false.encode(w),
Slot::Present(s) => { Slot::Present(s) => {
true.encode(w)?; true.encode(w)?;
s.item_id.encode(w)?; s.item.encode(w)?;
s.item_count.encode(w)?; s.item_count.encode(w)?;
match &s.nbt { match &s.nbt {
Some(n) => n.encode(w), Some(n) => n.encode(w),
@ -40,7 +40,7 @@ impl Decode for Slot {
return Ok(Slot::Empty); return Ok(Slot::Empty);
} }
Ok(Slot::Present(ItemStack { Ok(Slot::Present(ItemStack {
item_id: VarInt::decode(r)?, item: ItemKind::decode(r)?,
item_count: u8::decode(r)?, item_count: u8::decode(r)?,
nbt: if r.first() == Some(&0) { nbt: if r.first() == Some(&0) {
r.read_u8()?; r.read_u8()?;