Made exploration task smarter by canceling if was close once

pull/5/head
Yan Wittmann 2025-01-14 12:01:11 +01:00
parent a75eb4aa1d
commit 624c14150e
6 changed files with 182 additions and 161 deletions

View File

@ -17,19 +17,24 @@ texture = ExtResource("1_ukrsa")
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_0 = true
3:0/8/custom_data_2 = 1
1:2/0 = 0 1:2/0 = 0
3:2/next_alternative_id = 7 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_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 = 2 0:0/0/custom_data_2 = 2
3:1/next_alternative_id = 4 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_0 = true
3:1/0/custom_data_2 = 1
3:1/2 = 2 3:1/2 = 2
3:1/2/custom_data_0 = true 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 = 0

File diff suppressed because one or more lines are too long

View File

@ -17,6 +17,7 @@ enum Event {
GAME_STATE_WIN, GAME_STATE_WIN,
NEW_EXPLORATION_GOAL, NEW_EXPLORATION_GOAL,
EXPLORATION_GOAL_REACHED, EXPLORATION_GOAL_REACHED,
EXPLORATION_GOAL_CLOSE_ENOUGH,
TEMPERATURE_COLD, TEMPERATURE_COLD,
TIME_SUNDOWN, TIME_SUNDOWN,
}; };
@ -82,6 +83,8 @@ static func populate_visual_log_create_label(event: TrackedEvent, container: Con
text = "New goal " + str(params["goal"]) text = "New goal " + str(params["goal"])
elif event_id == Event.EXPLORATION_GOAL_REACHED: elif event_id == Event.EXPLORATION_GOAL_REACHED:
text = "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: elif event_id == Event.TEMPERATURE_COLD:
text = "Temperature is cold: -" + str(params["temperature"]) text = "Temperature is cold: -" + str(params["temperature"])
elif event_id == Event.TIME_SUNDOWN: elif event_id == Event.TIME_SUNDOWN:

View File

@ -65,14 +65,9 @@ func _process(delta: float) -> void:
if Input.is_action_just_pressed("key_1"): if Input.is_action_just_pressed("key_1"):
get_tree().reload_current_scene() get_tree().reload_current_scene()
if Input.is_action_just_pressed("key_2"): if Input.is_action_just_pressed("key_2"):
var exploration_task: TaskPlannedExploration = player.behavior_tree.find_task_by_name("TaskPlannedExploration") player.exploration_task.current_goal = world.tilemap_ground.local_to_cell(world.get_local_mouse_position())
if exploration_task: player.behavior_tree.blackboard["cached_paths"] = {}
exploration_task.current_goal = world.tilemap_ground.local_to_cell(world.get_local_mouse_position()) player.behavior_tree.blackboard["path"] = []
player.behavior_tree.blackboard["cached_paths"] = {}
player.behavior_tree.blackboard["path"] = []
else:
print("[ERROR] TaskPlannedExploration not found")
push_error("TaskPlannedExploration not found")
if intro_image.is_visible(): if intro_image.is_visible():
intro_image.set_scale(calculate_scale(intro_image.texture.get_size())) intro_image.set_scale(calculate_scale(intro_image.texture.get_size()))
@ -116,6 +111,8 @@ func _on_game_tick_timeout() -> void:
player.game_tick() player.game_tick()
apply_player_exploration_distance()
tree_visualizer.update_task_statuses(player.behavior_tree.blackboard) tree_visualizer.update_task_statuses(player.behavior_tree.blackboard)
tilemap_navigation.game_tick_end() tilemap_navigation.game_tick_end()
@ -157,6 +154,10 @@ func camera_follow_player() -> void:
camera.go_to_zooming(avg_position, zoom_level) 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: func distance_to_zoom_level(distance: float) -> float:
var a: float = 862.08 var a: float = 862.08
var b: float = 274.13 var b: float = 274.13
@ -214,8 +215,8 @@ func calculate_display_time_of_day(current_time: int, day_length: int) -> String
func calculate_time_of_day_color(current_time: int, day_length: int) -> Color: func calculate_time_of_day_color(current_time: int, day_length: int) -> Color:
var start: Color = Color(1, 1, 0) var start: Color = Color(1, 1, 0)
var end: Color = Color(1, 0, 0) var end: Color = Color(1, 0, 0)
var progress: float = float(current_time) / day_length var progress: float = float(current_time) / day_length
progress = clamp(progress, 0, 1) progress = clamp(progress, 0, 1)
progress = pow(progress, 2) progress = pow(progress, 2)

View File

@ -20,226 +20,229 @@ var game_manager: GameManager = null
var last_board_position: Vector2i = Vector2i(0, 0) var last_board_position: Vector2i = Vector2i(0, 0)
var board_position: Vector2i = Vector2i(0, 0): var board_position: Vector2i = Vector2i(0, 0):
set(value): set(value):
last_board_position = board_position last_board_position = board_position
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 = {} var player_memory: Dictionary = {}
func _ready() -> void: func _ready() -> void:
call_deferred("defer_ready") call_deferred("defer_ready")
func defer_ready() -> void: func defer_ready() -> void:
behavior_tree.game_manager = game_manager behavior_tree.game_manager = game_manager
var player_start_position: Array[Vector2i] = game_manager.world.tilemap_player.get_cells_by_type(tilemap_types.PLAYER_DOWN) var player_start_position: Array[Vector2i] = game_manager.world.tilemap_player.get_cells_by_type(tilemap_types.PLAYER_DOWN)
if len(player_start_position) > 0: if len(player_start_position) > 0:
board_position = player_start_position[0] board_position = player_start_position[0]
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_1"): 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))
pick_up_item(Vector2i(9, 9)) pick_up_item(Vector2i(9, 9))
if Input.is_action_just_pressed("key_4"): if Input.is_action_just_pressed("key_4"):
var nearest: Vector2i = find_nearest_object([game_manager.world.tilemap_types.OBJECT_I_TREE_FULL]) var nearest: Vector2i = find_nearest_object([game_manager.world.tilemap_types.OBJECT_I_TREE_FULL])
# nearest.x = nearest.x - 1 # nearest.x = nearest.x - 1
walk_towards(nearest) walk_towards(nearest)
# SECTION: board access/mangement # SECTION: board access/mangement
func update_board() -> void: func update_board() -> void:
game_manager.world.tilemap_player.clear_cells() game_manager.world.tilemap_player.clear_cells()
# decide what direction the player is facing tilemap_types.PLAYER_DOWN, ... # decide what direction the player is facing tilemap_types.PLAYER_DOWN, ...
var direction: Vector2i = find_direction(last_board_position, board_position) var direction: Vector2i = find_direction(last_board_position, board_position)
game_manager.world.tilemap_player.set_cell(board_position, tilemap_types.player_sprite_from_direction(direction)) game_manager.world.tilemap_player.set_cell(board_position, tilemap_types.player_sprite_from_direction(direction))
if inventory_slot and inventory_slot != tilemap_types.EMPTY: if inventory_slot and inventory_slot != tilemap_types.EMPTY:
%InventoryContentRect.texture = game_manager.world.tilemap_interactive.get_cell_texture(inventory_slot) %InventoryContentRect.texture = game_manager.world.tilemap_interactive.get_cell_texture(inventory_slot)
else: else:
%InventoryContentRect.texture = null %InventoryContentRect.texture = null
# SECTION: inventory system # SECTION: inventory system
func pick_up_item(tilemap_pos: Vector2i) -> void: func pick_up_item(tilemap_pos: Vector2i) -> void:
var pick_up_cell: TileData = game_manager.world.tilemap_interactive.get_cell(tilemap_pos) var pick_up_cell: TileData = game_manager.world.tilemap_interactive.get_cell(tilemap_pos)
if not pick_up_cell: if not pick_up_cell:
push_warning("Player trying to pick up item that does not exist at ", tilemap_pos) push_warning("Player trying to pick up item that does not exist at ", tilemap_pos)
return return
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 inventory contains item that needs to be transformed on dropping # 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 # 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 # 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
pick_up_item_type = tilemap_types.OBJECT_I_BERRY pick_up_item_type = tilemap_types.OBJECT_I_BERRY
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
# 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:
inventory_slot = pick_up_item_type inventory_slot = pick_up_item_type
if tile_after_pickup_transform: if tile_after_pickup_transform:
game_manager.world.tilemap_interactive.set_cell(tilemap_pos, tile_after_pickup_transform) game_manager.world.tilemap_interactive.set_cell(tilemap_pos, tile_after_pickup_transform)
else: else:
game_manager.world.tilemap_interactive.clear_cell(tilemap_pos) game_manager.world.tilemap_interactive.clear_cell(tilemap_pos)
print("Picked up item: ", pick_up_item_type) print("Picked up item: ", pick_up_item_type)
EventsTracker.track(EventsTracker.Event.PLAYER_PICKED_UP_ITEM, {"item": pick_up_item_type}) EventsTracker.track(EventsTracker.Event.PLAYER_PICKED_UP_ITEM, {"item": pick_up_item_type})
else: else:
# inventory is full, swap the item # inventory is full, swap the item
print("Inventory is full. Swapping item: ", inventory_slot, " with item: ", pick_up_item_type) print("Inventory is full. Swapping item: ", inventory_slot, " with item: ", pick_up_item_type)
EventsTracker.track(EventsTracker.Event.PLAYER_DROPPED_ITEM, {"item": inventory_slot}) EventsTracker.track(EventsTracker.Event.PLAYER_DROPPED_ITEM, {"item": inventory_slot})
EventsTracker.track(EventsTracker.Event.PLAYER_PICKED_UP_ITEM, {"item": pick_up_item_type}) EventsTracker.track(EventsTracker.Event.PLAYER_PICKED_UP_ITEM, {"item": pick_up_item_type})
if tile_after_pickup_transform: if tile_after_pickup_transform:
game_manager.world.tilemap_interactive.set_cell(tilemap_pos, tile_after_pickup_transform) game_manager.world.tilemap_interactive.set_cell(tilemap_pos, tile_after_pickup_transform)
var drop_location: Vector2i = game_manager.world.find_item_drop_location(tilemap_pos) var drop_location: Vector2i = game_manager.world.find_item_drop_location(tilemap_pos)
if drop_location != tilemap_types.EMPTY: if drop_location != tilemap_types.EMPTY:
game_manager.world.tilemap_interactive.set_cell(drop_location, tile_drop_item) game_manager.world.tilemap_interactive.set_cell(drop_location, tile_drop_item)
else: else:
push_warning("Could not find valid drop position for ", inventory_slot) push_warning("Could not find valid drop position for ", inventory_slot)
else: else:
game_manager.world.tilemap_interactive.set_cell(tilemap_pos, tile_drop_item) game_manager.world.tilemap_interactive.set_cell(tilemap_pos, tile_drop_item)
inventory_slot = pick_up_item_type inventory_slot = pick_up_item_type
# SECTION: player movement # SECTION: player movement
func walk_towards(position: Vector2i) -> void: func walk_towards(position: Vector2i) -> void:
var path: Array[Vector2i] = game_manager.tilemap_navigation.find_path(board_position, position) var path: Array[Vector2i] = game_manager.tilemap_navigation.find_path(board_position, position)
walk_along(path) walk_along(path)
func walk_along(path: Array[Vector2i]) -> void: func walk_along(path: Array[Vector2i]) -> void:
if len(path) > 1: if len(path) > 1:
var next_position: Vector2i var next_position: Vector2i
if path.has(board_position): if path.has(board_position):
var current_index: int = path.find(board_position) var current_index: int = path.find(board_position)
if current_index < path.size() - 1: if current_index < path.size() - 1:
next_position = path[current_index + 1] next_position = path[current_index + 1]
else: else:
next_position = path[1] next_position = path[1]
else: else:
next_position = path[1] next_position = path[1]
var direction: Vector2i = find_direction(board_position, next_position) var direction: Vector2i = find_direction(board_position, next_position)
move_player(direction) move_player(direction)
game_manager.tilemap_navigation.chosen_path = path game_manager.tilemap_navigation.chosen_path = path
else: else:
push_warning("walk_along path is empty") push_warning("walk_along path is empty")
func move_player(direction: Vector2i) -> void: func move_player(direction: Vector2i) -> void:
var new_position: Vector2 = board_position + direction var new_position: Vector2 = board_position + direction
if game_manager.world.is_walkable(new_position): if game_manager.world.is_walkable(new_position):
board_position = new_position board_position = new_position
else: else:
push_warning("Player trying to move to non-walkable position, prevented ", new_position) push_warning("Player trying to move to non-walkable position, prevented ", new_position)
func find_nearest_object(object_collection: Array[Vector2i]) -> Vector2i: func find_nearest_object(object_collection: Array[Vector2i]) -> Vector2i:
var object_positions: Array[Vector2i] = [] var object_positions: Array[Vector2i] = []
for obj in object_collection: for obj in object_collection:
object_positions.append_array(game_manager.world.tilemap_interactive.get_cells_by_type(obj)) object_positions.append_array(game_manager.world.tilemap_interactive.get_cells_by_type(obj))
if object_positions.size() == 0: if object_positions.size() == 0:
push_warning("No " + str(object_collection) + " found!") push_warning("No " + str(object_collection) + " found!")
return tilemap_types.NO_TILE_FOUND return tilemap_types.NO_TILE_FOUND
var closest_object: Vector2i = tilemap_types.NO_TILE_FOUND var closest_object: Vector2i = tilemap_types.NO_TILE_FOUND
var shortest_distance: float = 99999999 var shortest_distance: float = 99999999
for position in object_positions: for position in object_positions:
var distance: float = game_manager.tilemap_navigation.manhattan_distance(board_position, position) var distance: float = game_manager.tilemap_navigation.manhattan_distance(board_position, position)
if closest_object == tilemap_types.NO_TILE_FOUND or distance < shortest_distance: if closest_object == tilemap_types.NO_TILE_FOUND or distance < shortest_distance:
closest_object = position closest_object = position
shortest_distance = distance shortest_distance = distance
print("Find nearest " + str(object_collection) + " at:", closest_object) print("Find nearest " + str(object_collection) + " at:", closest_object)
return closest_object return closest_object
func find_direction(pos_a: Vector2i, pos_b: Vector2i) -> Vector2i: func find_direction(pos_a: Vector2i, pos_b: Vector2i) -> Vector2i:
var direction: Vector2i = Vector2i(0, 0) var direction: Vector2i = Vector2i(0, 0)
if pos_a.x < pos_b.x: if pos_a.x < pos_b.x:
direction.x = 1 direction.x = 1
elif pos_a.x > pos_b.x: elif pos_a.x > pos_b.x:
direction.x = -1 direction.x = -1
if pos_a.y < pos_b.y: if pos_a.y < pos_b.y:
direction.y = 1 direction.y = 1
elif pos_a.y > pos_b.y: elif pos_a.y > pos_b.y:
direction.y = -1 direction.y = -1
return direction return direction
# SECTION: game tick # SECTION: game tick
func get_current_temperature() -> int: func get_current_temperature() -> int:
return game_manager.world.tilemap_temperature.get_custom_data(board_position, "temperature", 0) as int return game_manager.world.tilemap_temperature.get_custom_data(board_position, "temperature", 0) as int
func tick_handle_temperature(cell_temperature: int) -> void: func tick_handle_temperature(cell_temperature: int) -> void:
if temperature_buff_timer > 0: if temperature_buff_timer > 0:
temperature_buff_timer -= 1 temperature_buff_timer -= 1
return return
if cell_temperature == 0: if cell_temperature == 0:
temperature_timer = 0 temperature_timer = 0
elif temperature_timer > temperature_endure: elif temperature_timer > temperature_endure:
temperature_timer += cell_temperature temperature_timer += cell_temperature
health -= temperature_damage health -= temperature_damage
func tick_handle_food(): func tick_handle_food():
if food > 0: if food > 0:
food -= 1 food -= 1
if food <= 0: if food <= 0:
health -= food_damage health -= food_damage
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) 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()
if health < 0: if health < 0:
game_manager.player_health_depleted() game_manager.player_health_depleted()
update_board() update_board()

View File

@ -1,8 +1,9 @@
class_name TaskPlannedExploration class_name TaskPlannedExploration
extends Task extends Task
var last_goals: Array[Vector2i] = [] var last_goals: Array[Vector2i] = []
var current_goal: Vector2i = tilemap_types.NO_TILE_FOUND var current_goal: Vector2i = tilemap_types.NO_TILE_FOUND
var closest_distance_to_goal: float = 99999999
func run(blackboard: Dictionary) -> void: func run(blackboard: Dictionary) -> void:
@ -10,6 +11,13 @@ func run(blackboard: Dictionary) -> void:
var player: PlayerManager = blackboard["player"] var player: PlayerManager = blackboard["player"]
var navigation: TilemapNavigation = blackboard["navigation"] 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 # check if player distance is < 2 to the current goal
if current_goal != tilemap_types.NO_TILE_FOUND: if current_goal != tilemap_types.NO_TILE_FOUND:
if TilemapNavigation.manhattan_distance(player.board_position, current_goal) < 3: if TilemapNavigation.manhattan_distance(player.board_position, current_goal) < 3:
@ -20,6 +28,7 @@ func run(blackboard: Dictionary) -> void:
find_new_goal(world, player, navigation) find_new_goal(world, player, navigation)
if current_goal != tilemap_types.NO_TILE_FOUND: if current_goal != tilemap_types.NO_TILE_FOUND:
EventsTracker.track(EventsTracker.Event.NEW_EXPLORATION_GOAL, {"goal": current_goal}) EventsTracker.track(EventsTracker.Event.NEW_EXPLORATION_GOAL, {"goal": current_goal})
closest_distance_to_goal = 99999999
if current_goal == tilemap_types.NO_TILE_FOUND: if current_goal == tilemap_types.NO_TILE_FOUND:
status = Task.FAILURE status = Task.FAILURE
@ -98,7 +107,7 @@ func determine_an_interesting_goal(world: World) -> Vector2i:
var picked_goal: Vector2i = Vector2i(0, 0) var picked_goal: Vector2i = Vector2i(0, 0)
for i in range(10): for i in range(10):
var check_position: Vector2i = last_walkable + Vector2i(randi_range(-10, 10), randi_range(-10, 10)) var check_position: Vector2i = last_walkable + Vector2i(randi_range(-10, 10), randi_range(-10, 10))
if world.is_walkable(check_position): if world.is_walkable(check_position) and world.tilemap_ground.get_custom_data(check_position, "cost", 999) < 7:
picked_goal = check_position picked_goal = check_position
break break