Skip to content
Merged
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
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ members = [
'packages/rust/bevy/bevy_player',
'packages/rust/bevy/bevy_cam',
'packages/rust/bevy/bevy_kbve_net',
'packages/rust/bevy/bevy_items',
]

[profile.dev]
Expand Down
27 changes: 27 additions & 0 deletions packages/rust/bevy/bevy_items/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "bevy_items"
authors = ["kbve", "h0lybyte"]
version = "0.1.0"
edition = "2024"
rust-version = "1.94"
license = "MIT"
description = "Proto-driven item definitions for Bevy games — compiles itemdb.proto into typed Rust structs with a searchable registry."
homepage = "https://kbve.com/"
repository = "https://github.com/KBVE/kbve/tree/main/packages/rust/bevy/bevy_items"
keywords = ["bevy", "items", "gamedev", "protobuf", "plugin"]
categories = ["game-development", "game-engines"]

[dependencies]
bevy = { version = "0.18", default-features = false, features = [
"bevy_state",
] }
prost = { version = "0.14", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[build-dependencies]
prost-build = "0.14"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
116 changes: 116 additions & 0 deletions packages/rust/bevy/bevy_items/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# bevy_items

Proto-driven item definitions for Bevy games. Compiles `itemdb.proto` into typed Rust structs via `prost` and wraps them in a searchable `ItemDb` Bevy resource.

Game-agnostic — any game can load the same proto item registry and query it by slug, ULID, type flags, or rarity.

## Data Flow

```
itemdb.proto (source of truth — shared item schema)
├──► prost-build ──► item.rs (Rust structs for Bevy games)
└──► protoc/codegen ──► Zod schema (TypeScript for Astro site)
Astro MDX files (human-authored item content)
/api/itemdb.json (runtime JSON endpoint)
ItemDb::from_json() ◄── any Bevy game
```

1. **`itemdb.proto`** defines the canonical item schema (stats, rarity, equipment, recipes, etc.)
2. **Astro MDX files** at `kbve.com/itemdb/<slug>/` are the human-authored content — names, descriptions, lore, stat values
3. **`/api/itemdb.json`** serves all items as JSON at build time
4. **`ItemDb::from_json()`** parses that JSON into proto structs, handling string→enum conversion automatically
5. Any Bevy game (Isometric, DiscordSH, etc.) loads the same `ItemDb` and queries items by slug, ULID, type flags, or rarity

## Usage

Add to your `Cargo.toml`:

```toml
[dependencies]
bevy_items = { path = "packages/rust/bevy/bevy_items" }
```

### Plugin Setup

```rust
use bevy::prelude::*;
use bevy_items::BevyItemsPlugin;

App::new()
.add_plugins(BevyItemsPlugin)
.run();
```

### Loading from Astro JSON

```rust
use bevy_items::ItemDb;

fn load_items(mut commands: Commands) {
let json = include_str!("path/to/itemdb.json");
let db = ItemDb::from_json(json).expect("Failed to parse item JSON");
commands.insert_resource(db);
}
```

### Loading from Proto Binary

```rust
let bytes = include_bytes!("path/to/items.binpb");
let db = ItemDb::from_bytes(bytes).expect("Failed to decode item registry");
```

### Querying Items

```rust
fn print_item(db: Res<ItemDb>) {
// By slug
if let Some(item) = db.get_by_slug("blue-shark") {
println!("{}: {}", item.name, item.description.as_deref().unwrap_or(""));
}

// By ULID
if let Some(item) = db.get_by_ulid("01JQPJV...") {
println!("Found: {}", item.name);
}

// By type flags
let weapons = db.find_by_type_flags(0x02); // TYPE_WEAPON
println!("Found {} weapons", weapons.len());

// By rarity
use bevy_items::ItemRarity;
let rares = db.find_by_rarity(ItemRarity::Rare);
println!("Found {} rare items", rares.len());
}
```

### ProtoItemId

`ProtoItemId` is a stable hash of the item slug, used as a lightweight key for inventory slots, network packets, and save files:

```rust
use bevy_items::{ProtoItemId, ItemDb};

let id = ProtoItemId::from_slug("blue-shark");
let name = db.display_name(id); // "Blue Shark"
let stack = db.max_stack(id); // e.g. 1
```

## Regenerating Proto Types

Proto types are committed to the repo. To regenerate after editing `itemdb.proto`:

```bash
BUILD_PROTO=1 cargo build -p bevy_items
```

This requires `protoc` on your PATH.
40 changes: 40 additions & 0 deletions packages/rust/bevy/bevy_items/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::fs;
use std::path::PathBuf;

fn main() {
if std::env::var("BUILD_PROTO").is_err() {
println!("cargo:warning=Skipping protobuf compilation (BUILD_PROTO not set)");
return;
}

let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());

// Walk up to the workspace root (4 levels: bevy_items → bevy → rust → packages → root)
let workspace_root = manifest_dir
.ancestors()
.nth(4)
.expect("Cannot find workspace root");
let proto_root = workspace_root.join("packages/data/proto");

assert!(
proto_root.exists(),
"Proto root not found at: {}",
proto_root.display()
);

let item_proto = proto_root.join("item/itemdb.proto");
let out_dir = manifest_dir.join("src/proto");
fs::create_dir_all(&out_dir).unwrap();

prost_build::Config::new()
.out_dir(&out_dir)
.type_attribute(".item", "#[derive(serde::Serialize, serde::Deserialize)]")
.compile_protos(
&[item_proto.to_str().unwrap()],
&[
proto_root.join("item").to_str().unwrap(),
proto_root.to_str().unwrap(),
],
)
.expect("Failed to compile itemdb.proto");
}
Loading
Loading