NPCs can take quests!

This commit is contained in:
2025-08-07 01:02:48 -04:00
parent 6bfbd4b70c
commit fb312401cd
14 changed files with 133 additions and 76 deletions

View File

@@ -1,4 +1,4 @@
[gd_scene load_steps=8 format=3 uid="uid://dfa6ep4o53s08"]
[gd_scene load_steps=7 format=3 uid="uid://dfa6ep4o53s08"]
[ext_resource type="Script" uid="uid://cci652umkym1f" path="res://test_scene.gd" id="1_8p2cu"]
[ext_resource type="PackedScene" uid="uid://cd08dp16bixfv" path="res://guildhall.tscn" id="1_fcxuj"]
@@ -14,10 +14,6 @@ cell_size = 50.0
border_size = 250.0
agent_radius = 35.0
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_d0hfk"]
radius = 15.0
height = 54.0
[node name="Active Scene" type="Node2D"]
script = ExtResource("1_8p2cu")
@@ -33,21 +29,9 @@ position = Vector2(512, 82)
[node name="Queue" parent="Guildhall/Sprites/Receptionist" index="3"]
position = Vector2(-99, 189)
[node name="CollisionShape2D" parent="Guildhall/Sprites/CharacterBody2D" index="0"]
shape = SubResource("CapsuleShape2D_d0hfk")
[node name="Sprite2D" parent="Guildhall/Sprites/CharacterBody2D" index="1"]
position = Vector2(0, -43)
[node name="Quest Board" parent="Guildhall" index="3"]
position = Vector2(927, 33)
[node name="CollisionShape2D" parent="Guildhall/Quest Board" index="1"]
position = Vector2(0, -14)
[node name="Queue" parent="Guildhall/Quest Board" index="2"]
position = Vector2(2, 44)
[node name="VisitorSpawner" type="Node2D" parent="Guildhall"]
position = Vector2(505, 870)
script = ExtResource("6_d0hfk")
@@ -58,6 +42,7 @@ total_visitors = 3
[node name="UI" type="CanvasLayer" parent="."]
[node name="VBoxContainer" type="VBoxContainer" parent="UI"]
anchors_preset = -1
offset_left = 1567.0
offset_top = 23.0
offset_right = 1903.0

View File

@@ -58,6 +58,8 @@ func set_movement_target(target : Vector2) -> void:
func show_speech_bubble(bubble_type : String) -> void:
bubble.try_show_speech(bubble_type)
func _on_nav_agent_finished() -> void:
navigation_finished.emit()

View File

@@ -20,3 +20,6 @@ var quest : Quest
func assign_quest(quest : Quest) -> void:
self.quest = quest
quest.initiate(self)
func full_name() -> String:
return given_name + " " + surname

View File

@@ -12,7 +12,7 @@ enum Phases {
ARRIVE,
QUEUE,
WAIT,
SERVICE,
INTERACT,
COMPLETE
}
@@ -37,7 +37,7 @@ func _generate_name() -> String:
func _enter() -> void:
var eq = Guild.hall.interactables.get(equipment_name)
if !eq:
printerr("Use Guild Equipment (%s) - %s, '%s' not found!", equipment_name, service_name, equipment_name)
printerr("Use Guild Equipment (%s) - %s, '%s' not found!" % [equipment_name, service_name, equipment_name])
return
equipment = eq
queue = equipment.queue
@@ -61,11 +61,11 @@ func _tick(delta: float) -> Status:
pass
Phases.WAIT:
pass
Phases.SERVICE:
Phases.INTERACT:
if wait_time_remaining > 0:
wait_time_remaining -= delta
if wait_time_remaining <= 0:
equipment.service_provided.connect(_on_service_complete)
equipment.interaction_complete.connect(_on_interaction_complete)
equipment.interact(agent, service_name)
Phases.COMPLETE:
return SUCCESS
@@ -81,10 +81,11 @@ func _on_navigation_failed() -> void:
wait_time_remaining = randf_range(.5, 2)
agent.navigation_finished.disconnect(_on_navigation_complete)
func use_service():
phase = Phases.SERVICE
func interact():
phase = Phases.INTERACT
wait_time_remaining = randf_range(2,5)
#TODO: Make them both do the talking emoji
equipment.busy = true
agent.show_speech_bubble("busy")
func wait():
@@ -98,12 +99,14 @@ func _on_queue_advanced() -> void:
if equipment.busy:
wait()
else:
use_service()
interact()
pass
func _on_service_complete() -> void:
equipment.service_provided.disconnect(_on_service_complete)
func _on_interaction_complete() -> void:
equipment.interaction_complete.disconnect(_on_interaction_complete)
equipment.busy = false
agent.show_speech_bubble("")
queue.remove_member(agent)
phase = Phases.COMPLETE

View File

@@ -1,5 +1,5 @@
extends BTCondition
var invert : bool
@export var invert : bool
func _tick(delta: float) -> Status:
if agent.data and ((agent.data.quest == null) == invert):
return SUCCESS

View File

@@ -1,4 +1,4 @@
[gd_resource type="BehaviorTree" load_steps=42 format=3 uid="uid://dght2flegv70i"]
[gd_resource type="BehaviorTree" load_steps=43 format=3 uid="uid://dght2flegv70i"]
[ext_resource type="Script" uid="uid://h113xg55h4r8" path="res://ai/tasks/actions/go_to.gd" id="1_s3kkm"]
[ext_resource type="Script" uid="uid://bsq5dxul0uto" path="res://ai/tasks/actions/use_guild_service.gd" id="2_1441p"]
@@ -6,6 +6,7 @@
[ext_resource type="Script" uid="uid://b2vuw12mttm40" path="res://ai/tasks/decorators/busy.gd" id="2_mtixs"]
[ext_resource type="Script" uid="uid://bcbfnm21rtkuo" path="res://ai/tasks/conditions/is_unregistered.gd" id="3_mtixs"]
[ext_resource type="Script" uid="uid://xom38ohdwfms" path="res://ai/tasks/conditions/has_quest.gd" id="4_1441p"]
[ext_resource type="Script" uid="uid://bhatmue8jr2ab" path="res://ai/tasks/actions/use_guild_equipment.gd" id="7_nqy1p"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_6h604"]
var/pos/name = &"pos"
@@ -77,18 +78,19 @@ activity = "Registering as an adventurer"
[sub_resource type="BTCondition" id="BTCondition_mtixs"]
script = ExtResource("4_1441p")
invert = true
[sub_resource type="BTAction" id="BTAction_fe6jf"]
script = ExtResource("2_1441p")
employee_name = "Questboard"
service_name = "get_quest"
[sub_resource type="BTAction" id="BTAction_8lwgx"]
script = ExtResource("7_nqy1p")
equipment_name = "Quest Board"
service_name = "quest"
[sub_resource type="BTAction" id="BTAction_mwsop"]
script = ExtResource("2_fe6jf")
[sub_resource type="BTSequence" id="BTSequence_nqy1p"]
custom_name = "Get a Quest"
children = [SubResource("BTCondition_mtixs"), SubResource("BTAction_fe6jf"), SubResource("BTAction_mwsop")]
children = [SubResource("BTCondition_mtixs"), SubResource("BTAction_8lwgx"), SubResource("BTAction_mwsop")]
[sub_resource type="BTDecorator" id="BTDecorator_s18yy"]
children = [SubResource("BTSequence_nqy1p")]
@@ -120,12 +122,14 @@ custom_name = "Idle"
[sub_resource type="BTProbability" id="BTProbability_gc1l4"]
children = [SubResource("BTWait_8lwgx")]
_enabled = false
[sub_resource type="BTAction" id="BTAction_jq6fo"]
script = ExtResource("2_fe6jf")
[sub_resource type="BTProbability" id="BTProbability_8lwgx"]
children = [SubResource("BTAction_jq6fo")]
_enabled = false
[sub_resource type="BTProbabilitySelector" id="BTProbabilitySelector_mtixs"]
children = [SubResource("BTProbability_s3kkm"), SubResource("BTProbability_1441p"), SubResource("BTProbability_gc1l4"), SubResource("BTProbability_8lwgx")]

34
ai/trees/quest_board.tres Normal file
View File

@@ -0,0 +1,34 @@
[gd_resource type="BehaviorTree" load_steps=9 format=3 uid="uid://tr5enbq48w6x"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_7h63t"]
[sub_resource type="BBVariant" id="BBVariant_7h63t"]
type = 1
saved_value = false
resource_name = "false"
[sub_resource type="BTCheckAgentProperty" id="BTCheckAgentProperty_un3nm"]
property = &"busy"
value = SubResource("BBVariant_7h63t")
[sub_resource type="BBNode" id="BBNode_3iu7m"]
saved_value = NodePath("Queue")
resource_name = "Queue"
[sub_resource type="BTCallMethod" id="BTCallMethod_8wdsa"]
node = SubResource("BBNode_3iu7m")
method = &"try_advance"
[sub_resource type="BTSequence" id="BTSequence_26pbt"]
children = [SubResource("BTCheckAgentProperty_un3nm"), SubResource("BTCallMethod_8wdsa")]
[sub_resource type="BTCooldown" id="BTCooldown_3glab"]
duration = 2.0
children = [SubResource("BTSequence_26pbt")]
[sub_resource type="BTRepeat" id="BTRepeat_hcvsp"]
children = [SubResource("BTCooldown_3glab")]
[resource]
blackboard_plan = SubResource("BlackboardPlan_7h63t")
root_task = SubResource("BTRepeat_hcvsp")

View File

@@ -10,7 +10,6 @@ func generate_quest() -> void:
quest.location = "Nestor Woods"
quest.difficulty = 1
quest.rewards = {"exp":100,"gold":1}
func update_quest_window() -> void:
if quest:
@@ -34,6 +33,9 @@ func _on_generate_button_pressed() -> void:
func _on_post_button_pressed() -> void:
Guild.add_quest(quest)
quest = null
%GenerateQuest.show()
%PostQuest.hide()
hide()
func _on_post_back_button_pressed() -> void:

View File

@@ -1,4 +1,4 @@
[gd_scene load_steps=14 format=4 uid="uid://cd08dp16bixfv"]
[gd_scene load_steps=11 format=4 uid="uid://cd08dp16bixfv"]
[ext_resource type="Script" uid="uid://ccorfvcfa84gf" path="res://guildhall.gd" id="1_lsinl"]
[ext_resource type="TileSet" uid="uid://6im0g3eg6sr4" path="res://test_tiles.tres" id="1_qel1r"]
@@ -6,9 +6,7 @@
[ext_resource type="Texture2D" uid="uid://83ayd5rg7x8l" path="res://reception-table.png" id="3_13vc8"]
[ext_resource type="Texture2D" uid="uid://cg6ptmynq0aq0" path="res://basic-sprite.png" id="4_l3mu1"]
[ext_resource type="PackedScene" uid="uid://cf6nnjyp8kv78" path="res://receptionist.tscn" id="5_l3mu1"]
[ext_resource type="Texture2D" uid="uid://bnt2dlv7kxw7s" path="res://questboard.png" id="6_2wofw"]
[ext_resource type="Script" uid="uid://b0q2233msdtgo" path="res://guild_queue.gd" id="7_hph4e"]
[ext_resource type="PackedScene" uid="uid://drrtypncppjps" path="res://quest_board_window.tscn" id="8_uo85v"]
[ext_resource type="PackedScene" uid="uid://b3mksvn2jf7e0" path="res://quest_board.tscn" id="7_hph4e"]
[sub_resource type="NavigationPolygon" id="NavigationPolygon_w7eqs"]
vertices = PackedVector2Array(591.3203, 170.54688, 591.3125, 170.53906, 614.1094, 159.47656, 694.9531, 80.92969, 694.9375, 80.921875, 702.6406, 67, 1053, 67, 1053, 605, 665.27344, 134.64063, 35, 605, 578.35156, 176.85938, 512.60156, 177.99219, 445.89844, 179.14063, 35, 35, 432.14063, 172.14844, 432.14063, 172.14063, 406.6172, 159.21875, 368.17188, 139.71875, 368.17188, 139.71094, 357.59375, 134.42969, 326.3125, 77.5625, 324.82813, 35, 325.8828, 65.21875)
@@ -26,18 +24,6 @@ vertices = PackedVector2Array(117.96875, 286, 9.96875, 286, 9.03125, 0, 117.0312
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3)])
outlines = Array[PackedVector2Array]([PackedVector2Array(-1, -10, 127, -10, 128, 296, 0, 296)])
[sub_resource type="GDScript" id="GDScript_bog1h"]
script/source = "class_name QuestBoard extends Interactable
signal service_provided()
func _ready() -> void:
queue = $Queue
func interact(interactor, service : String):
service_provided.emit()
"
[node name="Guildhall" type="Node2D"]
script = ExtResource("1_lsinl")
@@ -72,19 +58,19 @@ position = Vector2(512, 29)
[node name="Queue" parent="Sprites/Receptionist" index="3"]
position = Vector2(0, 220)
[node name="CharacterBody2D" type="CharacterBody2D" parent="Sprites"]
[node name="Player" type="CharacterBody2D" parent="Sprites"]
position = Vector2(202, 389)
script = ExtResource("2_5n4iw")
[node name="CollisionShape2D" type="CollisionShape2D" parent="Sprites/CharacterBody2D"]
[node name="CollisionShape2D" type="CollisionShape2D" parent="Sprites/Player"]
rotation = 1.5707964
shape = SubResource("CapsuleShape2D_l3mu1")
[node name="Sprite2D" type="Sprite2D" parent="Sprites/CharacterBody2D"]
[node name="Sprite2D" type="Sprite2D" parent="Sprites/Player"]
position = Vector2(0, -64)
texture = ExtResource("4_l3mu1")
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="Sprites/CharacterBody2D"]
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="Sprites/Player"]
path_desired_distance = 30.0
avoidance_enabled = true
@@ -92,24 +78,7 @@ avoidance_enabled = true
position = Vector2(448, 600)
navigation_polygon = SubResource("NavigationPolygon_l3mu1")
[node name="Quest Board" type="StaticBody2D" parent="."]
[node name="Quest Board" parent="." instance=ExtResource("7_hph4e")]
position = Vector2(935, 32)
script = SubResource("GDScript_bog1h")
[node name="QuestBoardWindow" parent="Quest Board" instance=ExtResource("8_uo85v")]
[node name="Sprite2D" type="Sprite2D" parent="Quest Board"]
position = Vector2(0, -64)
texture = ExtResource("6_2wofw")
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Quest Board"]
position = Vector2(-1, 11)
rotation = 1.5707964
polygon = PackedVector2Array(-39.999996, 128, -26.999994, 139, -3.9999943, 139, 12.000006, 129, 13.999994, -127, -7.6293945e-06, -140, -30.000008, -140, -43.000008, -126)
[node name="Queue" type="Node2D" parent="Quest Board"]
position = Vector2(-12, 44)
script = ExtResource("7_hph4e")
direction = Vector2(0, 1)
[editable path="Sprites/Receptionist"]

View File

@@ -3,10 +3,10 @@ class_name Interactable extends StaticBody2D
var busy : bool = false
var queue : GuildQueue
signal service_provided()
signal interaction_complete()
func _ready() -> void:
queue = $Queue
func interact(interactor, service : String = ""):
service_provided.emit()
interaction_complete.emit()

View File

@@ -54,6 +54,9 @@ func approach(pos : Vector2) -> void:
set_movement_target(point)
func approach_and_interact(obj : Interactable) -> void:
var t : Vector2 = obj.global_position
if obj.has("queue") and obj.queue != null:
t = obj.queue.global_position
set_movement_target(obj.global_position)
nav_agent.target_desired_distance = interaction_range - 5
interaction_target = obj

View File

@@ -28,11 +28,18 @@ var taken : bool = false
var questor : AdventurerData = null
signal status_changed(status : Status)
func _init() -> void:
print("TEST!")
func initiate(member : AdventurerData) -> void:
questor = member
taken = true
status_changed.emit(Status.TAKEN)
#TODO: Put in quest requirements
func is_eligible(member : AdventurerData) -> bool:
return !taken
func difficulty_name() -> String:
match(difficulty):
0: return "None"

View File

@@ -3,11 +3,12 @@ class_name QuestBoard extends Interactable
@onready var polygon : CollisionPolygon2D = $CollisionPolygon2D
@onready var window : QuestBoardWindow = $QuestBoardWindow
@onready var btplayer : BTPlayer = $BTPlayer
signal interaction_complete()
func _ready() -> void:
register_board.call_deferred()
super._ready()
func register_board() -> void:
Guild.hall.register_interactables(self)
@@ -25,7 +26,19 @@ func interact(interactor, type : String = "") -> void:
window.populate(Guild.quests.keys())
window.popup_centered()
elif type == "quest":
var viable_quests : Dictionary[Quest,int] = {}
#Go through all quests and create a list of open quests suitable for their level
for quest in Guild.quests:
if quest.is_eligible(interactor.data):
#TODO: Make them weight different quests differently.
viable_quests[quest] = 1
#If that list is zero, return without giving them a quest
if viable_quests.size() != 0:
var rnd_quest = []
for quest in viable_quests.keys():
for i in range(viable_quests[quest]):
rnd_quest.append(quest)
var quest = rnd_quest.pick_random()
Guild.assign_quest(interactor.data, quest)
interaction_complete.emit()
#Else pick a random quest from the list and assign it to them
pass

32
quest_board.tscn Normal file
View File

@@ -0,0 +1,32 @@
[gd_scene load_steps=7 format=3 uid="uid://b3mksvn2jf7e0"]
[ext_resource type="PackedScene" uid="uid://drrtypncppjps" path="res://quest_board_window.tscn" id="1_4poi3"]
[ext_resource type="Script" uid="uid://blo7tb5135vfm" path="res://quest_board.gd" id="1_38mwx"]
[ext_resource type="Texture2D" uid="uid://bnt2dlv7kxw7s" path="res://questboard.png" id="2_3ceph"]
[ext_resource type="Script" uid="uid://b0q2233msdtgo" path="res://guild_queue.gd" id="3_38mwx"]
[ext_resource type="BehaviorTree" uid="uid://tr5enbq48w6x" path="res://ai/trees/quest_board.tres" id="4_3ceph"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_2xphb"]
[node name="Quest Board" type="StaticBody2D"]
script = ExtResource("1_38mwx")
[node name="QuestBoardWindow" parent="." instance=ExtResource("1_4poi3")]
[node name="Sprite2D" type="Sprite2D" parent="."]
position = Vector2(0, -64)
texture = ExtResource("2_3ceph")
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="."]
position = Vector2(-1, 11)
rotation = 1.5707964
polygon = PackedVector2Array(-39.999996, 128, -26.999994, 139, -3.9999943, 139, 12.000006, 129, 13.999994, -127, -7.6293945e-06, -140, -30.000008, -140, -43.000008, -126)
[node name="Queue" type="Node2D" parent="."]
position = Vector2(-12, 44)
script = ExtResource("3_38mwx")
direction = Vector2(0, 1)
[node name="BTPlayer" type="BTPlayer" parent="."]
behavior_tree = ExtResource("4_3ceph")
blackboard_plan = SubResource("BlackboardPlan_2xphb")