diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 10aa1718dda..26b1d2d44aa 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -726,3 +726,7 @@ #define COMSIG_XENO_TURF_CLICK_CTRL "xeno_turf_click_alt" ///from monkey CtrlClickOn(): (/mob) #define COMSIG_XENO_MONKEY_CLICK_CTRL "xeno_monkey_click_ctrl" + +///SSalarm signals +#define COMSIG_TRIGGERED_ALARM "ssalarm_triggered" +#define COMSIG_CANCELLED_ALARM "ssalarm_cancelled" diff --git a/code/controllers/subsystem/alarm.dm b/code/controllers/subsystem/alarm.dm new file mode 100644 index 00000000000..9e310909120 --- /dev/null +++ b/code/controllers/subsystem/alarm.dm @@ -0,0 +1,32 @@ +SUBSYSTEM_DEF(alarm) + name = "Alarm" + flags = SS_NO_INIT | SS_NO_FIRE + var/alarms = list("Motion" = list(), "Fire" = list(), "Atmosphere" = list(), "Power" = list(), "Camera" = list(), "Burglar" = list()) + +/datum/controller/subsystem/alarm/proc/triggerAlarm(class, area/A, list/O, obj/alarmsource) + var/list/L = alarms[class] + for(var/I in L) + if(I == A.name) + var/list/alarm = L[I] + var/list/sources = alarm[3] + if(!(alarmsource.UID() in sources)) + sources += alarmsource.UID() + return TRUE + L[A.name] = list(get_area_name(A, TRUE), O, list(alarmsource.UID())) + SEND_SIGNAL(SSalarm, COMSIG_TRIGGERED_ALARM, class, A, O, alarmsource) + return TRUE + +/datum/controller/subsystem/alarm/proc/cancelAlarm(class, area/A, obj/origin) + var/list/L = alarms[class] + var/cleared = FALSE + for(var/I in L) + if(I == A.name) + var/list/alarm = L[I] + var/list/srcs = alarm[3] + if(origin.UID() in srcs) + srcs -= origin.UID() + if(srcs.len == 0) + cleared = TRUE + L -= I + + SEND_SIGNAL(SSalarm, COMSIG_CANCELLED_ALARM, class, A, origin, cleared) diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index ef9cce290c2..9e3dd435a28 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -176,26 +176,10 @@ else C.network |= "Power Alarms" - for(var/item in GLOB.silicon_mob_list) - var/mob/living/silicon/aiPlayer = item - if(state == 1) - aiPlayer.cancelAlarm("Power", src, source) - else - aiPlayer.triggerAlarm("Power", src, cameras, source) - - for(var/item in GLOB.alert_consoles) - var/obj/machinery/computer/station_alert/a = item - if(state == 1) - a.cancelAlarm("Power", src, source) - else - a.triggerAlarm("Power", src, cameras, source) - - /* for(var/item in GLOB.alarmdisplay) //TO-DO:TGUI--Uncomment Modular computers - var/datum/computer_file/program/alarm_monitor/p = item - if(state == 1) - p.cancelAlarm("Power", src, source) - else - p.triggerAlarm("Power", src, cameras, source) */ + if(state == 1) + SSalarm.cancelAlarm("Power", src, source) + else + SSalarm.triggerAlarm("Power", src, cameras, source) /** * Generate an atmospheric alert for this area @@ -211,30 +195,16 @@ if(!QDELETED(C)) C.network |= "Atmosphere Alarms" - for(var/item in GLOB.silicon_mob_list) - var/mob/living/silicon/aiPlayer = item - aiPlayer.triggerAlarm("Atmosphere", src, cameras, source) - for(var/item in GLOB.alert_consoles) - var/obj/machinery/computer/station_alert/a = item - a.triggerAlarm("Atmosphere", src, cameras, source) - /* for(var/item in GLOB.alarmdisplay) //TO-DO:TGUI--Uncomment Modular computers - var/datum/computer_file/program/alarm_monitor/p = item - p.triggerAlarm("Atmosphere", src, cameras, source) */ + + SSalarm.triggerAlarm("Atmosphere", src, cameras, source) else if(atmosalm == ATMOS_ALARM_DANGER) for(var/thing in cameras) var/obj/machinery/camera/C = locateUID(thing) if(!QDELETED(C)) C.network -= "Atmosphere Alarms" - for(var/item in GLOB.silicon_mob_list) - var/mob/living/silicon/aiPlayer = item - aiPlayer.cancelAlarm("Atmosphere", src, source) - for(var/item in GLOB.alert_consoles) - var/obj/machinery/computer/station_alert/a = item - a.cancelAlarm("Atmosphere", src, source) - /* for(var/item in GLOB.alarmdisplay) //TO-DO:TGUI--Uncomment Modular computers - var/datum/computer_file/program/alarm_monitor/p = item - p.cancelAlarm("Atmosphere", src, source) */ + + SSalarm.cancelAlarm("Atmosphere", src, source) atmosalm = danger_level return TRUE @@ -284,15 +254,7 @@ if(!QDELETED(C)) C.network |= "Fire Alarms" - for(var/item in GLOB.alert_consoles) - var/obj/machinery/computer/station_alert/a = item - a.triggerAlarm("Fire", src, cameras, source) - for(var/item in GLOB.silicon_mob_list) - var/mob/living/silicon/aiPlayer = item - aiPlayer.triggerAlarm("Fire", src, cameras, source) -/* for(var/item in GLOB.alarmdisplay) //TO-DO:TGUI--Uncomment Modular computers - var/datum/computer_file/program/alarm_monitor/p = item - p.triggerAlarm("Fire", src, cameras, source) */ + SSalarm.triggerAlarm("Fire", src, cameras, source) START_PROCESSING(SSobj, src) @@ -317,15 +279,7 @@ if(!QDELETED(C)) C.network -= "Fire Alarms" - for(var/item in GLOB.silicon_mob_list) - var/mob/living/silicon/aiPlayer = item - aiPlayer.cancelAlarm("Fire", src, source) - for(var/item in GLOB.alert_consoles) - var/obj/machinery/computer/station_alert/a = item - a.cancelAlarm("Fire", src, source) -/* for(var/item in GLOB.alarmdisplay) //TO-DO:TGUI--Uncomment Modular computers - var/datum/computer_file/program/alarm_monitor/p = item - p.cancelAlarm("Fire", src, source) */ + SSalarm.cancelAlarm("Fire", src, source) STOP_PROCESSING(SSobj, src) @@ -386,11 +340,9 @@ for(var/obj/machinery/door/DOOR in src) close_and_lock_door(DOOR) - for(var/i in GLOB.silicon_mob_list) - var/mob/living/silicon/SILICON = i - if(SILICON.triggerAlarm("Burglar", src, cameras, trigger)) - //Cancel silicon alert after 1 minute - addtimer(CALLBACK(SILICON, /mob/living/silicon.proc/cancelAlarm, "Burglar", src, trigger), 600) + if(SSalarm.triggerAlarm("Burglar", src, cameras, trigger)) + //Cancel silicon alert after 1 minute + addtimer(CALLBACK(SSalarm, /datum/controller/subsystem/alarm.proc/cancelAlarm, "Burglar", src, trigger), 600) /** * Trigger the fire alarm visual affects in an area diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 62db9341e5c..6041cdcf119 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -332,13 +332,11 @@ /obj/machinery/camera/proc/triggerCameraAlarm() alarm_on = TRUE - for(var/mob/living/silicon/S in GLOB.silicon_mob_list) - S.triggerAlarm("Camera", get_area(src), list(UID()), src) + SSalarm.triggerAlarm("Camera", get_area(src), list(UID()), src) /obj/machinery/camera/proc/cancelCameraAlarm() alarm_on = FALSE - for(var/mob/living/silicon/S in GLOB.silicon_mob_list) - S.cancelAlarm("Camera", get_area(src), src) + SSalarm.cancelAlarm("Camera", get_area(src), src) /obj/machinery/camera/proc/can_use() if(!status) diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm index 47e98006e2a..9615d4afcce 100644 --- a/code/game/machinery/camera/motion.dm +++ b/code/game/machinery/camera/motion.dm @@ -44,20 +44,17 @@ /obj/machinery/camera/proc/cancelAlarm() if(detectTime == -1) - for(var/i in GLOB.silicon_mob_list) - var/mob/living/silicon/aiPlayer = i - if(status) - aiPlayer.cancelAlarm("Motion", get_area(src), src) + if(status) + SSalarm.cancelAlarm("Motion", get_area(src), src) detectTime = 0 return TRUE /obj/machinery/camera/proc/triggerAlarm() if(!detectTime) return FALSE - for(var/mob/living/silicon/aiPlayer in GLOB.player_list) - if(status) - aiPlayer.triggerAlarm("Motion", get_area(src), list(UID()), src) - visible_message("A red light flashes on the [src]!") + if(status) + SSalarm.triggerAlarm("Motion", get_area(src), list(UID()), src) + visible_message("A red light flashes on the [src]!") detectTime = -1 return TRUE diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index 679fd0c63b3..244a253d3af 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -8,11 +8,13 @@ circuit = /obj/item/circuitboard/stationalert_engineering var/ui_x = 325 var/ui_y = 500 - var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) + var/list/alarms_listend_for = list("Fire", "Atmosphere", "Power") /obj/machinery/computer/station_alert/Initialize(mapload) . = ..() GLOB.alert_consoles += src + RegisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM, .proc/alarm_triggered) + RegisterSignal(SSalarm, COMSIG_CANCELLED_ALARM, .proc/alarm_cancelled) /obj/machinery/computer/station_alert/Destroy() GLOB.alert_consoles -= src @@ -40,53 +42,39 @@ var/list/data = list() data["alarms"] = list() - for(var/class in alarms) + for(var/class in SSalarm.alarms) + if(!(class in alarms_listend_for)) + continue data["alarms"][class] = list() - for(var/area in alarms[class]) + for(var/area in SSalarm.alarms[class]) data["alarms"][class] += area return data -/obj/machinery/computer/station_alert/proc/triggerAlarm(class, area/A, list/O, obj/alarmsource) +/obj/machinery/computer/station_alert/proc/alarm_triggered(src, class, area/A, list/O, obj/alarmsource) + if(!(class in alarms_listend_for)) + return if(alarmsource.z != z) return if(stat & (BROKEN)) return - - var/list/L = alarms[class] - for(var/I in L) - if(I == A.name) - var/list/alarm = L[I] - var/list/sources = alarm[3] - if(!(alarmsource.UID() in sources)) - sources += alarmsource.UID() - return TRUE - L[A.name] = list(get_area_name(A, TRUE), O, list(alarmsource.UID())) update_icon() - return TRUE - -/obj/machinery/computer/station_alert/proc/cancelAlarm(class, area/A, obj/origin) +/obj/machinery/computer/station_alert/proc/alarm_cancelled(src, class, area/A, obj/origin, cleared) + if(!(class in alarms_listend_for)) + return + if(origin.z != z) + return if(stat & (BROKEN)) return - var/list/L = alarms[class] - var/cleared = FALSE - for(var/I in L) - if(I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if(origin.UID() in srcs) - srcs -= origin.UID() - if(srcs.len == 0) - cleared = TRUE - L -= I update_icon() - return !cleared /obj/machinery/computer/station_alert/update_icon() var/active_alarms = FALSE - for(var/cat in alarms) - var/list/L = alarms[cat] + for(var/cat in SSalarm.alarms) + if(!(cat in alarms_listend_for)) + continue + var/list/L = SSalarm.alarms[cat] if(L.len) active_alarms = TRUE if(active_alarms) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 2fc14cb9f7c..c922b2f9148 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -50,7 +50,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list( var/list/connected_robots = list() var/aiRestorePowerRoutine = 0 //var/list/laws = list() - var/alarms = list("Motion" = list(), "Fire" = list(), "Atmosphere" = list(), "Power" = list(), "Camera" = list(), "Burglar" =list()) + alarms_listend_for = list("Motion", "Fire", "Atmosphere", "Power", "Camera", "Burglar") var/viewalerts = 0 var/icon/holo_icon//Default is assigned when AI is created. var/obj/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye. @@ -244,9 +244,11 @@ GLOBAL_LIST_INIT(ai_verbs_default, list( /mob/living/silicon/ai/proc/ai_alerts() var/dat = "Current Station Alerts\n" dat += "Close

" - for(var/cat in alarms) + for(var/cat in SSalarm.alarms) + if(!(cat in alarms_listend_for)) + continue dat += text("[]
\n", cat) - var/list/L = alarms[cat] + var/list/L = SSalarm.alarms[cat] if(L.len) for(var/alarm in L) var/list/alm = L[alarm] @@ -818,20 +820,13 @@ GLOBAL_LIST_INIT(ai_verbs_default, list( Bot.call_bot(src, waypoint) -/mob/living/silicon/ai/triggerAlarm(class, area/A, list/O, obj/alarmsource) +/mob/living/silicon/ai/alarm_triggered(src, class, area/A, list/O, obj/alarmsource) + if(!(class in alarms_listend_for)) + return if(alarmsource.z != z) return if(stat == DEAD) return TRUE - var/list/L = alarms[class] - for(var/I in L) - if(I == A.name) - var/list/alarm = L[I] - var/list/sources = alarm[3] - if(!(alarmsource.UID() in sources)) - sources += alarmsource.UID() - return TRUE - L[A.name] = list(get_area_name(A, TRUE), O, list(alarmsource.UID())) if(O) var/obj/machinery/camera/C = locateUID(O[1]) if(O.len == 1 && !QDELETED(C) && C.can_use()) @@ -851,25 +846,16 @@ GLOBAL_LIST_INIT(ai_verbs_default, list( queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class) if(viewalerts) ai_alerts() - return TRUE -/mob/living/silicon/ai/cancelAlarm(class, area/A, obj/origin) - var/list/L = alarms[class] - var/cleared = FALSE - for(var/I in L) - if(I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if(origin.UID() in srcs) - srcs -= origin.UID() - if(srcs.len == 0) - cleared = TRUE - L -= I +/mob/living/silicon/ai/alarm_cancelled(src, class, area/A, obj/origin, cleared) if(cleared) + if(!(class in alarms_listend_for)) + return + if(origin.z != z) + return queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0) if(viewalerts) ai_alerts() - return !cleared /mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index eefc889dc77..6bd45688cce 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -72,7 +72,6 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( var/list/req_access var/ident = 0 //var/list/laws = list() - var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list()) var/viewalerts = 0 var/modtype = "Default" var/lower_mod = 0 @@ -551,9 +550,11 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( /mob/living/silicon/robot/proc/robot_alerts() var/dat = "" - for(var/cat in alarms) + for(var/cat in SSalarm.alarms) + if(!(cat in alarms_listend_for)) + continue dat += text("[cat]
\n") - var/list/L = alarms[cat] + var/list/L = SSalarm.alarms[cat] if(L.len) for(var/alarm in L) var/list/alm = L[alarm] @@ -630,38 +631,22 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( /mob/living/silicon/robot/InCritical() return low_power_mode -/mob/living/silicon/robot/triggerAlarm(class, area/A, list/O, obj/alarmsource) +/mob/living/silicon/robot/alarm_triggered(src, class, area/A, list/O, obj/alarmsource) + if(!(class in alarms_listend_for)) + return if(alarmsource.z != z) return if(stat == DEAD) - return TRUE - var/list/L = alarms[class] - for(var/I in L) - if(I == A.name) - var/list/alarm = L[I] - var/list/sources = alarm[3] - if(!(alarmsource.UID() in sources)) - sources += alarmsource.UID() - return TRUE - L[A.name] = list(get_area_name(A, TRUE), O, list(alarmsource.UID())) + return queueAlarm(text("--- [class] alarm detected in [A.name]!"), class) - return TRUE -/mob/living/silicon/robot/cancelAlarm(class, area/A, obj/origin) - var/list/L = alarms[class] - var/cleared = 0 - for(var/I in L) - if(I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if(origin.UID() in srcs) - srcs -= origin.UID() - if(srcs.len == 0) - cleared = 1 - L -= I +/mob/living/silicon/robot/alarm_cancelled(src, class, area/A, obj/origin, cleared) if(cleared) + if(!(class in alarms_listend_for)) + return + if(origin.z != z) + return queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0) - return !cleared /mob/living/silicon/robot/ex_act(severity) switch(severity) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index af8f192dda8..48be46c89db 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -13,6 +13,7 @@ var/list/alarms_to_clear = list() var/list/alarm_types_show = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) + var/list/alarms_listend_for = list("Motion", "Fire", "Atmosphere", "Power", "Camera") //var/list/hud_list[10] var/list/speech_synthesizer_langs = list() //which languages can be vocalized by the speech synthesizer var/designation = "" @@ -44,6 +45,8 @@ diag_hud_set_health() add_language("Galactic Common") init_subsystems() + RegisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM, .proc/alarm_triggered) + RegisterSignal(SSalarm, COMSIG_CANCELLED_ALARM, .proc/alarm_cancelled) /mob/living/silicon/med_hud_set_health() return //we use a different hud @@ -55,10 +58,10 @@ GLOB.silicon_mob_list -= src return ..() -/mob/living/silicon/proc/cancelAlarm() +/mob/living/silicon/proc/alarm_triggered(src, class, area/A, list/O, obj/alarmsource) return -/mob/living/silicon/proc/triggerAlarm() +/mob/living/silicon/proc/alarm_cancelled(src, class, area/A, obj/origin, cleared) return /mob/living/silicon/proc/queueAlarm(message, type, incoming = 1) diff --git a/code/modules/modular_computers/file_system/programs/engineering/alarm.dm b/code/modules/modular_computers/file_system/programs/engineering/alarm.dm index f0015305360..fe7e107781c 100644 --- a/code/modules/modular_computers/file_system/programs/engineering/alarm.dm +++ b/code/modules/modular_computers/file_system/programs/engineering/alarm.dm @@ -10,9 +10,8 @@ var/tgui_id = "NtosStationAlertConsole" var/ui_x = 315 var/ui_y = 500 - var/has_alert = 0 - var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) + var/list/alarms_listend_for = list("Fire", "Atmosphere", "Power") /datum/computer_file/program/alarm_monitor/process_tick() ..() @@ -32,60 +31,36 @@ var/list/data = get_header_data() data["alarms"] = list() - for(var/class in alarms) + for(var/class in SSalarm.alarms) + if(!(class in alarms_listend_for)) + continue data["alarms"][class] = list() for(var/area in alarms[class]) data["alarms"][class] += area return data -/datum/computer_file/program/alarm_monitor/proc/triggerAlarm(class, area/A, list/O, obj/alarmsource) - if(is_station_level(source.z)) +/datum/computer_file/program/alarm_monitor/proc/alarm_triggered(src, class, area/A, list/O, obj/alarmsource) + if(is_station_level(alarmsource.z)) if(!(A.type in GLOB.the_station_areas)) return - else if(!is_mining_level(source.z) || istype(A, /area/ruin)) + else if(!is_mining_level(alarmsource.z) || istype(A, /area/ruin)) return - - var/list/L = alarms[class] - for(var/I in L) - if(I == A.name) - var/list/alarm = L[I] - var/list/sources = alarm[3] - if(!(alarmsource.UID() in sources)) - sources += alarmsource.UID() - return TRUE - L[A.name] = list(get_area_name(A, TRUE), O, list(alarmsource.UID())) update_alarm_display() - return TRUE - - -/datum/computer_file/program/alarm_monitor/proc/cancelAlarm(class, area/A, obj/origin) - var/list/L = alarms[class] - var/cleared = 0 - var/arealevelalarm = FALSE // set to TRUE for alarms that set/clear whole areas - if(class == "Fire") - arealevelalarm = TRUE - for(var/I in L) - if(I == A.name) - if(!arealevelalarm) // the traditional behaviour - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if(origin.UID() in srcs) - srcs -= origin.UID() - if(srcs.len == 0) - cleared = 1 - L -= I - else - L -= I // wipe the instances entirely - cleared = 1 - +/datum/computer_file/program/alarm_monitor/proc/alarm_cancelled(src, class, area/A, obj/origin, cleared) + if(is_station_level(origin.z)) + if(!(A.type in GLOB.the_station_areas)) + return + else if(!is_mining_level(origin.z) || istype(A, /area/ruin)) + return update_alarm_display() - return !cleared /datum/computer_file/program/alarm_monitor/proc/update_alarm_display() has_alert = FALSE for(var/cat in alarms) + if(!(cat in alarms_listend_for)) + continue var/list/L = alarms[cat] if(L.len) has_alert = TRUE @@ -93,7 +68,11 @@ /datum/computer_file/program/alarm_monitor/run_program(mob/user) . = ..(user) GLOB.alarmdisplay += src + RegisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM, .proc/alarm_triggered) + RegisterSignal(SSalarm, COMSIG_CANCELLED_ALARM, .proc/alarm_cancelled) /datum/computer_file/program/alarm_monitor/kill_program(forced = FALSE) GLOB.alarmdisplay -= src + UnregisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM) + UnregisterSignal(SSalarm, COMSIG_CANCELLED_ALARM) ..() diff --git a/paradise.dme b/paradise.dme index b173e9e03b8..ee45944cb02 100644 --- a/paradise.dme +++ b/paradise.dme @@ -215,6 +215,7 @@ #include "code\controllers\subsystem\acid.dm" #include "code\controllers\subsystem\afk.dm" #include "code\controllers\subsystem\air.dm" +#include "code\controllers\subsystem\alarm.dm" #include "code\controllers\subsystem\assets.dm" #include "code\controllers\subsystem\atoms.dm" #include "code\controllers\subsystem\changelog.dm"