added inventory & active object
This commit is contained in:
parent
e1b491be73
commit
b3fda88784
@ -76,6 +76,10 @@ add_executable(space-game001
|
||||
|
||||
../src/items/GameObjectLoader.h
|
||||
../src/items/GameObjectLoader.cpp
|
||||
../src/items/Item.h
|
||||
../src/items/Item.cpp
|
||||
../src/items/InteractiveObject.h
|
||||
../src/items/InteractiveObject.cpp
|
||||
)
|
||||
|
||||
# Установка проекта по умолчанию для Visual Studio
|
||||
|
||||
@ -10,7 +10,8 @@
|
||||
"positionX": 0.0,
|
||||
"positionY": 0.0,
|
||||
"positionZ": 0.0,
|
||||
"scale": 1.0
|
||||
"scale": 1.0,
|
||||
"interactive": false
|
||||
},
|
||||
{
|
||||
"name": "inai",
|
||||
@ -22,7 +23,8 @@
|
||||
"positionX": 2.5,
|
||||
"positionY": 1.4,
|
||||
"positionZ": -9.9,
|
||||
"scale": 1.0
|
||||
"scale": 1.0,
|
||||
"interactive": false
|
||||
},
|
||||
{
|
||||
"name": "bench",
|
||||
@ -34,7 +36,29 @@
|
||||
"positionX": -2.1,
|
||||
"positionY": 0.5,
|
||||
"positionZ": -7.9,
|
||||
"scale": 3.0
|
||||
"scale": 3.0,
|
||||
"interactive": false
|
||||
},
|
||||
{
|
||||
"name": "medik",
|
||||
"texturePath": "resources/fire2.png",
|
||||
"meshPath": "resources/w/firebox.txt",
|
||||
"rotationX": 0.0,
|
||||
"rotationY": -1.5707963267948966,
|
||||
"rotationZ": 0.0,
|
||||
"positionX": 0.0,
|
||||
"positionY": 0.0,
|
||||
"positionZ": 4.0,
|
||||
"scale": 1.0,
|
||||
"interactive": true,
|
||||
"item": {
|
||||
"id": "health_potion",
|
||||
"name": "Health Potion",
|
||||
"description": "Restores 50 HP",
|
||||
"icon": "resources/fire2.png",
|
||||
"radius": 2.0
|
||||
},
|
||||
"activateFunction" : "on_health_pickup"
|
||||
}
|
||||
]
|
||||
}
|
||||
67
resources/config2/ui_inventory.json
Normal file
67
resources/config2/ui_inventory.json
Normal file
@ -0,0 +1,67 @@
|
||||
{
|
||||
"root": {
|
||||
"type": "FrameLayout",
|
||||
"name": "inventory_root",
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"children": [
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "inventory_button",
|
||||
"x": 50.0,
|
||||
"y": 50.0,
|
||||
"width": 150.0,
|
||||
"height": 60.0,
|
||||
"text": "Inventory",
|
||||
"fontSize": 24,
|
||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||
"textCentered": true,
|
||||
"color": [1.0, 1.0, 1.0, 1.0],
|
||||
"textures": {
|
||||
"normal": "resources/connecting.png",
|
||||
"hover": "resources/connecting.png",
|
||||
"pressed": "resources/connecting.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FrameLayout",
|
||||
"name": "inventory_items_panel",
|
||||
"x": 100.0,
|
||||
"y": 150.0,
|
||||
"width": 500.0,
|
||||
"height": 600.0,
|
||||
"children": [
|
||||
{
|
||||
"type": "TextView",
|
||||
"name": "inventory_items_text",
|
||||
"x": 100.0,
|
||||
"y": 30.0,
|
||||
"width": 460.0,
|
||||
"height": 450.0,
|
||||
"text": "Inventory (Empty)",
|
||||
"fontSize": 18,
|
||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||
"centered": false,
|
||||
"color": [1.0, 1.0, 1.0, 1.0]
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "close_inventory_button",
|
||||
"x": 200.0,
|
||||
"y": 20.0,
|
||||
"width": 40.0,
|
||||
"height": 40.0,
|
||||
"text": "X",
|
||||
"fontSize": 20,
|
||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||
"textCentered": true,
|
||||
"color": [1.0, 1.0, 1.0, 1.0],
|
||||
"textures": {
|
||||
"normal": "resources/spark.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
-- Chain of waypoints for NPC 0.
|
||||
-- Each on_arrived callback issues the next walk command.
|
||||
-- ============================================
|
||||
-- NPC PATROL WAYPOINTS
|
||||
-- ============================================
|
||||
|
||||
local function step3()
|
||||
game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1)
|
||||
@ -14,3 +15,25 @@ function step1()
|
||||
end
|
||||
|
||||
step1()
|
||||
|
||||
-- ============================================
|
||||
-- ITEM PICKUP HANDLER
|
||||
-- ============================================
|
||||
|
||||
function on_health_pickup()
|
||||
game_api.pickup_item("health_potion")
|
||||
|
||||
end
|
||||
|
||||
function on_item_pickup(object_name)
|
||||
print("========================================")
|
||||
print("on_item_pickup CALLBACK CALLED!")
|
||||
print("Player picked up item from: " .. tostring(object_name))
|
||||
print("========================================")
|
||||
|
||||
game_api.pickup_item(object_name)
|
||||
|
||||
--game_api.show_inventory()
|
||||
end
|
||||
|
||||
print("Lua script loaded successfully!")
|
||||
164
src/Game.cpp
164
src/Game.cpp
@ -148,34 +148,11 @@ namespace ZL
|
||||
roomMesh.RefreshVBO();
|
||||
std::cout << "Load resurces step 5" << std::endl;
|
||||
|
||||
// Load static game objects
|
||||
gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
|
||||
|
||||
/*
|
||||
std::cout << "Load resurces step 5" << std::endl;
|
||||
fireboxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/Cube001.png", CONST_ZIP_FILE));
|
||||
fireboxMesh.data = LoadFromTextFile02("resources/w/firebox.txt", CONST_ZIP_FILE);
|
||||
fireboxMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||
fireboxMesh.RefreshVBO();
|
||||
|
||||
|
||||
std::cout << "Load resurces step 6" << std::endl;
|
||||
inaiTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/inai001.png", CONST_ZIP_FILE));
|
||||
std::cout << "Load resurces step 6.1" << std::endl;
|
||||
inaiMesh.data = LoadFromTextFile02("resources/w/inai001.txt", CONST_ZIP_FILE);
|
||||
inaiMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||
inaiMesh.data.Move({ 2.5, 1.4, -9.9 });
|
||||
inaiMesh.RefreshVBO();
|
||||
|
||||
std::cout << "Load resurces step 7" << std::endl;
|
||||
benchTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/bench001opt.png", CONST_ZIP_FILE));
|
||||
benchMesh.data = LoadFromTextFile02("resources/w/bench002opt.txt", CONST_ZIP_FILE);
|
||||
benchMesh.data.Scale(3.f);
|
||||
benchMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||
benchMesh.data.Move({ -2.1, 0.5, -7.9 });
|
||||
benchMesh.RefreshVBO();
|
||||
*/
|
||||
|
||||
std::cout << "Load resurces step 8" << std::endl;
|
||||
// Load interactive objects
|
||||
interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
|
||||
|
||||
auto violaTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/viola.png", CONST_ZIP_FILE));
|
||||
|
||||
@ -236,8 +213,91 @@ namespace ZL
|
||||
scriptEngine.init(this);
|
||||
|
||||
std::cout << "Load resurces step 13" << std::endl;
|
||||
|
||||
// Load UI with inventory button
|
||||
try {
|
||||
menuManager.uiManager.loadFromFile("resources/config2/ui_inventory.json", renderer, CONST_ZIP_FILE);
|
||||
std::cout << "UI loaded successfully" << std::endl;
|
||||
|
||||
menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
|
||||
menuManager.uiManager.setNodeVisible("close_inventory_button", false);
|
||||
|
||||
menuManager.uiManager.setTextButtonCallback("inventory_button", [this](const std::string& name) {
|
||||
std::cout << "[UI] Inventory button clicked" << std::endl;
|
||||
//scriptEngine.callItemPickupCallback("toggle_inventory");
|
||||
scriptEngine.showInventory(this);
|
||||
//scriptEngine
|
||||
//scriptEngine
|
||||
});
|
||||
|
||||
menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
|
||||
std::cout << "[UI] Close button clicked" << std::endl;
|
||||
menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
|
||||
menuManager.uiManager.setNodeVisible("close_inventory_button", false);
|
||||
inventoryOpen = false;
|
||||
});
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "Failed to load UI: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveObject* Game::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
|
||||
if (interactiveObjects.empty()) {
|
||||
std::cout << "[RAYCAST] No interactive objects to check" << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::cout << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl;
|
||||
std::cout << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl;
|
||||
std::cout << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
|
||||
|
||||
float closestDistance = FLT_MAX;
|
||||
InteractiveObject* closestObject = nullptr;
|
||||
|
||||
for (auto& intObj : interactiveObjects) {
|
||||
std::cout << "[RAYCAST] Checking object: " << intObj.name << " (active: " << intObj.isActive << ")" << std::endl;
|
||||
|
||||
if (!intObj.isActive) {
|
||||
std::cout << "[RAYCAST] -> Object inactive, skipping" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
|
||||
<< intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
|
||||
|
||||
Eigen::Vector3f toObject = intObj.position - rayOrigin;
|
||||
std::cout << "[RAYCAST] Vector to object: (" << toObject.x() << ", " << toObject.y() << ", " << toObject.z() << ")" << std::endl;
|
||||
|
||||
float distanceAlongRay = toObject.dot(rayDir);
|
||||
std::cout << "[RAYCAST] Distance along ray: " << distanceAlongRay << std::endl;
|
||||
|
||||
if (distanceAlongRay < 0.1f) {
|
||||
std::cout << "[RAYCAST] -> Object behind camera, skipping" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
|
||||
float distToObject = (closestPointOnRay - intObj.position).norm();
|
||||
|
||||
std::cout << "[RAYCAST] Distance to object: " << distToObject
|
||||
<< " (interaction radius: " << intObj.interactionRadius << ")" << std::endl;
|
||||
|
||||
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
|
||||
std::cout << "[RAYCAST] *** HIT DETECTED! ***" << std::endl;
|
||||
closestDistance = distanceAlongRay;
|
||||
closestObject = &intObj;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestObject) {
|
||||
std::cout << "[RAYCAST] *** RAYCAST SUCCESS: Found object " << closestObject->name << " ***" << std::endl;
|
||||
} else {
|
||||
std::cout << "[RAYCAST] No objects hit" << std::endl;
|
||||
}
|
||||
|
||||
return closestObject;
|
||||
}
|
||||
|
||||
void Game::drawUI()
|
||||
{
|
||||
@ -291,6 +351,11 @@ namespace ZL
|
||||
glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(benchMesh);
|
||||
*/
|
||||
for (auto& intObj : interactiveObjects) {
|
||||
if (intObj.isActive) {
|
||||
intObj.draw(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
if (player) player->draw(renderer);
|
||||
for (auto& npc : npcs) npc->draw(renderer);
|
||||
@ -374,9 +439,23 @@ namespace ZL
|
||||
|
||||
if (player) player->update(delta);
|
||||
for (auto& npc : npcs) npc->update(delta);
|
||||
|
||||
// Check if player reached target interactive object
|
||||
if (targetInteractiveObject && player) {
|
||||
float distToObject = (player->position - targetInteractiveObject->position).norm();
|
||||
|
||||
// If player is close enough to pick up the item
|
||||
if (distToObject <= targetInteractiveObject->interactionRadius + 1.0f) {
|
||||
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
|
||||
std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->id << std::endl;
|
||||
|
||||
// Call Lua callback
|
||||
scriptEngine.callItemPickupCallback(targetInteractiveObject->id);
|
||||
|
||||
targetInteractiveObject = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Game::render() {
|
||||
@ -456,13 +535,14 @@ namespace ZL
|
||||
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
|
||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||
// Преобразуем экранные пиксели в проекционные единицы
|
||||
int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
|
||||
int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
|
||||
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl;
|
||||
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||
|
||||
// Unproject click to ground plane (y=0) for Viola's walk target
|
||||
// Calculate ray for picking
|
||||
float ndcX = 2.0f * event.button.x / Environment::width - 1.0f;
|
||||
float ndcY = 1.0f - 2.0f * event.button.y / Environment::height;
|
||||
float aspect = (float)Environment::width / (float)Environment::height;
|
||||
@ -479,11 +559,33 @@ namespace ZL
|
||||
|
||||
Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized();
|
||||
|
||||
if (rayDir.y() < -0.001f && player) {
|
||||
std::cout << "[CLICK] Camera position: (" << camPos.x() << ", " << camPos.y() << ", " << camPos.z() << ")" << std::endl;
|
||||
std::cout << "[CLICK] Ray direction: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
|
||||
|
||||
// First check if we clicked on interactive object
|
||||
InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir);
|
||||
if (clickedObject && player && clickedObject->isActive) {
|
||||
std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->name << " ***" << std::endl;
|
||||
std::cout << "[CLICK] Object position: (" << clickedObject->position.x() << ", "
|
||||
<< clickedObject->position.y() << ", " << clickedObject->position.z() << ")" << std::endl;
|
||||
std::cout << "[CLICK] Player position: (" << player->position.x() << ", "
|
||||
<< player->position.y() << ", " << player->position.z() << ")" << std::endl;
|
||||
|
||||
targetInteractiveObject = clickedObject;
|
||||
player->setTarget(clickedObject->position);
|
||||
std::cout << "[CLICK] Player moving to object..." << std::endl;
|
||||
}
|
||||
else if (rayDir.y() < -0.001f && player) {
|
||||
// Otherwise, unproject click to ground plane for Viola's walk target
|
||||
float t = -camPos.y() / rayDir.y();
|
||||
Eigen::Vector3f hit = camPos + rayDir * t;
|
||||
std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
|
||||
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
|
||||
}
|
||||
else {
|
||||
std::cout << "[CLICK] No valid target found" << std::endl;
|
||||
}
|
||||
std::cout << "========================================\n" << std::endl;
|
||||
} else {
|
||||
handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||
}
|
||||
|
||||
18
src/Game.h
18
src/Game.h
@ -10,6 +10,8 @@
|
||||
#include "Projectile.h"
|
||||
#include "utils/TaskManager.h"
|
||||
#include "items/GameObjectLoader.h"
|
||||
#include "items/Item.h"
|
||||
#include "items/InteractiveObject.h"
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
@ -61,12 +63,24 @@ namespace ZL {
|
||||
std::shared_ptr<Texture> benchTexture;
|
||||
VertexRenderStruct benchMesh;
|
||||
*/
|
||||
std::vector<InteractiveObject> interactiveObjects;
|
||||
Inventory inventory;
|
||||
InteractiveObject* pickedUpObject = nullptr;
|
||||
|
||||
// Interactive object targeting
|
||||
InteractiveObject* targetInteractiveObject = nullptr;
|
||||
bool inventoryOpen = false;
|
||||
|
||||
std::unique_ptr<Character> player;
|
||||
std::vector<std::unique_ptr<Character>> npcs;
|
||||
|
||||
float cameraAzimuth = 0.0f;
|
||||
float cameraInclination = M_PI * 30.f / 180.f;
|
||||
|
||||
// Public access for ScriptEngine
|
||||
MenuManager menuManager;
|
||||
ScriptEngine scriptEngine;
|
||||
|
||||
private:
|
||||
bool rightMouseDown = false;
|
||||
int lastMouseX = 0;
|
||||
@ -83,6 +97,7 @@ namespace ZL {
|
||||
void handleDown(int64_t fingerId, int mx, int my);
|
||||
void handleUp(int64_t fingerId, int mx, int my);
|
||||
void handleMotion(int64_t fingerId, int mx, int my);
|
||||
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
static Game* s_instance;
|
||||
@ -97,9 +112,6 @@ namespace ZL {
|
||||
|
||||
static const size_t CONST_TIMER_INTERVAL = 10;
|
||||
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
||||
|
||||
MenuManager menuManager;
|
||||
ScriptEngine scriptEngine;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -7,55 +7,200 @@
|
||||
|
||||
namespace ZL {
|
||||
|
||||
struct ScriptEngine::Impl {
|
||||
sol::state lua;
|
||||
};
|
||||
struct ScriptEngine::Impl {
|
||||
sol::state lua;
|
||||
};
|
||||
|
||||
ScriptEngine::ScriptEngine() = default;
|
||||
ScriptEngine::~ScriptEngine() = default;
|
||||
ScriptEngine::ScriptEngine() = default;
|
||||
ScriptEngine::~ScriptEngine() = default;
|
||||
|
||||
void ScriptEngine::init(Game* game) {
|
||||
impl = std::make_unique<Impl>();
|
||||
sol::state& lua = impl->lua;
|
||||
void ScriptEngine::init(Game* game) {
|
||||
impl = std::make_unique<Impl>();
|
||||
sol::state& lua = impl->lua;
|
||||
|
||||
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string, sol::lib::table);
|
||||
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string, sol::lib::table);
|
||||
|
||||
auto api = lua.create_named_table("game_api");
|
||||
auto api = lua.create_named_table("game_api");
|
||||
|
||||
// npc_walk_to(index, x, y, z [, on_arrived])
|
||||
// on_arrived is an optional Lua function called when the NPC reaches the target.
|
||||
// It can call npc_walk_to again (or anything else) to chain behaviour.
|
||||
api.set_function("npc_walk_to",
|
||||
[game](int index, float x, float y, float z, sol::object on_arrived) {
|
||||
auto& npcs = game->npcs;
|
||||
if (index < 0 || index >= static_cast<int>(npcs.size())) {
|
||||
std::cerr << "[script] npc_walk_to: index " << index
|
||||
<< " out of range (0.." << npcs.size() - 1 << ")\n";
|
||||
return;
|
||||
// npc_walk_to(index, x, y, z [, on_arrived])
|
||||
// on_arrived is an optional Lua function called when the NPC reaches the target.
|
||||
// It can call npc_walk_to again (or anything else) to chain behaviour.
|
||||
api.set_function("npc_walk_to",
|
||||
[game](int index, float x, float y, float z, sol::object on_arrived) {
|
||||
auto& npcs = game->npcs;
|
||||
if (index < 0 || index >= static_cast<int>(npcs.size())) {
|
||||
std::cerr << "[script] npc_walk_to: index " << index
|
||||
<< " out of range (0.." << npcs.size() - 1 << ")\n";
|
||||
return;
|
||||
}
|
||||
std::function<void()> cb;
|
||||
if (on_arrived.is<sol::protected_function>()) {
|
||||
sol::protected_function fn = on_arrived.as<sol::protected_function>();
|
||||
cb = [fn]() mutable {
|
||||
auto result = fn();
|
||||
if (!result.valid()) {
|
||||
sol::error err = result;
|
||||
std::cerr << "[script] on_arrived error: " << err.what() << "\n";
|
||||
}
|
||||
};
|
||||
}
|
||||
npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
|
||||
});
|
||||
|
||||
// pickup_item(object_name)
|
||||
api.set_function("pickup_item", [game](const std::string& objectName) {
|
||||
std::cout << "[script] pickup_item: " << objectName << std::endl;
|
||||
|
||||
for (auto& intObj : game->interactiveObjects) {
|
||||
if (intObj.id == objectName && intObj.isActive) {
|
||||
// Add item to inventory
|
||||
game->inventory.addItem(intObj.dropItem);
|
||||
|
||||
// Deactivate object
|
||||
intObj.isActive = false;
|
||||
|
||||
std::cout << "[script] Item picked up successfully: " << intObj.dropItem.name << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::function<void()> cb;
|
||||
if (on_arrived.is<sol::protected_function>()) {
|
||||
sol::protected_function fn = on_arrived.as<sol::protected_function>();
|
||||
cb = [fn]() mutable {
|
||||
auto result = fn();
|
||||
if (!result.valid()) {
|
||||
sol::error err = result;
|
||||
std::cerr << "[script] on_arrived error: " << err.what() << "\n";
|
||||
}
|
||||
};
|
||||
std::cerr << "[script] Warning: Interactive object not found or already picked up: " << objectName << std::endl;
|
||||
});
|
||||
|
||||
|
||||
// show_inventory()
|
||||
api.set_function("show_inventory", [game]() {
|
||||
std::cout << "[script] show_inventory called" << std::endl;
|
||||
|
||||
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", true);
|
||||
game->menuManager.uiManager.setNodeVisible("close_inventory_button", true);
|
||||
game->inventoryOpen = true;
|
||||
|
||||
// Update UI with current items
|
||||
const auto& items = game->inventory.getItems();
|
||||
std::string itemText;
|
||||
|
||||
if (items.empty()) {
|
||||
itemText = "Inventory (Empty)";
|
||||
}
|
||||
else {
|
||||
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
|
||||
for (size_t i = 0; i < items.size(); ++i) {
|
||||
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
|
||||
}
|
||||
}
|
||||
npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
|
||||
});
|
||||
|
||||
runScript("resources/start.lua");
|
||||
}
|
||||
game->menuManager.uiManager.setText("inventory_items_text", itemText);
|
||||
});
|
||||
|
||||
void ScriptEngine::runScript(const std::string& path) {
|
||||
auto result = impl->lua.safe_script_file(path, sol::script_pass_on_error);
|
||||
if (!result.valid()) {
|
||||
sol::error err = result;
|
||||
std::cerr << "[script] Error in " << path << ": " << err.what() << "\n";
|
||||
// hide_inventory()
|
||||
api.set_function("hide_inventory", [game]() {
|
||||
std::cout << "[script] hide_inventory called" << std::endl;
|
||||
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
|
||||
game->menuManager.uiManager.setNodeVisible("close_inventory_button", false);
|
||||
game->inventoryOpen = false;
|
||||
});
|
||||
|
||||
// add_item(item_id, name, description, icon)
|
||||
api.set_function("add_item", [game](const std::string& id, const std::string& name,
|
||||
const std::string& description, const std::string& icon) {
|
||||
std::cout << "[script] add_item: " << name << std::endl;
|
||||
Item newItem(id, name, description, icon);
|
||||
game->inventory.addItem(newItem);
|
||||
});
|
||||
|
||||
// remove_item(item_id)
|
||||
api.set_function("remove_item", [game](const std::string& id) {
|
||||
std::cout << "[script] remove_item: " << id << std::endl;
|
||||
game->inventory.removeItem(id);
|
||||
});
|
||||
|
||||
// deactivate_interactive_object(object_name)
|
||||
api.set_function("deactivate_interactive_object", [game](const std::string& objectName) {
|
||||
std::cout << "[script] deactivate_interactive_object: " << objectName << std::endl;
|
||||
for (auto& intObj : game->interactiveObjects) {
|
||||
if (intObj.id == objectName) {
|
||||
intObj.isActive = false;
|
||||
std::cout << "[script] Interactive object deactivated: " << objectName << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::cerr << "[script] Warning: Interactive object not found: " << objectName << std::endl;
|
||||
});
|
||||
|
||||
// get_inventory_count()
|
||||
api.set_function("get_inventory_count", [game]() {
|
||||
return game->inventory.getCount();
|
||||
});
|
||||
|
||||
// has_item(item_id)
|
||||
api.set_function("has_item", [game](const std::string& id) {
|
||||
return game->inventory.hasItem(id);
|
||||
});
|
||||
|
||||
lua.script_file("resources/start.lua");
|
||||
}
|
||||
|
||||
void ScriptEngine::runScript(const std::string& path) {
|
||||
auto result = impl->lua.safe_script_file(path, sol::script_pass_on_error);
|
||||
if (!result.valid()) {
|
||||
sol::error err = result;
|
||||
std::cerr << "[script] Error in " << path << ": " << err.what() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::showInventory(Game* game) {
|
||||
std::cout << "[script] toggle_inventory called" << std::endl;
|
||||
|
||||
bool isVisible = game->menuManager.uiManager.getNodeVisible("inventory_items_panel");
|
||||
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", !isVisible);
|
||||
game->menuManager.uiManager.setNodeVisible("close_inventory_button", !isVisible);
|
||||
|
||||
game->inventoryOpen = !isVisible;
|
||||
|
||||
// Update UI with current items only if showing
|
||||
if (!isVisible) {
|
||||
const auto& items = game->inventory.getItems();
|
||||
std::string itemText;
|
||||
|
||||
if (items.empty()) {
|
||||
itemText = "Inventory (Empty)";
|
||||
}
|
||||
else {
|
||||
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
|
||||
for (size_t i = 0; i < items.size(); ++i) {
|
||||
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
game->menuManager.uiManager.setText("inventory_items_text", itemText);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::callItemPickupCallback(const std::string& objectName) {
|
||||
if (!impl) {
|
||||
std::cerr << "[SCRIPT] impl is null!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
sol::state& lua = impl->lua;
|
||||
std::cout << "[SCRIPT] Looking for callback: on_item_pickup" << std::endl;
|
||||
|
||||
sol::function pickup = lua["on_item_pickup"];
|
||||
|
||||
if (pickup.valid()) {
|
||||
std::cout << "[SCRIPT] Callback found! Calling with argument: " << objectName << std::endl;
|
||||
auto result = pickup(objectName);
|
||||
if (!result.valid()) {
|
||||
sol::error err = result;
|
||||
std::cerr << "[SCRIPT] on_item_pickup callback error: " << err.what() << "\n";
|
||||
}
|
||||
else {
|
||||
std::cout << "[SCRIPT] Callback executed successfully!" << std::endl;
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::cerr << "[SCRIPT] on_item_pickup callback not found!" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
|
||||
@ -17,6 +17,9 @@ public:
|
||||
// Execute a Lua script file. Logs errors to stderr.
|
||||
void runScript(const std::string& path);
|
||||
|
||||
void callItemPickupCallback(const std::string& objectName);
|
||||
void showInventory(Game* game);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
|
||||
@ -1092,24 +1092,55 @@ namespace ZL {
|
||||
renderer.PushMatrix();
|
||||
renderer.LoadIdentity();
|
||||
|
||||
for (const auto& img : staticImages) {
|
||||
img->draw(renderer);
|
||||
}
|
||||
for (const auto& b : buttons) {
|
||||
b->draw(renderer);
|
||||
}
|
||||
for (const auto& tb : textButtons) {
|
||||
tb->draw(renderer);
|
||||
}
|
||||
for (const auto& s : sliders) {
|
||||
s->draw(renderer);
|
||||
}
|
||||
for (const auto& tv : textViews) {
|
||||
tv->draw(renderer);
|
||||
}
|
||||
for (const auto& tf : textFields) {
|
||||
tf->draw(renderer);
|
||||
}
|
||||
std::function<void(const std::shared_ptr<UiNode>&)> drawNode =
|
||||
[&](const std::shared_ptr<UiNode>& node) {
|
||||
if (!node || !node->visible) return;
|
||||
|
||||
renderer.PushMatrix();
|
||||
renderer.TranslateMatrix({ node->screenRect.x + node->screenRect.w * 0.5f,
|
||||
node->screenRect.y + node->screenRect.h * 0.5f, 0.0f });
|
||||
renderer.ScaleMatrix({ node->scaleX, node->scaleY, 1.0f });
|
||||
renderer.TranslateMatrix({ -(node->screenRect.x + node->screenRect.w * 0.5f),
|
||||
-(node->screenRect.y + node->screenRect.h * 0.5f), 0.0f });
|
||||
|
||||
|
||||
// 1. Сначала изображения
|
||||
if (node->staticImage) {
|
||||
node->staticImage->draw(renderer);
|
||||
}
|
||||
|
||||
// 2. Потом кнопки
|
||||
if (node->button) {
|
||||
node->button->draw(renderer);
|
||||
}
|
||||
if (node->textButton) {
|
||||
node->textButton->draw(renderer);
|
||||
}
|
||||
|
||||
// 3. Потом слайдеры
|
||||
if (node->slider) {
|
||||
node->slider->draw(renderer);
|
||||
}
|
||||
|
||||
// 4. Потом текстовые поля
|
||||
if (node->textField) {
|
||||
node->textField->draw(renderer);
|
||||
}
|
||||
|
||||
// 5. ПОСЛЕДНИЙ - ТЕКСТ (поверх всего!)
|
||||
if (node->textView) {
|
||||
node->textView->draw(renderer);
|
||||
}
|
||||
|
||||
// Рисуем детей
|
||||
for (auto& child : node->children) {
|
||||
drawNode(child);
|
||||
}
|
||||
|
||||
renderer.PopMatrix();
|
||||
};
|
||||
|
||||
drawNode(root);
|
||||
|
||||
renderer.PopMatrix();
|
||||
renderer.PopProjectionMatrix();
|
||||
@ -1641,4 +1672,23 @@ namespace ZL {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<UiNode> UiManager::findNode(const std::string& name) {
|
||||
if (!root) return nullptr;
|
||||
return findNodeByName(root, name);
|
||||
}
|
||||
|
||||
bool UiManager::setNodeVisible(const std::string& nodeName, bool visible) {
|
||||
if (!root) return false;
|
||||
auto node = findNodeByName(root, nodeName);
|
||||
if (!node) return false;
|
||||
node->visible = visible;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiManager::getNodeVisible(const std::string& nodeName) {
|
||||
if (!root) return false;
|
||||
auto node = findNodeByName(root, nodeName);
|
||||
if (!node) return false;
|
||||
return node->visible;
|
||||
}
|
||||
} // namespace ZL
|
||||
@ -228,6 +228,11 @@ namespace ZL {
|
||||
float width = 0;
|
||||
float height = 0;
|
||||
|
||||
float scaleX = 1.0f;
|
||||
float scaleY = 1.0f;
|
||||
|
||||
bool visible = true;
|
||||
|
||||
// Иерархия
|
||||
std::vector<std::shared_ptr<UiNode>> children;
|
||||
|
||||
@ -353,6 +358,11 @@ namespace ZL {
|
||||
bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb);
|
||||
void updateAllLayouts();
|
||||
|
||||
std::shared_ptr<UiNode> findNode(const std::string& name);
|
||||
|
||||
bool setNodeVisible(const std::string& nodeName, bool visible);
|
||||
bool getNodeVisible(const std::string& nodeName);
|
||||
|
||||
private:
|
||||
void layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY);
|
||||
void syncComponentRects(const std::shared_ptr<UiNode>& node);
|
||||
|
||||
@ -50,6 +50,17 @@ namespace ZL {
|
||||
|
||||
data.scale = item.value("scale", 1.0f);
|
||||
|
||||
// Interactive object properties
|
||||
data.interactive = item.value("interactive", false);
|
||||
if (data.interactive && item.contains("item") && item["item"].is_object()) {
|
||||
const auto& itemData = item["item"];
|
||||
data.itemId = itemData.value("id", "");
|
||||
data.itemName = itemData.value("name", "Unknown Item");
|
||||
data.itemDescription = itemData.value("description", "");
|
||||
data.itemIcon = itemData.value("icon", "");
|
||||
data.interactionRadius = itemData.value("radius", 2.0f);
|
||||
}
|
||||
|
||||
if (!data.meshPath.empty()) {
|
||||
objects.push_back(data);
|
||||
}
|
||||
@ -73,6 +84,8 @@ namespace ZL {
|
||||
std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath);
|
||||
|
||||
for (const auto& objData : objectsData) {
|
||||
if (objData.interactive) continue; // Skip interactive objects, they're handled separately
|
||||
|
||||
std::cout << "Loading game object: " << objData.name << std::endl;
|
||||
|
||||
LoadedGameObject gameObj;
|
||||
@ -131,4 +144,104 @@ namespace ZL {
|
||||
return gameObjects;
|
||||
}
|
||||
|
||||
std::vector<InteractiveObject> GameObjectLoader::loadAndCreateInteractiveObjects(
|
||||
const std::string& jsonPath,
|
||||
Renderer& renderer,
|
||||
const std::string& zipPath)
|
||||
{
|
||||
std::vector<InteractiveObject> interactiveObjects;
|
||||
std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath);
|
||||
|
||||
for (const auto& objData : objectsData) {
|
||||
if (!objData.interactive) continue;
|
||||
|
||||
std::cout << "Loading interactive object: " << objData.name << std::endl;
|
||||
|
||||
InteractiveObject intObj;
|
||||
intObj.id = objData.name;
|
||||
intObj.name = objData.name;
|
||||
intObj.interactionRadius = objData.interactionRadius;
|
||||
|
||||
// Load texture
|
||||
try {
|
||||
intObj.texture = std::make_shared<Texture>(CreateTextureDataFromPng(objData.texturePath, zipPath.c_str()));
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "GameObjectLoader: Failed to load texture for interactive '" << objData.name << "': " << e.what() << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load mesh
|
||||
try {
|
||||
intObj.mesh.data = LoadFromTextFile02(objData.meshPath, zipPath);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "GameObjectLoader: Failed to load mesh for interactive '" << objData.name << "': " << e.what() << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply rotation
|
||||
Eigen::Quaternionf rotationQuat = Eigen::Quaternionf::Identity();
|
||||
|
||||
if (objData.rotationX != 0.0f) {
|
||||
rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationX, Eigen::Vector3f::UnitX())) * rotationQuat;
|
||||
}
|
||||
if (objData.rotationY != 0.0f) {
|
||||
rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationY, Eigen::Vector3f::UnitY())) * rotationQuat;
|
||||
}
|
||||
if (objData.rotationZ != 0.0f) {
|
||||
rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationZ, Eigen::Vector3f::UnitZ())) * rotationQuat;
|
||||
}
|
||||
|
||||
intObj.mesh.data.RotateByMatrix(rotationQuat.toRotationMatrix());
|
||||
|
||||
// Apply scale
|
||||
if (objData.scale != 1.0f) {
|
||||
intObj.mesh.data.Scale(objData.scale);
|
||||
}
|
||||
|
||||
intObj.mesh.RefreshVBO();
|
||||
|
||||
// Calculate mesh bounds to properly offset position
|
||||
if (!intObj.mesh.data.PositionData.empty()) {
|
||||
Eigen::Vector3f meshMin = intObj.mesh.data.PositionData[0];
|
||||
Eigen::Vector3f meshMax = intObj.mesh.data.PositionData[0];
|
||||
|
||||
for (const auto& vert : intObj.mesh.data.PositionData) {
|
||||
meshMin = meshMin.cwiseMin(vert);
|
||||
meshMax = meshMax.cwiseMax(vert);
|
||||
}
|
||||
|
||||
Eigen::Vector3f meshCenter = (meshMin + meshMax) * 0.5f;
|
||||
std::cout << "[LOADER] Mesh bounds:" << std::endl;
|
||||
std::cout << " Min: (" << meshMin.x() << ", " << meshMin.y() << ", " << meshMin.z() << ")" << std::endl;
|
||||
std::cout << " Max: (" << meshMax.x() << ", " << meshMax.y() << ", " << meshMax.z() << ")" << std::endl;
|
||||
std::cout << " Center: (" << meshCenter.x() << ", " << meshCenter.y() << ", " << meshCenter.z() << ")" << std::endl;
|
||||
|
||||
// Translate mesh so it's centered at origin
|
||||
intObj.mesh.data.Move(-meshCenter);
|
||||
intObj.mesh.RefreshVBO();
|
||||
|
||||
// Adjust position to account for mesh offset
|
||||
intObj.position = Eigen::Vector3f(objData.positionX, objData.positionY, objData.positionZ) + meshCenter;
|
||||
|
||||
std::cout << "[LOADER] Corrected position: (" << intObj.position.x() << ", "
|
||||
<< intObj.position.y() << ", " << intObj.position.z() << ")" << std::endl;
|
||||
} else {
|
||||
// Fallback if no vertices
|
||||
intObj.position = Eigen::Vector3f(objData.positionX, objData.positionY, objData.positionZ);
|
||||
}
|
||||
|
||||
// Create item
|
||||
intObj.dropItem = Item(objData.itemId, objData.itemName, objData.itemDescription, objData.itemIcon);
|
||||
|
||||
interactiveObjects.push_back(intObj);
|
||||
|
||||
std::cout << "Successfully loaded interactive: " << objData.name << " (item: " << objData.itemName << ")" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Total interactive objects loaded: " << interactiveObjects.size() << std::endl;
|
||||
return interactiveObjects;
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
@ -5,6 +5,7 @@
|
||||
#include <unordered_map>
|
||||
#include "external/nlohmann/json.hpp"
|
||||
#include "TextModel.h"
|
||||
#include "InteractiveObject.h"
|
||||
|
||||
namespace ZL {
|
||||
|
||||
@ -23,6 +24,12 @@ namespace ZL {
|
||||
float positionY = 0.0f;
|
||||
float positionZ = 0.0f;
|
||||
float scale = 1.0f;
|
||||
bool interactive = false;
|
||||
std::string itemId;
|
||||
std::string itemName;
|
||||
std::string itemDescription;
|
||||
std::string itemIcon;
|
||||
float interactionRadius = 2.0f;
|
||||
};
|
||||
|
||||
struct LoadedGameObject {
|
||||
@ -40,6 +47,12 @@ namespace ZL {
|
||||
Renderer& renderer,
|
||||
const std::string& zipPath = ""
|
||||
);
|
||||
|
||||
static std::vector<InteractiveObject> loadAndCreateInteractiveObjects(
|
||||
const std::string& jsonPath,
|
||||
Renderer& renderer,
|
||||
const std::string& zipPath = ""
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace ZL
|
||||
37
src/items/InteractiveObject.cpp
Normal file
37
src/items/InteractiveObject.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include "InteractiveObject.h"
|
||||
#include "render/Renderer.h"
|
||||
#include "render/TextureManager.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace ZL {
|
||||
extern const std::string textureUniformName;
|
||||
}
|
||||
|
||||
namespace ZL {
|
||||
|
||||
void InteractiveObject::draw(Renderer& renderer) const {
|
||||
if (!isActive || !texture) return;
|
||||
|
||||
std::cout << "[DRAW] InteractiveObject::draw() called" << std::endl;
|
||||
std::cout << "[DRAW] Object: " << name << std::endl;
|
||||
std::cout << "[DRAW] Position: (" << position.x() << ", " << position.y() << ", " << position.z() << ")" << std::endl;
|
||||
|
||||
// Check mesh bounds
|
||||
if (!mesh.data.PositionData.empty()) {
|
||||
std::cout << "[DRAW] First vertex: (" << mesh.data.PositionData[0].x() << ", "
|
||||
<< mesh.data.PositionData[0].y() << ", " << mesh.data.PositionData[0].z() << ")" << std::endl;
|
||||
}
|
||||
|
||||
// Apply position transformation
|
||||
renderer.PushMatrix();
|
||||
renderer.TranslateMatrix(position);
|
||||
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(mesh);
|
||||
|
||||
renderer.PopMatrix();
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
33
src/items/InteractiveObject.h
Normal file
33
src/items/InteractiveObject.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <Eigen/Core>
|
||||
#include "Item.h"
|
||||
#include "render/Renderer.h"
|
||||
|
||||
namespace ZL {
|
||||
|
||||
struct Texture;
|
||||
struct VertexRenderStruct;
|
||||
class Renderer;
|
||||
|
||||
struct InteractiveObject {
|
||||
std::string id;
|
||||
std::string name;
|
||||
Eigen::Vector3f position;
|
||||
float interactionRadius;
|
||||
std::shared_ptr<Texture> texture;
|
||||
VertexRenderStruct mesh;
|
||||
Item dropItem;
|
||||
bool isActive = true;
|
||||
|
||||
InteractiveObject() : interactionRadius(2.0f) {}
|
||||
|
||||
bool isInRange(const Eigen::Vector3f& playerPos) const {
|
||||
return (playerPos - position).norm() <= interactionRadius;
|
||||
}
|
||||
|
||||
void draw(Renderer& renderer) const;
|
||||
};
|
||||
|
||||
} // namespace ZL
|
||||
26
src/items/Item.cpp
Normal file
26
src/items/Item.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#include "Item.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
void Inventory::addItem(const Item& item) {
|
||||
items.push_back(item);
|
||||
std::cout << "Item added to inventory: " << item.name << std::endl;
|
||||
}
|
||||
|
||||
void Inventory::removeItem(const std::string& itemId) {
|
||||
auto it = std::find_if(items.begin(), items.end(),
|
||||
[&itemId](const Item& item) { return item.id == itemId; });
|
||||
if (it != items.end()) {
|
||||
std::cout << "Item removed from inventory: " << it->name << std::endl;
|
||||
items.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool Inventory::hasItem(const std::string& itemId) const {
|
||||
return std::find_if(items.begin(), items.end(),
|
||||
[&itemId](const Item& item) { return item.id == itemId; }) != items.end();
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
33
src/items/Item.h
Normal file
33
src/items/Item.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
struct Item {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string icon;
|
||||
|
||||
Item() = default;
|
||||
Item(const std::string& _id, const std::string& _name, const std::string& _desc, const std::string& _icon)
|
||||
: id(_id), name(_name), description(_desc), icon(_icon) {
|
||||
}
|
||||
};
|
||||
|
||||
class Inventory {
|
||||
private:
|
||||
std::vector<Item> items;
|
||||
|
||||
public:
|
||||
void addItem(const Item& item);
|
||||
void removeItem(const std::string& itemId);
|
||||
const std::vector<Item>& getItems() const { return items; }
|
||||
bool hasItem(const std::string& itemId) const;
|
||||
void clear() { items.clear(); }
|
||||
size_t getCount() const { return items.size(); }
|
||||
};
|
||||
|
||||
} // namespace ZL
|
||||
Loading…
Reference in New Issue
Block a user