New art assets, work on implementing more adventurer behavior and making them work better together, and steps towards completing the first quest loop.

This commit is contained in:
2025-07-31 08:44:26 -04:00
parent c0a2c058ba
commit 38a7ed85b0
66 changed files with 1112 additions and 658 deletions

BIN
ai/icons/stopwatch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ho4mpdraykbv"
path="res://.godot/imported/stopwatch.png-693f4af9e4bcbeda164482669808a0d8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ai/icons/stopwatch.png"
dest_files=["res://.godot/imported/stopwatch.png-693f4af9e4bcbeda164482669808a0d8.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@@ -0,0 +1,103 @@
#*
#* use_guild_service.gd
#*
@tool
extends BTAction
## Moves the agent to the specified position, favoring horizontal movement. [br]
## Returns [code]SUCCESS[/code] when close to the target position (see [member tolerance]);
## otherwise returns [code]RUNNING[/code].
enum Phases {
ARRIVE,
QUEUE,
WAIT,
OBTAIN,
COMPLETE
}
var board : QuestBoard
var queue : GuildQueue
var wait_time_remaining : float = 0
var phase : Phases
func _generate_name() -> String:
return "Get a Quest"
func _enter() -> void:
var brd = Guild.hall.board
if !brd:
printerr("Get a Quest: Board not found!")
return
board = brd
queue = board.queue
phase = Phases.ARRIVE
queue.add_member(agent)
agent.approach(queue.get_last_position())
agent.navigation_finished.connect(_on_navigation_complete)
agent.navigation_failed.connect(_on_navigation_failed)
func _tick(delta: float) -> Status:
if board == null:
return FAILURE
match(phase):
Phases.ARRIVE:
if wait_time_remaining > 0:
wait_time_remaining -= delta
if wait_time_remaining <= 0:
agent.navigation_finished.connect(_on_navigation_complete)
agent.approach(queue.get_member_position(agent))
Phases.QUEUE:
pass
Phases.WAIT:
pass
Phases.OBTAIN:
if wait_time_remaining:
wait_time_remaining -= delta
if wait_time_remaining <= 0:
board.interaction_complete.connect(_on_interaction_complete)
board.interact(agent, "quest")
phase = Phases.OBTAIN
Phases.COMPLETE:
return SUCCESS
return RUNNING
func _on_navigation_complete() -> void:
agent.navigation_finished.disconnect(_on_navigation_complete)
agent.navigation_failed.disconnect(_on_navigation_failed)
queue.advanced.connect(_on_queue_advanced)
phase = Phases.QUEUE
func _on_navigation_failed() -> void:
wait_time_remaining = randf_range(.5, 2)
agent.navigation_finished.disconnect(_on_navigation_complete)
func get_quest():
phase = Phases.OBTAIN
wait_time_remaining = randf_range(2,5)
agent.show_speech_bubble("busy")
func wait():
wait_time_remaining = 1
phase = Phases.WAIT
func _on_queue_advanced() -> void:
if queue.front == agent:
queue.advanced.disconnect(_on_queue_advanced)
if board.busy:
wait()
else:
get_quest()
pass
func _on_interaction_complete() -> void:
board.interaction_complete.disconnect(_on_interaction_complete)
if agent.data.quest != null:
agent.show_speech_bubble("happy", 1.5)
else:
agent.show_speech_bubble("angry", 1.5)
queue.remove_member(agent)
phase = Phases.COMPLETE

View File

@@ -0,0 +1 @@
uid://ders6t7axct24

48
ai/tasks/actions/go_to.gd Normal file
View File

@@ -0,0 +1,48 @@
#*
#* go_to.gd
#*
@tool
extends BTAction
## Moves the agent to the specified position, favoring horizontal movement. [br]
## Returns [code]SUCCESS[/code] when close to the target position (see [member tolerance]);
## otherwise returns [code]RUNNING[/code].
## Blackboard variable that stores the target position (Vector2)
@export var target_position_var := &"pos"
var wait_time_remaining : float = 0
var goal_position : Vector2
var done : bool
func _generate_name() -> String:
return "Go to Position: %s" % [LimboUtility.decorate_var(target_position_var)]
func _enter() -> void:
done = false
goal_position = blackboard.get_var(target_position_var, Vector2.ZERO)
go_to(goal_position)
func _tick(delta: float) -> Status:
if done:
return SUCCESS
#If we were interrupted, wait a little bit and try again
if wait_time_remaining > 0:
wait_time_remaining -= delta
if wait_time_remaining <= 0:
go_to(goal_position)
return RUNNING
func go_to(pos : Vector2) -> void:
agent.navigation_finished.connect(_on_navigation_complete)
agent.navigation_failed.connect(_on_navigation_failed)
agent.approach(pos)
func _on_navigation_complete() -> void:
agent.navigation_finished.disconnect(_on_navigation_complete)
agent.navigation_failed.disconnect(_on_navigation_failed)
done = true
func _on_navigation_failed() -> void:
wait_time_remaining = randf_range(.5, 2)
agent.navigation_finished.disconnect(_on_navigation_complete)
agent.navigation_failed.disconnect(_on_navigation_failed)

View File

@@ -0,0 +1 @@
uid://h113xg55h4r8

View File

@@ -0,0 +1,111 @@
#*
#* use_guild_service.gd
#*
@tool
extends BTAction
## Moves the agent to the specified position, favoring horizontal movement. [br]
## Returns [code]SUCCESS[/code] when close to the target position (see [member tolerance]);
## otherwise returns [code]RUNNING[/code].
enum Phases {
ARRIVE,
QUEUE,
WAIT,
SERVICE,
COMPLETE
}
## Blackboard variable that stores the target position (Vector2)
@export var employee_name : String = ""
## Variable that stores desired speed (float)
@export var service_name : String = ""
var employee : GuildEmployee
var queue : GuildQueue
var wait_time_remaining : float = 0
var phase : Phases
func _generate_name() -> String:
return "Use Guild Service (%s) - %s" % [
employee_name,
service_name
]
func _enter() -> void:
var emp = Guild.hall.employees.get(employee_name)
if !emp:
printerr("Use Guild Service (%s) - %s, '%s' not found!", employee_name, service_name, employee_name)
return
employee = emp
queue = employee.queue
phase = Phases.ARRIVE
queue.add_member(agent)
agent.approach(queue.get_last_position())
agent.navigation_finished.connect(_on_navigation_complete)
agent.navigation_failed.connect(_on_navigation_failed)
func _tick(delta: float) -> Status:
if employee == null:
return FAILURE
match(phase):
Phases.ARRIVE:
if wait_time_remaining > 0:
wait_time_remaining -= delta
if wait_time_remaining <= 0:
agent.navigation_finished.connect(_on_navigation_complete)
agent.approach(queue.get_member_position(agent))
Phases.QUEUE:
pass
Phases.WAIT:
pass
Phases.SERVICE:
if wait_time_remaining > 0:
wait_time_remaining -= delta
if wait_time_remaining <= 0:
employee.service_provided.connect(_on_service_complete)
employee.interact(agent, service_name)
Phases.COMPLETE:
return SUCCESS
return RUNNING
func _on_navigation_complete() -> void:
agent.navigation_finished.disconnect(_on_navigation_complete)
agent.navigation_failed.disconnect(_on_navigation_failed)
queue.advanced.connect(_on_queue_advanced)
phase = Phases.QUEUE
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
wait_time_remaining = randf_range(2,5)
#TODO: Make them both do the talking emoji
agent.show_speech_bubble("talk")
employee.show_speech_bubble("talk")
func wait():
wait_time_remaining = 1
phase = Phases.WAIT
func _on_queue_advanced() -> void:
if queue.front == agent:
queue.advanced.disconnect(_on_queue_advanced)
if employee.busy:
wait()
else:
use_service()
pass
func _on_service_complete() -> void:
employee.service_provided.disconnect(_on_service_complete)
agent.show_speech_bubble("")
employee.show_speech_bubble("")
queue.remove_member(agent)
phase = Phases.COMPLETE

View File

@@ -0,0 +1 @@
uid://bsq5dxul0uto

View File

@@ -0,0 +1,50 @@
#*
#* go_to.gd
#*
@tool
extends BTAction
## Moves the agent to the specified position, favoring horizontal movement. [br]
## Returns [code]SUCCESS[/code] when close to the target position (see [member tolerance]);
## otherwise returns [code]RUNNING[/code].
var wait_time_remaining : float = 0
var goal_position : Vector2
var retries : int
var done : bool
func _generate_name() -> String:
return "Wander to New Position"
func _enter() -> void:
done = false
retries = 0
goal_position = NavigationServer2D.region_get_random_point(Guild.hall.nav_region.get_rid(),1,false)
go_to(goal_position)
func _tick(delta: float) -> Status:
if done:
return SUCCESS
#If we were interrupted, wait a little bit and try again
if wait_time_remaining > 0:
wait_time_remaining -= delta
if wait_time_remaining <= 0:
go_to(goal_position)
return RUNNING
func go_to(pos : Vector2) -> void:
agent.navigation_finished.connect(_on_navigation_complete)
agent.navigation_failed.connect(_on_navigation_failed)
agent.approach(pos)
func _on_navigation_complete() -> void:
agent.navigation_finished.disconnect(_on_navigation_complete)
agent.navigation_failed.disconnect(_on_navigation_failed)
done = true
func _on_navigation_failed() -> void:
wait_time_remaining = randf_range(.5, 2)
retries += 1
if retries >= 3:
done = true
agent.navigation_finished.disconnect(_on_navigation_complete)
agent.navigation_failed.disconnect(_on_navigation_failed)

View File

@@ -0,0 +1 @@
uid://767b4fdlrgr

View File

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

View File

@@ -0,0 +1 @@
uid://xom38ohdwfms

View File

@@ -0,0 +1,7 @@
extends BTCondition
func _tick(delta: float) -> Status:
if agent.data and !Guild.has_guild_member(agent.data):
return SUCCESS
else:
return FAILURE

View File

@@ -0,0 +1 @@
uid://bcbfnm21rtkuo

View File

@@ -0,0 +1,22 @@
@tool
extends BTDecorator
var prev_busy : bool
func _get_task_icon():
return load("res://ai/icons/stopwatch.png")
func _enter() -> void:
if agent.get("busy") != null:
prev_busy = agent.busy
agent.busy = true
func _exit() -> void:
if agent.get("busy") != null:
agent.busy = prev_busy
# Called to generate a display name for the task (requires @tool).
func _generate_name() -> String:
return "Busy"

View File

@@ -0,0 +1 @@
uid://b2vuw12mttm40

145
ai/trees/adventurer.tres Normal file
View File

@@ -0,0 +1,145 @@
[gd_resource type="BehaviorTree" load_steps=42 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"]
[ext_resource type="Script" uid="uid://767b4fdlrgr" path="res://ai/tasks/actions/wander.gd" id="2_fe6jf"]
[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"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_6h604"]
var/pos/name = &"pos"
var/pos/type = 5
var/pos/value = Vector2(0, 0)
var/pos/hint = 0
var/pos/hint_string = ""
var/speed/name = &"speed"
var/speed/type = 3
var/speed/value = 0.0
var/speed/hint = 0
var/speed/hint_string = ""
[sub_resource type="BTComment" id="BTComment_fe6jf"]
custom_name = "Walk from the entrance to the designated start point of the guild."
[sub_resource type="BBVariant" id="BBVariant_1441p"]
type = 5
saved_value = Vector2(900, 700)
resource_name = "(900.0, 700.0)"
[sub_resource type="BTSetVar" id="BTSetVar_mtixs"]
variable = &"pos"
value = SubResource("BBVariant_1441p")
[sub_resource type="BTAction" id="BTAction_0kac8"]
script = ExtResource("1_s3kkm")
[sub_resource type="BTWait" id="BTWait_s3kkm"]
duration = 1.5
[sub_resource type="BTAction" id="BTAction_nqy1p"]
script = ExtResource("2_fe6jf")
[sub_resource type="BTSequence" id="BTSequence_s3kkm"]
custom_name = "Enter the Guild"
children = [SubResource("BTSetVar_mtixs"), SubResource("BTAction_0kac8"), SubResource("BTWait_s3kkm"), SubResource("BTAction_nqy1p")]
[sub_resource type="BTDecorator" id="BTDecorator_nqy1p"]
children = [SubResource("BTSequence_s3kkm")]
script = ExtResource("2_mtixs")
[sub_resource type="BTRunLimit" id="BTRunLimit_1441p"]
children = [SubResource("BTDecorator_nqy1p")]
[sub_resource type="BTComment" id="BTComment_0kac8"]
custom_name = "Always try to register as a guildmember as their first action"
[sub_resource type="BTCondition" id="BTCondition_s18yy"]
script = ExtResource("3_mtixs")
[sub_resource type="BTAction" id="BTAction_700su"]
script = ExtResource("2_1441p")
employee_name = "Receptionist"
service_name = "register"
[sub_resource type="BTAction" id="BTAction_s18yy"]
script = ExtResource("2_fe6jf")
[sub_resource type="BTSequence" id="BTSequence_1441p"]
custom_name = "Register as a Guildmember"
children = [SubResource("BTCondition_s18yy"), SubResource("BTAction_700su"), SubResource("BTAction_s18yy")]
[sub_resource type="BTDecorator" id="BTDecorator_700su"]
children = [SubResource("BTSequence_1441p")]
script = ExtResource("2_mtixs")
[sub_resource type="BTCondition" id="BTCondition_mtixs"]
script = ExtResource("4_1441p")
[sub_resource type="BTAction" id="BTAction_fe6jf"]
script = ExtResource("2_1441p")
employee_name = "Questboard"
service_name = "get_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")]
[sub_resource type="BTDecorator" id="BTDecorator_s18yy"]
children = [SubResource("BTSequence_nqy1p")]
script = ExtResource("2_mtixs")
[sub_resource type="BTProbability" id="BTProbability_s3kkm"]
children = [SubResource("BTDecorator_s18yy")]
[sub_resource type="BTComment" id="BTComment_mwsop"]
custom_name = "TODO: Make them talk to a random other adventurer"
[sub_resource type="BTDecorator" id="BTDecorator_jq6fo"]
children = [SubResource("BTComment_mwsop")]
script = ExtResource("2_mtixs")
[sub_resource type="BTCooldown" id="BTCooldown_mtixs"]
duration = 5.0
trigger_on_failure = true
children = [SubResource("BTDecorator_jq6fo")]
[sub_resource type="BTProbability" id="BTProbability_1441p"]
children = [SubResource("BTCooldown_mtixs")]
_enabled = false
[sub_resource type="BTWait" id="BTWait_8lwgx"]
duration = 3.0
custom_name = "Idle"
[sub_resource type="BTProbability" id="BTProbability_gc1l4"]
children = [SubResource("BTWait_8lwgx")]
[sub_resource type="BTAction" id="BTAction_jq6fo"]
script = ExtResource("2_fe6jf")
[sub_resource type="BTProbability" id="BTProbability_8lwgx"]
children = [SubResource("BTAction_jq6fo")]
[sub_resource type="BTProbabilitySelector" id="BTProbabilitySelector_mtixs"]
children = [SubResource("BTProbability_s3kkm"), SubResource("BTProbability_1441p"), SubResource("BTProbability_gc1l4"), SubResource("BTProbability_8lwgx")]
[sub_resource type="BTComment" id="BTComment_y4ura"]
custom_name = "TODO: Make them wander to a random location after the interaction"
[sub_resource type="BTSequence" id="BTSequence_4w6ij"]
children = [SubResource("BTProbabilitySelector_mtixs"), SubResource("BTComment_y4ura")]
[sub_resource type="BTSelector" id="BTSelector_mwsop"]
children = [SubResource("BTComment_fe6jf"), SubResource("BTRunLimit_1441p"), SubResource("BTComment_0kac8"), SubResource("BTDecorator_700su"), SubResource("BTSequence_4w6ij")]
[sub_resource type="BTRepeat" id="BTRepeat_s3kkm"]
forever = true
children = [SubResource("BTSelector_mwsop")]
[resource]
blackboard_plan = SubResource("BlackboardPlan_6h604")
root_task = SubResource("BTRepeat_s3kkm")

View File

@@ -0,0 +1,34 @@
[gd_resource type="BehaviorTree" load_steps=9 format=3 uid="uid://dxyx7tjsd7khq"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_1q5ck"]
[sub_resource type="BBVariant" id="BBVariant_68j5j"]
type = 1
saved_value = false
resource_name = "false"
[sub_resource type="BTCheckAgentProperty" id="BTCheckAgentProperty_tkyhk"]
property = &"busy"
value = SubResource("BBVariant_68j5j")
[sub_resource type="BBNode" id="BBNode_1q5ck"]
saved_value = NodePath("Queue")
resource_name = "Queue"
[sub_resource type="BTCallMethod" id="BTCallMethod_xsfkt"]
node = SubResource("BBNode_1q5ck")
method = &"try_advance"
[sub_resource type="BTSequence" id="BTSequence_1q5ck"]
children = [SubResource("BTCheckAgentProperty_tkyhk"), SubResource("BTCallMethod_xsfkt")]
[sub_resource type="BTCooldown" id="BTCooldown_qle3k"]
duration = 2.0
children = [SubResource("BTSequence_1q5ck")]
[sub_resource type="BTRepeat" id="BTRepeat_aurho"]
children = [SubResource("BTCooldown_qle3k")]
[resource]
blackboard_plan = SubResource("BlackboardPlan_1q5ck")
root_task = SubResource("BTRepeat_aurho")