Added Glitch Room Source code to Gitty

This commit is contained in:
Jeff Luke committed 2026-10-04 11:31:17 -07:00
commit 1ac9eb5644
505 files changed
+28504

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
@tool
class_name CabinetPlacement
extends Resource
@export var cabinet_id: StringName = &""
@export var position: Vector2 = Vector2.ZERO
@export var rotation_degrees: float = 0.0
+1
View File
@@ -0,0 +1 @@
uid://cwblyetugbr4d
+86
View File
@@ -0,0 +1,86 @@
@tool
class_name RoomDefinition
extends Resource
@export var room_id: StringName = &""
@export var display_name: String = ""
@export var cabinet_placements: Array[CabinetPlacement] = []
@export var devices: Array[RoomDeviceDefinition] = []
@export var bindings: Array[RoomDeviceBinding] = []
@export_group("Geometry")
@export var boundary_points: PackedVector2Array = PackedVector2Array()
@export var walls: Array[RoomWallDefinition] = []
@export_group("Logic")
@export var logic_rules: Array[RoomLogicRule] = []
func get_device(
device_id: StringName
) -> RoomDeviceDefinition:
for device in devices:
if device == null:
continue
if device.device_id == device_id:
return device
return null
func get_cabinet_placement(
cabinet_id: StringName
) -> CabinetPlacement:
for placement in cabinet_placements:
if placement == null:
continue
if placement.cabinet_id == cabinet_id:
return placement
return null
func get_device_for_action(
cabinet_id: StringName,
action_id: StringName
) -> RoomDeviceDefinition:
for binding in bindings:
if binding == null:
continue
if (
binding.cabinet_id == cabinet_id
and
binding.hardware_action_id == action_id
):
return get_device(binding.device_id)
return null
func get_wall(
wall_id: StringName
) -> RoomWallDefinition:
for wall in walls:
if wall == null:
continue
if wall.wall_id == wall_id:
return wall
return null
func has_wall(
wall_id: StringName
) -> bool:
return get_wall(
wall_id
) != null
+1
View File
@@ -0,0 +1 @@
uid://dgm2dkw5rar8i
+15
View File
@@ -0,0 +1,15 @@
@tool
class_name RoomDeviceBinding
extends Resource
@export var cabinet_id: StringName = &""
@export var hardware_action_id: StringName = &""
@export var device_id: StringName = &""
@export_group("Physical Cable Route")
@export var route_points: PackedVector2Array = (
PackedVector2Array()
)
@@ -0,0 +1 @@
uid://r2rpbasss511
+75
View File
@@ -0,0 +1,75 @@
@tool
class_name RoomDeviceDefinition
extends Resource
enum DeviceType {
GENERIC,
COIN_RELEASE,
LIGHT,
DOOR_LOCK,
DRAWER_LOCK,
BUZZER
}
enum Behavior {
MOMENTARY,
LATCHED
}
@export var device_id: StringName = &""
@export var display_name: String = ""
@export var device_type: DeviceType = DeviceType.GENERIC
@export var behavior: Behavior = Behavior.MOMENTARY
@export var initial_active: bool = false
@export_group("Layout")
@export var position: Vector2 = Vector2.ZERO
const FEET_TO_METERS: float = 0.3048
const METERS_TO_FEET: float = 1.0 / FEET_TO_METERS
#
# Internal/storage representation.
#
@export_storage
var physical_size_m: Vector2 = Vector2(
0.25,
0.25
)
#
# Designer-facing representation.
#
@export_group("Physical Dimensions")
@export_custom(
PROPERTY_HINT_NONE,
"suffix: ft"
)
var physical_size_ft: Vector2:
get:
return (
physical_size_m
* METERS_TO_FEET
)
set(value):
physical_size_m = (
value
* FEET_TO_METERS
)
@export_group("Physical Appearance")
@export var editor_texture: Texture2D = null
@export_range(0.1, 4.0, 0.05)
var editor_texture_scale: float = 1.0
@@ -0,0 +1 @@
uid://ejum18yr7xeo
+420
View File
@@ -0,0 +1,420 @@
class_name RoomEditSession
extends RefCounted
signal room_changed(room: RoomDefinition)
signal dirty_changed(is_dirty: bool)
signal saved
signal reverted
var source_room: RoomDefinition
var working_room: RoomDefinition
var is_dirty: bool = false
var saved_state: Dictionary = {}
func begin(room: RoomDefinition) -> bool:
if room == null:
push_error(
"RoomEditSession cannot begin with a null room."
)
return false
source_room = room
_create_working_copy()
return true
func mark_dirty() -> void:
if working_room == null:
_set_dirty(
false
)
return
var current_state := (
_capture_room_state(
working_room
)
)
_set_dirty(
current_state != saved_state
)
func save() -> bool:
if source_room == null:
push_error(
"RoomEditSession has no source room."
)
return false
if working_room == null:
push_error(
"RoomEditSession has no working room."
)
return false
_apply_working_copy_to_source()
var path := source_room.resource_path
if path.is_empty():
push_error(
"Source RoomDefinition has no resource path."
)
return false
var error := ResourceSaver.save(
source_room,
path
)
if error != OK:
push_error(
"Failed to save RoomDefinition. Error: %s"
% error
)
return false
print(
"[ROOM EDITOR] Saved room to '%s'."
% path
)
saved_state = (
_capture_room_state(
working_room
)
)
_set_dirty(
false
)
saved.emit()
return true
func revert() -> void:
if source_room == null:
return
_create_working_copy()
reverted.emit()
print(
"[ROOM EDITOR] Reverted unsaved changes."
)
func reload_from_disk() -> bool:
if source_room == null:
return false
var path := (
source_room.resource_path
)
if path.is_empty():
push_error(
"Source RoomDefinition has no resource path."
)
return false
var reloaded := (
ResourceLoader.load(
path,
"",
ResourceLoader.CACHE_MODE_REPLACE
)
as RoomDefinition
)
if reloaded == null:
push_error(
"Failed to reload RoomDefinition from '%s'."
% path
)
return false
source_room = reloaded
_create_working_copy()
print(
"[ROOM EDITOR] Reloaded room from disk: '%s'."
% path
)
return true
func _create_working_copy() -> void:
working_room = (
_duplicate_room(
source_room
)
)
saved_state = (
_capture_room_state(
working_room
)
)
_set_dirty(
false
)
room_changed.emit(
working_room
)
func _apply_working_copy_to_source() -> void:
for working_placement in working_room.cabinet_placements:
if working_placement == null:
continue
var source_placement := (
source_room.get_cabinet_placement(
working_placement.cabinet_id
)
)
if source_placement == null:
continue
source_placement.position = (
working_placement.position
)
source_placement.rotation_degrees = (
working_placement.rotation_degrees
)
for working_device in working_room.devices:
if working_device == null:
continue
var source_device := (
source_room.get_device(
working_device.device_id
)
)
if source_device == null:
continue
source_device.position = (
working_device.position
)
for working_binding in working_room.bindings:
if working_binding == null:
continue
var source_binding: RoomDeviceBinding = null
for candidate in source_room.bindings:
if candidate == null:
continue
if (
candidate.cabinet_id
== working_binding.cabinet_id
and candidate.hardware_action_id
== working_binding.hardware_action_id
and candidate.device_id
== working_binding.device_id
):
source_binding = candidate
break
if source_binding == null:
continue
source_binding.route_points = (
working_binding.route_points.duplicate()
)
#
# Copy authored room geometry.
#
source_room.boundary_points = (
working_room.boundary_points.duplicate()
)
source_room.walls = _duplicate_walls(
working_room.walls
)
func _duplicate_room(
source: RoomDefinition
) -> RoomDefinition:
var copy := (
source.duplicate(true)
as RoomDefinition
)
if copy == null:
return null
#
# Explicitly duplicate authored geometry so
# wall Resources are never shared with source_room.
#
copy.boundary_points = (
source.boundary_points.duplicate()
)
copy.walls = _duplicate_walls(
source.walls
)
return copy
func _duplicate_walls(
source: Array[RoomWallDefinition]
) -> Array[RoomWallDefinition]:
var result: Array[RoomWallDefinition] = []
for wall in source:
if wall == null:
continue
var copy := (
wall.duplicate(true)
as RoomWallDefinition
)
if copy != null:
result.append(
copy
)
return result
func _capture_room_state(
room: RoomDefinition
) -> Dictionary:
if room == null:
return {}
var state := {
"room_id": room.room_id,
"display_name": room.display_name,
"boundary_points": room.boundary_points.duplicate(),
"cabinet_placements": [],
"devices": [],
"bindings": [],
"walls": [],
}
for placement in room.cabinet_placements:
if placement == null:
state["cabinet_placements"].append(
null
)
continue
state["cabinet_placements"].append(
{
"cabinet_id": placement.cabinet_id,
"position": placement.position,
"rotation_degrees": (
placement.rotation_degrees
),
}
)
for device in room.devices:
if device == null:
state["devices"].append(
null
)
continue
state["devices"].append(
{
"device_id": device.device_id,
"display_name": device.display_name,
"device_type": device.device_type,
"behavior": device.behavior,
"initial_active": (
device.initial_active
),
"position": device.position,
}
)
for binding in room.bindings:
if binding == null:
state["bindings"].append(
null
)
continue
state["bindings"].append(
{
"cabinet_id": binding.cabinet_id,
"hardware_action_id": (
binding.hardware_action_id
),
"device_id": binding.device_id,
"route_points": (
binding.route_points.duplicate()
),
}
)
for wall in room.walls:
if wall == null:
state["walls"].append(
null
)
continue
state["walls"].append(
{
"wall_id": wall.wall_id,
"start": wall.start,
"end": wall.end,
"thickness": wall.thickness,
}
)
return state
func _set_dirty(
value: bool
) -> void:
if is_dirty == value:
return
is_dirty = value
dirty_changed.emit(
is_dirty
)
+1
View File
@@ -0,0 +1 @@
uid://w0en0xus0g5j
+312
View File
@@ -0,0 +1,312 @@
@tool
class_name RoomSimulator
extends Node
signal device_state_changed(
device_id: StringName,
active: bool
)
signal binding_activated(
cabinet_id: StringName,
action_id: StringName,
device_id: StringName
)
signal binding_deactivated(
cabinet_id: StringName,
action_id: StringName,
device_id: StringName
)
signal path_activated(
path_id: StringName,
cabinet_id: StringName,
event_id: StringName,
action_id: StringName,
device_id: StringName
)
signal path_deactivated(
path_id: StringName,
cabinet_id: StringName,
event_id: StringName,
action_id: StringName,
device_id: StringName
)
var room: RoomDefinition
var device_states: Dictionary = {}
func configure(
room_definition: RoomDefinition
) -> void:
room = room_definition
device_states.clear()
if room == null:
push_error(
"RoomSimulator received a null RoomDefinition."
)
return
for device in room.devices:
if device == null:
continue
device_states[
device.device_id
] = device.initial_active
if not Engine.is_editor_hint():
_connect_hardware_manager()
print("")
print("======= ROOM SIMULATOR =======")
print("Room: ", room.display_name)
print("Devices: ", room.devices.size())
print("==============================")
print("")
func _connect_hardware_manager() -> void:
if not HardwareManager.action_activated.is_connected(
_on_hardware_action_activated
):
HardwareManager.action_activated.connect(
_on_hardware_action_activated
)
if not HardwareManager.action_deactivated.is_connected(
_on_hardware_action_deactivated
):
HardwareManager.action_deactivated.connect(
_on_hardware_action_deactivated
)
func reset() -> void:
if room == null:
return
for device in room.devices:
if device == null:
continue
_set_device_state(
device.device_id,
device.initial_active
)
func get_device_state(
device_id: StringName
) -> bool:
return device_states.get(
device_id,
false
)
#
# Runtime HardwareManager entry points
#
func _on_hardware_action_activated(
cabinet_id: StringName,
action_id: StringName,
context: Dictionary
) -> void:
_process_action_activated(
cabinet_id,
action_id,
context
)
func _on_hardware_action_deactivated(
cabinet_id: StringName,
action_id: StringName,
context: Dictionary
) -> void:
_process_action_deactivated(
cabinet_id,
action_id,
context
)
#
# Editor simulation entry points
#
func simulate_action_activated(
cabinet_id: StringName,
action_id: StringName,
context: Dictionary = {}
) -> void:
_process_action_activated(
cabinet_id,
action_id,
context
)
func simulate_action_deactivated(
cabinet_id: StringName,
action_id: StringName,
context: Dictionary = {}
) -> void:
_process_action_deactivated(
cabinet_id,
action_id,
context
)
#
# Shared processing
#
func _process_action_activated(
cabinet_id: StringName,
action_id: StringName,
context: Dictionary
) -> void:
if room == null:
return
var device := room.get_device_for_action(
cabinet_id,
action_id
)
if device == null:
print(
"[SIMULATION] No room device bound to %s:%s"
% [
cabinet_id,
action_id
]
)
return
var event_id := StringName(
context.get(
"game_event",
"unknown_event"
)
)
var path_id := LogicPathId.make(
cabinet_id,
event_id,
action_id,
device.device_id
)
path_activated.emit(
path_id,
cabinet_id,
event_id,
action_id,
device.device_id
)
binding_activated.emit(
cabinet_id,
action_id,
device.device_id
)
_set_device_state(
device.device_id,
true
)
func _process_action_deactivated(
cabinet_id: StringName,
action_id: StringName,
context: Dictionary
) -> void:
if room == null:
return
var device := room.get_device_for_action(
cabinet_id,
action_id
)
if device == null:
return
var event_id := StringName(
context.get(
"game_event",
"unknown_event"
)
)
var path_id := LogicPathId.make(
cabinet_id,
event_id,
action_id,
device.device_id
)
path_deactivated.emit(
path_id,
cabinet_id,
event_id,
action_id,
device.device_id
)
binding_deactivated.emit(
cabinet_id,
action_id,
device.device_id
)
if (
device.behavior
== RoomDeviceDefinition.Behavior.MOMENTARY
):
_set_device_state(
device.device_id,
false
)
func _set_device_state(
device_id: StringName,
active: bool
) -> void:
device_states[
device_id
] = active
print(
"[SIMULATION] Device '%s' -> %s"
% [
device_id,
"ACTIVE" if active else "INACTIVE"
]
)
device_state_changed.emit(
device_id,
active
)
+1
View File
@@ -0,0 +1 @@
uid://bfb7sad2mjnlf
@@ -0,0 +1,102 @@
class_name SimulatedHardwareBackend
extends RefCounted
signal pin_state_changed(
cabinet_id: StringName,
gpio_pin: int,
active: bool,
action_id: StringName,
context: Dictionary
)
signal trace_entry_created(
entry: Dictionary
)
var pin_states: Dictionary = {}
func reset() -> void:
pin_states.clear()
func activate_action(
cabinet_id: StringName,
action: HardwareAction,
context: Dictionary
) -> void:
if action == null:
return
var gpio_active := action.active_high
_set_pin_state(
cabinet_id,
action,
gpio_active,
true,
context
)
func deactivate_action(
cabinet_id: StringName,
action: HardwareAction,
context: Dictionary
) -> void:
if action == null:
return
var gpio_active := not action.active_high
_set_pin_state(
cabinet_id,
action,
gpio_active,
false,
context
)
func _set_pin_state(
cabinet_id: StringName,
action: HardwareAction,
electrical_state: bool,
logical_active: bool,
context: Dictionary
) -> void:
var key := "%s::%d" % [
cabinet_id,
action.gpio_pin
]
pin_states[key] = electrical_state
var entry := {
"type": &"gpio",
"cabinet_id": cabinet_id,
"action_id": action.action_id,
"gpio_pin": action.gpio_pin,
"electrical_state": electrical_state,
"logical_active": logical_active,
"active_high": action.active_high,
"context": context.duplicate(),
}
trace_entry_created.emit(
entry
)
pin_state_changed.emit(
cabinet_id,
action.gpio_pin,
logical_active,
action.action_id,
context
)
@@ -0,0 +1 @@
uid://b3xyoxcebkhoc
+42
View File
@@ -0,0 +1,42 @@
@tool
class_name TestSequence
extends Resource
@export var sequence_id: StringName = &""
@export var display_name: String = ""
@export_group("Compatibility")
@export var scenario_id: StringName = &""
@export_multiline
var description: String = ""
@export var steps: Array[TestSequenceStep] = []
func get_sorted_steps() -> Array[TestSequenceStep]:
var result: Array[TestSequenceStep] = []
for step in steps:
if step == null:
continue
result.append(
step
)
result.sort_custom(
func(
a: TestSequenceStep,
b: TestSequenceStep
) -> bool:
return (
a.time_offset_seconds
< b.time_offset_seconds
)
)
return result
+1
View File
@@ -0,0 +1 @@
uid://clbm4alwyibrd
+13
View File
@@ -0,0 +1,13 @@
@tool
class_name TestSequenceStep
extends Resource
@export_range(0.0, 36000.0, 0.1)
var time_offset_seconds: float = 0.0
@export var source_id: StringName = &""
@export var event_id: StringName = &""
@export_multiline
var note: String = ""
@@ -0,0 +1 @@
uid://c5022gsswr487