Add DeployToSteamOS and Procedural Dungeon Generation addons
This commit is contained in:
110
addons/SimpleDungeons/utils/AABBi.gd
Normal file
110
addons/SimpleDungeons/utils/AABBi.gd
Normal 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)
|
||||
80
addons/SimpleDungeons/utils/CSGStairMaker3D.tscn
Normal file
80
addons/SimpleDungeons/utils/CSGStairMaker3D.tscn
Normal 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)
|
||||
79
addons/SimpleDungeons/utils/DungeonAStarGrid3D.gd
Normal file
79
addons/SimpleDungeons/utils/DungeonAStarGrid3D.gd
Normal 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
|
||||
24
addons/SimpleDungeons/utils/RandomNumberMultiplayer.gd
Normal file
24
addons/SimpleDungeons/utils/RandomNumberMultiplayer.gd
Normal 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)
|
||||
42
addons/SimpleDungeons/utils/TreeGraph.gd
Normal file
42
addons/SimpleDungeons/utils/TreeGraph.gd
Normal 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]))
|
||||
Reference in New Issue
Block a user