diff --git a/resources/config2/interactive_objects_uni_interior.json b/resources/config2/interactive_objects_uni_interior.json index 5179925..745934b 100644 --- a/resources/config2/interactive_objects_uni_interior.json +++ b/resources/config2/interactive_objects_uni_interior.json @@ -52,7 +52,7 @@ "positionY": 0.95746, "positionZ": 0.786023, "scale": 0.5, - "interactionRadius": 1.5, + "interactionRadius": 0.3, "activateFunction": "on_book_pickup" }, { @@ -107,6 +107,9 @@ "positionX": 1.5, "positionY": 0.975, "positionZ": 6.965, + "pivotX": 0.0, + "pivotY": 0.0, + "pivotZ": 0.565, "scale": 1.0, "interactionRadius": 0.5, "activateFunction": "on_library_door_click" @@ -121,6 +124,9 @@ "positionX": -1.5, "positionY": 0.975, "positionZ": 6.965, + "pivotX": 0.0, + "pivotY": 0.0, + "pivotZ": 0.565, "scale": 1.0, "interactionRadius": 0.5, "activateFunction": "on_teachers_door_click" @@ -136,6 +142,9 @@ "positionY": 0.975, "positionZ": 8, "scale": 1.0, + "pivotX": 0.565, + "pivotY": 0.0, + "pivotZ": 0.0, "interactionRadius": 0.5, "activateFunction": "on_hall_door_click" } diff --git a/resources/config2/trigger_zones_uni_interior.json b/resources/config2/trigger_zones_uni_interior.json index 893dcb0..d40ec50 100644 --- a/resources/config2/trigger_zones_uni_interior.json +++ b/resources/config2/trigger_zones_uni_interior.json @@ -5,7 +5,7 @@ "positionX": -0.141875, "positionY": 0.0, "positionZ": 9.75898, - "radius": 2.0, + "radius": 1.7, "hysteresis": 0.1, "enabled": true }, diff --git a/resources/dialogue/uni_interior_dialogues.json b/resources/dialogue/uni_interior_dialogues.json index 3fb3e35..3d82837 100644 --- a/resources/dialogue/uni_interior_dialogues.json +++ b/resources/dialogue/uni_interior_dialogues.json @@ -69,10 +69,70 @@ "type": "End" } ] + }, + { + "id": "door_night_dialog001", + "start": "line_1", + "nodes": [ + { + "id": "line_1", + "type": "Line", + "speaker": "Бекзат", + "portrait": "resources/w/gg/gg2_s_podsvetkoy5.png", + "text": "Дверь закрыта на ключ.", + "next": "line_2" + }, + { + "id": "line_2", + "type": "Line", + "speaker": "Бекзат", + "portrait": "resources/w/gg/gg2_s_podsvetkoy5.png", + "text": "Видимо когда я спал, охранник запер дверь.", + "next": "line_3" + }, + { + "id": "line_3", + "type": "Line", + "speaker": "Бекзат", + "portrait": "resources/w/gg/gg2_s_podsvetkoy5.png", + "text": "Это уже не смешно, как я отсюда выберусь?", + "next": "end_1" + }, + { + "id": "end_1", + "type": "End" + } + ] }, { "id": "teacher_dialog001", "start": "line_1", + "nodes": [ + { + "id": "line_1", + "type": "Line", + "speaker": "Аида Дженибековна", + "portrait": "resources/w/avatar_teacher.png", + "text": "Бекзат, не мешай, я занята.", + "next": "line_2" + }, + { + "id": "line_2", + "type": "Line", + "speaker": "Бекзат", + "portrait": "resources/w/gg/gg2_s_podsvetkoy5.png", + "text": "Ладно...", + "next": "end_1" + }, + { + "id": "end_1", + "type": "End" + } + ] + }, + { + "id": "teacher_dialog002", + "start": "line_1", "nodes": [ { "id": "line_1", diff --git a/resources/start_uni_interior.lua b/resources/start_uni_interior.lua index 60e61c1..a66c71c 100644 --- a/resources/start_uni_interior.lua +++ b/resources/start_uni_interior.lua @@ -1,10 +1,13 @@ +hall_door_opened = false +teacher_door_opened = false lection_is_over = false player_hold_book = false player_hold_knife = false teacher_arrived = false +teacher_told_about_book = false night_time = false @@ -55,6 +58,7 @@ game_api.npc_walk_to(0, -4.57412, 0, 6.78495, on_teacher_arrived2) end function on_book_pickup() + if (teacher_told_about_book) then if not player_hold_book then game_api.pickup_item("book") game_api.deactivate_interactive_object("Book001") @@ -64,6 +68,7 @@ function on_book_pickup() game_api.activate_interactive_object("Book001") player_hold_book = false end + end end function on_bookshelf_clicked() @@ -88,34 +93,52 @@ function on_npc_interact(npc_index) game_api.start_dialogue("knife_dialog001") end if npc_index == 0 then - --game_api.start_dialogue("teacher_dialog001") + game_api.start_dialogue("teacher_dialog001") --game_api.set_trigger_zone_enabled(2, false) end end function teacher_zone001_enter_callback() -game_api.start_dialogue("teacher_dialog001") +game_api.start_dialogue("teacher_dialog002") game_api.set_trigger_zone_enabled(2, false) +teacher_told_about_book = true end function on_locked_door_click() game_api.start_dialogue("door_dialog001") +-- Example: uncomment and replace "DoorName" to animate a door open over 1.5 seconds +-- game_api.rotate_object("DoorName", 90, 1.5, function() +-- print("Door finished opening!") +-- end) end function on_library_door_click() +if (night_time) then +game_api.start_dialogue("door_night_dialog001") +else if (not lection_is_over) then game_api.start_dialogue("door_dialog001") end end +end function on_teachers_door_click() if (not lection_is_over) then game_api.start_dialogue("door_dialog001") +else +if (not teacher_door_opened) then +teacher_door_opened = true +game_api.rotate_object("Room_S_2_Leaf001", 90, 0.5, nil) +end end end function on_hall_door_click() +if (not hall_door_opened) then +hall_door_opened = true +game_api.rotate_object("Hall_Leaf001", 90, 0.5, nil) +end end @@ -151,13 +174,19 @@ function on_computer_clicked() end end +function on_teacher_arrived_intermediate() +game_api.rotate_object("Room_N_2_Leaf001", -90, 0.5, nil) +game_api.npc_walk_to(0, 3.19574, 0, 6.45595, on_teacher_arrived) +end + + game_api.set_cutscene_callback("test_cutscene_01", function() print("Cutscene done!") lection_is_over = true if (player_hold_knife == false) then game_api.set_npc_enabled(1, true) end - game_api.npc_walk_to(0, 3.19574, 0, 6.45595, on_teacher_arrived) + game_api.npc_walk_to(0, 0.916388, 0, 6.32083, on_teacher_arrived_intermediate) end) game_api.set_cutscene_callback("test_cutscene_02", function() @@ -165,6 +194,7 @@ game_api.set_cutscene_callback("test_cutscene_02", function() night_time = true game_api.set_npc_enabled(0, false) game_api.switch_navigation(0) + game_api.rotate_object("Room_N_2_Leaf001", 90, 0.01, nil) end) game_api.set_trigger_zone_callbacks("teacher_dialog_zone001", diff --git a/src/Game.cpp b/src/Game.cpp index a70d43b..e52164a 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -511,6 +511,7 @@ namespace ZL } } + if (event.type == SDL_KEYDOWN && event.key.repeat == 0) { switch (event.key.keysym.sym) { case SDLK_1: diff --git a/src/Location.cpp b/src/Location.cpp index 552cb58..f67f139 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -515,8 +515,8 @@ namespace ZL for (auto& intObj : interactiveObjects) { std::cout << "[RAYCAST] Checking object: " << intObj.loadedObject.name << " (active: " << intObj.isActive << ")" << std::endl; - if (!intObj.isActive) { - std::cout << "[RAYCAST] -> Object inactive, skipping" << std::endl; + if (!intObj.isActive || intObj.isAnimating) { + std::cout << "[RAYCAST] -> Object inactive or animating, skipping" << std::endl; continue; } @@ -1054,6 +1054,10 @@ namespace ZL void Location::update(int64_t delta) { + for (auto& intObj : interactiveObjects) { + intObj.update(delta); + } + if (player) { player->update(delta); dialogueSystem.update(static_cast(delta)); @@ -1068,7 +1072,7 @@ namespace ZL updateDynamicReplans(delta); // Check if player reached target interactive object - if (targetInteractiveObject && player) { + if (targetInteractiveObject && player && !targetInteractiveObject->isAnimating) { float distToObject = (player->position - targetInteractiveObject->position).norm(); // If player is close enough to pick up the item diff --git a/src/ScriptEngine.cpp b/src/ScriptEngine.cpp index 2875bf1..037b73a 100644 --- a/src/ScriptEngine.cpp +++ b/src/ScriptEngine.cpp @@ -185,6 +185,88 @@ namespace ZL { npcs[index]->enabled = value; }); + // move_object(name, x, y, z, duration_sec [, on_complete]) + api.set_function("move_object", + [game](const std::string& name, float x, float y, float z, + float durationSec, sol::object onComplete) { + for (auto& intObj : game->interactiveObjects) { + if (intObj.loadedObject.name != name) continue; + if (intObj.isAnimating) { + std::cerr << "[script] move_object: '" << name << "' is already animating\n"; + return; + } + std::function cb; + if (onComplete.is()) { + sol::protected_function fn = onComplete.as(); + cb = [fn]() mutable { + auto res = fn(); + if (!res.valid()) { + sol::error err = res; + std::cerr << "[script] move_object on_complete error: " << err.what() << "\n"; + } + }; + } + intObj.moveTo(Eigen::Vector3f(x, y, z), durationSec, std::move(cb)); + return; + } + std::cerr << "[script] move_object: object '" << name << "' not found\n"; + }); + + // rotate_object(name, angle_deg, duration_sec [, on_complete]) + api.set_function("rotate_object", + [game](const std::string& name, float angleDeg, + float durationSec, sol::object onComplete) { + for (auto& intObj : game->interactiveObjects) { + if (intObj.loadedObject.name != name) continue; + if (intObj.isAnimating) { + std::cerr << "[script] rotate_object: '" << name << "' is already animating\n"; + return; + } + std::function cb; + if (onComplete.is()) { + sol::protected_function fn = onComplete.as(); + cb = [fn]() mutable { + auto res = fn(); + if (!res.valid()) { + sol::error err = res; + std::cerr << "[script] rotate_object on_complete error: " << err.what() << "\n"; + } + }; + } + const float angleRad = angleDeg * static_cast(M_PI) / 180.f; + intObj.rotateTo(intObj.rotationY + angleRad, durationSec, std::move(cb)); + return; + } + std::cerr << "[script] rotate_object: object '" << name << "' not found\n"; + }); + + // scale_object(name, target_scale, duration_sec [, on_complete]) + api.set_function("scale_object", + [game](const std::string& name, float targetScale, + float durationSec, sol::object onComplete) { + for (auto& intObj : game->interactiveObjects) { + if (intObj.loadedObject.name != name) continue; + if (intObj.isAnimating) { + std::cerr << "[script] scale_object: '" << name << "' is already animating\n"; + return; + } + std::function cb; + if (onComplete.is()) { + sol::protected_function fn = onComplete.as(); + cb = [fn]() mutable { + auto res = fn(); + if (!res.valid()) { + sol::error err = res; + std::cerr << "[script] scale_object on_complete error: " << err.what() << "\n"; + } + }; + } + intObj.scaleTo(targetScale, durationSec, std::move(cb)); + return; + } + std::cerr << "[script] scale_object: object '" << name << "' not found\n"; + }); + lua.script_file(scriptPath); } diff --git a/src/items/GameObjectLoader.cpp b/src/items/GameObjectLoader.cpp index 4846c6e..f54fa73 100644 --- a/src/items/GameObjectLoader.cpp +++ b/src/items/GameObjectLoader.cpp @@ -93,6 +93,9 @@ namespace ZL { data.base = parseGameObjectData(item); data.interactionRadius = item.value("interactionRadius", 2.0f); data.activateFunctionName = item.value("activateFunction", ""); + data.pivotX = item.value("pivotX", 0.0f); + data.pivotY = item.value("pivotY", 0.0f); + data.pivotZ = item.value("pivotZ", 0.0f); if (!data.base.meshPath.empty()) objects.push_back(std::move(data)); } @@ -176,6 +179,7 @@ namespace ZL { intObj.loadedObject = buildLoadedObject(data.base, renderer, zipPath); intObj.interactionRadius = data.interactionRadius; intObj.activateFunctionName = data.activateFunctionName; + intObj.pivot = Eigen::Vector3f(data.pivotX, data.pivotY, data.pivotZ); intObj.loadedObject.mesh.RefreshVBO(); diff --git a/src/items/GameObjectLoader.h b/src/items/GameObjectLoader.h index 9c33390..ac0e356 100644 --- a/src/items/GameObjectLoader.h +++ b/src/items/GameObjectLoader.h @@ -29,6 +29,9 @@ namespace ZL { GameObjectData base; float interactionRadius = 2.0f; std::string activateFunctionName; + float pivotX = 0.0f; + float pivotY = 0.0f; + float pivotZ = 0.0f; }; struct NpcData { diff --git a/src/items/InteractiveObject.cpp b/src/items/InteractiveObject.cpp index 3f0a096..3d99f0e 100644 --- a/src/items/InteractiveObject.cpp +++ b/src/items/InteractiveObject.cpp @@ -3,6 +3,8 @@ #include "render/TextureManager.h" #include #include +#include +#include namespace ZL { extern const std::string textureUniformName; @@ -10,10 +12,102 @@ namespace ZL { namespace ZL { + void InteractiveObject::moveTo(const Eigen::Vector3f& target, float durationSec, std::function onComplete) { + if (isAnimating) return; + AnimTask task; + task.type = AnimTask::Type::Move; + task.startPos = position; + task.startRotY = rotationY; + task.startScale = scale; + task.targetPos = target; + task.targetRotY = rotationY; + task.targetScale = scale; + task.durationMs = durationSec * 1000.f; + task.elapsedMs = 0.f; + task.onComplete = std::move(onComplete); + animTask = std::move(task); + isAnimating = true; + } + + void InteractiveObject::rotateTo(float targetRotY, float durationSec, std::function onComplete) { + if (isAnimating) return; + AnimTask task; + task.type = AnimTask::Type::Rotate; + task.startPos = position; + task.startRotY = rotationY; + task.startScale = scale; + task.targetPos = position; + task.targetRotY = targetRotY; + task.targetScale = scale; + task.durationMs = durationSec * 1000.f; + task.elapsedMs = 0.f; + task.onComplete = std::move(onComplete); + animTask = std::move(task); + isAnimating = true; + } + + void InteractiveObject::scaleTo(float targetScale, float durationSec, std::function onComplete) { + if (isAnimating) return; + AnimTask task; + task.type = AnimTask::Type::Scale; + task.startPos = position; + task.startRotY = rotationY; + task.startScale = scale; + task.targetPos = position; + task.targetRotY = rotationY; + task.targetScale = targetScale; + task.durationMs = durationSec * 1000.f; + task.elapsedMs = 0.f; + task.onComplete = std::move(onComplete); + animTask = std::move(task); + isAnimating = true; + } + + void InteractiveObject::update(int64_t deltaMs) { + if (!isAnimating || !animTask) return; + + AnimTask& task = *animTask; + task.elapsedMs += static_cast(deltaMs); + + const float t = min(task.elapsedMs / task.durationMs, 1.0f); + + switch (task.type) { + case AnimTask::Type::Move: + position = task.startPos + (task.targetPos - task.startPos) * t; + break; + case AnimTask::Type::Rotate: + rotationY = task.startRotY + (task.targetRotY - task.startRotY) * t; + break; + case AnimTask::Type::Scale: + scale = task.startScale + (task.targetScale - task.startScale) * t; + break; + } + + if (t >= 1.0f) { + // Snap to exact target + position = task.targetPos; + rotationY = task.targetRotY; + scale = task.targetScale; + + std::function cb = std::move(task.onComplete); + animTask.reset(); + isAnimating = false; + + if (cb) cb(); + } + } + void InteractiveObject::draw(Renderer& renderer) const { if (!isActive || !loadedObject.texture) return; renderer.PushMatrix(); renderer.TranslateMatrix(position); + if (rotationY != 0.f) { + renderer.TranslateMatrix(pivot); + renderer.RotateMatrix(Eigen::AngleAxisf(rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix()); + renderer.TranslateMatrix(-pivot); + } + if (scale != 1.f) + renderer.ScaleMatrix(scale); renderer.RenderUniform1i(textureUniformName, 0); glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID()); renderer.DrawVertexRenderStruct(loadedObject.mesh); diff --git a/src/items/InteractiveObject.h b/src/items/InteractiveObject.h index 54aa295..9e8617e 100644 --- a/src/items/InteractiveObject.h +++ b/src/items/InteractiveObject.h @@ -1,6 +1,9 @@ #pragma once #include #include +#include +#include +#include #include #include "render/Renderer.h" @@ -17,12 +20,35 @@ namespace ZL { struct InteractiveObject { LoadedGameObject loadedObject; // name, texture, mesh Eigen::Vector3f position; + Eigen::Vector3f pivot = Eigen::Vector3f::Zero(); // local-space offset to rotation pivot + float rotationY = 0.0f; // Y-axis rotation in radians + float scale = 1.0f; // uniform scale float interactionRadius; - bool isActive = true; + bool isActive = true; + bool isAnimating = false; // true while a timed animation is running std::string activateFunctionName; + struct AnimTask { + enum class Type { Move, Rotate, Scale } type; + Eigen::Vector3f startPos; + float startRotY; + float startScale; + Eigen::Vector3f targetPos; + float targetRotY; + float targetScale; + float durationMs; + float elapsedMs = 0.f; + std::function onComplete; + }; + + std::optional animTask; + InteractiveObject() : interactionRadius(2.0f) {} + void moveTo(const Eigen::Vector3f& target, float durationSec, std::function onComplete = {}); + void rotateTo(float targetRotY, float durationSec, std::function onComplete = {}); + void scaleTo(float targetScale, float durationSec, std::function onComplete = {}); + void update(int64_t deltaMs); void draw(Renderer& renderer) const; };