mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
feat: Enhance objectives system with new NPC interactions and Ink dialogue integration
This commit is contained in:
@@ -505,6 +505,9 @@ module BreakEscape
|
||||
contents
|
||||
end
|
||||
|
||||
# Items that are always allowed in inventory (core game mechanics)
|
||||
ALWAYS_ALLOWED_ITEMS = %w[notepad].freeze
|
||||
|
||||
def validate_item_collectible(item)
|
||||
item_type = item['type']
|
||||
# Use key_id for keys (more unique), fall back to id for other items
|
||||
@@ -513,6 +516,12 @@ module BreakEscape
|
||||
|
||||
Rails.logger.info "[BreakEscape] validate_item_collectible: type=#{item_type}, id=#{item_id}, name=#{item_name}"
|
||||
|
||||
# Always allow core game items like notepad
|
||||
if ALWAYS_ALLOWED_ITEMS.include?(item_type)
|
||||
Rails.logger.info "[BreakEscape] Item is always allowed: #{item_type}"
|
||||
return nil
|
||||
end
|
||||
|
||||
# Check if this is a starting item first (if so, skip all other checks)
|
||||
is_starting_item = @game.scenario_data['startItemsInInventory']&.any? do |start_item|
|
||||
start_item['type'] == item_type && (start_item['id'] == item_id || start_item['name'] == item_name)
|
||||
@@ -742,7 +751,7 @@ module BreakEscape
|
||||
|
||||
def compile_ink(ink_path)
|
||||
output_path = ink_path.to_s.gsub(/\.ink$/, '.json')
|
||||
inklecate_path = Rails.root.join('bin', 'inklecate')
|
||||
inklecate_path = BreakEscape::Engine.root.join('bin', 'inklecate')
|
||||
|
||||
stdout, stderr, status = Open3.capture3(
|
||||
inklecate_path.to_s,
|
||||
|
||||
@@ -380,7 +380,8 @@ module BreakEscape
|
||||
return { success: false, error: 'Object not unlocked' }
|
||||
end
|
||||
when 'npc_conversation'
|
||||
unless npc_encountered?(task['targetNpc'])
|
||||
target_npc = task['targetNPC'] || task['targetNpc']
|
||||
unless npc_encountered?(target_npc)
|
||||
return { success: false, error: 'NPC not encountered' }
|
||||
end
|
||||
when 'enter_room'
|
||||
|
||||
88
scenarios/test_objectives_ink/alice.ink
Normal file
88
scenarios/test_objectives_ink/alice.ink
Normal file
@@ -0,0 +1,88 @@
|
||||
// alice.ink
|
||||
// Demonstrates all three objective Ink tags:
|
||||
// - #complete_task:task_id - marks a task as completed
|
||||
// - #unlock_task:task_id - unlocks a locked task
|
||||
// - #unlock_aim:aim_id - unlocks a locked aim
|
||||
//
|
||||
// IMPORTANT: Uses mission hub pattern - never uses -> END
|
||||
// Instead uses #exit_conversation tag to leave the chat
|
||||
|
||||
VAR alice_talked = false
|
||||
VAR secret_revealed = false
|
||||
VAR secret_task_done = false
|
||||
|
||||
=== start ===
|
||||
Hey there! I'm Alice. Welcome to the objectives system test.
|
||||
Player: Hi there!
|
||||
-> hub
|
||||
|
||||
=== hub ===
|
||||
+ {not alice_talked} [Nice to meet you, Alice]
|
||||
-> first_meeting
|
||||
|
||||
+ {alice_talked and not secret_revealed} [Tell me about the secret mission]
|
||||
-> reveal_secret
|
||||
|
||||
+ {secret_revealed and not secret_task_done} [I'm ready for the secret task]
|
||||
-> complete_secret_task
|
||||
|
||||
+ {secret_task_done} [Any final words?]
|
||||
-> final_words
|
||||
|
||||
+ [What can you tell me about objectives?]
|
||||
-> explain_objectives
|
||||
|
||||
+ [I need to go]
|
||||
See you around!
|
||||
#exit_conversation
|
||||
-> hub
|
||||
|
||||
=== first_meeting ===
|
||||
Great to meet you too! This task is now complete.
|
||||
#complete_task:talk_to_alice
|
||||
~ alice_talked = true
|
||||
You should go talk to Bob next - I just unlocked that task for you.
|
||||
-> hub
|
||||
|
||||
=== explain_objectives ===
|
||||
The objectives system uses three Ink tags:
|
||||
-> explain_objectives_detail
|
||||
|
||||
=== explain_objectives_detail ===
|
||||
+ [Tell me about complete_task]
|
||||
NPC: **complete_task:task_id** marks a task as completed. The ObjectivesManager will update the UI and sync with the server.
|
||||
-> explain_objectives_detail
|
||||
|
||||
+ [Tell me about unlock_task]
|
||||
NPC: **unlock_task:task_id** unlocks a locked task so it becomes active and visible.
|
||||
-> explain_objectives_detail
|
||||
|
||||
+ [Tell me about unlock_aim]
|
||||
NPC: **unlock_aim:aim_id** unlocks an entire aim (objective group) that was previously locked.
|
||||
-> explain_objectives_detail
|
||||
|
||||
+ [That's enough info, thanks]
|
||||
NPC: These tags let NPCs control the player's objectives through dialogue!
|
||||
-> hub
|
||||
|
||||
=== reveal_secret ===
|
||||
Alright, I'll let you in on a secret...
|
||||
There's a hidden mission that only unlocks through dialogue!
|
||||
#unlock_aim:secret_mission
|
||||
~ secret_revealed = true
|
||||
I've just unlocked the "Secret Mission" aim for you. Check your objectives!
|
||||
But the tasks inside are still locked. Let me unlock the first one...
|
||||
#unlock_task:secret_task_1
|
||||
There! Now you can complete the first secret task.
|
||||
-> hub
|
||||
|
||||
=== complete_secret_task ===
|
||||
Excellent! You're doing great with the secret mission.
|
||||
#complete_task:secret_task_1
|
||||
~ secret_task_done = true
|
||||
That's one secret task down! Bob can help you with the second one.
|
||||
-> hub
|
||||
|
||||
=== final_words ===
|
||||
You've done great! Once Bob helps you finish, come back for the final debrief.
|
||||
-> hub
|
||||
1
scenarios/test_objectives_ink/alice.json
Normal file
1
scenarios/test_objectives_ink/alice.json
Normal file
@@ -0,0 +1 @@
|
||||
{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["^Hey there! I'm Alice. Welcome to the objectives system test.","\n","^Player: Hi there!","\n",{"->":"hub"},null],"hub":[["ev","str","^Nice to meet you, Alice","/str",{"VAR?":"alice_talked"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^Tell me about the secret mission","/str",{"VAR?":"alice_talked"},{"VAR?":"secret_revealed"},"!","&&","/ev",{"*":".^.c-1","flg":5},"ev","str","^I'm ready for the secret task","/str",{"VAR?":"secret_revealed"},{"VAR?":"secret_task_done"},"!","&&","/ev",{"*":".^.c-2","flg":5},"ev","str","^Any final words?","/str",{"VAR?":"secret_task_done"},"/ev",{"*":".^.c-3","flg":5},"ev","str","^What can you tell me about objectives?","/str","/ev",{"*":".^.c-4","flg":4},"ev","str","^I need to go","/str","/ev",{"*":".^.c-5","flg":4},{"c-0":["\n",{"->":"first_meeting"},null],"c-1":["\n",{"->":"reveal_secret"},null],"c-2":["\n",{"->":"complete_secret_task"},null],"c-3":["\n",{"->":"final_words"},null],"c-4":["\n",{"->":"explain_objectives"},null],"c-5":["^ ","\n","^See you around!","\n","#","^exit_conversation","/#",{"->":"hub"},null]}],null],"first_meeting":["^Great to meet you too! This task is now complete.","\n","#","^complete_task:talk_to_alice","/#","ev",true,"/ev",{"VAR=":"alice_talked","re":true},"^You should go talk to Bob next - I just unlocked that task for you.","\n",{"->":"hub"},null],"explain_objectives":["^The objectives system uses three Ink tags:","\n",{"->":"explain_objectives_detail"},null],"explain_objectives_detail":[["ev","str","^Tell me about complete_task","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Tell me about unlock_task","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^Tell me about unlock_aim","/str","/ev",{"*":".^.c-2","flg":4},"ev","str","^That's enough info, thanks","/str","/ev",{"*":".^.c-3","flg":4},{"c-0":["\n","^NPC: **complete_task:task_id** marks a task as completed. The ObjectivesManager will update the UI and sync with the server.","\n",{"->":".^.^.^"},null],"c-1":["\n","^NPC: **unlock_task:task_id** unlocks a locked task so it becomes active and visible.","\n",{"->":".^.^.^"},null],"c-2":["\n","^NPC: **unlock_aim:aim_id** unlocks an entire aim (objective group) that was previously locked.","\n",{"->":".^.^.^"},null],"c-3":["\n","^NPC: These tags let NPCs control the player's objectives through dialogue!","\n",{"->":"hub"},null]}],null],"reveal_secret":["^Alright, I'll let you in on a secret...","\n","^There's a hidden mission that only unlocks through dialogue!","\n","#","^unlock_aim:secret_mission","/#","ev",true,"/ev",{"VAR=":"secret_revealed","re":true},"^I've just unlocked the \"Secret Mission\" aim for you. Check your objectives!","\n","^But the tasks inside are still locked. Let me unlock the first one...","\n","#","^unlock_task:secret_task_1","/#","^There! Now you can complete the first secret task.","\n",{"->":"hub"},null],"complete_secret_task":["^Excellent! You're doing great with the secret mission.","\n","#","^complete_task:secret_task_1","/#","ev",true,"/ev",{"VAR=":"secret_task_done","re":true},"^That's one secret task down! Bob can help you with the second one.","\n",{"->":"hub"},null],"final_words":["^You've done great! Once Bob helps you finish, come back for the final debrief.","\n",{"->":"hub"},null],"global decl":["ev",false,{"VAR=":"alice_talked"},false,{"VAR=":"secret_revealed"},false,{"VAR=":"secret_task_done"},"/ev","end",null]}],"listDefs":{}}
|
||||
62
scenarios/test_objectives_ink/bob.ink
Normal file
62
scenarios/test_objectives_ink/bob.ink
Normal file
@@ -0,0 +1,62 @@
|
||||
// bob.ink
|
||||
// Demonstrates objective Ink tags for the second NPC
|
||||
// Works in conjunction with alice.ink to show task chaining
|
||||
//
|
||||
// IMPORTANT: Uses mission hub pattern - never uses -> END
|
||||
// Instead uses #exit_conversation tag to leave the chat
|
||||
|
||||
VAR bob_talked = false
|
||||
VAR helped_with_secret = false
|
||||
VAR secret_revealed = false // from alice.ink
|
||||
|
||||
=== start ===
|
||||
*cough* Oh, hey. I'm Bob. Didn't see you there.
|
||||
-> hub
|
||||
|
||||
=== hub ===
|
||||
+ {not bob_talked} [Alice sent me to talk to you]
|
||||
-> first_meeting
|
||||
|
||||
+ {bob_talked and secret_revealed and not helped_with_secret} [Can you help with the secret task?]
|
||||
-> secret_task_help
|
||||
|
||||
+ {helped_with_secret} [Thanks for the help!]
|
||||
-> thanks_response
|
||||
|
||||
+ [What do you do here?]
|
||||
-> about_bob
|
||||
|
||||
+ [Goodbye]
|
||||
Later.
|
||||
#exit_conversation
|
||||
-> hub
|
||||
|
||||
=== first_meeting ===
|
||||
Ah, Alice sent you? Good, good.
|
||||
#complete_task:talk_to_bob
|
||||
~ bob_talked = true
|
||||
I've just marked that task complete.
|
||||
If Alice told you about anything... special... come back and ask me.
|
||||
-> hub
|
||||
|
||||
=== about_bob ===
|
||||
I handle the technical side of things.
|
||||
Mostly just unlocking things that need to be unlocked.
|
||||
Speaking of which... if there are any locked tasks you need help with, just ask.
|
||||
-> hub
|
||||
|
||||
=== secret_task_help ===
|
||||
The secret task, eh? Let me help you with that.
|
||||
#unlock_task:secret_task_2
|
||||
#complete_task:secret_task_2
|
||||
~ helped_with_secret = true
|
||||
Done! Both secret tasks are now complete.
|
||||
That means the secret aim should be finished too.
|
||||
#unlock_aim:finale
|
||||
#unlock_task:final_debrief
|
||||
I've also unlocked the finale for you. Go talk to Alice for the final debrief!
|
||||
-> hub
|
||||
|
||||
=== thanks_response ===
|
||||
No problem! Go see Alice for the final debrief. She's waiting for you.
|
||||
-> hub
|
||||
1
scenarios/test_objectives_ink/bob.json
Normal file
1
scenarios/test_objectives_ink/bob.json
Normal file
@@ -0,0 +1 @@
|
||||
{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":[[["ev",{"^->":"start.0.0.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":".^.^.c-0","flg":18},{"s":["^cough* Oh, hey. I'm Bob. Didn't see you there.",{"->":"$r","var":true},null]}],{"c-0":["ev",{"^->":"start.0.c-0.$r2"},"/ev",{"temp=":"$r"},{"->":".^.^.0.s"},[{"#n":"$r2"}],"\n",{"->":"hub"},{"#f":5}]}],null],"hub":[["ev","str","^Alice sent me to talk to you","/str",{"VAR?":"bob_talked"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^Can you help with the secret task?","/str",{"VAR?":"bob_talked"},{"VAR?":"secret_revealed"},"&&",{"VAR?":"helped_with_secret"},"!","&&","/ev",{"*":".^.c-1","flg":5},"ev","str","^Thanks for the help!","/str",{"VAR?":"helped_with_secret"},"/ev",{"*":".^.c-2","flg":5},"ev","str","^What do you do here?","/str","/ev",{"*":".^.c-3","flg":4},"ev","str","^Goodbye","/str","/ev",{"*":".^.c-4","flg":4},{"c-0":["\n",{"->":"first_meeting"},null],"c-1":["\n",{"->":"secret_task_help"},null],"c-2":["\n",{"->":"thanks_response"},null],"c-3":["\n",{"->":"about_bob"},null],"c-4":["\n","^Later.","\n","#","^exit_conversation","/#",{"->":"hub"},null]}],null],"first_meeting":["^Ah, Alice sent you? Good, good.","\n","#","^complete_task:talk_to_bob","/#","ev",true,"/ev",{"VAR=":"bob_talked","re":true},"^I've just marked that task complete.","\n","^If Alice told you about anything... special... come back and ask me.","\n",{"->":"hub"},null],"about_bob":["^I handle the technical side of things.","\n","^Mostly just unlocking things that need to be unlocked.","\n","^Speaking of which... if there are any locked tasks you need help with, just ask.","\n",{"->":"hub"},null],"secret_task_help":["^The secret task, eh? Let me help you with that.","\n","#","^unlock_task:secret_task_2","/#","#","^complete_task:secret_task_2","/#","ev",true,"/ev",{"VAR=":"helped_with_secret","re":true},"^Done! Both secret tasks are now complete.","\n","^That means the secret aim should be finished too.","\n","#","^unlock_aim:finale","/#","#","^unlock_task:final_debrief","/#","^I've also unlocked the finale for you. Go talk to Alice for the final debrief!","\n",{"->":"hub"},null],"thanks_response":["^No problem! Go see Alice for the final debrief. She's waiting for you.","\n",{"->":"hub"},null],"global decl":["ev",false,{"VAR=":"bob_talked"},false,{"VAR=":"helped_with_secret"},false,{"VAR=":"secret_revealed"},"/ev","end",null]}],"listDefs":{}}
|
||||
7
scenarios/test_objectives_ink/mission.json
Normal file
7
scenarios/test_objectives_ink/mission.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"display_name": "Test Objectives (Ink Tags)",
|
||||
"description": "Demonstrates objectives controlled via Ink dialogue tags: complete_task, unlock_task, and unlock_aim.",
|
||||
"difficulty_level": 1,
|
||||
"secgen_scenario": null,
|
||||
"collection": "testing"
|
||||
}
|
||||
136
scenarios/test_objectives_ink/scenario.json.erb
Normal file
136
scenarios/test_objectives_ink/scenario.json.erb
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"scenario_brief": "Test scenario demonstrating Ink-based objective control. Talk to NPCs to complete, unlock, and progress objectives through dialogue.",
|
||||
"endGoal": "Complete all objectives by talking to the NPCs",
|
||||
"version": "1.0",
|
||||
"startRoom": "lobby",
|
||||
"startItemsInInventory": [
|
||||
{
|
||||
"type": "notepad",
|
||||
"name": "Notepad",
|
||||
"takeable": true,
|
||||
"readable": true,
|
||||
"text": "Use this notepad to review your collected notes and observations.",
|
||||
"observations": "A handy notepad for keeping track of important information."
|
||||
}
|
||||
],
|
||||
"objectives": [
|
||||
{
|
||||
"aimId": "meet_contacts",
|
||||
"title": "Meet Your Contacts",
|
||||
"description": "Talk to the NPCs in the lobby to get started",
|
||||
"status": "active",
|
||||
"order": 0,
|
||||
"tasks": [
|
||||
{
|
||||
"taskId": "talk_to_alice",
|
||||
"title": "Talk to Alice",
|
||||
"type": "npc_conversation",
|
||||
"targetNPC": "alice",
|
||||
"status": "active",
|
||||
"onComplete": {
|
||||
"unlockTask": "talk_to_bob"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "talk_to_bob",
|
||||
"title": "Talk to Bob",
|
||||
"type": "npc_conversation",
|
||||
"targetNPC": "bob",
|
||||
"status": "locked"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aimId": "secret_mission",
|
||||
"title": "The Secret Mission",
|
||||
"description": "A hidden aim that gets unlocked via Ink dialogue",
|
||||
"status": "locked",
|
||||
"order": 1,
|
||||
"tasks": [
|
||||
{
|
||||
"taskId": "secret_task_1",
|
||||
"title": "Complete the first secret task",
|
||||
"type": "npc_conversation",
|
||||
"targetNPC": "alice",
|
||||
"status": "locked"
|
||||
},
|
||||
{
|
||||
"taskId": "secret_task_2",
|
||||
"title": "Complete the second secret task",
|
||||
"type": "npc_conversation",
|
||||
"targetNPC": "bob",
|
||||
"status": "locked"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aimId": "finale",
|
||||
"title": "Mission Complete",
|
||||
"description": "Finish up with your contacts",
|
||||
"status": "locked",
|
||||
"order": 2,
|
||||
"tasks": [
|
||||
{
|
||||
"taskId": "final_debrief",
|
||||
"title": "Get the final debrief",
|
||||
"type": "npc_conversation",
|
||||
"targetNPC": "alice",
|
||||
"status": "locked"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"globalVariables": {
|
||||
"alice_talked": false,
|
||||
"bob_talked": false,
|
||||
"secret_unlocked": false
|
||||
},
|
||||
"rooms": {
|
||||
"lobby": {
|
||||
"type": "room_reception",
|
||||
"connections": {},
|
||||
"objects": [],
|
||||
"npcs": [
|
||||
{
|
||||
"id": "alice",
|
||||
"displayName": "Alice",
|
||||
"npcType": "person",
|
||||
"position": { "x": 3, "y": 5 },
|
||||
"spriteSheet": "hacker",
|
||||
"spriteTalk": "assets/characters/hacker-talk.png",
|
||||
"spriteConfig": {
|
||||
"idleFrameStart": 20,
|
||||
"idleFrameEnd": 23
|
||||
},
|
||||
"storyPath": "scenarios/test_objectives_ink/alice.json",
|
||||
"currentKnot": "start",
|
||||
"avatar": "assets/npc/avatars/npc_alice.png",
|
||||
"externalVariables": {},
|
||||
"persistentVariables": {
|
||||
"alice_talked": false,
|
||||
"secret_revealed": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bob",
|
||||
"displayName": "Bob",
|
||||
"npcType": "person",
|
||||
"position": { "x": 8, "y": 5 },
|
||||
"spriteSheet": "hacker-red",
|
||||
"spriteTalk": "assets/characters/hacker-red-talk.png",
|
||||
"spriteConfig": {
|
||||
"idleFrameStart": 20,
|
||||
"idleFrameEnd": 23
|
||||
},
|
||||
"storyPath": "scenarios/test_objectives_ink/bob.json",
|
||||
"currentKnot": "start",
|
||||
"avatar": "assets/npc/avatars/npc_bob.png",
|
||||
"externalVariables": {},
|
||||
"persistentVariables": {
|
||||
"bob_talked": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user