Add DeployToSteamOS and Procedural Dungeon Generation addons
935
addons/SimpleDungeons/DungeonGenerator3D.gd
Normal file
@@ -0,0 +1,935 @@
|
||||
@tool
|
||||
class_name DungeonGenerator3D
|
||||
extends Node3D
|
||||
|
||||
signal done_generating()
|
||||
signal generating_failed()
|
||||
|
||||
# This can be set to a callable to customize room spawning in a fine grained way.
|
||||
# Function signature should be Callable(room_instances : Array[DungeonRoom3D], rng : RandomNumberGenerator) -> Array[DungeonRoom3D]
|
||||
var custom_get_rooms_function = null
|
||||
|
||||
# For any vars which may be accessed from multiple threads
|
||||
var t_mutex := Mutex.new()
|
||||
|
||||
enum BuildStage { NOT_STARTED = -2, PREPARING = -1, PLACE_ROOMS = 0, PLACE_STAIRS = 1, SEPARATE_ROOMS = 2, CONNECT_ROOMS = 3, FINALIZING = 4, DONE = 5 }
|
||||
var stage : BuildStage = BuildStage.NOT_STARTED :
|
||||
set(v): t_mutex.lock(); stage = v; t_mutex.unlock();
|
||||
get: t_mutex.lock(); var v = stage; t_mutex.unlock(); return v;
|
||||
|
||||
## Add all the rooms for the dungeon generator to use in this array.
|
||||
## Each one must inherit DungeonRoom.
|
||||
## Not necessary to place the corridor room here. Place that in the corridor_room_scene property.
|
||||
@export var room_scenes : Array[PackedScene] = []
|
||||
## The corridor room is a special room scene which must be a 1x1x1 (in voxels) scene inheriting DungeonRoom which is used to connect all the placed rooms.
|
||||
@export var corridor_room_scene : PackedScene
|
||||
|
||||
## Dungeon grid size measured in voxel units, voxel size is chosen in the voxel_scale property.
|
||||
@export var dungeon_size := Vector3i(10,10,10) :
|
||||
set(v):
|
||||
dungeon_size = v.clamp(Vector3i(1,1,1),Vector3i(9999,9999,9999))
|
||||
|
||||
## Voxel scale/size in world units. Controls the standardized 1x1x1 room size of the dungeon.
|
||||
## This should match the voxel scale on each of your rooms.voxel_scale
|
||||
## If the voxel scale is different, the rooms will be scaled (perhaps non uniformly) to match the DungeonGenerator's voxel scale.
|
||||
@export var voxel_scale := Vector3(10,10,10) :
|
||||
set(v):
|
||||
voxel_scale = v.clamp(Vector3(0.0001,0.0001,0.0001),Vector3(9999,9999,9999))
|
||||
|
||||
## Seed to use when generating the dungeon. Blank for random. You can call .generate(seed : int) to override.
|
||||
## I'm setting this as a string because it didn't seem like you could set very large ints from editor if set to int.
|
||||
@export var generate_seed : String = "" :
|
||||
set(v):
|
||||
var stripped_value = v.strip_edges().replace(r"[^0-9-]", "")
|
||||
if stripped_value.begins_with("-"):
|
||||
stripped_value = "-" + stripped_value.replace("-", "")
|
||||
else:
|
||||
stripped_value = stripped_value.replace("-", "")
|
||||
generate_seed = stripped_value
|
||||
|
||||
@export var generate_on_ready : bool = true
|
||||
## Depending on the dungeon kit used and the separation/connecting algorithm, it's possible that
|
||||
## a dungeon may not be able to correctly generate and have all rooms reachable/not overlapping.
|
||||
## In that case, the algorithm can simply restart from the beginning and try again.
|
||||
@export var max_retries : int = 1
|
||||
## Max total iterations in generate loop running one pass for the current stage.
|
||||
## Generation stages: PLACE_ROOMS, PLACE_STAIRS, SEPARATE_ROOMS, CONNECT_ROOMS
|
||||
@export var max_safe_iterations : int = 250
|
||||
|
||||
## Run generation of dungeon on a separate thread.
|
||||
## It is safe to access vars .stage, .is_currently_generating, and .failed_to_generate but otherwise you should use .call_deferred_thread_group() to call any functions.
|
||||
## Does not work in editor - was causing crashes so I disabled it.
|
||||
@export var generate_threaded := false
|
||||
|
||||
## Generate the dungeon in editor. If you added any custom rooms which extend DungeonRoom,
|
||||
## they also need the @tool directive for this to work.
|
||||
@export var editor_button_generate_dungeon : bool = false :
|
||||
set(value):
|
||||
generate()
|
||||
|
||||
## Abort the generation started in editor
|
||||
@export var abort_editor_button : bool = false :
|
||||
set(value):
|
||||
abort_generation()
|
||||
|
||||
@export_group("AStar room connection options")
|
||||
enum AStarHeuristics { NONE_DIJKSTRAS = 0, MANHATTAN = 1, EUCLIDEAN = 2 }
|
||||
## What heuristic to use for the AStar room connection algorithm.
|
||||
## Euclidan - Standard, tends towards straight corridors connecting rooms.
|
||||
## Manhattan - May lead to zigzagging corridors between rooms.
|
||||
## Dijkstra's - No heuristic, this turns AStar into Dijkstra's algorithm. Guaranteed to find the shortest possible path but may lead to zigzagging corridors.
|
||||
@export var astar_heuristic : AStarHeuristics = AStarHeuristics.EUCLIDEAN
|
||||
## Increasing the heuristic scale may make the path less optimal but can help reduce zigzagging corridors.
|
||||
## A heuristic of 3.0 (with either manhattan or euclidean, Dijkstra's already means 0 heuristic scale) may help reduce zigzagging corridors.
|
||||
@export var heuristic_scale : float = 1.0
|
||||
## By making the corridors cost less to walk through, the algorithm will tend towards merging into single corridors,
|
||||
## thus making hallways more compact.
|
||||
@export var corridor_cost_multiplier : float = 0.25
|
||||
## Similar to corridor cost, setting this lower makes it so the algorithm will walk through a rooms to connect 2 rooms/doors, thus saving corridors placements.
|
||||
## You could also set it greater than 1 to make the algorithm less likely to walk through existing (non-corridor) rooms.
|
||||
@export var room_cost_multiplier : float = 0.25
|
||||
## After connecting all rooms, some doors may not have been connected.
|
||||
## By setting the cost for astar to walk through rooms higher at this stage, it encourages more interesting room connections,
|
||||
## rather than just putting 1x1x1 corridor caps at doors.
|
||||
@export var room_cost_at_end_for_required_doors : float = 2.0
|
||||
|
||||
@export_group("Debug options")
|
||||
@export var show_debug_in_editor : bool = true
|
||||
@export var show_debug_in_game : bool = false
|
||||
## Whether to place the dungeon rooms so far even if the generation failed.
|
||||
@export var place_even_if_fail : bool = false
|
||||
@export var visualize_generation_progress : bool = false
|
||||
# By default hide debug after gen. Othewise will lag.
|
||||
@export var hide_debug_visuals_for_all_generated_rooms : bool = true
|
||||
@export var cycle_debug_draw_when_press_n : bool = false
|
||||
## A timer will be called with this wait time for the next iteration of the generation loop
|
||||
@export_range(0, 1000, 1, "or_greater", "suffix:ms") var visualize_generation_wait_between_iterations : int = 100
|
||||
|
||||
###########################
|
||||
## INIT/PROCESS/BUILTINS ##
|
||||
###########################
|
||||
|
||||
func _init():
|
||||
RenderingServer.set_debug_generate_wireframes(true)
|
||||
|
||||
func _input(event):
|
||||
if cycle_debug_draw_when_press_n and event is InputEventKey and Input.is_key_pressed(KEY_N):
|
||||
var vp = get_viewport()
|
||||
vp.debug_draw = (vp.debug_draw + 1 ) % 4
|
||||
|
||||
var _debug_view = null
|
||||
func add_debug_view_if_not_exist():
|
||||
if not _debug_view:
|
||||
_debug_view = preload("res://addons/SimpleDungeons/debug_visuals/DungeonGenerator3DDebugView.gd").new()
|
||||
add_child(_debug_view)
|
||||
|
||||
func _ready():
|
||||
add_debug_view_if_not_exist()
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
if generate_on_ready:
|
||||
generate()
|
||||
|
||||
func _process(delta):
|
||||
if Engine.is_editor_hint():
|
||||
# Debug view doesn't get added in Editor sometimes, like if you manually drag script on.
|
||||
for c in get_children():
|
||||
if c is DungeonRoom3D and not c.virtualized_from:
|
||||
c.add_debug_view_if_not_exist()
|
||||
return
|
||||
if _visualization_in_progress and Time.get_ticks_msec() - _last_iteration_end_time > visualize_generation_wait_between_iterations:
|
||||
_run_generate_loop(false)
|
||||
|
||||
###################################
|
||||
## GENERATION ENTRY POINT & LOOP ##
|
||||
###################################
|
||||
|
||||
# Mostly setting up proper error handling, threads, visualization here.
|
||||
# Most of the interesting stuff is in the build logic portion of the code below.
|
||||
|
||||
var room_instances : Array[DungeonRoom3D]
|
||||
var corridor_room_instance : DungeonRoom3D
|
||||
var iterations := 0
|
||||
var retry_attempts := 0
|
||||
var rooms_container : Node3D
|
||||
var rng := RandomNumberGenerator.new()
|
||||
|
||||
var running_thread : Thread
|
||||
var failed_to_generate := false :
|
||||
set(v): t_mutex.lock(); failed_to_generate = v; t_mutex.unlock();
|
||||
get: t_mutex.lock(); var v = failed_to_generate; t_mutex.unlock(); return v;
|
||||
var full_abort_triggered := false :
|
||||
set(v): t_mutex.lock(); full_abort_triggered = v; t_mutex.unlock();
|
||||
get: t_mutex.lock(); var v = full_abort_triggered; t_mutex.unlock(); return v;
|
||||
var is_currently_generating : bool :
|
||||
get: return not (stage == BuildStage.NOT_STARTED or stage == BuildStage.DONE) and not failed_to_generate
|
||||
|
||||
var _is_generating_threaded = false
|
||||
var _visualization_in_progress = false
|
||||
var _last_iteration_end_time : int = Time.get_ticks_msec()
|
||||
|
||||
func generate(seed : int = int(generate_seed) if generate_seed.is_valid_int() else randi()) -> void:
|
||||
if is_currently_generating:
|
||||
_printerr("SimpleDungeons Error: Dungeon currently generating, cannot generate.")
|
||||
return
|
||||
|
||||
stage = BuildStage.NOT_STARTED
|
||||
|
||||
if not validate_dungeon():
|
||||
_printerr("SimpleDungeons Error: Cannot generate.")
|
||||
return
|
||||
|
||||
rng = RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
print("DungeonGenerator3D generate(): Using seed ", seed)
|
||||
|
||||
cleanup_and_reset_dungeon_generator()
|
||||
create_or_recreate_rooms_container()
|
||||
get_preplaced_rooms() # Ensure cached because can't call get_children on threads
|
||||
|
||||
for room in get_preplaced_rooms():
|
||||
room.snap_room_to_dungeon_grid()
|
||||
if not room.validate_room():
|
||||
_fail_generation("Could not validate preplaced rooms.")
|
||||
return
|
||||
room.ensure_doors_and_or_transform_cached_for_threads_and_virtualized_rooms()
|
||||
|
||||
stage = BuildStage.PREPARING
|
||||
if not setup_room_instances_and_validate_before_generate():
|
||||
_fail_generation("DungeonGenerator3D generation failed while setting up rooms.")
|
||||
return
|
||||
|
||||
_is_generating_threaded = generate_threaded and not visualize_generation_progress
|
||||
_visualization_in_progress = visualize_generation_progress
|
||||
|
||||
if _is_generating_threaded and Engine.is_editor_hint():
|
||||
_is_generating_threaded = false
|
||||
_printwarning("Disabling threaded generation because in editor. Kept running into crashes with editor threads, looked like Godot bugs, so disabling for now. You can still use visualize generation in editor. Threaded generation in game seems to work fine.")
|
||||
|
||||
if _is_generating_threaded:
|
||||
running_thread = Thread.new()
|
||||
running_thread.start(_run_generate_loop)
|
||||
else:
|
||||
_run_generate_loop()
|
||||
|
||||
func _run_generate_loop(first_call : bool = true) -> void:
|
||||
if first_call:
|
||||
retry_attempts = 0
|
||||
iterations = 0
|
||||
failed_to_generate = false
|
||||
stage = BuildStage.PLACE_ROOMS
|
||||
while retry_attempts <= max_retries and not full_abort_triggered:
|
||||
while iterations < max_safe_iterations and not failed_to_generate and stage != BuildStage.FINALIZING:
|
||||
var start_stage := stage
|
||||
_run_one_loop_iteration_and_increment_iterations()
|
||||
_last_iteration_end_time = Time.get_ticks_msec()
|
||||
if _visualization_in_progress and stage != BuildStage.FINALIZING and not failed_to_generate:
|
||||
return # Will be called again from _process
|
||||
if stage == BuildStage.FINALIZING:
|
||||
break
|
||||
# If the dungeon failed to generate after looping through all stages until max_safe_iterations, retry.
|
||||
retry_attempts += 1
|
||||
if retry_attempts > max_retries:
|
||||
_fail_generation("Reached max generation retries. Failed to generate. Failed at stage "+BuildStage.find_key(stage))
|
||||
break
|
||||
else:
|
||||
clear_rooms_container_and_setup_for_next_iteration()
|
||||
iterations = 0
|
||||
failed_to_generate = false
|
||||
stage = BuildStage.PLACE_ROOMS
|
||||
if _visualization_in_progress:
|
||||
return # Will be called again from _process
|
||||
_printwarning("Generation failed on attempt "+str(retry_attempts)+" at stage "+BuildStage.find_key(stage)+". Retrying generation.")
|
||||
|
||||
_visualization_in_progress = false
|
||||
if _is_generating_threaded:
|
||||
if not failed_to_generate: _dungeon_finished_generating.call_deferred()
|
||||
else: _dungeon_failed_generating.call_deferred()
|
||||
else:
|
||||
if not failed_to_generate: _dungeon_finished_generating()
|
||||
else: _dungeon_failed_generating()
|
||||
|
||||
var _stage_just_changed = false
|
||||
func _run_one_loop_iteration_and_increment_iterations() -> void:
|
||||
if iterations == 0:
|
||||
_stage_just_changed = true
|
||||
|
||||
var cur_stage = stage
|
||||
# Each stage will increment stage once it is done.
|
||||
# Also, each stage can call _abort_generation_and_fail if it encounters any errors.
|
||||
if stage == BuildStage.PLACE_ROOMS:
|
||||
place_room_iteration(_stage_just_changed)
|
||||
elif stage == BuildStage.PLACE_STAIRS:
|
||||
place_stairs_iteration(_stage_just_changed)
|
||||
elif stage == BuildStage.SEPARATE_ROOMS:
|
||||
# This looked to be so extremely slow to run all the checks after each separate iteration, so just going to iterate here:
|
||||
separate_rooms_iteration(_stage_just_changed)
|
||||
elif stage == BuildStage.CONNECT_ROOMS:
|
||||
connect_rooms_iteration(_stage_just_changed)
|
||||
|
||||
_stage_just_changed = stage != cur_stage
|
||||
iterations += 1
|
||||
|
||||
func _fail_generation(error : String = "Aborted generation") -> void:
|
||||
_printerr("SimpleDungeons Error: ", error)
|
||||
_printerr("SimpleDungeons Error: Failed to generate dungeon")
|
||||
failed_to_generate = true
|
||||
|
||||
func abort_generation():
|
||||
if not is_currently_generating:
|
||||
_printwarning("DungeonGenerator3D not currently generating.")
|
||||
return
|
||||
failed_to_generate = true
|
||||
full_abort_triggered = true
|
||||
_printerr("abort_generation() called")
|
||||
if running_thread and running_thread.is_alive() and OS.get_main_thread_id() == OS.get_thread_caller_id():
|
||||
running_thread.wait_to_finish()
|
||||
for room in room_instances:
|
||||
room.queue_free()
|
||||
for room in _rooms_placed:
|
||||
room.queue_free()
|
||||
rooms_container.queue_free()
|
||||
if rooms_container.is_inside_tree():
|
||||
rooms_container.get_parent().remove_child(rooms_container)
|
||||
|
||||
func _finalize_rooms(ready_callback = null) -> void:
|
||||
if not rooms_container.is_inside_tree():
|
||||
add_child(rooms_container)
|
||||
rooms_container.owner = self.owner
|
||||
for room in _rooms_placed.slice(0):
|
||||
_rooms_placed.erase(room)
|
||||
var unvirtualized = room.unvirtualize_and_free_clone_if_needed(rooms_container)
|
||||
unvirtualized.owner = self.owner
|
||||
_rooms_placed.push_back(unvirtualized)
|
||||
for room in room_instances:
|
||||
if room and is_instance_valid(room):
|
||||
room.queue_free()
|
||||
room_instances = []
|
||||
if corridor_room_instance and is_instance_valid(corridor_room_instance):
|
||||
corridor_room_instance.queue_free()
|
||||
corridor_room_instance = null
|
||||
if ready_callback is Callable:
|
||||
if rooms_container.is_node_ready():
|
||||
ready_callback.call_deferred()
|
||||
else:
|
||||
rooms_container.ready.connect(ready_callback)
|
||||
|
||||
|
||||
func _dungeon_finished_generating() -> void:
|
||||
_finalize_rooms(_emit_done_signals)
|
||||
|
||||
func _dungeon_failed_generating() -> void:
|
||||
if place_even_if_fail:
|
||||
_finalize_rooms()
|
||||
elif not visualize_generation_progress:
|
||||
rooms_container.queue_free()
|
||||
for room in _rooms_placed:
|
||||
room.queue_free()
|
||||
for room in room_instances:
|
||||
room.queue_free()
|
||||
_emit_failed_signal.call_deferred() # Ensure rooms container placed/might be on thread.
|
||||
|
||||
# Emit done signals for dungeon & place_room for all DungeonRooms.
|
||||
func _emit_done_signals():
|
||||
stage = BuildStage.DONE
|
||||
# Also need to call emit signal for each of place_rooms
|
||||
for room in _rooms_placed:
|
||||
if not room.original_ready_func_called:
|
||||
_printwarning("_ready not called on "+room.name+". Room placement, finalization, and doors will be broken. Make sure to call super._ready() at the top of your _ready func when inheriting DungeonRoom3D.")
|
||||
room.dungeon_done_generating.emit()
|
||||
for preplaced_room in find_children("*", "DungeonRoom3D", false):
|
||||
preplaced_room.dungeon_done_generating.emit()
|
||||
print("DungeonGenerator3D finished generating.")
|
||||
for room in room_instances:
|
||||
room.queue_free()
|
||||
done_generating.emit()
|
||||
|
||||
func _emit_failed_signal(): # So I can call_deferred
|
||||
if running_thread:
|
||||
running_thread.wait_to_finish()
|
||||
generating_failed.emit()
|
||||
|
||||
#########################
|
||||
## DUNGEON BUILD LOGIC ##
|
||||
#########################
|
||||
|
||||
# Placing rooms: A random room is selected from room_scenes until the min_count of all is matched
|
||||
|
||||
var _rooms_placed : Array[DungeonRoom3D]
|
||||
var _custom_rand_rooms : Array[DungeonRoom3D]
|
||||
var _use_custom_rand_rooms := false
|
||||
func place_room_iteration(first_call_in_loop : bool) -> void:
|
||||
if first_call_in_loop:
|
||||
_rooms_placed = []
|
||||
_custom_rand_rooms = []
|
||||
_use_custom_rand_rooms = false
|
||||
if custom_get_rooms_function is Callable:
|
||||
_use_custom_rand_rooms = true
|
||||
_custom_rand_rooms = custom_get_rooms_function.call(room_instances, rng)
|
||||
if not _custom_rand_rooms is Array or len(_custom_rand_rooms) == 0:
|
||||
_fail_generation("custom_get_rooms_function takes should return a non-empty Array of DungeonRoom3Ds.")
|
||||
_printwarning("custom_get_rooms_function takes (room_instances : Array[DungeonRoom3D], rng_seeded : RandomNumberGenerator) as the arguments and should use .create_clone_and_make_virtual_unless_visualizing() to clone and then position with .set_position_by_grid_pos(Vector3i) or .rotation = 0 through 3 for number of 90 degree y rotations for the room.")
|
||||
return
|
||||
for room in _custom_rand_rooms:
|
||||
if not room is DungeonRoom3D:
|
||||
_fail_generation("custom_get_rooms_function supplied an object that is not a DungeonRoom3D. Ensure all rooms supplied inherit DungeonRoom3D, and use the @tool annotation if generating in editor.")
|
||||
return
|
||||
if room_instances.find(room) != -1:
|
||||
_fail_generation("custom_get_rooms_function supplied a room instance without cloning it. Always use DungeonRoom3D.create_clone_and_make_virtual_unless_visualizing() to create room instances.")
|
||||
return
|
||||
|
||||
var rand_room : DungeonRoom3D
|
||||
if _use_custom_rand_rooms:
|
||||
rand_room = _custom_rand_rooms.pop_front()
|
||||
else:
|
||||
if get_rooms_less_than_max_count(false).size() > 0:
|
||||
rand_room = get_randomly_positioned_room()
|
||||
|
||||
if rand_room:
|
||||
place_room(rand_room)
|
||||
|
||||
if _use_custom_rand_rooms:
|
||||
if _custom_rand_rooms.size() == 0:
|
||||
stage += 1
|
||||
else:
|
||||
if get_rooms_less_than_min_count(false).size() == 0:
|
||||
if _rooms_placed.size() == 0:
|
||||
_fail_generation("Unable to place any rooms. Ensure min_count and max_count are set correctly on rooms.")
|
||||
else:
|
||||
stage += 1
|
||||
|
||||
# Placing stairs:
|
||||
|
||||
# Array [DungeonRoom3D, grid_pos_y] for where to place rooms
|
||||
var _stair_rooms_and_placements = []
|
||||
var _stair_rooms_placed_count = {}
|
||||
func place_stairs_iteration(first_call_in_loop : bool) -> void:
|
||||
if first_call_in_loop:
|
||||
_stair_rooms_placed_count = {}
|
||||
for s in get_stair_rooms_from_instances():
|
||||
_stair_rooms_placed_count[s] = 0
|
||||
_stair_rooms_and_placements = _make_and_solve_floors_graph()
|
||||
if failed_to_generate:
|
||||
return # solve failed and called abort
|
||||
|
||||
if _stair_rooms_and_placements.size() > 0:
|
||||
var stair_and_y_pos = _stair_rooms_and_placements.pop_back()
|
||||
_stair_rooms_placed_count[stair_and_y_pos[0]] += 1
|
||||
var room = stair_and_y_pos[0].create_clone_and_make_virtual_unless_visualizing()
|
||||
var y_pos = stair_and_y_pos[1]
|
||||
room.room_rotations = rng.randi_range(0,4)
|
||||
room.set_position_by_grid_pos(Vector3i(
|
||||
rng.randi_range(0, dungeon_size.x - room.get_grid_aabbi(true).size.x),
|
||||
y_pos,
|
||||
rng.randi_range(0, dungeon_size.z - room.get_grid_aabbi(true).size.z)))
|
||||
place_room(room)
|
||||
elif get_stair_rooms_from_instances().filter(func(s): return s.min_count > _stair_rooms_placed_count[s]).size() > 0:
|
||||
var stairs_less_than_min = get_stair_rooms_from_instances().filter(func(s): return s.min_count > _stair_rooms_placed_count[s])
|
||||
var room_original = stairs_less_than_min[rng.randi() % stairs_less_than_min.size()]
|
||||
_stair_rooms_placed_count[room_original] += 1
|
||||
var room = room_original.create_clone_and_make_virtual_unless_visualizing()
|
||||
room.room_rotations = rng.randi_range(0,4)
|
||||
room.set_position_by_grid_pos(Vector3i(
|
||||
rng.randi_range(0, dungeon_size.x - room.get_grid_aabbi(true).size.x),
|
||||
rng.randi_range(0, dungeon_size.y - room.get_grid_aabbi(true).size.y),
|
||||
rng.randi_range(0, dungeon_size.z - room.get_grid_aabbi(true).size.z)))
|
||||
place_room(room)
|
||||
else:
|
||||
stage += 1
|
||||
|
||||
# Encapsulate some checks for stair room instances & how they can connect two or more floors.
|
||||
class StairRoomInfo:
|
||||
var inst : DungeonRoom3D
|
||||
var stair_gaps = [] # Has door(s) leading 1 floors up, 2 floors up, etc.
|
||||
var stair_gaps_dict = {} # Gap:local door y position. To check y offset to place room at when connecting floors.
|
||||
var lowest_door_local_y_pos : int
|
||||
var available_to_use : int # Make sure not to go above stair's max_count value
|
||||
|
||||
# Helper funcs for the stair chains where it might fail
|
||||
var _saved_available_to_use : int = 0
|
||||
func save_available_to_use():
|
||||
_saved_available_to_use = available_to_use
|
||||
func restore_available_to_use():
|
||||
available_to_use =_saved_available_to_use
|
||||
|
||||
func _init(room_instance : DungeonRoom3D):
|
||||
self.inst = room_instance
|
||||
available_to_use = room_instance.max_count
|
||||
|
||||
var door_y_positions = []
|
||||
for door in room_instance.get_doors_cached():
|
||||
if not door.local_pos.y in door_y_positions:
|
||||
door_y_positions.push_back(door.local_pos.y)
|
||||
door_y_positions.sort()
|
||||
stair_gaps_dict = {}
|
||||
for i in range(0, len(door_y_positions)):
|
||||
for j in range(i+1, len(door_y_positions)):
|
||||
var gap = door_y_positions[j] - door_y_positions[i]
|
||||
if not stair_gaps_dict.has(gap): stair_gaps_dict[gap] = []
|
||||
stair_gaps_dict[gap].append(door_y_positions[i])
|
||||
stair_gaps = stair_gaps_dict.keys()
|
||||
|
||||
# format: [[DungeonRoom3D, grid_pos_y], ...]. returns [] if no valid way to connect those floors
|
||||
func get_valid_connect_positions(floor_1 : int, floor_2 : int) -> Array:
|
||||
if available_to_use == 0: return []
|
||||
|
||||
var bottom_floor : int = min(floor_1, floor_2)
|
||||
var top_floor : int = max(floor_1, floor_2)
|
||||
var gap : int = top_floor - bottom_floor
|
||||
if stair_gaps_dict.has(gap):
|
||||
# Find actual valid pos from door local y positions, where doors spanning gap will land on floor_1 and floor_2
|
||||
var valid_offsets = stair_gaps_dict[gap].filter(func(y_offset : int): return floor_1 - y_offset >= 0 and floor_1 - y_offset + inst.size_in_voxels.y <= inst.dungeon_generator.dungeon_size.y)
|
||||
return valid_offsets.map(func(y_offset : int): return [inst, floor_1 - y_offset])
|
||||
return []
|
||||
|
||||
# Returns empty array if couldn't find a chain of stair rooms to connect the two floors.
|
||||
# The main case I want to solve here is just a simple chain of multiple 2 floor stairs to connect a more than 2 floor gap.
|
||||
# There are a ton of edge cases, which I won't even check for.
|
||||
# This is some heuristic algorithm I thought of which just tries to find a set of rooms which when chained together,
|
||||
# continually closes the gap between floors. Should work for basic cases and also some more advanced ones.
|
||||
func _find_stair_chain_to_connect_floors(floor_1 : int, floor_2 : int, floor_graph : TreeGraph, stair_info_dict : Dictionary) -> Array:
|
||||
var stair_chain := []
|
||||
var bottom_floor : int = min(floor_1, floor_2)
|
||||
var top_floor : int = max(floor_1, floor_2)
|
||||
var cur_floor := bottom_floor
|
||||
for s in stair_info_dict.values():
|
||||
s.save_available_to_use()
|
||||
while cur_floor != top_floor:
|
||||
# Valid floor = any floor which is closer to top_floor than cur_floor.
|
||||
var valid_to_floors := range(dungeon_size.y).filter(func(floor : int): return abs(floor - top_floor) < abs(top_floor - cur_floor))
|
||||
var valid_stair_placements := {}
|
||||
for to_floor in valid_to_floors:
|
||||
for stair_info in stair_info_dict.values():
|
||||
var valid_connect = stair_info.get_valid_connect_positions(cur_floor, to_floor)
|
||||
if valid_connect.size() > 0:
|
||||
if not valid_stair_placements.has(to_floor): valid_stair_placements[to_floor] = []
|
||||
valid_stair_placements[to_floor].append_array(valid_connect)
|
||||
if valid_stair_placements.keys().size() == 0:
|
||||
for s in stair_info_dict.values():
|
||||
s.restore_available_to_use()
|
||||
return [] # None found
|
||||
var to_floor : int = valid_stair_placements.keys()[rng.randi() % valid_stair_placements.keys().size()]
|
||||
var choose = valid_stair_placements[to_floor][rng.randi() % valid_stair_placements[to_floor].size()]
|
||||
if not floor_graph.has_node(cur_floor) or not floor_graph.has_node(to_floor) or not floor_graph.are_nodes_connected(cur_floor, to_floor):
|
||||
stair_chain.push_back(choose) # No need unless not connected already
|
||||
stair_info_dict[choose[0]].available_to_use -= 1
|
||||
cur_floor = to_floor
|
||||
return stair_chain
|
||||
|
||||
# returns Array with elements in format [room : DungeonRoom3D, room_grid_pos_y : int]
|
||||
func _make_and_solve_floors_graph() -> Array:
|
||||
# Set up a tree (union find) graph of all the floors with doors on them.
|
||||
# This keeps track of what floors are connected by rooms spanning multiple floors.
|
||||
var floors_tree_graph := TreeGraph.new()
|
||||
var add_room_to_floors_graph := (func(room : DungeonRoom3D, room_y_pos : int):
|
||||
for door in room.get_doors_cached():
|
||||
var door_exit_y = door.local_pos.y + room_y_pos
|
||||
if not door_exit_y in floors_tree_graph.get_all_nodes():
|
||||
floors_tree_graph.add_node(door_exit_y)
|
||||
floors_tree_graph.connect_nodes(door_exit_y, room.get_doors_cached()[0].local_pos.y + room_y_pos))
|
||||
var rooms = []
|
||||
rooms.append_array(_rooms_placed)
|
||||
rooms.append_array(get_preplaced_rooms())
|
||||
for room in rooms:
|
||||
add_room_to_floors_graph.call(room, room.get_grid_pos().y)
|
||||
|
||||
if not floors_tree_graph.is_fully_connected() and get_stair_rooms_from_instances().size() == 0:
|
||||
_fail_generation("No stair rooms defined. Add a room with with is_stair_room set to true.")
|
||||
return []
|
||||
|
||||
# Solve the stair graph. We'll use a heuristic algorithm.
|
||||
# Tried to think of something elegant to do this but it may be harder than I thought.
|
||||
# This should be fine for 99% of cases. For truly odd stair patterns in dungeons you can preplace stairs or mod yourself.
|
||||
|
||||
var stair_info_dict : Dictionary = {}
|
||||
for room in get_stair_rooms_from_instances():
|
||||
stair_info_dict[room] = StairRoomInfo.new(room)
|
||||
|
||||
var stairs_to_add : Array = [] # element format: [DungeonRoom3D, grid_pos_y]
|
||||
while not floors_tree_graph.is_fully_connected():
|
||||
# Simple case - look for when we can directly connect 2 floors with a stair
|
||||
var stair_able_to_connect_floors_directly = []
|
||||
for f1 in floors_tree_graph.get_all_nodes():
|
||||
for f2 in floors_tree_graph.get_all_nodes().filter(func(_f2): return _f2 > f1):
|
||||
if floors_tree_graph.are_nodes_connected(f1, f2): continue
|
||||
for s in stair_info_dict.values():
|
||||
# Just looking for 1 valid element [DungeonRoom3D, grid_pos_y]
|
||||
stair_able_to_connect_floors_directly.append_array(s.get_valid_connect_positions(f1, f2))
|
||||
if stair_able_to_connect_floors_directly.size() > 0:
|
||||
break
|
||||
if stair_able_to_connect_floors_directly.size() > 0: break
|
||||
if stair_able_to_connect_floors_directly.size() > 0: break
|
||||
if stair_able_to_connect_floors_directly.size() > 0:
|
||||
var stair_and_pos = stair_able_to_connect_floors_directly[rng.randi() % stair_able_to_connect_floors_directly.size()]
|
||||
stairs_to_add.push_back(stair_and_pos)
|
||||
add_room_to_floors_graph.call(stair_and_pos[0], stair_and_pos[1])
|
||||
stair_info_dict[stair_and_pos[0]].available_to_use -= 1
|
||||
continue
|
||||
# There are many cases where it's not possible to directly connect floors,
|
||||
# but you could chain more than 1 stair room together to connect them.
|
||||
# So here we'll do a non exhaustive search which attempts to cover most of these cases.
|
||||
var stair_room_chain_to_connect_floors = []
|
||||
for f1 in floors_tree_graph.get_all_nodes():
|
||||
var nearest_floors_up = floors_tree_graph.get_all_nodes().filter(func(_f2): return _f2 > f1)
|
||||
nearest_floors_up.sort_custom(func(fa,fb): return abs(fb - f1) > abs(fa - f1))
|
||||
for f2 in nearest_floors_up:
|
||||
if floors_tree_graph.are_nodes_connected(f1, f2): continue
|
||||
stair_room_chain_to_connect_floors = _find_stair_chain_to_connect_floors(f1, f2, floors_tree_graph, stair_info_dict)
|
||||
if stair_room_chain_to_connect_floors.size() > 0:
|
||||
break
|
||||
if stair_room_chain_to_connect_floors.size() > 0: break
|
||||
if stair_room_chain_to_connect_floors.size() > 0:
|
||||
for room_and_pos in stair_room_chain_to_connect_floors:
|
||||
add_room_to_floors_graph.call(room_and_pos[0], room_and_pos[1])
|
||||
stairs_to_add.push_back(room_and_pos)
|
||||
continue
|
||||
|
||||
_fail_generation("Failed to connect all floors together with stairs. Ensure you have at least 1 DungeonRoom3D with 'is_stair_room' set to true with 2 or more doors leading different floors. Simplest is a 2 floor room with 1 door on each floor.")
|
||||
_printerr("Stair algorithm failed. If your stairs are shaped very oddly it can fail, it's not an exhaustive search but should work for most cases. Also ensure stairs max_count is enough to connect all the floors in your dungeon.")
|
||||
for s in stair_info_dict.values():
|
||||
_printwarning("Room "+str(s.inst.name)+" max_count is "+str(s.inst.max_count)+". One potential reason this could fail is you need to increase the max_count on your stair room(s) so all floors can be connected.")
|
||||
return []
|
||||
|
||||
return stairs_to_add
|
||||
|
||||
# Separating rooms:
|
||||
|
||||
var _aabbis_with_doors = {}
|
||||
func separate_rooms_iteration(first_call_in_loop : bool) -> void:
|
||||
var rooms = get_all_placed_and_preplaced_rooms()
|
||||
if first_call_in_loop:
|
||||
_aabbis_with_doors = {}
|
||||
for room in rooms:
|
||||
_aabbis_with_doors[room] = room.get_grid_aabbi(true)
|
||||
|
||||
var any_overlap := false
|
||||
for i in range(0, len(rooms)):
|
||||
for j in range(i + 1, len(rooms)):
|
||||
var fast_check = _aabbis_with_doors[rooms[i]].intersects(_aabbis_with_doors[rooms[j]])
|
||||
if fast_check and rooms[i].overlaps_room(rooms[j]):
|
||||
if not rooms[i] in get_preplaced_rooms():
|
||||
rooms[i].push_away_from_and_stay_within_bounds(rooms[j])
|
||||
_aabbis_with_doors[rooms[i]] = rooms[i].get_grid_aabbi(true)
|
||||
if not rooms[j] in get_preplaced_rooms():
|
||||
rooms[j].push_away_from_and_stay_within_bounds(rooms[i])
|
||||
_aabbis_with_doors[rooms[j]] = rooms[j].get_grid_aabbi(true)
|
||||
any_overlap = true
|
||||
|
||||
if not any_overlap:
|
||||
stage += 1
|
||||
|
||||
# Connecting rooms:
|
||||
|
||||
var _astar3d : DungeonAStar3D
|
||||
var _quick_room_check_dict : Dictionary
|
||||
var _quick_corridors_check_dict : Dictionary
|
||||
var _non_corridor_rooms : Array
|
||||
var _rooms_to_connect : Array
|
||||
var _all_doors_dict : Dictionary
|
||||
var _required_doors_dict : Dictionary
|
||||
var _last_rooms_to_connect_counts := []
|
||||
func connect_rooms_iteration(first_call_in_loop : bool) -> void:
|
||||
if first_call_in_loop:
|
||||
_rooms_to_connect = []
|
||||
_last_rooms_to_connect_counts = []
|
||||
_non_corridor_rooms = []
|
||||
_quick_room_check_dict = {}
|
||||
_quick_corridors_check_dict = {}
|
||||
_all_doors_dict = {}
|
||||
_required_doors_dict = {}
|
||||
for room in get_all_placed_and_preplaced_rooms():
|
||||
var doors = room.get_doors_cached()
|
||||
if room.get_doors_cached().size() > 0:
|
||||
_rooms_to_connect.push_back(room)
|
||||
_non_corridor_rooms.push_back(room)
|
||||
for door in doors:
|
||||
if get_grid_aabbi().contains_point(door.exit_pos_grid):
|
||||
_all_doors_dict[door.exit_pos_grid] = door
|
||||
if not door.optional:
|
||||
_required_doors_dict[door.exit_pos_grid] = door
|
||||
var aabbi = room.get_grid_aabbi(false)
|
||||
for x in aabbi.size.x: for y in aabbi.size.y: for z in aabbi.size.z:
|
||||
_quick_room_check_dict[aabbi.position + Vector3i(x,y,z)] = room
|
||||
# Init after corridors/room dict setup
|
||||
_astar3d = DungeonAStar3D.new(self, _quick_room_check_dict, _quick_corridors_check_dict)
|
||||
# Sort rooms by door y positions. Likely to make astar connections more stable
|
||||
_rooms_to_connect.sort_custom(func(a,b): return b.get_doors_cached()[0].exit_pos_grid.y > a.get_doors_cached()[0].exit_pos_grid.y)
|
||||
|
||||
# Can exit early if _rooms_to_connect stops going down, aka we tried shuffling and couldn't find a connection
|
||||
# Don't think this is necessary actually, I just exit below instead if it fails ever.
|
||||
#_last_rooms_to_connect_counts.push_front(len(_rooms_to_connect))
|
||||
#_last_rooms_to_connect_counts = _last_rooms_to_connect_counts.filter(func(c): return c == _last_rooms_to_connect_counts[0])
|
||||
#if len(_last_rooms_to_connect_counts) > (len(_rooms_to_connect) * 4) and _last_rooms_to_connect_counts[0] == len(_rooms_to_connect):
|
||||
#_fail_generation("Unable to connect all rooms")
|
||||
#return
|
||||
|
||||
# First, just pathfind through all the rooms, one to the next, until all rooms are connected
|
||||
if len(_rooms_to_connect) >= 2:
|
||||
var room_0_pos = _rooms_to_connect[0].get_grid_aabbi(false).position
|
||||
var room_1_pos = _rooms_to_connect[1].get_grid_aabbi(false).position
|
||||
var connect_path := _astar3d.get_vec3i_path(room_0_pos, room_1_pos)
|
||||
var room_a := _rooms_to_connect.pop_front()
|
||||
if len(connect_path) == 0:
|
||||
#print("Failed somehow")
|
||||
#_rooms_to_connect.insert(rng.randi_range(1, len(_rooms_to_connect)), room_a)
|
||||
_fail_generation("Failed to fully connect dungeon rooms with corridors.")
|
||||
return
|
||||
#print("Connecting ", room_a.name, " to ", _rooms_to_connect[0].name, ". Result: ", connect_path)
|
||||
for corridor_pos in connect_path:
|
||||
if not _quick_room_check_dict.has(corridor_pos) and not _quick_corridors_check_dict.has(corridor_pos):
|
||||
var room := corridor_room_instance.create_clone_and_make_virtual_unless_visualizing()
|
||||
room.set_position_by_grid_pos(corridor_pos)
|
||||
place_room(room)
|
||||
_quick_corridors_check_dict[corridor_pos] = room
|
||||
return
|
||||
|
||||
# Next, not all the doors may be connected, so we have to do some strategy to nicely connect the required doors remaining.
|
||||
for required_door in _required_doors_dict.values().slice(0): # Slice necessary? Not sure.
|
||||
# No need to connect doors which already have a corridor
|
||||
if _quick_room_check_dict.has(required_door.exit_pos_grid) or _quick_corridors_check_dict.has(required_door.exit_pos_grid):
|
||||
_required_doors_dict.erase(required_door)
|
||||
continue
|
||||
# Get other room doors which are closest to the required door
|
||||
var closest_other_room_doors = _all_doors_dict.values().slice(0)#filter(func(d): return d.room != required_door.room)
|
||||
closest_other_room_doors.sort_custom(func(a,b):
|
||||
# Make sure doors not on same floor sorted far after
|
||||
# Also make sure doors of same room sorted far after
|
||||
var b_dist = Vector3(b.exit_pos_grid - required_door.exit_pos_grid).length()
|
||||
var a_dist = Vector3(a.exit_pos_grid - required_door.exit_pos_grid).length()
|
||||
if a.exit_pos_grid.y != required_door.exit_pos_grid.y or a.room == required_door.room:
|
||||
a_dist += dungeon_size.x + dungeon_size.y + dungeon_size.z
|
||||
if b.exit_pos_grid.y != required_door.exit_pos_grid.y or b.room == required_door.room:
|
||||
b_dist += dungeon_size.x + dungeon_size.y + dungeon_size.z
|
||||
return b_dist > a_dist)
|
||||
|
||||
_astar3d.cap_required_doors_phase = true
|
||||
var connect_path := _astar3d.get_vec3i_path(required_door.exit_pos_grid, closest_other_room_doors[0].exit_pos_grid)
|
||||
for corridor_pos in connect_path:
|
||||
if not _quick_room_check_dict.has(corridor_pos) and not _quick_corridors_check_dict.has(corridor_pos):
|
||||
var room := corridor_room_instance.create_clone_and_make_virtual_unless_visualizing()
|
||||
room.set_position_by_grid_pos(corridor_pos)
|
||||
place_room(room)
|
||||
_quick_corridors_check_dict[corridor_pos] = room
|
||||
_required_doors_dict.erase(required_door)
|
||||
return
|
||||
|
||||
stage = BuildStage.FINALIZING
|
||||
|
||||
####################################
|
||||
## DUNGEON BUILD HELPER FUNCTIONS ##
|
||||
####################################
|
||||
|
||||
func get_rooms_less_than_min_count(include_stairs : bool):
|
||||
return room_instances.filter(func(room : DungeonRoom3D):
|
||||
if not include_stairs and room.is_stair_room:
|
||||
return false
|
||||
var already_in_tree = _rooms_placed.filter(func(placed_room : DungeonRoom3D):
|
||||
return room.get_original_packed_scene() == placed_room.get_original_packed_scene())
|
||||
return len(already_in_tree) < room.min_count)
|
||||
|
||||
func get_rooms_less_than_max_count(include_stairs : bool):
|
||||
return room_instances.filter(func(room : DungeonRoom3D):
|
||||
if not include_stairs and room.is_stair_room:
|
||||
return false
|
||||
var already_in_tree = _rooms_placed.filter(func(placed_room : DungeonRoom3D):
|
||||
return room.get_original_packed_scene() == placed_room.get_original_packed_scene())
|
||||
return len(already_in_tree) < room.min_count)
|
||||
|
||||
func get_randomly_positioned_room() -> DungeonRoom3D:
|
||||
var room : DungeonRoom3D = get_rooms_less_than_max_count(false)[rng.randi() % get_rooms_less_than_max_count(false).size()]
|
||||
room = room.create_clone_and_make_virtual_unless_visualizing()
|
||||
var buf := dungeon_size - room.get_grid_aabbi(false).size
|
||||
var rand_pos : Vector3i = Vector3i(rng.randi_range(0, buf.x), rng.randi_range(0, buf.y), rng.randi_range(0, buf.z))
|
||||
room.room_rotations = rng.randi_range(0,3)
|
||||
room.set_position_by_grid_pos(rand_pos)
|
||||
return room
|
||||
|
||||
func place_room(room : DungeonRoom3D) -> void:
|
||||
room.dungeon_generator = self # Incase wasn't set yet
|
||||
_rooms_placed.push_back(room)
|
||||
if visualize_generation_progress:
|
||||
rooms_container.add_child(room)
|
||||
room.snap_room_to_dungeon_grid()
|
||||
|
||||
###################################
|
||||
## INITIALIZE/CLEANUP GENERATION ##
|
||||
###################################
|
||||
|
||||
func create_or_recreate_rooms_container() -> void:
|
||||
if rooms_container and is_instance_valid(rooms_container):
|
||||
if rooms_container.is_inside_tree():
|
||||
rooms_container.get_parent().remove_child(rooms_container)
|
||||
rooms_container.queue_free()
|
||||
if get_node_or_null("RoomsContainer"): # Incase it's still a child if rooms_container was null somehow
|
||||
var rc = get_node_or_null("RoomsContainer")
|
||||
remove_child(rc)
|
||||
rc.queue_free()
|
||||
rooms_container = Node3D.new()
|
||||
rooms_container.name = "RoomsContainer"
|
||||
if visualize_generation_progress:
|
||||
add_child(rooms_container)
|
||||
|
||||
func clear_rooms_container_and_setup_for_next_iteration():
|
||||
if visualize_generation_progress:
|
||||
for c in rooms_container.get_children():
|
||||
c.queue_free()
|
||||
rooms_container.remove_child(c)
|
||||
else:
|
||||
for room in _rooms_placed:
|
||||
room.queue_free()
|
||||
_rooms_placed = []
|
||||
|
||||
# Clears the room & corridor instances pool, unless they are in tree.
|
||||
# For flexibility checking if in tree
|
||||
# Room instances are used as a library to run checks on,
|
||||
# but could allow placing them as last room of their kind to save some memory/an instantiate call.
|
||||
func _clear_room_instances() -> void:
|
||||
for room in room_instances:
|
||||
if room and is_instance_valid(room):
|
||||
room.queue_free()
|
||||
if corridor_room_instance and is_instance_valid(corridor_room_instance):
|
||||
corridor_room_instance.queue_free()
|
||||
room_instances = []
|
||||
corridor_room_instance = null
|
||||
|
||||
func cleanup_and_reset_dungeon_generator() -> void:
|
||||
if is_currently_generating:
|
||||
_fail_generation("Dungeon reset while generating.")
|
||||
if running_thread:
|
||||
if running_thread.is_alive() or running_thread.is_started():
|
||||
running_thread.wait_to_finish()
|
||||
running_thread = null
|
||||
if get_node_or_null("RoomsContainer"):
|
||||
var rc = get_node_or_null("RoomsContainer")
|
||||
remove_child(rc)
|
||||
rc.queue_free()
|
||||
_clear_room_instances()
|
||||
failed_to_generate = false
|
||||
full_abort_triggered = false
|
||||
iterations = 0
|
||||
retry_attempts = 0
|
||||
stage = BuildStage.NOT_STARTED
|
||||
|
||||
func setup_room_instances_and_validate_before_generate() -> bool:
|
||||
if not room_scenes:
|
||||
_printerr("SimpleDungeons Error: No DungeonRoom3D room scenes set.")
|
||||
return false
|
||||
var inst_arr : Array[DungeonRoom3D] = []
|
||||
for s in room_scenes:
|
||||
if not s: continue
|
||||
var inst = s.instantiate()
|
||||
if not inst is DungeonRoom3D:
|
||||
_printerr("SimpleDungeons Error: "+s.resource_path+" room scene does not inherit DungeonRoom3D. Also may need @tool annotation if generating in editor.")
|
||||
return false
|
||||
else:
|
||||
inst.dungeon_generator = self
|
||||
# Need to save door info before cloning/making virtual copies w/o actual nodes/meshes inside for performance
|
||||
inst.ensure_doors_and_or_transform_cached_for_threads_and_virtualized_rooms()
|
||||
inst_arr.append(inst as DungeonRoom3D)
|
||||
room_instances = inst_arr
|
||||
corridor_room_instance = corridor_room_scene.instantiate() if corridor_room_scene else null
|
||||
if corridor_room_instance:
|
||||
#corridor_room_instance.dungeon_generator = self
|
||||
corridor_room_instance.set("dungeon_generator", self)
|
||||
#corridor_room_instance.ensure_doors_and_or_transform_cached_for_threads_and_virtualized_rooms()
|
||||
return validate_dungeon()
|
||||
|
||||
####################
|
||||
## UTIL FUNCTIONS ##
|
||||
####################
|
||||
|
||||
# printerr() and push_warning() eat my outputs a lot. Regular prints are more reliable.
|
||||
func _printerr(str : String, str2 : String = "", str3 : String = "", str4 : String = ""):
|
||||
print_rich("[color=#FF3531]"+(str+str2+str3+str4)+"[/color]")
|
||||
func _printwarning(str : String, str2 : String = "", str3 : String = "", str4 : String = ""):
|
||||
print_rich("[color=#FFF831]"+(str+str2+str3+str4)+"[/color]")
|
||||
|
||||
func get_grid_aabbi() -> AABBi:
|
||||
return AABBi.new(Vector3i(0,0,0), dungeon_size)
|
||||
|
||||
func get_room_at_pos(grid_pos : Vector3i) -> DungeonRoom3D:
|
||||
if stage > BuildStage.CONNECT_ROOMS:
|
||||
# Can use these vars for speedup if past the connect rooms stage where we set them
|
||||
var quick_check = _quick_room_check_dict.get(grid_pos)
|
||||
return quick_check if quick_check else _quick_corridors_check_dict.get(grid_pos)
|
||||
for room in get_all_placed_and_preplaced_rooms():
|
||||
if room.get_grid_aabbi(false).contains_point(grid_pos):
|
||||
return room
|
||||
return null
|
||||
|
||||
var _preplaced_rooms_cached : Array = []
|
||||
func get_preplaced_rooms() -> Array:
|
||||
var rooms := []
|
||||
if OS.get_thread_caller_id() != OS.get_main_thread_id():
|
||||
return _preplaced_rooms_cached.slice(0)
|
||||
else:
|
||||
rooms.assign(get_children().filter(func(c): return c is DungeonRoom3D))
|
||||
_preplaced_rooms_cached = rooms.slice(0)
|
||||
return rooms
|
||||
|
||||
func get_all_placed_and_preplaced_rooms() -> Array:
|
||||
var rooms := get_preplaced_rooms()
|
||||
rooms.append_array(_rooms_placed)
|
||||
return rooms
|
||||
|
||||
# Any rooms with doors leading to more than 1 floor are considered stairs
|
||||
func get_stair_rooms_from_instances() -> Array:
|
||||
var rooms := []
|
||||
rooms.assign(room_instances.filter(func(c):
|
||||
if not c is DungeonRoom3D: return false
|
||||
if not c.is_stair_room: return false
|
||||
var floors_dict = {}
|
||||
for door in c.get_doors_cached():
|
||||
floors_dict[door.local_pos.y] = true
|
||||
return floors_dict.keys().size() >= 2))
|
||||
return rooms
|
||||
|
||||
################
|
||||
## VALIDATION ##
|
||||
################
|
||||
|
||||
# Returns true if no errors found before generating.
|
||||
# Calls callbacks with warning/error string if any.
|
||||
# Upon generate, also calls validation checks on each of the rooms.
|
||||
func validate_dungeon(error_callback = null, warning_callback = null) -> bool:
|
||||
# printerr and push_warning do not always work. Only print() seems to reliably output.
|
||||
if not warning_callback is Callable: warning_callback = (func(str): _printwarning("SimpleDungeons Warning: ", str))
|
||||
if not error_callback is Callable: error_callback = (func(str): _printerr("SimpleDungeons Error: ", str))
|
||||
var any_errors : = {"err": false} # So lambda closure captures
|
||||
error_callback = (func(str): any_errors["err"] = true; error_callback.call(str))
|
||||
|
||||
if not corridor_room_scene:
|
||||
error_callback.call("No corridor room scene set. Add a 1x1x1 (in voxels) corridor room scene.")
|
||||
if room_scenes.size() == 0:
|
||||
error_callback.call("No rooms added. Add DungeonRoom scenes to the room_scenes property.")
|
||||
|
||||
if not is_currently_generating:
|
||||
return not any_errors["err"]
|
||||
|
||||
# Build stage checks:
|
||||
|
||||
if not room_instances is Array or len(room_instances) == 0:
|
||||
error_callback.call("No rooms added. Cannot generate dungeon.")
|
||||
if room_instances is Array:
|
||||
for room in room_instances:
|
||||
if not room is DungeonRoom3D:
|
||||
error_callback.call("Room "+room.name+" does not inherit DungeonRoom3D! Also add @tool to dungeon room script if generating in editor.")
|
||||
else:
|
||||
if not room.has_method("validate_room"):
|
||||
error_callback.call("validate_room() method not found on room "+room.name+". Ensure it inherits DungeonRoom3D and has the @tool annotation if you're trying to generate in editor.")
|
||||
room.validate_room(error_callback, warning_callback)
|
||||
if not corridor_room_instance is DungeonRoom3D:
|
||||
var corridor_name = corridor_room_instance.name if corridor_room_instance else "null"
|
||||
error_callback.call("Corridor Room "+corridor_name+" does not inherit DungeonRoom3D!")
|
||||
if corridor_room_instance is DungeonRoom3D and corridor_room_instance.size_in_voxels != Vector3i(1,1,1):
|
||||
error_callback.call("Corridor Room must be 1x1x1 in voxels.")
|
||||
if corridor_room_instance and not corridor_room_instance.has_method("get_doors"):
|
||||
error_callback.call("get_doors() method not found on the corridor room. Ensure it inherits DungeonRoom3D and has the @tool annotation if you're trying to generate in editor.")
|
||||
if corridor_room_instance is DungeonRoom3D and (corridor_room_instance.get_doors().size() != 4 or corridor_room_instance.get_doors().any(func(d): return not d.optional)):
|
||||
error_callback.call("Corridor Room must have 4 optional doors")
|
||||
if corridor_room_instance is DungeonRoom3D:
|
||||
corridor_room_instance.validate_room(error_callback, warning_callback)
|
||||
if corridor_room_instance.size_in_voxels != Vector3i(1,1,1):
|
||||
error_callback.call("Corridor room scene must be 1x1x1 in voxels. It is used to connect the rooms with hallways.")
|
||||
|
||||
return not any_errors["err"]
|
||||
423
addons/SimpleDungeons/DungeonRoom3D.gd
Normal file
@@ -0,0 +1,423 @@
|
||||
@tool
|
||||
class_name DungeonRoom3D
|
||||
extends Node3D
|
||||
|
||||
signal dungeon_done_generating()
|
||||
|
||||
var dungeon_generator : DungeonGenerator3D :
|
||||
get:
|
||||
if is_inside_tree() and get_parent() is DungeonGenerator3D:
|
||||
return get_parent()
|
||||
else: return dungeon_generator
|
||||
|
||||
@export var size_in_voxels := Vector3i(1,1,1) :
|
||||
set(v):
|
||||
size_in_voxels = v.clamp(Vector3i(1,1,1),Vector3i(9999,9999,9999))
|
||||
@export var voxel_scale := Vector3(10,10,10) :
|
||||
set(v):
|
||||
voxel_scale = v.clamp(Vector3(0.0001,0.0001,0.0001),Vector3(9999,9999,9999))
|
||||
|
||||
@export var min_count : int = 2
|
||||
@export var max_count : int = 5
|
||||
|
||||
## Stair rooms are used in the floor connection stage and will have their min/max counts ignored.
|
||||
## For the dungeon to generate, you must mark at least 1 room as a stair and have its doors span multiple floors.
|
||||
@export var is_stair_room := false
|
||||
|
||||
## Preplaced rooms are immovable rooms you can place at a preset position in the dungeon
|
||||
@export_group("Pre-placed room options")
|
||||
## Editor button to align the room's position & scale with the dungeon's voxel grid.
|
||||
## Will be called before generation to ensure room is aligned with grid.
|
||||
@export var force_align_with_grid_button := false :
|
||||
set(_v):
|
||||
virtual_transform = self.transform
|
||||
snap_room_to_dungeon_grid()
|
||||
|
||||
func _validate_property(property: Dictionary):
|
||||
if property.name in ["force_align_with_grid_button"] and not get_parent() is DungeonGenerator3D:
|
||||
property.usage = PROPERTY_USAGE_NO_EDITOR
|
||||
|
||||
@export_group("Debug view")
|
||||
@export var show_debug_in_editor : bool = true
|
||||
@export var show_debug_in_game : bool = false
|
||||
## For internal debug use only
|
||||
@export var show_grid_aabb_with_doors : bool = false
|
||||
|
||||
var was_preplaced = false :
|
||||
get:
|
||||
if Engine.is_editor_hint():
|
||||
return is_inside_tree() and get_parent() is DungeonGenerator3D
|
||||
else: return was_preplaced
|
||||
|
||||
## Number of 90 degree rotations around the y axis
|
||||
var room_rotations : int :
|
||||
set(v): virtual_transform.basis = Basis.from_euler(Vector3(0,wrapi(v, 0, 4) * deg_to_rad(90.0),0)).scaled(virtual_transform.basis.get_scale())
|
||||
get: return round(wrapi(round(virtual_transform.basis.get_euler().y / deg_to_rad(90.0)), 0, 4))
|
||||
|
||||
# For performance, we should not spawn/instantiate many dungeon rooms.
|
||||
# This is because the dungeon generator may have to restart multiple times.
|
||||
# We don't want to have to delete and restart all nodes/models/etc we instantiated
|
||||
# Also, we cannot access the scene tree from other threads.
|
||||
# So instead, we will create virtual DungeonRoom3Ds which reference the single original we create.
|
||||
# Then, we can still encapsulate all the logic into this one class & improve performance.
|
||||
var virtualized_from : DungeonRoom3D = null
|
||||
# To be called instead of self on anything that requires transform or node access
|
||||
# I think just in getting door nodes is the only part we need this.
|
||||
var virtual_self : DungeonRoom3D = self :
|
||||
get: return virtualized_from if virtualized_from else self
|
||||
# Cannot access the transform in threads at all so just saving it here and copying on unvirtualize.
|
||||
var virtual_transform : Transform3D = Transform3D() :
|
||||
set(v):
|
||||
virtual_transform = v
|
||||
if is_inside_tree() and OS.get_main_thread_id() == OS.get_thread_caller_id():
|
||||
self.transform = v
|
||||
|
||||
var original_ready_func_called := false # For error checking. Ensure noone inherits this class w/o calling _ready.
|
||||
func _ready():
|
||||
original_ready_func_called = true
|
||||
if not virtualized_from:
|
||||
add_debug_view_if_not_exist()
|
||||
# When spawning a room, make sure to set its transform to whatever its virtual/out of tree transform was set to.
|
||||
if virtual_transform != Transform3D():
|
||||
self.transform = virtual_transform
|
||||
elif self.transform != Transform3D():
|
||||
# For preplaced rooms:
|
||||
virtual_transform = self.transform
|
||||
# Mostly for debug and pre placed rooms:
|
||||
if get_parent() is Node3D:
|
||||
if get_parent() is DungeonGenerator3D:
|
||||
dungeon_generator = get_parent()
|
||||
if get_parent().get_parent() is DungeonGenerator3D:
|
||||
dungeon_generator = get_parent().get_parent()
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
func _process(delta):
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
const _dungeon_room_export_props_names = ["size_in_voxels", "voxel_scale", "min_count", "max_count", "is_stair_room", "show_debug_in_editor", "show_debug_in_game", "show_grid_aabb_with_doors"]
|
||||
func copy_all_props(from : DungeonRoom3D, to : DungeonRoom3D) -> void:
|
||||
for prop in _dungeon_room_export_props_names:
|
||||
if from.get(prop) != to.get(prop):
|
||||
to.set(prop, from.get(prop))
|
||||
to.name = from.name
|
||||
to.dungeon_generator = from.dungeon_generator
|
||||
|
||||
func get_original_packed_scene() -> PackedScene:
|
||||
# Try to find the room packed scene in the DungeonGenerator3D so we don't have to load()
|
||||
if dungeon_generator:
|
||||
if dungeon_generator.corridor_room_scene.resource_path == virtual_self.scene_file_path:
|
||||
return dungeon_generator.corridor_room_scene
|
||||
for scene in dungeon_generator.room_scenes:
|
||||
if scene.resource_path == virtual_self.scene_file_path:
|
||||
return scene
|
||||
# Fall back to just getting it from the scene path just incase
|
||||
if virtual_self.scene_file_path:
|
||||
return load(virtual_self.scene_file_path)
|
||||
printerr(self.name+" Could not find DungeonRoom3D's original packed scene. This shouldn't happen. Are you manually spawning rooms?")
|
||||
return null
|
||||
|
||||
func create_clone_and_make_virtual_unless_visualizing() -> DungeonRoom3D:
|
||||
var make_clone_virtual : bool = true
|
||||
if dungeon_generator and dungeon_generator.visualize_generation_progress:
|
||||
make_clone_virtual = false
|
||||
var _clone
|
||||
if make_clone_virtual:
|
||||
#if not self.virtual_self._doors_cache:
|
||||
# printerr("Cloning dungeon room without doors cached!!! Make sure to call .get_doors() at least once for all rooms.")
|
||||
_clone = DungeonRoom3D.new()
|
||||
_clone.virtualized_from = self.virtual_self
|
||||
# Can't access door nodes on threads, also if it's a clone, won't have any door nodes to check door pos/dir.
|
||||
#_clone._doors_cache = []
|
||||
for d in _doors_cache:
|
||||
_clone._doors_cache.push_back(Door.new(d.local_pos, d.dir, d.optional, _clone, d.door_node))
|
||||
else: _clone = get_original_packed_scene().instantiate()
|
||||
copy_all_props(virtual_self, _clone)
|
||||
_clone.dungeon_generator = self.dungeon_generator
|
||||
var name = virtual_self.name
|
||||
if dungeon_generator:
|
||||
name = name + "_" + str(len(dungeon_generator._rooms_placed))
|
||||
_clone.name = name
|
||||
return _clone
|
||||
|
||||
# Spawn the real room into the DungeonGenerator
|
||||
func unvirtualize_and_free_clone_if_needed(into_parent : Node3D) -> DungeonRoom3D:
|
||||
if not virtualized_from:
|
||||
#if self.get_parent() != into_parent:
|
||||
#if self.get_parent() != null:
|
||||
#self.get_parent().remove_child(self)
|
||||
#into_parent.add_child(self)
|
||||
return self
|
||||
# Can't do this threaded anyway for virtualized so it's implied there will be no parent
|
||||
#var parent = self.get_parent()
|
||||
#parent.remove_child(self)
|
||||
self.queue_free()
|
||||
var inst : DungeonRoom3D = get_original_packed_scene().instantiate()
|
||||
copy_all_props(self, inst)
|
||||
inst.transform = self.virtual_transform
|
||||
into_parent.add_child(inst)
|
||||
inst.owner = into_parent.owner
|
||||
return inst
|
||||
|
||||
var _debug_view = null
|
||||
func add_debug_view_if_not_exist():
|
||||
if not _debug_view:
|
||||
_debug_view = preload("res://addons/SimpleDungeons/debug_visuals/DungeonRoom3DDebugView.gd").new()
|
||||
add_child(_debug_view)
|
||||
|
||||
###########
|
||||
## DOORS ##
|
||||
###########
|
||||
|
||||
## A class that represent a door on a dungeon room
|
||||
## TODO probably remove this 'dir' variable as it is just confusing now that we have rotations
|
||||
class Door:
|
||||
var local_pos : Vector3i
|
||||
var grid_pos : Vector3i :
|
||||
get: return room.local_grid_pos_to_dungeon_grid_pos(local_pos)
|
||||
var exit_pos_local : Vector3i :
|
||||
get: return local_pos + Vector3i(DungeonUtils.DIRECTION_VECTORS[dir])
|
||||
var exit_pos_grid : Vector3i :
|
||||
get: return room.local_grid_pos_to_dungeon_grid_pos(exit_pos_local)
|
||||
var dir : DungeonUtils.Direction
|
||||
var optional : bool
|
||||
var room : DungeonRoom3D
|
||||
var door_node : Node3D
|
||||
func _init(local_pos : Vector3, dir : DungeonUtils.Direction, optional : bool, room : DungeonRoom3D, door_node : Node3D):
|
||||
self.local_pos = Vector3i(local_pos.round())
|
||||
self.dir = dir
|
||||
self.optional = optional
|
||||
self.room = room
|
||||
self.door_node = door_node
|
||||
func fits_other_door(other_room_door : Door) -> bool:
|
||||
return other_room_door.exit_pos_grid == grid_pos and other_room_door.grid_pos == exit_pos_grid
|
||||
func find_duplicates() -> Array:
|
||||
return room.get_doors().filter(func (d : Door): return d.exit_pos_local == exit_pos_local and d.local_pos == local_pos)
|
||||
func validate_door() -> bool:
|
||||
if not AABBi.new(Vector3i(), room.size_in_voxels).contains_point(local_pos):
|
||||
return false
|
||||
if AABBi.new(Vector3i(), room.size_in_voxels).contains_point(exit_pos_local):
|
||||
return false
|
||||
if find_duplicates().size() > 1:
|
||||
return false
|
||||
return true
|
||||
func get_room_leads_to() -> DungeonRoom3D:
|
||||
var other_room = room.dungeon_generator.get_room_at_pos(exit_pos_grid)
|
||||
if other_room == null: return null
|
||||
for door in other_room.get_doors():
|
||||
if fits_other_door(door):
|
||||
return other_room
|
||||
return null
|
||||
|
||||
func get_door_nodes() -> Array[Node3D]:
|
||||
var doors : Array[Node3D] = [] # .assign typecast workaround https://github.com/godotengine/godot/issues/72566
|
||||
doors.assign(virtual_self.find_children("DOOR*", "Node3D"))
|
||||
return doors
|
||||
|
||||
func get_door_by_node(node : Node) -> Door:
|
||||
for door in get_doors():
|
||||
if door.door_node == node:
|
||||
return door
|
||||
return null
|
||||
|
||||
# For calling on other threads/for virtualized rooms
|
||||
func get_doors_cached() -> Array:
|
||||
return self._doors_cache
|
||||
|
||||
func ensure_doors_and_or_transform_cached_for_threads_and_virtualized_rooms() -> void:
|
||||
if is_inside_tree(): # transform cache only applies to preplaced rooms
|
||||
virtual_transform = self.transform
|
||||
get_doors()
|
||||
|
||||
# For some reason this mutex is required or I get crashes all over the place on threads.
|
||||
# I never access _doors_cache from main thread so maybe it's overly sensitive thread guards.
|
||||
var _thread_fix_mutex := Mutex.new()
|
||||
var _doors_cache : Array = [] :
|
||||
set(v):
|
||||
_thread_fix_mutex.lock()
|
||||
_doors_cache = v
|
||||
_thread_fix_mutex.unlock()
|
||||
get:
|
||||
_thread_fix_mutex.lock()
|
||||
var tmp = _doors_cache
|
||||
_thread_fix_mutex.unlock()
|
||||
return tmp
|
||||
|
||||
func get_doors() -> Array:
|
||||
if OS.get_thread_caller_id() != OS.get_main_thread_id() or virtualized_from != null:
|
||||
# Ensure using get_doors_cached() when dealing with virtual rooms/threads.
|
||||
return _doors_cache
|
||||
var real_aabb_local = get_local_aabb()
|
||||
|
||||
var room_doors = []
|
||||
for door in get_door_nodes():
|
||||
# Get door pos from min corner of aabb, then divide by the full aabb size.
|
||||
var door_pos_pct_across = (get_transform_rel_to(door, self).origin - real_aabb_local.position) / real_aabb_local.size
|
||||
# Snap door pos to the grid square it's in
|
||||
var door_pos_grid = (door_pos_pct_across * Vector3(size_in_voxels)).floor()
|
||||
door_pos_grid = door_pos_grid.clamp(Vector3(0,0,0), Vector3(size_in_voxels) - Vector3(1,1,1))
|
||||
var grid_center_pct_across = (door_pos_grid + Vector3(0.5,0.5,0.5)) / Vector3(size_in_voxels)
|
||||
# Find the door direction by the its vector from the grid square's center point
|
||||
var door_dir := DungeonUtils.vec3_to_direction(door_pos_pct_across - grid_center_pct_across)
|
||||
var door_obj := Door.new(door_pos_grid, door_dir, door.name.begins_with("DOOR?"), self, door)
|
||||
room_doors.push_back(door_obj)
|
||||
|
||||
_doors_cache = room_doors
|
||||
return room_doors
|
||||
|
||||
#######################
|
||||
## UTILITY FUNCTIONS ##
|
||||
#######################
|
||||
|
||||
func push_away_from_and_stay_within_bounds(other_room : DungeonRoom3D) -> void:
|
||||
var diff := other_room.virtual_transform.origin - self.virtual_transform.origin
|
||||
var move := Vector3i(
|
||||
-1 if diff.x > 0 else 1,
|
||||
0,
|
||||
-1 if diff.z > 0 else 1)
|
||||
var dpos = get_grid_aabbi(true)
|
||||
var able_to_move = dpos.translated(move).push_within(dungeon_generator.get_grid_aabbi(), true).position - dpos.position
|
||||
if able_to_move.x != 0 or able_to_move.z != 0:
|
||||
set_position_by_grid_pos(get_grid_pos() + able_to_move)
|
||||
|
||||
func overlaps_room(other_room : DungeonRoom3D) -> bool:
|
||||
var aabbis = { self: self.get_grid_aabbi(false), other_room: other_room.get_grid_aabbi(false) }
|
||||
if aabbis[self].intersects(aabbis[other_room]): return true
|
||||
# Separate with a margin for doors, but allow if 2 opposing doors fit together
|
||||
var door_intersects = (func(door : Door, room : DungeonRoom3D):
|
||||
# Optional doors can intersect but not if stair room
|
||||
if door.optional and not door.room.is_stair_room: return false
|
||||
if not aabbis[room].contains_point(door.exit_pos_grid): return false
|
||||
return not room.get_doors().any(func(_d): return _d.fits_other_door(door)))
|
||||
if other_room.get_doors().any(door_intersects.bind(self)): return true
|
||||
if get_doors().any(door_intersects.bind(other_room)): return true
|
||||
return false
|
||||
|
||||
func snap_room_to_dungeon_grid() -> void:
|
||||
if not dungeon_generator:
|
||||
return
|
||||
snap_rotation_and_scale_to_dungeon_grid()
|
||||
set_position_by_grid_pos()
|
||||
constrain_room_to_bounds_with_doors()
|
||||
|
||||
func constrain_room_to_bounds_with_doors():
|
||||
# For stair rooms, also ensure optional doors can be reached. So stairs don't get their path blocked against wall
|
||||
var aabbi_with_doors := get_grid_aabbi(true)
|
||||
var aabbi_with_doors_constrained := aabbi_with_doors.push_within(dungeon_generator.get_grid_aabbi(), false)
|
||||
set_position_by_grid_pos(get_grid_pos() + (aabbi_with_doors_constrained.position - aabbi_with_doors.position))
|
||||
|
||||
## Room must be scaled so voxel scale matches the DungeonGenerator3D's voxel scale.
|
||||
func snap_rotation_and_scale_to_dungeon_grid() -> void:
|
||||
virtual_transform = Transform3D(Basis().rotated(Vector3(0,1,0), self.room_rotations * deg_to_rad(90.0)).scaled(dungeon_generator.voxel_scale / voxel_scale), virtual_transform.origin)
|
||||
|
||||
## Returns room pos from corner (min) of AABB on dungeon grid
|
||||
func get_grid_pos() -> Vector3i:
|
||||
return get_grid_aabbi(false).position
|
||||
|
||||
## Set position of room from corner (min) of AABB on dungeon grid
|
||||
func set_position_by_grid_pos(new_grid_pos : Vector3i = get_grid_pos()) -> void:
|
||||
if not dungeon_generator: printerr("set_position_by_grid_pos: No dungeon_generator set on DungeonRoom3D")
|
||||
var cur_aabb := xform_aabb(get_local_aabb(), get_xform_to(SPACE.LOCAL_SPACE, SPACE.DUNGEON_GRID))
|
||||
cur_aabb.position = Vector3(new_grid_pos)
|
||||
cur_aabb = xform_aabb(cur_aabb, get_xform_to(SPACE.DUNGEON_GRID, SPACE.DUNGEON_SPACE))
|
||||
virtual_transform.origin = cur_aabb.get_center()
|
||||
|
||||
# Probably a good idea to have this in. Hopping between local/dungeon space, grid, and editor positions
|
||||
enum SPACE { LOCAL_GRID = 0, LOCAL_SPACE = 1, DUNGEON_SPACE = 2, DUNGEON_GRID = 3 }
|
||||
func get_xform_to(from : SPACE, to : SPACE) -> Transform3D:
|
||||
var t = Transform3D()
|
||||
var inv := to < from
|
||||
if inv:
|
||||
var tmp = to; to = from; from = tmp
|
||||
|
||||
if from <= SPACE.LOCAL_GRID and to >= SPACE.LOCAL_SPACE:
|
||||
t = Transform3D(Basis().scaled(voxel_scale), self.get_local_aabb().position) * t
|
||||
if from <= SPACE.LOCAL_SPACE and to >= SPACE.DUNGEON_SPACE:
|
||||
t = virtual_transform * t
|
||||
if from <= SPACE.DUNGEON_SPACE and to >= SPACE.DUNGEON_GRID and dungeon_generator:
|
||||
t = Transform3D(Basis().scaled(dungeon_generator.voxel_scale.inverse()), Vector3(dungeon_generator.dungeon_size)/2.0) * t
|
||||
|
||||
return t.affine_inverse() if inv else t
|
||||
|
||||
## Different behavior than Godot's transform * AABB. This properly scales too. Regular just does position & rotation.
|
||||
func xform_aabb(aabb : AABB, xform : Transform3D) -> AABB:
|
||||
var pos := xform * aabb.position
|
||||
var end := xform * aabb.end
|
||||
return AABB(pos, end - pos).abs()
|
||||
|
||||
func xform_aabbi(aabbi : AABBi, xform : Transform3D) -> AABBi:
|
||||
return AABBi.from_AABB_rounded(xform_aabb(aabbi.to_AABB(), xform))
|
||||
|
||||
func get_local_aabb() -> AABB:
|
||||
var size := Vector3(size_in_voxels) * voxel_scale
|
||||
return AABB(-size/2.0, size)
|
||||
|
||||
func get_grid_aabbi(include_doors : bool) -> AABBi:
|
||||
# For stair rooms, all doors are counted for AABB. Generally even if they are optional, want to guarantee able to walk out of them to connect floors
|
||||
var grid_aabbi = AABBi.from_AABB_rounded(xform_aabb(get_local_aabb(), get_xform_to(SPACE.LOCAL_SPACE, SPACE.DUNGEON_GRID)))
|
||||
if include_doors: # Include doors after to keep position the same
|
||||
for door in get_doors().filter(func(d : Door): return !d.optional or self.is_stair_room):
|
||||
grid_aabbi = grid_aabbi.expand_to_include(door.exit_pos_grid)
|
||||
return grid_aabbi
|
||||
|
||||
func local_grid_pos_to_dungeon_grid_pos(local_pos : Vector3i) -> Vector3i:
|
||||
# It will be rounded in wrong direction when rotated 180 going to dungeon space.
|
||||
# So use middle of local grid and .floor() instead of .round() at end.
|
||||
var transformed_middle := get_xform_to(SPACE.LOCAL_GRID, SPACE.DUNGEON_GRID) * (Vector3(local_pos) + Vector3(0.5,0.5,0.5))
|
||||
return Vector3i(transformed_middle.floor())
|
||||
|
||||
# Can't use global_transform when it's not actually in the scene tree.
|
||||
# Just using this for doors.
|
||||
func get_transform_rel_to(child_node : Node3D, parent_node : Node3D) -> Transform3D:
|
||||
var transform = Transform3D()
|
||||
while child_node != parent_node:
|
||||
transform = child_node.transform * transform
|
||||
if child_node.get_parent() is Node3D: child_node = child_node.get_parent()
|
||||
else: break
|
||||
return transform
|
||||
|
||||
################
|
||||
## VALIDATION ##
|
||||
################
|
||||
|
||||
# printerr() and push_warning() eat my outputs a lot. Regular prints are more reliable.
|
||||
func _printerr(str : String, str2 : String = "", str3 : String = "", str4 : String = ""):
|
||||
print_rich("[color=#FF3531]"+(str+str2+str3+str4)+"[/color]")
|
||||
func _printwarning(str : String, str2 : String = "", str3 : String = "", str4 : String = ""):
|
||||
print_rich("[color=#FFF831]"+(str+str2+str3+str4)+"[/color]")
|
||||
|
||||
# Returns true if no errors found before generating.
|
||||
# Calls callbacks with warning/error string if any.
|
||||
func validate_room(error_callback = null, warning_callback = null) -> bool:
|
||||
if not warning_callback is Callable: warning_callback = (func(str): _printwarning(str))
|
||||
if not error_callback is Callable: error_callback = (func(str): _printerr(str))
|
||||
var any_errors := {"err": false} # So lambda closure captures
|
||||
error_callback = func(str): any_errors["err"] = true; error_callback.call(str)
|
||||
|
||||
var doors = get_doors()
|
||||
if doors.size() == 0:
|
||||
warning_callback.call("Room "+self.name+" has no doors defined. Room will be unreachable. Add doors by creating Node3Ds with names prefixed with DOOR or DOOR? for optional doors.")
|
||||
if not doors.all(func(d : Door): return d.validate_door()):
|
||||
error_callback.call("Room "+self.name+" has one or more invalid doors.")
|
||||
if doors.any(func(d : Door): return d.find_duplicates().size() > 1):
|
||||
error_callback.call("Room "+self.name+" has one or more overlapping/duplicate doors.")
|
||||
|
||||
var unique_door_y = doors.reduce(func(acc : Dictionary, d : Door):
|
||||
acc[d.local_pos.y] = true
|
||||
return acc, {})
|
||||
if is_stair_room and unique_door_y.keys().size() < 2:
|
||||
error_callback.call("Room "+self.name+" is set as is_stair_room but does not have doors leading to 2 or more floors.")
|
||||
|
||||
# Post instantiate/place checks:
|
||||
if not dungeon_generator:
|
||||
return not any_errors["err"]
|
||||
|
||||
if not self.scale.is_equal_approx(Vector3(1,1,1)):
|
||||
warning_callback.call("Room "+self.name+"'s root node scale should be set to Vector3(1,1,1). Will be scaled during generation.")
|
||||
if voxel_scale != dungeon_generator.voxel_scale:
|
||||
warning_callback.call("Room "+self.name+"'s voxel scale does not match DungeonGenerator3D voxel size. Room will be scaled to match.")
|
||||
if size_in_voxels != size_in_voxels.clamp(Vector3i(0,0,0), dungeon_generator.dungeon_size):
|
||||
error_callback.call("Room "+self.name+" is too big for the DungeonGenerator3D!")
|
||||
|
||||
return not any_errors["err"]
|
||||
69
addons/SimpleDungeons/DungeonUtils.gd
Normal file
@@ -0,0 +1,69 @@
|
||||
class_name DungeonUtils
|
||||
extends Node
|
||||
|
||||
enum Direction { LEFT = 0, RIGHT = 1, FRONT = 2, BACK = 3 }
|
||||
const NEGATE_DIRECTION = {
|
||||
Direction.LEFT: Direction.RIGHT, Direction.RIGHT: Direction.LEFT,
|
||||
Direction.FRONT: Direction.BACK, Direction.BACK: Direction.FRONT
|
||||
}
|
||||
const DIRECTION_VECTORS = {
|
||||
Direction.LEFT: Vector3(-1,0,0), Direction.RIGHT: Vector3(1,0,0),
|
||||
Direction.FRONT: Vector3(0,0,1), Direction.BACK: Vector3(0,0,-1),
|
||||
}
|
||||
static func vec3_to_direction(vec : Vector3) -> Direction:
|
||||
var closest = Direction.LEFT
|
||||
var closest_dot = -INF
|
||||
for dir in DIRECTION_VECTORS.keys():
|
||||
var dir_vec = DIRECTION_VECTORS[dir]
|
||||
if dir_vec.dot(vec.normalized()) > closest_dot:
|
||||
closest_dot = dir_vec.dot(vec.normalized())
|
||||
closest = dir
|
||||
return closest
|
||||
|
||||
#static func rotate_vec3i(vec : Vector3i, angle : float, axis : Vector3 = Vector3.UP) -> Vector3i:
|
||||
#for i in wrapi(cc_90_turns, 0, 4): vec = Vector3i(-vec.z, vec.y, vec.x)
|
||||
# return vec
|
||||
|
||||
static func _make_set(arr: Array) -> Array:
|
||||
var unique_elements = []
|
||||
for element in arr:
|
||||
if element not in unique_elements:
|
||||
unique_elements.append(element)
|
||||
return unique_elements
|
||||
|
||||
static func _flatten(arr: Array, depth: int = 1) -> Array:
|
||||
var result = Array()
|
||||
for element in arr:
|
||||
if element is Array and depth > 0:
|
||||
result += _flatten(element, depth - 1)
|
||||
else:
|
||||
result.append(element)
|
||||
return result
|
||||
|
||||
|
||||
static func _vec3i_min(a : Vector3i, b : Vector3i) -> Vector3i:
|
||||
return Vector3i(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z))
|
||||
|
||||
static func _vec3i_max(a : Vector3i, b : Vector3i) -> Vector3i:
|
||||
return Vector3i(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z))
|
||||
|
||||
class DungeonFloorAStarGrid2D extends AStarGrid2D:
|
||||
var corridors = [] as Array[Vector2i]
|
||||
|
||||
func _init(dungeon_size : Vector3i, all_dungeon_rooms : Array, floor : int):
|
||||
self.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_NEVER
|
||||
self.region = Rect2i(0, 0, dungeon_size.x, dungeon_size.z)
|
||||
self.update()
|
||||
for x in dungeon_size.x:
|
||||
for z in dungeon_size.z:
|
||||
if all_dungeon_rooms.any(func(_r): return _r.get_aabb_in_grid().has_point(Vector3(x + 0.5,floor + 0.5,z + 0.5))):
|
||||
set_point_solid(Vector2i(x,z))
|
||||
#
|
||||
func _compute_cost(from : Vector2i, to : Vector2i):
|
||||
return 0 if corridors.has(to) else self.cell_size.x
|
||||
|
||||
func _estimate_cost(from : Vector2i, to : Vector2i):
|
||||
return 0 # Make it Dijkstra's algorithm
|
||||
|
||||
func add_corridor(xz : Vector2i):
|
||||
corridors.push_back(xz)
|
||||
17
addons/SimpleDungeons/debug_visuals/DebugAlert.gd
Normal file
@@ -0,0 +1,17 @@
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
@export var text = "" :
|
||||
set(v):
|
||||
text = v
|
||||
if is_inside_tree(): update_visual()
|
||||
|
||||
func update_visual():
|
||||
self.visible = len(text) > 0
|
||||
self.global_transform.basis = self.global_transform.basis.orthonormalized() * self.global_transform.basis.y.length()
|
||||
$Label3D.text = text
|
||||
$Sprite3D.texture = preload("res://addons/SimpleDungeons/res/error-sign.svg") if text.begins_with("Error") else preload("res://addons/SimpleDungeons/res/warning-sign.svg")
|
||||
|
||||
func _ready():
|
||||
update_visual()
|
||||
set_process_input(false)
|
||||
20
addons/SimpleDungeons/debug_visuals/DebugAlert.tscn
Normal file
@@ -0,0 +1,20 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://djjvvvx7svoux"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/debug_visuals/DebugAlert.gd" id="1_ngt8o"]
|
||||
[ext_resource type="Texture2D" uid="uid://byywkosue8x0q" path="res://addons/SimpleDungeons/res/warning-sign.svg" id="2_q7g1p"]
|
||||
|
||||
[node name="DebugAlert" type="Node3D"]
|
||||
visible = false
|
||||
script = ExtResource("1_ngt8o")
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Label3D" type="Label3D" parent="."]
|
||||
pixel_size = 0.02
|
||||
billboard = 1
|
||||
no_depth_test = true
|
||||
|
||||
[node name="Sprite3D" type="Sprite3D" parent="."]
|
||||
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 4.17956, 0)
|
||||
billboard = 1
|
||||
no_depth_test = true
|
||||
texture = ExtResource("2_q7g1p")
|
||||
54
addons/SimpleDungeons/debug_visuals/DoorDebugVisual.tscn
Normal file
@@ -0,0 +1,54 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cjxyp5lrix0pu"]
|
||||
|
||||
[ext_resource type="Material" uid="uid://pq2fqq4ophsy" path="res://addons/SimpleDungeons/debug_visuals/WireframeColorMat.tres" id="1_3h4gq"]
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_18siu"]
|
||||
script/source = "@tool
|
||||
extends Node3D
|
||||
|
||||
@export var text = \"DOOR\"
|
||||
@export var color = Color(0, 1, 0)
|
||||
|
||||
func _ready():
|
||||
set_process_input(false)
|
||||
|
||||
func _process(_delta):
|
||||
$CSGCylinder3D2.material.set_shader_parameter(\"color\", Vector3(color.r, color.g, color.b))
|
||||
$CSGBox3D2.material.set_shader_parameter(\"color\", Vector3(color.r, color.g, color.b))
|
||||
$MeshInstance3D.mesh.material.set_shader_parameter(\"color\", Vector3(color.r, color.g, color.b))
|
||||
$Label3D.modulate = color
|
||||
$Label3D.text = text
|
||||
"
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_35eqg"]
|
||||
resource_local_to_scene = true
|
||||
material = ExtResource("1_3h4gq")
|
||||
size = Vector2(10, 10)
|
||||
|
||||
[node name="DoorDebugVisual" type="Node3D"]
|
||||
script = SubResource("GDScript_18siu")
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Label3D" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.657576, 0)
|
||||
pixel_size = 0.01
|
||||
no_depth_test = true
|
||||
modulate = Color(0, 1, 0, 1)
|
||||
text = "DOOR"
|
||||
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="."]
|
||||
transform = Transform3D(0.48016, 1.27422e-07, 0.48016, 0.48016, -2.09885e-08, -0.48016, -7.52601e-08, 0.679049, -1.04942e-07, 0, 0, 0.997)
|
||||
radius = 0.423
|
||||
height = 0.716463
|
||||
sides = 4
|
||||
cone = true
|
||||
smooth_faces = false
|
||||
material = ExtResource("1_3h4gq")
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.464)
|
||||
size = Vector3(0.166, 0.166, 0.682)
|
||||
material = ExtResource("1_3h4gq")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("QuadMesh_35eqg")
|
||||
@@ -0,0 +1,50 @@
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
var dungeon_generator : DungeonGenerator3D = null
|
||||
var wireframe_cube : Node3D = null
|
||||
var debug_alert : Node3D = null
|
||||
|
||||
func update_visual():
|
||||
var show_editor = Engine.is_editor_hint() and dungeon_generator.show_debug_in_editor
|
||||
var show_game = not Engine.is_editor_hint() and dungeon_generator.show_debug_in_game
|
||||
if not show_editor and not show_game:
|
||||
if wireframe_cube and is_instance_valid(wireframe_cube):
|
||||
remove_child(wireframe_cube)
|
||||
wireframe_cube.queue_free()
|
||||
wireframe_cube = null
|
||||
if debug_alert and is_instance_valid(debug_alert):
|
||||
remove_child(debug_alert)
|
||||
debug_alert.queue_free()
|
||||
debug_alert = null
|
||||
return
|
||||
|
||||
if not wireframe_cube or not is_instance_valid(wireframe_cube):
|
||||
wireframe_cube = preload("res://addons/SimpleDungeons/debug_visuals/WireframeCube.tscn").instantiate()
|
||||
wireframe_cube.enable_depth_test = true
|
||||
add_child(wireframe_cube)
|
||||
wireframe_cube.scale = Vector3(dungeon_generator.dungeon_size) * dungeon_generator.voxel_scale
|
||||
wireframe_cube.grid_size = dungeon_generator.dungeon_size
|
||||
|
||||
if not debug_alert or not is_instance_valid(debug_alert):
|
||||
debug_alert = preload("res://addons/SimpleDungeons/debug_visuals/DebugAlert.tscn").instantiate()
|
||||
add_child(debug_alert)
|
||||
|
||||
debug_alert.scale = Vector3(dungeon_generator.voxel_scale.y/5.0, dungeon_generator.voxel_scale.y/5.0, dungeon_generator.voxel_scale.y/5.0)
|
||||
debug_alert.position = ((Vector3(dungeon_generator.dungeon_size) / 2) + Vector3(0,0.35,0)) * Vector3(0, dungeon_generator.voxel_scale.y, 0)
|
||||
|
||||
var err_warning = {"error": "", "warning": ""} # Closure won't capture normal vars
|
||||
dungeon_generator.validate_dungeon(
|
||||
(func(str):
|
||||
err_warning["error"] = "Error: " + str),
|
||||
(func(str): err_warning["warning"] = "Warning: " + str))
|
||||
debug_alert.text = err_warning["error"] if err_warning["error"] else err_warning["warning"]
|
||||
if not debug_alert.text: debug_alert.position = Vector3() # Fix AABB visual in editor
|
||||
|
||||
func _ready():
|
||||
dungeon_generator = get_parent()
|
||||
update_visual.call_deferred() # Call after parent _ready()
|
||||
set_process_input(false)
|
||||
|
||||
func _process(_delta):
|
||||
update_visual()
|
||||
@@ -0,0 +1,92 @@
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
var dungeon_room : DungeonRoom3D = null
|
||||
var wireframe_cube : Node3D = null
|
||||
var debug_alert : Node3D = null
|
||||
var aabb_with_doors : Node3D = null
|
||||
|
||||
# Dict mapping DungeonRoom3D Door:DoorDebugVisual
|
||||
var door_visuals = []
|
||||
func update_door_visuals(show_visuals : bool):
|
||||
var room_doors := []
|
||||
if show_visuals:
|
||||
room_doors = dungeon_room.get_doors()
|
||||
# Add/remove debug visuals to match
|
||||
while len(door_visuals) < len(room_doors):
|
||||
var door_visual = preload("res://addons/SimpleDungeons/debug_visuals/DoorDebugVisual.tscn").instantiate()
|
||||
add_child(door_visual)
|
||||
door_visuals.push_back(door_visual)
|
||||
while len(door_visuals) > len(room_doors):
|
||||
var door_visual = door_visuals.pop_back()
|
||||
remove_child(door_visual)
|
||||
door_visual.queue_free()
|
||||
for i in len(room_doors):
|
||||
door_visuals[i].position = (Vector3(room_doors[i].local_pos) + Vector3(.5,.5,.5)) * dungeon_room.voxel_scale
|
||||
door_visuals[i].position -= Vector3(dungeon_room.size_in_voxels) * dungeon_room.voxel_scale * 0.5
|
||||
door_visuals[i].position += DungeonUtils.DIRECTION_VECTORS[room_doors[i].dir] * 0.5 * dungeon_room.voxel_scale
|
||||
door_visuals[i].rotation.y = -DungeonUtils.DIRECTION_VECTORS[room_doors[i].dir].signed_angle_to(DungeonUtils.DIRECTION_VECTORS[DungeonUtils.Direction.FRONT], Vector3.UP)
|
||||
door_visuals[i].scale = dungeon_room.voxel_scale / 10.0
|
||||
var col = Color.YELLOW if room_doors[i].optional else Color.GREEN
|
||||
door_visuals[i].text = "OPTIONAL DOOR" if room_doors[i].optional else "DOOR"
|
||||
door_visuals[i].color = col if room_doors[i].validate_door() else Color.RED
|
||||
|
||||
func update_visual():
|
||||
var show_editor = Engine.is_editor_hint() and dungeon_room.show_debug_in_editor
|
||||
var show_game = not Engine.is_editor_hint() and dungeon_room.show_debug_in_game
|
||||
#if not get_parent() is DungeonGenerator3D:
|
||||
#print(dungeon_room.name)
|
||||
if dungeon_room.dungeon_generator and dungeon_room.dungeon_generator.hide_debug_visuals_for_all_generated_rooms and not (dungeon_room.get_parent() is DungeonGenerator3D):
|
||||
show_editor = false
|
||||
show_game = false
|
||||
if not show_editor and not show_game:
|
||||
update_door_visuals(false)
|
||||
for dbg_visual in [wireframe_cube, debug_alert, aabb_with_doors]:
|
||||
if dbg_visual and is_instance_valid(dbg_visual):
|
||||
remove_child(dbg_visual)
|
||||
dbg_visual.queue_free()
|
||||
wireframe_cube = null; debug_alert = null; aabb_with_doors = null;
|
||||
return
|
||||
|
||||
update_door_visuals(true)
|
||||
|
||||
if not wireframe_cube or not is_instance_valid(wireframe_cube):
|
||||
wireframe_cube = preload("res://addons/SimpleDungeons/debug_visuals/WireframeCube.tscn").instantiate()
|
||||
add_child(wireframe_cube)
|
||||
wireframe_cube.scale = Vector3(dungeon_room.size_in_voxels) * dungeon_room.voxel_scale
|
||||
wireframe_cube.grid_size = dungeon_room.size_in_voxels
|
||||
wireframe_cube.show_coordinates = false
|
||||
wireframe_cube.color = Color.WHITE if dungeon_room.was_preplaced else Color.BLACK
|
||||
|
||||
# Show grid AABB with doors
|
||||
if not aabb_with_doors:
|
||||
aabb_with_doors = CSGBox3D.new()
|
||||
aabb_with_doors.material = preload("res://addons/SimpleDungeons/debug_visuals/WireframeColorMat.tres")
|
||||
add_child(aabb_with_doors)
|
||||
var rel_room_aabb = dungeon_room.xform_aabb(dungeon_room.get_grid_aabbi(true).to_AABB(), dungeon_room.get_xform_to(DungeonRoom3D.SPACE.DUNGEON_GRID, DungeonRoom3D.SPACE.LOCAL_SPACE)).abs()
|
||||
aabb_with_doors.size = rel_room_aabb.size if dungeon_room.show_grid_aabb_with_doors else Vector3()
|
||||
aabb_with_doors.position = rel_room_aabb.get_center()
|
||||
aabb_with_doors.visible = dungeon_room.show_grid_aabb_with_doors
|
||||
|
||||
if not debug_alert or not is_instance_valid(debug_alert):
|
||||
debug_alert = preload("res://addons/SimpleDungeons/debug_visuals/DebugAlert.tscn").instantiate()
|
||||
add_child(debug_alert)
|
||||
|
||||
debug_alert.scale = Vector3(dungeon_room.voxel_scale.y/10.0, dungeon_room.voxel_scale.y/10.0, dungeon_room.voxel_scale.y/10.0)
|
||||
debug_alert.position = ((Vector3(dungeon_room.size_in_voxels) / 2) + Vector3(0,0.35,0)) * Vector3(0, dungeon_room.voxel_scale.y, 0)
|
||||
|
||||
var err_warning = {"error": "", "warning": ""} # Closure won't capture normal vars
|
||||
dungeon_room.validate_room(
|
||||
(func(str):
|
||||
err_warning["error"] = "Error: " + str),
|
||||
(func(str): err_warning["warning"] = "Warning: " + str))
|
||||
debug_alert.text = err_warning["error"] if err_warning["error"] else err_warning["warning"]
|
||||
if not debug_alert.text: debug_alert.position = Vector3() # Fix AABB visual in editor
|
||||
|
||||
func _ready():
|
||||
dungeon_room = get_parent()
|
||||
update_visual.call_deferred() # Call after parent _ready()
|
||||
set_process_input(false)
|
||||
|
||||
func _process(_delta):
|
||||
update_visual()
|
||||
44
addons/SimpleDungeons/debug_visuals/WireframeColorMat.tres
Normal file
@@ -0,0 +1,44 @@
|
||||
[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://pq2fqq4ophsy"]
|
||||
|
||||
[sub_resource type="Shader" id="Shader_xtf8b"]
|
||||
resource_local_to_scene = true
|
||||
code = "shader_type spatial;
|
||||
render_mode unshaded, cull_disabled, depth_draw_always, depth_test_disabled;
|
||||
|
||||
uniform vec3 color = vec3(0.0, 1.0, 0.0);
|
||||
uniform float line_width : hint_range(0.0, 10.0) = 0.85;
|
||||
|
||||
varying vec3 barycentric;
|
||||
|
||||
void vertex() {
|
||||
vec3 b_coords[3] = vec3[](vec3(1, 0, 0), vec3(0, 1, 0), vec3(0, 0, 1));
|
||||
int vertex_id = int(VERTEX_ID) % 3;
|
||||
barycentric = b_coords[vertex_id];
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
// Calculate the screen-space derivatives of the barycentric coordinates
|
||||
vec3 dFdx_barycentric = dFdx(barycentric);
|
||||
vec3 dFdy_barycentric = dFdy(barycentric);
|
||||
|
||||
// Calculate the minimum barycentric coordinate value
|
||||
float min_bary = min(min(barycentric.x, barycentric.y), barycentric.z);
|
||||
|
||||
// Calculate the screen-space line width
|
||||
float screen_line_width = line_width * length(vec2(dFdx_barycentric.x, dFdy_barycentric.x));
|
||||
|
||||
// Draw the line based on the calculated screen-space line width
|
||||
if (min_bary < screen_line_width) {
|
||||
ALBEDO = color;
|
||||
} else {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
"
|
||||
|
||||
[resource]
|
||||
resource_local_to_scene = true
|
||||
render_priority = 0
|
||||
shader = SubResource("Shader_xtf8b")
|
||||
shader_parameter/color = Vector3(0, 1, 0)
|
||||
shader_parameter/line_width = 0.85
|
||||
69
addons/SimpleDungeons/debug_visuals/WireframeCube.gd
Normal file
@@ -0,0 +1,69 @@
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
@export var grid_size := Vector3i(1,1,1)
|
||||
@export var show_coordinates := true
|
||||
@export var color := Color.BLACK
|
||||
@export var enable_depth_test := false
|
||||
|
||||
var last_grid_size = null
|
||||
var last_voxel_scale = null
|
||||
var last_show_coordinates = null
|
||||
var last_enable_depth = null
|
||||
|
||||
func update_grid():
|
||||
for child in %Levels.get_children():
|
||||
%Levels.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
%Front.mesh.material.shader = preload("res://addons/SimpleDungeons/debug_visuals/WireframeCubeDepthEnabled.gdshader") if enable_depth_test else preload("res://addons/SimpleDungeons/debug_visuals/WireframeCube.gdshader")
|
||||
%Right.mesh.material.shader = preload("res://addons/SimpleDungeons/debug_visuals/WireframeCubeDepthEnabled.gdshader") if enable_depth_test else preload("res://addons/SimpleDungeons/debug_visuals/WireframeCube.gdshader")
|
||||
%Top.mesh.material.shader = preload("res://addons/SimpleDungeons/debug_visuals/WireframeCubeDepthEnabled.gdshader") if enable_depth_test else preload("res://addons/SimpleDungeons/debug_visuals/WireframeCube.gdshader")
|
||||
|
||||
%Front.mesh.material.set_shader_parameter("grid_size", Vector2(grid_size.x, grid_size.y))
|
||||
%Right.mesh.material.set_shader_parameter("grid_size", Vector2(grid_size.z, grid_size.y))
|
||||
%Top.mesh.material.set_shader_parameter("grid_size", Vector2(grid_size.x, grid_size.z))
|
||||
|
||||
%Front.mesh.material.set_shader_parameter("color", Vector3(color.r, color.g, color.b))
|
||||
%Right.mesh.material.set_shader_parameter("color", Vector3(color.r, color.g, color.b))
|
||||
%Top.mesh.material.set_shader_parameter("color", Vector3(color.r, color.g, color.b))
|
||||
|
||||
if grid_size.x < 1 or grid_size.y < 1 or grid_size.z < 1:
|
||||
return
|
||||
if grid_size.x <= 1 and grid_size.y <= 1 and grid_size.z <= 1:
|
||||
return
|
||||
|
||||
for y in range(1, grid_size.y):
|
||||
var level = %Top.duplicate()
|
||||
%Levels.add_child(level)
|
||||
level.position.y -= float(y) / grid_size.y - 0.00001
|
||||
var b = grid_size - Vector3i(1,1,1)
|
||||
var positions = [Vector3i(0,0,0), Vector3i(grid_size.x-1,0,0), Vector3i(0,grid_size.y-1,0), Vector3i(0,0,grid_size.z-1),
|
||||
b, b - Vector3i(grid_size.x-1,0,0), b - Vector3i(0,grid_size.y-1,0), b - Vector3i(0,0,grid_size.z-1)]
|
||||
|
||||
if not show_coordinates:
|
||||
return
|
||||
|
||||
var make_scale_uniform_node = Node3D.new()
|
||||
%Levels.add_child(make_scale_uniform_node)
|
||||
make_scale_uniform_node.scale = self.transform.basis.inverse().get_scale()
|
||||
for grid_pos in positions:
|
||||
var label = Label3D.new()
|
||||
label.text = str(grid_pos)
|
||||
make_scale_uniform_node.add_child(label)
|
||||
label.position = ((Vector3(grid_pos) + Vector3(0.5, 0.5, 0.5)) / Vector3(grid_size)) * self.scale - self.scale/2
|
||||
label.scale *= (Vector3(1,1,1) / Vector3(grid_size)) * self.scale
|
||||
label.scale.x = min(label.scale.x, label.scale.y, label.scale.z)
|
||||
label.scale.y = min(label.scale.x, label.scale.y, label.scale.z)
|
||||
label.scale.z = min(label.scale.x, label.scale.y, label.scale.z)
|
||||
|
||||
func _ready():
|
||||
update_grid()
|
||||
set_process_input(false)
|
||||
|
||||
func _process(delta):
|
||||
if last_grid_size != grid_size or last_show_coordinates != show_coordinates or last_enable_depth != enable_depth_test:
|
||||
update_grid()
|
||||
last_grid_size = grid_size
|
||||
last_show_coordinates = show_coordinates
|
||||
last_enable_depth = enable_depth_test
|
||||
14
addons/SimpleDungeons/debug_visuals/WireframeCube.gdshader
Normal file
@@ -0,0 +1,14 @@
|
||||
shader_type spatial;
|
||||
render_mode unshaded, cull_disabled, depth_test_disabled;
|
||||
uniform vec2 grid_size;
|
||||
uniform vec3 color = vec3(0.);
|
||||
|
||||
void fragment() {
|
||||
vec2 uv = fract(UV * grid_size);
|
||||
vec2 eps = 0.0015 * grid_size;
|
||||
vec2 inv_eps = 1.0 - eps;
|
||||
if(uv.x > eps.x && uv.x < inv_eps.x && uv.y > eps.y && uv.y < inv_eps.y) {
|
||||
discard;
|
||||
}
|
||||
ALBEDO = color;
|
||||
}
|
||||
79
addons/SimpleDungeons/debug_visuals/WireframeCube.tscn
Normal file
@@ -0,0 +1,79 @@
|
||||
[gd_scene load_steps=11 format=3 uid="uid://cfch5o0wphv4r"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/debug_visuals/WireframeCube.gd" id="1_unksl"]
|
||||
[ext_resource type="Shader" path="res://addons/SimpleDungeons/debug_visuals/WireframeCube.gdshader" id="2_hwi17"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_jymxj"]
|
||||
resource_local_to_scene = true
|
||||
render_priority = 0
|
||||
shader = ExtResource("2_hwi17")
|
||||
shader_parameter/grid_size = Vector2(1, 1)
|
||||
shader_parameter/color = Vector3(0, 0, 0)
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_pwl2e"]
|
||||
resource_local_to_scene = true
|
||||
material = SubResource("ShaderMaterial_jymxj")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_lnw1d"]
|
||||
resource_local_to_scene = true
|
||||
render_priority = 0
|
||||
shader = ExtResource("2_hwi17")
|
||||
shader_parameter/grid_size = Vector2(1, 1)
|
||||
shader_parameter/color = Vector3(0, 0, 0)
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_llwc2"]
|
||||
resource_local_to_scene = true
|
||||
material = SubResource("ShaderMaterial_lnw1d")
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_hdmjf"]
|
||||
resource_local_to_scene = true
|
||||
material = SubResource("ShaderMaterial_lnw1d")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_3xn3f"]
|
||||
resource_local_to_scene = true
|
||||
render_priority = 0
|
||||
shader = ExtResource("2_hwi17")
|
||||
shader_parameter/grid_size = Vector2(1, 1)
|
||||
shader_parameter/color = Vector3(0, 0, 0)
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_d7b6y"]
|
||||
resource_local_to_scene = true
|
||||
material = SubResource("ShaderMaterial_3xn3f")
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_kjtax"]
|
||||
resource_local_to_scene = true
|
||||
material = SubResource("ShaderMaterial_3xn3f")
|
||||
|
||||
[node name="WireframeCube" type="Node3D"]
|
||||
script = ExtResource("1_unksl")
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Back" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.5)
|
||||
mesh = SubResource("QuadMesh_pwl2e")
|
||||
|
||||
[node name="Front" type="MeshInstance3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.5)
|
||||
mesh = SubResource("QuadMesh_pwl2e")
|
||||
|
||||
[node name="Right" type="MeshInstance3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0.5, 0, 0)
|
||||
mesh = SubResource("QuadMesh_llwc2")
|
||||
|
||||
[node name="Left" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -0.5, 0, 0)
|
||||
mesh = SubResource("QuadMesh_hdmjf")
|
||||
|
||||
[node name="Top" type="MeshInstance3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0.5, 0)
|
||||
mesh = SubResource("QuadMesh_d7b6y")
|
||||
|
||||
[node name="Bottom" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, -0.5, 0)
|
||||
mesh = SubResource("QuadMesh_kjtax")
|
||||
|
||||
[node name="Levels" type="Node3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
@@ -0,0 +1,14 @@
|
||||
shader_type spatial;
|
||||
render_mode unshaded, cull_disabled;
|
||||
uniform vec2 grid_size;
|
||||
uniform vec3 color = vec3(0.);
|
||||
|
||||
void fragment() {
|
||||
vec2 uv = fract(UV * grid_size);
|
||||
vec2 eps = 0.0015 * grid_size;
|
||||
vec2 inv_eps = 1.0 - eps;
|
||||
if(uv.x > eps.x && uv.x < inv_eps.x && uv.y > eps.y && uv.y < inv_eps.y) {
|
||||
discard;
|
||||
}
|
||||
ALBEDO = color;
|
||||
}
|
||||
7
addons/SimpleDungeons/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="SimpleDungeons"
|
||||
description="3D Procedurally Generated Dungeons Addon"
|
||||
author="MajikayoGames"
|
||||
version=""
|
||||
script="plugin.gd"
|
||||
11
addons/SimpleDungeons/plugin.gd
Normal file
@@ -0,0 +1,11 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
func _enter_tree():
|
||||
add_custom_type("DungeonGenerator3D", "Node3D", preload("DungeonGenerator3D.gd"), preload("res://addons/SimpleDungeons/res/dungeongenerator3dicon.svg"))
|
||||
add_custom_type("DungeonRoom3D", "Node3D", preload("DungeonRoom3D.gd"), preload("res://addons/SimpleDungeons/res/dungeonroom3dicon.svg"))
|
||||
|
||||
func _exit_tree():
|
||||
# Clean-up of the plugin goes here.
|
||||
remove_custom_type("DungeonGenerator3D")
|
||||
remove_custom_type("DungeonRoom3D")
|
||||
102
addons/SimpleDungeons/res/dungeongenerator3dicon.svg
Normal file
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46666 135.46667"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="dungeongenerator3d.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:zoom="1.2065631"
|
||||
inkscape:cx="244.08173"
|
||||
inkscape:cy="293.80975"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g9852"
|
||||
showguides="false" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g9852"
|
||||
transform="translate(0.65974083,1.9323538)">
|
||||
<g
|
||||
id="g12191"
|
||||
style="stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none;stroke:#000000;stroke-opacity:1">
|
||||
<g
|
||||
id="g12175"
|
||||
style="stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none;stroke:#000000;stroke-opacity:1">
|
||||
<g
|
||||
id="g13029"
|
||||
style="stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none;stroke:#000000;stroke-opacity:1">
|
||||
<path
|
||||
d="M 35.675158,13.999619 8.915225,32.403803 V 117.60234 H 98.472027 L 125.23196,99.198168 V 13.999619 Z"
|
||||
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:12;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path10584"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
d="M 8.915225,32.403803 H 98.472029 L 125.23196,13.999619 H 35.675158 Z"
|
||||
style="fill:#808080;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.96875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5522" />
|
||||
<path
|
||||
d="M 98.472029,32.403803 V 117.60234 L 125.23196,99.198168 V 13.999619 Z"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.96875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5524" />
|
||||
<path
|
||||
d="m 8.915225,32.403803 h 89.5568 v 85.198537 h -89.5568 z"
|
||||
style="fill:#b3b3b3;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.96875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5526" />
|
||||
<path
|
||||
d="M 52.658313,32.403803 79.418244,13.999619 Z"
|
||||
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.96875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path6110"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
d="M 52.658313,32.403803 V 117.60234 Z"
|
||||
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.96875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path6114"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
d="M 9.573803,76.61366 H 99.130609 L 125.20453,58.617937"
|
||||
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.96875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path8145"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
d="M 23.972342,22.473583 H 113.52915"
|
||||
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.96875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path8145-5"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
d="M 113.52915,22.473583 V 106.87305"
|
||||
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.96875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path8709"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
37
addons/SimpleDungeons/res/dungeongenerator3dicon.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://jesvvp3ney5x"
|
||||
path="res://.godot/imported/dungeongenerator3dicon.svg-8ea7452a326a8749c574f5171cab795a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/res/dungeongenerator3dicon.svg"
|
||||
dest_files=["res://.godot/imported/dungeongenerator3dicon.svg-8ea7452a326a8749c574f5171cab795a.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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
72
addons/SimpleDungeons/res/dungeonroom3dicon.svg
Normal file
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46666 135.46667"
|
||||
version="1.1"
|
||||
id="svg13273"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="dungeonroom3dicon.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview13275"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:zoom="1.0482005"
|
||||
inkscape:cx="293.35991"
|
||||
inkscape:cy="254.24525"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g13444" />
|
||||
<defs
|
||||
id="defs13270" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g13444"
|
||||
style="fill:none;stroke:#000000;stroke-width:45.3543;stroke-linecap:round;stroke-linejoin:round"
|
||||
transform="translate(-0.32914132,-0.10880017)">
|
||||
<path
|
||||
d="M 87.137443,16.227017 127.90612,29.987555 V 92.820072 L 87.137443,79.059533 Z"
|
||||
style="fill:#e9e9ff;fill-rule:evenodd;stroke:none;stroke-width:42.2873;stroke-linejoin:round"
|
||||
id="path14240" />
|
||||
<path
|
||||
d="M 8.2188293,42.864195 V 105.69671 L 87.137443,79.059533 V 16.227017 Z"
|
||||
style="fill:#353564;fill-rule:evenodd;stroke:none;stroke-linejoin:round"
|
||||
id="path14242" />
|
||||
<path
|
||||
d="M 8.2188293,105.69671 48.987508,119.45725 127.90612,92.820072 87.137443,79.059533 Z"
|
||||
style="fill:#afafde;fill-rule:evenodd;stroke:none;stroke-linejoin:round"
|
||||
id="path14244" />
|
||||
<path
|
||||
d="M 8.2188293,42.864195 48.987508,56.624733 127.90612,29.987555 87.137443,16.227017 Z"
|
||||
style="fill:#4d4d9f;fill-rule:evenodd;stroke:none;stroke-linejoin:round"
|
||||
id="path14246" />
|
||||
<path
|
||||
d="M 48.987508,56.624733 V 119.45725 L 127.90612,92.820072 V 29.987555 Z"
|
||||
style="fill:#d7d7ff;fill-rule:evenodd;stroke:none;stroke-linejoin:round"
|
||||
id="path14248" />
|
||||
<path
|
||||
d="M 8.2188293,42.864195 48.987508,56.624733 V 119.45725 L 8.2188293,105.69671 Z"
|
||||
style="fill:#8686bf;fill-rule:evenodd;stroke:none;stroke-linejoin:round"
|
||||
id="path14250" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
37
addons/SimpleDungeons/res/dungeonroom3dicon.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bbkx850bx8fsi"
|
||||
path="res://.godot/imported/dungeonroom3dicon.svg-7eed1d33227e64437c65643acbd1d7c7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/res/dungeonroom3dicon.svg"
|
||||
dest_files=["res://.godot/imported/dungeonroom3dicon.svg-7eed1d33227e64437c65643acbd1d7c7.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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
80
addons/SimpleDungeons/res/error-sign.svg
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46666 135.46666"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="error-sign.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:zoom="0.37059484"
|
||||
inkscape:cx="338.64476"
|
||||
inkscape:cy="360.23168"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient18996">
|
||||
<stop
|
||||
style="stop-color:#ffdcc5;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop18992" />
|
||||
<stop
|
||||
style="stop-color:#ff6f48;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop18994" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient18996"
|
||||
id="linearGradient18998"
|
||||
x1="105.91866"
|
||||
y1="58.452225"
|
||||
x2="25.6213"
|
||||
y2="91.293571"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
id="rect846"
|
||||
style="fill:url(#linearGradient18998);stroke:#ff2d2d;stroke-width:14.9057;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;fill-opacity:1;stroke-opacity:1"
|
||||
d="M 67.733336,15.69611 C 75.03685,26.110351 124.90333,119.77028 124.90333,119.77028 H 10.56334 c 0,0 42.752814,-83.513868 57.169996,-104.07417 z"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<g
|
||||
aria-label="!"
|
||||
id="text4934"
|
||||
style="font-size:66.2082px;line-height:1.25;stroke-width:0.344834">
|
||||
<path
|
||||
d="m 61.914243,54.291506 h 11.63816 v 18.524071 l -1.648739,13.513196 h -8.340681 l -1.64874,-13.513196 z m 0,36.757188 h 11.63816 v 11.508846 h -11.63816 z"
|
||||
style="font-weight:bold;-inkscape-font-specification:'sans-serif Bold'"
|
||||
id="path915" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
38
addons/SimpleDungeons/res/error-sign.svg.import
Normal file
@@ -0,0 +1,38 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dj3t48rpleaqy"
|
||||
path.s3tc="res://.godot/imported/error-sign.svg-7d93dee82584cb708ab0dfad824e912d.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/res/error-sign.svg"
|
||||
dest_files=["res://.godot/imported/error-sign.svg-7d93dee82584cb708ab0dfad824e912d.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
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=0
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
80
addons/SimpleDungeons/res/warning-sign.svg
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46666 135.46666"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="warning-sign.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:zoom="0.37059484"
|
||||
inkscape:cx="311.66111"
|
||||
inkscape:cy="640.86159"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient18996">
|
||||
<stop
|
||||
style="stop-color:#ffcc00;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop18992" />
|
||||
<stop
|
||||
style="stop-color:#ff9000;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop18994" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient18996"
|
||||
id="linearGradient18998"
|
||||
x1="105.91866"
|
||||
y1="58.452225"
|
||||
x2="25.6213"
|
||||
y2="91.293571"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
id="rect846"
|
||||
style="fill:url(#linearGradient18998);stroke:#ffec2d;stroke-width:14.9057;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;fill-opacity:1;stroke-opacity:1"
|
||||
d="M 67.733336,15.69611 C 75.03685,26.110351 124.90333,119.77028 124.90333,119.77028 H 10.56334 c 0,0 42.752814,-83.513868 57.169996,-104.07417 z"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<g
|
||||
aria-label="!"
|
||||
id="text4934"
|
||||
style="font-size:66.2082px;line-height:1.25;stroke-width:0.344834">
|
||||
<path
|
||||
d="m 61.914243,54.291506 h 11.63816 v 18.524071 l -1.648739,13.513196 h -8.340681 l -1.64874,-13.513196 z m 0,36.757188 h 11.63816 v 11.508846 h -11.63816 z"
|
||||
style="font-weight:bold;-inkscape-font-specification:'sans-serif Bold'"
|
||||
id="path1152" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
38
addons/SimpleDungeons/res/warning-sign.svg.import
Normal file
@@ -0,0 +1,38 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://byywkosue8x0q"
|
||||
path.s3tc="res://.godot/imported/warning-sign.svg-8e061f920e0c1c369bbe5e6c9df382f2.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/res/warning-sign.svg"
|
||||
dest_files=["res://.godot/imported/warning-sign.svg-8e061f920e0c1c369bbe5e6c9df382f2.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
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=0
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
BIN
addons/SimpleDungeons/sample_assets/blue-checkerboard-cc0.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
@@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://i7rtr0ewxm5j"
|
||||
path.s3tc="res://.godot/imported/blue-checkerboard-cc0.png-1c2a1348fd1d3b6ea7498ed312065a17.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/sample_assets/blue-checkerboard-cc0.png"
|
||||
dest_files=["res://.godot/imported/blue-checkerboard-cc0.png-1c2a1348fd1d3b6ea7498ed312065a17.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
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=0
|
||||
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://d2vy5uyl5mwba"
|
||||
path="res://.godot/imported/free-modular-lowpoly-dungeon-cc0-by-rgsdev.glb-3f9c0eedad3e0ef421e24580ad8e0e11.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/sample_assets/free-modular-lowpoly-dungeon-cc0-by-rgsdev.glb"
|
||||
dest_files=["res://.godot/imported/free-modular-lowpoly-dungeon-cc0-by-rgsdev.glb-3f9c0eedad3e0ef421e24580ad8e0e11.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=1
|
||||
gltf/embedded_image_handling=1
|
||||
|
After Width: | Height: | Size: 637 B |
@@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://djhy80eyqjqpv"
|
||||
path.s3tc="res://.godot/imported/kenney-dark-grey-checkboard-cc0.png-a474cdcb7f780d0c1f0fbe07f09dca20.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/sample_assets/kenney-dark-grey-checkboard-cc0.png"
|
||||
dest_files=["res://.godot/imported/kenney-dark-grey-checkboard-cc0.png-a474cdcb7f780d0c1f0fbe07f09dca20.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
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=0
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://wvh1xmoax4ok"
|
||||
path.s3tc="res://.godot/imported/kenney-dark-grey-grid-cc0.png-641e868f49742dac557c00f298646a06.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/sample_assets/kenney-dark-grey-grid-cc0.png"
|
||||
dest_files=["res://.godot/imported/kenney-dark-grey-grid-cc0.png-641e868f49742dac557c00f298646a06.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
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=0
|
||||
|
After Width: | Height: | Size: 637 B |
@@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bwe02yta75ooa"
|
||||
path.s3tc="res://.godot/imported/kenney-green-checkerboar-cc0.png-5b7bcfea56d2c3f2ba54eb8bc0be04ac.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/sample_assets/kenney-green-checkerboar-cc0.png"
|
||||
dest_files=["res://.godot/imported/kenney-green-checkerboar-cc0.png-5b7bcfea56d2c3f2ba54eb8bc0be04ac.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
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=0
|
||||
|
After Width: | Height: | Size: 637 B |
@@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dqvyc0ok7chld"
|
||||
path.s3tc="res://.godot/imported/kenney-grey-checkerboard-cc0.png-e0b9147c60d57a064eba7df286f44f21.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/sample_assets/kenney-grey-checkerboard-cc0.png"
|
||||
dest_files=["res://.godot/imported/kenney-grey-checkerboard-cc0.png-e0b9147c60d57a064eba7df286f44f21.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
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=0
|
||||
|
After Width: | Height: | Size: 637 B |
@@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bxbtgil61x5sx"
|
||||
path.s3tc="res://.godot/imported/kenney-orange-checkerboard-cc0.png-b451adc6009037708d3774ae5cabc4ed.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/sample_assets/kenney-orange-checkerboard-cc0.png"
|
||||
dest_files=["res://.godot/imported/kenney-orange-checkerboard-cc0.png-b451adc6009037708d3774ae5cabc4ed.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
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=0
|
||||
|
After Width: | Height: | Size: 637 B |
@@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dydd6x64uxryr"
|
||||
path.s3tc="res://.godot/imported/kenney-red-checkerboard-cc0.png-de703f131e4265ef1314045e85661e68.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SimpleDungeons/sample_assets/kenney-red-checkerboard-cc0.png"
|
||||
dest_files=["res://.godot/imported/kenney-red-checkerboard-cc0.png-de703f131e4265ef1314045e85661e68.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
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=0
|
||||
@@ -0,0 +1,61 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cq6rrn2gpph6y"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_i6opq"]
|
||||
[ext_resource type="Texture2D" uid="uid://i7rtr0ewxm5j" path="res://addons/SimpleDungeons/sample_assets/blue-checkerboard-cc0.png" id="2_6nsgb"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_b5q7u"]
|
||||
albedo_texture = ExtResource("2_6nsgb")
|
||||
uv1_triplanar = true
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_3qcmr"]
|
||||
script/source = "@tool
|
||||
extends Node
|
||||
|
||||
func remove_unused_doors():
|
||||
for door in $\"..\".get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
|
||||
func _on_dungeon_done_generating():
|
||||
remove_unused_doors()
|
||||
"
|
||||
|
||||
[node name="BlueRoom" type="Node3D"]
|
||||
script = ExtResource("1_i6opq")
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, -5.68248e-07, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_b5q7u")
|
||||
use_collision = true
|
||||
size = Vector3(10, 10, 10)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(9.5, 9.5, 9.5)
|
||||
|
||||
[node name="DOOR?_F_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, -5.25)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_R_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_B_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="RemoveUnusedDoors" type="Node" parent="."]
|
||||
script = SubResource("GDScript_3qcmr")
|
||||
|
||||
[node name="PlayerSpawnPoint" type="Node3D" parent="." groups=["player_spawn_point"]]
|
||||
|
||||
[connection signal="dungeon_done_generating" from="." to="RemoveUnusedDoors" method="_on_dungeon_done_generating"]
|
||||
@@ -0,0 +1,59 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://b2t06bycrtldn"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_qqrlr"]
|
||||
[ext_resource type="Texture2D" uid="uid://dqvyc0ok7chld" path="res://addons/SimpleDungeons/sample_assets/kenney-grey-checkerboard-cc0.png" id="2_jdiqu"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_oj2cm"]
|
||||
albedo_texture = ExtResource("2_jdiqu")
|
||||
uv1_triplanar = true
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_ldqn2"]
|
||||
script/source = "@tool
|
||||
extends Node
|
||||
|
||||
func remove_unused_doors():
|
||||
for door in $\"..\".get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
|
||||
func _on_dungeon_done_generating():
|
||||
remove_unused_doors()
|
||||
"
|
||||
|
||||
[node name="Corridor" type="Node3D"]
|
||||
script = ExtResource("1_qqrlr")
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, -5.68248e-07, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_oj2cm")
|
||||
use_collision = true
|
||||
size = Vector3(10, 10, 10)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(9.5, 9.5, 9.5)
|
||||
|
||||
[node name="DOOR?_F_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, -5.25)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_R_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_B_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="RemoveUnusedDoors" type="Node" parent="."]
|
||||
script = SubResource("GDScript_ldqn2")
|
||||
|
||||
[connection signal="dungeon_done_generating" from="." to="RemoveUnusedDoors" method="_on_dungeon_done_generating"]
|
||||
@@ -0,0 +1,39 @@
|
||||
@tool
|
||||
extends DungeonGenerator3D
|
||||
|
||||
func _ready():
|
||||
self.custom_get_rooms_function = custom_get_rand_rooms
|
||||
super._ready()
|
||||
|
||||
func _process(delta):
|
||||
super._process(delta)
|
||||
|
||||
func custom_get_rand_rooms(room_instances : Array[DungeonRoom3D], rng_seeded : RandomNumberGenerator) -> Array[DungeonRoom3D]:
|
||||
var num_blue_rooms : int = 30
|
||||
var num_red_rooms : int = 30
|
||||
var blue_room = room_instances.filter(func(r): return r.name == "BlueRoom")[0]
|
||||
var red_room = room_instances.filter(func(r): return r.name == "RedRoom")[0]
|
||||
var rooms : Array[DungeonRoom3D] = []
|
||||
while num_red_rooms > 0:
|
||||
var inst = red_room.create_clone_and_make_virtual_unless_visualizing()
|
||||
rooms.push_back(inst)
|
||||
# Set room_rotations before set_position_by_grid_pos as it is set by AABB positon. May change when rotated.
|
||||
inst.room_rotations = rng_seeded.randi()
|
||||
inst.set_position_by_grid_pos(
|
||||
Vector3i(
|
||||
(rng_seeded.randi() % dungeon_size.x) / 2,
|
||||
rng_seeded.randi() % dungeon_size.y,
|
||||
rng_seeded.randi() % dungeon_size.z))
|
||||
num_red_rooms -= 1
|
||||
while num_blue_rooms > 0:
|
||||
var inst = blue_room.create_clone_and_make_virtual_unless_visualizing()
|
||||
rooms.push_back(inst)
|
||||
# Set room_rotations before set_position_by_grid_pos as it is set by AABB positon. May change when rotated.
|
||||
inst.room_rotations = rng_seeded.randi()
|
||||
inst.set_position_by_grid_pos(
|
||||
Vector3i(
|
||||
(rng_seeded.randi() % dungeon_size.x) / 2 + dungeon_size.x / 2,
|
||||
rng_seeded.randi() % dungeon_size.y,
|
||||
rng_seeded.randi() % dungeon_size.z))
|
||||
num_blue_rooms -= 1
|
||||
return rooms
|
||||
@@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://d1k8jtivamexj"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/sample_dungeons/custom_random_room_function_example/dungeon_generator_3d_custom.gd" id="1_smyj4"]
|
||||
[ext_resource type="PackedScene" uid="uid://cq6rrn2gpph6y" path="res://addons/SimpleDungeons/sample_dungeons/custom_random_room_function_example/blue_room.tscn" id="2_rfqbn"]
|
||||
[ext_resource type="PackedScene" uid="uid://dh2i3dk3g0icv" path="res://addons/SimpleDungeons/sample_dungeons/custom_random_room_function_example/red_room.tscn" id="3_hfdry"]
|
||||
[ext_resource type="PackedScene" uid="uid://br3pjd5mcjj11" path="res://addons/SimpleDungeons/sample_dungeons/custom_random_room_function_example/stairs.tscn" id="4_2jnyt"]
|
||||
[ext_resource type="PackedScene" uid="uid://b2t06bycrtldn" path="res://addons/SimpleDungeons/sample_dungeons/custom_random_room_function_example/corridor.tscn" id="5_5efio"]
|
||||
|
||||
[node name="DungeonGenerator3dInheritedClass" type="Node3D"]
|
||||
script = ExtResource("1_smyj4")
|
||||
room_scenes = Array[PackedScene]([ExtResource("2_rfqbn"), ExtResource("3_hfdry"), ExtResource("4_2jnyt")])
|
||||
corridor_room_scene = ExtResource("5_5efio")
|
||||
@@ -0,0 +1,59 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://dh2i3dk3g0icv"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_ruwq1"]
|
||||
[ext_resource type="Texture2D" uid="uid://dydd6x64uxryr" path="res://addons/SimpleDungeons/sample_assets/kenney-red-checkerboard-cc0.png" id="2_270m7"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_yl5wx"]
|
||||
albedo_texture = ExtResource("2_270m7")
|
||||
uv1_triplanar = true
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_mnevw"]
|
||||
script/source = "@tool
|
||||
extends Node
|
||||
|
||||
func remove_unused_doors():
|
||||
for door in $\"..\".get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
|
||||
func _on_dungeon_done_generating():
|
||||
remove_unused_doors()
|
||||
"
|
||||
|
||||
[node name="RedRoom" type="Node3D"]
|
||||
script = ExtResource("1_ruwq1")
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, -5.68248e-07, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_yl5wx")
|
||||
use_collision = true
|
||||
size = Vector3(10, 10, 10)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(9.5, 9.5, 9.5)
|
||||
|
||||
[node name="DOOR?_F_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, -5.25)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_R_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_B_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="RemoveUnusedDoors" type="Node" parent="."]
|
||||
script = SubResource("GDScript_mnevw")
|
||||
|
||||
[connection signal="dungeon_done_generating" from="." to="RemoveUnusedDoors" method="_on_dungeon_done_generating"]
|
||||
@@ -0,0 +1,42 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://br3pjd5mcjj11"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_pixse"]
|
||||
[ext_resource type="Texture2D" uid="uid://dqvyc0ok7chld" path="res://addons/SimpleDungeons/sample_assets/kenney-grey-checkerboard-cc0.png" id="2_bdw51"]
|
||||
[ext_resource type="PackedScene" uid="uid://bak8ltrhbmlv5" path="res://addons/SimpleDungeons/utils/CSGStairMaker3D.tscn" id="3_dobr2"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6cnww"]
|
||||
albedo_texture = ExtResource("2_bdw51")
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[node name="Stairs" type="Node3D"]
|
||||
script = ExtResource("1_pixse")
|
||||
size_in_voxels = Vector3i(2, 2, 1)
|
||||
max_count = 20
|
||||
is_stair_room = true
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_6cnww")
|
||||
use_collision = true
|
||||
size = Vector3(20, 20, 10)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(19.5, 19.5, 9.5)
|
||||
|
||||
[node name="DOOR?_R_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 9.8, -7.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -9.9, 2.25, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="CSGStairMaker3D" parent="." instance=ExtResource("3_dobr2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.75, -4.75, 0)
|
||||
use_collision = true
|
||||
size = Vector3(14, 10, 9.5)
|
||||
num_stairs = 32
|
||||
@@ -0,0 +1,25 @@
|
||||
@tool
|
||||
extends DungeonRoom3D
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
super._ready()
|
||||
dungeon_done_generating.connect(remove_unused_doors_and_walls)
|
||||
|
||||
func remove_unused_doors_and_walls():
|
||||
if RandomNumberGenerator.new().randf_range(0,10) > 2.5: $Models/F_WALL/torch_001.queue_free()
|
||||
if RandomNumberGenerator.new().randf_range(0,10) > 2.5: $Models/B_WALL/torch_001.queue_free()
|
||||
if RandomNumberGenerator.new().randf_range(0,10) > 2.5: $Models/R_WALL/torch_001.queue_free()
|
||||
if RandomNumberGenerator.new().randf_range(0,10) > 2.5: $Models/L_WALL/torch_001.queue_free()
|
||||
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_F_CUT").get_room_leads_to() != null: $Models/F_WALL.queue_free()
|
||||
else: $Models/F_WALL.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_R_CUT").get_room_leads_to() != null: $Models/R_WALL.queue_free()
|
||||
else: $Models/R_WALL.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_B_CUT").get_room_leads_to() != null: $Models/B_WALL.queue_free()
|
||||
else: $Models/B_WALL.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_L_CUT").get_room_leads_to() != null: $Models/L_WALL.queue_free()
|
||||
else: $Models/L_WALL.visible = true
|
||||
for door in get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
@@ -0,0 +1,23 @@
|
||||
@tool
|
||||
extends DungeonRoom3D
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
super._ready()
|
||||
dungeon_done_generating.connect(remove_unused_doors_and_walls)
|
||||
|
||||
func remove_unused_doors_and_walls():
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_B_CUT").get_room_leads_to() == null: $Models/B_DOOR.queue_free(); $Models/B_WALL.visible = true
|
||||
else: $Models/B_WALL.queue_free(); $Models/B_DOOR.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_RB_CUT").get_room_leads_to() == null: $Models/RB_DOOR.queue_free(); $Models/RB_WALL.visible = true
|
||||
else: $Models/RB_WALL.queue_free(); $Models/RB_DOOR.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_RF_CUT").get_room_leads_to() == null: $Models/RF_DOOR.queue_free(); $Models/RF_WALL.visible = true
|
||||
else: $Models/RF_WALL.queue_free(); $Models/RF_DOOR.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_LB_CUT").get_room_leads_to() == null: $Models/LB_DOOR.queue_free(); $Models/LB_WALL.visible = true
|
||||
else: $Models/LB_WALL.queue_free(); $Models/LB_DOOR.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_LF_CUT").get_room_leads_to() == null: $Models/LF_DOOR.queue_free(); $Models/LF_WALL.visible = true
|
||||
else: $Models/LF_WALL.queue_free(); $Models/LF_DOOR.visible = true
|
||||
for door in get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
@@ -0,0 +1,25 @@
|
||||
@tool
|
||||
extends DungeonRoom3D
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
super._ready()
|
||||
dungeon_done_generating.connect(remove_unused_doors_and_walls)
|
||||
|
||||
func remove_unused_doors_and_walls():
|
||||
if RandomNumberGenerator.new().randf_range(0,10) > 2.5: $Models/F_WALL/torch_001.queue_free()
|
||||
if RandomNumberGenerator.new().randf_range(0,10) > 2.5: $Models/B_WALL/torch_001.queue_free()
|
||||
if RandomNumberGenerator.new().randf_range(0,10) > 2.5: $Models/R_WALL/torch_001.queue_free()
|
||||
if RandomNumberGenerator.new().randf_range(0,10) > 2.5: $Models/L_WALL/torch_001.queue_free()
|
||||
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_F_CUT").get_room_leads_to() != null: $Models/F_WALL.queue_free()
|
||||
else: $Models/F_WALL.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_R_CUT").get_room_leads_to() != null: $Models/R_WALL.queue_free()
|
||||
else: $Models/R_WALL.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_B_CUT").get_room_leads_to() != null: $Models/B_WALL.queue_free()
|
||||
else: $Models/B_WALL.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_L_CUT").get_room_leads_to() != null: $Models/L_WALL.queue_free()
|
||||
else: $Models/L_WALL.visible = true
|
||||
for door in get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
@@ -0,0 +1,23 @@
|
||||
@tool
|
||||
extends DungeonRoom3D
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
super._ready()
|
||||
dungeon_done_generating.connect(remove_unused_doors_and_walls)
|
||||
|
||||
func remove_unused_doors_and_walls():
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_B_CUT").get_room_leads_to() == null: $Models/B_DOOR.queue_free(); $Models/B_WALL.visible = true
|
||||
else: $Models/B_WALL.queue_free(); $Models/B_DOOR.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_RB_CUT").get_room_leads_to() == null: $Models/RB_DOOR.queue_free(); $Models/RB_WALL.visible = true
|
||||
else: $Models/RB_WALL.queue_free(); $Models/RB_DOOR.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_RF_CUT").get_room_leads_to() == null: $Models/RF_DOOR.queue_free(); $Models/RF_WALL.visible = true
|
||||
else: $Models/RF_WALL.queue_free(); $Models/RF_DOOR.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_LB_CUT").get_room_leads_to() == null: $Models/LB_DOOR.queue_free(); $Models/LB_WALL.visible = true
|
||||
else: $Models/LB_WALL.queue_free(); $Models/LB_DOOR.visible = true
|
||||
if get_door_by_node($"CSGBox3D/DOOR?_LF_CUT").get_room_leads_to() == null: $Models/LF_DOOR.queue_free(); $Models/LF_WALL.visible = true
|
||||
else: $Models/LF_WALL.queue_free(); $Models/LF_DOOR.visible = true
|
||||
for door in get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
@@ -0,0 +1,35 @@
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
@export var dungeon_generator : DungeonGenerator3D
|
||||
@export var show_delay_ms := 1000
|
||||
|
||||
var done_time
|
||||
var done = false
|
||||
|
||||
func _update_to_dungeon():
|
||||
if not dungeon_generator:
|
||||
return
|
||||
var realsize = Vector3(dungeon_generator.dungeon_size) * dungeon_generator.voxel_scale
|
||||
$HouseWalls/InsideCut.size = realsize
|
||||
$HouseWalls.size = realsize + Vector3(5,5,5)
|
||||
$Roof.mesh.size = realsize + Vector3(10,10,10)
|
||||
$Roof.mesh.size.y = 20
|
||||
$Roof.position.y = realsize.y / 2 + 10 + 2.5
|
||||
if not done_time and dungeon_generator.stage == DungeonGenerator3D.BuildStage.DONE:
|
||||
done_time = Time.get_ticks_msec()
|
||||
if not done and done_time and Time.get_ticks_msec() - done_time > show_delay_ms:
|
||||
done = true
|
||||
var entrance = dungeon_generator.get_node("MansionEntranceRoom")
|
||||
var xform_to_global = dungeon_generator.global_transform * entrance.get_xform_to(DungeonRoom3D.SPACE.LOCAL_GRID, DungeonRoom3D.SPACE.DUNGEON_SPACE)
|
||||
var corner_of_room = xform_to_global * Vector3(0,0,3)
|
||||
$Entrance.position = corner_of_room
|
||||
$Entrance/FrontDoorCut.reparent($HouseWalls)
|
||||
self.visible = done
|
||||
|
||||
|
||||
func _ready():
|
||||
_update_to_dungeon()
|
||||
|
||||
func _process(delta):
|
||||
_update_to_dungeon()
|
||||
@@ -0,0 +1,86 @@
|
||||
[gd_scene load_steps=13 format=3 uid="uid://bv23twweurssv"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/sample_dungeons/mansion/house_exterior.gd" id="1_d4jd8"]
|
||||
[ext_resource type="PackedScene" uid="uid://bak8ltrhbmlv5" path="res://addons/SimpleDungeons/utils/CSGStairMaker3D.tscn" id="1_wv7fx"]
|
||||
[ext_resource type="Texture2D" uid="uid://bwe02yta75ooa" path="res://addons/SimpleDungeons/sample_assets/kenney-green-checkerboar-cc0.png" id="2_dgnpe"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_0gfbx"]
|
||||
colors = PackedColorArray(0.19, 0.0988, 0.0988, 1, 0.356863, 0.607843, 1, 1)
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_ay5m5"]
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_ikcx8"]
|
||||
color_ramp = SubResource("Gradient_0gfbx")
|
||||
noise = SubResource("FastNoiseLite_ay5m5")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cjtu7"]
|
||||
albedo_texture = SubResource("NoiseTexture2D_ikcx8")
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_ks6gq"]
|
||||
colors = PackedColorArray(0.352941, 0.25098, 0, 1, 0.190162, 0.128887, 0, 1)
|
||||
|
||||
[sub_resource type="GradientTexture2D" id="GradientTexture2D_f0vc6"]
|
||||
gradient = SubResource("Gradient_ks6gq")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_570wq"]
|
||||
albedo_texture = SubResource("GradientTexture2D_f0vc6")
|
||||
uv1_triplanar = true
|
||||
uv1_triplanar_sharpness = 0.00158643
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[sub_resource type="PrismMesh" id="PrismMesh_urnwm"]
|
||||
material = SubResource("StandardMaterial3D_570wq")
|
||||
size = Vector3(110, 20, 110)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_42bcn"]
|
||||
albedo_texture = ExtResource("2_dgnpe")
|
||||
|
||||
[node name="HouseExterior" type="Node3D"]
|
||||
script = ExtResource("1_d4jd8")
|
||||
|
||||
[node name="HouseWalls" type="CSGBox3D" parent="."]
|
||||
material_override = SubResource("StandardMaterial3D_cjtu7")
|
||||
use_collision = true
|
||||
size = Vector3(105, 105, 105)
|
||||
|
||||
[node name="InsideCut" type="CSGBox3D" parent="HouseWalls"]
|
||||
operation = 2
|
||||
size = Vector3(100, 100, 100)
|
||||
|
||||
[node name="Roof" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 63, 0)
|
||||
mesh = SubResource("PrismMesh_urnwm")
|
||||
|
||||
[node name="Entrance" type="Node3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10, -30, 50)
|
||||
|
||||
[node name="CSGStairMaker3D" parent="Entrance" instance=ExtResource("1_wv7fx")]
|
||||
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 10, -10, 12.5)
|
||||
use_collision = true
|
||||
size = Vector3(20, 20, 20)
|
||||
num_stairs = 42
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="Entrance"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -20.5, 25)
|
||||
use_collision = true
|
||||
size = Vector3(66, 1, 45)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="Entrance"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -12.5, -13.5, 9)
|
||||
material_override = SubResource("StandardMaterial3D_42bcn")
|
||||
use_collision = true
|
||||
size = Vector3(5, 14, 5)
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="Entrance"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 32.5, -13.5, 9)
|
||||
material_override = SubResource("StandardMaterial3D_42bcn")
|
||||
use_collision = true
|
||||
size = Vector3(5, 14, 5)
|
||||
|
||||
[node name="PlayerSpawnPoint" type="Node3D" parent="Entrance" groups=["player_spawn_point"]]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -14, 41)
|
||||
|
||||
[node name="FrontDoorCut" type="CSGBox3D" parent="Entrance"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 5, 0)
|
||||
operation = 2
|
||||
size = Vector3(20, 10, 10)
|
||||
@@ -0,0 +1,86 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://b3tl36squaoel"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_1qelf"]
|
||||
[ext_resource type="Texture2D" uid="uid://wvh1xmoax4ok" path="res://addons/SimpleDungeons/sample_assets/kenney-dark-grey-grid-cc0.png" id="7_x4518"]
|
||||
[ext_resource type="PackedScene" uid="uid://bak8ltrhbmlv5" path="res://addons/SimpleDungeons/utils/CSGStairMaker3D.tscn" id="8_tpxey"]
|
||||
[ext_resource type="Texture2D" uid="uid://dydd6x64uxryr" path="res://addons/SimpleDungeons/sample_assets/kenney-red-checkerboard-cc0.png" id="9_68bqd"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cy7sd"]
|
||||
albedo_texture = ExtResource("7_x4518")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_inmcr"]
|
||||
albedo_color = Color(0.252028, 0.252028, 0.252028, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_geohb"]
|
||||
albedo_color = Color(0.223103, 0.223103, 0.223103, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0fi8r"]
|
||||
albedo_texture = ExtResource("9_68bqd")
|
||||
|
||||
[node name="MansionEntranceRoom" type="Node3D"]
|
||||
script = ExtResource("1_1qelf")
|
||||
size_in_voxels = Vector3i(2, 4, 3)
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
material_override = SubResource("StandardMaterial3D_cy7sd")
|
||||
use_collision = true
|
||||
size = Vector3(20, 40, 30)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(19, 39, 29)
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -4.75, 13)
|
||||
operation = 2
|
||||
size = Vector3(19, 29.5, 7)
|
||||
|
||||
[node name="DOOR_R" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 9.25, -5, -9.95)
|
||||
operation = 2
|
||||
size = Vector3(9.2, 9.2, 2)
|
||||
|
||||
[node name="DOOR_L" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -9.75, -5, -9.95)
|
||||
operation = 2
|
||||
size = Vector3(9.2, 9.2, 2)
|
||||
|
||||
[node name="DOOR_R2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 9.25, -5, 10.05)
|
||||
operation = 2
|
||||
size = Vector3(9.2, 9.2, 2)
|
||||
|
||||
[node name="DOOR_L2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -9.75, -5, 10.05)
|
||||
operation = 2
|
||||
size = Vector3(9.2, 9.2, 2)
|
||||
|
||||
[node name="CSGStairMaker3D" parent="." instance=ExtResource("8_tpxey")]
|
||||
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 8.74228e-08, -14.9, 0)
|
||||
material_override = SubResource("StandardMaterial3D_inmcr")
|
||||
use_collision = true
|
||||
size = Vector3(10, 10, 10)
|
||||
num_stairs = 42
|
||||
|
||||
[node name="CSGBox3D4" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -14.5, -9.75)
|
||||
use_collision = true
|
||||
size = Vector3(19.8, 10, 9.5)
|
||||
material = SubResource("StandardMaterial3D_geohb")
|
||||
|
||||
[node name="CSGBox3D5" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.5, -9.95, 5)
|
||||
use_collision = true
|
||||
size = Vector3(4, 0.7, 20)
|
||||
material = SubResource("StandardMaterial3D_geohb")
|
||||
|
||||
[node name="CSGBox3D6" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.5, -9.95, 5)
|
||||
use_collision = true
|
||||
size = Vector3(4, 0.7, 20)
|
||||
material = SubResource("StandardMaterial3D_geohb")
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -19.9, 10)
|
||||
size = Vector3(6, 1, 9)
|
||||
material = SubResource("StandardMaterial3D_0fi8r")
|
||||
103
addons/SimpleDungeons/sample_dungeons/mansion/rooms/bedroom.tscn
Normal file
@@ -0,0 +1,103 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://crr6031qmir35"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_1c8qy"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_omn5o"]
|
||||
|
||||
[sub_resource type="GradientTexture2D" id="GradientTexture2D_ls0we"]
|
||||
gradient = SubResource("Gradient_omn5o")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1b4dt"]
|
||||
albedo_texture = SubResource("GradientTexture2D_ls0we")
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_4tekc"]
|
||||
script/source = "extends Node
|
||||
|
||||
func _ready():
|
||||
$\"..\".connect(\"dungeon_done_generating\", remove_unused_doors)
|
||||
|
||||
func remove_unused_doors():
|
||||
for door in $\"..\".get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
"
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_sva35"]
|
||||
albedo_color = Color(1, 0.44, 0.44, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8485v"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1qq54"]
|
||||
albedo_color = Color(0.5, 0.243333, 0.225, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ca5kw"]
|
||||
albedo_color = Color(0.5, 0.243333, 0.225, 1)
|
||||
|
||||
[node name="Bedroom" type="Node3D"]
|
||||
script = ExtResource("1_1c8qy")
|
||||
size_in_voxels = Vector3i(3, 1, 3)
|
||||
min_count = 5
|
||||
max_count = 10
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
use_collision = true
|
||||
size = Vector3(30, 10, 30)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(29, 9, 29)
|
||||
|
||||
[node name="DOOR" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.5, 0, -14.5)
|
||||
operation = 2
|
||||
size = Vector3(10, 9, 2)
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -4.42782, 0)
|
||||
operation = 2
|
||||
size = Vector3(29, 0.2, 29)
|
||||
material = SubResource("StandardMaterial3D_1b4dt")
|
||||
|
||||
[node name="RemoveUnusedDoors" type="Node" parent="."]
|
||||
script = SubResource("GDScript_4tekc")
|
||||
|
||||
[node name="CSGCombiner3D" type="CSGCombiner3D" parent="."]
|
||||
use_collision = true
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGCombiner3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.5, -3, 0)
|
||||
size = Vector3(8, 1, 8)
|
||||
material = SubResource("StandardMaterial3D_sva35")
|
||||
|
||||
[node name="CSGBox3D9" type="CSGBox3D" parent="CSGCombiner3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.5, -3, 0)
|
||||
size = Vector3(4, 1, 8)
|
||||
material = SubResource("StandardMaterial3D_8485v")
|
||||
|
||||
[node name="CSGBox3D4" type="CSGBox3D" parent="CSGCombiner3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.91372, -3.3, 0)
|
||||
size = Vector3(13.1274, 1, 8.3)
|
||||
material = SubResource("StandardMaterial3D_1qq54")
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGCombiner3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12, -2, 0)
|
||||
size = Vector3(1, 3, 8)
|
||||
material = SubResource("StandardMaterial3D_ca5kw")
|
||||
|
||||
[node name="CSGBox3D5" type="CSGBox3D" parent="CSGCombiner3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12, -4.3, 3.5)
|
||||
material = SubResource("StandardMaterial3D_ca5kw")
|
||||
|
||||
[node name="CSGBox3D6" type="CSGBox3D" parent="CSGCombiner3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -4.3, 3.5)
|
||||
material = SubResource("StandardMaterial3D_ca5kw")
|
||||
|
||||
[node name="CSGBox3D7" type="CSGBox3D" parent="CSGCombiner3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12, -4.3, -3.63669)
|
||||
material = SubResource("StandardMaterial3D_ca5kw")
|
||||
|
||||
[node name="CSGBox3D8" type="CSGBox3D" parent="CSGCombiner3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -4.3, -3.63669)
|
||||
material = SubResource("StandardMaterial3D_ca5kw")
|
||||
@@ -0,0 +1,76 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://bn6lta2vs6qh8"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_m0wvh"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8tlf5"]
|
||||
albedo_color = Color(0, 0, 0, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_4tt88"]
|
||||
albedo_color = Color(0.176419, 0.176419, 0.176419, 1)
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_nd35r"]
|
||||
offsets = PackedFloat32Array(0.276119, 1)
|
||||
colors = PackedColorArray(0.301961, 0, 0, 1, 0.555762, 0.29171, 0.357937, 1)
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_4grxn"]
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_4g5kp"]
|
||||
color_ramp = SubResource("Gradient_nd35r")
|
||||
noise = SubResource("FastNoiseLite_4grxn")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ovx52"]
|
||||
albedo_texture = SubResource("NoiseTexture2D_4g5kp")
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_nl3oo"]
|
||||
script/source = "extends Node
|
||||
|
||||
func _ready():
|
||||
$\"..\".connect(\"dungeon_done_generating\", remove_unused_doors)
|
||||
|
||||
func remove_unused_doors():
|
||||
for door in $\"..\".get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
"
|
||||
|
||||
[node name="Corridor" type="Node3D"]
|
||||
script = ExtResource("1_m0wvh")
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
use_collision = true
|
||||
size = Vector3(10, 10, 10)
|
||||
material = SubResource("StandardMaterial3D_8tlf5")
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(9, 9, 9)
|
||||
material = SubResource("StandardMaterial3D_4tt88")
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -4.52714, 0)
|
||||
operation = 2
|
||||
size = Vector3(9, 0.096, 9)
|
||||
material = SubResource("StandardMaterial3D_ovx52")
|
||||
|
||||
[node name="DOOR?" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -5)
|
||||
operation = 2
|
||||
size = Vector3(9, 9, 3)
|
||||
|
||||
[node name="DOOR?2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 5)
|
||||
operation = 2
|
||||
size = Vector3(9, 9, 3)
|
||||
|
||||
[node name="DOOR?3" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 4, 0, 0)
|
||||
operation = 2
|
||||
size = Vector3(9, 9, 3)
|
||||
|
||||
[node name="DOOR?4" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -4, 0, 0)
|
||||
operation = 2
|
||||
size = Vector3(9, 9, 3)
|
||||
|
||||
[node name="RemoveUnusedDoors" type="Node" parent="."]
|
||||
script = SubResource("GDScript_nl3oo")
|
||||
@@ -0,0 +1,158 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://bnxjj8dpefmfd"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_vbc11"]
|
||||
[ext_resource type="Texture2D" uid="uid://i7rtr0ewxm5j" path="res://addons/SimpleDungeons/sample_assets/blue-checkerboard-cc0.png" id="2_1ufaf"]
|
||||
|
||||
[sub_resource type="Shader" id="Shader_vq8fj"]
|
||||
code = "// NOTE: Shader automatically converted from Godot Engine 4.2.2.stable's StandardMaterial3D.
|
||||
|
||||
shader_type spatial;
|
||||
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx;
|
||||
uniform vec4 albedo : source_color;
|
||||
uniform sampler2D texture_albedo : source_color,filter_linear_mipmap,repeat_enable;
|
||||
uniform float point_size : hint_range(0,128);
|
||||
uniform float roughness : hint_range(0,1);
|
||||
uniform sampler2D texture_metallic : hint_default_white,filter_linear_mipmap,repeat_enable;
|
||||
uniform vec4 metallic_texture_channel;
|
||||
uniform sampler2D texture_roughness : hint_roughness_r,filter_linear_mipmap,repeat_enable;
|
||||
uniform float specular;
|
||||
uniform float metallic;
|
||||
varying vec3 uv1_triplanar_pos;
|
||||
uniform float uv1_blend_sharpness;
|
||||
varying vec3 uv1_power_normal;
|
||||
uniform vec3 uv1_scale;
|
||||
uniform vec3 uv1_offset;
|
||||
uniform vec3 uv2_scale;
|
||||
uniform vec3 uv2_offset;
|
||||
|
||||
|
||||
void vertex() {
|
||||
vec3 normal = MODEL_NORMAL_MATRIX * NORMAL;
|
||||
TANGENT = vec3(0.0,0.0,-1.0) * abs(normal.x);
|
||||
TANGENT+= vec3(1.0,0.0,0.0) * abs(normal.y);
|
||||
TANGENT+= vec3(1.0,0.0,0.0) * abs(normal.z);
|
||||
TANGENT = inverse(MODEL_NORMAL_MATRIX) * normalize(TANGENT);
|
||||
BINORMAL = vec3(0.0,1.0,0.0) * abs(normal.x);
|
||||
BINORMAL+= vec3(0.0,0.0,-1.0) * abs(normal.y);
|
||||
BINORMAL+= vec3(0.0,1.0,0.0) * abs(normal.z);
|
||||
BINORMAL = inverse(MODEL_NORMAL_MATRIX) * normalize(BINORMAL);
|
||||
uv1_power_normal=pow(abs(normal),vec3(uv1_blend_sharpness));
|
||||
uv1_triplanar_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0f)).xyz * uv1_scale + uv1_offset + TIME * 0.01;
|
||||
uv1_power_normal/=dot(uv1_power_normal,vec3(1.0));
|
||||
uv1_triplanar_pos *= vec3(1.0,-1.0, 1.0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
vec4 triplanar_texture(sampler2D p_sampler,vec3 p_weights,vec3 p_triplanar_pos) {
|
||||
vec4 samp=vec4(0.0);
|
||||
samp+= texture(p_sampler,p_triplanar_pos.xy) * p_weights.z;
|
||||
samp+= texture(p_sampler,p_triplanar_pos.xz) * p_weights.y;
|
||||
samp+= texture(p_sampler,p_triplanar_pos.zy * vec2(-1.0,1.0)) * p_weights.x;
|
||||
return samp;
|
||||
}
|
||||
|
||||
|
||||
void fragment() {
|
||||
vec4 albedo_tex = triplanar_texture(texture_albedo,uv1_power_normal,uv1_triplanar_pos);
|
||||
ALBEDO = albedo.rgb * albedo_tex.rgb;
|
||||
float metallic_tex = dot(triplanar_texture(texture_metallic,uv1_power_normal,uv1_triplanar_pos),metallic_texture_channel);
|
||||
METALLIC = metallic_tex * metallic;
|
||||
vec4 roughness_texture_channel = vec4(1.0,0.0,0.0,0.0);
|
||||
float roughness_tex = dot(triplanar_texture(texture_roughness,uv1_power_normal,uv1_triplanar_pos),roughness_texture_channel);
|
||||
ROUGHNESS = roughness_tex * roughness;
|
||||
SPECULAR = specular;
|
||||
}
|
||||
"
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_fe7vc"]
|
||||
noise_type = 2
|
||||
frequency = 0.0165
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_bispe"]
|
||||
seamless = true
|
||||
noise = SubResource("FastNoiseLite_fe7vc")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_g7p6c"]
|
||||
render_priority = 0
|
||||
shader = SubResource("Shader_vq8fj")
|
||||
shader_parameter/albedo = Color(1, 1, 1, 1)
|
||||
shader_parameter/point_size = 1.0
|
||||
shader_parameter/roughness = 1.0
|
||||
shader_parameter/metallic_texture_channel = null
|
||||
shader_parameter/specular = 0.5
|
||||
shader_parameter/metallic = 0.0
|
||||
shader_parameter/uv1_blend_sharpness = 1.0
|
||||
shader_parameter/uv1_scale = Vector3(0.08, 0.08, 0.08)
|
||||
shader_parameter/uv1_offset = Vector3(0, 0, 0)
|
||||
shader_parameter/uv2_scale = Vector3(1, 1, 1)
|
||||
shader_parameter/uv2_offset = Vector3(0, 0, 0)
|
||||
shader_parameter/texture_albedo = SubResource("NoiseTexture2D_bispe")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_07mhj"]
|
||||
albedo_texture = ExtResource("2_1ufaf")
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_17gef"]
|
||||
script/source = "extends Node
|
||||
|
||||
func remove_unused_doors():
|
||||
for door in $\"..\".get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
|
||||
func _on_living_room_dungeon_done_generating():
|
||||
remove_unused_doors()
|
||||
"
|
||||
|
||||
[node name="LivingRoom" type="Node3D"]
|
||||
script = ExtResource("1_vbc11")
|
||||
size_in_voxels = Vector3i(3, 1, 2)
|
||||
min_count = 5
|
||||
max_count = 10
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 2.49155e-06, 0, 0)
|
||||
material_override = SubResource("ShaderMaterial_g7p6c")
|
||||
use_collision = true
|
||||
size = Vector3(30, 10, 20)
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(29.5, 9.5, 19.5)
|
||||
|
||||
[node name="DOOR?_B_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, 9.75)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_R2_CUT2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 15, -2.75, -5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_R1_CUT2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 15, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L2_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -15, -2.75, -5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L1_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -15, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 2.49155e-06, -4.825, 0)
|
||||
material_override = SubResource("StandardMaterial3D_07mhj")
|
||||
use_collision = true
|
||||
size = Vector3(8, 0.2, 13)
|
||||
|
||||
[node name="RemoveUnusedDoors" type="Node" parent="."]
|
||||
script = SubResource("GDScript_17gef")
|
||||
|
||||
[connection signal="dungeon_done_generating" from="." to="RemoveUnusedDoors" method="_on_living_room_dungeon_done_generating"]
|
||||
@@ -0,0 +1,90 @@
|
||||
[gd_scene load_steps=12 format=3 uid="uid://najwcu57f6bc"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_ht4j3"]
|
||||
[ext_resource type="PackedScene" uid="uid://bak8ltrhbmlv5" path="res://addons/SimpleDungeons/utils/CSGStairMaker3D.tscn" id="3_1hkio"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_n7jmt"]
|
||||
colors = PackedColorArray(1, 1, 1, 1, 0.5832, 0.5832, 0.81, 1)
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_xy5n2"]
|
||||
noise_type = 2
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_wo5pc"]
|
||||
color_ramp = SubResource("Gradient_n7jmt")
|
||||
noise = SubResource("FastNoiseLite_xy5n2")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0vchj"]
|
||||
albedo_texture = SubResource("NoiseTexture2D_wo5pc")
|
||||
uv1_scale = Vector3(4.165, 4.165, 4.165)
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_i80ea"]
|
||||
offsets = PackedFloat32Array(0, 0.442623)
|
||||
colors = PackedColorArray(0, 0, 0, 1, 0.52, 0.1872, 0.275947, 1)
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_805tn"]
|
||||
noise_type = 3
|
||||
seed = 16
|
||||
frequency = 0.029
|
||||
fractal_type = 0
|
||||
fractal_octaves = 1
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_wr6uy"]
|
||||
in_3d_space = true
|
||||
generate_mipmaps = false
|
||||
seamless = true
|
||||
color_ramp = SubResource("Gradient_i80ea")
|
||||
noise = SubResource("FastNoiseLite_805tn")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hudx1"]
|
||||
albedo_texture = SubResource("NoiseTexture2D_wr6uy")
|
||||
uv1_scale = Vector3(0.705, 0.705, 0.705)
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_i5qk8"]
|
||||
albedo_texture = SubResource("NoiseTexture2D_wr6uy")
|
||||
uv1_scale = Vector3(0.705, 0.705, 0.705)
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[node name="Stair" type="Node3D"]
|
||||
script = ExtResource("1_ht4j3")
|
||||
size_in_voxels = Vector3i(2, 2, 1)
|
||||
min_count = 5
|
||||
max_count = 30
|
||||
is_stair_room = true
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_0vchj")
|
||||
use_collision = true
|
||||
size = Vector3(20, 20, 10)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(19.5, 19.5, 9.5)
|
||||
|
||||
[node name="DOOR?_R_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 9.8, -7.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -9.9, 2.25, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="CSGStairMaker3D" parent="." instance=ExtResource("3_1hkio")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.75, -4.75, 0)
|
||||
material_override = SubResource("StandardMaterial3D_hudx1")
|
||||
use_collision = true
|
||||
size = Vector3(14, 10, 9.5)
|
||||
num_stairs = 32
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.98486, -9.75, 0)
|
||||
material_override = SubResource("StandardMaterial3D_i5qk8")
|
||||
use_collision = true
|
||||
size = Vector3(5.96973, 0.1, 4.2)
|
||||
@@ -0,0 +1,59 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://bsvhllycm8t4i"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_8cd2g"]
|
||||
[ext_resource type="Texture2D" uid="uid://bwe02yta75ooa" path="res://addons/SimpleDungeons/sample_assets/kenney-green-checkerboar-cc0.png" id="2_ylbpf"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_orb45"]
|
||||
albedo_texture = ExtResource("2_ylbpf")
|
||||
uv1_triplanar = true
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_8geu6"]
|
||||
script/source = "@tool
|
||||
extends Node
|
||||
|
||||
func remove_unused_doors():
|
||||
for door in $\"..\".get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
|
||||
func _on_corridor_dungeon_done_generating():
|
||||
remove_unused_doors()
|
||||
"
|
||||
|
||||
[node name="Corridor" type="Node3D"]
|
||||
script = ExtResource("1_8cd2g")
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, -5.68248e-07, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_orb45")
|
||||
use_collision = true
|
||||
size = Vector3(10, 10, 10)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(9.5, 9.5, 9.5)
|
||||
|
||||
[node name="DOOR?_F_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, -5.25)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_R_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_B_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="RemoveUnusedDoors" type="Node" parent="."]
|
||||
script = SubResource("GDScript_8geu6")
|
||||
|
||||
[connection signal="dungeon_done_generating" from="." to="RemoveUnusedDoors" method="_on_corridor_dungeon_done_generating"]
|
||||
@@ -0,0 +1,93 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://54kdovp1yfl5"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_defih"]
|
||||
[ext_resource type="Texture2D" uid="uid://bwe02yta75ooa" path="res://addons/SimpleDungeons/sample_assets/kenney-green-checkerboar-cc0.png" id="2_lnti6"]
|
||||
[ext_resource type="Texture2D" uid="uid://djhy80eyqjqpv" path="res://addons/SimpleDungeons/sample_assets/kenney-dark-grey-checkboard-cc0.png" id="3_yqsq5"]
|
||||
[ext_resource type="Texture2D" uid="uid://i7rtr0ewxm5j" path="res://addons/SimpleDungeons/sample_assets/blue-checkerboard-cc0.png" id="4_h1rri"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_76ghj"]
|
||||
albedo_texture = ExtResource("2_lnti6")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_x7cu1"]
|
||||
albedo_texture = ExtResource("3_yqsq5")
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_17gef"]
|
||||
script/source = "extends Node
|
||||
|
||||
func remove_unused_doors():
|
||||
for door in $\"..\".get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
|
||||
func _on_living_room_dungeon_done_generating():
|
||||
remove_unused_doors()
|
||||
"
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ao0hg"]
|
||||
transparency = 1
|
||||
depth_draw_mode = 1
|
||||
albedo_color = Color(1, 1, 1, 0.454902)
|
||||
albedo_texture = ExtResource("4_h1rri")
|
||||
clearcoat_roughness = 1.0
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[node name="GreenRoom" type="Node3D"]
|
||||
script = ExtResource("1_defih")
|
||||
size_in_voxels = Vector3i(3, 1, 2)
|
||||
min_count = 5
|
||||
max_count = 15
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 2.49155e-06, -4.74656, 0)
|
||||
material_override = SubResource("StandardMaterial3D_76ghj")
|
||||
use_collision = true
|
||||
size = Vector3(30, 0.496826, 20)
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 2.49155e-06, 4.75344, 0)
|
||||
material_override = SubResource("StandardMaterial3D_x7cu1")
|
||||
use_collision = true
|
||||
size = Vector3(30, 0.496826, 20)
|
||||
|
||||
[node name="RemoveUnusedDoors" type="Node" parent="."]
|
||||
script = SubResource("GDScript_17gef")
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 2.49155e-06, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_ao0hg")
|
||||
use_collision = true
|
||||
size = Vector3(30, 10, 20)
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(29.5, 9.5, 19.5)
|
||||
|
||||
[node name="DOOR?_R2_CUT2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 15, -2.75, -5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_R1_CUT2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 15, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L2_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -15, -2.75, -5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L1_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -15, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="CSGBox3D4" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0, 4.65546e-15)
|
||||
operation = 1
|
||||
size = Vector3(32, 9, 21)
|
||||
|
||||
[node name="PlayerSpawnPoint" type="Node3D" parent="." groups=["player_spawn_point"]]
|
||||
|
||||
[connection signal="dungeon_done_generating" from="." to="RemoveUnusedDoors" method="_on_living_room_dungeon_done_generating"]
|
||||
40
addons/SimpleDungeons/sample_dungeons/terrarium/stair.tscn
Normal file
@@ -0,0 +1,40 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cdffbslc7qhgi"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_13awt"]
|
||||
[ext_resource type="Texture2D" uid="uid://i7rtr0ewxm5j" path="res://addons/SimpleDungeons/sample_assets/blue-checkerboard-cc0.png" id="2_tjfvs"]
|
||||
[ext_resource type="PackedScene" uid="uid://bak8ltrhbmlv5" path="res://addons/SimpleDungeons/utils/CSGStairMaker3D.tscn" id="3_ldwg4"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_wrp25"]
|
||||
albedo_texture = ExtResource("2_tjfvs")
|
||||
|
||||
[node name="Stair" type="Node3D"]
|
||||
script = ExtResource("1_13awt")
|
||||
size_in_voxels = Vector3i(2, 2, 1)
|
||||
max_count = 50
|
||||
is_stair_room = true
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_wrp25")
|
||||
use_collision = true
|
||||
size = Vector3(20, 20, 10)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(19.5, 19.5, 9.5)
|
||||
|
||||
[node name="DOOR?_R_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 9.8, -7.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -9.9, 2.25, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="CSGStairMaker3D" parent="." instance=ExtResource("3_ldwg4")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.75, -4.75, 0)
|
||||
use_collision = true
|
||||
size = Vector3(14, 10, 9.5)
|
||||
num_stairs = 32
|
||||
@@ -0,0 +1,45 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://c74pq22tm4lts"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_xaivw"]
|
||||
[ext_resource type="Texture2D" uid="uid://djhy80eyqjqpv" path="res://addons/SimpleDungeons/sample_assets/kenney-dark-grey-checkboard-cc0.png" id="2_n22k3"]
|
||||
[ext_resource type="Texture2D" uid="uid://dqvyc0ok7chld" path="res://addons/SimpleDungeons/sample_assets/kenney-grey-checkerboard-cc0.png" id="3_x6cl1"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_jbl8u"]
|
||||
albedo_texture = ExtResource("2_n22k3")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p3xxc"]
|
||||
albedo_texture = ExtResource("3_x6cl1")
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[node name="BridgeRoom" type="Node3D"]
|
||||
script = ExtResource("1_xaivw")
|
||||
size_in_voxels = Vector3i(1, 1, 2)
|
||||
|
||||
[node name="bridge" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, -4.775, 0)
|
||||
material_override = SubResource("StandardMaterial3D_jbl8u")
|
||||
operation = 2
|
||||
use_collision = true
|
||||
size = Vector3(2, 0.1, 19.5)
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_p3xxc")
|
||||
use_collision = true
|
||||
size = Vector3(10, 10, 20)
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
|
||||
operation = 2
|
||||
size = Vector3(9.5, 9, 19.5)
|
||||
|
||||
[node name="DOOR_B_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.27374e-13, -2.75, 9.75)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR_F_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, -10.25)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
@@ -0,0 +1,59 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://c30fpfkorbyl6"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_6msra"]
|
||||
[ext_resource type="Texture2D" uid="uid://bxbtgil61x5sx" path="res://addons/SimpleDungeons/sample_assets/kenney-orange-checkerboard-cc0.png" id="2_6nmer"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_orb45"]
|
||||
albedo_texture = ExtResource("2_6nmer")
|
||||
uv1_triplanar = true
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_8geu6"]
|
||||
script/source = "@tool
|
||||
extends Node
|
||||
|
||||
func remove_unused_doors():
|
||||
for door in $\"..\".get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
|
||||
func _on_corridor_dungeon_done_generating():
|
||||
remove_unused_doors()
|
||||
"
|
||||
|
||||
[node name="Corridor" type="Node3D"]
|
||||
script = ExtResource("1_6msra")
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, -5.68248e-07, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_orb45")
|
||||
use_collision = true
|
||||
size = Vector3(10, 10, 10)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(9.5, 9.5, 9.5)
|
||||
|
||||
[node name="DOOR?_F_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, -5.25)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_R_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_B_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="RemoveUnusedDoors" type="Node" parent="."]
|
||||
script = SubResource("GDScript_8geu6")
|
||||
|
||||
[connection signal="dungeon_done_generating" from="." to="RemoveUnusedDoors" method="_on_corridor_dungeon_done_generating"]
|
||||
@@ -0,0 +1,50 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://jcrublu8sywn"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_0k746"]
|
||||
[ext_resource type="Texture2D" uid="uid://wvh1xmoax4ok" path="res://addons/SimpleDungeons/sample_assets/kenney-dark-grey-grid-cc0.png" id="2_y8cer"]
|
||||
[ext_resource type="Texture2D" uid="uid://bxbtgil61x5sx" path="res://addons/SimpleDungeons/sample_assets/kenney-orange-checkerboard-cc0.png" id="3_b5jl3"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_57r0k"]
|
||||
albedo_texture = ExtResource("2_y8cer")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_k8fsj"]
|
||||
albedo_texture = ExtResource("3_b5jl3")
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[node name="EntranceRoom" type="Node3D"]
|
||||
script = ExtResource("1_0k746")
|
||||
min_count = 1
|
||||
max_count = 1
|
||||
|
||||
[node name="FrontDoor" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, -1.83588e-06, -2.75, -4.5)
|
||||
material_override = SubResource("StandardMaterial3D_57r0k")
|
||||
size = Vector3(5, 4, 0.5)
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, -1.44248e-06, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_k8fsj")
|
||||
use_collision = true
|
||||
size = Vector3(10, 10, 10)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(9.5, 9.5, 9.5)
|
||||
|
||||
[node name="DOOR_F_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, -5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR_R_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR_L_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -5, -2.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="PlayerSpawnPoint" type="Node3D" parent="." groups=["player_spawn_point"]]
|
||||
@@ -0,0 +1,75 @@
|
||||
[gd_scene load_steps=7 format=3 uid="uid://b8bnbf7bch1s0"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_y1oj8"]
|
||||
[ext_resource type="Texture2D" uid="uid://dqvyc0ok7chld" path="res://addons/SimpleDungeons/sample_assets/kenney-grey-checkerboard-cc0.png" id="2_bphhi"]
|
||||
[ext_resource type="Texture2D" uid="uid://djhy80eyqjqpv" path="res://addons/SimpleDungeons/sample_assets/kenney-dark-grey-checkboard-cc0.png" id="3_edxhr"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_n0t7s"]
|
||||
albedo_texture = ExtResource("2_bphhi")
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_x7cu1"]
|
||||
albedo_texture = ExtResource("3_edxhr")
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_17gef"]
|
||||
script/source = "extends Node
|
||||
|
||||
func remove_unused_doors():
|
||||
for door in $\"..\".get_doors():
|
||||
if door.get_room_leads_to() == null:
|
||||
door.door_node.queue_free()
|
||||
|
||||
func _on_living_room_dungeon_done_generating():
|
||||
remove_unused_doors()
|
||||
"
|
||||
|
||||
[node name="LivingRoom" type="Node3D"]
|
||||
script = ExtResource("1_y1oj8")
|
||||
size_in_voxels = Vector3i(3, 1, 2)
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 2.49155e-06, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_n0t7s")
|
||||
use_collision = true
|
||||
size = Vector3(30, 10, 20)
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(29.5, 9.5, 19.5)
|
||||
|
||||
[node name="DOOR?_B_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.75, 9.75)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_R2_CUT2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 15, -2.75, -5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_R1_CUT2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 15, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L2_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -15, -2.75, -5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L1_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -15, -2.75, 5)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 2.49155e-06, -4.825, 0)
|
||||
material_override = SubResource("StandardMaterial3D_x7cu1")
|
||||
use_collision = true
|
||||
size = Vector3(8, 0.2, 13)
|
||||
|
||||
[node name="RemoveUnusedDoors" type="Node" parent="."]
|
||||
script = SubResource("GDScript_17gef")
|
||||
|
||||
[connection signal="dungeon_done_generating" from="." to="RemoveUnusedDoors" method="_on_living_room_dungeon_done_generating"]
|
||||
@@ -0,0 +1,42 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://dxmljn5pbp0b1"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_6obq4"]
|
||||
[ext_resource type="Texture2D" uid="uid://dqvyc0ok7chld" path="res://addons/SimpleDungeons/sample_assets/kenney-grey-checkerboard-cc0.png" id="2_s2snu"]
|
||||
[ext_resource type="PackedScene" uid="uid://bak8ltrhbmlv5" path="res://addons/SimpleDungeons/utils/CSGStairMaker3D.tscn" id="3_icb60"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ddeon"]
|
||||
albedo_texture = ExtResource("2_s2snu")
|
||||
uv1_triplanar = true
|
||||
uv1_world_triplanar = true
|
||||
|
||||
[node name="Stair" type="Node3D"]
|
||||
script = ExtResource("1_6obq4")
|
||||
size_in_voxels = Vector3i(2, 2, 1)
|
||||
max_count = 15
|
||||
is_stair_room = true
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="."]
|
||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)
|
||||
material_override = SubResource("StandardMaterial3D_ddeon")
|
||||
use_collision = true
|
||||
size = Vector3(20, 20, 10)
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGBox3D"]
|
||||
operation = 2
|
||||
size = Vector3(19.5, 19.5, 9.5)
|
||||
|
||||
[node name="DOOR?_R_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 9.8, -7.75, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="DOOR?_L_CUT" type="CSGBox3D" parent="CSGBox3D"]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -9.9, 2.25, 0)
|
||||
operation = 2
|
||||
size = Vector3(2, 4, 1)
|
||||
|
||||
[node name="CSGStairMaker3D" parent="." instance=ExtResource("3_icb60")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.75, -4.75, 0)
|
||||
use_collision = true
|
||||
size = Vector3(14, 10, 9.5)
|
||||
num_stairs = 32
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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]))
|
||||
53
addons/deploy_to_steamos/GodotExportManager.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
public class GodotExportManager
|
||||
{
|
||||
public static void ExportProject(
|
||||
string projectPath,
|
||||
string outputPath,
|
||||
bool isReleaseBuild,
|
||||
Callable logCallable,
|
||||
Action OnProcessExited = null)
|
||||
{
|
||||
logCallable.CallDeferred("Starting project export, this may take a while.");
|
||||
logCallable.CallDeferred($"Output path: {outputPath}");
|
||||
logCallable.CallDeferred($"Is Release Build: {(isReleaseBuild ? "Yes" : "No")}");
|
||||
|
||||
Process exportProcess = new Process();
|
||||
|
||||
exportProcess.StartInfo.FileName = OS.GetExecutablePath();
|
||||
|
||||
var arguments = $"--headless --path \"{projectPath}\" ";
|
||||
arguments += isReleaseBuild ? "--export-release " : "--export-debug ";
|
||||
arguments += "\"Steamdeck\" ";
|
||||
arguments += $"\"{outputPath}\"";
|
||||
|
||||
exportProcess.StartInfo.Arguments = arguments;
|
||||
exportProcess.StartInfo.UseShellExecute = false;
|
||||
exportProcess.StartInfo.RedirectStandardOutput = true;
|
||||
exportProcess.EnableRaisingEvents = true;
|
||||
|
||||
exportProcess.ErrorDataReceived += (sender, args) =>
|
||||
{
|
||||
throw new Exception("Error while building project: " + args.Data);
|
||||
};
|
||||
|
||||
exportProcess.OutputDataReceived += (sender, args) =>
|
||||
{
|
||||
logCallable.CallDeferred(args.Data);
|
||||
};
|
||||
|
||||
exportProcess.Exited += (sender, args) =>
|
||||
{
|
||||
OnProcessExited?.Invoke();
|
||||
exportProcess.WaitForExit();
|
||||
};
|
||||
|
||||
exportProcess.Start();
|
||||
exportProcess.BeginOutputReadLine();
|
||||
}
|
||||
}
|
||||
33
addons/deploy_to_steamos/Plugin.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
#if TOOLS
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
[Tool]
|
||||
public partial class Plugin : EditorPlugin
|
||||
{
|
||||
private Control _dock;
|
||||
private Control _settingsPanel;
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
AddAutoloadSingleton("SettingsManager", "res://addons/deploy_to_steamos/SettingsManager.cs");
|
||||
|
||||
_dock = GD.Load<PackedScene>("res://addons/deploy_to_steamos/deploy_dock/deploy_dock.tscn").Instantiate<Control>();
|
||||
_settingsPanel = GD.Load<PackedScene>("res://addons/deploy_to_steamos/settings_panel/settings_panel.tscn").Instantiate<Control>();
|
||||
|
||||
AddControlToContainer(CustomControlContainer.Toolbar, _dock);
|
||||
AddControlToContainer(CustomControlContainer.ProjectSettingTabRight, _settingsPanel);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
RemoveControlFromContainer(CustomControlContainer.Toolbar, _dock);
|
||||
RemoveControlFromContainer(CustomControlContainer.ProjectSettingTabRight, _settingsPanel);
|
||||
|
||||
RemoveAutoloadSingleton("SettingsManager");
|
||||
|
||||
_dock.Free();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
13
addons/deploy_to_steamos/SettingsFile.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
public class SettingsFile
|
||||
{
|
||||
public enum UploadMethods
|
||||
{
|
||||
Differential,
|
||||
Incremental,
|
||||
CleanReplace
|
||||
}
|
||||
|
||||
public string BuildPath { get; set; } = "";
|
||||
public string StartParameters { get; set; } = "";
|
||||
public UploadMethods UploadMethod { get; set; } = UploadMethods.Differential;
|
||||
}
|
||||
101
addons/deploy_to_steamos/SettingsManager.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
[Tool]
|
||||
public partial class SettingsManager : Node
|
||||
{
|
||||
public static SettingsManager Instance;
|
||||
|
||||
private const string FolderPath = "res://.deploy_to_steamos";
|
||||
private const string SettingsPath = FolderPath + "/settings.json";
|
||||
private const string DevicesPath = FolderPath + "/devices.json";
|
||||
|
||||
public bool IsDirty = false;
|
||||
public SettingsFile Settings = new();
|
||||
public List<SteamOSDevkitManager.Device> Devices = new();
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
if (!FileAccess.FileExists(SettingsPath))
|
||||
{
|
||||
Settings = new SettingsFile();
|
||||
IsDirty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
using var settingsFile = FileAccess.Open(SettingsPath, FileAccess.ModeFlags.Read);
|
||||
var settingsFileContent = settingsFile.GetAsText();
|
||||
|
||||
Settings = JsonSerializer.Deserialize<SettingsFile>(settingsFileContent, DefaultSerializerOptions);
|
||||
|
||||
// Failsafe if settings file is corrupt
|
||||
if (Settings == null)
|
||||
{
|
||||
Settings = new SettingsFile();
|
||||
IsDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!FileAccess.FileExists(SettingsPath))
|
||||
{
|
||||
Devices = new List<SteamOSDevkitManager.Device>();
|
||||
IsDirty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
using var devicesFile = FileAccess.Open(DevicesPath, FileAccess.ModeFlags.Read);
|
||||
var devicesFileContent = devicesFile.GetAsText();
|
||||
|
||||
Devices = JsonSerializer.Deserialize<List<SteamOSDevkitManager.Device>>(devicesFileContent, DefaultSerializerOptions);
|
||||
|
||||
// Failsafe if device file is corrupt
|
||||
if (Devices == null)
|
||||
{
|
||||
Devices = new List<SteamOSDevkitManager.Device>();
|
||||
IsDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsDirty)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
// Check if directory exists and create if it doesn't
|
||||
var dirAccess = DirAccess.Open(FolderPath);
|
||||
if (dirAccess == null)
|
||||
{
|
||||
DirAccess.MakeDirRecursiveAbsolute(FolderPath);
|
||||
}
|
||||
|
||||
// Save Settings
|
||||
var jsonSettings = JsonSerializer.Serialize(Settings);
|
||||
using var settingsFile = FileAccess.Open(SettingsPath, FileAccess.ModeFlags.Write);
|
||||
settingsFile.StoreString(jsonSettings);
|
||||
|
||||
// Save Devices
|
||||
var jsonDevices = JsonSerializer.Serialize(Devices);
|
||||
using var devicesFile = FileAccess.Open(DevicesPath, FileAccess.ModeFlags.Write);
|
||||
devicesFile.StoreString(jsonDevices);
|
||||
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
public static readonly JsonSerializerOptions DefaultSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
}
|
||||
251
addons/deploy_to_steamos/SteamOSDevkitManager.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
using Renci.SshNet;
|
||||
using Zeroconf;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
/// <summary>
|
||||
/// Scans the network for SteamOS Devkit devices via the ZeroConf / Bonjour protocol
|
||||
/// </summary>
|
||||
public class SteamOSDevkitManager
|
||||
{
|
||||
public const string SteamOSProtocol = "_steamos-devkit._tcp.local.";
|
||||
public const string CompatibleTextVersion = "1";
|
||||
|
||||
/// <summary>
|
||||
/// Scans the network for valid SteamOS devkit devices
|
||||
/// </summary>
|
||||
/// <returns>A list of valid SteamOS devkit devices</returns>
|
||||
public static async Task<List<Device>> ScanDevices()
|
||||
{
|
||||
var networkDevices = await ZeroconfResolver.ResolveAsync(SteamOSProtocol);
|
||||
List<Device> devices = new();
|
||||
|
||||
// Iterate through all network devices and request further connection info from the service
|
||||
foreach (var networkDevice in networkDevices)
|
||||
{
|
||||
var device = new Device
|
||||
{
|
||||
DisplayName = networkDevice.DisplayName,
|
||||
IPAdress = networkDevice.IPAddress,
|
||||
ServiceName = networkDevice.DisplayName + "." + SteamOSProtocol,
|
||||
};
|
||||
|
||||
var hasServiceData = networkDevice.Services.TryGetValue(device.ServiceName, out var serviceData);
|
||||
|
||||
// This device is not a proper SteamOS device
|
||||
if(!hasServiceData) continue;
|
||||
|
||||
var properties = serviceData.Properties.FirstOrDefault();
|
||||
if (properties == null) continue;
|
||||
|
||||
// Device is not compatible (version mismatch)
|
||||
// String"1" is for some reason not equal to String"1"
|
||||
//if (properties["txtvers"] != CompatibleTextVersion) continue;
|
||||
|
||||
device.Settings = properties["settings"];
|
||||
device.Login = properties["login"];
|
||||
device.Devkit1 = properties["devkit1"];
|
||||
|
||||
devices.Add(device);
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an SSH connection and runs a command
|
||||
/// </summary>
|
||||
/// <param name="device">A SteamOS devkit device</param>
|
||||
/// <param name="command">The SSH command to run</param>
|
||||
/// <param name="logCallable">A callable for logging</param>
|
||||
/// <returns>The SSH CLI output</returns>
|
||||
public static async Task<string> RunSSHCommand(Device device, string command, Callable logCallable)
|
||||
{
|
||||
logCallable.CallDeferred($"Connecting to {device.Login}@{device.IPAdress}");
|
||||
using var client = new SshClient(GetSSHConnectionInfo(device));
|
||||
await client.ConnectAsync(CancellationToken.None);
|
||||
|
||||
logCallable.CallDeferred($"Command: '{command}'");
|
||||
var sshCommand = client.CreateCommand(command);
|
||||
var result = await Task.Factory.FromAsync(sshCommand.BeginExecute(), sshCommand.EndExecute);
|
||||
|
||||
client.Disconnect();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new SCP connection and copies all local files to a remote path
|
||||
/// </summary>
|
||||
/// <param name="device">A SteamOS devkit device</param>
|
||||
/// <param name="localPath">The path on the host</param>
|
||||
/// <param name="remotePath">The path on the device</param>
|
||||
/// <param name="logCallable">A callable for logging</param>
|
||||
public static async Task CopyFiles(Device device, string localPath, string remotePath, Callable logCallable)
|
||||
{
|
||||
logCallable.CallDeferred($"Connecting to {device.Login}@{device.IPAdress}");
|
||||
using var client = new ScpClient(GetSSHConnectionInfo(device));
|
||||
await client.ConnectAsync(CancellationToken.None);
|
||||
|
||||
logCallable.CallDeferred($"Uploading files");
|
||||
|
||||
// Run async method until upload is done
|
||||
// TODO: Set Progress based on files
|
||||
var lastUploadedFilename = "";
|
||||
var uploadProgress = 0;
|
||||
var taskCompletion = new TaskCompletionSource<bool>();
|
||||
client.Uploading += (sender, e) =>
|
||||
{
|
||||
if (e.Filename != lastUploadedFilename)
|
||||
{
|
||||
lastUploadedFilename = e.Filename;
|
||||
uploadProgress = 0;
|
||||
}
|
||||
var progressPercentage = Mathf.CeilToInt((double)e.Uploaded / e.Size * 100);
|
||||
|
||||
if (progressPercentage != uploadProgress)
|
||||
{
|
||||
uploadProgress = progressPercentage;
|
||||
logCallable.CallDeferred($"Uploading {lastUploadedFilename} ({progressPercentage}%)");
|
||||
}
|
||||
|
||||
if (e.Uploaded == e.Size)
|
||||
{
|
||||
taskCompletion.TrySetResult(true);
|
||||
}
|
||||
};
|
||||
client.ErrorOccurred += (sender, args) => throw new Exception("Error while uploading build.");
|
||||
|
||||
await Task.Run(() => client.Upload(new DirectoryInfo(localPath), remotePath));
|
||||
await taskCompletion.Task;
|
||||
client.Disconnect();
|
||||
|
||||
logCallable.CallDeferred($"Fixing file permissions");
|
||||
await RunSSHCommand(device, $"chmod +x -R {remotePath}", logCallable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs an SSH command on the device that runs the steamos-prepare-upload script
|
||||
/// </summary>
|
||||
/// <param name="device">A SteamOS devkit device</param>
|
||||
/// <param name="gameId">An ID for the game</param>
|
||||
/// <param name="logCallable">A callable for logging</param>
|
||||
/// <returns>The CLI result</returns>
|
||||
public static async Task<PrepareUploadResult> PrepareUpload(Device device, string gameId, Callable logCallable)
|
||||
{
|
||||
logCallable.CallDeferred("Preparing upload");
|
||||
|
||||
var resultRaw = await RunSSHCommand(device, "python3 ~/devkit-utils/steamos-prepare-upload --gameid " + gameId, logCallable);
|
||||
var result = JsonSerializer.Deserialize<PrepareUploadResult>(resultRaw, DefaultSerializerOptions);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs an SSH command on the device that runs the steamos-create-shortcut script
|
||||
/// </summary>
|
||||
/// <param name="device">A SteamOS devkit device</param>
|
||||
/// <param name="parameters">Parameters for the shortcut</param>
|
||||
/// <param name="logCallable">A callable for logging</param>
|
||||
/// <returns>The CLI result</returns>
|
||||
public static async Task<CreateShortcutResult> CreateShortcut(Device device, CreateShortcutParameters parameters, Callable logCallable)
|
||||
{
|
||||
var parametersJson = JsonSerializer.Serialize(parameters);
|
||||
var command = $"python3 ~/devkit-utils/steam-client-create-shortcut --parms '{parametersJson}'";
|
||||
|
||||
var resultRaw = await RunSSHCommand(device, command, logCallable);
|
||||
var result = JsonSerializer.Deserialize<CreateShortcutResult>(resultRaw, DefaultSerializerOptions);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A SteamOS devkit device
|
||||
/// </summary>
|
||||
public class Device
|
||||
{
|
||||
public string DisplayName { get; set; }
|
||||
public string IPAdress { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string ServiceName { get; set; }
|
||||
public string Settings { get; set; }
|
||||
public string Login { get; set; }
|
||||
public string Devkit1 { get; set; }
|
||||
}
|
||||
|
||||
public class PrepareUploadResult
|
||||
{
|
||||
public string User { get; set; }
|
||||
public string Directory { get; set; }
|
||||
}
|
||||
|
||||
public class CreateShortcutResult
|
||||
{
|
||||
public string Error { get; set; }
|
||||
public string Success { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters for the CreateShortcut method
|
||||
/// </summary>
|
||||
public struct CreateShortcutParameters
|
||||
{
|
||||
public string gameid { get; set; }
|
||||
public string directory { get; set; }
|
||||
public string[] argv { get; set; }
|
||||
public Dictionary<string, string> settings { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the path to the devkit_rsa key generated by the official SteamOS devkit client
|
||||
/// </summary>
|
||||
/// <returns>The path to the "devkit_rsa" key</returns>
|
||||
public static string GetPrivateKeyPath()
|
||||
{
|
||||
string applicationDataPath;
|
||||
switch (System.Environment.OSVersion.Platform)
|
||||
{
|
||||
// TODO: Linux Support
|
||||
case PlatformID.Win32NT:
|
||||
applicationDataPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), "steamos-devkit");
|
||||
break;
|
||||
case PlatformID.Unix:
|
||||
applicationDataPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "Library", "Application Support");
|
||||
break;
|
||||
default:
|
||||
applicationDataPath = "";
|
||||
break;
|
||||
}
|
||||
|
||||
var keyFolder = Path.Combine(applicationDataPath, "steamos-devkit");
|
||||
return Path.Combine(keyFolder, "devkit_rsa");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SSH Connection Info for a SteamOS devkit device
|
||||
/// </summary>
|
||||
/// <param name="device">A SteamOS devkit device</param>
|
||||
/// <returns>An SSH ConnectionInfo</returns>
|
||||
/// <exception cref="Exception">Throws if there is no private key present</exception>
|
||||
public static ConnectionInfo GetSSHConnectionInfo(Device device)
|
||||
{
|
||||
var privateKeyPath = GetPrivateKeyPath();
|
||||
if (!File.Exists(privateKeyPath)) throw new Exception("devkit_rsa key is missing. Have you connected to your device via the official devkit UI yet?");
|
||||
|
||||
var privateKeyFile = new PrivateKeyFile(privateKeyPath);
|
||||
return new ConnectionInfo(device.IPAdress, device.Login, new PrivateKeyAuthenticationMethod(device.Login, privateKeyFile));
|
||||
}
|
||||
|
||||
public static readonly JsonSerializerOptions DefaultSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
}
|
||||
130
addons/deploy_to_steamos/add_device_window/AddDeviceWindow.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
[Tool]
|
||||
public partial class AddDeviceWindow : Window
|
||||
{
|
||||
private bool _isVisible;
|
||||
private bool _isUpdatingDevices;
|
||||
private float _scanCooldown;
|
||||
private List<SteamOSDevkitManager.Device> _scannedDevices = new();
|
||||
private List<SteamOSDevkitManager.Device> _pairedDevices = new();
|
||||
|
||||
public delegate void DevicesChangedDelegate(List<SteamOSDevkitManager.Device> devices);
|
||||
public event DevicesChangedDelegate OnDevicesChanged;
|
||||
|
||||
[ExportGroup("References")]
|
||||
[Export] private VBoxContainer _devicesContainer;
|
||||
[Export] private PackedScene _deviceItemPrefab;
|
||||
[Export] private Button _refreshButton;
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
_scanCooldown -= (float)delta;
|
||||
if (_scanCooldown < 0f)
|
||||
{
|
||||
_scanCooldown = 10f;
|
||||
UpdateDevices();
|
||||
UpdateDeviceList();
|
||||
}
|
||||
|
||||
_refreshButton.Disabled = _isUpdatingDevices;
|
||||
}
|
||||
|
||||
private async void UpdateDevices()
|
||||
{
|
||||
if (!Visible || _isUpdatingDevices) return;
|
||||
|
||||
_isUpdatingDevices = true;
|
||||
|
||||
var devices = await SteamOSDevkitManager.ScanDevices();
|
||||
if (devices != _scannedDevices)
|
||||
{
|
||||
foreach (var device in devices)
|
||||
{
|
||||
if (
|
||||
_scannedDevices.Exists(x => x.IPAdress == device.IPAdress && x.Login == device.Login)
|
||||
|| _pairedDevices.Exists(x => x.IPAdress == device.IPAdress && x.Login == device.Login)
|
||||
) continue;
|
||||
|
||||
_scannedDevices.Add(device);
|
||||
}
|
||||
|
||||
UpdateDeviceList();
|
||||
}
|
||||
|
||||
_isUpdatingDevices = false;
|
||||
}
|
||||
|
||||
public void UpdateDeviceList()
|
||||
{
|
||||
// Clear List
|
||||
foreach (var childNode in _devicesContainer.GetChildren())
|
||||
{
|
||||
childNode.QueueFree();
|
||||
}
|
||||
|
||||
var devices = new List<SteamOSDevkitManager.Device>();
|
||||
devices.AddRange(_pairedDevices);
|
||||
foreach (var scannedDevice in _scannedDevices)
|
||||
{
|
||||
if (devices.Exists(x => x.IPAdress == scannedDevice.IPAdress && x.Login == scannedDevice.Login))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
devices.Add(scannedDevice);
|
||||
}
|
||||
|
||||
foreach (var scannedDevice in devices)
|
||||
{
|
||||
var deviceItem = _deviceItemPrefab.Instantiate<DeviceItemPrefab>();
|
||||
deviceItem.SetUI(scannedDevice);
|
||||
deviceItem.OnDevicePair += device =>
|
||||
{
|
||||
// TODO: Connect to device and run a random ssh command to check communication
|
||||
if (!_pairedDevices.Exists(x => x.IPAdress == device.IPAdress && x.Login == device.Login))
|
||||
{
|
||||
_pairedDevices.Add(device);
|
||||
}
|
||||
UpdateDeviceList();
|
||||
};
|
||||
deviceItem.OnDeviceUnpair += device =>
|
||||
{
|
||||
_pairedDevices.Remove(device);
|
||||
UpdateDeviceList();
|
||||
};
|
||||
_devicesContainer.AddChild(deviceItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Hide();
|
||||
|
||||
if (_pairedDevices != SettingsManager.Instance.Devices)
|
||||
{
|
||||
OnDevicesChanged?.Invoke(_pairedDevices);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnVisibilityChanged()
|
||||
{
|
||||
if (Visible)
|
||||
{
|
||||
_isVisible = true;
|
||||
|
||||
// Prepopulate device list with saved devices
|
||||
_pairedDevices = new List<SteamOSDevkitManager.Device>(SettingsManager.Instance.Devices);
|
||||
UpdateDeviceList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_isVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
[Tool]
|
||||
public partial class DeviceItemPrefab : PanelContainer
|
||||
{
|
||||
private SteamOSDevkitManager.Device _device;
|
||||
|
||||
public delegate void DeviceDelegate(SteamOSDevkitManager.Device device);
|
||||
public event DeviceDelegate OnDevicePair;
|
||||
public event DeviceDelegate OnDeviceUnpair;
|
||||
|
||||
[ExportGroup("References")]
|
||||
[Export] private Label _deviceNameLabel;
|
||||
[Export] private Label _deviceConnectionLabel;
|
||||
[Export] private Button _devicePairButton;
|
||||
[Export] private Button _deviceUnpairButton;
|
||||
|
||||
public void SetUI(SteamOSDevkitManager.Device device)
|
||||
{
|
||||
_device = device;
|
||||
_deviceNameLabel.Text = device.DisplayName;
|
||||
_deviceConnectionLabel.Text = $"{device.Login}@{device.IPAdress}";
|
||||
|
||||
if (SettingsManager.Instance.Devices.Exists(x => x.IPAdress == device.IPAdress && x.Login == device.Login))
|
||||
{
|
||||
_devicePairButton.Visible = false;
|
||||
_deviceUnpairButton.Visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Pair()
|
||||
{
|
||||
OnDevicePair?.Invoke(_device);
|
||||
}
|
||||
|
||||
public void Unpair()
|
||||
{
|
||||
OnDeviceUnpair?.Invoke(_device);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
[gd_scene load_steps=7 format=3 uid="uid://6be5afncp3nr"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/deploy_to_steamos/add_device_window/AddDeviceWindow.cs" id="1_3tepc"]
|
||||
[ext_resource type="PackedScene" uid="uid://p64nkj5ii2xt" path="res://addons/deploy_to_steamos/add_device_window/device_item_prefab.tscn" id="2_dka1k"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mam6h"]
|
||||
content_margin_left = 0.0
|
||||
content_margin_top = 0.0
|
||||
content_margin_right = 0.0
|
||||
content_margin_bottom = 0.0
|
||||
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_imdn0"]
|
||||
content_margin_left = 30.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 30.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0.2, 0.0823529, 0.121569, 1)
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_74135"]
|
||||
content_margin_left = 15.0
|
||||
content_margin_top = 15.0
|
||||
content_margin_right = 15.0
|
||||
content_margin_bottom = 15.0
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ne8al"]
|
||||
content_margin_left = 30.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 30.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0.133333, 0.133333, 0.133333, 1)
|
||||
|
||||
[node name="AddDeviceWindow" type="Window" node_paths=PackedStringArray("_devicesContainer", "_refreshButton")]
|
||||
title = "Add devkit device"
|
||||
initial_position = 1
|
||||
size = Vector2i(650, 500)
|
||||
transient = true
|
||||
exclusive = true
|
||||
min_size = Vector2i(650, 500)
|
||||
script = ExtResource("1_3tepc")
|
||||
_devicesContainer = NodePath("PanelContainer/VBoxContainer/ScrollContainer/ContentContainer/DevicesContainer")
|
||||
_deviceItemPrefab = ExtResource("2_dka1k")
|
||||
_refreshButton = NodePath("PanelContainer/VBoxContainer/ActionsContainer/HBoxContainer/RefreshButton")
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_mam6h")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="InfoContainer" type="PanelContainer" parent="PanelContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_imdn0")
|
||||
|
||||
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/InfoContainer"]
|
||||
custom_minimum_size = Vector2(0, 10)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(1, 0.788235, 0.858824, 1)
|
||||
theme_override_font_sizes/font_size = 11
|
||||
text = "Please keep in mind that you need to connect to your devkit device at least once through the official SteamOS devkit client to install the devkit tools to your device and create the necessary authentication keys."
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="ContentContainer" type="PanelContainer" parent="PanelContainer/VBoxContainer/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 0
|
||||
theme_override_styles/panel = SubResource("StyleBoxEmpty_74135")
|
||||
|
||||
[node name="DevicesContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ScrollContainer/ContentContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="ActionsContainer" type="PanelContainer" parent="PanelContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ne8al")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ActionsContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
alignment = 2
|
||||
|
||||
[node name="RefreshButton" type="Button" parent="PanelContainer/VBoxContainer/ActionsContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Refresh"
|
||||
|
||||
[node name="CloseButton" type="Button" parent="PanelContainer/VBoxContainer/ActionsContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
|
||||
[connection signal="close_requested" from="." to="." method="Close"]
|
||||
[connection signal="visibility_changed" from="." to="." method="OnVisibilityChanged"]
|
||||
[connection signal="pressed" from="PanelContainer/VBoxContainer/ActionsContainer/HBoxContainer/RefreshButton" to="." method="UpdateDevices"]
|
||||
[connection signal="pressed" from="PanelContainer/VBoxContainer/ActionsContainer/HBoxContainer/CloseButton" to="." method="Close"]
|
||||
@@ -0,0 +1,63 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://p64nkj5ii2xt"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/deploy_to_steamos/add_device_window/DeviceItemPrefab.cs" id="1_q77lw"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6g2hd"]
|
||||
content_margin_left = 15.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 15.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0.133333, 0.133333, 0.133333, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[node name="DeviceItemPrefab" type="PanelContainer" node_paths=PackedStringArray("_deviceNameLabel", "_deviceConnectionLabel", "_devicePairButton", "_deviceUnpairButton")]
|
||||
offset_right = 532.0
|
||||
offset_bottom = 51.0
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_6g2hd")
|
||||
script = ExtResource("1_q77lw")
|
||||
_deviceNameLabel = NodePath("HBoxContainer/VBoxContainer/DeviceNameLabel")
|
||||
_deviceConnectionLabel = NodePath("HBoxContainer/VBoxContainer/DeviceConnectionLabel")
|
||||
_devicePairButton = NodePath("HBoxContainer/PairButton")
|
||||
_deviceUnpairButton = NodePath("HBoxContainer/UnpairButton")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 5
|
||||
alignment = 1
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = -5
|
||||
|
||||
[node name="DeviceNameLabel" type="Label" parent="HBoxContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "steamdeck"
|
||||
|
||||
[node name="DeviceConnectionLabel" type="Label" parent="HBoxContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_colors/font_color = Color(1, 1, 1, 0.490196)
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "deck@127.0.0.1"
|
||||
|
||||
[node name="PairButton" type="Button" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
theme_override_constants/icon_max_width = 20
|
||||
text = "Pair"
|
||||
|
||||
[node name="UnpairButton" type="Button" parent="HBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
theme_override_constants/icon_max_width = 20
|
||||
text = "Unpair"
|
||||
|
||||
[connection signal="pressed" from="HBoxContainer/PairButton" to="." method="Pair"]
|
||||
[connection signal="pressed" from="HBoxContainer/UnpairButton" to="." method="Unpair"]
|
||||
108
addons/deploy_to_steamos/deploy_dock/DeployDock.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
[Tool]
|
||||
public partial class DeployDock : PanelContainer
|
||||
{
|
||||
private int _selectedId = 10000;
|
||||
|
||||
[Export] private OptionButton _deployTargetButton;
|
||||
[Export] private Button _deployButton;
|
||||
[Export] private AddDeviceWindow _addDeviceWindow;
|
||||
[Export] private DeployWindow _deployWindow;
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
_addDeviceWindow.OnDevicesChanged += OnDevicesChanged;
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
_addDeviceWindow.OnDevicesChanged -= OnDevicesChanged;
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
UpdateDropdown();
|
||||
}
|
||||
|
||||
public void OnDeployTargetItemSelected(int index)
|
||||
{
|
||||
var itemId = _deployTargetButton.GetItemId(index);
|
||||
if (_selectedId == itemId) return;
|
||||
|
||||
if (itemId <= 10000)
|
||||
{
|
||||
_selectedId = itemId;
|
||||
}
|
||||
else if (itemId == 10001)
|
||||
{
|
||||
_addDeviceWindow.Show();
|
||||
}
|
||||
|
||||
_deployTargetButton.Select(_deployTargetButton.GetItemIndex(_selectedId));
|
||||
_deployButton.Disabled = _selectedId >= 10000;
|
||||
}
|
||||
|
||||
public void Deploy()
|
||||
{
|
||||
var device = SettingsManager.Instance.Devices.ElementAtOrDefault(_selectedId);
|
||||
if (device == null)
|
||||
{
|
||||
GD.PrintErr("[DeployToSteamOS] Unknown deploy target.");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Detect Export Presets and stop if no Linux preset named "Steamdeck" exists
|
||||
|
||||
_deployWindow.Deploy(device);
|
||||
}
|
||||
|
||||
private void OnDevicesChanged(List<SteamOSDevkitManager.Device> devices)
|
||||
{
|
||||
// Adding Devices
|
||||
var devicesToAdd = devices.Where(device => !SettingsManager.Instance.Devices.Exists(x => x.IPAdress == device.IPAdress && x.Login == device.Login)).ToList();
|
||||
foreach (var device in devicesToAdd)
|
||||
{
|
||||
SettingsManager.Instance.Devices.Add(device);
|
||||
}
|
||||
|
||||
// Removing Devices
|
||||
var devicesToRemove = SettingsManager.Instance.Devices.Where(savedDevice => !devices.Exists(x => x.IPAdress == savedDevice.IPAdress && x.Login == savedDevice.Login)).ToList();
|
||||
foreach (var savedDevice in devicesToRemove)
|
||||
{
|
||||
SettingsManager.Instance.Devices.Remove(savedDevice);
|
||||
}
|
||||
|
||||
SettingsManager.Instance.Save();
|
||||
UpdateDropdown();
|
||||
}
|
||||
|
||||
private async void UpdateDropdown()
|
||||
{
|
||||
// Hack to prevent console error
|
||||
// Godot apparently calls this before the settings are loaded
|
||||
while (SettingsManager.Instance == null)
|
||||
{
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
_deployTargetButton.Clear();
|
||||
_deployTargetButton.AddItem("None", 10000);
|
||||
|
||||
for (var index = 0; index < SettingsManager.Instance.Devices.Count; index++)
|
||||
{
|
||||
var savedDevice = SettingsManager.Instance.Devices.ElementAtOrDefault(index);
|
||||
if(savedDevice == null) continue;
|
||||
|
||||
_deployTargetButton.AddItem($"{savedDevice.DisplayName} ({savedDevice.Login}@{savedDevice.IPAdress})", index);
|
||||
}
|
||||
|
||||
_deployTargetButton.AddSeparator();
|
||||
_deployTargetButton.AddItem("Add devkit device", 10001);
|
||||
}
|
||||
}
|
||||
54
addons/deploy_to_steamos/deploy_dock/deploy_dock.tscn
Normal file
@@ -0,0 +1,54 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://kdbpdei4v1ub"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/deploy_to_steamos/deploy_dock/DeployDock.cs" id="1_atc6e"]
|
||||
[ext_resource type="Texture2D" uid="uid://s1tcpal4iir4" path="res://addons/deploy_to_steamos/icon.svg" id="2_rpj28"]
|
||||
[ext_resource type="PackedScene" uid="uid://6be5afncp3nr" path="res://addons/deploy_to_steamos/add_device_window/add_device_window.tscn" id="3_qfyb3"]
|
||||
[ext_resource type="PackedScene" uid="uid://ds2umdqybfls8" path="res://addons/deploy_to_steamos/deploy_window/deploy_window.tscn" id="4_8y8co"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ebqha"]
|
||||
content_margin_left = 10.0
|
||||
|
||||
[node name="DeployDock" type="PanelContainer" node_paths=PackedStringArray("_deployTargetButton", "_deployButton", "_addDeviceWindow", "_deployWindow")]
|
||||
offset_right = 337.0
|
||||
offset_bottom = 32.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxEmpty_ebqha")
|
||||
script = ExtResource("1_atc6e")
|
||||
_deployTargetButton = NodePath("HBoxContainer/DeployTargetButton")
|
||||
_deployButton = NodePath("HBoxContainer/DeployButton")
|
||||
_addDeviceWindow = NodePath("AddDeviceWindow")
|
||||
_deployWindow = NodePath("DeployWindow")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DeployTargetButton" type="OptionButton" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
item_count = 3
|
||||
selected = 0
|
||||
popup/item_0/text = "None"
|
||||
popup/item_0/id = 10000
|
||||
popup/item_1/text = ""
|
||||
popup/item_1/id = -1
|
||||
popup/item_1/separator = true
|
||||
popup/item_2/text = "Add devkit device"
|
||||
popup/item_2/id = 10001
|
||||
|
||||
[node name="DeployButton" type="Button" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(32, 32)
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
icon = ExtResource("2_rpj28")
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="AddDeviceWindow" parent="." instance=ExtResource("3_qfyb3")]
|
||||
visible = false
|
||||
|
||||
[node name="DeployWindow" parent="." instance=ExtResource("4_8y8co")]
|
||||
visible = false
|
||||
|
||||
[connection signal="item_selected" from="HBoxContainer/DeployTargetButton" to="." method="OnDeployTargetItemSelected"]
|
||||
[connection signal="pressed" from="HBoxContainer/DeployButton" to="." method="Deploy"]
|
||||
38
addons/deploy_to_steamos/deploy_window/DeployWindow.Build.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
public partial class DeployWindow
|
||||
{
|
||||
private async Task DeployBuild(Callable logCallable)
|
||||
{
|
||||
CurrentStep = DeployStep.Building;
|
||||
CurrentProgress = StepProgress.Running;
|
||||
UpdateUI();
|
||||
|
||||
// Adding a 2 second delay so the UI can update
|
||||
await ToSignal(GetTree().CreateTimer(1), "timeout");
|
||||
var buildTask = new TaskCompletionSource<bool>();
|
||||
|
||||
if (SettingsManager.Instance.Settings.UploadMethod == SettingsFile.UploadMethods.CleanReplace)
|
||||
{
|
||||
AddToConsole(DeployStep.Building, "Removing previous build as upload method is set to CleanReplace");
|
||||
await SteamOSDevkitManager.RunSSHCommand(_device, "python3 ~/devkit-utils/steamos-delete --delete-title " + _gameId, logCallable);
|
||||
}
|
||||
|
||||
GodotExportManager.ExportProject(
|
||||
ProjectSettings.GlobalizePath("res://"),
|
||||
Path.Join(_localPath, "game.x86_64"),
|
||||
false,
|
||||
logCallable,
|
||||
() => { buildTask.SetResult(true); }
|
||||
);
|
||||
|
||||
await buildTask.Task;
|
||||
|
||||
CurrentProgress = StepProgress.Succeeded;
|
||||
UpdateUI();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
public partial class DeployWindow
|
||||
{
|
||||
private async Task DeployCreateShortcut(Callable logCallable)
|
||||
{
|
||||
CurrentStep = DeployStep.CreateShortcut;
|
||||
CurrentProgress = StepProgress.Running;
|
||||
UpdateUI();
|
||||
|
||||
var createShortcutParameters = new SteamOSDevkitManager.CreateShortcutParameters
|
||||
{
|
||||
gameid = _gameId,
|
||||
directory = _prepareUploadResult.Directory,
|
||||
argv = new[] { "game.x86_64", SettingsManager.Instance.Settings.StartParameters },
|
||||
settings = new Dictionary<string, string>
|
||||
{
|
||||
{ "steam_play", "0" }
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Fix Result, success/error are not filled in response but exist/dont
|
||||
_createShortcutResult = await SteamOSDevkitManager.CreateShortcut(
|
||||
_device,
|
||||
createShortcutParameters,
|
||||
logCallable
|
||||
);
|
||||
|
||||
CurrentProgress = StepProgress.Succeeded;
|
||||
UpdateUI();
|
||||
}
|
||||
}
|
||||
41
addons/deploy_to_steamos/deploy_window/DeployWindow.Init.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
public partial class DeployWindow
|
||||
{
|
||||
private async Task DeployInit()
|
||||
{
|
||||
CurrentStep = DeployStep.Init;
|
||||
CurrentProgress = StepProgress.Running;
|
||||
UpdateUI();
|
||||
|
||||
await Task.Delay(0);
|
||||
|
||||
_gameId = ProjectSettings.GetSetting("application/config/name", "game").AsString();
|
||||
_gameId = Regex.Replace(_gameId, @"[^a-zA-Z0-9]", string.Empty);
|
||||
|
||||
// Add current timestamp to gameid for incremental builds
|
||||
if (SettingsManager.Instance.Settings.UploadMethod == SettingsFile.UploadMethods.Incremental)
|
||||
{
|
||||
_gameId += "_" + DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
GD.Print($"[DeployToSteamOS] Deploying '{_gameId}' to '{_device.DisplayName} ({_device.Login}@{_device.IPAdress})'");
|
||||
|
||||
_localPath = SettingsManager.Instance.Settings.BuildPath;
|
||||
if (DirAccess.Open(_localPath) == null)
|
||||
{
|
||||
GD.PrintErr($"[DeployToSteamOS] Build path '{_localPath}' does not exist.");
|
||||
UpdateUIToFail();
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentProgress = StepProgress.Succeeded;
|
||||
UpdateUI();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
public partial class DeployWindow
|
||||
{
|
||||
private async Task DeployPrepareUpload(Callable logCallable)
|
||||
{
|
||||
CurrentStep = DeployStep.PrepareUpload;
|
||||
CurrentProgress = StepProgress.Running;
|
||||
UpdateUI();
|
||||
|
||||
_prepareUploadResult = await SteamOSDevkitManager.PrepareUpload(
|
||||
_device,
|
||||
_gameId,
|
||||
logCallable
|
||||
);
|
||||
|
||||
AddToConsole(DeployStep.PrepareUpload, $"User: {_prepareUploadResult.User}");
|
||||
AddToConsole(DeployStep.PrepareUpload, $"Directory: {_prepareUploadResult.Directory}");
|
||||
|
||||
CurrentProgress = StepProgress.Succeeded;
|
||||
UpdateUI();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
public partial class DeployWindow
|
||||
{
|
||||
private async Task DeployUpload(Callable logCallable)
|
||||
{
|
||||
CurrentStep = DeployStep.Uploading;
|
||||
CurrentProgress = StepProgress.Running;
|
||||
UpdateUI();
|
||||
|
||||
try
|
||||
{
|
||||
await SteamOSDevkitManager.CopyFiles(
|
||||
_device,
|
||||
_localPath,
|
||||
_prepareUploadResult.Directory,
|
||||
logCallable
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AddToConsole(DeployStep.Uploading, e.Message);
|
||||
UpdateUIToFail();
|
||||
}
|
||||
|
||||
CurrentProgress = StepProgress.Succeeded;
|
||||
UpdateUI();
|
||||
}
|
||||
}
|
||||
304
addons/deploy_to_steamos/deploy_window/DeployWindow.cs
Normal file
@@ -0,0 +1,304 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace Laura.DeployToSteamOS;
|
||||
|
||||
[Tool]
|
||||
public partial class DeployWindow : Window
|
||||
{
|
||||
public enum DeployStep
|
||||
{
|
||||
Init,
|
||||
Building,
|
||||
PrepareUpload,
|
||||
Uploading,
|
||||
CreateShortcut,
|
||||
Done
|
||||
}
|
||||
public DeployStep CurrentStep = DeployStep.Init;
|
||||
|
||||
public enum StepProgress
|
||||
{
|
||||
Queued,
|
||||
Running,
|
||||
Succeeded,
|
||||
Failed
|
||||
}
|
||||
public StepProgress CurrentProgress = StepProgress.Queued;
|
||||
|
||||
private string _localPath;
|
||||
private string _gameId;
|
||||
private SteamOSDevkitManager.Device _device;
|
||||
private SteamOSDevkitManager.PrepareUploadResult _prepareUploadResult;
|
||||
private SteamOSDevkitManager.CreateShortcutResult _createShortcutResult;
|
||||
|
||||
private Dictionary<StepProgress, Color> _colorsBright = new()
|
||||
{
|
||||
{ StepProgress.Queued, new Color("#6a6a6a") },
|
||||
{ StepProgress.Running, new Color("#ffffff") },
|
||||
{ StepProgress.Succeeded, new Color("#00f294") },
|
||||
{ StepProgress.Failed, new Color("#ff4245") },
|
||||
};
|
||||
private Dictionary<StepProgress, Color> _colorsDim = new()
|
||||
{
|
||||
{ StepProgress.Queued, new Color("#1d1d1d") },
|
||||
{ StepProgress.Running, new Color("#222222") },
|
||||
{ StepProgress.Succeeded, new Color("#13241d") },
|
||||
{ StepProgress.Failed, new Color("#241313") },
|
||||
};
|
||||
|
||||
[ExportGroup("References")]
|
||||
[Export] private VBoxContainer _buildingContainer;
|
||||
[Export] private VBoxContainer _prepareUploadContainer;
|
||||
[Export] private VBoxContainer _uploadingContainer;
|
||||
[Export] private VBoxContainer _createShortcutContainer;
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (CurrentStep != DeployStep.Done)
|
||||
{
|
||||
var container = CurrentStep switch
|
||||
{
|
||||
DeployStep.Building => _buildingContainer,
|
||||
DeployStep.PrepareUpload => _prepareUploadContainer,
|
||||
DeployStep.Uploading => _uploadingContainer,
|
||||
DeployStep.CreateShortcut => _createShortcutContainer,
|
||||
_ => null
|
||||
};
|
||||
if (container != null)
|
||||
{
|
||||
var progressBar = container.GetNode<ProgressBar>("Progressbar");
|
||||
if (progressBar.Value < 95f)
|
||||
{
|
||||
var shouldDoubleSpeed = CurrentStep is DeployStep.PrepareUpload or DeployStep.CreateShortcut;
|
||||
progressBar.Value += (float)delta * (shouldDoubleSpeed ? 2f : 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void Deploy(SteamOSDevkitManager.Device device)
|
||||
{
|
||||
if (device == null)
|
||||
{
|
||||
GD.PrintErr("[DeployToSteamOS] Device is not available.");
|
||||
return;
|
||||
}
|
||||
|
||||
_device = device;
|
||||
_prepareUploadResult = null;
|
||||
_createShortcutResult = null;
|
||||
ResetUI();
|
||||
|
||||
Title = $"Deploying to {_device.DisplayName} ({_device.Login}@{_device.IPAdress})";
|
||||
Show();
|
||||
|
||||
await DeployInit();
|
||||
await DeployBuild(Callable.From((Variant log) => AddToConsole(DeployStep.Building, log.AsString())));
|
||||
await DeployPrepareUpload(Callable.From((Variant log) => AddToConsole(DeployStep.PrepareUpload, log.AsString())));
|
||||
await DeployUpload(Callable.From((Variant log) => AddToConsole(DeployStep.Uploading, log.AsString())));
|
||||
await DeployCreateShortcut(Callable.From((Variant log) => AddToConsole(DeployStep.CreateShortcut, log.AsString())));
|
||||
|
||||
CurrentStep = DeployStep.Done;
|
||||
CurrentProgress = StepProgress.Succeeded;
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (CurrentStep != DeployStep.Init && CurrentStep != DeployStep.Done) return;
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
UpdateContainer(CurrentStep, CurrentProgress);
|
||||
if (CurrentStep == DeployStep.Done)
|
||||
{
|
||||
SetConsoleVisibility(DeployStep.Building, false, false);
|
||||
SetConsoleVisibility(DeployStep.PrepareUpload, false, false);
|
||||
SetConsoleVisibility(DeployStep.Uploading, false, false);
|
||||
SetConsoleVisibility(DeployStep.CreateShortcut, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetUI()
|
||||
{
|
||||
// Clear Consoles
|
||||
List<Node> consoleNodes = new();
|
||||
consoleNodes.AddRange(_buildingContainer.GetNode<VBoxContainer>("ConsoleContainer/ScrollContainer/VBoxContainer").GetChildren());
|
||||
consoleNodes.AddRange(_prepareUploadContainer.GetNode<VBoxContainer>("ConsoleContainer/ScrollContainer/VBoxContainer").GetChildren());
|
||||
consoleNodes.AddRange(_uploadingContainer.GetNode<VBoxContainer>("ConsoleContainer/ScrollContainer/VBoxContainer").GetChildren());
|
||||
consoleNodes.AddRange(_createShortcutContainer.GetNode<VBoxContainer>("ConsoleContainer/ScrollContainer/VBoxContainer").GetChildren());
|
||||
|
||||
foreach (var consoleNode in consoleNodes)
|
||||
{
|
||||
consoleNode.QueueFree();
|
||||
}
|
||||
|
||||
// Clear States
|
||||
UpdateContainer(DeployStep.Building, StepProgress.Queued);
|
||||
UpdateContainer(DeployStep.PrepareUpload, StepProgress.Queued);
|
||||
UpdateContainer(DeployStep.Uploading, StepProgress.Queued);
|
||||
UpdateContainer(DeployStep.CreateShortcut, StepProgress.Queued);
|
||||
|
||||
CurrentStep = DeployStep.Init;
|
||||
CurrentProgress = StepProgress.Queued;
|
||||
}
|
||||
|
||||
private void UpdateContainer(DeployStep step, StepProgress progress)
|
||||
{
|
||||
var container = step switch
|
||||
{
|
||||
DeployStep.Building => _buildingContainer,
|
||||
DeployStep.PrepareUpload => _prepareUploadContainer,
|
||||
DeployStep.Uploading => _uploadingContainer,
|
||||
DeployStep.CreateShortcut => _createShortcutContainer,
|
||||
_ => null
|
||||
};
|
||||
if (container == null) return;
|
||||
|
||||
var headerContainer = container.GetNode<PanelContainer>("HeaderContainer");
|
||||
var label = headerContainer.GetNode<Label>("HBoxContainer/Label");
|
||||
var toggleConsoleButton = headerContainer.GetNode<Button>("HBoxContainer/ToggleConsoleButton");
|
||||
var progressBar = container.GetNode<ProgressBar>("Progressbar");
|
||||
|
||||
label.AddThemeColorOverride("font_color", _colorsBright[progress]);
|
||||
|
||||
headerContainer.AddThemeStyleboxOverride("panel", new StyleBoxFlat
|
||||
{
|
||||
BgColor = _colorsDim[progress],
|
||||
ContentMarginTop = 10,
|
||||
ContentMarginBottom = 10,
|
||||
ContentMarginLeft = 10,
|
||||
ContentMarginRight = 10,
|
||||
});
|
||||
|
||||
progressBar.AddThemeStyleboxOverride("fill", new StyleBoxFlat
|
||||
{
|
||||
BgColor = _colorsBright[progress],
|
||||
});
|
||||
progressBar.Value = progress switch
|
||||
{
|
||||
StepProgress.Queued => 0,
|
||||
StepProgress.Running => 0,
|
||||
StepProgress.Succeeded => 100,
|
||||
StepProgress.Failed => 100,
|
||||
_ => progressBar.Value
|
||||
};
|
||||
|
||||
SetConsoleVisibility(step, progress == StepProgress.Running, false);
|
||||
|
||||
toggleConsoleButton.Visible = progress is StepProgress.Succeeded or StepProgress.Failed;
|
||||
toggleConsoleButton.Disabled = progress is not StepProgress.Succeeded and not StepProgress.Failed;
|
||||
}
|
||||
|
||||
private void AddToConsole(DeployStep step, string text)
|
||||
{
|
||||
var consoleContainer = step switch
|
||||
{
|
||||
DeployStep.Building => _buildingContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
DeployStep.PrepareUpload => _prepareUploadContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
DeployStep.Uploading => _uploadingContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
DeployStep.CreateShortcut => _createShortcutContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
_ => null
|
||||
};
|
||||
if (consoleContainer == null) return;
|
||||
|
||||
var consoleScrollContainer = consoleContainer.GetNode<ScrollContainer>("ScrollContainer");
|
||||
var consoleVBoxContainer = consoleScrollContainer.GetNode<VBoxContainer>("VBoxContainer");
|
||||
|
||||
// Create new Label
|
||||
var newLabel = new Label { Text = text };
|
||||
newLabel.AddThemeFontSizeOverride("font_size", 12);
|
||||
newLabel.AutowrapMode = TextServer.AutowrapMode.Word;
|
||||
newLabel.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
|
||||
|
||||
consoleVBoxContainer.AddChild(newLabel);
|
||||
consoleScrollContainer.ScrollVertical = (int)consoleScrollContainer.GetVScrollBar().MaxValue;
|
||||
}
|
||||
|
||||
private void SetConsoleVisibility(DeployStep step, bool shouldOpen, bool closeOthers)
|
||||
{
|
||||
var consoleContainer = step switch
|
||||
{
|
||||
DeployStep.Building => _buildingContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
DeployStep.PrepareUpload => _prepareUploadContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
DeployStep.Uploading => _uploadingContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
DeployStep.CreateShortcut => _createShortcutContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
_ => null
|
||||
};
|
||||
if (consoleContainer == null) return;
|
||||
|
||||
// Open Container
|
||||
if (shouldOpen)
|
||||
{
|
||||
// Close all other open containers if not running anymore
|
||||
if (closeOthers)
|
||||
{
|
||||
var consoleContainers = new List<PanelContainer>
|
||||
{
|
||||
_buildingContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
_prepareUploadContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
_uploadingContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
_createShortcutContainer.GetNode<PanelContainer>("ConsoleContainer")
|
||||
};
|
||||
foreach (var container in consoleContainers)
|
||||
{
|
||||
container.Visible = false;
|
||||
container.GetParent<VBoxContainer>().SizeFlagsVertical = Control.SizeFlags.ShrinkBegin;
|
||||
}
|
||||
}
|
||||
|
||||
// Open container
|
||||
consoleContainer.Visible = true;
|
||||
consoleContainer.GetParent<VBoxContainer>().SizeFlagsVertical = Control.SizeFlags.ExpandFill;
|
||||
}
|
||||
else
|
||||
{
|
||||
consoleContainer.Visible = false;
|
||||
consoleContainer.GetParent<VBoxContainer>().SizeFlagsVertical = Control.SizeFlags.ShrinkBegin;
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleConsoleVisiblity(DeployStep step)
|
||||
{
|
||||
var consoleContainer = step switch
|
||||
{
|
||||
DeployStep.Building => _buildingContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
DeployStep.PrepareUpload => _prepareUploadContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
DeployStep.Uploading => _uploadingContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
DeployStep.CreateShortcut => _createShortcutContainer.GetNode<PanelContainer>("ConsoleContainer"),
|
||||
_ => null
|
||||
};
|
||||
if (consoleContainer == null) return;
|
||||
|
||||
SetConsoleVisibility(step, !consoleContainer.Visible, CurrentStep == DeployStep.Done);
|
||||
}
|
||||
|
||||
private void UpdateUIToFail()
|
||||
{
|
||||
CurrentProgress = StepProgress.Failed;
|
||||
UpdateUI();
|
||||
CurrentStep = DeployStep.Done;
|
||||
CurrentProgress = StepProgress.Queued;
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public void OnBuildingConsolePressed()
|
||||
{
|
||||
ToggleConsoleVisiblity(DeployStep.Building);
|
||||
}
|
||||
public void OnPrepareUploadConsolePressed()
|
||||
{
|
||||
ToggleConsoleVisiblity(DeployStep.PrepareUpload);
|
||||
}
|
||||
public void OnUploadingConsolePressed()
|
||||
{
|
||||
ToggleConsoleVisiblity(DeployStep.Uploading);
|
||||
}
|
||||
public void OnCreatingShortcutConsolePressed()
|
||||
{
|
||||
ToggleConsoleVisiblity(DeployStep.CreateShortcut);
|
||||
}
|
||||
}
|
||||