From 8e745ce0cd0ebe68705f44cfe0c15ae7e1c11188 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Fri, 10 Jul 2026 17:00:10 +0300 Subject: [PATCH] Working on fixing scripting bugs' --- .../dialogue/uni_interior_dialogues_009.json | 18 +++ resources/start_dorm.lua | 5 + resources/start_uni_exterior.lua | 3 + resources/start_uni_interior.lua | 130 +++++++++--------- resources/w/ui/img/main/letters002.png | 4 +- resources/w/ui_en/img/main/letters002.png | 4 +- src/Game.cpp | 8 +- src/ScriptEngine.cpp | 18 +++ 8 files changed, 119 insertions(+), 71 deletions(-) diff --git a/resources/dialogue/uni_interior_dialogues_009.json b/resources/dialogue/uni_interior_dialogues_009.json index c517637..3f2f52b 100644 --- a/resources/dialogue/uni_interior_dialogues_009.json +++ b/resources/dialogue/uni_interior_dialogues_009.json @@ -4138,6 +4138,24 @@ } ] }, + { + "id": "teacher_dialog008", + "start": "line_1", + "nodes": [ + { + "id": "line_1", + "type": "Line", + "speaker": "Аида Джаныбековна", + "portrait": "resources/dialogue/portrait_teacher.png", + "text": "Бекзат, я сейчас занята, не мешай мне!", + "next": "end_1" + }, + { + "id": "end_1", + "type": "End" + } + ] + }, { "id": "cake_dialog001", "start": "line_1", diff --git a/resources/start_dorm.lua b/resources/start_dorm.lua index 777d8e5..45148f1 100644 --- a/resources/start_dorm.lua +++ b/resources/start_dorm.lua @@ -81,11 +81,13 @@ function on_bed_sleep() game_api.play_sound("audio/587036__antoineromo__bed-sheets.ogg") end game_api.player_stop() + game_api.player_rotate_to(0.0) end function on_alik_bathroom_click() game_api.start_dialogue("door_bathroom_alik_dialog001") game_api.player_stop() + game_api.player_rotate_to(180.0) end function on_locked_door_click() @@ -110,6 +112,7 @@ function on_alik_door_click() end end game_api.player_stop() + game_api.player_rotate_to(-90.0) end function callback_deactivate_cover_alik_room() @@ -187,6 +190,7 @@ function on_player_bathroom_click() game_api.play_sound("audio/702171__foxfire__door-opening.ogg") game_api.player_stop() + game_api.player_rotate_to(180.0) end end @@ -296,6 +300,7 @@ end function on_shelf_click() game_api.start_dialogue("dialog_shelf001") game_api.player_stop() + game_api.player_rotate_to(0.0) end function on_stove1_click() diff --git a/resources/start_uni_exterior.lua b/resources/start_uni_exterior.lua index f295170..ac3ea0c 100644 --- a/resources/start_uni_exterior.lua +++ b/resources/start_uni_exterior.lua @@ -29,10 +29,13 @@ function on_container_clicked() else game_api.start_dialogue("dialog_contaier001") end + game_api.player_stop() end function on_car_clicked() game_api.start_dialogue("dialog_car001") + game_api.player_stop() + game_api.player_rotate_to(90.0) end function dialog_contaier002_get_coursework() diff --git a/resources/start_uni_interior.lua b/resources/start_uni_interior.lua index 152d0ca..6887ef0 100644 --- a/resources/start_uni_interior.lua +++ b/resources/start_uni_interior.lua @@ -3,18 +3,12 @@ CONST_MSG_WHERE = "Бекзат, ты где? Я жду..." hall_door_opened = false teacher_door_opened = false ---lection_is_over = false player_hold_book = false player_hold_knife = false ---player_hold_key = false -teacher_arrived = false +teacher_arrived_to_library = false teacher_told_about_book = false ---player_left_darklands = false - ---was_darklands = false - --[[ 0 - day0 @@ -50,11 +44,8 @@ function dialog_aiperi_give_key() end function lection_hall_zone001_enter_callback() - --game_api.start_dialogue("") - --Start cutscene local lection_is_over = game_api.getIntValue("lection_is_over") - if (lection_is_over == 0) then game_api.player_stop() game_api.start_cutscene("lection_cutscene001") @@ -68,7 +59,6 @@ game_api.set_trigger_zone_callbacks("lection_hall_zone001", ) function knife_dialog_zone001_enter_callback() - print("knife_dialog_zone001_enter_callback--!") local day = game_api.getIntValue("day") local player_hold_key = game_api.getIntValue("player_hold_key") @@ -76,6 +66,7 @@ function knife_dialog_zone001_enter_callback() if (player_hold_knife == false) then local lection_is_over = game_api.getIntValue("lection_is_over") if lection_is_over == 1 then + game_api.npc_stop_and_rotate_to_player(1) if (player_hold_key == 1) then game_api.start_dialogue("knife_dialog002") else @@ -125,6 +116,7 @@ function on_knife_pickup() game_api.set_trigger_zone_enabled(2, true) 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") + game_api.player_stop() end function on_book_pickup() @@ -154,6 +146,7 @@ function on_book_pickup() print("on_book_pickup step2") game_api.start_dialogue("book_dialog005") end + game_api.player_stop() end function on_bookshelf_clicked() @@ -164,12 +157,14 @@ function on_bookshelf2_clicked() local day = game_api.getIntValue("day") if (game_api.is_night()) or (day >= 1) then game_api.start_dialogue("book_dialog007") + game_api.player_stop() else if (teacher_told_about_book and (player_hold_book == false)) then game_api.start_dialogue("book_dialog010") else game_api.start_dialogue("book_dialog007") end + game_api.player_stop() end end @@ -177,12 +172,14 @@ function on_bookshelf3_clicked() local day = game_api.getIntValue("day") if (game_api.is_night()) or (day >= 1) then game_api.start_dialogue("book_dialog008") + game_api.player_stop() else if (teacher_told_about_book and (player_hold_book == false)) then game_api.start_dialogue("book_dialog011") else game_api.start_dialogue("book_dialog008") end + game_api.player_stop() end end @@ -190,12 +187,14 @@ function on_bookshelf4_clicked() local day = game_api.getIntValue("day") if (game_api.is_night()) or (day >= 1) then game_api.start_dialogue("book_dialog009") + game_api.player_stop() else if (teacher_told_about_book and (player_hold_book == false)) then game_api.start_dialogue("book_dialog012") else game_api.start_dialogue("book_dialog009") end + game_api.player_stop() end end @@ -203,17 +202,20 @@ function on_bookshelf5_clicked() local day = game_api.getIntValue("day") if (game_api.is_night()) or (day >= 1) then game_api.start_dialogue("book_dialog013") + game_api.player_stop() else if (teacher_told_about_book and (player_hold_book == false)) then game_api.start_dialogue("book_dialog014") else game_api.start_dialogue("book_dialog013") end + game_api.player_stop() end end function on_cake_clicked() game_api.start_dialogue("cake_dialog001") + game_api.player_stop() end function on_npc_interact(npc_index) @@ -223,6 +225,7 @@ function on_npc_interact(npc_index) print("[Lua] NPC interaction! Index: " .. tostring(npc_index)) if npc_index == 1 then + game_api.player_stop_and_rotate_to_npc(1) if (game_api.is_dawn()) or (day > 0) then game_api.start_dialogue("dialog_aiperi_morning001") else @@ -234,11 +237,16 @@ function on_npc_interact(npc_index) end end if npc_index == 0 then + game_api.player_stop_and_rotate_to_npc(0) if (day == 0) then - if (teacher_told_about_book) then - game_api.start_dialogue("teacher_dialog006") + if (teacher_arrived_to_library) then + if (teacher_told_about_book) then + game_api.start_dialogue("teacher_dialog006") + else + game_api.start_dialogue("teacher_dialog001") + end else - game_api.start_dialogue("teacher_dialog001") + game_api.start_dialogue("teacher_dialog008") end else local player_has_coursework = game_api.getIntValue("player_has_coursework") @@ -257,6 +265,7 @@ function on_npc_interact(npc_index) end end if npc_index == 2 then + game_api.player_stop_and_rotate_to_npc(2) local report_card_signed = game_api.getIntValue("report_card_signed") if (report_card_signed == 1) then game_api.start_dialogue("dialog_with_ghost003") @@ -265,6 +274,7 @@ function on_npc_interact(npc_index) end end if npc_index == 6 then + game_api.player_stop_and_rotate_to_npc(6) game_api.start_dialogue("dialog_female_student002") end end @@ -298,6 +308,7 @@ function on_library_door_click() local lection_is_over = game_api.getIntValue("lection_is_over") if (day == 0) then + game_api.player_stop() if game_api.is_dawn() then if not (morning_did_open_door_index == 5) then game_api.start_dialogue("door_dialog007") @@ -313,23 +324,14 @@ function on_library_door_click() end end else - print("Library door step 1") - if (game_api.is_night()) then - print("Library door step 2") - print("morning_did_open_door_index") - print(morning_did_open_door_index) - print("game_api.is_dawn()") - print(game_api.is_dawn()) if (not game_api.is_dawn() or not (morning_did_open_door_index == 5)) then - print("Library door step 3") + game_api.player_stop() game_api.start_dialogue("door_dialog001") + game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") end - print("Library door step 4") end - print("Library door step5") end - print("Library door step 5") end function callback_deactivate_cover_s3() @@ -348,19 +350,14 @@ function callback_deactivate_cover_main_hall() game_api.deactivate_interactive_object("Room_Cover_Main_Hall_001") end ---Room_Cover_North_3_001 ---Room_Cover_Main_Hall_001 +function callback_player_rotate_90() +game_api.player_rotate_to(90.0) +end function on_teachers_door_click() - print("on_teachers_door_click---") - local player_hold_key = game_api.getIntValue("player_hold_key") - local day = game_api.getIntValue("day") - print("day is") - print(day) - if (day == 0) and not game_api.is_dawn() then if (player_hold_key == 1) then if (not teacher_door_opened) then @@ -369,16 +366,15 @@ function on_teachers_door_click() game_api.fade_object("Room_Cover_South_3_001", 0, 0.5, callback_deactivate_cover_s3) game_api.switch_navigation(3) game_api.play_sound("audio/702171__foxfire__door-opening.ogg") + game_api.player_stop() end else game_api.start_dialogue("door_dialog004") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end else if (game_api.is_dawn()) then - print("morning_can_open_door_index is") - print(morning_can_open_door_index) - if (teacher_door_opened == false) then teacher_door_opened = true if (morning_can_open_door_index == 3) then @@ -387,6 +383,9 @@ function on_teachers_door_click() game_api.fade_object("Room_Cover_Corridor_001", 0, 0.5, callback_deactivate_cover_corridor) game_api.switch_navigation(10) game_api.play_sound("audio/702171__foxfire__door-opening.ogg") + --game_api.player_stop() + game_api.player_walk_to(-2.63033, 0, 6.18222, callback_player_rotate_90) + --Player pos: -2.63033 0 6.18222 else if (morning_did_open_door_index == 1) then --s1 + s3 @@ -395,6 +394,7 @@ function on_teachers_door_click() game_api.fade_object("Room_Cover_South_3_001", 0, 0.5, callback_deactivate_cover_s3) game_api.switch_navigation(11) game_api.play_sound("audio/702171__foxfire__door-opening.ogg") + game_api.player_stop() elseif (morning_did_open_door_index == 2) then --s2 + s3 @@ -402,20 +402,21 @@ function on_teachers_door_click() game_api.fade_object("Room_Cover_South_3_001", 0, 0.5, callback_deactivate_cover_s3) game_api.switch_navigation(15) game_api.play_sound("audio/702171__foxfire__door-opening.ogg") + game_api.player_stop() elseif (morning_did_open_door_index == 5) then --n3 + s3 - game_api.rotate_object("Room_S_2_Leaf001", 90, 0.5, nil) game_api.fade_object("Room_Cover_South_3_001", 0, 0.5, callback_deactivate_cover_s3) game_api.switch_navigation(13) game_api.play_sound("audio/702171__foxfire__door-opening.ogg") + game_api.player_stop() elseif (morning_did_open_door_index == 4) then --n2 + s3 - game_api.rotate_object("Room_S_2_Leaf001", 90, 0.5, nil) game_api.fade_object("Room_Cover_South_3_001", 0, 0.5, callback_deactivate_cover_s3) game_api.switch_navigation(14) game_api.play_sound("audio/702171__foxfire__door-opening.ogg") + game_api.player_stop() elseif (morning_did_open_door_index == 6) then --hall + s3 @@ -423,6 +424,7 @@ function on_teachers_door_click() game_api.fade_object("Room_Cover_South_3_001", 0, 0.5, callback_deactivate_cover_s3) game_api.switch_navigation(12) game_api.play_sound("audio/702171__foxfire__door-opening.ogg") + game_api.player_stop() else --Player was in corridor, but tries to open the teacher's door --s3 only @@ -430,6 +432,7 @@ function on_teachers_door_click() game_api.fade_object("Room_Cover_South_3_001", 0, 0.5, callback_deactivate_cover_s3) game_api.switch_navigation(10) game_api.play_sound("audio/702171__foxfire__door-opening.ogg") + game_api.player_stop() end end end @@ -448,13 +451,13 @@ function on_teachers_door_click() game_api.switch_navigation(10) game_api.play_sound("audio/702171__foxfire__door-opening.ogg") + game_api.player_stop() end end end end function on_hall_door_click() - print("on_hall_door_click---") local day = game_api.getIntValue("day") @@ -463,6 +466,7 @@ function on_hall_door_click() if not (morning_did_open_door_index == 6) then game_api.start_dialogue("door_dialog007") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end else if (not hall_door_opened) then @@ -474,6 +478,7 @@ function on_hall_door_click() game_api.set_npc_enabled(6, true) game_api.npc_rotate_to(6, 180) game_api.play_sound("audio/702171__foxfire__door-opening.ogg") + game_api.player_stop() end end else @@ -481,6 +486,7 @@ function on_hall_door_click() if not (morning_did_open_door_index == 6) then game_api.start_dialogue("door_dialog001") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end end end @@ -492,16 +498,19 @@ function on_s1_door_click() if not (morning_did_open_door_index == 1) then game_api.start_dialogue("door_dialog007") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end else if (game_api.is_night()) then if not (morning_did_open_door_index == 1) then game_api.start_dialogue("door_dialog001") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end else game_api.start_dialogue("door_dialog006") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end end end @@ -512,16 +521,19 @@ function on_s2_door_click() if not (morning_did_open_door_index == 2) then game_api.start_dialogue("door_dialog007") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end else if (game_api.is_night()) then if not (morning_did_open_door_index == 2) then game_api.start_dialogue("door_dialog001") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end else game_api.start_dialogue("door_dialog006") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end end end @@ -532,43 +544,39 @@ function on_n2_door_click() if not (morning_did_open_door_index == 4) then game_api.start_dialogue("door_dialog007") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end else if (game_api.is_night()) then if not (morning_did_open_door_index == 4) then game_api.start_dialogue("door_dialog001") game_api.play_sound("audio/398751__anthousai__door-lock-01.ogg") + game_api.player_stop() end end end end function on_teacher_arrived() - print("Teacher arrived") game_api.npc_rotate_to(0, 180) + teacher_arrived_to_library = true end function on_student_arrived() - print("Student arrived") game_api.npc_rotate_to(6, 270) end function on_teacher_arrived2() - print("Teacher arrived2") game_api.npc_rotate_to(0, 0) end function book_dialog_zone001_enter_callback() - print("book_dialog_zone001_enter_callback step 1") if (game_api.is_darklands()) then - print("book_dialog_zone001_enter_callback step 2") game_api.start_dialogue("ghost_dialog002") game_api.set_trigger_zone_enabled(3, false) game_api.advance_darklands_hud() else - print("book_dialog_zone001_enter_callback step 3") if (player_hold_book) and (game_api.is_night() == false) then - print("book_dialog_zone001_enter_callback step 4") game_api.start_dialogue("book_dialog001") game_api.switch_navigation(0) game_api.player_stop() @@ -577,43 +585,36 @@ function book_dialog_zone001_enter_callback() end function book_dialog_zone001_exit_callback() - print("book_dialog_zone001_exit_callback step 1") if (player_hold_book) and (game_api.is_night() == false) then game_api.switch_navigation(3) end end function on_computer_clicked() - print("on_computer_clicked--") + local day = game_api.getIntValue("day") if game_api.is_darklands() then game_api.start_dialogue("computer_dialog004") elseif (day >= 1) or game_api.is_dawn() then - print("on_computer_clicked--1") game_api.start_dialogue("computer_dialog003") else if teacher_told_about_book then - print("on_computer_clicked--2") if (game_api.is_night() == false) then - print("on_computer_clicked--3") if (player_hold_book) then - print("on_computer_clicked--4") game_api.quest_set_objective_completed("study_project", "study_project_write") game_api.start_cutscene("computer_cutscene001") else - print("on_computer_clicked--5") game_api.start_dialogue("book_dialog002") end else - print("on_computer_clicked--6") game_api.start_dialogue("computer_dialog002") end else - print("on_computer_clicked--7") game_api.start_dialogue("computer_dialog001") end end + game_api.player_stop() end function on_sleep_cutscene() @@ -649,7 +650,6 @@ function on_teacher_arrived_intermediate() game_api.fade_object("Room_Cover_North_3_001", 0, 0.5, callback_deactivate_cover_n3) game_api.switch_navigation(2) game_api.npc_walk_to(0, 4.94194, 0, 6.64111, on_teacher_arrived) - teacher_arrived = true game_api.play_sound("audio/702171__foxfire__door-opening.ogg") end @@ -684,18 +684,17 @@ end game_api.set_cutscene_callback("lection_cutscene001", callback_on_lection_cutscene_over) - function on_note_pickup() game_api.start_dialogue("note_dialog001") game_api.pickup_item("note_spell") game_api.deactivate_interactive_object("Note001") + game_api.player_stop() end function on_report_card_pickup() local player_repord_card_discussed = game_api.getIntValue("player_repord_card_discussed") if player_repord_card_discussed == 1 then local player_has_report_card = game_api.getIntValue("player_has_report_card") - --local report_card_signed = game_api.getIntValue("report_card_signed") if (player_has_report_card == 1) then game_api.start_dialogue("dialog_report_card003") -- Еще рано возвращать зачетку обратно в шкаф @@ -709,6 +708,7 @@ function on_report_card_pickup() else game_api.start_dialogue("dialog_report_card001") end + game_api.player_stop() end function on_bookshelf_teacher_clicked() @@ -750,6 +750,7 @@ function callback_enter_darklands() end game_api.switch_navigation(5) + game_api.player_stop() end function callback_exit_darklands() @@ -762,13 +763,10 @@ function callback_exit_darklands() game_api.set_npc_enabled(5, false) game_api.set_npc_enabled(6, false) - --game_api.setIntValue("need_sleep", 1) - --player_left_darklands = true morning_did_open_door_index = 0 setDay1MorningSetup() game_api.set_dawn() - local px = game_api.get_player_x() local pz = game_api.get_player_z() @@ -847,6 +845,8 @@ function callback_exit_darklands() game_api.setIntValue("morning_aiperi_save_again", 1) end end + + game_api.player_stop() end game_api.set_darklands_callbacks( @@ -868,7 +868,6 @@ function setDay0setup() game_api.deactivate_interactive_object("Room_Cover_Corridor_001") game_api.deactivate_interactive_object("Room_Cover_North_2_001") - game_api.rotate_object("Room_N_1_Leaf001", -90, 0.01, nil) --game_api.set_object_rotation("Room_N_1_Leaf001", -90) @@ -1070,6 +1069,8 @@ function setDay1NightSetup() game_api.deactivate_interactive_object("car001") game_api.deactivate_interactive_object("car001wheels") + + game_api.deactivate_interactive_object("Cake001") local px = game_api.get_player_x() local pz = game_api.get_player_z() @@ -1112,6 +1113,7 @@ function setDay1NightSetup() end function callback_enter_night() + game_api.start_dialogue("dialog_video001") setDay1NightSetup() game_api.switch_navigation(16) @@ -1133,6 +1135,8 @@ function callback_enter_night() end end --------------- + game_api.player_stop() + end game_api.set_enter_night_callback(callback_enter_night) @@ -1217,6 +1221,8 @@ game_api.set_chat_callbacks( ) function callback_aiperi_dialog_morning() + game_api.player_stop_and_rotate_to_npc(1) + game_api.npc_stop_and_rotate_to_player(1) game_api.start_dialogue("dialog_aiperi_morning001") end diff --git a/resources/w/ui/img/main/letters002.png b/resources/w/ui/img/main/letters002.png index 8a6c926..56bb04f 100644 --- a/resources/w/ui/img/main/letters002.png +++ b/resources/w/ui/img/main/letters002.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:571b3ba2c13cd936b8a13e14d0b27930049fb2fe9b00799eb2ff653893ad897c -size 176244 +oid sha256:9adf383f97ec4fb54052ed4ab0eb34ff9fb59641eaab866175473cae1e4254fe +size 159035 diff --git a/resources/w/ui_en/img/main/letters002.png b/resources/w/ui_en/img/main/letters002.png index c8497eb..6562dc4 100644 --- a/resources/w/ui_en/img/main/letters002.png +++ b/resources/w/ui_en/img/main/letters002.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:739bf2133ec00ffd0c5e19def5ca4d076c0c7361ad57dd25f30baa4e7ec3b976 -size 205010 +oid sha256:05e6d36f671453475c320b0792cadd9006255c26fa4aa69eb6e2648d6d9f881f +size 185932 diff --git a/src/Game.cpp b/src/Game.cpp index 3f022b9..0b572cf 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -16,6 +16,7 @@ #include #endif + #ifdef EMSCRIPTEN #include #endif @@ -1163,11 +1164,8 @@ namespace ZL case SDLK_k: x = x - 1; std::cout << "current x: " << x << std::endl; - //y = y + 1; - //std::cout << "current y: " << y << std::endl; - //y = y - 0.002; - std::cout << "Player pos: " << currentLocation()->player->state.position.transpose() << std::endl; + std::cout << "Player rotation: " << currentLocation()->player->state.facingAngle*180.0/M_PI << std::endl; //currentLocation->npcs[0]->walkSpeed -= 0.01f; //std::cout << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl; break; @@ -1515,7 +1513,6 @@ namespace ZL bool Game::startDarklandsTransition() { - audioPlayer->playSoundAsync("audio/459618__n-lerche__blow.ogg"); if (!gameState.isNight) { currentLocation()->dialogueSystem.startDialogue("darklands_day_dialog001"); @@ -1528,6 +1525,7 @@ namespace ZL return false; } + audioPlayer->playSoundAsync("audio/459618__n-lerche__blow.ogg"); if (darklandsFlashActive) return false; darklandsFlashActive = true; darklandsFlashFadingIn = true; diff --git a/src/ScriptEngine.cpp b/src/ScriptEngine.cpp index f065d34..cd07736 100644 --- a/src/ScriptEngine.cpp +++ b/src/ScriptEngine.cpp @@ -128,11 +128,29 @@ namespace ZL { npcs[index]->state.targetFacingAngle = rad; }); + api.set_function("player_rotate_to", + [loc](float angle) { + const float rad = angle * static_cast(M_PI) / 180.f; + std::cout << "[script] player_rotate_to: angle " << angle << " degrees (" << rad << " radians)" << std::endl; + loc->player->state.targetFacingAngle = rad; + }); + api.set_function("player_stop_and_rotate_to_npc", [loc](int index) { loc->player->stopAndRotateToTarget(index); }); + api.set_function("npc_stop_and_rotate_to_player", + [loc](int index) { + auto& npcs = loc->npcs; + if (index < 0 || index >= static_cast(npcs.size())) { + std::cerr << "[script] npc_stop_and_rotate_to_player: index " << index + << " out of range (0.." << npcs.size() - 1 << ")\n"; + return; + } + npcs[index]->stopAndRotateToTarget(CharacterState::kPlayerIndex); + }); + api.set_function("player_walk_to", [loc, this_impl = impl.get()](float x, float y, float z, sol::object on_arrived) {