Add DeployToSteamOS and Procedural Dungeon Generation addons

This commit is contained in:
2024-08-22 16:51:05 -07:00
parent 6a6be038e8
commit 5c81398c83
108 changed files with 17406 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
class_name AABBi
# AABB class using integer Vector3 (Vector3i)
# Member variables
var position : Vector3i
var size : Vector3i
var end : Vector3i:
set(v):
if position.clamp(position, v.clamp(v, position)) < position:
position = position.clamp(position, v.clamp(position, v))
size = v - position
get:
return size + position
# Constructor
func _init(position : Vector3i = Vector3i(0, 0, 0), size : Vector3i = Vector3i(0, 0, 0)) -> void:
self.position = position
self.size = size
static func from_AABB_rounded(aabb : AABB) -> AABBi:
return AABBi.new(round(aabb.position), round(aabb.size))
func to_AABB() -> AABB:
return AABB(self.position, self.size)
# Method to check if a point is inside the AABB
func contains_point(point : Vector3i) -> bool:
return (
point.x >= position.x and point.x < position.x + size.x and
point.y >= position.y and point.y < position.y + size.y and
point.z >= position.z and point.z < position.z + size.z
)
# Method to check if another AABB intersects with this one
func intersects(aabb : AABBi) -> bool:
aabb = aabb.normalized()
var my := self.normalized()
# Find separating axis
if aabb.position.x >= my.end.x or my.position.x >= aabb.end.x: return false
if aabb.position.y >= my.end.y or my.position.y >= aabb.end.y: return false
if aabb.position.z >= my.end.z or my.position.z >= aabb.end.z: return false
return true
# Method to push this AABB inside another AABB (returns new AABBi)
func push_within(aabb : AABBi, ignore_y : bool) -> AABBi:
aabb = aabb.normalized()
var new_aabb = AABBi.new(self.position, self.size).normalized()
new_aabb.position = new_aabb.position.clamp(aabb.position, aabb.end - new_aabb.size)
if ignore_y:
new_aabb.position.y = self.position.y
return new_aabb
func expand_to_include(point : Vector3i) -> AABBi:
var new_position : Vector3i = Vector3i(
min(position.x, point.x),
min(position.y, point.y),
min(position.z, point.z)
)
var new_end_position : Vector3i = Vector3i(
max(end.x, point.x + 1),
max(end.y, point.y + 1),
max(end.z, point.z + 1)
)
var new_size : Vector3i = new_end_position - new_position
return AABBi.new(new_position, new_size)
# Method to check if two AABBs are equal
func equals(aabb : AABBi) -> bool:
return position == aabb.position and size == aabb.size
# Method to check if this AABB encloses another AABB
func encloses(aabb : AABBi) -> bool:
aabb = aabb.normalized()
return (
self.normalized().contains_point(aabb.position) and
self.normalized().contains_point(aabb.end - Vector3i(1, 1, 1))
)
# Method to normalize the AABB (ensures size is positive)
func normalized() -> AABBi:
var new_position : Vector3i = position
var new_size : Vector3i = size
if size.x < 0:
new_position.x += size.x
new_size.x = -size.x
if size.y < 0:
new_position.y += size.y
new_size.y = -size.y
if size.z < 0:
new_position.z += size.z
new_size.z = -size.z
return AABBi.new(new_position, new_size)
# Method to grow the AABB by a specified amount in all directions
func grow(amount : int) -> AABBi:
var new_position : Vector3i = position - Vector3i(amount, amount, amount)
var new_size : Vector3i = size + Vector3i(amount * 2, amount * 2, amount * 2)
return AABBi.new(new_position, new_size)
# Method to grow the AABB by a specified amount in the x and z directions
func grow_xz(amount : int) -> AABBi:
var new_position : Vector3i = position - Vector3i(amount, 0, amount)
var new_size : Vector3i = size + Vector3i(amount * 2, 0, amount * 2)
return AABBi.new(new_position, new_size)
func translated(pos : Vector3i) -> AABBi:
return AABBi.new(self.position + pos, self.size)

View File

@@ -0,0 +1,80 @@
[gd_scene load_steps=2 format=3 uid="uid://bak8ltrhbmlv5"]
[sub_resource type="GDScript" id="GDScript_0eugj"]
script/source = "@tool
extends CSGBox3D
@export var num_stairs : int = 10
var _cur_num_stairs = -1
var _cur_size : Vector3
func add_fresh_stairs_csg_poly():
if has_node('./StairsSubtractCSG'):
var mesh = $StairsSubtractCSG
remove_child(mesh)
mesh.queue_free()
#print(\"removed old stairs\")
#print(\"adding stairs\")
var poly = CSGPolygon3D.new()
poly.name = 'StairsSubtractCSG'
add_child(poly)
#poly.owner = get_tree().edited_scene_root
poly.operation = CSGShape3D.OPERATION_SUBTRACTION
return poly
func make_stairs():
#if not Engine.is_editor_hint():
#return
#
num_stairs = clampi(num_stairs, 0, 999)
var stairs_poly = $StairsSubtractCSG#add_fresh_stairs_csg_poly()
var point_arr : PackedVector2Array = PackedVector2Array()
var step_height = self.size.y / num_stairs
var step_width = self.size.x / num_stairs
if num_stairs == 0:
# For 0 stairs make a ramp
point_arr.append(Vector2(self.size.x, self.size.y))
point_arr.append(Vector2(0, self.size.y))
point_arr.append(Vector2(0, 0))
else:
# Creating the points for the stairs polygon
for i in range(num_stairs - 1):
point_arr.append(Vector2(i * step_width, (i + 1) * step_height))
if i < num_stairs:
point_arr.append(Vector2((i + 1) * step_width, (i + 1) * step_height))
# Closing the polygon by adding the last two points
point_arr.append(Vector2(self.size.x - step_width, self.size.y))
point_arr.append(Vector2(0, self.size.y))
stairs_poly.polygon = point_arr
stairs_poly.depth = self.size.z
stairs_poly.position.z = self.size.z / 2.0
stairs_poly.position.y = -self.size.y / 2.0
stairs_poly.position.x = -self.size.x / 2.0
_cur_num_stairs = num_stairs
_cur_size = self.size
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta):
if _cur_num_stairs != num_stairs or _cur_size != self.size:
make_stairs()
"
[node name="CSGStairMaker3D" type="CSGBox3D"]
script = SubResource("GDScript_0eugj")
num_stairs = 4
[node name="StairsSubtractCSG" type="CSGPolygon3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, -0.5, 0.5)
operation = 2
polygon = PackedVector2Array(0, 0.25, 0.25, 0.25, 0.25, 0.5, 0.5, 0.5, 0.5, 0.75, 0.75, 0.75, 0.75, 1, 0, 1)

View File

@@ -0,0 +1,79 @@
class_name DungeonAStar3D
extends AStar3D
var doors_list = []
var pt_id_to_vec3i = {}
var vec3i_to_pt_id = {}
var dungeon_generator : DungeonGenerator3D
var rooms_check_dict : Dictionary # Vector3i : DungeonRoom3D (Non-Corridor) instance
var corridors_check_dict : Dictionary # Vector3i : DungeonRoom3D (Corridor) instance
var cap_required_doors_phase := false
func can_walk_from_to(dungeon_generator : DungeonGenerator3D, pos_a : Vector3i, pos_b : Vector3i) -> bool:
if not dungeon_generator.get_grid_aabbi().contains_point(pos_a): return false
if not dungeon_generator.get_grid_aabbi().contains_point(pos_b): return false
var room_a = rooms_check_dict[pos_a] if rooms_check_dict.has(pos_a) else null
var room_b = rooms_check_dict[pos_b] if rooms_check_dict.has(pos_b) else null
if room_a == null and room_b == null: return pos_a.y == pos_b.y # Outside rooms, only move horizontal
if room_a == room_b: return true # Inside rooms, move anywhere, up and down, i.e. stairs
# Ensure walking through doorways if not a simple case:
var fits_room_a_door = room_a == null or room_a.get_doors_cached().filter(func(d): return d.grid_pos == pos_a and d.exit_pos_grid == pos_b).size() == 1
var fits_room_b_door = room_b == null or room_b.get_doors_cached().filter(func(d): return d.grid_pos == pos_b and d.exit_pos_grid == pos_a).size() == 1
return fits_room_a_door and fits_room_b_door
func _init(dungeon_generator : DungeonGenerator3D, rooms_check_dict : Dictionary, corridors_check_dict : Dictionary):
self.dungeon_generator = dungeon_generator
self.rooms_check_dict = rooms_check_dict
self.corridors_check_dict = corridors_check_dict
# Add points to the AStar3D grid
var point_id = 0
for x in range(dungeon_generator.dungeon_size.x):
for y in range(dungeon_generator.dungeon_size.y):
for z in range(dungeon_generator.dungeon_size.z):
add_point(point_id, Vector3(x,y,z))
pt_id_to_vec3i[point_id] = Vector3i(x,y,z)
vec3i_to_pt_id[Vector3i(x,y,z)] = point_id
point_id += 1
var xyz_dirs := [Vector3i(1,0,0), Vector3i(-1,0,0), Vector3i(0,0,1), Vector3i(0,0,-1), Vector3i(0,1,0), Vector3i(0,-1,0)] as Array[Vector3i]
# Connect points - allow walking in & out of all doors but don't connect where walls are.
for x in range(dungeon_generator.dungeon_size.x):
for y in range(dungeon_generator.dungeon_size.y):
for z in range(dungeon_generator.dungeon_size.z):
var cur_pt_id = get_closest_point(Vector3(x,y,z))
# Allow walk in/out of room, up & down too for stairs
for dir in xyz_dirs:
if can_walk_from_to(dungeon_generator, Vector3i(x,y,z), Vector3i(x,y,z) + dir):
connect_points(cur_pt_id, get_closest_point(Vector3(x,y,z) + Vector3(dir)))
func _estimate_cost(from_id : int, to_id : int) -> float:
if dungeon_generator.astar_heuristic == DungeonGenerator3D.AStarHeuristics.NONE_DIJKSTRAS:
return 0.0
elif dungeon_generator.astar_heuristic == DungeonGenerator3D.AStarHeuristics.MANHATTAN:
var diff := get_point_position(to_id) - get_point_position(from_id)
return (abs(diff.x) + abs(diff.y) + abs(diff.z)) * dungeon_generator.heuristic_scale
elif dungeon_generator.astar_heuristic == DungeonGenerator3D.AStarHeuristics.EUCLIDEAN:
var diff := get_point_position(to_id) - get_point_position(from_id)
return diff.length() * dungeon_generator.heuristic_scale
return 0.0
func _compute_cost(from_id : int, to_id : int) -> float:
var diff := get_point_position(to_id) - get_point_position(from_id)
var cost := diff.length()
if rooms_check_dict.has(Vector3i(get_point_position(to_id).round())):
if not cap_required_doors_phase:
cost *= dungeon_generator.room_cost_multiplier
else:
cost *= dungeon_generator.room_cost_at_end_for_required_doors
if corridors_check_dict.has(Vector3i(get_point_position(to_id).round())):
cost *= dungeon_generator.corridor_cost_multiplier
return cost
func get_vec3i_path(from : Vector3i, to : Vector3i) -> Array[Vector3i]:
var path = get_id_path(vec3i_to_pt_id[from], vec3i_to_pt_id[to])
var path_vec3i = Array(path).map(func(pt_id : int): return pt_id_to_vec3i[pt_id])
var typefix : Array[Vector3i] = [] as Array[Vector3i]
typefix.assign(path_vec3i)
return typefix

View File

@@ -0,0 +1,24 @@
class_name RandomNumberMultiplayer
extends Node
## Can be used to seed the DungeonGenerator to sync the seed with all clients on multiplayer
# Connect to DungeonGenerator generate(seed) function
signal got_random_int(num : int)
var random_number : int
func _ready():
if is_multiplayer_authority():
random_number = randi()
emit_random_number(random_number)
else:
request_random_number.rpc_id(get_multiplayer_authority())
@rpc("authority", "call_remote", "reliable", 0)
func emit_random_number(num : int):
got_random_int.emit(num)
@rpc("any_peer", "call_remote", "reliable", 0)
func request_random_number():
emit_random_number.rpc_id(multiplayer.get_remote_sender_id(), random_number)

View File

@@ -0,0 +1,42 @@
class_name TreeGraph
## TreeGraph: A utility class used to ensure all rooms and floors on the dungeon are connected.
var _nodes = []
var _roots = {}
func _init(nodes : Array = []):
self._nodes = nodes
# Initially, each node is its own root.
for node in nodes:
self._roots[node] = node
func add_node(node : Variant):
self._nodes.append(node)
self._roots[node] = node
func has_node(node : Variant):
return node in _nodes
func get_all_nodes() -> Array:
return _nodes
func find_root(node) -> Variant:
if _roots[node] != node:
# Compress the path and set all nodes along the way to the root
_roots[node] = find_root(_roots[node])
return _roots[node]
# Tricky. Messed this up at first. Always set the root to the lower (index) root to keep it stable
func connect_nodes(node_a, node_b) -> void:
if find_root(node_a) != find_root(node_b):
if _nodes.find(find_root(node_a)) < _nodes.find(find_root(node_b)):
_roots[find_root(node_b)] = _roots[find_root(node_a)]
else:
_roots[find_root(node_a)] = _roots[find_root(node_b)]
func are_nodes_connected(node_a, node_b) -> bool:
return find_root(node_a) == find_root(node_b)
func is_fully_connected() -> bool:
return len(_nodes) == 0 or _nodes.all(func(node): return are_nodes_connected(node, _nodes[0]))