Refactored to use Vector4i. Added Spider/Grasshopper. TODO: Ant

This commit is contained in:
Sch1nken 2024-03-14 19:30:18 +01:00
parent ff4aa93845
commit 11f8a71f52
19 changed files with 482 additions and 326 deletions

View file

@ -13,6 +13,13 @@ const DIR_ALL: Array[Vector3] = [DIR_N, DIR_NE, DIR_SE, DIR_S, DIR_SW, DIR_NW]
#const size = Vector2(1, sqrt(3.0)/2.0)
# We can hover all of our tiles (will show on-hover shader)
# If we click on a placed tile, we do the following
# Check if we would split the hive -> deny selection
# Get possible movement tiles (via MovementBehaviour)
# If 0 -> Deny
# Filter remaining movement spots to not include hive-splitting moves(?)
const size: float = 0.5
var used_cells: Dictionary = {}
@ -26,30 +33,20 @@ class TileStorage:
# we use a vector4i for coordinates
# q r s y (layer)
func add_tile(tile: InsectTile, coords: CubeCoordinates, layer: int = 0) -> void:
tiles[Vector4i(coords.q, coords.r, coords.s, layer)] = tile
func add_tile(tile: InsectTile, coords: Vector4i) -> void:
tiles[coords] = tile
pass
func remove_tile(coords: CubeCoordinates, layer: int = 0) -> void:
func remove_tile(coords: Vector4i) -> void:
pass
func has_tile(coords: CubeCoordinates, layer: int = 0) -> bool:
return tiles.has(Vector4i(coords.q, coords.r, coords.s, layer))
func has_tile(coords: Vector4i) -> bool:
return tiles.has(coords)
func get_tile(coords: CubeCoordinates, layer: int = 0) -> InsectTile:
return tiles[Vector4i(coords.q, coords.r, coords.s, layer)]
func get_tile(coords: Vector4i) -> InsectTile:
return tiles[coords]
class CubeCoordinates:
var q: float
var r: float
var s: float
func _init(_q: float, _r: float, _s: float):
q = _q
r = _r
s = _s
class AxialCoordinates:
var q: float
var r: float
@ -68,7 +65,7 @@ func flat_hex_to_world_position(coords: AxialCoordinates) -> Vector2:
var y = size * (sqrt(3.0)/2.0 * coords.q + sqrt(3.0) * coords.r)
return Vector2(x, y)
func cube_to_world_pos(coords: CubeCoordinates) -> Vector2:
func cube_to_world_pos(coords: Vector4i) -> Vector2:
return flat_hex_to_world_position(cube_to_axial(coords))
#func world_to_hex_tile(world_pos: Vector3) -> Vector2:
@ -86,28 +83,28 @@ func world_to_hex_tile(coords: Vector2) -> AxialCoordinates:
var r = (-1.0/3.0 * coords.x + sqrt(3.0)/3.0 * coords.y) / size
return axial_round(AxialCoordinates.new(q, r))
func axial_to_cube(coords: AxialCoordinates) -> CubeCoordinates:
var q = coords.q
var r = coords.r
var s = -q-r
return CubeCoordinates.new(q, r, s)
func cube_to_axial(coords: CubeCoordinates) -> AxialCoordinates:
var q = coords.q
var r = coords.r
func cube_to_axial(coords: Vector4i) -> AxialCoordinates:
var q = coords.x
var r = coords.y
return AxialCoordinates.new(q, r)
func axial_round(coords: AxialCoordinates) -> AxialCoordinates:
return cube_to_axial(cube_round(axial_to_cube(coords)))
func cube_round(coords: CubeCoordinates) -> CubeCoordinates:
var q: float = round(coords.q)
var r: float = round(coords.r)
var s: float = round(coords.s)
func axial_to_cube(coords: AxialCoordinates) -> Vector4i:
var q = coords.q
var r = coords.r
var s = -q-r
return Vector4i(q, r, s, 0)
func cube_round(coords: Vector4i) -> Vector4i:
var q: float = round(coords.x)
var r: float = round(coords.y)
var s: float = round(coords.z)
var q_diff: float = abs(q - coords.q)
var r_diff: float = abs(r - coords.r)
var s_diff: float = abs(s - coords.s)
var q_diff: float = abs(q - coords.x)
var r_diff: float = abs(r - coords.y)
var s_diff: float = abs(s - coords.z)
if q_diff > r_diff and q_diff > s_diff:
q = -r-s
@ -116,7 +113,7 @@ func cube_round(coords: CubeCoordinates) -> CubeCoordinates:
else:
s = -q-r
return CubeCoordinates.new(q, r, s)
return Vector4i(q, r, s, 0)
@export var dragging_intersect_plane_normal: Vector3 = Vector3.UP
@export var dragging_intersect_plane_distance: float = 0.0
@ -126,105 +123,173 @@ func get_3d_pos(position2D: Vector2):
var placements: Dictionary = {}
func is_cell_empty(coords: CubeCoordinates) -> bool:
return !used_cells.has(Vector4i(coords.q, coords.r, coords.s, 0))
func is_cell_empty(coords: Vector4i) -> bool:
return !used_cells.has(coords)
func is_cell_not_empty(coords: CubeCoordinates) -> bool:
func is_cell_not_empty(coords: Vector4i) -> bool:
return !is_cell_empty(coords)
func get_empty_neighbours(coords: CubeCoordinates) -> Array[CubeCoordinates]:
func get_empty_neighbours(coords: Vector4i) -> Array[Vector4i]:
return get_neighbours(coords).filter(is_cell_empty)
func get_neighbours(coords: CubeCoordinates) -> Array[CubeCoordinates]:
func get_neighbours(coords: Vector4i) -> Array[Vector4i]:
return [
CubeCoordinates.new(coords.q + 1, coords.r, coords.s - 1),
CubeCoordinates.new(coords.q + 1, coords.r - 1, coords.s),
CubeCoordinates.new(coords.q, coords.r - 1, coords.s + 1),
CubeCoordinates.new(coords.q - 1, coords.r, coords.s + 1),
CubeCoordinates.new(coords.q - 1, coords.r + 1, coords.s),
CubeCoordinates.new(coords.q, coords.r + 1, coords.s - 1)
Vector4i(coords.x + 1, coords.y, coords.z - 1, coords.w),
Vector4i(coords.x + 1, coords.y - 1, coords.z, coords.w),
Vector4i(coords.x, coords.y - 1, coords.z + 1, coords.w),
Vector4i(coords.x - 1, coords.y, coords.z + 1, coords.w),
Vector4i(coords.x - 1, coords.y + 1, coords.z, coords.w),
Vector4i(coords.x, coords.y + 1, coords.z - 1, coords.w)
]
var current_tile: Node3D
const HEX_OUTLINE = preload("res://hex_outline.tscn")
func _on_insect_selected(insect_resource: TileResource, is_black: bool) -> void:
func get_placeable_positions(button: InsectButton) -> Array:
if used_cells.size() == 0:
return [Vector4i.ZERO]
elif used_cells.size() == 1:
var single_cell = used_cells.keys().front()
var neighbours = get_neighbours(single_cell)
var positions = []
for neighbour in neighbours:
#var hex_pos = cube_to_world_pos(neighbour)
positions.push_back(neighbour)
return positions
var possible_placements: Dictionary = {}
var positions = []
for hex in used_cells.keys():
for neighbour in get_empty_neighbours(hex):
if not used_cells.has(neighbour):
possible_placements[neighbour] = true
for p in possible_placements:
var eligible: bool = true
for neighbour in get_neighbours(p):
if not used_cells.has(neighbour):
continue
if used_cells[neighbour].is_black != button.is_black:
eligible = false
break
if eligible:
positions.push_back(p)
return positions
func get_left_neighbour(pos: Vector4i) -> Vector4i:
return Vector4i(-pos.z, -pos.x, -pos.y, 0)
func get_right_neighbour(pos: Vector4i) -> Vector4i:
return Vector4i(-pos.y, -pos.z, -pos.x, 0)
func can_reach(start: Vector4i, target: Vector4i) -> bool:
# if we have 5 potential spaces it can never be blocked
var offset: Vector4i = Vector4i.ZERO
offset.x = target.x - start.x
offset.y = target.y - start.y
offset.z = target.z - start.z
var left = get_left_neighbour(offset)
var right = get_right_neighbour(offset)
var left_coord = Vector4i(left.x + start.x, left.y + start.y, left.z + start.z, 0)
var right_coord = Vector4i(right.x + start.x, right.y + start.y, right.z + start.z, 0)
return is_cell_empty(left_coord) or is_cell_empty(right_coord)
func _on_insect_selected(button: InsectButton, is_black: bool) -> void:
var positions = get_placeable_positions(button)
for p in positions:
var outline = HEX_OUTLINE.instantiate()
var hex_pos = cube_to_world_pos(p)
outline.position = Vector3(hex_pos.x, 0.0, hex_pos.y)
outline.visible = true
outline.insect_resource = button.insect_resource
outline.is_black = is_black
outline.coordinates = p
outline.map_reference = self
placement_visualizer.add_child(outline)
#placements[p] = outline
# create a hexagon with insect resource data
#var tile = INSECT_TILE.instantiate()
#tile.resource = insect_resource
#tile.is_black = is_black
#current_tile = tile
#add_child(tile)
# spawn possible placement locations :)
if used_cells.size() == 0: # we have no cells placed, display a placement outline at 0, 0
var outline = HEX_OUTLINE.instantiate()
var cubepos = CubeCoordinates.new(0, 0, 0)
var hex_pos = cube_to_world_pos(cubepos)
outline.position = Vector3(hex_pos.x, 0.0, hex_pos.y)
outline.visible = true
outline.insect_resource = insect_resource
outline.is_black = is_black
outline.coordinates = cubepos
placement_visualizer.add_child(outline)
placements[hex_pos] = outline
elif used_cells.size() == 1: # we have ONE cell placed, this is a special case in which
# the opposing player is allowed to place a tile that touches the enemy color
# We display outline placement around all spaces of this single cell
var single_cell = used_cells.keys().front()
var neighbours = get_neighbours(CubeCoordinates.new(single_cell.x, single_cell.y, single_cell.z))
for neighbour in neighbours:
var outline = HEX_OUTLINE.instantiate()
var hex_pos = cube_to_world_pos(neighbour)
outline.position = Vector3(hex_pos.x, 0.0, hex_pos.y)
outline.visible = true
outline.insect_resource = insect_resource
outline.is_black = is_black
outline.coordinates = neighbour
placement_visualizer.add_child(outline)
placements[hex_pos] = outline
else:
# iterate over all used_cells, get all empty cells surrounding those cells
# iterate over all those empty cells, check if they only neighbour the same color
var possible_placements: Dictionary = {}
for hex in used_cells.keys():
#var insect_resource: TileResource = button.insect_resource
#
## spawn possible placement locations :)
#if used_cells.size() == 0: # we have no cells placed, display a placement outline at 0, 0
#var outline = HEX_OUTLINE.instantiate()
#var cubepos = Vector4i.new(0, 0, 0)
#var hex_pos = cube_to_world_pos(cubepos)
#outline.position = Vector3(hex_pos.x, 0.0, hex_pos.y)
#outline.visible = true
#outline.insect_resource = insect_resource
#outline.is_black = is_black
#outline.coordinates = cubepos
#outline.map_reference = self
#placement_visualizer.add_child(outline)
#placements[hex_pos] = outline
#elif used_cells.size() == 1: # we have ONE cell placed, this is a special case in which
## the opposing player is allowed to place a tile that touches the enemy color
## We display outline placement around all spaces of this single cell
#var single_cell = used_cells.keys().front()
#var neighbours = get_neighbours(Vector4i.new(single_cell.x, single_cell.y, single_cell.z))
#for neighbour in neighbours:
#var outline = HEX_OUTLINE.instantiate()
#var hex_pos = cube_to_world_pos(neighbour)
#outline.position = Vector3(hex_pos.x, 0.0, hex_pos.y)
#outline.visible = true
#outline.insect_resource = insect_resource
#outline.is_black = is_black
#outline.coordinates = neighbour
#outline.map_reference = self
#placement_visualizer.add_child(outline)
#placements[hex_pos] = outline
#else:
## iterate over all used_cells, get all empty cells surrounding those cells
## iterate over all those empty cells, check if they only neighbour the same color
#var possible_placements: Dictionary = {}
#
#for hex in used_cells.keys():
##var eligible: bool = true
#for neighbour in get_empty_neighbours(Vector4i.new(hex.x, hex.y, hex.z)):
#if not used_cells.has(Vector4(neighbour.q, neighbour.r, neighbour.s, 0)):
#possible_placements[Vector4i(neighbour.q, neighbour.r, neighbour.s, 0)] = true
#
#for p in possible_placements:
#var eligible: bool = true
for neighbour in get_empty_neighbours(CubeCoordinates.new(hex.x, hex.y, hex.z)):
if not used_cells.has(Vector4(neighbour.q, neighbour.r, neighbour.s, 0)):
possible_placements[Vector4i(neighbour.q, neighbour.r, neighbour.s, 0)] = true
for p in possible_placements:
#var neighbours = [
# Vector2i(p.x + 1, p.y), Vector2i(p.x + 1, p.y - 1), Vector2i(p.x, p.y - 1),
# Vector2i(p.x - 1, p.y), Vector2i(p.x - 1, p.y + 1), Vector2i(p.x, p.y + 1)
#]
var eligible: bool = true
for neighbour in get_neighbours(CubeCoordinates.new(p.x, p.y, p.z)):
if not used_cells.has(Vector4i(neighbour.q, neighbour.r, neighbour.s, 0)):
continue
if used_cells[Vector4i(neighbour.q, neighbour.r, neighbour.s, 0)].is_black != is_black:
eligible = false
break
if eligible:
var outline = HEX_OUTLINE.instantiate()
var hex_pos = cube_to_world_pos(CubeCoordinates.new(p.x, p.y, p.z))
outline.position = Vector3(hex_pos.x, 0.0, hex_pos.y)
outline.visible = true
outline.insect_resource = insect_resource
outline.is_black = is_black
outline.coordinates = CubeCoordinates.new(p.x, p.y, p.z)
placement_visualizer.add_child(outline)
placements[p] = outline
pass
pass
#
#for neighbour in get_neighbours(Vector4i.new(p.x, p.y, p.z)):
#if not used_cells.has(Vector4i(neighbour.q, neighbour.r, neighbour.s, 0)):
#continue
#
#if used_cells[Vector4i(neighbour.q, neighbour.r, neighbour.s, 0)].is_black != is_black:
#eligible = false
#break
#
#if eligible:
#var outline = HEX_OUTLINE.instantiate()
#var hex_pos = cube_to_world_pos(Vector4i.new(p.x, p.y, p.z))
#outline.position = Vector3(hex_pos.x, 0.0, hex_pos.y)
#outline.visible = true
#outline.insect_resource = insect_resource
#outline.is_black = is_black
#outline.coordinates = Vector4i.new(p.x, p.y, p.z)
#outline.map_reference = self
#placement_visualizer.add_child(outline)
#placements[p] = outline
func _on_insect_placement_cancelled() -> void:
if current_tile:
@ -234,7 +299,7 @@ func _on_insect_placement_cancelled() -> void:
for child in placement_visualizer.get_children():
child.queue_free()
func _on_insect_placed(resource: TileResource, is_black: bool, pos: CubeCoordinates) -> void:
func _on_insect_placed(resource: TileResource, is_black: bool, pos: Vector4i) -> void:
var tile_copy = INSECT_TILE.instantiate()
var hex_pos = cube_to_world_pos(pos)
@ -242,41 +307,42 @@ func _on_insect_placed(resource: TileResource, is_black: bool, pos: CubeCoordina
tile_copy.resource = resource
tile_copy.is_black = is_black
tile_copy.coordinates = pos
tile_copy.map_reference = self
var target_pos = Vector3(hex_pos.x, 0.0, hex_pos.y)
used_cells[Vector4i(pos.q, pos.r, pos.s, 0)] = tile_copy
used_cells[pos] = tile_copy
add_child(tile_copy)
var tween = get_tree().create_tween()
tween.tween_property(tile_copy, "position", target_pos, 1.0).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO)
func can_move(tile: InsectTile) -> bool:
return can_hive_exist_without_tile(tile)
func create_move_positions() -> void:
pass
func _on_insect_tile_selected(tile: InsectTile) -> void:
if not can_hive_exist_without_tile(tile):
return
if tile.resource.movement_behaviour == null:
return
var spaces = tile.resource.movement_behaviour.get_available_spaces(tile.coordinates, self)
print(spaces.size())
if spaces.is_empty():
GameEvents.insect_tile_selection_request_failed.emit(tile)
#GameEvents.insect_tile_selection_request_failed.emit(tile)
return
for space in spaces:
var neighbours = get_neighbours(space)
# if all neighbours are empty, move would disconnect us from the hive
# so we discard it
# also: if there are 5 empty space and the only remaining one is our current tile...
# we would also be disconnected after the move
# maybe the 6 check is not needed
var non_empty_neighbours = neighbours.filter(is_cell_not_empty)
var coords_vec4: Vector4i = Vector4i(tile.coordinates.q, tile.coordinates.r, tile.coordinates.s, 0)
if non_empty_neighbours.size() == 1:
var occupied_neighbour = non_empty_neighbours.front()
var neighbour_vec4: Vector4i = Vector4i(occupied_neighbour.q, occupied_neighbour.r, occupied_neighbour.s, 0)
if neighbour_vec4 == coords_vec4:
if occupied_neighbour == tile.coordinates:
continue
var outline = HEX_OUTLINE.instantiate()
@ -292,8 +358,8 @@ func _on_insect_tile_selected(tile: InsectTile) -> void:
placements[space] = outline
func _on_insect_tile_moved(tile: InsectTile, target: CubeCoordinates) -> void:
used_cells.erase(Vector4i(tile.coordinates.q, tile.coordinates.r, tile.coordinates.s, 0))
func _on_insect_tile_moved(tile: InsectTile, target: Vector4i) -> void:
used_cells.erase(tile.coordinates)
var new_hex_pos = cube_to_world_pos(target)
var sky_new_hex_pos = Vector3(new_hex_pos.x, 20.0, new_hex_pos.y)
@ -309,19 +375,34 @@ func _on_insect_tile_moved(tile: InsectTile, target: CubeCoordinates) -> void:
tile.coordinates = target
used_cells[Vector4i(target.q, target.r, target.s, 0)] = tile
used_cells[tile.coordinates] = tile
func get_same_neighbours(cell1: Vector4i, cell2: Vector4i) -> Array[Vector4i]:
var neighbours1 = get_neighbours(cell1).filter(is_cell_not_empty)
var neighbours2 = get_neighbours(cell2).filter(is_cell_not_empty)
var shared_neighbours: Array[Vector4i] = []
for n1 in neighbours1:
for n2 in neighbours2:
if n1 == n2:
if n1 not in shared_neighbours:
shared_neighbours.push_back(n1)
return shared_neighbours
func is_position_on_hive(pos: Vector4i) -> bool:
return get_empty_neighbours(pos).size() > 0
func can_hive_exist_without_tile(tile: InsectTile) -> bool:
# TODO: BFS-Search from random cell to see if all other cells could still be reached when this
# tile would be empty space
if get_empty_neighbours(tile.coordinates).size() == 5: # we only have one real neighbour, so can't break anything
return true
var vector_coords: Vector4i = Vector4i(tile.coordinates.q, tile.coordinates.r, tile.coordinates.s, 0)
# DO BFS
var tiles_reached: Array = []
var tiles_available: Array = used_cells.keys().filter(func(coords): return coords != vector_coords)
var tiles_available: Array = used_cells.keys().filter(func(coords): return coords != tile.coordinates)
if tiles_available.size() <= 1:
# If we only have 1 or 2 total tiles, we can always move
@ -338,65 +419,17 @@ func can_hive_exist_without_tile(tile: InsectTile) -> bool:
while queue.size() > 0:
var m = queue.pop_front()
for neighbour in get_neighbours(CubeCoordinates.new(m.x, m.y, m.z)):
var neighbour_vec4: Vector4i = Vector4i(neighbour.q, neighbour.r, neighbour.s, 0)
if neighbour_vec4 not in tiles_reached and neighbour_vec4 != vector_coords:
if used_cells.has(neighbour_vec4):
tiles_reached.push_back(neighbour_vec4)
queue.push_back(neighbour_vec4)
for neighbour in get_neighbours(m):
if neighbour not in tiles_reached and neighbour != tile.coordinates:
if used_cells.has(neighbour):
tiles_reached.push_back(neighbour)
queue.push_back(neighbour)
return tiles_reached.size() == used_cells.size() - 1
func _on_insect_tile_request_selection(tile: InsectTile) -> void:
if can_hive_exist_without_tile(tile):
GameEvents.insect_tile_selection_request_successful.emit(tile)
else:
GameEvents.insect_tile_selection_request_failed.emit(tile)
func _ready() -> void:
GameEvents.insect_selected.connect(_on_insect_selected)
GameEvents.insect_placement_cancelled.connect(_on_insect_placement_cancelled)
GameEvents.insect_placed.connect(_on_insect_placed)
GameEvents.insect_tile_selected.connect(_on_insect_tile_selected)
GameEvents.insect_tile_moved.connect(_on_insect_tile_moved)
GameEvents.insect_tile_request_selection.connect(_on_insect_tile_request_selection)
#func spawn_random_tile() -> void:
#var tile_copy = hex.duplicate()
#var hex_pos = flat_hex_to_world_position(AxialCoordinates.new(randi_range(-20, 20), randi_range(-20, 20)))
#
#tile_copy.position = Vector3(hex_pos.x, 20.0, hex_pos.y)
#var target_pos = Vector3(hex_pos.x, 0.0, hex_pos.y)
#
#add_child(tile_copy)
#
#var tween = get_tree().create_tween()
#tween.tween_property(tile_copy, "position", target_pos, 1.0).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO)
#
#func move_tile_to_random_position() -> void:
#var new_hex_pos = flat_hex_to_world_position(AxialCoordinates.new(randi_range(-20, 20), randi_range(-20, 20)))
#var sky_new_hex_pos = Vector3(new_hex_pos.x, 20.0, new_hex_pos.y)
#var ground_new_hex_pos = Vector3(new_hex_pos.x, 0.0, new_hex_pos.y)
#
#var current_hex_pos = hex.position
#var sky_current_hex_pos = hex.position + Vector3(0.0, 20.0, 0.0)
#
#var tween = get_tree().create_tween()
#tween.tween_property(hex, "position", sky_current_hex_pos, 0.5).set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_EXPO)
#tween.tween_property(hex, "position", sky_new_hex_pos, 0.0)
#tween.tween_property(hex, "position", ground_new_hex_pos, 1.0).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO)
func _process(delta) -> void:
#if Input.is_action_just_pressed("ui_accept"):
# print("yay")
# spawn_random_tile()
return
if current_tile == null:
return
#var pos3d = get_3d_pos(get_viewport().get_mouse_position())
#if pos3d:
#var hex_pos = flat_hex_to_world_position(world_to_hex_tile(Vector2(pos3d.x, pos3d.z)))
#current_tile.position = Vector3(hex_pos.x, 0.0, hex_pos.y)
#coord_label.text = "%d, %d" % [hex_pos.x, hex_pos.y]