Added Glitch Room Source code to Gitty
This commit is contained in:
commit
1ac9eb5644
505 files changed
+28504
No files matched your search
@@ -0,0 +1,7 @@
|
||||
class_name GameEvents
|
||||
|
||||
|
||||
const STARTED: StringName = &"game_started"
|
||||
const WON: StringName = &"game_won"
|
||||
const LOST: StringName = &"game_lost"
|
||||
const RESET: StringName = &"game_reset"
|
||||
@@ -0,0 +1 @@
|
||||
uid://cs0xs0amg3lrv
|
||||
@@ -0,0 +1,81 @@
|
||||
class_name GameHost
|
||||
extends Node
|
||||
|
||||
|
||||
signal game_loaded(game: ArcadeGame)
|
||||
signal game_unloaded
|
||||
|
||||
|
||||
var current_game: ArcadeGame
|
||||
|
||||
|
||||
func load_game(game_definition: GameDefinition) -> ArcadeGame:
|
||||
unload_game()
|
||||
|
||||
if game_definition == null:
|
||||
push_error("GameHost received a null GameDefinition.")
|
||||
return null
|
||||
|
||||
if game_definition.scene == null:
|
||||
push_error(
|
||||
"Game '%s' has no scene assigned."
|
||||
% game_definition.game_id
|
||||
)
|
||||
return null
|
||||
|
||||
var instance := game_definition.scene.instantiate()
|
||||
|
||||
if not instance is ArcadeGame:
|
||||
push_error(
|
||||
"Game scene '%s' does not inherit from ArcadeGame."
|
||||
% game_definition.game_id
|
||||
)
|
||||
|
||||
instance.queue_free()
|
||||
return null
|
||||
|
||||
current_game = instance as ArcadeGame
|
||||
|
||||
add_child(current_game)
|
||||
|
||||
current_game.game_started.connect(_on_game_started)
|
||||
current_game.game_won.connect(_on_game_won)
|
||||
current_game.game_lost.connect(_on_game_lost)
|
||||
current_game.game_reset.connect(_on_game_reset)
|
||||
|
||||
game_loaded.emit(current_game)
|
||||
|
||||
print(
|
||||
"[GAME HOST] Loaded '%s'"
|
||||
% game_definition.display_name
|
||||
)
|
||||
|
||||
return current_game
|
||||
|
||||
|
||||
func unload_game() -> void:
|
||||
if current_game == null:
|
||||
return
|
||||
|
||||
print("[GAME HOST] Unloading current game.")
|
||||
|
||||
current_game.queue_free()
|
||||
current_game = null
|
||||
|
||||
game_unloaded.emit()
|
||||
|
||||
|
||||
func _on_game_started() -> void:
|
||||
RoomRuntime.fire_game_event(GameEvents.STARTED)
|
||||
|
||||
|
||||
func _on_game_won() -> void:
|
||||
RoomRuntime.fire_game_event(GameEvents.WON)
|
||||
|
||||
|
||||
func _on_game_lost() -> void:
|
||||
RoomRuntime.fire_game_event(GameEvents.LOST)
|
||||
|
||||
|
||||
func _on_game_reset() -> void:
|
||||
RoomRuntime.fire_game_event(GameEvents.RESET)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cp6novpbgl1uk
|
||||
@@ -0,0 +1,77 @@
|
||||
extends Node
|
||||
signal action_activated(cabinet_id: StringName, action_id: StringName, context: Dictionary)
|
||||
signal action_deactivated(cabinet_id: StringName, action_id: StringName, context: Dictionary)
|
||||
signal status_changed(message: String)
|
||||
var active_cabinet: CabinetProfile
|
||||
var real_gpio := false
|
||||
var status := "GPIO simulation (use --gpio=real on the Pi)"
|
||||
var client: Node
|
||||
var generations: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
real_gpio = "--gpio=real" in OS.get_cmdline_user_args()
|
||||
if real_gpio:
|
||||
client = preload("res://core/runtime/pi_gpio_client.gd").new()
|
||||
client.status_changed.connect(_status)
|
||||
add_child(client)
|
||||
_status("GPIO real mode — local Pi bridge required")
|
||||
if "--gpio-test" in OS.get_cmdline_user_args():
|
||||
test_led.call_deferred()
|
||||
|
||||
func _status(message: String) -> void:
|
||||
status = message
|
||||
print("[GPIO] ", message)
|
||||
status_changed.emit(message)
|
||||
|
||||
func configure(cabinet: CabinetProfile) -> void:
|
||||
active_cabinet = cabinet
|
||||
|
||||
func trigger(action_id: StringName, context: Dictionary = {}) -> void:
|
||||
if active_cabinet == null:
|
||||
_status("No active cabinet for GPIO action")
|
||||
return
|
||||
trigger_for_cabinet(active_cabinet, action_id, context)
|
||||
|
||||
func trigger_for_cabinet(cabinet: CabinetProfile, action_id: StringName, context: Dictionary = {}) -> void:
|
||||
if cabinet == null or cabinet.hardware_config == null:
|
||||
_status("Missing cabinet hardware configuration")
|
||||
return
|
||||
var action := cabinet.hardware_config.get_action(action_id)
|
||||
if action == null:
|
||||
_status("Unknown hardware action: %s" % action_id)
|
||||
return
|
||||
_send_action(cabinet, action, context)
|
||||
|
||||
func _send_action(cabinet: CabinetProfile, action: HardwareAction, context: Dictionary) -> void:
|
||||
var key := "%s:%s" % [cabinet.hardware_config.controller_id, action.gpio_pin]
|
||||
var generation := int(generations.get(key, 0)) + 1
|
||||
generations[key] = generation
|
||||
var pulse := action.action_type == HardwareAction.ActionType.PULSE_OUTPUT
|
||||
var active := true if pulse else bool(context.get("active", true))
|
||||
if real_gpio:
|
||||
var ok: bool = await client.send({
|
||||
"controller": str(cabinet.hardware_config.controller_id),
|
||||
"pin": action.gpio_pin,
|
||||
"active_high": action.active_high,
|
||||
"mode": "pulse" if pulse else "set",
|
||||
"duration_ms": action.pulse_duration_ms,
|
||||
"active": active,
|
||||
})
|
||||
if not ok: return
|
||||
else:
|
||||
print("[MOCK GPIO] %s cabinet=%s action=%s BCM=%d" % ["PULSE" if pulse else "SET", cabinet.cabinet_id, action.action_id, action.gpio_pin])
|
||||
if active:
|
||||
action_activated.emit(cabinet.cabinet_id, action.action_id, context)
|
||||
else:
|
||||
action_deactivated.emit(cabinet.cabinet_id, action.action_id, context)
|
||||
if pulse:
|
||||
await get_tree().create_timer(action.pulse_duration_ms / 1000.0).timeout
|
||||
if int(generations.get(key, 0)) == generation:
|
||||
# Physical timing belongs to the bridge, even if the game exits mid-pulse.
|
||||
action_deactivated.emit(cabinet.cabinet_id, action.action_id, context)
|
||||
|
||||
func test_led() -> void:
|
||||
var cabinet = load("res://data/cabinets/cabinet_01.tres").duplicate(true)
|
||||
var action: HardwareAction = cabinet.hardware_config.get_action(&"coin_release")
|
||||
action.pulse_duration_ms = 2000
|
||||
_send_action(cabinet, action, {"source": "gpio_led_test"})
|
||||
@@ -0,0 +1 @@
|
||||
uid://5d4k46jwlo4y
|
||||
@@ -0,0 +1,48 @@
|
||||
class_name LaunchConfig
|
||||
|
||||
|
||||
var cabinet_id: StringName
|
||||
var scenario_id: StringName
|
||||
|
||||
|
||||
func _init(
|
||||
default_cabinet: StringName = &"",
|
||||
default_scenario: StringName = &""
|
||||
) -> void:
|
||||
|
||||
cabinet_id = default_cabinet
|
||||
scenario_id = default_scenario
|
||||
|
||||
|
||||
static func from_command_line(
|
||||
default_cabinet: StringName = &"",
|
||||
default_scenario: StringName = &""
|
||||
) -> LaunchConfig:
|
||||
|
||||
var config := LaunchConfig.new(
|
||||
default_cabinet,
|
||||
default_scenario
|
||||
)
|
||||
|
||||
for arg in OS.get_cmdline_user_args():
|
||||
|
||||
if arg.begins_with("--cabinet="):
|
||||
config.cabinet_id = StringName(
|
||||
arg.trim_prefix("--cabinet=")
|
||||
)
|
||||
|
||||
elif arg.begins_with("--scenario="):
|
||||
config.scenario_id = StringName(
|
||||
arg.trim_prefix("--scenario=")
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
func print_debug() -> void:
|
||||
print("")
|
||||
print("========= LAUNCH CONFIG =========")
|
||||
print("Cabinet: ", cabinet_id)
|
||||
print("Scenario: ", scenario_id)
|
||||
print("=================================")
|
||||
print("")
|
||||
@@ -0,0 +1 @@
|
||||
uid://brd536grsx38f
|
||||
@@ -0,0 +1,19 @@
|
||||
class_name LogicPathId
|
||||
|
||||
|
||||
static func make(
|
||||
cabinet_id: StringName,
|
||||
event_id: StringName,
|
||||
action_id: StringName,
|
||||
device_id: StringName
|
||||
) -> StringName:
|
||||
|
||||
return StringName(
|
||||
"%s::%s::%s::%s"
|
||||
% [
|
||||
cabinet_id,
|
||||
event_id,
|
||||
action_id,
|
||||
device_id
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cy746kl1sgsrn
|
||||
@@ -0,0 +1,58 @@
|
||||
extends Node
|
||||
signal completed(id: int, ok: bool)
|
||||
signal status_changed(message: String)
|
||||
const URL := "http://127.0.0.1:8765"
|
||||
var pending: Array[Dictionary] = []
|
||||
var serial := 0
|
||||
var busy := false
|
||||
var heartbeat: HTTPRequest
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
heartbeat = HTTPRequest.new()
|
||||
heartbeat.timeout = 1.0
|
||||
add_child(heartbeat)
|
||||
var timer := Timer.new()
|
||||
timer.wait_time = 1.0
|
||||
timer.timeout.connect(_heartbeat)
|
||||
add_child(timer)
|
||||
timer.start()
|
||||
|
||||
func _heartbeat() -> void:
|
||||
if heartbeat.get_http_client_status() == HTTPClient.STATUS_DISCONNECTED:
|
||||
heartbeat.request(URL + "/heartbeat", [], HTTPClient.METHOD_POST, "{}")
|
||||
|
||||
func send(payload: Dictionary) -> bool:
|
||||
if pending.size() >= 32:
|
||||
status_changed.emit("GPIO output queue is full")
|
||||
return false
|
||||
serial += 1
|
||||
var id := serial
|
||||
pending.append({"id": id, "payload": payload})
|
||||
_pump.call_deferred()
|
||||
while true:
|
||||
var result: Array = await completed
|
||||
if result[0] == id: return result[1]
|
||||
return false
|
||||
|
||||
func _pump() -> void:
|
||||
if busy or pending.is_empty(): return
|
||||
busy = true
|
||||
var item: Dictionary = pending[0]
|
||||
var request := HTTPRequest.new()
|
||||
request.timeout = 2.0
|
||||
add_child(request)
|
||||
var ok := false
|
||||
var message := "GPIO bridge unavailable — start pi_gpio_bridge.py on this Pi"
|
||||
var error := request.request(URL + "/output", ["Content-Type: application/json"], HTTPClient.METHOD_POST, JSON.stringify(item.payload))
|
||||
if error == OK:
|
||||
var response: Array = await request.request_completed
|
||||
var body = JSON.parse_string(response[3].get_string_from_utf8())
|
||||
ok = response[0] == HTTPRequest.RESULT_SUCCESS and response[1] == 200 and body is Dictionary and body.get("ok", false) == true
|
||||
if body is Dictionary and body.has("error"): message = str(body.error)
|
||||
request.queue_free()
|
||||
pending.pop_front()
|
||||
busy = false
|
||||
status_changed.emit("GPIO %s accepted by Pi bridge" % item.payload.pin if ok else message)
|
||||
completed.emit(item.id, ok)
|
||||
_pump.call_deferred()
|
||||
@@ -0,0 +1 @@
|
||||
uid://74k3b8polfi2
|
||||
@@ -0,0 +1,50 @@
|
||||
@tool
|
||||
class_name ProjectDatabase
|
||||
extends Resource
|
||||
|
||||
|
||||
@export var scenarios: Array[ScenarioDefinition] = []
|
||||
@export var cabinets: Array[CabinetProfile] = []
|
||||
@export var games: Array[GameDefinition] = []
|
||||
|
||||
|
||||
func get_scenario(
|
||||
scenario_id: StringName
|
||||
) -> ScenarioDefinition:
|
||||
|
||||
for scenario in scenarios:
|
||||
if scenario == null:
|
||||
continue
|
||||
|
||||
if scenario.scenario_id == scenario_id:
|
||||
return scenario
|
||||
|
||||
return null
|
||||
|
||||
|
||||
func get_cabinet(
|
||||
cabinet_id: StringName
|
||||
) -> CabinetProfile:
|
||||
|
||||
for cabinet in cabinets:
|
||||
if cabinet == null:
|
||||
continue
|
||||
|
||||
if cabinet.cabinet_id == cabinet_id:
|
||||
return cabinet
|
||||
|
||||
return null
|
||||
|
||||
|
||||
func get_game(
|
||||
game_id: StringName
|
||||
) -> GameDefinition:
|
||||
|
||||
for game in games:
|
||||
if game == null:
|
||||
continue
|
||||
|
||||
if game.game_id == game_id:
|
||||
return game
|
||||
|
||||
return null
|
||||
@@ -0,0 +1 @@
|
||||
uid://diqnrxrhwx2ug
|
||||
@@ -0,0 +1,315 @@
|
||||
class_name RoomController
|
||||
extends Node
|
||||
|
||||
|
||||
signal scenario_loaded(scenario: ScenarioDefinition)
|
||||
|
||||
signal validation_completed(
|
||||
result: ValidationResult
|
||||
)
|
||||
|
||||
signal cabinet_status_changed(
|
||||
cabinet_id: StringName,
|
||||
status: StringName
|
||||
)
|
||||
|
||||
signal room_status_changed(
|
||||
status: StringName
|
||||
)
|
||||
|
||||
|
||||
var database: ProjectDatabase
|
||||
var active_scenario: ScenarioDefinition
|
||||
|
||||
var cabinet_statuses: Dictionary = {}
|
||||
var room: RoomDefinition
|
||||
|
||||
func configure(
|
||||
project_database: ProjectDatabase,
|
||||
room_definition: RoomDefinition = null
|
||||
) -> void:
|
||||
|
||||
database = project_database
|
||||
room = room_definition
|
||||
|
||||
|
||||
func load_scenario(
|
||||
scenario_id: StringName
|
||||
) -> bool:
|
||||
|
||||
if database == null:
|
||||
push_error(
|
||||
"RoomController has no ProjectDatabase."
|
||||
)
|
||||
return false
|
||||
|
||||
var scenario := database.get_scenario(
|
||||
scenario_id
|
||||
)
|
||||
|
||||
if scenario == null:
|
||||
push_error(
|
||||
"Scenario '%s' not found."
|
||||
% scenario_id
|
||||
)
|
||||
return false
|
||||
|
||||
active_scenario = scenario
|
||||
|
||||
_initialize_cabinet_statuses()
|
||||
|
||||
print("")
|
||||
print("======= ROOM CONTROLLER =======")
|
||||
print(
|
||||
"Loaded scenario: %s v%d"
|
||||
% [
|
||||
scenario.display_name,
|
||||
scenario.version
|
||||
]
|
||||
)
|
||||
print("===============================")
|
||||
print("")
|
||||
|
||||
scenario_loaded.emit(
|
||||
active_scenario
|
||||
)
|
||||
|
||||
room_status_changed.emit(
|
||||
&"LOADED"
|
||||
)
|
||||
|
||||
return true
|
||||
|
||||
|
||||
func validate_active_scenario() -> ValidationResult:
|
||||
var result := ValidationResult.new()
|
||||
|
||||
result.merge(
|
||||
ScenarioValidator.validate(
|
||||
active_scenario
|
||||
)
|
||||
)
|
||||
|
||||
if room != null:
|
||||
result.merge(
|
||||
RoomValidator.validate(
|
||||
room,
|
||||
database
|
||||
)
|
||||
)
|
||||
|
||||
validation_completed.emit(
|
||||
result
|
||||
)
|
||||
|
||||
if result.is_valid():
|
||||
room_status_changed.emit(
|
||||
&"VALID"
|
||||
)
|
||||
else:
|
||||
room_status_changed.emit(
|
||||
&"INVALID"
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func prep_room() -> bool:
|
||||
if active_scenario == null:
|
||||
push_error(
|
||||
"Cannot prep room: no scenario loaded."
|
||||
)
|
||||
return false
|
||||
|
||||
var validation := validate_active_scenario()
|
||||
|
||||
if not validation.is_valid():
|
||||
print(
|
||||
"[ROOM CONTROLLER] Prep aborted: "
|
||||
+ validation.get_summary()
|
||||
)
|
||||
|
||||
room_status_changed.emit(
|
||||
&"PREP_FAILED"
|
||||
)
|
||||
|
||||
return false
|
||||
|
||||
room_status_changed.emit(
|
||||
&"PREPARING"
|
||||
)
|
||||
|
||||
print("")
|
||||
print("=========== PREP ROOM ===========")
|
||||
|
||||
for assignment in active_scenario.assignments:
|
||||
_prep_assignment(assignment)
|
||||
|
||||
print("=================================")
|
||||
print("")
|
||||
|
||||
room_status_changed.emit(
|
||||
&"READY"
|
||||
)
|
||||
|
||||
return true
|
||||
|
||||
|
||||
func _prep_assignment(
|
||||
assignment: ScenarioAssignment
|
||||
) -> void:
|
||||
|
||||
var cabinet := assignment.cabinet
|
||||
var game := assignment.game
|
||||
|
||||
_set_cabinet_status(
|
||||
cabinet.cabinet_id,
|
||||
&"PREPARING"
|
||||
)
|
||||
|
||||
print(
|
||||
"[ROOM CONTROLLER] Cabinet '%s'"
|
||||
% cabinet.cabinet_id
|
||||
)
|
||||
|
||||
print(
|
||||
" Game: %s"
|
||||
% game.game_id
|
||||
)
|
||||
|
||||
print(
|
||||
" Variant: %s"
|
||||
% assignment.variant
|
||||
)
|
||||
|
||||
print(
|
||||
" Hardware: %s"
|
||||
% cabinet.hardware_config.controller_id
|
||||
)
|
||||
|
||||
#
|
||||
# Networking will eventually happen here.
|
||||
#
|
||||
# send_to_cabinet(
|
||||
# cabinet.cabinet_id,
|
||||
# scenario,
|
||||
# assignment
|
||||
# )
|
||||
#
|
||||
|
||||
print(
|
||||
" [SIMULATION] Cabinet ready."
|
||||
)
|
||||
|
||||
_set_cabinet_status(
|
||||
cabinet.cabinet_id,
|
||||
&"READY"
|
||||
)
|
||||
|
||||
|
||||
func _initialize_cabinet_statuses() -> void:
|
||||
cabinet_statuses.clear()
|
||||
|
||||
if active_scenario == null:
|
||||
return
|
||||
|
||||
for assignment in active_scenario.assignments:
|
||||
|
||||
if assignment == null:
|
||||
continue
|
||||
|
||||
if assignment.cabinet == null:
|
||||
continue
|
||||
|
||||
_set_cabinet_status(
|
||||
assignment.cabinet.cabinet_id,
|
||||
&"NOT_PREPARED"
|
||||
)
|
||||
|
||||
|
||||
func _set_cabinet_status(
|
||||
cabinet_id: StringName,
|
||||
status: StringName
|
||||
) -> void:
|
||||
|
||||
cabinet_statuses[cabinet_id] = status
|
||||
|
||||
cabinet_status_changed.emit(
|
||||
cabinet_id,
|
||||
status
|
||||
)
|
||||
|
||||
|
||||
func get_cabinet_status(
|
||||
cabinet_id: StringName
|
||||
) -> StringName:
|
||||
|
||||
return cabinet_statuses.get(
|
||||
cabinet_id,
|
||||
&"UNKNOWN"
|
||||
)
|
||||
|
||||
func simulate_game_event(
|
||||
cabinet_id: StringName,
|
||||
event_name: StringName
|
||||
) -> void:
|
||||
|
||||
if active_scenario == null:
|
||||
push_error(
|
||||
"Cannot simulate event: no active scenario."
|
||||
)
|
||||
return
|
||||
|
||||
var assignment := (
|
||||
active_scenario
|
||||
.get_assignment_for_cabinet(
|
||||
cabinet_id
|
||||
)
|
||||
)
|
||||
|
||||
if assignment == null:
|
||||
push_error(
|
||||
"No assignment exists for cabinet '%s'."
|
||||
% cabinet_id
|
||||
)
|
||||
return
|
||||
|
||||
print("")
|
||||
print(
|
||||
"[ROOM SIMULATION] %s -> %s"
|
||||
% [
|
||||
cabinet_id,
|
||||
event_name
|
||||
]
|
||||
)
|
||||
|
||||
var actions := (
|
||||
assignment
|
||||
.get_actions_for_event(
|
||||
event_name
|
||||
)
|
||||
)
|
||||
|
||||
if actions.is_empty():
|
||||
print(
|
||||
"[ROOM SIMULATION] No actions mapped."
|
||||
)
|
||||
return
|
||||
|
||||
for action_id in actions:
|
||||
|
||||
print(
|
||||
"[ROOM SIMULATION] %s -> %s"
|
||||
% [
|
||||
event_name,
|
||||
action_id
|
||||
]
|
||||
)
|
||||
|
||||
HardwareManager.trigger_for_cabinet(
|
||||
assignment.cabinet,
|
||||
action_id,
|
||||
{
|
||||
"game_event": event_name
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dbe24sbmbh6tt
|
||||
@@ -0,0 +1,69 @@
|
||||
extends Node
|
||||
|
||||
|
||||
signal game_event_fired(
|
||||
event_name: StringName,
|
||||
cabinet_id: StringName
|
||||
)
|
||||
|
||||
|
||||
var active_assignment: ScenarioAssignment
|
||||
|
||||
|
||||
func configure(assignment: ScenarioAssignment) -> void:
|
||||
if assignment == null:
|
||||
push_error("Cannot configure RoomRuntime with null assignment.")
|
||||
return
|
||||
|
||||
active_assignment = assignment
|
||||
|
||||
HardwareManager.configure(assignment.cabinet)
|
||||
|
||||
print("")
|
||||
print("======= ROOM RUNTIME =======")
|
||||
print("Cabinet: ", assignment.cabinet.display_name)
|
||||
print("Game: ", assignment.game.display_name)
|
||||
print("Variant: ", assignment.variant)
|
||||
print("============================")
|
||||
print("")
|
||||
|
||||
|
||||
func fire_game_event(event_name: StringName) -> void:
|
||||
if active_assignment == null:
|
||||
push_error("RoomRuntime has no active assignment.")
|
||||
return
|
||||
|
||||
print("")
|
||||
print(
|
||||
"[GAME EVENT] %s -> %s"
|
||||
% [
|
||||
active_assignment.game.game_id,
|
||||
event_name
|
||||
]
|
||||
)
|
||||
|
||||
game_event_fired.emit(
|
||||
event_name,
|
||||
active_assignment.cabinet.cabinet_id
|
||||
)
|
||||
|
||||
var actions := active_assignment.get_actions_for_event(
|
||||
event_name
|
||||
)
|
||||
|
||||
if actions.is_empty():
|
||||
print("[SCENARIO] No hardware actions mapped.")
|
||||
return
|
||||
|
||||
for action_id in actions:
|
||||
print(
|
||||
"[SCENARIO] %s -> %s"
|
||||
% [event_name, action_id]
|
||||
)
|
||||
|
||||
HardwareManager.trigger(
|
||||
action_id,
|
||||
{
|
||||
"game_event": event_name
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://eelj57byt0su
|
||||
@@ -0,0 +1,223 @@
|
||||
class_name RoomValidator
|
||||
|
||||
|
||||
static func validate(
|
||||
room: RoomDefinition,
|
||||
database: ProjectDatabase
|
||||
) -> ValidationResult:
|
||||
|
||||
var result := ValidationResult.new()
|
||||
|
||||
if room == null:
|
||||
result.add_error(
|
||||
"RoomDefinition is null."
|
||||
)
|
||||
return result
|
||||
|
||||
_validate_cabinet_placements(
|
||||
room,
|
||||
database,
|
||||
result
|
||||
)
|
||||
|
||||
_validate_devices(
|
||||
room,
|
||||
result
|
||||
)
|
||||
|
||||
_validate_bindings(
|
||||
room,
|
||||
database,
|
||||
result
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
static func _validate_cabinet_placements(
|
||||
room: RoomDefinition,
|
||||
database: ProjectDatabase,
|
||||
result: ValidationResult
|
||||
) -> void:
|
||||
|
||||
var seen_ids: Dictionary = {}
|
||||
|
||||
for placement in room.cabinet_placements:
|
||||
|
||||
if placement == null:
|
||||
result.add_warning(
|
||||
"Room contains a null cabinet placement."
|
||||
)
|
||||
continue
|
||||
|
||||
var cabinet_id := placement.cabinet_id
|
||||
|
||||
if cabinet_id == &"":
|
||||
result.add_error(
|
||||
"Room contains a cabinet placement with no cabinet_id."
|
||||
)
|
||||
continue
|
||||
|
||||
if seen_ids.has(cabinet_id):
|
||||
result.add_error(
|
||||
"Cabinet '%s' is placed in the room more than once."
|
||||
% cabinet_id
|
||||
)
|
||||
else:
|
||||
seen_ids[cabinet_id] = true
|
||||
|
||||
if database != null:
|
||||
if database.get_cabinet(
|
||||
cabinet_id
|
||||
) == null:
|
||||
|
||||
result.add_error(
|
||||
"Room references unknown cabinet '%s'."
|
||||
% cabinet_id
|
||||
)
|
||||
|
||||
static func _validate_devices(
|
||||
room: RoomDefinition,
|
||||
result: ValidationResult
|
||||
) -> void:
|
||||
|
||||
var seen_ids: Dictionary = {}
|
||||
|
||||
for device in room.devices:
|
||||
|
||||
if device == null:
|
||||
result.add_warning(
|
||||
"Room contains a null device."
|
||||
)
|
||||
continue
|
||||
|
||||
if device.device_id == &"":
|
||||
result.add_error(
|
||||
"Room contains a device with no device_id."
|
||||
)
|
||||
continue
|
||||
|
||||
if seen_ids.has(
|
||||
device.device_id
|
||||
):
|
||||
result.add_error(
|
||||
"Room device '%s' appears more than once."
|
||||
% device.device_id
|
||||
)
|
||||
else:
|
||||
seen_ids[
|
||||
device.device_id
|
||||
] = true
|
||||
|
||||
static func _validate_bindings(
|
||||
room: RoomDefinition,
|
||||
database: ProjectDatabase,
|
||||
result: ValidationResult
|
||||
) -> void:
|
||||
|
||||
var seen_bindings: Dictionary = {}
|
||||
|
||||
for binding in room.bindings:
|
||||
|
||||
if binding == null:
|
||||
result.add_warning(
|
||||
"Room contains a null device binding."
|
||||
)
|
||||
continue
|
||||
|
||||
var cabinet_id := binding.cabinet_id
|
||||
var action_id := binding.hardware_action_id
|
||||
var device_id := binding.device_id
|
||||
|
||||
if cabinet_id == &"":
|
||||
result.add_error(
|
||||
"A room binding has no cabinet_id."
|
||||
)
|
||||
continue
|
||||
|
||||
if action_id == &"":
|
||||
result.add_error(
|
||||
"A room binding for cabinet '%s' has no hardware action."
|
||||
% cabinet_id
|
||||
)
|
||||
continue
|
||||
|
||||
if device_id == &"":
|
||||
result.add_error(
|
||||
"A room binding for cabinet '%s' has no device_id."
|
||||
% cabinet_id
|
||||
)
|
||||
continue
|
||||
|
||||
if room.get_cabinet_placement(
|
||||
cabinet_id
|
||||
) == null:
|
||||
|
||||
result.add_error(
|
||||
(
|
||||
"Binding references cabinet '%s', "
|
||||
+ "but that cabinet is not placed in the room."
|
||||
)
|
||||
% cabinet_id
|
||||
)
|
||||
|
||||
if room.get_device(
|
||||
device_id
|
||||
) == null:
|
||||
|
||||
result.add_error(
|
||||
"Binding references unknown room device '%s'."
|
||||
% device_id
|
||||
)
|
||||
|
||||
var cabinet: CabinetProfile = null
|
||||
|
||||
if database != null:
|
||||
cabinet = database.get_cabinet(
|
||||
cabinet_id
|
||||
)
|
||||
|
||||
if cabinet == null:
|
||||
result.add_error(
|
||||
"Binding references unknown cabinet '%s'."
|
||||
% cabinet_id
|
||||
)
|
||||
else:
|
||||
if cabinet.hardware_config == null:
|
||||
result.add_error(
|
||||
"Cabinet '%s' has no HardwareConfig."
|
||||
% cabinet_id
|
||||
)
|
||||
elif not cabinet.hardware_config.has_action(
|
||||
action_id
|
||||
):
|
||||
result.add_error(
|
||||
(
|
||||
"Room binding references unknown hardware action '%s' "
|
||||
+ "on cabinet '%s'."
|
||||
)
|
||||
% [
|
||||
action_id,
|
||||
cabinet_id
|
||||
]
|
||||
)
|
||||
|
||||
var binding_key := (
|
||||
"%s::%s::%s"
|
||||
% [
|
||||
cabinet_id,
|
||||
action_id,
|
||||
device_id
|
||||
]
|
||||
)
|
||||
|
||||
if seen_bindings.has(
|
||||
binding_key
|
||||
):
|
||||
result.add_warning(
|
||||
"Duplicate room binding '%s'."
|
||||
% binding_key
|
||||
)
|
||||
else:
|
||||
seen_bindings[
|
||||
binding_key
|
||||
] = true
|
||||
@@ -0,0 +1 @@
|
||||
uid://bbu1ejey4q6vx
|
||||
@@ -0,0 +1,211 @@
|
||||
class_name ScenarioValidator
|
||||
|
||||
|
||||
static func validate(
|
||||
scenario: ScenarioDefinition
|
||||
) -> ValidationResult:
|
||||
|
||||
var result := ValidationResult.new()
|
||||
|
||||
if scenario == null:
|
||||
result.add_error("Scenario is null.")
|
||||
return result
|
||||
|
||||
if scenario.assignments.is_empty():
|
||||
result.add_error(
|
||||
"Scenario has no cabinet assignments."
|
||||
)
|
||||
return result
|
||||
|
||||
var seen_cabinets: Dictionary = {}
|
||||
|
||||
for assignment in scenario.assignments:
|
||||
_validate_assignment(
|
||||
assignment,
|
||||
seen_cabinets,
|
||||
result
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
static func _validate_assignment(
|
||||
assignment: ScenarioAssignment,
|
||||
seen_cabinets: Dictionary,
|
||||
result: ValidationResult
|
||||
) -> void:
|
||||
|
||||
if assignment == null:
|
||||
result.add_error(
|
||||
"Scenario contains a null assignment."
|
||||
)
|
||||
return
|
||||
|
||||
if assignment.cabinet == null:
|
||||
result.add_error(
|
||||
"An assignment has no cabinet."
|
||||
)
|
||||
return
|
||||
|
||||
var cabinet := assignment.cabinet
|
||||
var cabinet_id := cabinet.cabinet_id
|
||||
|
||||
if cabinet_id == &"":
|
||||
result.add_error(
|
||||
"A cabinet has no cabinet_id."
|
||||
)
|
||||
|
||||
if seen_cabinets.has(cabinet_id):
|
||||
result.add_error(
|
||||
"Cabinet '%s' appears more than once."
|
||||
% cabinet_id
|
||||
)
|
||||
else:
|
||||
seen_cabinets[cabinet_id] = true
|
||||
|
||||
if assignment.game == null:
|
||||
result.add_error(
|
||||
"Cabinet '%s' has no game assigned."
|
||||
% cabinet_id
|
||||
)
|
||||
return
|
||||
|
||||
var game := assignment.game
|
||||
|
||||
if game.game_id == &"":
|
||||
result.add_error(
|
||||
"Cabinet '%s' has a game with no game_id."
|
||||
% cabinet_id
|
||||
)
|
||||
|
||||
if game.scene == null:
|
||||
result.add_error(
|
||||
"Game '%s' has no scene assigned."
|
||||
% game.game_id
|
||||
)
|
||||
|
||||
if not game.can_run_on(cabinet):
|
||||
result.add_error(
|
||||
"Game '%s' cannot run on cabinet '%s'."
|
||||
% [
|
||||
game.game_id,
|
||||
cabinet_id
|
||||
]
|
||||
)
|
||||
|
||||
if cabinet.hardware_config == null:
|
||||
result.add_error(
|
||||
"Cabinet '%s' has no HardwareConfig."
|
||||
% cabinet_id
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
_validate_event_mappings(
|
||||
assignment,
|
||||
result
|
||||
)
|
||||
|
||||
|
||||
static func _validate_event_mappings(
|
||||
assignment: ScenarioAssignment,
|
||||
result: ValidationResult
|
||||
) -> void:
|
||||
|
||||
var cabinet := assignment.cabinet
|
||||
var cabinet_id := cabinet.cabinet_id
|
||||
var hardware := cabinet.hardware_config
|
||||
var game:= assignment.game
|
||||
|
||||
_validate_game_events(
|
||||
game,
|
||||
result
|
||||
)
|
||||
|
||||
for mapping in assignment.event_mappings:
|
||||
|
||||
if mapping == null:
|
||||
result.add_warning(
|
||||
"Cabinet '%s' contains a null event mapping."
|
||||
% cabinet_id
|
||||
)
|
||||
continue
|
||||
|
||||
if mapping.game_event == &"":
|
||||
result.add_warning(
|
||||
"Cabinet '%s' has a mapping with no game event."
|
||||
% cabinet_id
|
||||
)
|
||||
else:
|
||||
if not game.has_event(
|
||||
mapping.game_event
|
||||
):
|
||||
result.add_error(
|
||||
(
|
||||
"Game '%s' does not define event '%s' "
|
||||
+ "used by cabinet '%s'."
|
||||
)
|
||||
% [
|
||||
game.game_id,
|
||||
mapping.game_event,
|
||||
cabinet_id
|
||||
]
|
||||
)
|
||||
|
||||
if mapping.hardware_actions.is_empty():
|
||||
result.add_warning(
|
||||
"Event '%s' on cabinet '%s' has no actions."
|
||||
% [
|
||||
mapping.game_event,
|
||||
cabinet_id
|
||||
]
|
||||
)
|
||||
|
||||
for action_id in mapping.hardware_actions:
|
||||
|
||||
if not hardware.has_action(action_id):
|
||||
result.add_error(
|
||||
"Cabinet '%s': event '%s' references unknown hardware action '%s'."
|
||||
% [
|
||||
cabinet_id,
|
||||
mapping.game_event,
|
||||
action_id
|
||||
]
|
||||
)
|
||||
|
||||
static func _validate_game_events(
|
||||
game: GameDefinition,
|
||||
result: ValidationResult
|
||||
) -> void:
|
||||
|
||||
var seen_events: Dictionary = {}
|
||||
|
||||
for event in game.events:
|
||||
if event == null:
|
||||
result.add_warning(
|
||||
"Game '%s' contains a null event definition."
|
||||
% game.game_id
|
||||
)
|
||||
continue
|
||||
|
||||
if event.event_id == &"":
|
||||
result.add_error(
|
||||
"Game '%s' contains an event with no event_id."
|
||||
% game.game_id
|
||||
)
|
||||
continue
|
||||
|
||||
if seen_events.has(
|
||||
event.event_id
|
||||
):
|
||||
result.add_error(
|
||||
"Game '%s' defines event '%s' more than once."
|
||||
% [
|
||||
game.game_id,
|
||||
event.event_id
|
||||
]
|
||||
)
|
||||
else:
|
||||
seen_events[
|
||||
event.event_id
|
||||
] = true
|
||||
@@ -0,0 +1 @@
|
||||
uid://dhcgaiviprvg8
|
||||
@@ -0,0 +1,53 @@
|
||||
@tool
|
||||
class_name ValidationResult
|
||||
extends RefCounted
|
||||
|
||||
|
||||
var errors: Array[String] = []
|
||||
var warnings: Array[String] = []
|
||||
|
||||
|
||||
func add_error(message: String) -> void:
|
||||
errors.append(message)
|
||||
|
||||
|
||||
func add_warning(message: String) -> void:
|
||||
warnings.append(message)
|
||||
|
||||
|
||||
func is_valid() -> bool:
|
||||
return errors.is_empty()
|
||||
|
||||
|
||||
func has_warnings() -> bool:
|
||||
return not warnings.is_empty()
|
||||
|
||||
|
||||
func get_summary() -> String:
|
||||
if not is_valid():
|
||||
return "%d error(s), %d warning(s)" % [
|
||||
errors.size(),
|
||||
warnings.size()
|
||||
]
|
||||
|
||||
if has_warnings():
|
||||
return "Valid with %d warning(s)" % warnings.size()
|
||||
|
||||
return "Valid"
|
||||
|
||||
func merge(
|
||||
other: ValidationResult
|
||||
) -> void:
|
||||
|
||||
if other == null:
|
||||
return
|
||||
|
||||
errors.append_array(
|
||||
other.errors
|
||||
)
|
||||
|
||||
warnings.append_array(
|
||||
other.warnings
|
||||
)
|
||||
|
||||
var result := ValidationResult.new()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bey4rq671pjlp
|
||||
Reference in new issue
Block a user