Replays 2.0 (#16211)

* C++ demo

* linuxy shit

* Working linuxy shit

* demo writer is necessary I guess

* use the working one

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>
This commit is contained in:
monster860
2022-11-21 18:11:17 -05:00
committed by GitHub
parent a8705b11a3
commit 8de16779d6
30 changed files with 137 additions and 447 deletions

View File

@@ -213,8 +213,6 @@
}\ }\
}\ }\
A.flags_1 &= ~OVERLAY_QUEUED_1;\ A.flags_1 &= ~OVERLAY_QUEUED_1;\
if(isturf(A)){SSdemo.mark_turf(A);}\
if(isobj(A) || ismob(A)){SSdemo.mark_dirty(A);}\
} while (FALSE) } while (FALSE)

View File

@@ -537,6 +537,21 @@ GLOBAL_LIST_EMPTY(species_list)
to_chat(M, rendered_message, avoid_highlighting = speaker_key == M.key) to_chat(M, rendered_message, avoid_highlighting = speaker_key == M.key)
else else
to_chat(M, message, avoid_highlighting = speaker_key == M.key) to_chat(M, message, avoid_highlighting = speaker_key == M.key)
var/demo_message = message
if(follow_target)
var/F
if(turf_target)
F = FOLLOW_OR_TURF_LINK(SSdemo, follow_target, turf_target)
else
F = FOLLOW_LINK(SSdemo, follow_target)
demo_message = "[F] [message]"
else if(turf_target)
var/turf_link = TURF_LINK(SSdemo, turf_target)
demo_message = "[turf_link] [message]"
to_chat(SSdemo, demo_message)
//Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value. //Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value.
/proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN) /proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN)

View File

@@ -57,6 +57,7 @@
#endif #endif
#define EXTOOLS (world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so") #define EXTOOLS (world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so")
#define DEMO_WRITER (world.system_type == MS_WINDOWS ? "demo-writer.dll" : "libdemo-writer.so")
//If you update these values, update the message in the #error //If you update these values, update the message in the #error
#define MAX_BYOND_MAJOR 514 #define MAX_BYOND_MAJOR 514

View File

@@ -16,6 +16,7 @@ GLOBAL_LIST(end_titles)
for(var/client/C in GLOB.clients) for(var/client/C in GLOB.clients)
if(C.prefs.show_credits) if(C.prefs.show_credits)
C.screen += new /atom/movable/screen/credit/title_card(null, null, SSticker.mode.title_icon) C.screen += new /atom/movable/screen/credit/title_card(null, null, SSticker.mode.title_icon)
SSdemo.flush()
sleep(CREDIT_SPAWN_SPEED * 3) sleep(CREDIT_SPAWN_SPEED * 3)
for(var/i in 1 to GLOB.end_titles.len) for(var/i in 1 to GLOB.end_titles.len)
var/C = GLOB.end_titles[i] var/C = GLOB.end_titles[i]
@@ -23,6 +24,7 @@ GLOBAL_LIST(end_titles)
continue continue
create_credit(C) create_credit(C)
SSdemo.flush()
sleep(CREDIT_SPAWN_SPEED) sleep(CREDIT_SPAWN_SPEED)

View File

@@ -5,11 +5,6 @@
var/resolved = target.attackby(src, user, params) var/resolved = target.attackby(src, user, params)
if(!resolved && target && !QDELETED(src)) if(!resolved && target && !QDELETED(src))
afterattack(target, user, 1, params) // 1: clicking something Adjacent afterattack(target, user, 1, params) // 1: clicking something Adjacent
SSdemo.mark_dirty(src)
if(isturf(target))
SSdemo.mark_turf(target)
else
SSdemo.mark_dirty(target)
//Checks if the item can work as a tool, calling the appropriate tool behavior on the target //Checks if the item can work as a tool, calling the appropriate tool behavior on the target
/obj/item/proc/tool_attack_chain(mob/user, atom/target) /obj/item/proc/tool_attack_chain(mob/user, atom/target)
@@ -27,7 +22,6 @@
if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SELF, user) & COMPONENT_NO_INTERACT) if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SELF, user) & COMPONENT_NO_INTERACT)
return return
interact(user) interact(user)
SSdemo.mark_dirty(src)
/obj/item/proc/pre_attack(atom/A, mob/living/user, params) //do stuff before attackby! /obj/item/proc/pre_attack(atom/A, mob/living/user, params) //do stuff before attackby!
if(SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK, A, user, params) & COMPONENT_NO_ATTACK) if(SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK, A, user, params) & COMPONENT_NO_ATTACK)

View File

@@ -5,426 +5,135 @@ SUBSYSTEM_DEF(demo)
init_order = INIT_ORDER_DEMO init_order = INIT_ORDER_DEMO
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
loading_points = 12.6 SECONDS // Yogs -- loading times loading_points = 1 SECONDS // Yogs -- loading times
var/list/pre_init_lines = list() // stuff like chat before the init var/last_size = 0
var/list/icon_cache = list() var/last_embedded_size = 0
var/list/icon_state_caches = list() var/demo_started = 0
var/list/name_cache = list()
var/list/marked_dirty = list() var/ckey = "@@demoobserver"
var/list/marked_new = list() var/mob/dead/observer/dummy_observer
var/list/marked_turfs = list()
var/list/del_list = list()
var/last_written_time = null var/list/embed_list = list()
var/last_chat_message = null var/list/embedded_list = list()
var/list/chat_list = list()
// stats stuff /datum/controller/subsystem/demo/proc/write_chat(target, message)
var/last_queued = 0 if(!demo_started && !chat_list)
var/last_completed = 0 return
var/list/target_list
/datum/controller/subsystem/demo/proc/write_time() if(target == GLOB.clients || target == world)
var/new_time = world.time target_list = list(world)
if(last_written_time != new_time) else if(istype(target, /datum/controller/subsystem/demo) || target == dummy_observer || target == "d")
if(initialized) target_list = list("d")
WRITE_LOG_NO_FORMAT(GLOB.demo_log, "time [new_time]\n")
else
pre_init_lines += "time [new_time]"
last_written_time = new_time
/datum/controller/subsystem/demo/proc/write_event_line(line)
write_time()
if(initialized)
WRITE_LOG_NO_FORMAT(GLOB.demo_log, "[line]\n")
else
pre_init_lines += line
/datum/controller/subsystem/demo/proc/write_chat(target, text)
var/target_text = ""
if(target == GLOB.clients)
target_text = "world"
else if(islist(target)) else if(islist(target))
var/list/target_keys = list() target_list = list()
for(var/T in target) for(var/T in target)
var/client/C = CLIENT_FROM_VAR(T) if(istype(T, /datum/controller/subsystem/demo) || T == dummy_observer || T == "d")
if(C) target_list += "d"
target_keys += C.ckey else
if(!target_keys.len) var/client/C = CLIENT_FROM_VAR(target)
return if(C)
target_text = jointext(target_keys, ",") target_list += C
else else
var/client/C = CLIENT_FROM_VAR(target) var/client/C = CLIENT_FROM_VAR(target)
if(C) if(C)
target_text = C.ckey target_list = list(C)
if(!target_list || !target_list.len)
return
var/message_str = ""
var/is_text = FALSE
if(islist(message))
if(message["text"])
is_text = TRUE
message_str = message["text"]
else else
return message_str = message["html"]
var/json_encoded = json_encode(text) else if(istext(message))
write_event_line("chat [target_text] [last_chat_message == json_encoded ? "=" : json_encoded]") message_str = message
last_chat_message = json_encoded if(demo_started)
for(var/I in 1 to target_list.len)
if(!istext(target_list[I])) target_list[I] = "\ref[target_list[I]]"
call(DEMO_WRITER, "demo_chat")(target_list.Join(","), "\ref[message_str]", "[is_text]")
else if(chat_list)
chat_list[++chat_list.len] = list(world.time, target_list, message_str, is_text)
/datum/controller/subsystem/demo/Initialize() /datum/controller/subsystem/demo/Initialize()
WRITE_LOG_NO_FORMAT(GLOB.demo_log, "demo version 1\n") // increment this if you change the format dummy_observer = new
dummy_observer.forceMove(null)
dummy_observer.key = dummy_observer.ckey = ckey
dummy_observer.name = dummy_observer.real_name = "SSdemo Dummy Observer"
var/revdata_list = list()
if(GLOB.revdata) if(GLOB.revdata)
WRITE_LOG_NO_FORMAT(GLOB.demo_log, "commit [GLOB.revdata.commit || GLOB.revdata.originmastercommit]\n") revdata_list["commit"] = "[GLOB.revdata.commit || GLOB.revdata.originmastercommit]"
if(GLOB.revdata.originmastercommit) revdata_list["originmastercommit"] = "[GLOB.revdata.originmastercommit]"
revdata_list["repo"] = "yogstation13/Yogstation"
var/revdata_str = json_encode(revdata_list);
var/result = call(DEMO_WRITER, "demo_start")(GLOB.demo_log, revdata_str)
// write a "snapshot" of the world at this point. if(result == "SUCCESS")
// start with turfs demo_started = 1
log_world("Writing turfs...") for(var/L in embed_list)
WRITE_LOG_NO_FORMAT(GLOB.demo_log, "init [world.maxx] [world.maxy] [world.maxz]\n") embed_resource(arglist(L))
marked_turfs.Cut()
for(var/z in 1 to world.maxz) for(var/list/L in chat_list)
var/row_list = list() call(DEMO_WRITER, "demo_set_time_override")(L[1])
var/last_appearance var/list/target_list = L[2]
var/rle_count = 1 for(var/I in 1 to target_list.len)
for(var/y in 1 to world.maxy) if(!istext(target_list[I])) target_list[I] = "\ref[target_list[I]]"
for(var/x in 1 to world.maxx) call(DEMO_WRITER, "demo_chat")(target_list.Join(","), "\ref[L[3]]", "[L[4]]")
var/turf/T = locate(x,y,z) call(DEMO_WRITER, "demo_set_time_override")("null")
T.demo_last_appearance = T.appearance
var/this_appearance
// space turfs are difficult to RLE otherwise, because they all
// have different appearances despite being the same thing.
if(T.type == /turf/open/space || T.type == /turf/open/space/basic)
this_appearance = "s" // save the bytes
else if(istype(T, /turf/open/space/transit))
this_appearance = "t[T.dir]"
else
this_appearance = T.appearance
if(this_appearance == last_appearance)
rle_count++
else
if(rle_count > 1)
row_list += rle_count
rle_count = 1
if(istext(this_appearance))
row_list += this_appearance
else
// do a diff with the previous turf to save those bytes
row_list += encode_appearance(this_appearance, istext(last_appearance) ? null : last_appearance)
last_appearance = this_appearance
if(rle_count > 1)
row_list += rle_count
WRITE_LOG_NO_FORMAT(GLOB.demo_log, jointext(row_list, ",") + "\n")
CHECK_TICK
// then do objects
log_world("Writing objects")
marked_new.Cut()
marked_dirty.Cut()
for(var/z in 1 to world.maxz)
var/spacing = 0
var/row_list = list()
for(var/y in 1 to world.maxy)
for(var/x in 1 to world.maxx)
var/turf/T = locate(x,y,z)
var/list/turf_list = list()
for(var/C in T.contents)
var/atom/movable/as_movable = C
if(as_movable.loc != T)
continue
if(isobj(C) || ismob(C))
turf_list += encode_init_obj(C)
if(turf_list.len)
if(spacing)
row_list += spacing
spacing = 0
row_list += turf_list
spacing++
CHECK_TICK // This is a bit risky because something might change but meh, its not a big deal.
WRITE_LOG_NO_FORMAT(GLOB.demo_log, jointext(row_list, ",") + "\n")
// track objects that exist in nullspace last_size = text2num(call(DEMO_WRITER, "demo_get_size")())
var/nullspace_list = list() else
for(var/M in world) log_world("Failed to initialize demo system: [result]")
if(!isobj(M) && !ismob(M))
continue embed_list = null
var/atom/movable/AM = M chat_list = null
if(AM.loc != null)
continue
nullspace_list += encode_init_obj(AM)
CHECK_TICK
WRITE_LOG_NO_FORMAT(GLOB.demo_log, jointext(nullspace_list, ",") + "\n")
for(var/line in pre_init_lines)
WRITE_LOG_NO_FORMAT(GLOB.demo_log, "[line]\n")
return ..() return ..()
/datum/controller/subsystem/demo/fire() /datum/controller/subsystem/demo/fire()
if(!src.marked_new.len && !src.marked_dirty.len && !src.marked_turfs.len && !src.del_list.len) if(demo_started)
return // nothing to do last_size = text2num(call(DEMO_WRITER, "demo_flush")())
last_queued = src.marked_new.len + src.marked_dirty.len + src.marked_turfs.len /datum/controller/subsystem/demo/proc/flush()
last_completed = 0 if(demo_started)
last_size = text2num(call(DEMO_WRITER, "demo_flush")())
write_time() /datum/controller/subsystem/demo/Shutdown()
if(src.del_list.len) call(DEMO_WRITER, "demo_end")()
var/s = "del [jointext(src.del_list, ",")]\n" // if I don't do it like this I get "incorrect number of macro arguments" because byond is stupid and sucks
WRITE_LOG_NO_FORMAT(GLOB.demo_log, s)
src.del_list.Cut()
var/canceled = FALSE
var/list/marked_dirty = src.marked_dirty
var/list/dirty_updates = list()
while(marked_dirty.len)
last_completed++
var/atom/movable/M = marked_dirty[marked_dirty.len]
marked_dirty.len--
if(M.gc_destroyed || !M)
continue
if(M.loc == M.demo_last_loc && M.appearance == M.demo_last_appearance)
continue
var/loc_string = "="
if(M.loc != M.demo_last_loc)
loc_string = "null"
if(isturf(M.loc))
loc_string = "[M.x],[M.y],[M.z]"
else if(ismovable(M.loc))
loc_string = "\ref[M.loc]"
M.demo_last_loc = M.loc
var/appearance_string = "="
if(M.appearance != M.demo_last_appearance)
appearance_string = encode_appearance(M.appearance, M.demo_last_appearance)
M.demo_last_appearance = M.appearance
dirty_updates += "\ref[M] [loc_string] [appearance_string]"
if(MC_TICK_CHECK)
canceled = TRUE
break
if(dirty_updates.len)
var/s = "update [jointext(dirty_updates, ",")]\n"
WRITE_LOG_NO_FORMAT(GLOB.demo_log, s)
if(canceled)
return
var/list/marked_new = src.marked_new
var/list/new_updates = list()
while(marked_new.len)
last_completed++
var/atom/movable/M = marked_new[marked_new.len]
marked_new.len--
if(M.gc_destroyed || !M)
continue
var/loc_string = "null"
if(isturf(M.loc))
loc_string = "[M.x],[M.y],[M.z]"
else if(ismovable(M.loc))
loc_string = "\ref[M.loc]"
M.demo_last_appearance = M.appearance
new_updates += "\ref[M] [loc_string] [encode_appearance(M.appearance)]"
if(MC_TICK_CHECK)
canceled = TRUE
break
if(new_updates.len)
var/s = "new [jointext(new_updates, ",")]\n"
WRITE_LOG_NO_FORMAT(GLOB.demo_log, s)
if(canceled)
return
var/list/marked_turfs = src.marked_turfs
var/list/turf_updates = list()
while(marked_turfs.len)
last_completed++
var/turf/T = marked_turfs[marked_turfs.len]
marked_turfs.len--
if(T && T.appearance != T.demo_last_appearance)
turf_updates += "([T.x],[T.y],[T.z])=[encode_appearance(T.appearance, T.demo_last_appearance)]"
T.demo_last_appearance = T.appearance
if(MC_TICK_CHECK)
canceled = TRUE
break
if(turf_updates.len)
var/s = "turf [jointext(turf_updates, ",")]\n"
WRITE_LOG_NO_FORMAT(GLOB.demo_log, s)
if(canceled)
return
/datum/controller/subsystem/demo/proc/encode_init_obj(atom/movable/M)
M.demo_last_loc = M.loc
M.demo_last_appearance = M.appearance
var/encoded_appearance = encode_appearance(M.appearance)
var/list/encoded_contents = list()
for(var/C in M.contents)
if(isobj(C) || ismob(C))
encoded_contents += encode_init_obj(C)
return "\ref[M]=[encoded_appearance][(encoded_contents.len ? "([jointext(encoded_contents, ",")])" : "")]"
// please make sure the order you call this function in is the same as the order you write
/datum/controller/subsystem/demo/proc/encode_appearance(image/appearance, image/diff_appearance, diff_remove_overlays = FALSE)
if(appearance == null)
return "n"
if(appearance == diff_appearance)
return "="
var/icon_txt = "[appearance.icon]"
var/cached_icon = icon_cache[icon_txt] || icon_txt
var/list/icon_state_cache
if(!isnum(cached_icon))
icon_cache[icon_txt] = icon_cache.len + 1
icon_state_cache = (icon_state_caches[++icon_state_caches.len] = list())
else
icon_state_cache = icon_state_caches[cached_icon]
var/list/cached_icon_state = icon_state_cache[appearance.icon_state] || appearance.icon_state
if(!isnum(cached_icon_state))
icon_state_cache[appearance.icon_state] = icon_state_cache.len + 1
var/cached_name = name_cache[appearance.name] || appearance.name
if(!isnum(cached_name))
name_cache[appearance.name] = name_cache.len + 1
var/color_string = appearance.color || "w"
if(islist(color_string))
var/list/old_list = appearance.color
var/list/inted = list()
inted.len = old_list.len
for(var/i in 1 to old_list.len)
inted[i] += round(old_list[i] * 255)
color_string = jointext(inted, ",")
var/overlays_string = "\[]"
if(appearance.overlays.len)
var/list/overlays_list = list()
for(var/i in 1 to appearance.overlays.len)
var/image/overlay = appearance.overlays[i]
overlays_list += encode_appearance(overlay, appearance, TRUE)
overlays_string = "\[[jointext(overlays_list, ",")]]"
var/underlays_string = "\[]"
if(appearance.underlays.len)
var/list/underlays_list = list()
for(var/i in 1 to appearance.underlays.len)
var/image/underlay = appearance.underlays[i]
underlays_list += encode_appearance(underlay, appearance, TRUE)
underlays_string = "\[[jointext(underlays_list, ",")]]"
var/appearance_transform_string = "i"
if(appearance.transform)
var/matrix/M = appearance.transform
appearance_transform_string = "[M.a],[M.b],[M.c],[M.d],[M.e],[M.f]"
if(appearance_transform_string == "1,0,0,0,1,0")
appearance_transform_string = "i"
var/list/appearance_list = list(
json_encode(cached_icon),
json_encode(cached_icon_state),
json_encode(cached_name),
appearance.appearance_flags,
appearance.layer,
appearance.plane == -32767 ? "" : appearance.plane,
appearance.dir == 2 ? "" : appearance.dir,
appearance.color ? color_string : "",
appearance.alpha == 255 ? "" : appearance.alpha,
appearance.pixel_x == 0 ? "" : appearance.pixel_x,
appearance.pixel_y == 0 ? "" : appearance.pixel_y,
appearance.blend_mode <= 1 ? "" : appearance.blend_mode,
appearance_transform_string != "i" ? appearance_transform_string : "",
appearance:invisibility == 0 ? "" : appearance:invisibility, // colon because dreamchecker is dumb
appearance.pixel_w == 0 ? "" : appearance.pixel_w,
appearance.pixel_z == 0 ? "" : appearance.pixel_z,
appearance.overlays.len ? overlays_string : "",
appearance.underlays.len ? underlays_string : ""
)
while(appearance_list[appearance_list.len] == "" && appearance_list.len > 0)
appearance_list.len--
var/undiffed_string = "{[jointext(appearance_list, ";")]}"
if(diff_appearance)
var/overlays_identical = TRUE
if(diff_remove_overlays)
overlays_identical = (appearance.overlays.len == 0)
else if(appearance.overlays.len != diff_appearance.overlays.len)
overlays_identical = FALSE
else
for(var/i in 1 to appearance.overlays.len)
if(appearance.overlays[i] != diff_appearance.overlays[i])
overlays_identical = FALSE
break
var/underlays_identical = TRUE
if(diff_remove_overlays)
underlays_identical = (appearance.underlays.len == 0)
else if(appearance.underlays.len != diff_appearance.underlays.len)
underlays_identical = FALSE
else
for(var/i in 1 to appearance.underlays.len)
if(appearance.underlays[i] != diff_appearance.underlays[i])
underlays_identical = FALSE
break
var/diff_transform_string = "i"
if(diff_appearance.transform)
var/matrix/M = diff_appearance.transform
diff_transform_string = "[M.a],[M.b],[M.c],[M.d],[M.e],[M.f]"
if(diff_transform_string == "1,0,0,0,1,0")
diff_transform_string = "i"
var/list/diffed_appearance_list = list(
json_encode(cached_icon),
json_encode(cached_icon_state),
json_encode(cached_name),
appearance.appearance_flags == diff_appearance.appearance_flags ? "" : appearance.appearance_flags,
appearance.layer == diff_appearance.layer ? "" : appearance.layer,
appearance.plane == diff_appearance.plane ? "" : appearance.plane,
appearance.dir == diff_appearance.dir ? "" : appearance.dir,
appearance.color == diff_appearance.color ? "" : color_string,
appearance.alpha == diff_appearance.alpha ? "" : appearance.alpha,
appearance.pixel_x == diff_appearance.pixel_x ? "" : appearance.pixel_x,
appearance.pixel_y == diff_appearance.pixel_y ? "" : appearance.pixel_y,
appearance.blend_mode == diff_appearance.blend_mode ? "" : appearance.blend_mode,
appearance_transform_string == diff_transform_string ? "" : appearance_transform_string,
appearance:invisibility == diff_appearance:invisibility ? "" : appearance:invisibility, // colon because dreamchecker is too dumb
appearance.pixel_w == diff_appearance.pixel_w ? "" : appearance.pixel_w,
appearance.pixel_z == diff_appearance.pixel_z ? "" : appearance.pixel_z,
overlays_identical ? "" : overlays_string,
underlays_identical ? "" :underlays_string
)
while(diffed_appearance_list[diffed_appearance_list.len] == "" && diffed_appearance_list.len > 0)
diffed_appearance_list.len--
var/diffed_string = "~{[jointext(diffed_appearance_list, ";")]}"
if(length(diffed_string) < length(undiffed_string))
return diffed_string
return undiffed_string
/datum/controller/subsystem/demo/stat_entry(msg) /datum/controller/subsystem/demo/stat_entry(msg)
msg += "Remaining: {" msg += "ALL: [format_size(last_size)] | RSC: [format_size(last_embedded_size)]"
msg += "Trf:[marked_turfs.len]|"
msg += "New:[marked_new.len]|"
msg += "Upd:[marked_dirty.len]|"
msg += "Del:[del_list.len]"
msg += "}"
return ..(msg) return ..(msg)
/datum/controller/subsystem/demo/proc/mark_turf(turf/T) /datum/controller/subsystem/demo/proc/format_size(size)
if(!isturf(T)) if(size < 1000000)
return return "[round(size / 1000, 0.01)]kB"
marked_turfs[T] = TRUE return "[round(size / 1000000, 0.01)]MB"
/datum/controller/subsystem/demo/proc/mark_new(atom/movable/M) /datum/controller/subsystem/demo/proc/embed_resource(res, path)
if(!isobj(M) && !ismob(M)) res = fcopy_rsc(res)
return if(!demo_started)
if(M.gc_destroyed) if(embed_list)
return embed_list += list(list(res, path))
marked_new[M] = TRUE return res
if(marked_dirty[M]) if(!res || embedded_list[res])
marked_dirty -= M return res
var/do_del = FALSE
// I can't wait for when TG ports this and they make this a #define macro. if(!istext(path))
/datum/controller/subsystem/demo/proc/mark_dirty(atom/movable/M) path = "tmp/rsc_[ckey("\ref[res]")]_[rand(0, 100000)]"
if(!isobj(M) && !ismob(M)) fcopy(res, path)
return do_del = TRUE
if(M.gc_destroyed) var/size = length(file(path))
return last_embedded_size += size
if(!marked_new[M]) log_world("Embedding \ref[res] [res] from [path] ([size] bytes)")
marked_dirty[M] = TRUE if(call(DEMO_WRITER, "demo_embed_resource")("\ref[res]", path) != "SUCCESS")
log_world("Failed to copy \ref[res] [res] from [path]!")
/datum/controller/subsystem/demo/proc/mark_destroyed(atom/movable/M) embedded_list[res] = 1
if(!isobj(M) && !ismob(M)) if(do_del)
return fdel(path)
if(marked_new[M]) return res
marked_new -= M
if(marked_dirty[M])
marked_dirty -= M
if(initialized)
del_list["\ref[M]"] = 1

View File

@@ -281,7 +281,6 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D) SSgarbage.Queue(D)
if (QDEL_HINT_IWILLGC) if (QDEL_HINT_IWILLGC)
D.gc_destroyed = world.time D.gc_destroyed = world.time
SSdemo.mark_destroyed(D)
return return
if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory. if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
if(!force) if(!force)
@@ -301,10 +300,8 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D) SSgarbage.Queue(D)
if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
SSdemo.mark_destroyed(D)
SSgarbage.Queue(D, GC_QUEUE_HARDDELETE) SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste. if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSdemo.mark_destroyed(D)
SSgarbage.HardDelete(D) SSgarbage.HardDelete(D)
if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion. if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
SSgarbage.Queue(D) SSgarbage.Queue(D)
@@ -323,8 +320,6 @@ SUBSYSTEM_DEF(garbage)
#endif #endif
I.no_hint++ I.no_hint++
SSgarbage.Queue(D) SSgarbage.Queue(D)
if(D)
SSdemo.mark_destroyed(D)
else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED) else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic") CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")

View File

@@ -57,6 +57,7 @@ SUBSYSTEM_DEF(title)
continue continue
var/atom/movable/screen/splash/S = new(thing, FALSE) var/atom/movable/screen/splash/S = new(thing, FALSE)
S.Fade(FALSE,FALSE) S.Fade(FALSE,FALSE)
SSdemo.flush()
/datum/controller/subsystem/title/Shutdown() /datum/controller/subsystem/title/Shutdown()
if(file_path) if(file_path)

View File

@@ -122,6 +122,7 @@
sentmsg = "[span_prefix("RELAY: [input["source"]]")] " + sentmsg sentmsg = "[span_prefix("RELAY: [input["source"]]")] " + sentmsg
//no pinging across servers, thats intentional //no pinging across servers, thats intentional
to_chat(C,sentmsg) to_chat(C,sentmsg)
to_chat(SSdemo, "[span_prefix("RELAY: [input["source"]]")] " + oocmsg)
/datum/world_topic/server_hop /datum/world_topic/server_hop
keyword = "server_hop" keyword = "server_hop"

View File

@@ -106,7 +106,6 @@
if(SSatoms.InitAtom(src, args)) if(SSatoms.InitAtom(src, args))
//we were deleted //we were deleted
return return
SSdemo.mark_new(src)
/** /**
* The primary method that objects are setup in SS13 with * The primary method that objects are setup in SS13 with

View File

@@ -427,7 +427,6 @@
if (length(client_mobs_in_contents)) if (length(client_mobs_in_contents))
update_parallax_contents() update_parallax_contents()
SSdemo.mark_dirty(src)
return TRUE return TRUE
/atom/movable/Destroy(force) /atom/movable/Destroy(force)

View File

@@ -602,7 +602,6 @@
if(AIRLOCK_DENY, AIRLOCK_OPENING, AIRLOCK_CLOSING, AIRLOCK_EMAG) if(AIRLOCK_DENY, AIRLOCK_OPENING, AIRLOCK_CLOSING, AIRLOCK_EMAG)
icon_state = "nonexistenticonstate" //MADNESS icon_state = "nonexistenticonstate" //MADNESS
set_airlock_overlays(state) set_airlock_overlays(state)
SSdemo.mark_dirty(src)
/obj/machinery/door/airlock/proc/set_side_overlays(obj/effect/overlay/airlock_part/base, show_lights = FALSE) /obj/machinery/door/airlock/proc/set_side_overlays(obj/effect/overlay/airlock_part/base, show_lights = FALSE)
var/side = base.side_id var/side = base.side_id

View File

@@ -224,7 +224,6 @@
icon_state = "door_open" icon_state = "door_open"
if(welded) if(welded)
add_overlay("welded_open") add_overlay("welded_open")
SSdemo.mark_dirty(src)
/obj/machinery/door/firedoor/open() /obj/machinery/door/firedoor/open()
. = ..() . = ..()

View File

@@ -94,7 +94,6 @@
icon_state = "closed" icon_state = "closed"
else else
icon_state = "open" icon_state = "open"
SSdemo.mark_dirty(src)
/obj/machinery/door/poddoor/try_to_activate_door(mob/user) /obj/machinery/door/poddoor/try_to_activate_door(mob/user)
return return

View File

@@ -53,7 +53,6 @@
icon_state = base_state icon_state = base_state
else else
icon_state = "[base_state]open" icon_state = "[base_state]open"
SSdemo.mark_dirty(src)
/obj/machinery/door/window/proc/open_and_close() /obj/machinery/door/window/proc/open_and_close()
if(!open()) if(!open())

View File

@@ -186,6 +186,8 @@
for(var/mob/dead/observer/M in GLOB.player_list) for(var/mob/dead/observer/M in GLOB.player_list)
if(M.client && (M.client.prefs.chat_toggles & CHAT_GHOSTRADIO)) if(M.client && (M.client.prefs.chat_toggles & CHAT_GHOSTRADIO))
receive |= M receive |= M
if(SSdemo.dummy_observer)
receive |= SSdemo.dummy_observer
// Render the message and have everybody hear it. // Render the message and have everybody hear it.
// Always call this on the virtualspeaker to avoid issues. // Always call this on the virtualspeaker to avoid issues.

View File

@@ -63,6 +63,7 @@
the_icon.Insert(icon('icons/obj/contraband.dmi', "poster_ripped"), "poster_ripped") the_icon.Insert(icon('icons/obj/contraband.dmi', "poster_ripped"), "poster_ripped")
icon = the_icon icon = the_icon
SSdemo.embed_resource(icon)
/* /*
This proc will write "WANTED" or MISSING" at the top of the poster. This proc will write "WANTED" or MISSING" at the top of the poster.

View File

@@ -795,6 +795,7 @@ GLOBAL_LIST_EMPTY(PDAs)
for(var/mob/M in GLOB.player_list) for(var/mob/M in GLOB.player_list)
if(isobserver(M) && M.client && (M.client.prefs.chat_toggles & CHAT_GHOSTPDA)) if(isobserver(M) && M.client && (M.client.prefs.chat_toggles & CHAT_GHOSTPDA))
to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]") to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]")
to_chat(SSdemo, "[FOLLOW_LINK(SSdemo, user)] [ghost_message]")
// Log in the talk log // Log in the talk log
user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]") user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]")
to_chat(user, span_info("Message sent to [target_text]: \"[message]\"")) to_chat(user, span_info("Message sent to [target_text]: \"[message]\""))

View File

@@ -135,7 +135,6 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
for(var/turf/open/space/S in RANGE_TURFS(1, src)) //RANGE_TURFS is in code\__HELPERS\game.dm for(var/turf/open/space/S in RANGE_TURFS(1, src)) //RANGE_TURFS is in code\__HELPERS\game.dm
S.update_starlight() S.update_starlight()
SSdemo.mark_turf(W)
return W return W

View File

@@ -275,6 +275,7 @@ GLOBAL_VAR(restart_counter)
log_world("Deallocated [num_deleted] gas mixtures") log_world("Deallocated [num_deleted] gas mixtures")
if(fexists(EXTOOLS)) if(fexists(EXTOOLS))
call(EXTOOLS, "cleanup")() call(EXTOOLS, "cleanup")()
SSdemo?.Shutdown()
..() ..()
/world/proc/update_status() //yogs -- Mirrored in the Yogs folder in March 2019. Do not edit, swallow, or submerge in acid /world/proc/update_status() //yogs -- Mirrored in the Yogs folder in March 2019. Do not edit, swallow, or submerge in acid

View File

@@ -33,6 +33,7 @@
continue continue
if (M.stat == DEAD || (M.client && M.client.holder && (M.client.prefs.chat_toggles & CHAT_DEAD))) //admins can toggle deadchat on and off. This is a proc in admin.dm and is only give to Administrators and above if (M.stat == DEAD || (M.client && M.client.holder && (M.client.prefs.chat_toggles & CHAT_DEAD))) //admins can toggle deadchat on and off. This is a proc in admin.dm and is only give to Administrators and above
to_chat(M, rendered) to_chat(M, rendered)
to_chat(SSdemo, rendered)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Dsay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! SSblackbox.record_feedback("tally", "admin_verb", 1, "Dsay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

View File

@@ -141,6 +141,7 @@ GLOBAL_VAR_INIT(mentor_ooc_colour, YOGS_MENTOR_OOC_COLOUR) // yogs - mentor ooc
SEND_SOUND(C,pingsound) SEND_SOUND(C,pingsound)
sentmsg = "<span style='background-color: #ccccdd'>" + sentmsg + "</span>" sentmsg = "<span style='background-color: #ccccdd'>" + sentmsg + "</span>"
to_chat(C,sentmsg) to_chat(C,sentmsg)
to_chat(SSdemo, oocmsg);
//YOGS END //YOGS END
var/data = list() var/data = list()
data["normal"] = oocmsg data["normal"] = oocmsg

View File

@@ -343,13 +343,13 @@ BLIND // can't see anything
var/icon/female_clothing_icon = icon(icon, t_color) // and make the uniform the "female" shape. female_s is either the top-only one (for jumpskirts and the like) or the full one (for jumpsuits) var/icon/female_clothing_icon = icon(icon, t_color) // and make the uniform the "female" shape. female_s is either the top-only one (for jumpskirts and the like) or the full one (for jumpsuits)
var/icon/female_s = icon('icons/effects/clothing.dmi', "[(type == FEMALE_UNIFORM_FULL) ? "female_full" : "female_top"]") var/icon/female_s = icon('icons/effects/clothing.dmi', "[(type == FEMALE_UNIFORM_FULL) ? "female_full" : "female_top"]")
female_clothing_icon.Blend(female_s, ICON_MULTIPLY) female_clothing_icon.Blend(female_s, ICON_MULTIPLY)
GLOB.female_clothing_icons[index] = fcopy_rsc(female_clothing_icon) //Then it saves the icon in a global list so it doesn't have to make it again GLOB.female_clothing_icons[index] = SSdemo.embed_resource(fcopy_rsc(female_clothing_icon)) //Then it saves the icon in a global list so it doesn't have to make it again
/proc/generate_skinny_clothing(index,t_color,icon,type) //Works the exact same as above but for skinny people /proc/generate_skinny_clothing(index,t_color,icon,type) //Works the exact same as above but for skinny people
var/icon/skinny_clothing_icon = icon(icon, t_color) var/icon/skinny_clothing_icon = icon(icon, t_color)
var/icon/skinny_s = icon('icons/effects/clothing.dmi', "[(type == FEMALE_UNIFORM_FULL) ? "skinny_full" : "skinny_top"]") //Hooks into same check to see if it's eligible var/icon/skinny_s = icon('icons/effects/clothing.dmi', "[(type == FEMALE_UNIFORM_FULL) ? "skinny_full" : "skinny_top"]") //Hooks into same check to see if it's eligible
skinny_clothing_icon.Blend(skinny_s, ICON_MULTIPLY) skinny_clothing_icon.Blend(skinny_s, ICON_MULTIPLY)
GLOB.skinny_clothing_icons[index] = fcopy_rsc(skinny_clothing_icon) GLOB.skinny_clothing_icons[index] = SSdemo.embed_resource(fcopy_rsc(skinny_clothing_icon))
/obj/item/clothing/under/verb/toggle() /obj/item/clothing/under/verb/toggle()
set name = "Adjust Suit Sensors" set name = "Adjust Suit Sensors"

View File

@@ -1,24 +0,0 @@
/atom
var/image/demo_last_appearance
/atom/movable
var/atom/demo_last_loc
/mob/Login()
. = ..()
SSdemo.write_event_line("setmob [client.ckey] \ref[src]")
/client/New()
SSdemo.write_event_line("login [ckey]")
. = ..()
/client/Del()
. = ..()
SSdemo.write_event_line("logout [ckey]")
/turf/setDir()
. = ..()
SSdemo.mark_turf(src)
/atom/movable/setDir()
. = ..()
SSdemo.mark_dirty(src)

View File

@@ -104,8 +104,6 @@ All ShuttleMove procs go here
loc = newT loc = newT
SSdemo.mark_dirty(src)
return TRUE return TRUE
// Called on atoms after everything has been moved // Called on atoms after everything has been moved

BIN
demo-writer.dll Normal file

Binary file not shown.

BIN
demo-writer.pdb Normal file

Binary file not shown.

BIN
libdemo-writer.so Executable file

Binary file not shown.

View File

@@ -37,4 +37,5 @@ cp -r strings/* $1/strings/
#dlls on windows #dlls on windows
cp rust_g* $1/ || true cp rust_g* $1/ || true
cp *byond-extools.* $1/ || true cp *byond-extools.* $1/ || true
cp *demo-writer.* $1/ || true

View File

@@ -1904,7 +1904,6 @@
#include "code\modules\clothing\under\jobs\Plasmaman\medsci.dm" #include "code\modules\clothing\under\jobs\Plasmaman\medsci.dm"
#include "code\modules\clothing\under\jobs\Plasmaman\security.dm" #include "code\modules\clothing\under\jobs\Plasmaman\security.dm"
#include "code\modules\corporations\corporation.dm" #include "code\modules\corporations\corporation.dm"
#include "code\modules\demo\hooks.dm"
#include "code\modules\detectivework\detective_work.dm" #include "code\modules\detectivework\detective_work.dm"
#include "code\modules\detectivework\evidence.dm" #include "code\modules\detectivework\evidence.dm"
#include "code\modules\detectivework\footprints_and_rag.dm" #include "code\modules\detectivework\footprints_and_rag.dm"