From a4606f596c5b95d67fc6b8c44daeedcc28418f92 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Mon, 1 Dec 2025 17:31:12 +0000 Subject: [PATCH] Enhance NPC encounter logging and fix item type references in scenario scripts --- .../break_escape/games_controller.rb | 26 +++++++++++++++++- app/models/break_escape/game.rb | 20 ++++++++++++-- .../break_escape/assets/objects/id_badge.png | Bin 0 -> 192 bytes .../break_escape/js/systems/npc-behavior.js | 4 +-- public/break_escape/js/systems/npc-sprites.js | 13 +++++---- scenarios/m01_first_contact/FIXES_APPLIED.md | 12 ++++---- .../m01_first_contact/ink/m01_npc_sarah.ink | 2 +- .../m01_first_contact/ink/m01_npc_sarah.json | 2 +- scenarios/m01_first_contact/scenario.json.erb | 8 +++--- 9 files changed, 64 insertions(+), 23 deletions(-) create mode 100644 public/break_escape/assets/objects/id_badge.png diff --git a/app/controllers/break_escape/games_controller.rb b/app/controllers/break_escape/games_controller.rb index 6ea54fd..a996b94 100644 --- a/app/controllers/break_escape/games_controller.rb +++ b/app/controllers/break_escape/games_controller.rb @@ -545,10 +545,23 @@ module BreakEscape new_npcs = npc_ids - @game.player_state['encounteredNPCs'] return if new_npcs.empty? + # Log detailed information about each new NPC encountered + new_npcs.each do |npc_id| + npc_data = room_data['npcs'].find { |npc| npc['id'] == npc_id } + if npc_data + display_name = npc_data['displayName'] || npc_id + npc_type = npc_data['npcType'] || 'unknown' + Rails.logger.info "[BreakEscape] 🎭 NPC ENCOUNTERED: #{display_name} (#{npc_id}) - Type: #{npc_type} - Room: #{room_id}" + else + Rails.logger.info "[BreakEscape] 🎭 NPC ENCOUNTERED: #{npc_id} - Room: #{room_id}" + end + end + @game.player_state['encounteredNPCs'] = (@game.player_state['encounteredNPCs'] + new_npcs).uniq @game.save! - Rails.logger.debug "[BreakEscape] Tracked NPC encounters: #{new_npcs.join(', ')}" + total_encountered = @game.player_state['encounteredNPCs'].length + Rails.logger.info "[BreakEscape] ✅ Tracked #{new_npcs.length} new NPC encounter(s) in room #{room_id}. Total NPCs encountered: #{total_encountered}" rescue => e Rails.logger.error "[BreakEscape] Error tracking NPC encounters: #{e.message}\n#{e.backtrace.first(5).join("\n")}" # Continue without tracking to avoid breaking room loading @@ -744,6 +757,17 @@ module BreakEscape end end end + + # Priority 3: Items held by NPCs in this room + room_data['npcs']&.each do |npc| + next unless npc['itemsHeld'].present? + + npc['itemsHeld'].each do |held_item| + if held_item['type'] == item_type && (held_item['key_id'] == item_id || held_item['id'] == item_id || held_item['name'] == item_name || held_item['name'] == item_id) + return { item: held_item, location: { type: 'npc', npc_id: npc['id'], room_id: room_id } } + end + end + end end nil diff --git a/app/models/break_escape/game.rb b/app/models/break_escape/game.rb index 468ea55..e5fac11 100644 --- a/app/models/break_escape/game.rb +++ b/app/models/break_escape/game.rb @@ -102,8 +102,24 @@ module BreakEscape # NPC tracking def encounter_npc!(npc_id) player_state['encounteredNPCs'] ||= [] - player_state['encounteredNPCs'] << npc_id unless player_state['encounteredNPCs'].include?(npc_id) - save! + unless player_state['encounteredNPCs'].include?(npc_id) + player_state['encounteredNPCs'] << npc_id + + # Try to get NPC display name from scenario for better logging + npc_display_name = npc_id + if scenario_data && scenario_data['rooms'] + scenario_data['rooms'].each do |_room_id, room_data| + npc_data = room_data['npcs']&.find { |npc| npc['id'] == npc_id } + if npc_data && npc_data['displayName'] + npc_display_name = npc_data['displayName'] + break + end + end + end + + Rails.logger.info "[BreakEscape] 🎭 NPC ENCOUNTERED (via encounter_npc!): #{npc_display_name} (#{npc_id})" + save! + end end # Global variables (synced with client) diff --git a/public/break_escape/assets/objects/id_badge.png b/public/break_escape/assets/objects/id_badge.png new file mode 100644 index 0000000000000000000000000000000000000000..a30fd69d825a70fb5e0b74ab10c0175e2108e42f GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^AhrMp8<5nmf9C+C7>k44ofy`glX(f`RC&5MhG2qQxZVHDJ":".^.b","c":true},{"b":["\n","ev",true,"/ev",{"VAR=":"met_sarah","re":true},"ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Hi! You must be the IT contractor. I'm Sarah, the receptionist.","\n","^Sarah: Let me get you checked in.","\n",{"->":"first_checkin"},{"->":"start.5"},null]}],"nop","\n","ev",{"VAR?":"met_sarah"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Sarah: Hey, need anything else?","\n",{"->":"hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_checkin":[["ev","str","^Thanks. I'm here to audit your network security","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Just point me to IT and I'll get started","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Oh good! Kevin mentioned you'd be coming.","\n","^Sarah: Let me print your visitor badge.","\n",{"->":"receive_badge"},null],"c-1":["\n","^Sarah: Sure thing. Let me get your badge first.","\n",{"->":"receive_badge"},null]}],null],"receive_badge":["ev",true,"/ev",{"VAR=":"has_badge","re":true},"#","^give_item:visitor_badge","/#","#","^complete_task:meet_reception","/#","^Sarah: Here you go. This gets you into public areas.","\n","^Sarah: Restricted areas need keycard access or you'll need to ask Kevin.","\n",{"->":"hub"},null],"hub":[["ev","str","^Where can I find Kevin?","/str",{"VAR?":"asked_about_kevin"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^Can you tell me about the office layout?","/str",{"VAR?":"asked_about_office"},"!","/ev",{"*":".^.c-1","flg":5},"ev","str","^Anyone working late I should know about?","/str",{"VAR?":"asked_about_derek"},"!",{"VAR?":"influence"},3,">=","&&","/ev",{"*":".^.c-2","flg":5},"ev","str","^Thanks, I'll get started","/str","/ev",{"*":".^.c-3","flg":4},{"c-0":["\n",{"->":"ask_kevin_location"},null],"c-1":["\n",{"->":"ask_office_layout"},null],"c-2":["\n",{"->":"ask_late_workers"},null],"c-3":["\n","#","^exit_conversation","/#","^Sarah: Good luck with the audit!","\n",{"->":"hub"},null]}],null],"ask_kevin_location":[["ev",true,"/ev",{"VAR=":"asked_about_kevin","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Kevin's desk is in the main office area—can't miss it. Covered in monitors and coffee cups.","\n","^Sarah: He's usually there this time of day.","\n","ev","str","^What's he like?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"kevin_personality"},null],"c-1":["\n",{"->":"hub"},null]}],null],"kevin_personality":["ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Super helpful, kind of overworked. The company relies on him way too much.","\n","^Sarah: He'll appreciate having someone competent help out.","\n",{"->":"hub"},null],"ask_office_layout":[["ev",true,"/ev",{"VAR=":"asked_about_office","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Main office is through there—hot-desking setup. Conference room on the west side, break room to the east.","\n","^Sarah: Server room is behind main office, but you'll need Kevin's access for that.","\n","ev","str","^What about executive offices?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Got it, thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"ask_executive_offices"},null],"c-1":["\n",{"->":"hub"},null]}],null],"ask_executive_offices":["ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Derek's office is off the main area—he's our Senior Marketing Manager. Usually locks his door when he's out.","\n","^Sarah: Most people just have desk space, but Derek got an office because of client confidentiality stuff.","\n",{"->":"hub"},null],"ask_late_workers":[["ev",true,"/ev",{"VAR=":"asked_about_derek","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Derek's usually here late. Like, really late. Sometimes I leave at 6 and he's still working.","\n","^Sarah: He says it's because of client timezones, but...","\n","ev","str","^But what?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Dedication, I guess","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"derek_suspicion"},null],"c-1":["\n",{"->":"hub"},null]}],null],"derek_suspicion":[["ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: I don't know. It just seems weird, you know? He's marketing, not IT.","\n","^Sarah: And I've seen him in the server room a couple times. Told me he was checking on campaign servers.","\n","ev","str","^That does seem odd","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Maybe he's just thorough","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Right? But I'm just the receptionist. What do I know?","\n",{"->":"hub"},null],"c-1":["\n","^Sarah: Maybe. Anyway, Kevin would know more about the technical stuff.","\n",{"->":"hub"},null]}],null],"global decl":["ev",0,{"VAR=":"influence"},false,{"VAR=":"met_sarah"},false,{"VAR=":"has_badge"},false,{"VAR=":"asked_about_derek"},false,{"VAR=":"asked_about_office"},false,{"VAR=":"asked_about_kevin"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"met_sarah"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","ev",true,"/ev",{"VAR=":"met_sarah","re":true},"ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Hi! You must be the IT contractor. I'm Sarah, the receptionist.","\n","^Sarah: Let me get you checked in.","\n",{"->":"first_checkin"},{"->":"start.5"},null]}],"nop","\n","ev",{"VAR?":"met_sarah"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Sarah: Hey, need anything else?","\n",{"->":"hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_checkin":[["ev","str","^Thanks. I'm here to audit your network security","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Just point me to IT and I'll get started","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Oh good! Kevin mentioned you'd be coming.","\n","^Sarah: Let me print your visitor badge.","\n",{"->":"receive_badge"},null],"c-1":["\n","^Sarah: Sure thing. Let me get your badge first.","\n",{"->":"receive_badge"},null]}],null],"receive_badge":["ev",true,"/ev",{"VAR=":"has_badge","re":true},"#","^give_item:id_badge","/#","#","^complete_task:meet_reception","/#","^Sarah: Here you go. This gets you into public areas.","\n","^Sarah: Restricted areas need keycard access or you'll need to ask Kevin.","\n",{"->":"hub"},null],"hub":[["ev","str","^Where can I find Kevin?","/str",{"VAR?":"asked_about_kevin"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^Can you tell me about the office layout?","/str",{"VAR?":"asked_about_office"},"!","/ev",{"*":".^.c-1","flg":5},"ev","str","^Anyone working late I should know about?","/str",{"VAR?":"asked_about_derek"},"!",{"VAR?":"influence"},3,">=","&&","/ev",{"*":".^.c-2","flg":5},"ev","str","^Thanks, I'll get started","/str","/ev",{"*":".^.c-3","flg":4},{"c-0":["\n",{"->":"ask_kevin_location"},null],"c-1":["\n",{"->":"ask_office_layout"},null],"c-2":["\n",{"->":"ask_late_workers"},null],"c-3":["\n","#","^exit_conversation","/#","^Sarah: Good luck with the audit!","\n",{"->":"hub"},null]}],null],"ask_kevin_location":[["ev",true,"/ev",{"VAR=":"asked_about_kevin","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Kevin's desk is in the main office area—can't miss it. Covered in monitors and coffee cups.","\n","^Sarah: He's usually there this time of day.","\n","ev","str","^What's he like?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"kevin_personality"},null],"c-1":["\n",{"->":"hub"},null]}],null],"kevin_personality":["ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Super helpful, kind of overworked. The company relies on him way too much.","\n","^Sarah: He'll appreciate having someone competent help out.","\n",{"->":"hub"},null],"ask_office_layout":[["ev",true,"/ev",{"VAR=":"asked_about_office","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Main office is through there—hot-desking setup. Conference room on the west side, break room to the east.","\n","^Sarah: Server room is behind main office, but you'll need Kevin's access for that.","\n","ev","str","^What about executive offices?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Got it, thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"ask_executive_offices"},null],"c-1":["\n",{"->":"hub"},null]}],null],"ask_executive_offices":["ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Derek's office is off the main area—he's our Senior Marketing Manager. Usually locks his door when he's out.","\n","^Sarah: Most people just have desk space, but Derek got an office because of client confidentiality stuff.","\n",{"->":"hub"},null],"ask_late_workers":[["ev",true,"/ev",{"VAR=":"asked_about_derek","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Derek's usually here late. Like, really late. Sometimes I leave at 6 and he's still working.","\n","^Sarah: He says it's because of client timezones, but...","\n","ev","str","^But what?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Dedication, I guess","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"derek_suspicion"},null],"c-1":["\n",{"->":"hub"},null]}],null],"derek_suspicion":[["ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: I don't know. It just seems weird, you know? He's marketing, not IT.","\n","^Sarah: And I've seen him in the server room a couple times. Told me he was checking on campaign servers.","\n","ev","str","^That does seem odd","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Maybe he's just thorough","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Right? But I'm just the receptionist. What do I know?","\n",{"->":"hub"},null],"c-1":["\n","^Sarah: Maybe. Anyway, Kevin would know more about the technical stuff.","\n",{"->":"hub"},null]}],null],"global decl":["ev",0,{"VAR=":"influence"},false,{"VAR=":"met_sarah"},false,{"VAR=":"has_badge"},false,{"VAR=":"asked_about_derek"},false,{"VAR=":"asked_about_office"},false,{"VAR=":"asked_about_kevin"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/m01_first_contact/scenario.json.erb b/scenarios/m01_first_contact/scenario.json.erb index fe915fd..8959233 100644 --- a/scenarios/m01_first_contact/scenario.json.erb +++ b/scenarios/m01_first_contact/scenario.json.erb @@ -57,7 +57,7 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "id": "briefing_cutscene", "displayName": "Agent 0x99", "npcType": "person", - "position": { "x": 5, "y": 5 }, + "position": { "x": 500, "y": 500 }, "spriteSheet": "hacker", "spriteConfig": { "idleFrameStart": 20, @@ -75,7 +75,7 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "id": "sarah_martinez", "displayName": "Sarah Martinez", "npcType": "person", - "position": { "x": 3, "y": 4 }, + "position": { "x": 4, "y": 1.5 }, "spriteSheet": "hacker-red", "spriteTalk": "assets/characters/hacker-red-talk.png", "spriteConfig": { @@ -86,7 +86,7 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "currentKnot": "start", "itemsHeld": [ { - "type": "visitor_badge", + "type": "id_badge", "name": "Visitor Badge", "takeable": true, "observations": "Temporary visitor badge for office access" @@ -121,7 +121,7 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "id": "kevin_park", "displayName": "Kevin Park", "npcType": "person", - "position": { "x": 10, "y": 7 }, + "position": { "x": 8, "y": 7 }, "spriteSheet": "hacker", "spriteConfig": { "idleFrameStart": 20,