feat(item): add item manager with JSON asset loading

Implement ItemManager to load and query item definitions from assets/item JSON files. Add ItemData struct, item asset metadata, CMake source registration, and initialize the manager during app startup. Also add AGENTS.md repository guidelines.
This commit is contained in:
2026-08-04 14:24:10 +08:00
parent 5e7c6ec891
commit 2358cfd9a4
16 changed files with 469 additions and 1 deletions

251
AGENTS.md Normal file
View File

@@ -0,0 +1,251 @@
# Code Modification Guidelines
## 1. Git Branch Protection
- Never modify code directly on the `main` branch.
- Before editing, check the current branch:
```bash
git branch --show-current
````
* If the result is `main`, create or switch to a development branch first:
```bash
git checkout -b feature/xxx
```
* Do not bypass branch protection by:
* Committing directly to `main`.
* Running code generation, formatting, or large refactors on `main`.
* Rewriting or damaging `main` history.
---
## 2. Code Comments
* All new comments must be written in English.
* Comments must be:
* Short and clear.
* Explain the purpose, not repeat the code.
* Avoid unnecessary details.
Recommended:
```cpp
// AI-generated: Prevent stale handle access.
```
Avoid:
```cpp
// AI-generated: This checks if the handle is invalid because...
```
* Important AI-added or modified logic should include:
```cpp
// AI-generated
```
* Do not add comments for trivial changes such as:
* One or two line logic fixes.
* Spelling corrections.
* Simple variable renames.
### 2.1 Do not write excessive or granular comments
AI must avoid sprinkling small explanatory comments throughout code.
Specifically:
* **No line-by-line narration.** Do not add a comment above (or beside)
every field, signal, slot, branch, or block just to restate what it is.
If the name is self-explanatory, no comment is needed.
* **No multi-part explanations on a single marker.** A line like
`// AI-generated: abort the in-flight download; finished handler clears state.`
is too much. Either drop it, or shorten to a single short clause
(e.g. `// AI-generated: abort in-flight download.`).
* **Prefer zero or one comment per file/section**, not many. A short
header comment explaining the file's purpose is acceptable; dozens of
inline labels are not.
* **Do not comment obvious QML/C++ bindings, layout splits, or default
values** (e.g. `// AI-generated: index 0 -> zh_CN, index 1 -> en.`).
The code already says that.
* **When in doubt, omit the comment.** A missing comment is fine; a
redundant one is noise that future maintainers must clean up.
Rule of thumb: if removing the comment would not lose useful
information, remove it.
---
## 3. Project Style
Follow the existing project style strictly:
* Naming conventions.
* File organization.
* Formatting rules.
* Include order.
* Existing architecture.
Do not:
* Introduce unrelated coding styles.
* Add unnecessary design patterns.
* Create duplicate systems.
* Add abstractions without need.
* Change module responsibilities.
---
## 4. Modification Scope
Only modify the minimum code required.
Do not:
* Format unrelated files.
* Remove existing comments.
* Rename large numbers of symbols.
* Change public APIs.
* Perform large refactors without confirmation.
If large changes are required, explain first:
1. Current problem.
2. Required changes.
3. Affected modules.
4. Expected impact.
---
## 5. Build and Test
After modifications:
* Verify compilation.
* Check for new warnings.
* Ensure existing features still work.
Do not:
* Commit without verification.
* Ignore compiler errors.
* Hide problems with temporary hacks.
---
## 6. Compatibility
Consider:
* Existing callers.
* API/ABI compatibility.
* Serialization formats.
* Network protocols.
* Save data.
* Threading impact.
Do not:
* Change network structures without compatibility handling.
* Break old data formats.
* Change public data layouts carelessly.
---
## 7. Multi-threading Safety
For multi-threaded code, check:
* Data races.
* Object lifetime.
* Lock contention.
* Atomic correctness.
* Cross-thread resource access.
Do not:
* Assume single-threaded execution.
* Modify shared data without protection.
* Add hidden locks that hurt performance.
---
## 8. Memory Safety
Consider:
* Ownership.
* Lifetime.
* RAII.
* Memory leaks.
* Dangling references.
* Iterator invalidation.
Do not:
* Return references to local variables.
* Store pointers to temporary objects.
* Use uninitialized data.
* Introduce undefined behavior.
---
## 9. Third-party Libraries
Before adding or changing dependencies:
Check:
* Existing project dependencies.
* Build system impact.
* Maintenance cost.
Do not:
* Add large libraries for simple features.
* Duplicate existing library functionality.
* Modify third-party source code.
---
## 10. AI Behavior Rules
AI-generated code must:
* Reuse existing code first.
* Preserve current architecture.
* Prefer simple solutions.
* Minimize risk and code changes.
Do not:
* Guess requirements.
* Add unrequested features.
* Redesign architecture.
* Add unnecessary abstraction layers.
Choose the implementation with:
> The smallest change, lowest risk, and best compatibility with existing code.
---
## 11. Pre-commit Checklist
Before committing:
* [ ] Not on `main`.
* [ ] Changes match the requested task.
* [ ] New comments are in English.
* [ ] AI changes are marked when needed.
* [ ] No unrelated formatting changes.
* [ ] Build passes.
* [ ] No obvious performance/security/thread issues.
* [ ] No API, protocol, or data format breakage.

5
assets/item/air.json Normal file
View File

@@ -0,0 +1,5 @@
{
"id": 0,
"name": "air",
"description": ""
}

6
assets/item/dirt.json Normal file
View File

@@ -0,0 +1,6 @@
{
"id": 2,
"name": "dirt",
"description": "",
"texture": "texture/item/block/dirt.png"
}

6
assets/item/grass.json Normal file
View File

@@ -0,0 +1,6 @@
{
"id": 9,
"name": "grass",
"description": "",
"texture": "texture/item/block/grass.png"
}

View File

@@ -0,0 +1,6 @@
{
"id": 1,
"name": "grass_block",
"description": "",
"texture": "texture/item/block/grass_block.png"
}

6
assets/item/leaf.json Normal file
View File

@@ -0,0 +1,6 @@
{
"id": 6,
"name": "leaf",
"description": "",
"texture": "texture/item/block/leaf.png"
}

6
assets/item/log.json Normal file
View File

@@ -0,0 +1,6 @@
{
"id": 5,
"name": "log",
"description": "",
"texture": "texture/item/block/log.png"
}

6
assets/item/sand.json Normal file
View File

@@ -0,0 +1,6 @@
{
"id": 4,
"name": "sand",
"description": "",
"texture": "texture/item/block/sand.png"
}

View File

@@ -0,0 +1,6 @@
{
"id": 8,
"name": "snowy_grass_block",
"description": "",
"texture": "texture/item/block/snowy_grass_block.png"
}

6
assets/item/stone.json Normal file
View File

@@ -0,0 +1,6 @@
{
"id": 3,
"name": "stone",
"description": "",
"texture": "texture/item/block/stone.png"
}

6
assets/item/water.json Normal file
View File

@@ -0,0 +1,6 @@
{
"id": 7,
"name": "water",
"description": "",
"texture": "texture/item/block/water.png"
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
#include <string>
namespace Cubed {
using ItemID = uint16_t;
struct ItemData {
ItemID id = 0;
std::string name;
std::string description;
std::string path;
};
} // namespace Cubed

View File

@@ -0,0 +1,36 @@
#pragma once
#include "Cubed/gameplay/item.hpp"
#include <filesystem>
#include <string_view>
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class ItemManager {
public:
ItemManager();
void init();
static ItemManager& instance();
static const ItemData& get(std::string_view key);
static const ItemData& get(ItemID id);
const ItemData& get_item_data(std::string_view key) const;
const ItemData& get_item_data(ItemID id) const;
private:
using ItemMap = tbb::concurrent_hash_map<ItemID, ItemData>;
using acc = ItemMap::accessor;
using cacc = ItemMap::const_accessor;
using IDMap = tbb::concurrent_hash_map<std::string_view, ItemID>;
void add(const std::filesystem::path& path);
ItemMap m_map;
IDMap m_id_map;
static constexpr ItemData EMPTY;
};
} // namespace Cubed

View File

@@ -104,4 +104,5 @@ target_sources(${PROJECT_NAME}
gameplay/client_entity_manager.cpp
gameplay/systems/wander_ai_system.cpp
tools/json_utils.cpp
gameplay/item_manager.cpp
)

View File

@@ -2,6 +2,7 @@
#include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/item_manager.hpp"
#include "Cubed/localization.hpp"
#include "Cubed/tools/arg_parser.hpp"
#include "Cubed/tools/cubed_assert.hpp"
@@ -73,7 +74,7 @@ void App::init(int argc, char** argv) {
Localization::instance().load_language(
m_game_config.get("language", default_value));
}
ItemManager::instance().init();
m_window.init(m_argument);
m_window.imgui_init();
m_opengl_init = true;

View File

@@ -0,0 +1,106 @@
#include "Cubed/gameplay/item_manager.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include <filesystem>
#include <fstream>
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
namespace fs = std::filesystem;
using namespace rapidjson;
namespace Cubed {
ItemManager::ItemManager() {}
ItemManager& ItemManager::instance() {
static ItemManager inst;
return inst;
}
void ItemManager::init() {
m_id_map.clear();
m_map.clear();
fs::path dir = ASSETS_PATH "item";
if (!fs::is_directory(dir)) {
throw std::runtime_error("Item path not exist!");
}
for (const auto& entry : fs::recursive_directory_iterator(
dir, fs::directory_options::skip_permission_denied)) {
if (fs::is_regular_file(entry)) {
if (entry.path().extension().string() == ".json") {
add(entry.path());
}
}
}
}
void ItemManager::add(const std::filesystem::path& path) {
std::ifstream s(path);
if (!s.is_open()) {
return;
}
IStreamWrapper isw(s);
Document doc;
doc.ParseStream(isw);
if (doc.HasParseError()) {
Logger::error("Can't Parse File {}, error code {}", path.string(),
static_cast<int>(doc.GetParseError()));
return;
}
ItemData data;
if (doc.HasMember("id")) {
data.id = static_cast<ItemID>(doc["id"].GetInt());
}
if (doc.HasMember("name")) {
data.name = doc["name"].GetString();
}
if (doc.HasMember("description")) {
data.description = doc["description"].GetString();
}
if (doc.HasMember("texture")) {
data.path = doc["texture"].GetString();
}
acc a;
if (m_map.emplace(a, data.id, std::move(data))) {
if (!m_id_map.emplace(a->second.name, a->first)) {
Logger::error("ItemManager: Can't Insterd {} {} to id map",
a->second.name, a->first);
}
} else {
Logger::error("ItemManager: Can't Insterd {} to map", path.string());
}
}
const ItemData& ItemManager::get_item_data(std::string_view key) const {
IDMap::const_accessor cacc;
ItemID id = 0;
if (m_id_map.find(cacc, key)) {
id = cacc->second;
} else {
Logger::error("Can't Find key {} in id map", key);
ASSERT(false);
return EMPTY;
}
return get_item_data(id);
}
const ItemData& ItemManager::get_item_data(ItemID id) const {
cacc c;
if (!m_map.find(c, id)) {
Logger::error("Can't find item {} in map", id);
ASSERT(false);
return EMPTY;
}
return c->second;
}
const ItemData& ItemManager::get(std::string_view key) {
return instance().get_item_data(key);
}
const ItemData& ItemManager::get(ItemID id) {
return instance().get_item_data(id);
}
} // namespace Cubed