Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ members = [
"src/base/macros",
"src/base/profiling",
"src/base/threadpool",
"src/blocks",
"src/blocks/crates/property",
"src/blocks/crates/generated",
"src/blocks/crates/build",
"src/bin",
"src/commands",
"src/components",
Expand Down Expand Up @@ -115,6 +119,8 @@ debug = true
# Workspace members
temper-app = { path = "src/app" }
temper-anvil = { path = "src/adapters/anvil" }
temper-blocks = { path = "src/blocks" }
temper-block-properties = { path = "src/blocks/crates/property" }
temper-config = { path = "src/config" }
temper-core = { path = "src/core" }
temper-default-commands = { path = "src/default_commands" }
Expand Down
19 changes: 19 additions & 0 deletions src/blocks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "temper-blocks"
edition.workspace = true
version = "0.0.1"

[dependencies]
temper-world = { workspace = true }
temper-block-properties = { workspace = true }
temper-blocks-generated = { path = "crates/generated" }
temper-core = { workspace = true }
temper-macros = { workspace = true }
bevy_math = { workspace = true }

[build-dependencies]
temper-blocks-build = { path = "crates/build" }
quote = { workspace = true }

[lints]
workspace = true
76 changes: 76 additions & 0 deletions src/blocks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Temper Blocks

This is a quick tutorial on how to use this library to create and modify block behavior and structs.

## Overview

This is a brief overview of what this crate is actually doing. This is split up into 2 distinct pieces,
the struct generator and the behavior implementation.

The struct generator (the `temper-blocks-build` crate) is given the `build_config.toml` and `blockstates.json` files
and generates structs based off of groups of blockstates. For each block in `blockstates.json`, it
figures out which properties that block has. It will then collect blocks that have those same properties
and group them under the same struct. When it's saving the structs, it will look at the `build_config.toml`
file and rename structs according to the names in that file. The `build_config.toml` also provides the struct
names of various block state properties. The list of block state properties Minecraft uses can be found at
`net.minecraft.world.level.block.state.properties.BlockStateProperties` (as of 1.21.11).

The next step is the block behavior implementations (this is the main crate). To add behavior to blocks, implement
the `BlockBehavior` trait for that block struct. The trait has already been implemented for all block
structs so far (to avoid unsafe features, such as `min_specialization`, it's not the most convenient, but it works).
The build script for this crate uses the `blockstates.json` file to generate a list that maps protocol ids (index)
to block states (value). This list is then used to dispatch behavior functions based on the protocol id.

This crate also implements a helper trait on `BlockStateId` that allows you to call block functions directly from it
(and it will also be updated if the function mutates the block state).

As a side note, the `temper-blocks-generated` crate contains the glue code to use the generated structs from
the build crate. The build crate outputs to the build directory and this crate imports those files back in.
Additionally, the `temper-block-properties` crate is where block state property structs and enums can be found.

## Tips and Notes

- If the build script is emitting a warning about an unknown block, see the **Adding / Modifying Block Structs** section for more information.

## Creating Block Behavior

To create a new function for blocks to implement, navigate to the `behavior_trait.rs` file. Due to the complexity of the inner workings of the system, a macro has been built to make creating functions for blocks super easy. The macro syntax is pretty simple:

```rust
block_behavior_trait!(
fn <function name>([mut]; <arguments>) [-> <return type>; {<default return value}],
...
);
```

- Function Name: The name of the function.
- mut: Optional; whether the function should allow blocks to be mutated. Note: the semicolon after mut is required whether mut is specified or not.
- Arguments: Any amount of arguments for the function to take in.
- Return Type: Optional; The return type of the function.
- Default Return Value: Only present if a return type is specified; the value returned by the default trait implementation of the function.

You can look at the existing functions for tips on usage as well. Simply add your function to this list for it to be a function that blocks can implement.

**PLEASE NOTE:** This macro should ***NOT*** be used anywhere else in the project. It is only here to autogenerate structs and function pointer types that are needed for function dispatch while making it easy to create and modify block functions.

## Implementing / Modifying Block Behavior

To implement or modify the behavior of a block, navigate to where the `BlockBehavior` trait is implemented for that block type.

**NOTE:** Not all blocks have their own block type/struct. Most blocks are lumped in to a single struct (for example, all slab blocks are implemented as the SlabBlock type). This is also how Minecraft implements this (blocks of the same type and logic are instantiated as the same class). Blocks can still be distinguished by the enum attached to the struct.

Once you find the `BlockBehavior` implementation, simply add or modify the function you want.

## Adding / Modifying Block Structs

To add a struct for a specific block type you must edit the `build_config.toml` file. Which section you edit is dependent on what you'd like to do. All blocks are placed into a struct no matter what, so you must either override the name or explicitly place the specific block type into a separate struct.

The `name_overrides` section is for assigning a name to a particular arrangement of block state properties. For example, slabs in the game have the block state properties `type` and `waterlogged`. The name `SlabBlock` was assigned to all blocks with that arrangement of block state properties.

**NOTE:** There are quite a few blocks that have completely different behavior to other blocks but share the same block state property configuration. In this situation, the `block_overrides` section may have to be used.

The `block_overrides` section is for placing specific blocks into different structs. If a block is placed here it will ignore any name given to its configuration of block state properties and use this name instead.

### Important Note

All block struct names **MUST** be unique. The generated crate will not compile otherwise.
39 changes: 39 additions & 0 deletions src/blocks/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use quote::__private::TokenStream;
use quote::quote;
use std::fs;
use std::path::Path;
use temper_blocks_build::complex::fill_complex_block_mappings;
use temper_blocks_build::config::{get_block_states, get_build_config};
use temper_blocks_build::separate_blocks;
use temper_blocks_build::simple::fill_simple_block_mappings;

fn main() {
println!("cargo:rerun-if-changed=build.rs");

let build_config = get_build_config();
let block_states = get_block_states();

let mut mappings = Vec::with_capacity(block_states.len());
mappings.resize(block_states.len(), TokenStream::new());
let (simple_blocks, complex_blocks) = separate_blocks(block_states);

let enum_const = fill_simple_block_mappings(simple_blocks, &mut mappings);
let complex_consts = fill_complex_block_mappings(&build_config, complex_blocks, &mut mappings);

let mapping_const = quote! {
{
use temper_blocks_generated::*;

#enum_const
#(#complex_consts)*

&[
#(#mappings),*
]
}
};

let out_dir = std::env::var_os("OUT_DIR").unwrap();
let dir = Path::new(&out_dir).join("mappings.rs");
fs::write(dir, mapping_const.to_string()).unwrap();
}
224 changes: 224 additions & 0 deletions src/blocks/build_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# Overrides the default generated struct names (unreadable) for the property map given with the given struct name
[name_overrides]
"snowy" = "SnowyBlock"
"age" = "CropBlock"
"type+waterlogged" = "SlabBlock"
"attached+rotation+waterlogged" = "HangingSignBlock"
"waterlogged" = "WaterloggableBlock"
"thickness+vertical_direction+waterlogged" = "DripstoneBlock"
"stage" = "SaplingBlock"
"rotation+waterlogged" = "SignBlock"
"rotation" = "BannerBlock"
"powered" = "PressurePlateBlock"
"axis" = "PillarBlock"
"axis+creaking_heart_state+natural" = "CreakingHeartBlock"
"bites" = "CakeBlock"
"candles+lit+waterlogged" = "CandleBlock"
"conditional+facing" = "CommandBlock"
"distance+persistent+waterlogged" = "LeavesBlock"
"down+east+north+south+up+waterlogged+west" = "LichenBlock"
"down+east+north+south+up+west" = "LargeMushroomBlock"
"dusted" = "SuspiciousBlock"
"east+north+south+up+waterlogged+west" = "WallBlock"
"east+north+south+waterlogged+west" = "FenceAndPaneBlock"
"extended+facing" = "PistonBlock"
"face+facing+powered" = "ButtonBlock"
"facing" = "FacingBlock"
"facing+flower_amount" = "FlowerCoverBlock"
"facing+half+hinge+open+powered" = "DoorBlock"
"facing+half+open+powered+waterlogged" = "TrapdoorBlock"
"facing+half+shape+waterlogged" = "StairsBlock"
"facing+honey_level" = "HiveBlock"
"facing+in_wall+open+powered" = "FenceGateBlock"
"facing+lit" = "FurnaceBlock"
"facing+lit+signal_fire+waterlogged" = "CampfireBlock"
"facing+mode+powered" = "ComparatorBlock"
"facing+occupied+part" = "BedBlock"
"facing+ominous+vault_state" = "VaultBlock"
"facing+powered" = "WallSkullBlock"
"facing+triggered" = "DispenserBlock"
"facing+type+waterlogged" = "ChestBlock"
"facing+waterlogged" = "WaterloggableWallAttachedBlock"
"half" = "DoublePlantBlock"
"hanging+waterlogged" = "LanternBlock"
"lit" = "CandleCakeBlock"
"lit+powered" = "BulbBlock"
"ominous+trial_spawner_state" = "TrialSpawnerBlock"
"pickles+waterlogged" = "SeaPickleBlock"
"power" = "WeightedPressurePlateBlock"
"powered+rotation" = "SkullBlock"
"powered+shape+waterlogged" = "RedstoneRailBlock"
"shape+waterlogged" = "RailBlock"
"age+berries" = "GlowBerriesBlock"
"age+east+north+south+up+west" = "FireBlock"
"age+facing" = "CocoaBeansBlock"
"age+half" = "PitcherCropBlock"
"age+hanging+stage+waterlogged" = "MangrovePopaguleBlock"
"age+leaves+stage" = "BambooBlock"
"attached+disarmed+east+north+powered+south+west" = "TripwireBlock"
"attached+facing+powered" = "TripwireHookBlock"
"attachment+facing+powered" = "BellBlock"
"axis+waterlogged" = "ChainBlock"
"berries" = "GlowBerriesPlantBlock"
"bloom" = "SculkCatalystBlock"
"bottom+distance+waterlogged" = "ScaffoldingBlock"
"bottom+east+north+south+west" = "PaleMossCarpetBlock"
"can_summon+shrieking+waterlogged" = "SculkShriekerBlock"
"charges" = "RespawnAnchor"
"cracked+facing+waterlogged" = "DecoratedPotBlock"
"crafting+orientation+triggered" = "CrafterBlock"
"delay+facing+locked+powered" = "RepeaterBlock"
"drag" = "BubbleColumnBlock"
"east+north+power+south+west" = "RedstoneWireBlock"
"east+north+south+up+west" = "VineBlock"
"eggs+hatch" = "TurtleEggBlock"
"enabled+facing" = "HopperBlock"
"eye+facing" = "EndPortalBlock"
"face+facing" = "GrindstoneBlock"
"facing+half+waterlogged" = "SmallDripleafBlock"
"facing+has_book+powered" = "LecternBlock"
"facing+open" = "BarrelBlock"
"facing+power+sculk_sensor_phase+waterlogged" = "CalibratedSculkSensorBlock"
"facing+powered+waterlogged" = "LightningRodBlock"
"facing+segment_amount" = "LeafLitterBlock"
"facing+short+type" = "PistonHeadBlock"
"facing+slot_0_occupied+slot_1_occupied+slot_2_occupied+slot_3_occupied+slot_4_occupied+slot_5_occupied" = "ChiseledBookshelfBlock"
"facing+tilt+waterlogged" = "BigDripleafBlock"
"facing+type" = "MovingPistonBlock"
"has_bottle_0+has_bottle_1+has_bottle_2" = "BrewingStandBlock"
"has_record" = "JukeboxBlock"
"hatch" = "SnifferEggBlock"
"instrument+note+powered" = "NoteBlock"
"inverted+power" = "DaylightDetectorBlock"
"layers" = "SnowBlock"
"level+waterlogged" = "LightBlock"
"moisture" = "FarmlandBlock"
"orientation" = "JigsawBlock"
"power+sculk_sensor_phase+waterlogged" = "SculkSensorBlock"
"tip" = "PaleHangingMossBlock"
"unstable" = "TntBlock"

# Tells the generator to use a different struct for the block specified.
# If the struct is defined above and the property map of the block and struct don't match, the generator will panic
[block_overrides]
"minecraft:frosted_ice" = "FrostedIceBlock"
"minecraft:redstone_ore" = "RedstoneOreBlock"
"minecraft:deepslate_redstone_ore" = "RedstoneOreBlock"
"minecraft:redstone_lamp" = "RedstoneLampBlock"
"minecraft:redstone_torch" = "RedstoneTorchBlock"
"minecraft:chorus_plant" = "ChorusPlantBlock"
"minecraft:lever" = "LeverBlock"
"minecraft:attached_melon_stem" = "StemBlock"
"minecraft:attached_pumpkin_stem" = "StemBlock"
"minecraft:redstone_wall_torch" = "WallRedstoneTorchBlock"
"minecraft:observer" = "ObserverBlock"
"minecraft:composter" = "ComposterBlock"
"minecraft:lava" = "LiquidBlock"
"minecraft:water" = "LiquidBlock"
"minecraft:powder_snow_cauldron" = "LevelCauldronBlock"
"minecraft:water_cauldron" = "LevelCauldronBlock"
"minecraft:target" = "TargetBlock"
"minecraft:structure_block" = "StructureBlock"
"minecraft:test_block" = "TestBlock"

# From Minecraft's net.minecraft.world.level.block.state.properties.BlockStateProperties class
#
# Maps property names to the type the generator should use in the generated structs.
# If a property name isn't found in this list, the generator will panic. In this case, check to see what the type is and add it below.
# If a name could be multiple types, use a list. The generator will try to decipher the type from the context.
[property_types]
"attached" = "bool"
"berries" = "bool"
"bloom" = "bool"
"bottom" = "bool"
"can_summon" = "bool"
"conditional" = "bool"
"disarmed" = "bool"
"drag" = "bool"
"enabled" = "bool"
"extended" = "bool"
"eye" = "bool"
"falling" = "bool"
"hanging" = "bool"
"has_bottle_0" = "bool"
"has_bottle_1" = "bool"
"has_bottle_2" = "bool"
"has_record" = "bool"
"has_book" = "bool"
"inverted" = "bool"
"in_wall" = "bool"
"lit" = "bool"
"locked" = "bool"
"natural" = "bool"
"occupied" = "bool"
"open" = "bool"
"persistent" = "bool"
"powered" = "bool"
"short" = "bool"
"shrieking" = "bool"
"signal_fire" = "bool"
"snowy" = "bool"
"tip" = "bool"
"triggered" = "bool"
"unstable" = "bool"
"waterlogged" = "bool"
"horizontal_axis" = "Axis"
"axis" = "Axis"
"up" = "bool"
"down" = "bool"
"north" = ["bool", "WallSide", "RedstoneSide"]
"east" = ["bool", "WallSide", "RedstoneSide"]
"south" = ["bool", "WallSide", "RedstoneSide"]
"west" = ["bool", "WallSide", "RedstoneSide"]
"facing" = "Direction"
"flower_amount" = "i32"
"segment_amount" = "i32"
"orientation" = "FrontAndTop"
"face" = "AttachFace"
"attachment" = "BellAttachType"
"half" = ["DoubleBlockHalf", "Half"]
"side_chain" = "SideChainPart"
"shape" = ["RailShape", "StairsShape"]
"age" = "i32"
"bites" = "i32"
"candles" = "i32"
"delay" = "i32"
"distance" = "i32"
"eggs" = "i32"
"hatch" = "i32"
"layers" = "i32"
"level" = "i32"
"honey_level" = "i32"
"moisture" = "i32"
"note" = "i32"
"pickles" = "i32"
"power" = "i32"
"stage" = "i32"
"charges" = "i32"
"hydration" = "i32"
"rotation" = "i32"
"part" = "BedPart"
"type" = ["ChestType", "PistonType", "SlabType"]
"mode" = ["ComparatorMode", "StructureMode", "TestBlockMode"]
"hinge" = "DoorHingeSide"
"instrument" = "NoteBlockInstrument"
"leaves" = "BambooLeaves"
"tilt" = "Tilt"
"vertical_direction" = "Direction"
"thickness" = "DripstoneThickness"
"sculk_sensor_phase" = "SculkSensorPhase"
"slot_0_occupied" = "bool"
"slot_1_occupied" = "bool"
"slot_2_occupied" = "bool"
"slot_3_occupied" = "bool"
"slot_4_occupied" = "bool"
"slot_5_occupied" = "bool"
"dusted" = "i32"
"cracked" = "bool"
"crafting" = "bool"
"trial_spawner_state" = "TrialSpawnerState"
"vault_state" = "VaultState"
"creaking_heart_state" = "CreakingHeartState"
"ominous" = "bool"
"map" = "bool"
"copper_golem_pose" = "CopperGolemPose"
14 changes: 14 additions & 0 deletions src/blocks/crates/build/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "temper-blocks-build"
version = "0.1.0"
edition = "2024"

[dependencies]
temper-block-properties = { path = "../property", features = ["block-struct-generation"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
heck = "0.5"
proc-macro2 = "1.0"
quote = "1.0"
toml = "0.9.8"
fxhash = "0.2.1"
Loading
Loading