Compare commits

...

31 Commits

Author SHA1 Message Date
Yan Wittmann 3b4d8a4d8e Merge pull request 'CampAndInventoryUI' (#8) from CampAndInventoryUI into main
Reviewed-on: #8
2025-01-14 21:37:23 +01:00
Yan Wittmann 3d716ed06d Added "better" implementation for camp UI 2025-01-14 21:37:13 +01:00
Yan Wittmann f3f4cef1bf Added image files back in 2025-01-14 19:30:01 +01:00
Yan Wittmann 263816fa53 Merge branch 'main' into CampAndInventoryUI 2025-01-14 19:26:43 +01:00
Luca 08647a4188 BoatParrtProgress und CampUI hinzugefügt 2025-01-14 19:25:18 +01:00
Yan Wittmann 7a18765ebd Added images 2025-01-14 19:21:08 +01:00
Yan Wittmann da1fdf10d0 Some more adjustments to the map 2025-01-14 12:11:45 +01:00
Yan Wittmann 624c14150e Made exploration task smarter by canceling if was close once 2025-01-14 12:01:11 +01:00
Yan Wittmann a75eb4aa1d Adjusted map, added key to set exploration goal 2025-01-14 11:35:16 +01:00
Yan Wittmann 28c9b857bd Added outro sequence 2025-01-14 10:13:35 +01:00
Yan Wittmann 2e1559d96e Added clock timer 2025-01-14 09:43:53 +01:00
Yan Wittmann 9822c716f2 Added instructions on how to play during gameplay 2025-01-13 20:12:42 +01:00
Yan Wittmann b187b96507 Added graphedit node highlighting and scrolling 2025-01-13 19:30:46 +01:00
Yan Wittmann fcbd269f68 FINALLY, A WORKING TREE DISTRIBUTION ALGORITHM 2025-01-13 18:57:05 +01:00
Yan Wittmann b3220f968d Merge pull request 'IntroAndTastaturLayout' (#7) from IntroAndTastaturLayout into main
Reviewed-on: #7
2025-01-13 18:18:06 +01:00
Yan Wittmann dc3ead30b4 Added auto-waking toggle with auto-follow and zoom, and better different screen sizes support 2025-01-13 18:17:25 +01:00
Crazyrx7 d026c07804 Fade-in mit tastaturlayout hinzugefügt 2025-01-13 11:37:55 +01:00
Crazyrx7 4b8479e87e Fade-in bei Spielstart into Tastaturlayout und bei Tastendruck gehts dann los 2025-01-13 11:35:12 +01:00
Yan Wittmann d8bc800077 Initial version of a tree visualizer 2025-01-12 17:45:21 +01:00
Yan Wittmann 0becddf3d2 Campfire and sleep mechanic 2025-01-12 16:28:36 +01:00
Yan Wittmann cd01fd24a2 Made UI more modular 2025-01-12 15:04:17 +01:00
Yan Wittmann 57db80e685 Added exploration behavior 2025-01-12 14:37:40 +01:00
Yan Wittmann 268f2c0ce1 Game now finally works somewhat properly 2025-01-11 19:05:03 +01:00
Yan Wittmann 4763de9827 Attempt #1 at fixing main 2025-01-11 17:43:13 +01:00
Yan Wittmann 238af1cc83 Merge branch 'working-map'
# Conflicts:
#	project/main-scenes/island.tscn
2025-01-11 17:40:50 +01:00
Yan Wittmann cd82cf5bdf Fixed up tilemap and content 2025-01-11 17:39:37 +01:00
2021587 2923738906 now its only the gameworld 2025-01-11 17:00:31 +01:00
2021587 8bbca6cef5 Revert "added game world"
This reverts commit 7304b48f3c.
2025-01-11 16:59:42 +01:00
2021587 7304b48f3c added game world 2025-01-11 16:59:08 +01:00
Yan Wittmann 9446c10556 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	project/assets/tilemap/tileset.tres
#	project/main-scenes/island.tscn
2025-01-11 16:15:03 +01:00
Yan Wittmann 731ff9ed12 Initial version of boat construction 2025-01-11 16:09:10 +01:00
57 changed files with 1949 additions and 167 deletions

View File

@ -1,3 +0,0 @@
{
"godotTools.editorPath.godot4": "g:\\Godot\\Godot_v4.3-stable_win64_console.exe"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://di16cmfomo60u"
path="res://.godot/imported/intro.png-5733d5421d999e0273f5c7e8c62d2491.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/images/intro.png"
dest_files=["res://.godot/imported/intro.png-5733d5421d999e0273f5c7e8c62d2491.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cr4e1faypphkg"
path="res://.godot/imported/ingame-instructions.png-47b6e281450dc0b79c6fd2f40f059336.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/images/ingame-instructions.png"
dest_files=["res://.godot/imported/ingame-instructions.png-47b6e281450dc0b79c6fd2f40f059336.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cynull1bsik42"
path="res://.godot/imported/outro.png-474f491bb8e2fbb9b182fa83ab072d54.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/images/outro.png"
dest_files=["res://.godot/imported/outro.png-474f491bb8e2fbb9b182fa83ab072d54.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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

@ -1 +1 @@
{"frames":[{"duration":0.1}],"height":320,"filename":"tilemaps.aseprite","layers":[{"name":"ground","cels":[{"frame":0,"image":"tilemaps/tilemap_ground.png"}]},{"name":"objects","cels":[{"frame":0,"image":"tilemaps/tilemap_objects.png"}]},{"name":"temperature","cels":[{"frame":0,"image":"tilemaps/tilemap_temperature.png"}]},{"name":"player","cels":[{"frame":0,"image":"tilemaps/tilemap_player.png"}]}],"width":320} {"filename":"tilemaps.aseprite","width":320,"frames":[{"duration":0.1}],"layers":[{"cels":[{"frame":0,"image":"tilemaps\\tilemap_ground.png"}],"name":"ground"},{"cels":[{"frame":0,"image":"tilemaps\\tilemap_objects.png"}],"name":"objects"},{"cels":[{"frame":0,"image":"tilemaps\\tilemap_temperature.png"}],"name":"temperature"},{"cels":[{"frame":0,"image":"tilemaps\\tilemap_player.png"}],"name":"player"}],"height":320}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -16,19 +16,36 @@ texture = ExtResource("1_ukrsa")
3:0/0/custom_data_0 = true 3:0/0/custom_data_0 = true
3:0/0/custom_data_2 = 1 3:0/0/custom_data_2 = 1
3:0/8 = 8 3:0/8 = 8
3:0/8/custom_data_0 = true
3:0/8/custom_data_2 = 1
1:2/0 = 0 1:2/0 = 0
3:2/next_alternative_id = 5 3:2/next_alternative_id = 7
3:2/0 = 0 3:2/0 = 0
3:2/0/custom_data_0 = true
3:2/0/custom_data_2 = 1
3:2/4 = 4 3:2/4 = 4
3:2/4/custom_data_2 = 1
0:0/0 = 0 0:0/0 = 0
0:0/0/custom_data_0 = true 0:0/0/custom_data_0 = true
0:0/0/custom_data_2 = 4 0:0/0/custom_data_2 = 2
3:1/next_alternative_id = 3 3:1/next_alternative_id = 4
3:1/0 = 0 3:1/0 = 0
3:1/0/custom_data_0 = true
3:1/0/custom_data_2 = 1
3:1/2 = 2 3:1/2 = 2
4:0/0 = 0 3:1/2/custom_data_0 = true
3:1/2/custom_data_2 = 1
5:3/0 = 0 5:3/0 = 0
5:3/0/custom_data_0 = true 5:3/0/custom_data_0 = true
4:0/0 = 0
4:0/0/custom_data_0 = true
4:0/0/custom_data_2 = 3
0:1/0 = 0
0:1/0/custom_data_0 = true
0:1/0/custom_data_2 = 8
0:2/0 = 0
0:2/0/custom_data_0 = true
0:2/0/custom_data_2 = 400
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_x77e4"] [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_x77e4"]
texture = ExtResource("2_15xge") texture = ExtResource("2_15xge")

File diff suppressed because one or more lines are too long

View File

@ -76,16 +76,6 @@ key_9={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":57,"key_label":0,"unicode":57,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":57,"key_label":0,"unicode":57,"location":0,"echo":false,"script":null)
] ]
} }
force_game_tick={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
]
}
force_game_tick_fast={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":71,"key_label":0,"unicode":103,"location":0,"echo":false,"script":null)
]
}
key_4={ key_4={
"deadzone": 0.5, "deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":52,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":52,"location":0,"echo":false,"script":null)
@ -106,9 +96,35 @@ key_6={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":54,"key_label":0,"unicode":54,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":54,"key_label":0,"unicode":54,"location":0,"echo":false,"script":null)
] ]
} }
force_game_tick={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":71,"key_label":0,"unicode":103,"location":0,"echo":false,"script":null)
]
}
force_game_tick_fast={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
]
}
auto_tick={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
]
}
toggle_graph_edit={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
toggle_temperature_layer={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":84,"key_label":0,"unicode":116,"location":0,"echo":false,"script":null)
]
}
[rendering] [rendering]
textures/canvas_textures/default_texture_filter=0 textures/canvas_textures/default_texture_filter=0
renderer/rendering_method="gl_compatibility" renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility" renderer/rendering_method.mobile="gl_compatibility"
environment/defaults/default_clear_color=Color(0.356863, 0.431373, 0.882353, 1)

View File

@ -7,8 +7,8 @@ extends Camera2D
@export var max_speed: float = 500.0 @export var max_speed: float = 500.0
@export var inner_border_threshold: float = 0.0 # 60.0 @export var inner_border_threshold: float = 0.0 # 60.0
@export var outer_border_threshold: float = 0.0 # 40.0 @export var outer_border_threshold: float = 0.0 # 40.0
@export var min_position: Vector2 = Vector2(0, 0) @export var min_position: Vector2 = Vector2(874, 843)
@export var max_position: Vector2 = Vector2(1375, 660) @export var max_position: Vector2 = Vector2(4884, 4623)
var velocity: Vector2 = Vector2.ZERO var velocity: Vector2 = Vector2.ZERO
# #

View File

@ -8,14 +8,22 @@ enum Event {
CAMP_ADDED_ITEM, CAMP_ADDED_ITEM,
CAMP_TAKEN_ITEM, CAMP_TAKEN_ITEM,
CAMP_TAKE_ITEM_FAILED, CAMP_TAKE_ITEM_FAILED,
CAMP_BOAT_PART_DELIVERED,
CAMP_BOAT_COMPLETE,
SLEEP, SLEEP,
PLAYER_PICKED_UP_ITEM, PLAYER_PICKED_UP_ITEM,
PLAYER_DROPPED_ITEM, PLAYER_DROPPED_ITEM,
PLAYER_USED_ITEM, PLAYER_USED_ITEM,
GAME_STATE_WIN,
NEW_EXPLORATION_GOAL,
EXPLORATION_GOAL_REACHED,
EXPLORATION_GOAL_CLOSE_ENOUGH,
TEMPERATURE_COLD,
TIME_SUNDOWN,
}; };
# #
static var events: Array[TrackedEvent] = [] static var events: Array[TrackedEvent] = []
static var max_events: int = 20 static var max_events: int = 14
static var callbacks: Array[Callable] = [] static var callbacks: Array[Callable] = []
@ -54,20 +62,39 @@ static func populate_visual_log_create_label(event: TrackedEvent, container: Con
elif event_id == Event.CAMP_ADDED_ITEM: elif event_id == Event.CAMP_ADDED_ITEM:
text = "Camp added item" text = "Camp added item"
elif event_id == Event.CAMP_TAKEN_ITEM: elif event_id == Event.CAMP_TAKEN_ITEM:
text = "Camp taken item" text = "Camp taken item x" + str(params["count"])
elif event_id == Event.CAMP_TAKE_ITEM_FAILED: elif event_id == Event.CAMP_TAKE_ITEM_FAILED:
text = "Could not take item from camp" text = "Could not take item from camp"
elif event_id == Event.SLEEP: elif event_id == Event.SLEEP:
text = "Player slept" text = "Player slept"
elif event_id == Event.PLAYER_PICKED_UP_ITEM: elif event_id == Event.PLAYER_PICKED_UP_ITEM:
text = "took" text = "Took"
elif event_id == Event.PLAYER_DROPPED_ITEM: elif event_id == Event.PLAYER_DROPPED_ITEM:
text = "dropped" text = "Dropped"
elif event_id == Event.PLAYER_USED_ITEM: elif event_id == Event.PLAYER_USED_ITEM:
text = "used" text = "Used"
elif event_id == Event.CAMP_BOAT_PART_DELIVERED:
text = "Boat construction"
elif event_id == Event.CAMP_BOAT_COMPLETE:
text = "Boat complete"
elif event_id == Event.GAME_STATE_WIN:
text = "Game won"
elif event_id == Event.NEW_EXPLORATION_GOAL:
text = "New goal " + str(params["goal"])
elif event_id == Event.EXPLORATION_GOAL_REACHED:
text = "Goal reached"
elif event_id == Event.EXPLORATION_GOAL_CLOSE_ENOUGH:
text = "Got close enough to goal..."
elif event_id == Event.TEMPERATURE_COLD:
text = "Temperature is cold: -" + str(params["temperature"])
elif event_id == Event.TIME_SUNDOWN:
text = "The sun is setting..."
else:
text = "Something happened..."
var event_label: Label = Label.new() var event_label: Label = Label.new()
event_label.text = text event_label.text = text
event_label.add_theme_font_size_override("font_size", 24 * game_manager.calculate_scale(Vector2(1200, 1200)).y)
event_label.add_theme_color_override("font_color", Color(0, 0, 0)) event_label.add_theme_color_override("font_color", Color(0, 0, 0))
var event_container: HBoxContainer = HBoxContainer.new() var event_container: HBoxContainer = HBoxContainer.new()
@ -78,6 +105,8 @@ static func populate_visual_log_create_label(event: TrackedEvent, container: Con
if item_texture: if item_texture:
var item_texture_rect: TextureRect = TextureRect.new() var item_texture_rect: TextureRect = TextureRect.new()
item_texture_rect.texture = item_texture item_texture_rect.texture = item_texture
item_texture_rect.set_expand_mode(TextureRect.EXPAND_FIT_WIDTH)
item_texture_rect.set_stretch_mode(TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
event_container.add_child(item_texture_rect) event_container.add_child(item_texture_rect)
container.add_child(event_container) container.add_child(event_container)

View File

@ -8,13 +8,21 @@ var tilemap_types: TileMapTileTypes = TileMapTileTypes.new()
@onready var camera: CameraController = $Camera2D as CameraController @onready var camera: CameraController = $Camera2D as CameraController
@onready var game_ticker: Timer = $GameTick @onready var game_ticker: Timer = $GameTick
# #
@onready var health_bar: ProgressBar = $Camera2D/CanvasLayer/VBoxContainer/HealthBar @onready var health_bar: ProgressBar = %HealthBar
@onready var food_bar: ProgressBar = $Camera2D/CanvasLayer/VBoxContainer/FoodBar @onready var food_bar: ProgressBar = %FoodBar
@onready var temperature_bar: ProgressBar = $Camera2D/CanvasLayer/VBoxContainer/TemperatureBar @onready var temperature_bar: ProgressBar = %TemperatureBar
@onready var temperature_layer: Node2D = $Tileset/TemperatureLayer @onready var temperature_resistance_bar: ProgressBar = %TemperatureResistanceBar
@onready var time_of_day_bar: ProgressBar = %TimeOfDayBar
var tilemap_navigation: TilemapNavigation = TilemapNavigation.new() var tilemap_navigation: TilemapNavigation = TilemapNavigation.new()
@onready var tree_visualizer: BehaviorTreeVisualizer = %TreeVisualizer
#
var waiting_for_input: bool = true
@onready var intro_image: Sprite2D = $Camera2D/IntroImage
func _ready() -> void: func _ready() -> void:
tilemap_navigation.world = world tilemap_navigation.world = world
@ -24,27 +32,71 @@ func _ready() -> void:
world.step_visualizer.game_manager = self world.step_visualizer.game_manager = self
world.step_visualizer.world = world world.step_visualizer.world = world
update_bars() update_bars()
call_deferred("defer_ready")
# game_ticker.start() func defer_ready() -> void:
tree_visualizer.behavior_tree = player.behavior_tree
tree_visualizer.build_tree()
intro_image.visible = true
await wait_for_key_press()
get_tree().create_tween().tween_method(set_intro_opacity, 1.0, 0.0, 1.0)
get_tree().create_tween().tween_method(set_instructions_opacity, 0.0, 1.0, 1.0)
func _process(delta: float) -> void: func _process(delta: float) -> void:
if Input.is_action_just_pressed("key_1"):
camera.go_to_zooming(Vector2(517.469787597656, 289.846008300781), 1.771561)
if Input.is_action_just_pressed("key_2"):
camera.go_to_zooming(Vector2(789.883972167969, 450.102813720703), 0.56015348434448)
if Input.is_action_just_pressed("key_9"):
world.camp_manager.campfire_light()
world.camp_manager.sleep_effect()
world.camp_manager.campfire_extinguish()
if Input.is_action_just_pressed("force_game_tick"): if Input.is_action_just_pressed("force_game_tick"):
Task.print_behavior_tree_evaluation = true
_on_game_tick_timeout() _on_game_tick_timeout()
Task.print_behavior_tree_evaluation = false
if Input.is_action_pressed("force_game_tick_fast"): if Input.is_action_pressed("force_game_tick_fast"):
_on_game_tick_timeout() _on_game_tick_timeout()
if Input.is_action_just_pressed("key_6"): if Input.is_action_just_pressed("toggle_temperature_layer"):
toggle_temperature_layer() toggle_temperature_layer()
camera.print_config()
if Input.is_action_just_pressed("auto_tick"):
if game_ticker.is_stopped():
game_ticker.start()
else:
game_ticker.stop()
if Input.is_action_just_pressed("key_1"):
get_tree().reload_current_scene()
if Input.is_action_just_pressed("key_2"):
player.exploration_task.current_goal = world.tilemap_ground.local_to_cell(world.get_local_mouse_position())
player.behavior_tree.blackboard["cached_paths"] = {}
player.behavior_tree.blackboard["path"] = []
if Input.is_action_just_pressed("key_3"):
player.board_position = Vector2i(world.camp_manager.camp)
player.board_position.y += 1
if intro_image.is_visible():
intro_image.set_scale(calculate_scale(intro_image.texture.get_size()))
func calculate_scale(image_size: Vector2) -> Vector2:
var viewport_size: Vector2 = world.get_viewport_rect().size
var scale: float = viewport_size.x / image_size.x
return Vector2(scale, scale)
# SECTION: intro / outro
func set_intro_opacity(opacity: float) -> void:
intro_image.set_modulate(Color(1, 1, 1, opacity))
func set_instructions_opacity(opacity: float) -> void:
%InstructionsRect.set_modulate(Color(1, 1, 1, opacity))
%InstructionsRect.show()
func set_outro_opacity(opacity: float) -> void:
%OutroImageContainer.set_modulate(Color(1, 1, 1, opacity))
%OutroImageContainer.show()
# SECTION: game tick
func player_health_depleted(): func player_health_depleted():
# TODO # TODO
@ -53,43 +105,191 @@ func player_health_depleted():
func _on_game_tick_timeout() -> void: func _on_game_tick_timeout() -> void:
var timer_on_game_tick_timeout: PerformanceTimer = PerformanceTimer.new() var timer_on_game_tick_timeout: PerformanceTimer = PerformanceTimer.new()
timer_on_game_tick_timeout.display_name = "game tick duration" timer_on_game_tick_timeout.display_name = "frame"
tilemap_navigation.game_tick_start() tilemap_navigation.game_tick_start()
world.game_tick_start() world.game_tick_start()
player.game_tick() player.game_tick()
apply_player_exploration_distance()
tree_visualizer.update_task_statuses(player.behavior_tree.blackboard)
tilemap_navigation.game_tick_end() tilemap_navigation.game_tick_end()
world.game_tick_end() world.game_tick_end()
EventsTracker.populate_visual_log(%RecentEventsLog, self) EventsTracker.populate_visual_log(%RecentEventsLog, self)
update_bars() update_bars()
world.camp_manager.populate_camp_visualization(%BoatProcessUI, %CampItemUI)
handle_result_game_state(player.behavior_tree.blackboard)
if not game_ticker.is_stopped():
camera_follow_player()
timer_on_game_tick_timeout.stop() timer_on_game_tick_timeout.stop()
func camera_follow_player() -> void:
var player_position: Vector2 = world.tilemap_player.cell_to_local(player.board_position)
var targeted_position = null
if player.behavior_tree.blackboard.has("path"):
var path: Array = player.behavior_tree.blackboard["path"]
if path.size() > 0:
targeted_position = world.tilemap_player.cell_to_local(path[path.size() - 1])
if not targeted_position:
camera.go_to(player_position)
return
var avg_position = (player_position + targeted_position) / 2
var distance: float = player_position.distance_to(targeted_position)
var zoom_level: float
if distance < 200:
zoom_level = distance_to_zoom_level(200)
else:
zoom_level = distance_to_zoom_level(distance)
avg_position.x += distance / 2
camera.go_to_zooming(avg_position, zoom_level)
func apply_player_exploration_distance():
player.exploration_task.closest_distance_to_goal = min(player.exploration_task.closest_distance_to_goal, TilemapNavigation.manhattan_distance(player.board_position, player.exploration_task.current_goal))
func distance_to_zoom_level(distance: float) -> float:
var a: float = 862.08
var b: float = 274.13
return a / (distance + b)
func handle_result_game_state(blackboard: Dictionary) -> void:
if blackboard.has("game_state_win"):
EventsTracker.track(EventsTracker.Event.GAME_STATE_WIN)
game_ticker.stop()
get_tree().create_tween().tween_method(set_outro_opacity, 0.0, 1.0, 1.0)
func update_boat_progress_old() -> void:
var part_counts: Dictionary = { # @formatter:off
tilemap_types.OBJECT_I_BOAT_PART_ENGINE: 0,
tilemap_types.OBJECT_I_BOAT_PART_FUEL: 0,
tilemap_types.OBJECT_I_BOAT_PART_ANCHOR: 0,
tilemap_types.OBJECT_I_BOAT_PART_CHEST: 0,
tilemap_types.OBJECT_I_BOAT_PART_GEARS: 0,
tilemap_types.OBJECT_I_BOAT_PART_MEDIKIT: 0,
tilemap_types.OBJECT_I_BOAT_PART_PADDLE: 0,
tilemap_types.OBJECT_I_BOAT_PART_GAS_STOVE: 0
} # @formatter:on
for boat_part in world.camp_manager.boat_items:
if part_counts.has(boat_part):
part_counts[boat_part] += 1
for part in part_counts.keys():
var count = part_counts[part]
if count > 0:
if part == tilemap_types.OBJECT_I_BOAT_PART_ENGINE:
%BoatPartEngine.texture = world.tilemap_interactive.get_cell_texture(tilemap_types.OBJECT_I_BOAT_PART_ENGINE)
%BoatPartEngine.visible = true
%EngineCount.text = str(count)
elif part == tilemap_types.OBJECT_I_BOAT_PART_FUEL:
%BoatPartFuel.texture = world.tilemap_interactive.get_cell_texture(tilemap_types.OBJECT_I_BOAT_PART_FUEL)
%BoatPartFuel.visible = true
%FuelCount.text = str(count)
elif part == tilemap_types.OBJECT_I_BOAT_PART_ANCHOR:
%BoatPartAnchor.texture = world.tilemap_interactive.get_cell_texture(tilemap_types.OBJECT_I_BOAT_PART_ANCHOR)
%BoatPartAnchor.visible = true
%AnchorCount.text = str(count)
elif part == tilemap_types.OBJECT_I_BOAT_PART_CHEST:
%BoatPartChest.texture = world.tilemap_interactive.get_cell_texture(tilemap_types.OBJECT_I_BOAT_PART_CHEST)
%BoatPartChest.visible = true
%ChestCount.text = str(count)
elif part == tilemap_types.OBJECT_I_BOAT_PART_GEARS:
%BoatPartGears.texture = world.tilemap_interactive.get_cell_texture(tilemap_types.OBJECT_I_BOAT_PART_GEARS)
%BoatPartGears.visible = true
%GearsCount.text = str(count)
elif part == tilemap_types.OBJECT_I_BOAT_PART_MEDIKIT:
%BoatPartMedikit.texture = world.tilemap_interactive.get_cell_texture(tilemap_types.OBJECT_I_BOAT_PART_MEDIKIT)
%BoatPartMedikit.visible = true
%MedikitCount.text = str(count)
elif part == tilemap_types.OBJECT_I_BOAT_PART_PADDLE:
%BoatPartPaddle.texture = world.tilemap_interactive.get_cell_texture(tilemap_types.OBJECT_I_BOAT_PART_PADDLE)
%BoatPartPaddle.visible = true
%PaddleCount.text = str(count)
elif part == tilemap_types.OBJECT_I_BOAT_PART_GAS_STOVE:
%BoatPartGasStove.texture = world.tilemap_interactive.get_cell_texture(tilemap_types.OBJECT_I_BOAT_PART_GAS_STOVE)
%BoatPartGasStove.visible = true
%StoveCount.text = str(count)
else:
push_error("Unknown boat part: " + str(part))
func update_bars() -> void: func update_bars() -> void:
if health_bar != null: if health_bar != null:
health_bar.max_value = player.max_health health_bar.max_value = player.max_health
health_bar.value = clamp(player.health, 0, player.max_health) health_bar.value = clamp(player.health, 0, player.max_health)
$Camera2D/CanvasLayer/VBoxContainer/HealthBar/HealthLabel.text = str(health_bar.value) + "/" + str(player.max_health) %HealthLabel.text = str(health_bar.value) + "/" + str(player.max_health)
$Camera2D/CanvasLayer/VBoxContainer/HealthBar/HealthLabel.add_theme_color_override("font_color", Color(1, 1, 1)) %HealthLabel.add_theme_color_override("font_color", Color(1, 1, 1))
if food_bar != null: if food_bar != null:
food_bar.max_value = player.max_food food_bar.max_value = player.max_food
food_bar.value = clamp(player.food, 0, player.max_food) food_bar.value = clamp(player.food, 0, player.max_food)
$Camera2D/CanvasLayer/VBoxContainer/FoodBar/FoodLabel.text = str(food_bar.value) + "/" + str(player.max_food) %FoodLabel.text = str(food_bar.value) + "/" + str(player.max_food)
if temperature_resistance_bar != null:
temperature_resistance_bar.max_value = player.temperature_set_buff_value
temperature_resistance_bar.value = clamp(player.temperature_buff_timer, 0, player.temperature_set_buff_value)
%TemperatureResistanceLabel.text = str(temperature_resistance_bar.value) + "/" + str(player.temperature_set_buff_value)
if temperature_bar != null: if temperature_bar != null:
temperature_bar.max_value = player.temperature_set_buff_value temperature_bar.max_value = player.temperature_endure
temperature_bar.value = clamp(player.temperature_buff_timer, 0, player.temperature_set_buff_value) # invert the value to show the time left
$Camera2D/CanvasLayer/VBoxContainer/TemperatureBar/TemperatureLabel.text = str(temperature_bar.value) + "/" + str(player.temperature_set_buff_value) var countdown: int = player.temperature_endure - player.temperature_timer
temperature_bar.value = clamp(countdown, 0, player.temperature_endure)
%TemperatureLabel.text = str(temperature_bar.value) + "/" + str(player.temperature_endure)
if time_of_day_bar != null:
time_of_day_bar.max_value = 1
time_of_day_bar.value = float(world.camp_manager.time_of_day) / world.camp_manager.day_length
time_of_day_bar.self_modulate = calculate_time_of_day_color(world.camp_manager.time_of_day, world.camp_manager.day_length)
%TimeOfDayLabel.text = calculate_display_time_of_day(world.camp_manager.time_of_day, world.camp_manager.day_length)
func calculate_display_time_of_day(current_time: int, day_length: int) -> String:
# format as 24 hour clock, start at 06:00 and end at 21:00
var start: int = 6
var end: int = 21
var hours_per_day: int = end - start
var time_of_day: float = float(current_time) / day_length * hours_per_day + start
var hours: int = int(time_of_day)
var minutes: int = int((time_of_day - hours) * 60)
hours %= 24
return str(hours).pad_zeros(2) + ":" + str(minutes).pad_zeros(2)
func calculate_time_of_day_color(current_time: int, day_length: int) -> Color:
var start: Color = Color(1, 1, 0)
var end: Color = Color(1, 0, 0)
var progress: float = float(current_time) / day_length
progress = clamp(progress, 0, 1)
progress = pow(progress, 2)
return start.lerp(end, progress)
func toggle_temperature_layer() -> void: func toggle_temperature_layer() -> void:
if temperature_layer != null: world.tilemap_temperature.tilemap.visible = not world.tilemap_temperature.tilemap.visible
temperature_layer.visible = not temperature_layer.visible
print("TemperatureLayer visibility:", temperature_layer.visible)
else: func wait_for_key_press():
print("TemperatureLayer is null!") waiting_for_input = true
while waiting_for_input:
await get_tree().process_frame
func _input(event):
if event is InputEventKey and event.pressed:
waiting_for_input = false

View File

@ -3,12 +3,12 @@ extends Node
@export var max_health: int = 100 @export var max_health: int = 100
# food system # food system
@export var max_food: int = 100 @export var max_food: int = 250
@export var food_damage: int = 1 @export var food_damage: int = 1
@export var food_addon_per_berry: int = 100
@export var food_critical_threshold: int = 50 @export var food_critical_threshold: int = 50
@export var food_base_threshold: int = 150
# temperature # temperature
@export var temperature_set_buff_value: int = 50 @export var temperature_set_buff_value: int = 150
@export var temperature_damage: int = 1 @export var temperature_damage: int = 1
@export var temperature_endure: int = 50 @export var temperature_endure: int = 50
# viewing # viewing
@ -25,20 +25,22 @@ var board_position: Vector2i = Vector2i(0, 0):
board_position = value board_position = value
update_board() update_board()
@onready var behavior_tree: BehaviorTree = $BehaviorTree @onready var behavior_tree: BehaviorTree = $BehaviorTree
var exploration_task: TaskPlannedExploration = null
var food: int = max_food var food: int = max_food
# var water: int = 0 # var water: int = 0
var temperature_buff_timer: int = 0 var temperature_buff_timer: int = 0
var temperature_timer: int = 0 var temperature_timer: int = 0
var health: int = max_health var health: int = max_health
# var inventory_slot: Vector2i = tilemap_types.EMPTY:
var inventory_slot: Vector2i = tilemap_types.EMPTY:
set(value): set(value):
inventory_slot = value inventory_slot = value
update_board() update_board()
var player_memory: Dictionary = {}
func _ready() -> void: func _ready() -> void:
call_deferred("defer_ready") call_deferred("defer_ready")
@ -52,10 +54,11 @@ func defer_ready() -> void:
else: else:
push_error("No player start position found on tilemap") push_error("No player start position found on tilemap")
update_board() update_board()
exploration_task = behavior_tree.find_task_by_name("TaskPlannedExploration")
func _process(delta: float) -> void: func _process(delta: float) -> void:
if Input.is_action_just_pressed("key_3"): if Input.is_action_just_pressed("key_1"):
game_manager.camera.go_to_zooming(game_manager.world.tilemap_player.cell_to_local(board_position), 2) game_manager.camera.go_to_zooming(game_manager.world.tilemap_player.cell_to_local(board_position), 2)
if Input.is_action_just_pressed("key_5"): if Input.is_action_just_pressed("key_5"):
pick_up_item(Vector2i(5, 8)) pick_up_item(Vector2i(5, 8))
@ -90,13 +93,15 @@ func pick_up_item(tilemap_pos: Vector2i) -> void:
var pick_up_item_type: Vector2i = game_manager.world.tilemap_interactive.tilemap.get_cell_atlas_coords(tilemap_pos) var pick_up_item_type: Vector2i = game_manager.world.tilemap_interactive.tilemap.get_cell_atlas_coords(tilemap_pos)
# check if tile will transform into another tile upon pickup # check if inventory contains item that needs to be transformed on dropping
# this should never be the case, as the pick up item operation should already reflect this transformation
var tile_drop_item: Vector2i = inventory_slot var tile_drop_item: Vector2i = inventory_slot
if tile_drop_item == tilemap_types.OBJECT_I_FILLED_BUSH: if tile_drop_item == tilemap_types.OBJECT_I_FILLED_BUSH:
tile_drop_item = tilemap_types.OBJECT_I_BERRY tile_drop_item = tilemap_types.OBJECT_I_BERRY
elif tile_drop_item == tilemap_types.OBJECT_I_TREE_FULL: elif tile_drop_item == tilemap_types.OBJECT_I_TREE_FULL:
tile_drop_item = tilemap_types.OBJECT_I_STICK tile_drop_item = tilemap_types.OBJECT_I_STICK
# check if tile will transform into another tile upon pickup
var tile_after_pickup_transform = null var tile_after_pickup_transform = null
if pick_up_item_type == tilemap_types.OBJECT_I_FILLED_BUSH: if pick_up_item_type == tilemap_types.OBJECT_I_FILLED_BUSH:
tile_after_pickup_transform = tilemap_types.OBJECT_I_EMPTY_BUSH tile_after_pickup_transform = tilemap_types.OBJECT_I_EMPTY_BUSH
@ -104,7 +109,6 @@ func pick_up_item(tilemap_pos: Vector2i) -> void:
elif pick_up_item_type == tilemap_types.OBJECT_I_TREE_FULL: elif pick_up_item_type == tilemap_types.OBJECT_I_TREE_FULL:
tile_after_pickup_transform = tilemap_types.OBJECT_I_TREE_CUT tile_after_pickup_transform = tilemap_types.OBJECT_I_TREE_CUT
pick_up_item_type = tilemap_types.OBJECT_I_STICK pick_up_item_type = tilemap_types.OBJECT_I_STICK
tile_drop_item = tilemap_types.OBJECT_I_STICK
# check if the inventory slot is empty # check if the inventory slot is empty
if inventory_slot == tilemap_types.EMPTY: if inventory_slot == tilemap_types.EMPTY:
@ -233,6 +237,7 @@ func tick_handle_food():
func game_tick() -> void: func game_tick() -> void:
behavior_tree.game_tick() behavior_tree.game_tick()
StepVisualization.add_circle_tileset(board_position, view_distance / 1.2, StepVisualization.CircleType.PLAYER_VIEW)
tick_handle_temperature(get_current_temperature()) tick_handle_temperature(get_current_temperature())
tick_handle_food() tick_handle_food()

View File

@ -8,25 +8,34 @@ var behavior_tree: Task = null
func _ready() -> void: func _ready() -> void:
if get_child_count() == 0 or get_child_count() > 1: if get_child_count() == 0 or get_child_count() > 1:
push_error("This controller needs exactly one Task child, got " + str(get_child_count())) push_error("This controller needs exactly one Task child, got " + str(get_child_count()))
var child: Node = get_child(0) var child: Node = get_child(0)
if not (child is Task): if not (child is Task):
push_error("Child is not a task: " + child.name) push_error("Child is not a task: " + child.name)
behavior_tree = child as Task behavior_tree = child as Task
initialize_blackboard()
func initialize_blackboard() -> void:
blackboard["cached_paths"] = {}
func populate_blackboard(): func populate_blackboard():
blackboard["world"] = game_manager.world blackboard["world"] = game_manager.world
blackboard["player"] = game_manager.player blackboard["player"] = game_manager.player
blackboard["camera"] = game_manager.camera blackboard["camera"] = game_manager.camera
blackboard["navigation"] = game_manager.tilemap_navigation blackboard["navigation"] = game_manager.tilemap_navigation
func game_tick() -> void: func game_tick() -> void:
print("game_tick:") populate_blackboard()
populate_blackboard() behavior_tree.internal_run(blackboard)
behavior_tree.internal_run(blackboard) if Task.print_behavior_tree_evaluation:
print(" ==> [active state=", blackboard["current_task"], "] [status=", behavior_tree.status, "] [blackboard=", blackboard, "]") print(" ==> [active state=", blackboard["current_task"], "] [status=", behavior_tree.status, "] [blackboard=", blackboard, "]")
func find_task_by_name(name: String) -> Task:
return behavior_tree.find_task_by_name(name)

View File

@ -2,6 +2,8 @@ class_name Task
extends Node extends Node
enum {FAILURE = -1, SUCCESS = 1, RUNNING = 0, SUCCESS_STOP = 2} enum {FAILURE = -1, SUCCESS = 1, RUNNING = 0, SUCCESS_STOP = 2}
static var print_behavior_tree_evaluation: bool = false
#
var status: int = FAILURE var status: int = FAILURE
var status_reason: String = "" var status_reason: String = ""
var tilemap_types: TileMapTileTypes = TileMapTileTypes.new() var tilemap_types: TileMapTileTypes = TileMapTileTypes.new()
@ -17,14 +19,16 @@ func _ready() -> void:
func internal_run(blackboard: Dictionary) -> void: func internal_run(blackboard: Dictionary) -> void:
blackboard["current_task"] = self blackboard["current_task"] = self
var running_child: Task = find_running_child() var running_child: Task = find_running_child()
var extra_string: String = "" var extra_string: String = ""
if running_child != null: if running_child != null:
extra_string = running_child.name extra_string = running_child.name
print(" -> ", human_readable(extra_string)) if print_behavior_tree_evaluation:
print(" -> ", human_readable(extra_string))
run(blackboard) run(blackboard)
print(" <- ", human_readable(extra_string)) if print_behavior_tree_evaluation:
print(" <- ", human_readable(extra_string))
func find_running_child() -> Task: func find_running_child() -> Task:
@ -66,15 +70,7 @@ func get_first_child() -> Task:
func human_readable(addon: String = "") -> String: func human_readable(addon: String = "") -> String:
var clear_status: String = "UNKNOWN" var clear_status: String = clear_status()
if status == FAILURE:
clear_status = "FAILURE"
elif status == SUCCESS:
clear_status = "SUCCESS"
elif status == RUNNING:
clear_status = "RUNNING"
elif status == SUCCESS_STOP:
clear_status = "SUCCESS_STOP"
var ret: String = name; var ret: String = name;
if addon != "": if addon != "":
@ -85,3 +81,53 @@ func human_readable(addon: String = "") -> String:
ret += " [" + clear_status + "]" ret += " [" + clear_status + "]"
return ret return ret
func clear_status() -> String:
if status == FAILURE:
return "FAILURE"
elif status == SUCCESS:
return "SUCCESS"
elif status == RUNNING:
return "RUNNING"
elif status == SUCCESS_STOP:
return "SUCCESS_STOP"
return "UNKNOWN"
# SECTION: utility
func find_closest_item(blackboard: Dictionary, item_types: Array[Vector2i], memory_key: String, max_distance: int = -1) -> Dictionary:
var world: World = blackboard["world"]
var player: PlayerManager = blackboard["player"]
var navigation: TilemapNavigation = blackboard["navigation"]
var result: Dictionary = {"status": FAILURE, "status_reason": "", "closest_item": null}
var items: Array[Vector2i] = world.tilemap_interactive.get_cells_by_type_collection(
item_types, player.board_position, max_distance if max_distance > -1 else player.view_distance)
if len(items) == 0:
result.status_reason = "No items of type " + str(item_types) + " found"
return result
var closest_item: Vector2i = navigation.manhattan_distance_closest(items, player.board_position)
player.player_memory[memory_key] = closest_item
StepVisualization.add_line_tileset(player.board_position, closest_item, StepVisualization.LineType.SEARCH_SELECTED)
if closest_item == tilemap_types.NO_TILE_FOUND:
result.status_reason = "No closest item of type " + str(item_types) + " found"
return result
result.status = SUCCESS
result.closest_item = closest_item
return result
func find_task_by_name(name: String) -> Task:
for c in get_children():
if c.name == name:
return c
if c.get_child_count() > 0:
var found: Task = c.find_task_by_name(name)
if found != null:
return found
return null

View File

@ -19,6 +19,7 @@ func run(blackboard: Dictionary) -> void:
player.walk_along(path) player.walk_along(path)
if navigation.has_arrived(player.board_position, path): if navigation.has_arrived(player.board_position, path):
blackboard["cached_paths"] = {}
status = SUCCESS status = SUCCESS
status_reason = "already arrived at destination" status_reason = "already arrived at destination"
return return

View File

@ -19,6 +19,7 @@ func run(blackboard: Dictionary) -> void:
player.walk_along(path) player.walk_along(path)
if navigation.has_arrived(player.board_position, path): if navigation.has_arrived(player.board_position, path):
blackboard["cached_paths"] = {}
status = SUCCESS status = SUCCESS
status_reason = "already arrived at destination" status_reason = "already arrived at destination"
return return

View File

@ -19,6 +19,7 @@ func run(blackboard: Dictionary) -> void:
player.walk_along(path) player.walk_along(path)
if navigation.has_arrived(player.board_position, path): if navigation.has_arrived(player.board_position, path):
blackboard["cached_paths"] = {}
status = SUCCESS status = SUCCESS
status_reason = "already arrived at destination" status_reason = "already arrived at destination"
return return

View File

@ -2,17 +2,22 @@ class_name TaskSelector
extends Task extends Task
func run(blackboard: Dictionary) -> void: func run(blackboard: Dictionary) -> void:
if get_children().size() == 0:
status = FAILURE
status_reason = "no children"
return
var running_child: Task = find_running_child() var running_child: Task = find_running_child()
for c in slice_at_child(running_child): for c in slice_at_child(running_child):
run_child(blackboard, c) run_child(blackboard, c)
if c.status == SUCCESS_STOP: if c.status == SUCCESS_STOP:
c.status = SUCCESS c.status = SUCCESS
status = SUCCESS status = SUCCESS
status_reason = "stopping at child " + c.name + ", as it returned SUCCESS_STOP" status_reason = "stopping at " + c.name + " (STOP)"
return return
if c.status != FAILURE: if c.status != FAILURE:
status = c.status status = c.status
status_reason = "stopped at child " + c.name status_reason = "stopped at " + c.name
return return
status = FAILURE status = FAILURE
status_reason = "all children failed" status_reason = "all children failed"

View File

@ -2,17 +2,22 @@ class_name TaskSequence
extends Task extends Task
func run(blackboard: Dictionary) -> void: func run(blackboard: Dictionary) -> void:
if get_children().size() == 0:
status = FAILURE
status_reason = "no children"
return
var running_child: Task = find_running_child() var running_child: Task = find_running_child()
for c in slice_at_child(running_child): for c in slice_at_child(running_child):
run_child(blackboard, c) run_child(blackboard, c)
if c.status == SUCCESS_STOP: if c.status == SUCCESS_STOP:
c.status = SUCCESS c.status = SUCCESS
status = SUCCESS status = SUCCESS
status_reason = "stopping at child " + c.name + ", as it returned SUCCESS_STOP" status_reason = "stopping at " + c.name + " (STOP)"
return return
if c.status != SUCCESS: if c.status != SUCCESS:
status = c.status status = c.status
status_reason = "stopped at child " + c.name status_reason = "stopped at " + c.name
return return
status = SUCCESS status = SUCCESS
status_reason = "all children succeeded" status_reason = "all children succeeded"

View File

@ -0,0 +1,114 @@
class_name TaskPlannedExploration
extends Task
var last_goals: Array[Vector2i] = []
var current_goal: Vector2i = tilemap_types.NO_TILE_FOUND
var closest_distance_to_goal: float = 99999999
func run(blackboard: Dictionary) -> void:
var world: World = blackboard["world"]
var player: PlayerManager = blackboard["player"]
var navigation: TilemapNavigation = blackboard["navigation"]
# check if player distance is < 10 to the camp (world, camp_manager, camp) and if he was closer than the view distance to the goal once (closest_distance_to_goal), reset the goal in that case
if TilemapNavigation.manhattan_distance(player.board_position, world.camp_manager.camp) < 10 and closest_distance_to_goal < player.view_distance:
current_goal = tilemap_types.NO_TILE_FOUND
closest_distance_to_goal = 99999999
EventsTracker.track(EventsTracker.Event.EXPLORATION_GOAL_CLOSE_ENOUGH, {"item": tilemap_types.OBJECT_I_TENT})
print("Resetting goal, player close to camp and was close to goal once")
# check if player distance is < 2 to the current goal
if current_goal != tilemap_types.NO_TILE_FOUND:
if TilemapNavigation.manhattan_distance(player.board_position, current_goal) < 3:
EventsTracker.track(EventsTracker.Event.EXPLORATION_GOAL_REACHED, {"goal": current_goal})
current_goal = tilemap_types.NO_TILE_FOUND
if current_goal == tilemap_types.NO_TILE_FOUND:
find_new_goal(world, player, navigation)
if current_goal != tilemap_types.NO_TILE_FOUND:
EventsTracker.track(EventsTracker.Event.NEW_EXPLORATION_GOAL, {"goal": current_goal})
closest_distance_to_goal = 99999999
if current_goal == tilemap_types.NO_TILE_FOUND:
status = Task.FAILURE
status_reason = "No goal found"
return
StepVisualization.add_circle_tileset(current_goal, 2, StepVisualization.CircleType.GOAL)
StepVisualization.add_line_tileset(player.board_position, current_goal, StepVisualization.LineType.SEARCH_SELECTED)
var path: Array[Vector2i] = navigation.cached_path_allow_neighbors(blackboard, "exploration_goal", current_goal)
if path.size() == 0:
current_goal = tilemap_types.NO_TILE_FOUND
status = Task.FAILURE
status_reason = "No path found"
return
blackboard["path"] = path
status = Task.SUCCESS
status_reason = "goal: " + str(current_goal)
func find_new_goal(world: World, player: PlayerManager, navigation: TilemapNavigation) -> void:
if last_goals.size() == 0:
last_goals.append(world.camp_manager.camp)
# perform search for a new goal X times, pick the one that is furthest away from the last goal
var best_goal: Vector2i = tilemap_types.NO_TILE_FOUND
var best_distance: float = 0
for i in range(3):
var goal_consideration: Vector2i = determine_an_interesting_goal(world)
if goal_consideration == tilemap_types.NO_TILE_FOUND:
continue
StepVisualization.add_circle_tileset(goal_consideration, 2, StepVisualization.CircleType.GOAL_CONSIDERATION)
StepVisualization.add_line_tileset(player.board_position, goal_consideration, StepVisualization.LineType.SEARCH_BASE)
var distance: float = TilemapNavigation.manhattan_distance(goal_consideration, navigation.manhattan_distance_closest(last_goals, goal_consideration))
if distance > best_distance:
best_goal = goal_consideration
best_distance = distance
if best_goal != tilemap_types.NO_TILE_FOUND:
last_goals.append(best_goal)
current_goal = best_goal
func determine_an_interesting_goal(world: World) -> Vector2i:
# starting from the camp position (world.camp_manager.camp),
# pick a random direction (360 degrees, random on x and y then normalize),
# then step in that direction until the last walkable tile is found
# (if not walkable, check every step for the next 10 tiles again and only stop if none of them are walkable)
# then, pick a random walkable tile in the area around that last walkable tile,
# and check if the player can get there using the navigation system.
var camp_position: Vector2 = Vector2(world.camp_manager.camp)
var direction: Vector2 = Vector2(randf() * 2 - 1, randf() * 2 - 1).normalized()
var last_walkable: Vector2i = Vector2i(0, 0)
var iterations_no_walkable: int = 0
for i in range(2500):
var check_position: Vector2i = camp_position + (direction * i * 2).floor()
if not world.is_walkable(check_position):
iterations_no_walkable += 1
else:
iterations_no_walkable = 0
last_walkable = check_position
if iterations_no_walkable > 10:
break
if last_walkable == Vector2i(0, 0):
return tilemap_types.NO_TILE_FOUND
var picked_goal: Vector2i = Vector2i(0, 0)
for i in range(10):
var check_position: Vector2i = last_walkable + Vector2i(randi_range(-10, 10), randi_range(-10, 10))
if world.is_walkable(check_position) and world.tilemap_ground.get_custom_data(check_position, "cost", 999) < 7:
picked_goal = check_position
break
return picked_goal

View File

@ -0,0 +1,14 @@
class_name TaskRandomWalking
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
var navigation: TilemapNavigation = blackboard["navigation"]
var direction: Vector2i = navigation.walking_directions[randi() % navigation.walking_directions.size()]
var target: Vector2i = player.board_position + direction
player.walk_towards(target)
status = SUCCESS
status_reason = "Walking towards " + str(target)

View File

@ -0,0 +1,13 @@
class_name TaskCheckFoodBaseThreshold
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
if player.food > player.food_base_threshold:
status = FAILURE
status_reason = "Player food is not base threshold (" + str(player.food) + " > " + str(player.food_critical_threshold) + ")"
return
status = SUCCESS
status_reason = "Player food is base threshold (" + str(player.food) + " <= " + str(player.food_critical_threshold) + ")"

View File

@ -0,0 +1,13 @@
class_name TaskCheckBoatCompleted
extends Task
func run(blackboard: Dictionary) -> void:
var world: World = blackboard["world"]
if world.camp_manager.boat_items.size() >= world.camp_manager.required_boat_parts:
status = SUCCESS
status_reason = "Boat is completed with " + str(world.camp_manager.boat_items.size()) + " parts"
return
status = FAILURE
status_reason = "Boat is not completed, got only " + str(world.camp_manager.boat_items.size()) + " parts"

View File

@ -0,0 +1,20 @@
class_name TaskDeliverBoatPart
extends Task
func run(blackboard: Dictionary) -> void:
var world: World = blackboard["world"]
var player: PlayerManager = blackboard["player"]
if tilemap_types.is_part_of_collection(tilemap_types.OBJECT_COLLECTION_BOAT_PARTS, player.inventory_slot):
EventsTracker.track(EventsTracker.Event.CAMP_BOAT_PART_DELIVERED, {"item": player.inventory_slot})
world.camp_manager.boat_items.append(player.inventory_slot)
player.inventory_slot = tilemap_types.EMPTY
if world.camp_manager.boat_items.size() >= world.camp_manager.required_boat_parts:
EventsTracker.track(EventsTracker.Event.CAMP_BOAT_COMPLETE, {"item": tilemap_types.OBJECT_I_BOAT_WITH_ENGINE})
world.tilemap_interactive.set_cell(world.camp_manager.boat_build_location, tilemap_types.OBJECT_I_BOAT_WITH_ENGINE)
status = SUCCESS
status_reason = "Player delivered boat part"
return
status = FAILURE
status_reason = "Player does not have boat part to deliver"

View File

@ -0,0 +1,31 @@
class_name TaskFindClosestBoatPart
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
var navigation: TilemapNavigation = blackboard["navigation"]
if tilemap_types.is_part_of_collection(tilemap_types.OBJECT_COLLECTION_BOAT_PARTS, player.inventory_slot):
status = FAILURE
status_reason = "Player already has boat part"
return
var result: Dictionary = find_closest_item(blackboard, tilemap_types.OBJECT_COLLECTION_BOAT_PARTS, "boat_part")
if result.status == FAILURE:
status = FAILURE
status_reason = result.status_reason
return
var closest_part: Vector2i = result.closest_item
blackboard["closest_part"] = closest_part
var path: Array[Vector2i] = navigation.cached_path_allow_neighbors(blackboard, "path_to_boat_part", closest_part, player.view_distance * 1.5)
if path.size() > 0:
blackboard["path"] = path
status_reason = "Found path to closest boat part"
status = SUCCESS
return
status = FAILURE
status_reason = "No path found to closest boat part " + str(closest_part)

View File

@ -0,0 +1,17 @@
class_name TaskGoToBoatLeaveLocation
extends Task
func run(blackboard: Dictionary) -> void:
var world: World = blackboard["world"]
var navigation: TilemapNavigation = blackboard["navigation"]
var target: Vector2i = world.camp_manager.boat_leave_location
var path: Array[Vector2i] = navigation.cached_path_allow_neighbors(blackboard, "path_to_boat_leave", target)
if path.size() > 0:
blackboard["path"] = path
status_reason = "Found path to boat leave location"
status = SUCCESS
else:
status = FAILURE
status_reason = "No path found to boat leave location " + str(target)

View File

@ -0,0 +1,25 @@
class_name TaskGoToBoatLocation
extends Task
func run(blackboard: Dictionary) -> void:
var navigation: TilemapNavigation = blackboard["navigation"]
var result: Dictionary = find_closest_item(blackboard, tilemap_types.OBJECT_COLLECTION_BOAT, "boat_building_location", TileMapLayerAccess.ANY_DISTANCE)
if result.status == FAILURE:
status = FAILURE
status_reason = result.status_reason
return
# var target: Vector2i = world.camp_manager.boat_build_location
var target: Vector2i = result.closest_item
var path: Array[Vector2i] = navigation.cached_path_allow_neighbors(blackboard, "path_to_boat", target)
if path.size() > 0:
blackboard["path"] = path
status_reason = "Found path to boat build location"
status = SUCCESS
else:
status = FAILURE
status_reason = "No path found to boat build location " + str(target)

View File

@ -0,0 +1,12 @@
class_name TaskWinningSequence
extends Task
func run(blackboard: Dictionary) -> void:
var world: World = blackboard["world"]
var player: PlayerManager = blackboard["player"]
world.tilemap_interactive.set_cell(world.camp_manager.boat_leave_location, player.inventory_slot)
EventsTracker.track(EventsTracker.Event.PLAYER_USED_ITEM, {"item": player.inventory_slot})
player.inventory_slot = tilemap_types.EMPTY
blackboard["game_state_win"] = true

View File

@ -0,0 +1,13 @@
class_name TaskCampContainsEnoughSticksToLightCampfire
extends Task
func run(blackboard: Dictionary) -> void:
var world: World = blackboard["world"]
if world.camp_manager.camp_contains_enough_sticks_to_light_campfire():
status = SUCCESS
status_reason = "Camp contains enough sticks to light campfire"
return
status = FAILURE
status_reason = "Camp does not contain enough sticks to light campfire"

View File

@ -0,0 +1,24 @@
class_name TaskCampSleep
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
var world: World = blackboard["world"]
if not world.camp_manager.is_sleep_active and world.camp_manager.is_sundown():
world.camp_manager.campfire_light()
world.camp_manager.sleep_effect()
player.health = player.max_health
status = RUNNING
status_reason = "Sleeping"
return
if world.camp_manager.is_sleep_active:
player.food += 1
status = RUNNING
status_reason = "Still sleeping"
return
world.camp_manager.campfire_extinguish()
status = SUCCESS
status_reason = "Slept"

View File

@ -8,7 +8,7 @@ func run(blackboard: Dictionary) -> void:
blackboard["location_camp"] = world.camp_manager.camp blackboard["location_camp"] = world.camp_manager.camp
var path: Array[Vector2i] = navigation.find_path_allow_neighbors(player.board_position, world.camp_manager.camp, 99999999) var path: Array[Vector2i] = navigation.cached_path_allow_neighbors(blackboard, "path_to_camp", world.camp_manager.camp, 99999999)
if path.size() > 0: if path.size() > 0:
blackboard["path"] = path blackboard["path"] = path
status = SUCCESS status = SUCCESS

View File

@ -0,0 +1,25 @@
class_name TaskGoCloseToCamp
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
var world: World = blackboard["world"]
var navigation: TilemapNavigation = blackboard["navigation"]
# allow a radius of the player view distance / 1.5 to find a camp
var camp: Vector2i = world.camp_manager.camp
if TilemapNavigation.manhattan_distance(player.board_position, camp) < player.view_distance / 1.5:
blackboard["path"] = []
status = SUCCESS
status_reason = "Player is close to camp"
return
var path: Array[Vector2i] = navigation.cached_path_allow_neighbors(blackboard, "path_to_camp", camp, 99999999)
if path.size() > 0:
blackboard["path"] = path
status = FAILURE
status_reason = "Found path to camp"
return
status = FAILURE
status_reason = "No path found to camp " + str(camp)

View File

@ -0,0 +1,16 @@
class_name TaskPutInventoryContentInCamp
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
var world: World = blackboard["world"]
if player.inventory_slot != tilemap_types.EMPTY:
world.camp_manager.camp_add_item(player.inventory_slot)
player.inventory_slot = tilemap_types.EMPTY
status = SUCCESS
status_reason = "Put inventory content in camp"
return
status = FAILURE
status_reason = "Player has no inventory content"

View File

@ -0,0 +1,13 @@
class_name TaskWantsToSleep
extends Task
func run(blackboard: Dictionary) -> void:
var world: World = blackboard["world"]
if world.camp_manager.is_sundown():
status = SUCCESS
status_reason = "It is sundown " + str(world.camp_manager.time_of_day)
return
status = FAILURE
status_reason = "It is not sundown " + str(world.camp_manager.time_of_day)

View File

@ -1,13 +1,27 @@
class_name TaskCheckTemperatureCold class_name TaskCheckTemperatureCold
extends Task extends Task
var last_temperature: float = 0
func run(blackboard: Dictionary) -> void: func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"] var player: PlayerManager = blackboard["player"]
if player.get_current_temperature() > 0: var current_temperature: int = player.get_current_temperature()
var temperature_changed: bool = current_temperature != last_temperature
var temperature_cold: bool = current_temperature > 0
if temperature_changed and temperature_cold:
EventsTracker.track(EventsTracker.Event.TEMPERATURE_COLD, {"temperature": current_temperature})
if temperature_changed:
last_temperature = current_temperature
if temperature_cold:
status = SUCCESS status = SUCCESS
status_reason = "cold: " + str(player.get_current_temperature()) status_reason = "cold: " + str(current_temperature)
return return
status = FAILURE status = FAILURE
status_reason = "not cold: " + str(player.get_current_temperature()) status_reason = "not cold: " + str(current_temperature)

View File

@ -7,7 +7,9 @@ func run(blackboard: Dictionary) -> void:
var navigation: TilemapNavigation = blackboard["navigation"] var navigation: TilemapNavigation = blackboard["navigation"]
var warm_tiles: Array[Vector2i] = world.tilemap_temperature.get_cells_by_type( var warm_tiles: Array[Vector2i] = world.tilemap_temperature.get_cells_by_type(
tilemap_types.TEMPERATURE_NORMAL, player.board_position, player.view_distance) tilemap_types.TEMPERATURE_NORMAL,
player.board_position, player.view_distance,
false)
if len(warm_tiles) == 0: if len(warm_tiles) == 0:
status = FAILURE status = FAILURE
status_reason = "No warm tiles found" status_reason = "No warm tiles found"

View File

@ -0,0 +1,13 @@
class_name TaskInventoryContainsBoat
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
if tilemap_types.is_part_of_collection(tilemap_types.OBJECT_COLLECTION_BOAT, player.inventory_slot):
status = SUCCESS
status_reason = "Player has boat"
return
status = FAILURE
status_reason = "Player does not have boat"

View File

@ -0,0 +1,13 @@
class_name TaskInventoryContainsBoatPart
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
if tilemap_types.is_part_of_collection(tilemap_types.OBJECT_COLLECTION_BOAT_PARTS, player.inventory_slot):
status = SUCCESS
status_reason = "Player has boat part"
return
status = FAILURE
status_reason = "Player does not have boat part"

View File

@ -0,0 +1,13 @@
class_name TaskInventoryContainsStick
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
if player.inventory_slot == tilemap_types.OBJECT_I_STICK:
status = SUCCESS
status_reason = "Player has stick"
return
status = FAILURE
status_reason = "Player does not have stick"

View File

@ -0,0 +1,16 @@
class_name TaskPickupBoat
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
var result: Dictionary = find_closest_item(blackboard, tilemap_types.OBJECT_COLLECTION_BOAT, "boat_building_location", TileMapLayerAccess.ANY_DISTANCE)
if result.status == FAILURE:
status = FAILURE
status_reason = result.status_reason
return
player.pick_up_item(result.closest_item)
status = SUCCESS
status_reason = "Picked up boat"

View File

@ -0,0 +1,12 @@
class_name TaskPickupBoatPart
extends Task
func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"]
var closest_part: Vector2i = blackboard["closest_part"]
player.pick_up_item(closest_part)
player.player_memory.erase("boat_part")
status = SUCCESS
status_reason = "Picked up boat part"

View File

@ -4,9 +4,10 @@ extends Task
func run(blackboard: Dictionary) -> void: func run(blackboard: Dictionary) -> void:
var world: World = blackboard["world"] var world: World = blackboard["world"]
var player: PlayerManager = blackboard["player"] var player: PlayerManager = blackboard["player"]
var tilemap_navigation: TilemapNavigation = blackboard["navigation"] var navigation: TilemapNavigation = blackboard["navigation"]
var path: Array[Vector2i] = tilemap_navigation.find_path_allow_neighbors(player.board_position, world.tilemap_mouse_position(), player.view_distance) # var path: Array[Vector2i] = navigation.find_path_allow_neighbors(player.board_position, world.tilemap_mouse_position(), player.view_distance)
var path: Array[Vector2i] = navigation.cached_path_allow_neighbors(blackboard, "path_to_boat_part", world.tilemap_mouse_position(), player.view_distance * 1.4)
if len(path) == 0: if len(path) == 0:
status = FAILURE status = FAILURE

View File

@ -4,10 +4,13 @@ extends Node2D
static var game_manager: GameManager static var game_manager: GameManager
static var world: World static var world: World
# #
enum LineType { SEARCH_BASE, SEARCH_SELECTED } enum LineType { SEARCH_BASE, SEARCH_SELECTED, SEARCH_FAILED }
enum CircleType { PLAYER_VIEW, GOAL_CONSIDERATION, GOAL, BOAT_PART }
# #
# Dictionary[Array[Vector2i], LineType] ([from, to], line_type) # Dictionary[Array[Vector2i], LineType] ([from, to], line_type)
static var draw_lines: Dictionary = {} static var draw_lines: Dictionary = {}
# Dictionary[Array[Vector2i], CircleType] ([center, radius], circle_type)
static var draw_circles: Dictionary = {}
static func add_line(from: Vector2i, to: Vector2i, line_type: LineType) -> void: static func add_line(from: Vector2i, to: Vector2i, line_type: LineType) -> void:
@ -20,8 +23,19 @@ static func add_line_tileset(from: Vector2i, to: Vector2i, line_type: LineType)
draw_lines[[from_tileset, to_tileset]] = line_type draw_lines[[from_tileset, to_tileset]] = line_type
static func add_circle(center: Vector2i, radius: int, circle_type: CircleType) -> void:
draw_circles[[center, radius]] = circle_type
static func add_circle_tileset(center: Vector2i, radius: int, circle_type: CircleType) -> void:
var center_tileset: Vector2i = world.tilemap_ground.cell_to_local(center)
radius *= world.tilemap_ground.tilemap.tile_set.tile_size.x
draw_circles[[center_tileset, radius]] = circle_type
func game_tick_start(): func game_tick_start():
draw_lines.clear() draw_lines.clear()
draw_circles.clear()
func game_tick_end(): func game_tick_end():
@ -29,7 +43,7 @@ func game_tick_end():
var label_font = Control.new().get_theme_default_font() var label_font = Control.new().get_theme_default_font()
@export var default_line_color: Color = Color("red") @export var default_color: Color = Color("red")
func _ready() -> void: func _ready() -> void:
@ -37,7 +51,20 @@ func _ready() -> void:
func _draw() -> void: func _draw() -> void:
# draw all draw_lines with their labels for key in draw_circles.keys():
var center: Vector2i = key[0]
var radius: int = key[1]
var circle_type: CircleType = draw_circles[key]
if circle_type == CircleType.PLAYER_VIEW:
draw_circle(center, radius, Color("green"), false, 2, true)
elif circle_type == CircleType.GOAL_CONSIDERATION:
draw_circle(center, radius, Color("yellow"), false, 2, true)
elif circle_type == CircleType.GOAL:
draw_circle(center, radius, Color("orange"), false, 2, true)
elif circle_type == CircleType.BOAT_PART:
draw_circle(center, radius, Color(255, 0, 0, 0.5), false, 1, true)
for key in draw_lines.keys(): for key in draw_lines.keys():
var from: Vector2i = key[0] var from: Vector2i = key[0]
var to: Vector2i = key[1] var to: Vector2i = key[1]
@ -47,6 +74,5 @@ func _draw() -> void:
draw_line(from, to, Color("blue"), 1) draw_line(from, to, Color("blue"), 1)
elif line_type == LineType.SEARCH_SELECTED: elif line_type == LineType.SEARCH_SELECTED:
draw_line(from, to, Color("green"), 2) draw_line(from, to, Color("green"), 2)
elif line_type == LineType.SEARCH_FAILED:
# var center: Vector2 = (from + to) / 2 draw_line(from, to, Color(255, 0, 0, 0.1), 1)
# draw_string(label_font, center, label, 0, -1, 12, text_color)

View File

@ -1,6 +1,8 @@
class_name TileMapLayerAccess class_name TileMapLayerAccess
extends Node extends Node
var tilemap_types: TileMapTileTypes = TileMapTileTypes.new()
#
var tilemap: TileMapLayer = null var tilemap: TileMapLayer = null
var sid: int = 0 var sid: int = 0
@ -11,21 +13,25 @@ func setup() -> void:
func get_cells_by_type( func get_cells_by_type(
atlas_coords: Vector2i, atlas_coords: Vector2i,
center: Vector2i = Vector2i(-1, -1), max_distance: int = 99999999 center: Vector2i = Vector2i(-1, -1), max_distance: int = 99999999,
record: bool = true
) -> Array[Vector2i]: ) -> Array[Vector2i]:
var tiles_with_type: Array[Vector2i] = tilemap.get_used_cells_by_id(sid, atlas_coords) var tiles_with_type: Array[Vector2i] = tilemap.get_used_cells_by_id(sid, atlas_coords)
if max_distance < 99999999: if max_distance < 99999999:
var filtered_tiles: Array[Vector2i] = [] var filtered_tiles: Array[Vector2i] = []
for tile in tiles_with_type: for tile in tiles_with_type:
if TilemapNavigation.manhattan_distance(center, tile, true) <= max_distance: if TilemapNavigation.is_within_radius(center, tile, max_distance, record):
filtered_tiles.append(tile) filtered_tiles.append(tile)
return filtered_tiles return filtered_tiles
return tiles_with_type return tiles_with_type
const ANY_DISTANCE: int = 99999999
func get_cells_by_type_collection( func get_cells_by_type_collection(
atlas_coords: Array[Vector2i], atlas_coords: Array[Vector2i],
center: Vector2i = Vector2i(-1, -1), max_distance: int = 99999999 center: Vector2i = Vector2i(-1, -1), max_distance: int = 99999999
) -> Array[Vector2i]: ) -> Array[Vector2i]:
var tiles_with_type: Array[Vector2i] = [] var tiles_with_type: Array[Vector2i] = []
for coords in atlas_coords: for coords in atlas_coords:
@ -33,7 +39,7 @@ func get_cells_by_type_collection(
if max_distance < 99999999: if max_distance < 99999999:
var filtered_tiles: Array[Vector2i] = [] var filtered_tiles: Array[Vector2i] = []
for tile in tiles_with_type: for tile in tiles_with_type:
if TilemapNavigation.manhattan_distance(center, tile, true) <= max_distance: if TilemapNavigation.is_within_radius(center, tile, max_distance, true):
filtered_tiles.append(tile) filtered_tiles.append(tile)
return filtered_tiles return filtered_tiles
return tiles_with_type return tiles_with_type
@ -67,6 +73,8 @@ func get_cell(position: Vector2i) -> TileData:
func get_cell_atlas_coords(position: Vector2i) -> Vector2i: func get_cell_atlas_coords(position: Vector2i) -> Vector2i:
if not get_cell(position):
return tilemap_types.NO_TILE_FOUND
return tilemap.get_cell_atlas_coords(position) return tilemap.get_cell_atlas_coords(position)

View File

@ -10,7 +10,6 @@ const GROUND_WATER_SHALLOW: Vector2i = Vector2i(1, 0)
const GROUND_WATER_DEEP: Vector2i = Vector2i(2, 0) const GROUND_WATER_DEEP: Vector2i = Vector2i(2, 0)
const GROUND_SAND: Vector2i = Vector2i(3, 0) const GROUND_SAND: Vector2i = Vector2i(3, 0)
const GROUND_DOCK: Vector2i = Vector2i(3, 0) const GROUND_DOCK: Vector2i = Vector2i(3, 0)
const GROUND_SNOW: Vector2i = Vector2i(4,0)
# #
# objects, sid = 1 # objects, sid = 1
# NI = not interactive # NI = not interactive
@ -20,10 +19,10 @@ const OBJECT_NI_ROCK_1: Vector2i = Vector2i(2, 0)
# #
# I = interactive # I = interactive
# boat # boat
const OBJECT_I_BOAT_NO_ENIGNE: Vector2i = Vector2i(4, 4) const OBJECT_I_BOAT_NO_ENIGNE: Vector2i = Vector2i(0, 4)
const OBJECT_I_BOAT_WITH_ENGINE: Vector2i = Vector2i(6, 4) const OBJECT_I_BOAT_WITH_ENGINE: Vector2i = Vector2i(2, 4)
# boat parts # boat parts
const OBJECT_I_BOAT_PART_GENERIC: Vector2i = Vector2i(4584, 5234) const OBJECT_I_BOAT_PART_GENERIC: Vector2i = Vector2i(1, 1)
const OBJECT_I_BOAT_PART_ENGINE: Vector2i = Vector2i(0, 1) const OBJECT_I_BOAT_PART_ENGINE: Vector2i = Vector2i(0, 1)
const OBJECT_I_BOAT_PART_FUEL: Vector2i = Vector2i(1, 1) const OBJECT_I_BOAT_PART_FUEL: Vector2i = Vector2i(1, 1)
const OBJECT_I_BOAT_PART_ANCHOR: Vector2i = Vector2i(2, 1) const OBJECT_I_BOAT_PART_ANCHOR: Vector2i = Vector2i(2, 1)
@ -53,6 +52,7 @@ const OBJECT_COLLECTION_BOAT_PARTS: Array[Vector2i] = [ # @formatter:off
OBJECT_I_BOAT_PART_CHEST, OBJECT_I_BOAT_PART_GEARS, OBJECT_I_BOAT_PART_MEDIKIT, OBJECT_I_BOAT_PART_CHEST, OBJECT_I_BOAT_PART_GEARS, OBJECT_I_BOAT_PART_MEDIKIT,
OBJECT_I_BOAT_PART_PADDLE, OBJECT_I_BOAT_PART_GAS_STOVE OBJECT_I_BOAT_PART_PADDLE, OBJECT_I_BOAT_PART_GAS_STOVE
] # @formatter:on ] # @formatter:on
const OBJECT_COLLECTION_BOAT: Array[Vector2i] = [OBJECT_I_BOAT_NO_ENIGNE, OBJECT_I_BOAT_WITH_ENGINE]
# #
# temperature, sid = 2 # temperature, sid = 2
const TEMPERATURE_NORMAL: Vector2i = Vector2i(2, 0) const TEMPERATURE_NORMAL: Vector2i = Vector2i(2, 0)
@ -83,3 +83,6 @@ func player_sprite_from_direction(direction: Vector2i) -> Vector2i:
if direction == Vector2i(1, 0): if direction == Vector2i(1, 0):
return PLAYER_RIGHT return PLAYER_RIGHT
return PLAYER_DOWN return PLAYER_DOWN
func is_part_of_collection(collection: Array[Vector2i], item: Vector2i) -> bool:
return collection.find(item) != -1

View File

@ -32,15 +32,18 @@ func game_tick_end() -> void:
world.tilemap_nav_vis.set_cell(chosen_path[chosen_path.size() - 1], tilemap_types.NAVIGATION_TARGET) world.tilemap_nav_vis.set_cell(chosen_path[chosen_path.size() - 1], tilemap_types.NAVIGATION_TARGET)
func is_within_radius(position: Vector2i, center: Vector2i, radius: int, record: bool = false) -> bool: static func is_within_radius(position: Vector2i, center: Vector2i, radius: int, record: bool = false) -> bool:
return TilemapNavigation.manhattan_distance(position, center, record) <= radius var is_within: bool = TilemapNavigation.manhattan_distance(position, center) <= radius
static func manhattan_distance(a: Vector2i, b: Vector2i, record: bool = false) -> int:
var dist: int = abs(a.x - b.x) + abs(a.y - b.y)
if record: if record:
StepVisualization.add_line_tileset(a, b, StepVisualization.LineType.SEARCH_BASE) if is_within:
return dist StepVisualization.add_line_tileset(center, position, StepVisualization.LineType.SEARCH_BASE)
else:
StepVisualization.add_line_tileset(center, position, StepVisualization.LineType.SEARCH_FAILED)
return is_within
static func manhattan_distance(a: Vector2i, b: Vector2i) -> int:
return abs(a.x - b.x) + abs(a.y - b.y)
func manhattan_distance_closest(options: Array[Vector2i], target: Vector2i) -> Vector2i: func manhattan_distance_closest(options: Array[Vector2i], target: Vector2i) -> Vector2i:
@ -91,6 +94,46 @@ func has_arrived(position: Vector2i, path: Array[Vector2i]) -> bool:
return path.size() > 0 and path[path.size() - 1] == position return path.size() > 0 and path[path.size() - 1] == position
func path_still_valid(require_on_path: Vector2i, require_close_end: Vector2i, path: Array[Vector2i]) -> bool:
# check if:
# - player is on the path
# - target is close to the last position in the path (<= 1 step away)
# - all positions are still walkable
if not require_on_path == tilemap_types.NO_TILE_FOUND and not path.has(require_on_path):
return false
if not require_close_end == tilemap_types.NO_TILE_FOUND and (path.size() == 0 or not is_within_radius(require_close_end, path[path.size() - 1], 1)):
return false
for pos in path:
if not world.is_walkable(pos):
return false
return true
func cached_path_allow_neighbors(blackboard: Dictionary, path_key: String, target: Vector2i, max_radius: int = -1) -> Array[Vector2i]:
var player: PlayerManager = blackboard["player"]
if blackboard["cached_paths"].has(path_key):
# clear ALL other that are not the current path
for key in blackboard["cached_paths"].keys():
if key != path_key:
blackboard["cached_paths"].erase(key)
# check if the path is still valid
if path_still_valid(player.board_position, target, blackboard["cached_paths"][path_key]):
return blackboard["cached_paths"][path_key]
else:
print("Cached path is invalid, recalculating for ", target, " ", path_key)
blackboard["cached_paths"].erase(path_key)
StepVisualization.add_line_tileset(player.board_position, target, StepVisualization.LineType.SEARCH_SELECTED)
var path: Array[Vector2i] = find_path_allow_neighbors(player.board_position, target, max_radius)
if path.size() > 0:
blackboard["cached_paths"][path_key] = path
return path
func find_path(start_position: Vector2i, end_position: Vector2i, max_radius: int = -1) -> Array[Vector2i]: func find_path(start_position: Vector2i, end_position: Vector2i, max_radius: int = -1) -> Array[Vector2i]:
var path: Array[Vector2i] = _find_path_internal(start_position, end_position, max_radius) var path: Array[Vector2i] = _find_path_internal(start_position, end_position, max_radius)
if path.size() > 0: if path.size() > 0:

View File

@ -56,6 +56,11 @@ func tilemap_mouse_position() -> Vector2i:
func find_item_drop_location(center_pos: Vector2i) -> Vector2i: func find_item_drop_location(center_pos: Vector2i) -> Vector2i:
for x in range(center_pos.x - 1, center_pos.x + 1):
for y in range(center_pos.y - 1, center_pos.y + 1):
var check_pos: Vector2i = Vector2i(x, y)
if not tilemap_interactive.get_cell(check_pos) and is_walkable(check_pos):
return check_pos
for x in range(center_pos.x - 2, center_pos.x + 2): for x in range(center_pos.x - 2, center_pos.x + 2):
for y in range(center_pos.y - 2, center_pos.y + 2): for y in range(center_pos.y - 2, center_pos.y + 2):
var check_pos: Vector2i = Vector2i(x, y) var check_pos: Vector2i = Vector2i(x, y)
@ -74,6 +79,7 @@ func is_walkable(position: Vector2i) -> bool:
func game_tick_start() -> void: func game_tick_start() -> void:
step_visualizer.game_tick_start() step_visualizer.game_tick_start()
camp_manager.game_tick_start()
# refill empty bushes # refill empty bushes
var empty_bushes: Array[Vector2i] = tilemap_interactive.get_cells_by_type(tilemap_types.OBJECT_I_EMPTY_BUSH) var empty_bushes: Array[Vector2i] = tilemap_interactive.get_cells_by_type(tilemap_types.OBJECT_I_EMPTY_BUSH)
@ -87,6 +93,12 @@ func game_tick_start() -> void:
if randf() < 0.01: if randf() < 0.01:
tilemap_interactive.set_cell(tree, tilemap_types.OBJECT_I_TREE_FULL) tilemap_interactive.set_cell(tree, tilemap_types.OBJECT_I_TREE_FULL)
# mark all boat parts on the map
var boat_parts: Array[Vector2i] = tilemap_interactive.get_cells_by_type_collection(tilemap_types.OBJECT_COLLECTION_BOAT_PARTS)
for part in boat_parts:
StepVisualization.add_circle_tileset(part, 1, StepVisualization.CircleType.BOAT_PART)
func game_tick_end() -> void: func game_tick_end() -> void:
step_visualizer.game_tick_end() step_visualizer.game_tick_end()
camp_manager.game_tick_end()

View File

@ -6,9 +6,17 @@ var tilemap_types: TileMapTileTypes = TileMapTileTypes.new()
var game_manager: GameManager = null var game_manager: GameManager = null
var tilemap_interactive: TileMapLayerAccess = null var tilemap_interactive: TileMapLayerAccess = null
# #
var items: Array[Vector2i] = [] var camp_items: Array[Vector2i] = []
var camp: Vector2i = Vector2i(0, 0) var boat_items: Array[Vector2i] = []
var campfire: Vector2i = Vector2i(0, 0) var camp: Vector2i = tilemap_types.EMPTY
var campfire: Vector2i = tilemap_types.EMPTY
var boat_build_location: Vector2i = tilemap_types.EMPTY
var boat_leave_location: Vector2i = tilemap_types.EMPTY
#
var time_of_day: int = 0
var day_length: int = 1000
@export var required_boat_parts: int = 8
func setup() -> void: func setup() -> void:
@ -18,30 +26,62 @@ func setup() -> void:
camp = camp_locations[0] camp = camp_locations[0]
else: else:
push_error("No camp location found on tilemap") push_error("No camp location found on tilemap")
var firepit_locations: Array[Vector2i] = tilemap_interactive.get_cells_by_type_collection(tilemap_types.OBJECT_COLLECTION_FIREPIT) var firepit_locations: Array[Vector2i] = tilemap_interactive.get_cells_by_type_collection(tilemap_types.OBJECT_COLLECTION_FIREPIT)
if len(firepit_locations) > 0: if len(firepit_locations) > 0:
campfire = firepit_locations[0] campfire = firepit_locations[0]
else: else:
push_error("No firepit location found on tilemap") push_error("No firepit location found on tilemap")
print("CampManager: camp=", camp, " campfire=", campfire)
var boat_build_locations: Array[Vector2i] = tilemap_interactive.get_cells_by_type(tilemap_types.OBJECT_I_BOAT_NO_ENIGNE)
if len(boat_build_locations) > 0:
boat_build_location = boat_build_locations[0]
else:
push_error("No boat build location found on tilemap")
var boat_leave_locations: Array[Vector2i] = tilemap_interactive.get_cells_by_type(tilemap_types.OBJECT_I_BOAT_WITH_ENGINE)
if len(boat_leave_locations) > 0:
boat_leave_location = boat_leave_locations[0]
tilemap_interactive.set_cell(boat_leave_location, tilemap_types.EMPTY)
else:
push_error("No boat leave location found on tilemap")
print("CampManager: camp=", camp, " campfire=", campfire, " boat_build_location=", boat_build_location, " boat_leave_location=", boat_leave_location)
tilemap_interactive.set_cell(campfire, tilemap_types.OBJECT_I_FIREPIT_OFF) tilemap_interactive.set_cell(campfire, tilemap_types.OBJECT_I_FIREPIT_OFF)
func game_tick_start() -> void:
time_of_day += 1
func game_tick_end() -> void:
if time_of_day == day_length:
EventsTracker.track(EventsTracker.Event.TIME_SUNDOWN)
func is_sundown() -> bool:
return time_of_day >= day_length
func camp_contains_enough_sticks_to_light_campfire() -> bool:
return camp_item_count(tilemap_types.OBJECT_I_STICK) >= 2
func camp_contains_item(item: Vector2i) -> bool: func camp_contains_item(item: Vector2i) -> bool:
return items.find(item) != -1 return camp_items.find(item) != -1
func camp_contains_item_collection(item: Array[Vector2i]) -> bool: func camp_contains_item_collection(item: Array[Vector2i]) -> bool:
for i in item: for i in item:
if items.find(i) == -1: if camp_items.find(i) == -1:
return false return false
return true return true
func camp_item_count(item: Vector2i) -> int: func camp_item_count(item: Vector2i) -> int:
var count: int = 0 var count: int = 0
for i in items: for i in camp_items:
if i == item: if i == item:
count += 1 count += 1
return count return count
@ -49,7 +89,7 @@ func camp_item_count(item: Vector2i) -> int:
func camp_item_collection_count(item: Array[Vector2i]) -> int: func camp_item_collection_count(item: Array[Vector2i]) -> int:
var count: int = 0 var count: int = 0
for i in items: for i in camp_items:
if item.find(i) != -1: if item.find(i) != -1:
count += 1 count += 1
return count return count
@ -62,9 +102,9 @@ func camp_take_item(item: Vector2i, count: int = 1) -> bool:
return false return false
var taken: int = 0 var taken: int = 0
for i in range(items.size()): for i in range(camp_items.size() - 1, -1, -1):
if items[i] == item: if camp_items[i] == item:
items.remove_at(i) camp_items.remove_at(i)
taken += 1 taken += 1
if taken == count: if taken == count:
break break
@ -74,7 +114,7 @@ func camp_take_item(item: Vector2i, count: int = 1) -> bool:
func camp_add_item(item: Vector2i) -> void: func camp_add_item(item: Vector2i) -> void:
items.append(item) camp_items.append(item)
EventsTracker.track(EventsTracker.Event.CAMP_ADDED_ITEM, {"item": item, "count": 1, "new_count": camp_item_count(item)}) EventsTracker.track(EventsTracker.Event.CAMP_ADDED_ITEM, {"item": item, "count": 1, "new_count": camp_item_count(item)})
@ -93,11 +133,52 @@ func campfire_extinguish() -> void:
tilemap_interactive.set_cell(campfire, tilemap_types.OBJECT_I_FIREPIT_OFF) tilemap_interactive.set_cell(campfire, tilemap_types.OBJECT_I_FIREPIT_OFF)
EventsTracker.track(EventsTracker.Event.CAMPFIRE_EXTINGUISHED) EventsTracker.track(EventsTracker.Event.CAMPFIRE_EXTINGUISHED)
var is_sleep_active: bool = false
func sleep_effect() -> void: func sleep_effect() -> void:
if is_sleep_active:
return
is_sleep_active = true
EventsTracker.track(EventsTracker.Event.SLEEP) EventsTracker.track(EventsTracker.Event.SLEEP)
game_manager.camera.go_to_zooming(game_manager.world.tilemap_player.cell_to_local(camp), 3) game_manager.camera.go_to_zooming(game_manager.world.tilemap_player.cell_to_local(camp), 3)
var tween_in: Tween = game_manager.world.get_tree().create_tween() var tween_in: Tween = game_manager.world.get_tree().create_tween()
tween_in.tween_method(game_manager.camera.set_vignette_intensity, 0.0, 1.0, 2.0).set_delay(0.5) tween_in.tween_method(game_manager.camera.set_vignette_intensity, 0.0, 1.0, 2.0).set_delay(0.5)
var tween_out: Tween = game_manager.world.get_tree().create_tween() var tween_out: Tween = game_manager.world.get_tree().create_tween()
tween_out.tween_method(game_manager.camera.set_vignette_intensity, 1.0, 0.0, 2.0).set_delay(4.0) tween_out.tween_method(game_manager.camera.set_vignette_intensity, 1.0, 0.0, 2.0).set_delay(4.0)
await game_manager.world.get_tree().create_timer(6.0).timeout
print("Sleep effect done")
is_sleep_active = false
time_of_day = 0
func populate_camp_visualization(boat_ui: HBoxContainer, camp_ui: HBoxContainer) -> void:
for child in boat_ui.get_children():
if child.name != "HeightLabel":
boat_ui.remove_child(child)
for boat_part in boat_items:
var texture: TextureRect = create_item_texture(boat_part)
boat_ui.add_child(texture)
for child in camp_ui.get_children():
if child.name != "HeightLabel":
camp_ui.remove_child(child)
for boat_part in camp_items:
var texture: TextureRect = create_item_texture(boat_part)
camp_ui.add_child(texture)
func create_item_texture(item: Vector2i) -> TextureRect:
var item_texture: Texture = game_manager.world.tilemap_interactive.get_cell_texture(item)
if item_texture:
var item_texture_rect: TextureRect = TextureRect.new()
item_texture_rect.texture = item_texture
item_texture_rect.set_expand_mode(TextureRect.EXPAND_FIT_WIDTH)
item_texture_rect.set_stretch_mode(TextureRect.STRETCH_KEEP_ASPECT_CENTERED)
return item_texture_rect
return null

View File

@ -0,0 +1,158 @@
class_name BehaviorTreeVisualizer
extends Window
const D_TREE_NODE: PackedScene = preload("res://scripts/visualization/d_tree_node.tscn")
@onready var graph_edit: GraphEdit = %GraphEdit
#
var behavior_tree: BehaviorTree
#
var x_spacing: int = 430
var y_spacing: int = 100
#
var all_nodes: Array[DTreeNode] = []
# Dictionary[Task, DTreeNode]
var task_to_node: Dictionary = {}
var parent_nodes: Dictionary = {}
#
var current_lowest_node_pos: Vector2 = Vector2(0, 0)
func _physics_process(delta: float) -> void:
if Input.is_action_just_pressed("toggle_graph_edit"):
if is_visible():
hide()
else:
show()
func build_tree() -> void:
if not behavior_tree:
push_error("No behavior tree set.")
return
# reset current visualization
graph_edit.clear_connections()
for node in all_nodes:
node.queue_free()
all_nodes.clear()
var root_task: Task = behavior_tree.behavior_tree
if not root_task:
push_error("Root behavior tree node is null.")
return
current_lowest_node_pos = Vector2(0, 0)
build_tree_from_task(root_task, 0)
func build_tree_from_task(task: Task, depth: int) -> DTreeNode:
var child_nodes: Array[DTreeNode] = []
var child_node_positions: Array[Vector2] = []
for child in task.get_children():
var child_node: DTreeNode = build_tree_from_task(child, depth + 1)
child_nodes.append(child_node)
child_node_positions.append(child_node.position_offset)
var current_node: DTreeNode = D_TREE_NODE.instantiate()
graph_edit.add_child(current_node)
task_to_node[task] = current_node
current_node.name = task.get_name() + str(randf())
current_node.title = human_readable_task_name(task.get_name())
current_node.add_label("status", true, true)
all_nodes.append(current_node)
for child in child_nodes:
graph_edit.connect_node(current_node.name, 0, child.name, 0)
parent_nodes[child] = current_node
if child_node_positions.size() > 0:
var average_position: Vector2 = Vector2(0, 0)
for pos in child_node_positions:
average_position += pos
average_position /= child_node_positions.size()
current_node.position_offset = average_position - Vector2(x_spacing, 0)
# print("as parent: ", current_node.name, " ", current_node.position_offset, " ", depth, " ", child_node_positions)
else:
current_node.position_offset = Vector2(current_lowest_node_pos)
current_node.position_offset.x += depth * x_spacing
current_lowest_node_pos.y += y_spacing
# print("as leaf: ", current_node.name, " ", current_node.position_offset, " ", depth)
pass
return current_node
func update_task_statuses(blackboard: Dictionary) -> void:
for t in task_to_node.keys():
var task: Task = t as Task
var node: DTreeNode = task_to_node[task]
var status: int = task.status
var clear_status: String = task.clear_status()
var status_reason: String = task.status_reason
if status_reason != "":
node.set_label_text(0, status_reason)
else:
node.set_label_text(0, clear_status)
node.set_body_color(node.color_normal)
if status == Task.RUNNING or status == Task.SUCCESS or status == Task.SUCCESS_STOP:
node.set_body_color(node.color_success)
if blackboard.has("current_task"):
var selected_node = task_to_node[blackboard["current_task"]]
if selected_node:
center_view_on_position(selected_node.position_offset)
selected_node.set_body_color(selected_node.color_executed)
while parent_nodes.has(selected_node):
selected_node = parent_nodes[selected_node]
selected_node.set_body_color(selected_node.color_checked)
func center_view_on_position(target_position: Vector2) -> void:
var graph_edit_size: Vector2 = graph_edit.size
var zoom: float = graph_edit.zoom
var offset_x: float = target_position.x * zoom - graph_edit_size.x / 2
var offset_y: float = target_position.y * zoom - graph_edit_size.y / 2
graph_edit.scroll_offset = Vector2(offset_x, offset_y)
func human_readable_task_name(input: String) -> String:
var prefixes: Dictionary = {"sl_": "Selector: ", "sq_": "Sequence: ", "Task": ""}
var selected_prefix: String = ""
for prefix in prefixes.keys():
if input.begins_with(prefix):
selected_prefix = prefix
input = input.substr(prefix.length())
break
var words: Array[Variant] = []
var current_word: String = ""
for i in range(input.length()):
var character: String = input[i]
if character.to_upper() == character and current_word.length() > 0:
words.append(current_word)
current_word = "" + character.to_lower()
elif character == "_":
if current_word.length() > 0:
words.append(current_word)
current_word = ""
else:
current_word += character.to_lower()
if current_word.length() > 0:
words.append(current_word)
var result: String = " ".join(words)
if selected_prefix in prefixes and prefixes[selected_prefix] != "":
result = prefixes[selected_prefix] + result
return result
func _on_close_requested() -> void:
hide()

View File

@ -0,0 +1,45 @@
class_name DTreeNode
extends GraphNode
var left_slots: Array[String] = []
var right_slots: Array[String] = []
var color_normal: StyleBoxFlat = StyleBoxFlat.new()
var color_success: StyleBoxFlat = StyleBoxFlat.new()
var color_executed: StyleBoxFlat = StyleBoxFlat.new()
var color_checked: StyleBoxFlat = StyleBoxFlat.new()
func _ready() -> void:
color_normal.bg_color = Color(1.0, 1.0, 1.0)
color_success.bg_color = Color(0.25490198, 0.78431374, 0.9529412)
color_executed.bg_color = Color(0.5058824, 0.9529412, 0.30588236)
color_checked.bg_color = Color(0.6509804, 0.9254902, 0.5372549)
func add_label(label_text: String, left: bool, right: bool) -> Vector3i:
var new_label: Label = Label.new()
new_label.text = label_text
new_label.add_theme_color_override("font_color", Color(0, 0, 0, 1))
self.add_child(new_label)
var child_index: int = self.get_child_count() - 1
if left:
self.set_slot_enabled_left(child_index, true)
self.set_slot_color_left(child_index, Color(0.9, 0.9, 0.9, 1))
left_slots.append(label_text)
if right:
self.set_slot_enabled_right(child_index, true)
self.set_slot_color_right(child_index, Color(0.9, 0.9, 0.9, 1))
right_slots.append(label_text)
# the port index is counted separately from the left and right slots
return Vector3i(child_index, left_slots.size() - 1, right_slots.size() - 1)
func set_body_color(color: StyleBoxFlat) -> void:
self.add_theme_stylebox_override("panel", color)
func set_label_text(label_index: int, text: String) -> void:
var label: Label = self.get_child(label_index) as Label
label.text = text

View File

@ -0,0 +1,18 @@
[gd_scene load_steps=4 format=3 uid="uid://bmv75j2e8xc6n"]
[ext_resource type="Script" path="res://scripts/visualization/d_tree_node.gd" id="1_o2ffa"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k54lu"]
bg_color = Color(0.95158, 0.95158, 0.95158, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dmqj2"]
bg_color = Color(0.6, 0.39, 0.39, 1)
[node name="DTreeNode" type="GraphNode"]
offset_right = 41.0
offset_bottom = 23.0
mouse_filter = 1
theme_override_styles/panel = SubResource("StyleBoxFlat_k54lu")
theme_override_styles/titlebar = SubResource("StyleBoxFlat_dmqj2")
title = "TITEL"
script = ExtResource("1_o2ffa")

View File

@ -0,0 +1,60 @@
class_name InfoPanel
extends VBoxContainer
var values: Dictionary = {}
var last_values: Dictionary = {}
var value_order: Array = []
func _ready() -> void:
hide()
func to_str(value) -> String:
if value is float:
return str(round(value * 100) / 100)
elif value is Vector2:
return "(" + to_str(value.x) + ", " + to_str(value.y) + ")"
elif value is Vector3:
return "(" + to_str(value.x) + ", " + to_str(value.y) + ", " + to_str(value.z) + ")"
elif value is Vector4:
return "(" + to_str(value.x) + ", " + to_str(value.y) + ", " + to_str(value.z) + ", " + to_str(value.w) + ")"
return str(value)
func _process(delta: float) -> void:
if Input.is_action_just_pressed("debug_vis_2"):
if is_visible():
hide()
else:
show()
for child in self.get_children():
if child is Label:
child.queue_free()
# 1. Update and Track Order of Values
for value in values.keys():
if value in value_order:
# Move to the end (most recent)
value_order.erase(value)
value_order.append(value)
# 2. Display Current Values (Most Recent First)
for value in value_order:
if values.has(value):
var new_label: Label = Label.new()
new_label.text = value + ": " + to_str(values[value])
new_label.add_theme_color_override("font_color", Color(0, 0, 0, 1))
self.add_child(new_label)
last_values[value] = values[value]
# 3. Display Old Values
for value in last_values.keys():
if not values.has(value):
var new_label: Label = Label.new()
new_label.text = value + ": " + to_str(last_values[value])
new_label.add_theme_color_override("font_color", Color(0.5, 0.5, 0.5, 1))
self.add_child(new_label)
values.clear()