Working on quest system
This commit is contained in:
parent
aaae368a07
commit
e7ae148ead
@ -13,6 +13,13 @@
|
|||||||
"description": "Это мой журнал куда я вношу свои заметки.",
|
"description": "Это мой журнал куда я вношу свои заметки.",
|
||||||
"icon": "resources/w/ui/img/inv/ItemJournal001.png",
|
"icon": "resources/w/ui/img/inv/ItemJournal001.png",
|
||||||
"selectedIcon": "resources/w/ui/img/inv/ItemSelJournal001.png"
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelJournal001.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "teacher_room_key",
|
||||||
|
"name": "Ключ от учительской",
|
||||||
|
"description": "Это ключ от учительской, который я получил от Айпери.",
|
||||||
|
"icon": "resources/w/ui/img/inv/ItemKey001.png",
|
||||||
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelKey001.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "knife",
|
"id": "knife",
|
||||||
|
|||||||
@ -184,7 +184,8 @@
|
|||||||
"portrait": "resources/dialogue/portrait_phone.png",
|
"portrait": "resources/dialogue/portrait_phone.png",
|
||||||
"text": "Так что жду тебя в универе! Не вздумай прогулять!",
|
"text": "Так что жду тебя в универе! Не вздумай прогулять!",
|
||||||
"next": "end_1",
|
"next": "end_1",
|
||||||
"bubbleSlot": "message11in"
|
"bubbleSlot": "message11in",
|
||||||
|
"questUnlock" : "aiperi_knife"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "end_1",
|
"id": "end_1",
|
||||||
|
|||||||
@ -17,7 +17,41 @@
|
|||||||
"type": "Line",
|
"type": "Line",
|
||||||
"speaker": "Айпери",
|
"speaker": "Айпери",
|
||||||
"portrait": "resources/dialogue/portrait_aiperi.png",
|
"portrait": "resources/dialogue/portrait_aiperi.png",
|
||||||
"text": "Пока ты не заберешь нож из учительской, никуда я тебя не выпущу.",
|
"text": "Вот тебе ключ от учительской. Иди туда и забери нож.",
|
||||||
|
"next": "line_3",
|
||||||
|
"luaCallback" : "dialog_aiperi_give_key"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "line_3",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Айпери",
|
||||||
|
"portrait": "resources/dialogue/portrait_aiperi.png",
|
||||||
|
"text": "Пока ты не принесешь мне нож из учительской, никуда я тебя не выпущу.",
|
||||||
|
"next": "end_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "end_1",
|
||||||
|
"type": "End"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"id": "knife_dialog002",
|
||||||
|
"start": "line_1",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "line_1",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Айпери",
|
||||||
|
"portrait": "resources/dialogue/portrait_aiperi.png",
|
||||||
|
"text": "Ты куда собрался, Бекзат?",
|
||||||
|
"next": "line_2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "line_2",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Айпери",
|
||||||
|
"portrait": "resources/dialogue/portrait_aiperi.png",
|
||||||
|
"text": "Пока ты не принесешь мне нож из учительской, никуда я тебя не выпущу.",
|
||||||
"next": "end_1"
|
"next": "end_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -244,6 +278,24 @@
|
|||||||
"type": "End"
|
"type": "End"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "door_teacher_dialog001",
|
||||||
|
"start": "line_1",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "line_1",
|
||||||
|
"type": "Line",
|
||||||
|
"speaker": "Бекзат",
|
||||||
|
"portrait": "resources/dialogue/portrait_hero_neutral.png",
|
||||||
|
"text": "Это дверь в учительскую, и она закрыта. Мне нужно взять ключи у Айпери.",
|
||||||
|
"next": "end_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "end_1",
|
||||||
|
"type": "End"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "door_unlock_dialog001",
|
"id": "door_unlock_dialog001",
|
||||||
@ -374,6 +426,8 @@
|
|||||||
"speaker": "Бекзат",
|
"speaker": "Бекзат",
|
||||||
"portrait": "resources/dialogue/portrait_hero_neutral.png",
|
"portrait": "resources/dialogue/portrait_hero_neutral.png",
|
||||||
"text": "Ладно...",
|
"text": "Ладно...",
|
||||||
|
"objectiveComplete" : "study_beginning.study_beginning_task",
|
||||||
|
"questUnlock" : "study_project",
|
||||||
"next": "end_1"
|
"next": "end_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,51 +1,82 @@
|
|||||||
{
|
{
|
||||||
"quests": [
|
"quests": [
|
||||||
{
|
{
|
||||||
"id": "tutorial_open_journal",
|
"id": "tutorial_take_items",
|
||||||
"title": "Журнал заданий",
|
"title": "Телефон и Журнал",
|
||||||
"category": "Tutorial",
|
"status": "Available",
|
||||||
"status": "Completed",
|
"autoComplete": true,
|
||||||
"recommendedLevel": 0,
|
"description": "Когда я куда-то собираюсь идти, я всегда беру с собой свой телефон и свой журнал.",
|
||||||
"description": "Теперь у героя есть журнал заданий. В нём можно просматривать текущие поручения, цели и подробные записи о событиях, которые уже произошли в мире игры.",
|
|
||||||
"objectives": [
|
"objectives": [
|
||||||
{ "id": "open_journal", "text": "Открыть журнал заданий", "completed": true }
|
{ "id": "take_phone", "text": "Взять телефон", "completed": false },
|
||||||
|
{ "id": "take_journal", "text": "Взять журнал", "completed": false }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "side_lost_bag",
|
"id": "study_beginning",
|
||||||
"title": "Потерянная сумка",
|
"title": "Манасоведение",
|
||||||
"category": "Side",
|
"status": "Available",
|
||||||
"status": "Completed",
|
"autoComplete": true,
|
||||||
"recommendedLevel": 1,
|
"description": "Скоро будет модуль по Манасоведению, а я еще ни разу не ходил на лекции. Сегодня у нас лекция по Манасоведению, надо ее посетить и получить у преподавательницы задание на модуль.",
|
||||||
"description": "Путник потерял сумку недалеко от лесной тропы. Внутри оказались вещи, важные для его дальнейшего пути. Возвращение сумки укрепило доверие местных жителей к герою.",
|
|
||||||
"objectives": [
|
"objectives": [
|
||||||
{ "id": "find_bag", "text": "Найти сумку у лесной тропы", "completed": true },
|
{ "id": "study_beginning_lecture", "text": "Посетить лекцию", "completed": false },
|
||||||
{ "id": "bring_bag", "text": "Вернуть сумку путнику", "completed": true }
|
{ "id": "study_beginning_task", "text": "Получить задание на модуль", "completed": false }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "failed_missing_courier",
|
"id": "study_project",
|
||||||
"title": "След курьера",
|
"title": "Эссе манасчи",
|
||||||
"category": "Side",
|
"status": "Hidden",
|
||||||
"status": "Failed",
|
"description": "Преподавательница по манасоведению Аида Джаныбекова дала мне задачу - найти в библиотеке книгу про манасчи Жусупа Мамая, и написать по этой книге эссе. Книгу нельзя выносить из библиотеки, но можно воспользоваться стоящим в библиотеке компьютером, чтобы написать эссе.",
|
||||||
"recommendedLevel": 2,
|
|
||||||
"description": "Курьер исчез до того, как герой смог выйти на его след. Местные говорят, что на дороге остались только следы копыт и обрывок ремня. Возможность узнать, кто забрал посылку, была упущена.",
|
|
||||||
"objectives": [
|
"objectives": [
|
||||||
{ "id": "ask_innkeeper", "text": "Расспросить трактирщика", "completed": true },
|
{ "id": "study_project_book", "text": "Найти книгу", "completed": false },
|
||||||
{ "id": "find_courier", "text": "Найти курьера до наступления ночи", "completed": false }
|
{ "id": "study_project_write", "text": "Написать эссе", "completed": false },
|
||||||
|
{ "id": "study_project_give", "text": "Сдать эссе", "completed": false }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "contract_old_well",
|
"id": "ghost_lore",
|
||||||
"title": "Старый колодец",
|
"title": "Призрак",
|
||||||
"category": "Contract",
|
"status": "Hidden",
|
||||||
"status": "Active",
|
"autoComplete": true,
|
||||||
"recommendedLevel": 2,
|
"description": "В университете каждую ночь появляется призрак студентки. Она заявляет, что ее зовут Бегимай и что она должна сдать курсовую работу. Я не помню студентки с таким именем, но может быть она с другого курса? Я должен узнать об этом подробнее.",
|
||||||
"description": "На окраине деревни стоит старый колодец. Ночью из него слышны голоса, а утром вокруг находят мокрые следы. Местные боятся подходить к нему, но староста считает, что причина происходящего связана с давним проклятием.",
|
|
||||||
"objectives": [
|
"objectives": [
|
||||||
{ "id": "inspect_well", "text": "Осмотреть старый колодец", "completed": false },
|
{ "id": "ghost_lore_teacher", "text": "Поговорить с Аидой Джаныбековой", "completed": false },
|
||||||
{ "id": "search_tracks", "text": "Найти следы возле каменной ограды", "completed": false },
|
{ "id": "ghost_lore_aiperi", "text": "Поговорить с Айпери", "completed": false },
|
||||||
{ "id": "report_elder", "text": "Вернуться к старосте", "completed": false }
|
{ "id": "ghost_lore_alik", "text": "Поговорить с Аликом", "completed": false, "visible": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ghost_coursework",
|
||||||
|
"title": "Курсовая Призрака",
|
||||||
|
"status": "Hidden",
|
||||||
|
"autoComplete": true,
|
||||||
|
"description": "История Бегимай еще более загадочная. Во время сдачи курсовой работы в университете происходила генеральная уборка. По случайности, ее курсовая работа оказалась в стопке бумаг на выброс. В результате курсач оказался на помойке, а преподаватель, не обнаружив курсовую работу, поставил ей ноль баллов. По словам Алика, курсовая работа до сих пор лежит в куче мусора во дворе университета.",
|
||||||
|
"objectives": [
|
||||||
|
{ "id": "ghost_coursework_find", "text": "Найти курсовую работу Бегимай", "completed": false },
|
||||||
|
{ "id": "ghost_coursework_mark", "text": "Показать курсовую работу Аиде Джаныбековой", "completed": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ghost_release",
|
||||||
|
"title": "Освобождение",
|
||||||
|
"status": "Hidden",
|
||||||
|
"autoComplete": true,
|
||||||
|
"description": "Студентка по имени Бегимай действительно училась в университете в прошлом году. Она пыталась сдать курсовую работу, но по стечению обстоятельств она не смогла это сделать, и получила ноль баллов. Эта курсовая сломала все ее планы и надежды, и Бегимай выбросилась из окна. Теперь она вернулась в виде призрака, и чтобы получить освобождение, она должна убедиться что ей выставили оценку за курсовую работу.",
|
||||||
|
"objectives": [
|
||||||
|
{ "id": "ghost_release_reportcard", "text": "Найти зачетку Бегимай", "completed": false },
|
||||||
|
{ "id": "ghost_release_mark", "text": "Поставить оценку за курсовую в зачетку", "completed": false },
|
||||||
|
{ "id": "ghost_release_show", "text": "Показать зачетку с оценкой призраку", "completed": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aiperi_knife",
|
||||||
|
"title": "Серебряный нож",
|
||||||
|
"status": "Hidden",
|
||||||
|
"description": "Айпери одолжила мне серебряный нож, который мы использовали чтобы нарезать торт преподавателям на день рождения. Нож до сих пор где-то лежит в учительской, и я должен его забрать и вернуть Айпери.",
|
||||||
|
"objectives": [
|
||||||
|
{ "id": "aiperi_knife_keys", "text": "Получить ключи от учительской", "completed": false },
|
||||||
|
{ "id": "aiperi_knife_take", "text": "Забрать нож", "completed": false },
|
||||||
|
{ "id": "aiperi_knife_give", "text": "Отдать нож Айпери", "completed": false }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -50,6 +50,7 @@ game_api.pickup_item("phone")
|
|||||||
game_api.deactivate_interactive_object("Phone001")
|
game_api.deactivate_interactive_object("Phone001")
|
||||||
game_api.start_dialogue("dialog_phone_pickup001")
|
game_api.start_dialogue("dialog_phone_pickup001")
|
||||||
phone_picked_up = true
|
phone_picked_up = true
|
||||||
|
game_api.quest_set_objective_completed("tutorial_take_items", "take_phone")
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_journal_pickup()
|
function on_journal_pickup()
|
||||||
@ -57,6 +58,7 @@ game_api.pickup_item("journal")
|
|||||||
game_api.deactivate_interactive_object("Journal001")
|
game_api.deactivate_interactive_object("Journal001")
|
||||||
game_api.start_dialogue("dialog_journal_pickup001")
|
game_api.start_dialogue("dialog_journal_pickup001")
|
||||||
journal_picked_up = true
|
journal_picked_up = true
|
||||||
|
game_api.quest_set_objective_completed("tutorial_take_items", "take_journal")
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@ -203,12 +205,12 @@ game_api.set_location_callbacks(
|
|||||||
game_api.deactivate_interactive_object("Room_Cover_LivingRoom_W_N_2_001")
|
game_api.deactivate_interactive_object("Room_Cover_LivingRoom_W_N_2_001")
|
||||||
|
|
||||||
--debug
|
--debug
|
||||||
game_api.deactivate_interactive_object("Room_Cover_Bath_W_N_2_001")
|
--game_api.deactivate_interactive_object("Room_Cover_Bath_W_N_2_001")
|
||||||
|
|
||||||
game_api.deactivate_interactive_object("Room_Cover_LivingRoom_W_S_2_001")
|
--game_api.deactivate_interactive_object("Room_Cover_LivingRoom_W_S_2_001")
|
||||||
game_api.deactivate_interactive_object("Room_Cover_Main_Hall_And_Corridors_002")
|
--game_api.deactivate_interactive_object("Room_Cover_Main_Hall_And_Corridors_002")
|
||||||
game_api.deactivate_interactive_object("Room_Cover_Utility_W_N_3_001")
|
--game_api.deactivate_interactive_object("Room_Cover_Utility_W_N_3_001")
|
||||||
game_api.deactivate_interactive_object("Room_Cover_Utility_W_S_3_001")
|
--game_api.deactivate_interactive_object("Room_Cover_Utility_W_S_3_001")
|
||||||
--debug end
|
--debug end
|
||||||
game_api.rotate_object("Door_Utility_-1_1_2_Leaf_001", 90, 0.01, nil)
|
game_api.rotate_object("Door_Utility_-1_1_2_Leaf_001", 90, 0.01, nil)
|
||||||
game_api.rotate_object("Door_Utility_-1_-1_2_Leaf_001", -90, 0.01, nil)
|
game_api.rotate_object("Door_Utility_-1_-1_2_Leaf_001", -90, 0.01, nil)
|
||||||
|
|||||||
@ -6,6 +6,7 @@ lection_is_over = false
|
|||||||
|
|
||||||
player_hold_book = false
|
player_hold_book = false
|
||||||
player_hold_knife = false
|
player_hold_knife = false
|
||||||
|
player_hold_key = false
|
||||||
teacher_arrived = false
|
teacher_arrived = false
|
||||||
teacher_told_about_book = false
|
teacher_told_about_book = false
|
||||||
|
|
||||||
@ -21,12 +22,22 @@ player_ghost_aware = false
|
|||||||
|
|
||||||
ghost_gone = false
|
ghost_gone = false
|
||||||
|
|
||||||
|
function dialog_aiperi_give_key()
|
||||||
|
if (player_hold_key == false) then
|
||||||
|
game_api.pickup_item("teacher_room_key")
|
||||||
|
game_api.quest_unlock("aiperi_knife")
|
||||||
|
game_api.quest_set_objective_completed("aiperi_knife", "aiperi_knife_keys")
|
||||||
|
player_hold_key = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function lection_hall_zone001_enter_callback()
|
function lection_hall_zone001_enter_callback()
|
||||||
--game_api.start_dialogue("")
|
--game_api.start_dialogue("")
|
||||||
--Start cutscene
|
--Start cutscene
|
||||||
if (lection_is_over == false) then
|
if (lection_is_over == false) then
|
||||||
game_api.player_stop()
|
game_api.player_stop()
|
||||||
game_api.start_cutscene("test_cutscene_01")
|
game_api.start_cutscene("test_cutscene_01")
|
||||||
|
game_api.quest_set_objective_completed("study_beginning", "study_beginning_lecture")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -42,7 +53,11 @@ function knife_dialog_zone001_enter_callback()
|
|||||||
if (day == 0) then
|
if (day == 0) then
|
||||||
if (player_hold_knife == false) then
|
if (player_hold_knife == false) then
|
||||||
if lection_is_over then
|
if lection_is_over then
|
||||||
|
if (player_hold_key) then
|
||||||
|
game_api.start_dialogue("knife_dialog002")
|
||||||
|
else
|
||||||
game_api.start_dialogue("knife_dialog001")
|
game_api.start_dialogue("knife_dialog001")
|
||||||
|
end
|
||||||
game_api.switch_navigation(4)
|
game_api.switch_navigation(4)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -82,6 +97,7 @@ game_api.set_npc_enabled(1, false)
|
|||||||
player_hold_knife = true
|
player_hold_knife = true
|
||||||
game_api.set_trigger_zone_enabled(2, true)
|
game_api.set_trigger_zone_enabled(2, true)
|
||||||
game_api.npc_walk_to(0, -4.57412, 0, 6.78495, on_teacher_arrived2)
|
game_api.npc_walk_to(0, -4.57412, 0, 6.78495, on_teacher_arrived2)
|
||||||
|
game_api.quest_set_objective_completed("aiperi_knife", "aiperi_knife_take")
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_book_pickup()
|
function on_book_pickup()
|
||||||
@ -136,7 +152,11 @@ function on_npc_interact(npc_index)
|
|||||||
local day = game_api.getIntValue("day")
|
local day = game_api.getIntValue("day")
|
||||||
|
|
||||||
if (day == 0) then
|
if (day == 0) then
|
||||||
|
if (player_hold_key) then
|
||||||
|
game_api.start_dialogue("knife_dialog002")
|
||||||
|
else
|
||||||
game_api.start_dialogue("knife_dialog001")
|
game_api.start_dialogue("knife_dialog001")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
if (player_ghost_aware) then
|
if (player_ghost_aware) then
|
||||||
local player_alik_aware = game_api.getIntValue("player_alik_aware")
|
local player_alik_aware = game_api.getIntValue("player_alik_aware")
|
||||||
@ -261,8 +281,8 @@ function on_teachers_door_click()
|
|||||||
game_api.start_dialogue("door_dialog001")
|
game_api.start_dialogue("door_dialog001")
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif (not teacher_arrived) then
|
elseif (not player_hold_key) then
|
||||||
game_api.start_dialogue("door_dialog001")
|
game_api.start_dialogue("door_teacher_dialog001")
|
||||||
elseif (not teacher_door_opened) then
|
elseif (not teacher_door_opened) then
|
||||||
teacher_door_opened = true
|
teacher_door_opened = true
|
||||||
game_api.rotate_object("Room_S_2_Leaf001", 90, 0.5, nil)
|
game_api.rotate_object("Room_S_2_Leaf001", 90, 0.5, nil)
|
||||||
|
|||||||
BIN
resources/w/ui/img/inv/ItemKey001.png
(Stored with Git LFS)
Normal file
BIN
resources/w/ui/img/inv/ItemKey001.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/ui/img/inv/ItemSelKey001.png
(Stored with Git LFS)
Normal file
BIN
resources/w/ui/img/inv/ItemSelKey001.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -15,7 +15,6 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#endif
|
#endif
|
||||||
@ -259,7 +258,7 @@ namespace ZL
|
|||||||
uniInteriorParams.playerPosition = Eigen::Vector3f(0.942694, 0, -9.63104);
|
uniInteriorParams.playerPosition = Eigen::Vector3f(0.942694, 0, -9.63104);
|
||||||
|
|
||||||
locations["uni_interior"] = std::make_shared<Location>(renderer, inventory);
|
locations["uni_interior"] = std::make_shared<Location>(renderer, inventory);
|
||||||
locations["uni_interior"]->setup(uniInteriorParams);
|
locations["uni_interior"]->setup(uniInteriorParams, &menuManager.questJournal);
|
||||||
locations["uni_interior"]->scriptEngine.setGlobalStore(&globalInts);
|
locations["uni_interior"]->scriptEngine.setGlobalStore(&globalInts);
|
||||||
locations["uni_interior"]->scriptEngine.setGlobalFloatStore(&globalFloats);
|
locations["uni_interior"]->scriptEngine.setGlobalFloatStore(&globalFloats);
|
||||||
locations["uni_interior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
|
locations["uni_interior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
|
||||||
@ -286,7 +285,7 @@ namespace ZL
|
|||||||
uniExteriorParams.dialoguesJsonPath = "resources/dialogue/uni_exterior_dialogues.json";
|
uniExteriorParams.dialoguesJsonPath = "resources/dialogue/uni_exterior_dialogues.json";
|
||||||
|
|
||||||
locations["uni_exterior"] = std::make_shared<Location>(renderer, inventory);
|
locations["uni_exterior"] = std::make_shared<Location>(renderer, inventory);
|
||||||
locations["uni_exterior"]->setup(uniExteriorParams);
|
locations["uni_exterior"]->setup(uniExteriorParams, &menuManager.questJournal);
|
||||||
locations["uni_exterior"]->scriptEngine.setGlobalStore(&globalInts);
|
locations["uni_exterior"]->scriptEngine.setGlobalStore(&globalInts);
|
||||||
locations["uni_exterior"]->scriptEngine.setGlobalFloatStore(&globalFloats);
|
locations["uni_exterior"]->scriptEngine.setGlobalFloatStore(&globalFloats);
|
||||||
locations["uni_exterior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
|
locations["uni_exterior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
|
||||||
@ -346,7 +345,7 @@ namespace ZL
|
|||||||
params_dorm.playerPosition = Eigen::Vector3f(6.76345, 0, -14.6022);
|
params_dorm.playerPosition = Eigen::Vector3f(6.76345, 0, -14.6022);
|
||||||
|
|
||||||
locations["location_dorm"] = std::make_shared<Location>(renderer, inventory);
|
locations["location_dorm"] = std::make_shared<Location>(renderer, inventory);
|
||||||
locations["location_dorm"]->setup(params_dorm);
|
locations["location_dorm"]->setup(params_dorm, &menuManager.questJournal);
|
||||||
locations["location_dorm"]->scriptEngine.setGlobalStore(&globalInts);
|
locations["location_dorm"]->scriptEngine.setGlobalStore(&globalInts);
|
||||||
locations["location_dorm"]->scriptEngine.setGlobalFloatStore(&globalFloats);
|
locations["location_dorm"]->scriptEngine.setGlobalFloatStore(&globalFloats);
|
||||||
locations["location_dorm"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
|
locations["location_dorm"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
|
||||||
|
|||||||
@ -48,7 +48,7 @@ namespace ZL
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Location::setup(const LocationSetup& params)
|
void Location::setup(const LocationSetup& params, Quest::QuestJournal* journal)
|
||||||
{
|
{
|
||||||
|
|
||||||
// Load static game objects
|
// Load static game objects
|
||||||
@ -130,6 +130,7 @@ namespace ZL
|
|||||||
|
|
||||||
dialogueSystem.init(renderer, CONST_ZIP_FILE);
|
dialogueSystem.init(renderer, CONST_ZIP_FILE);
|
||||||
dialogueSystem.loadDatabase(params.dialoguesJsonPath);
|
dialogueSystem.loadDatabase(params.dialoguesJsonPath);
|
||||||
|
dialogueSystem.setQuestJournal(journal);
|
||||||
|
|
||||||
npcNameText = std::make_unique<TextRenderer>();
|
npcNameText = std::make_unique<TextRenderer>();
|
||||||
if (!npcNameText->init(renderer, "resources/fonts/DroidSans.ttf", 24, CONST_ZIP_FILE)) {
|
if (!npcNameText->init(renderer, "resources/fonts/DroidSans.ttf", 24, CONST_ZIP_FILE)) {
|
||||||
@ -138,6 +139,7 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
scriptEngine.init(this, &inventory, params.scriptPath);
|
scriptEngine.init(this, &inventory, params.scriptPath);
|
||||||
|
scriptEngine.setQuestJournal(journal);
|
||||||
|
|
||||||
dialogueSystem.setOnCutsceneFinished([this](const std::string& cutsceneId) {
|
dialogueSystem.setOnCutsceneFinished([this](const std::string& cutsceneId) {
|
||||||
scriptEngine.callCutsceneCompleteCallback(cutsceneId);
|
scriptEngine.callCutsceneCompleteCallback(cutsceneId);
|
||||||
|
|||||||
@ -112,7 +112,7 @@ namespace ZL
|
|||||||
int lastMouseY = 0;
|
int lastMouseY = 0;
|
||||||
|
|
||||||
|
|
||||||
void setup(const LocationSetup& params);
|
void setup(const LocationSetup& params, Quest::QuestJournal* journal);
|
||||||
void setupNavigation(const std::vector<std::string>& paths);
|
void setupNavigation(const std::vector<std::string>& paths);
|
||||||
bool switchNavigation(int index);
|
bool switchNavigation(int index);
|
||||||
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
|
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
|
||||||
|
|||||||
@ -8,11 +8,10 @@ namespace ZL {
|
|||||||
|
|
||||||
static int questStatusPriority(Quest::QuestStatus status) {
|
static int questStatusPriority(Quest::QuestStatus status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case Quest::QuestStatus::Active: return 0;
|
case Quest::QuestStatus::Available: return 0;
|
||||||
case Quest::QuestStatus::Available: return 1;
|
case Quest::QuestStatus::Completed: return 1;
|
||||||
case Quest::QuestStatus::Completed: return 2;
|
case Quest::QuestStatus::Failed: return 2;
|
||||||
case Quest::QuestStatus::Failed: return 3;
|
default: return 3;
|
||||||
default: return 4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,8 +57,7 @@ namespace ZL {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case Quest::QuestStatus::Completed: return { 0.25f, 0.95f, 0.35f, 1.0f };
|
case Quest::QuestStatus::Completed: return { 0.25f, 0.95f, 0.35f, 1.0f };
|
||||||
case Quest::QuestStatus::Failed: return { 1.0f, 0.25f, 0.25f, 1.0f };
|
case Quest::QuestStatus::Failed: return { 1.0f, 0.25f, 0.25f, 1.0f };
|
||||||
case Quest::QuestStatus::Active: return { 1.0f, 1.0f, 1.0f, 1.0f };
|
case Quest::QuestStatus::Available: return { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||||
case Quest::QuestStatus::Available: return { 0.86f, 0.86f, 0.86f, 1.0f };
|
|
||||||
default: return { 0.45f, 0.45f, 0.45f, 1.0f };
|
default: return { 0.45f, 0.45f, 0.45f, 1.0f };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -468,7 +466,7 @@ namespace ZL {
|
|||||||
const int pa = questStatusPriority(a->status);
|
const int pa = questStatusPriority(a->status);
|
||||||
const int pb = questStatusPriority(b->status);
|
const int pb = questStatusPriority(b->status);
|
||||||
if (pa != pb) return pa < pb;
|
if (pa != pb) return pa < pb;
|
||||||
return a->orderIndex > b->orderIndex;
|
return a->orderIndex < b->orderIndex;
|
||||||
});
|
});
|
||||||
|
|
||||||
static const char* kItemNames[9] = {
|
static const char* kItemNames[9] = {
|
||||||
@ -540,10 +538,14 @@ namespace ZL {
|
|||||||
static const char* kCheckboxes[3] = { "objective1checkbox", "objective2checkbox", "objective3checkbox" };
|
static const char* kCheckboxes[3] = { "objective1checkbox", "objective2checkbox", "objective3checkbox" };
|
||||||
static const char* kObjNames[3] = { "objective1name", "objective2name", "objective3name" };
|
static const char* kObjNames[3] = { "objective1name", "objective2name", "objective3name" };
|
||||||
|
|
||||||
|
std::vector<const Quest::QuestObjective*> visibleObjs;
|
||||||
|
for (const auto& obj : def.objectives)
|
||||||
|
if (obj.visible) visibleObjs.push_back(&obj);
|
||||||
|
|
||||||
for (int i = 0; i < 3; ++i) {
|
for (int i = 0; i < 3; ++i) {
|
||||||
if (i < static_cast<int>(def.objectives.size())) {
|
if (i < static_cast<int>(visibleObjs.size())) {
|
||||||
const auto& obj = def.objectives[i];
|
const auto& obj = *visibleObjs[i];
|
||||||
const bool isActive = (i == quest->activeObjectiveIndex);
|
const bool isActive = (&obj - def.objectives.data() == quest->activeObjectiveIndex);
|
||||||
|
|
||||||
auto img = uiManager.findStaticImage(kCheckboxes[i]);
|
auto img = uiManager.findStaticImage(kCheckboxes[i]);
|
||||||
if (img) img->texture = obj.completed ? texObjectiveCompleted_ : texObjectiveBlank_;
|
if (img) img->texture = obj.completed ? texObjectiveCompleted_ : texObjectiveBlank_;
|
||||||
|
|||||||
@ -20,6 +20,7 @@ namespace ZL {
|
|||||||
std::unordered_map<int, sol::protected_function> npcBumpsPlayerCallbacks;
|
std::unordered_map<int, sol::protected_function> npcBumpsPlayerCallbacks;
|
||||||
std::unordered_map<std::string, int>* globalInts = nullptr;
|
std::unordered_map<std::string, int>* globalInts = nullptr;
|
||||||
std::unordered_map<std::string, float>* globalFloats = nullptr;
|
std::unordered_map<std::string, float>* globalFloats = nullptr;
|
||||||
|
Quest::QuestJournal* questJournal = nullptr;
|
||||||
sol::protected_function locationEnterCallback;
|
sol::protected_function locationEnterCallback;
|
||||||
sol::protected_function locationExitCallback;
|
sol::protected_function locationExitCallback;
|
||||||
sol::protected_function darklandsEnterCallback;
|
sol::protected_function darklandsEnterCallback;
|
||||||
@ -505,6 +506,68 @@ namespace ZL {
|
|||||||
this_impl->npcBumpsPlayerCallbacks[index] = onNpcBumpsPlayer.as<sol::protected_function>();
|
this_impl->npcBumpsPlayerCallbacks[index] = onNpcBumpsPlayer.as<sol::protected_function>();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// quest_unlock(quest_id)
|
||||||
|
api.set_function("quest_unlock",
|
||||||
|
[this_impl = impl.get()](const std::string& questId) -> bool {
|
||||||
|
if (!this_impl->questJournal) {
|
||||||
|
std::cerr << "[script] quest_unlock: QuestJournal not set\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this_impl->questJournal->unlockQuest(questId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// quest_complete(quest_id)
|
||||||
|
api.set_function("quest_complete",
|
||||||
|
[this_impl = impl.get()](const std::string& questId) -> bool {
|
||||||
|
if (!this_impl->questJournal) {
|
||||||
|
std::cerr << "[script] quest_complete: QuestJournal not set\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this_impl->questJournal->completeQuest(questId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// quest_fail(quest_id)
|
||||||
|
api.set_function("quest_fail",
|
||||||
|
[this_impl = impl.get()](const std::string& questId) -> bool {
|
||||||
|
if (!this_impl->questJournal) {
|
||||||
|
std::cerr << "[script] quest_fail: QuestJournal not set\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this_impl->questJournal->failQuest(questId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// quest_set_objective_completed(quest_id, objective_id [, completed])
|
||||||
|
api.set_function("quest_set_objective_completed",
|
||||||
|
[this_impl = impl.get()](const std::string& questId, const std::string& objId, sol::object completed) -> bool {
|
||||||
|
if (!this_impl->questJournal) {
|
||||||
|
std::cerr << "[script] quest_set_objective_completed: QuestJournal not set\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bool val = completed.is<bool>() ? completed.as<bool>() : true;
|
||||||
|
return this_impl->questJournal->setObjectiveCompleted(questId, objId, val);
|
||||||
|
});
|
||||||
|
|
||||||
|
// quest_set_objective_visible(quest_id, objective_id [, visible])
|
||||||
|
api.set_function("quest_set_objective_visible",
|
||||||
|
[this_impl = impl.get()](const std::string& questId, const std::string& objId, sol::object visible) -> bool {
|
||||||
|
if (!this_impl->questJournal) {
|
||||||
|
std::cerr << "[script] quest_set_objective_visible: QuestJournal not set\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bool val = visible.is<bool>() ? visible.as<bool>() : true;
|
||||||
|
return this_impl->questJournal->setObjectiveVisible(questId, objId, val);
|
||||||
|
});
|
||||||
|
|
||||||
|
// quest_set_active_objective(quest_id, objective_index)
|
||||||
|
api.set_function("quest_set_active_objective",
|
||||||
|
[this_impl = impl.get()](const std::string& questId, int index) -> bool {
|
||||||
|
if (!this_impl->questJournal) {
|
||||||
|
std::cerr << "[script] quest_set_active_objective: QuestJournal not set\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this_impl->questJournal->setActiveObjective(questId, index);
|
||||||
|
});
|
||||||
|
|
||||||
lua.script_file(scriptPath);
|
lua.script_file(scriptPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -624,6 +687,10 @@ namespace ZL {
|
|||||||
if (impl) impl->globalFloats = store;
|
if (impl) impl->globalFloats = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::setQuestJournal(Quest::QuestJournal* journal) {
|
||||||
|
if (impl) impl->questJournal = journal;
|
||||||
|
}
|
||||||
|
|
||||||
void ScriptEngine::callLocationEnterCallback() {
|
void ScriptEngine::callLocationEnterCallback() {
|
||||||
if (!impl || !impl->locationEnterCallback.valid()) return;
|
if (!impl || !impl->locationEnterCallback.valid()) return;
|
||||||
auto result = impl->locationEnterCallback();
|
auto result = impl->locationEnterCallback();
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include "quest/QuestJournal.h"
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ public:
|
|||||||
|
|
||||||
void setGlobalStore(std::unordered_map<std::string, int>* store);
|
void setGlobalStore(std::unordered_map<std::string, int>* store);
|
||||||
void setGlobalFloatStore(std::unordered_map<std::string, float>* store);
|
void setGlobalFloatStore(std::unordered_map<std::string, float>* store);
|
||||||
|
void setQuestJournal(Quest::QuestJournal* journal);
|
||||||
|
|
||||||
void callLocationEnterCallback();
|
void callLocationEnterCallback();
|
||||||
void callLocationExitCallback();
|
void callLocationExitCallback();
|
||||||
|
|||||||
@ -106,6 +106,11 @@ Node DialogueDatabase::parseNode(const json& j) {
|
|||||||
node.cutsceneId = j.value("cutsceneId", "");
|
node.cutsceneId = j.value("cutsceneId", "");
|
||||||
node.luaCallback = j.value("luaCallback", "");
|
node.luaCallback = j.value("luaCallback", "");
|
||||||
node.bubbleSlot = j.value("bubbleSlot", "");
|
node.bubbleSlot = j.value("bubbleSlot", "");
|
||||||
|
node.questUnlock = j.value("questUnlock", "");
|
||||||
|
node.questComplete = j.value("questComplete", "");
|
||||||
|
node.questFail = j.value("questFail", "");
|
||||||
|
node.objectiveComplete = j.value("objectiveComplete", "");
|
||||||
|
node.objectiveVisible = j.value("objectiveVisible", "");
|
||||||
|
|
||||||
if (j.contains("conditions") && j["conditions"].is_array()) {
|
if (j.contains("conditions") && j["conditions"].is_array()) {
|
||||||
for (const auto& item : j["conditions"]) {
|
for (const auto& item : j["conditions"]) {
|
||||||
@ -157,6 +162,11 @@ CutsceneLine DialogueDatabase::parseCutsceneLine(const json& j) {
|
|||||||
line.luaCallback = j.value("luaCallback", "");
|
line.luaCallback = j.value("luaCallback", "");
|
||||||
line.durationMs = j.value("durationMs", 0);
|
line.durationMs = j.value("durationMs", 0);
|
||||||
line.waitForConfirm = j.value("waitForConfirm", false);
|
line.waitForConfirm = j.value("waitForConfirm", false);
|
||||||
|
line.questUnlock = j.value("questUnlock", "");
|
||||||
|
line.questComplete = j.value("questComplete", "");
|
||||||
|
line.questFail = j.value("questFail", "");
|
||||||
|
line.objectiveComplete = j.value("objectiveComplete", "");
|
||||||
|
line.objectiveVisible = j.value("objectiveVisible", "");
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,13 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace ZL::Dialogue {
|
namespace ZL::Dialogue {
|
||||||
|
|
||||||
|
static std::pair<std::string, std::string> splitDot(const std::string& s) {
|
||||||
|
const auto dot = s.find('.');
|
||||||
|
if (dot == std::string::npos) return {s, ""};
|
||||||
|
return {s.substr(0, dot), s.substr(dot + 1)};
|
||||||
|
}
|
||||||
|
|
||||||
void DialogueRuntime::setDatabase(const DialogueDatabase* value) {
|
void DialogueRuntime::setDatabase(const DialogueDatabase* value) {
|
||||||
database = value;
|
database = value;
|
||||||
}
|
}
|
||||||
@ -309,6 +315,29 @@ int DialogueRuntime::getFlag(const std::string& name) const {
|
|||||||
return (it != flags.end()) ? it->second : 0;
|
return (it != flags.end()) ? it->second : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DialogueRuntime::setQuestJournal(Quest::QuestJournal* journal) {
|
||||||
|
questJournal = journal;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialogueRuntime::applyQuestActions(
|
||||||
|
const std::string& questUnlock, const std::string& questComplete,
|
||||||
|
const std::string& questFail, const std::string& objectiveComplete,
|
||||||
|
const std::string& objectiveVisible)
|
||||||
|
{
|
||||||
|
if (!questJournal) return;
|
||||||
|
if (!questUnlock.empty()) questJournal->unlockQuest(questUnlock);
|
||||||
|
if (!questComplete.empty()) questJournal->completeQuest(questComplete);
|
||||||
|
if (!questFail.empty()) questJournal->failQuest(questFail);
|
||||||
|
if (!objectiveComplete.empty()) {
|
||||||
|
auto [qId, oId] = splitDot(objectiveComplete);
|
||||||
|
questJournal->setObjectiveCompleted(qId, oId);
|
||||||
|
}
|
||||||
|
if (!objectiveVisible.empty()) {
|
||||||
|
auto [qId, oId] = splitDot(objectiveVisible);
|
||||||
|
questJournal->setObjectiveVisible(qId, oId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool DialogueRuntime::evaluateConditions(const std::vector<Condition>& conditions) const {
|
bool DialogueRuntime::evaluateConditions(const std::vector<Condition>& conditions) const {
|
||||||
for (const Condition& condition : conditions) {
|
for (const Condition& condition : conditions) {
|
||||||
const int currentValue = getFlag(condition.flag);
|
const int currentValue = getFlag(condition.flag);
|
||||||
@ -432,6 +461,8 @@ void DialogueRuntime::presentLine(const Node& node) {
|
|||||||
revealCharacters = static_cast<float>(node.text.size());
|
revealCharacters = static_cast<float>(node.text.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyQuestActions(node.questUnlock, node.questComplete, node.questFail,
|
||||||
|
node.objectiveComplete, node.objectiveVisible);
|
||||||
if (!node.luaCallback.empty() && onDialogueLineStarted) {
|
if (!node.luaCallback.empty() && onDialogueLineStarted) {
|
||||||
onDialogueLineStarted(node.luaCallback);
|
onDialogueLineStarted(node.luaCallback);
|
||||||
}
|
}
|
||||||
@ -530,9 +561,12 @@ void DialogueRuntime::startCutscene(const std::string& cutsceneId, const std::st
|
|||||||
cutsceneTotalDurationMs = cutsceneContentDurationMs + activeCutscene->endFadeOutMs + activeCutscene->endFadeInMs;
|
cutsceneTotalDurationMs = cutsceneContentDurationMs + activeCutscene->endFadeOutMs + activeCutscene->endFadeInMs;
|
||||||
refreshCutscenePresentation();
|
refreshCutscenePresentation();
|
||||||
|
|
||||||
if (!activeCutscene->lines.empty() && onCutsceneLineStarted) {
|
if (!activeCutscene->lines.empty()) {
|
||||||
const std::string& cb = activeCutscene->lines[0].luaCallback;
|
const CutsceneLine& firstLine = activeCutscene->lines[0];
|
||||||
if (!cb.empty()) onCutsceneLineStarted(cb);
|
applyQuestActions(firstLine.questUnlock, firstLine.questComplete,
|
||||||
|
firstLine.questFail, firstLine.objectiveComplete, firstLine.objectiveVisible);
|
||||||
|
if (onCutsceneLineStarted && !firstLine.luaCallback.empty())
|
||||||
|
onCutsceneLineStarted(firstLine.luaCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "[CUTSCENE] start id=" << cutsceneId
|
std::cout << "[CUTSCENE] start id=" << cutsceneId
|
||||||
@ -621,10 +655,11 @@ void DialogueRuntime::advanceCutsceneLine() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onCutsceneLineStarted) {
|
const CutsceneLine& newLine = activeCutscene->lines[currentCutsceneLine];
|
||||||
const std::string& cb = activeCutscene->lines[currentCutsceneLine].luaCallback;
|
applyQuestActions(newLine.questUnlock, newLine.questComplete,
|
||||||
if (!cb.empty()) onCutsceneLineStarted(cb);
|
newLine.questFail, newLine.objectiveComplete, newLine.objectiveVisible);
|
||||||
}
|
if (onCutsceneLineStarted && !newLine.luaCallback.empty())
|
||||||
|
onCutsceneLineStarted(newLine.luaCallback);
|
||||||
|
|
||||||
refreshCutscenePresentation();
|
refreshCutscenePresentation();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "dialogue/DialogueDatabase.h"
|
#include "dialogue/DialogueDatabase.h"
|
||||||
|
#include "quest/QuestJournal.h"
|
||||||
#include "external/nlohmann/json.hpp"
|
#include "external/nlohmann/json.hpp"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -42,6 +43,8 @@ public:
|
|||||||
void setFlag(const std::string& name, int value);
|
void setFlag(const std::string& name, int value);
|
||||||
int getFlag(const std::string& name) const;
|
int getFlag(const std::string& name) const;
|
||||||
|
|
||||||
|
void setQuestJournal(Quest::QuestJournal* journal);
|
||||||
|
|
||||||
json buildSaveState() const;
|
json buildSaveState() const;
|
||||||
bool restoreSaveState(const json& state);
|
bool restoreSaveState(const json& state);
|
||||||
|
|
||||||
@ -62,6 +65,7 @@ private:
|
|||||||
bool fadeInCallbackFired = false;
|
bool fadeInCallbackFired = false;
|
||||||
|
|
||||||
const DialogueDatabase* database = nullptr;
|
const DialogueDatabase* database = nullptr;
|
||||||
|
Quest::QuestJournal* questJournal = nullptr;
|
||||||
const DialogueDefinition* activeDialogue = nullptr;
|
const DialogueDefinition* activeDialogue = nullptr;
|
||||||
const StaticCutsceneDefinition* activeCutscene = nullptr;
|
const StaticCutsceneDefinition* activeCutscene = nullptr;
|
||||||
|
|
||||||
@ -89,6 +93,9 @@ private:
|
|||||||
|
|
||||||
bool evaluateConditions(const std::vector<Condition>& conditions) const;
|
bool evaluateConditions(const std::vector<Condition>& conditions) const;
|
||||||
void applyEffects(const std::vector<Effect>& effects);
|
void applyEffects(const std::vector<Effect>& effects);
|
||||||
|
void applyQuestActions(const std::string& questUnlock, const std::string& questComplete,
|
||||||
|
const std::string& questFail, const std::string& objectiveComplete,
|
||||||
|
const std::string& objectiveVisible);
|
||||||
|
|
||||||
bool enterNode(const std::string& nodeId);
|
bool enterNode(const std::string& nodeId);
|
||||||
void presentLine(const Node& node);
|
void presentLine(const Node& node);
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "dialogue/DialogueOverlay.h"
|
#include "dialogue/DialogueOverlay.h"
|
||||||
#include "dialogue/DialogueRuntime.h"
|
#include "dialogue/DialogueRuntime.h"
|
||||||
|
#include "quest/QuestJournal.h"
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -37,6 +38,8 @@ public:
|
|||||||
void setFlag(const std::string& name, int value) { runtime.setFlag(name, value); }
|
void setFlag(const std::string& name, int value) { runtime.setFlag(name, value); }
|
||||||
int getFlag(const std::string& name) const { return runtime.getFlag(name); }
|
int getFlag(const std::string& name) const { return runtime.getFlag(name); }
|
||||||
|
|
||||||
|
void setQuestJournal(Quest::QuestJournal* journal) { runtime.setQuestJournal(journal); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DialogueDatabase database;
|
DialogueDatabase database;
|
||||||
DialogueRuntime runtime;
|
DialogueRuntime runtime;
|
||||||
|
|||||||
@ -99,6 +99,13 @@ struct Node {
|
|||||||
|
|
||||||
// Name of the UI node (StaticImage) to reveal in the phone chat when this line is shown
|
// Name of the UI node (StaticImage) to reveal in the phone chat when this line is shown
|
||||||
std::string bubbleSlot;
|
std::string bubbleSlot;
|
||||||
|
|
||||||
|
// Quest actions fired when this line is presented (empty = no action)
|
||||||
|
std::string questUnlock;
|
||||||
|
std::string questComplete;
|
||||||
|
std::string questFail;
|
||||||
|
std::string objectiveComplete; // "quest_id.objective_id"
|
||||||
|
std::string objectiveVisible; // "quest_id.objective_id"
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DialogueDefinition {
|
struct DialogueDefinition {
|
||||||
@ -120,6 +127,13 @@ struct CutsceneLine {
|
|||||||
int backgroundHeight = 0; // 0 = inherit from cutscene
|
int backgroundHeight = 0; // 0 = inherit from cutscene
|
||||||
int durationMs = 0;
|
int durationMs = 0;
|
||||||
bool waitForConfirm = false;
|
bool waitForConfirm = false;
|
||||||
|
|
||||||
|
// Quest actions fired when this line is shown (empty = no action)
|
||||||
|
std::string questUnlock;
|
||||||
|
std::string questComplete;
|
||||||
|
std::string questFail;
|
||||||
|
std::string objectiveComplete; // "quest_id.objective_id"
|
||||||
|
std::string objectiveVisible; // "quest_id.objective_id"
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CutsceneCameraPose {
|
struct CutsceneCameraPose {
|
||||||
|
|||||||
@ -12,38 +12,19 @@ const char* toString(QuestStatus status) {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case QuestStatus::Hidden: return "Hidden";
|
case QuestStatus::Hidden: return "Hidden";
|
||||||
case QuestStatus::Available: return "Available";
|
case QuestStatus::Available: return "Available";
|
||||||
case QuestStatus::Active: return "Active";
|
|
||||||
case QuestStatus::Completed: return "Completed";
|
case QuestStatus::Completed: return "Completed";
|
||||||
case QuestStatus::Failed: return "Failed";
|
case QuestStatus::Failed: return "Failed";
|
||||||
default: return "Unknown";
|
default: return "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* toString(QuestCategory category) {
|
|
||||||
switch (category) {
|
|
||||||
case QuestCategory::Main: return "Main";
|
|
||||||
case QuestCategory::Side: return "Side";
|
|
||||||
case QuestCategory::Contract: return "Contract";
|
|
||||||
case QuestCategory::Tutorial: return "Tutorial";
|
|
||||||
default: return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static QuestStatus parseQuestStatus(const std::string& value) {
|
static QuestStatus parseQuestStatus(const std::string& value) {
|
||||||
if (value == "Available") return QuestStatus::Available;
|
if (value == "Available") return QuestStatus::Available;
|
||||||
if (value == "Active") return QuestStatus::Active;
|
|
||||||
if (value == "Completed") return QuestStatus::Completed;
|
if (value == "Completed") return QuestStatus::Completed;
|
||||||
if (value == "Failed") return QuestStatus::Failed;
|
if (value == "Failed") return QuestStatus::Failed;
|
||||||
return QuestStatus::Hidden;
|
return QuestStatus::Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QuestCategory parseQuestCategory(const std::string& value) {
|
|
||||||
if (value == "Main") return QuestCategory::Main;
|
|
||||||
if (value == "Contract") return QuestCategory::Contract;
|
|
||||||
if (value == "Tutorial") return QuestCategory::Tutorial;
|
|
||||||
return QuestCategory::Side;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QuestJournal::loadFromFile(const std::string& path, const std::string& zipFile) {
|
bool QuestJournal::loadFromFile(const std::string& path, const std::string& zipFile) {
|
||||||
quests.clear();
|
quests.clear();
|
||||||
questOrder.clear();
|
questOrder.clear();
|
||||||
@ -87,9 +68,8 @@ bool QuestJournal::loadFromFile(const std::string& path, const std::string& zipF
|
|||||||
def.id = item.value("id", "");
|
def.id = item.value("id", "");
|
||||||
def.title = item.value("title", def.id);
|
def.title = item.value("title", def.id);
|
||||||
def.description = item.value("description", "");
|
def.description = item.value("description", "");
|
||||||
def.category = parseQuestCategory(item.value("category", "Side"));
|
|
||||||
def.initialStatus = parseQuestStatus(item.value("status", "Hidden"));
|
def.initialStatus = parseQuestStatus(item.value("status", "Hidden"));
|
||||||
def.recommendedLevel = item.value("recommendedLevel", 0);
|
def.autoComplete = item.value("autoComplete", false);
|
||||||
|
|
||||||
if (item.contains("objectives") && item["objectives"].is_array()) {
|
if (item.contains("objectives") && item["objectives"].is_array()) {
|
||||||
for (const auto& obj : item["objectives"]) {
|
for (const auto& obj : item["objectives"]) {
|
||||||
@ -97,6 +77,7 @@ bool QuestJournal::loadFromFile(const std::string& path, const std::string& zipF
|
|||||||
objective.id = obj.value("id", "");
|
objective.id = obj.value("id", "");
|
||||||
objective.text = obj.value("text", "");
|
objective.text = obj.value("text", "");
|
||||||
objective.completed = obj.value("completed", false);
|
objective.completed = obj.value("completed", false);
|
||||||
|
objective.visible = obj.value("visible", true);
|
||||||
if (!objective.id.empty()) {
|
if (!objective.id.empty()) {
|
||||||
def.objectives.push_back(objective);
|
def.objectives.push_back(objective);
|
||||||
}
|
}
|
||||||
@ -136,10 +117,6 @@ bool QuestJournal::unlockQuest(const std::string& questId) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuestJournal::startQuest(const std::string& questId) {
|
|
||||||
return setStatus(questId, QuestStatus::Active);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QuestJournal::completeQuest(const std::string& questId) {
|
bool QuestJournal::completeQuest(const std::string& questId) {
|
||||||
return setStatus(questId, QuestStatus::Completed);
|
return setStatus(questId, QuestStatus::Completed);
|
||||||
}
|
}
|
||||||
@ -155,6 +132,30 @@ bool QuestJournal::setObjectiveCompleted(const std::string& questId, const std::
|
|||||||
for (auto& objective : quest->definition.objectives) {
|
for (auto& objective : quest->definition.objectives) {
|
||||||
if (objective.id == objectiveId) {
|
if (objective.id == objectiveId) {
|
||||||
objective.completed = completed;
|
objective.completed = completed;
|
||||||
|
|
||||||
|
if (completed && quest->definition.autoComplete && quest->status == QuestStatus::Available) {
|
||||||
|
const bool allDone = std::all_of(
|
||||||
|
quest->definition.objectives.begin(),
|
||||||
|
quest->definition.objectives.end(),
|
||||||
|
[](const QuestObjective& o) { return o.completed; });
|
||||||
|
if (allDone)
|
||||||
|
quest->status = QuestStatus::Completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuestJournal::setObjectiveVisible(const std::string& questId, const std::string& objectiveId, bool visible) {
|
||||||
|
QuestState* quest = findQuest(questId);
|
||||||
|
if (!quest) return false;
|
||||||
|
|
||||||
|
for (auto& objective : quest->definition.objectives) {
|
||||||
|
if (objective.id == objectiveId) {
|
||||||
|
objective.visible = visible;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,11 +12,11 @@ public:
|
|||||||
bool loadFromFile(const std::string& path, const std::string& zipFile = "");
|
bool loadFromFile(const std::string& path, const std::string& zipFile = "");
|
||||||
|
|
||||||
bool unlockQuest(const std::string& questId);
|
bool unlockQuest(const std::string& questId);
|
||||||
bool startQuest(const std::string& questId);
|
|
||||||
bool completeQuest(const std::string& questId);
|
bool completeQuest(const std::string& questId);
|
||||||
bool failQuest(const std::string& questId);
|
bool failQuest(const std::string& questId);
|
||||||
|
|
||||||
bool setObjectiveCompleted(const std::string& questId, const std::string& objectiveId, bool completed = true);
|
bool setObjectiveCompleted(const std::string& questId, const std::string& objectiveId, bool completed = true);
|
||||||
|
bool setObjectiveVisible(const std::string& questId, const std::string& objectiveId, bool visible = true);
|
||||||
bool setActiveObjective(const std::string& questId, int objectiveIndex);
|
bool setActiveObjective(const std::string& questId, int objectiveIndex);
|
||||||
|
|
||||||
QuestState* findQuest(const std::string& questId);
|
QuestState* findQuest(const std::string& questId);
|
||||||
|
|||||||
@ -8,31 +8,23 @@ namespace ZL::Quest {
|
|||||||
enum class QuestStatus {
|
enum class QuestStatus {
|
||||||
Hidden,
|
Hidden,
|
||||||
Available,
|
Available,
|
||||||
Active,
|
|
||||||
Completed,
|
Completed,
|
||||||
Failed
|
Failed
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class QuestCategory {
|
|
||||||
Main,
|
|
||||||
Side,
|
|
||||||
Contract,
|
|
||||||
Tutorial
|
|
||||||
};
|
|
||||||
|
|
||||||
struct QuestObjective {
|
struct QuestObjective {
|
||||||
std::string id;
|
std::string id;
|
||||||
std::string text;
|
std::string text;
|
||||||
bool completed = false;
|
bool completed = false;
|
||||||
|
bool visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct QuestDefinition {
|
struct QuestDefinition {
|
||||||
std::string id;
|
std::string id;
|
||||||
std::string title;
|
std::string title;
|
||||||
std::string description;
|
std::string description;
|
||||||
QuestCategory category = QuestCategory::Side;
|
|
||||||
QuestStatus initialStatus = QuestStatus::Hidden;
|
QuestStatus initialStatus = QuestStatus::Hidden;
|
||||||
int recommendedLevel = 0;
|
bool autoComplete = false;
|
||||||
std::vector<QuestObjective> objectives;
|
std::vector<QuestObjective> objectives;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,6 +36,5 @@ struct QuestState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const char* toString(QuestStatus status);
|
const char* toString(QuestStatus status);
|
||||||
const char* toString(QuestCategory category);
|
|
||||||
|
|
||||||
} // namespace ZL::Quest
|
} // namespace ZL::Quest
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user