class_name ScenarioEditSession extends RefCounted signal scenario_changed( scenario: ScenarioDefinition ) signal dirty_changed( is_dirty: bool ) signal saved signal reverted var source_scenario: ScenarioDefinition var working_scenario: ScenarioDefinition var is_dirty: bool = false var saved_state: Dictionary = {} func begin( scenario: ScenarioDefinition ) -> bool: if scenario == null: push_error( "ScenarioEditSession cannot begin with a null scenario." ) return false source_scenario = scenario _create_working_copy() return true func mark_dirty() -> void: if working_scenario == null: _set_dirty( false ) return var current_state := ( _capture_scenario_state( working_scenario ) ) _set_dirty( current_state != saved_state ) func save() -> bool: if source_scenario == null: push_error( "ScenarioEditSession has no source scenario." ) return false if working_scenario == null: push_error( "ScenarioEditSession has no working scenario." ) return false _apply_working_copy_to_source() var path := source_scenario.resource_path if path.is_empty(): push_error( "Source ScenarioDefinition has no resource path." ) return false var error := ResourceSaver.save( source_scenario, path ) if error != OK: push_error( "Failed to save ScenarioDefinition. Error: %s" % error ) return false saved_state = ( _capture_scenario_state( working_scenario ) ) _set_dirty( false ) saved.emit() return true func revert() -> void: if source_scenario == null: return _create_working_copy() reverted.emit() print( "[SCENARIO EDITOR] Reverted unsaved changes." ) func _create_working_copy() -> void: var copy := ScenarioDefinition.new() copy.scenario_id = source_scenario.scenario_id copy.display_name = source_scenario.display_name copy.version = source_scenario.version for source_assignment in source_scenario.assignments: if source_assignment == null: continue var assignment := ( ScenarioAssignment.new() ) # # Preserve references to the actual # game/cabinet definitions. # assignment.cabinet = ( source_assignment.cabinet ) assignment.game = ( source_assignment.game ) assignment.variant = ( source_assignment.variant ) assignment.activate_after_win = source_assignment.activate_after_win assignment.game_config = source_assignment.game_config.duplicate(true) assignment.event_mappings = ( _duplicate_mappings( source_assignment.event_mappings ) ) copy.assignments.append( assignment ) working_scenario = copy # # This working copy is now our clean, # saved baseline. # saved_state = ( _capture_scenario_state( working_scenario ) ) _set_dirty( false ) scenario_changed.emit( working_scenario ) func _duplicate_mappings( source_mappings: Array[ScenarioEventMapping] ) -> Array[ScenarioEventMapping]: var result: Array[ScenarioEventMapping] = [] for source_mapping in source_mappings: if source_mapping == null: continue var mapping := ScenarioEventMapping.new() mapping.game_event = ( source_mapping.game_event ) mapping.hardware_actions = ( source_mapping.hardware_actions.duplicate() ) result.append(mapping) return result func _apply_working_copy_to_source() -> void: for working_assignment in working_scenario.assignments: if working_assignment == null: continue if working_assignment.cabinet == null: continue var source_assignment := ( source_scenario.get_assignment_for_cabinet( working_assignment.cabinet.cabinet_id ) ) if source_assignment == null: continue source_assignment.event_mappings = ( _duplicate_mappings( working_assignment.event_mappings ) ) func _capture_scenario_state( scenario: ScenarioDefinition ) -> Dictionary: if scenario == null: return {} var state := { "scenario_id": scenario.scenario_id, "display_name": scenario.display_name, "version": scenario.version, "assignments": [], } for assignment in scenario.assignments: if assignment == null: state["assignments"].append( null ) continue var assignment_state := { "cabinet_id": StringName(), "game_id": StringName(), "variant": assignment.variant, "activate_after_win": assignment.activate_after_win, "game_config": assignment.game_config.duplicate(true), "event_mappings": [], } if assignment.cabinet != null: assignment_state["cabinet_id"] = ( assignment.cabinet.cabinet_id ) if assignment.game != null: assignment_state["game_id"] = ( assignment.game.game_id ) for mapping in assignment.event_mappings: if mapping == null: assignment_state[ "event_mappings" ].append( null ) continue var mapping_state := { "game_event": mapping.game_event, "hardware_actions": [], } for action_id in mapping.hardware_actions: mapping_state[ "hardware_actions" ].append( action_id ) assignment_state[ "event_mappings" ].append( mapping_state ) state["assignments"].append( assignment_state ) return state func _set_dirty( value: bool ) -> void: if is_dirty == value: return is_dirty = value dirty_changed.emit( is_dirty )