From 70abf6d8e92c552cd3570bbe020c25a571a7a7f3 Mon Sep 17 00:00:00 2001 From: Jordie Date: Thu, 14 Dec 2017 20:43:26 +1100 Subject: [PATCH] Saves newscaster data and photos to round logs folder --- code/__HELPERS/roundend.dm | 432 +++++++++++++++++++++++++++ code/controllers/subsystem/ticker.dm | 1 + code/game/machinery/newscaster.dm | 11 +- 3 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 code/__HELPERS/roundend.dm diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm new file mode 100644 index 0000000000..c8710d6570 --- /dev/null +++ b/code/__HELPERS/roundend.dm @@ -0,0 +1,432 @@ +/datum/controller/subsystem/ticker/proc/gather_roundend_feedback() + var/clients = GLOB.player_list.len + var/surviving_humans = 0 + var/surviving_total = 0 + var/ghosts = 0 + var/escaped_humans = 0 + var/escaped_total = 0 + + for(var/mob/M in GLOB.player_list) + if(ishuman(M)) + if(!M.stat) + surviving_humans++ + if(M.z == ZLEVEL_CENTCOM) + escaped_humans++ + if(!M.stat) + surviving_total++ + if(M.z == ZLEVEL_CENTCOM) + escaped_total++ + + if(isobserver(M)) + ghosts++ + + if(clients) + SSblackbox.record_feedback("nested tally", "round_end_stats", clients, list("clients")) + if(ghosts) + SSblackbox.record_feedback("nested tally", "round_end_stats", ghosts, list("ghosts")) + if(surviving_humans) + SSblackbox.record_feedback("nested tally", "round_end_stats", surviving_humans, list("survivors", "human")) + if(surviving_total) + SSblackbox.record_feedback("nested tally", "round_end_stats", surviving_total, list("survivors", "total")) + if(escaped_humans) + SSblackbox.record_feedback("nested tally", "round_end_stats", escaped_humans, list("escapees", "human")) + if(escaped_total) + SSblackbox.record_feedback("nested tally", "round_end_stats", escaped_total, list("escapees", "total")) + + gather_antag_success_rate() + +/datum/controller/subsystem/ticker/proc/gather_antag_success_rate() + var/team_gid = 1 + var/list/team_ids = list() + + for(var/datum/antagonist/A in GLOB.antagonists) + var/list/antag_info = list() + antag_info["key"] = A.owner.key + antag_info["name"] = A.owner.name + antag_info["antagonist_type"] = A.type + antag_info["antagonist_name"] = A.name //For auto and custom roles + antag_info["objectives"] = list() + antag_info["team"] = list() + var/datum/objective_team/T = A.get_team() + if(T) + antag_info["team"]["type"] = T.type + antag_info["team"]["name"] = T.name + if(!team_ids[T]) + team_ids[T] = team_gid++ + antag_info["team"]["id"] = team_ids[T] + + if(!A.owner) + continue + if(A.objectives.len) + for(var/datum/objective/O in A.objectives) + var/result = O.check_completion() ? "SUCCESS" : "FAIL" + antag_info["objectives"] += list(list("objective_type"=O.type,"text"=O.explanation_text,"result"=result)) + SSblackbox.record_feedback("associative", "antagonists", 1, antag_info) + +/datum/controller/subsystem/ticker/proc/gather_newscaster() + var/json_file = file("[GLOB.log_directory]/newscaster.json") + var/list/file_data = list() + var/pos = 1 + for(var/datum/newscaster/feed_channel/channel in GLOB.news_network.network_channels) + if(!GLOB.news_network.network_channels.len) + break + file_data["[pos]"] = list("channel name" = "[channel.channel_name]", "author" = "[channel.author]", "censored" = channel.censored ? 1 : 0, "author censored" = channel.authorCensor ? 1 : 0, "messages" = list()) + if(!channel.messages.len) + continue + for(var/datum/newscaster/feed_message/message in channel.messages) + file_data["[pos]"]["messages"] |= list("author" = "[message.author]", "time stamp" = "[message.time_stamp]", "censored" = message.bodyCensor ? 1 : 0, "author censored" = message.authorCensor ? 1 : 0, "photo file" = "[message.photo_file]", "photo caption" = "[message.caption]", "body" = "[message.body]", "comments" = list()) + if(!message.comments.len) + continue + for(var/datum/newscaster/feed_comment/comment in message.comments) + file_data["[pos]"]["messages"]["comments"] = list("author" = "[comment.author]", "time stamp" = "[comment.time_stamp]", "body" = "[comment.body]") + pos++ + if(GLOB.news_network.wanted_issue.active) + file_data["wanted"] = list("author" = "[GLOB.news_network.wanted_issue.scannedUser]", "criminal" = "[GLOB.news_network.wanted_issue.criminal]", "description" = "[GLOB.news_network.wanted_issue.body]", "photo file" = "[GLOB.news_network.wanted_issue.photo_file]") + WRITE_FILE(json_file, json_encode(file_data)) + +/datum/controller/subsystem/ticker/proc/declare_completion() + set waitfor = FALSE + + to_chat(world, "


The round has ended.") + if(LAZYLEN(GLOB.round_end_notifiees)) + send2irc("Notice", "[GLOB.round_end_notifiees.Join(", ")] the round has ended.") + + for(var/client/C in GLOB.clients) + if(!C.credits) + C.RollCredits() + C.playtitlemusic(40) + + display_report() + + gather_roundend_feedback() + + CHECK_TICK + + //Set news report and mode result + mode.set_round_result() + + send2irc("Server", "Round just ended.") + + if(length(CONFIG_GET(keyed_string_list/cross_server))) + send_news_report() + + CHECK_TICK + + //These need update to actually reflect the real antagonists + //Print a list of antagonists to the server log + var/list/total_antagonists = list() + //Look into all mobs in world, dead or alive + for(var/datum/mind/Mind in minds) + var/temprole = Mind.special_role + if(temprole) //if they are an antagonist of some sort. + if(temprole in total_antagonists) //If the role exists already, add the name to it + total_antagonists[temprole] += ", [Mind.name]([Mind.key])" + else + total_antagonists.Add(temprole) //If the role doesnt exist in the list, create it and add the mob + total_antagonists[temprole] += ": [Mind.name]([Mind.key])" + + CHECK_TICK + + //Now print them all into the log! + log_game("Antagonists at round end were...") + for(var/i in total_antagonists) + log_game("[i]s[total_antagonists[i]].") + + CHECK_TICK + + //Collects persistence features + if(mode.allow_persistence_save) + SSpersistence.CollectData() + + //stop collecting feedback during grifftime + SSblackbox.Seal() + + sleep(50) + ready_for_reboot = TRUE + standard_reboot() + +/datum/controller/subsystem/ticker/proc/standard_reboot() + if(ready_for_reboot) + if(mode.station_was_nuked) + Reboot("Station destroyed by Nuclear Device.", "nuke") + else + Reboot("Round ended.", "proper completion") + else + CRASH("Attempted standard reboot without ticker roundend completion") + +//Common part of the report +/datum/controller/subsystem/ticker/proc/build_roundend_report() + var/list/parts = list() + + //Gamemode specific things. Should be empty most of the time. + parts += mode.special_report() + + CHECK_TICK + + //AI laws + parts += law_report() + + CHECK_TICK + + //Antagonists + parts += antag_report() + + CHECK_TICK + //Medals + parts += medal_report() + //Station Goals + parts += goal_report() + + listclearnulls(parts) + + return parts.Join() + + +/datum/controller/subsystem/ticker/proc/survivor_report() + var/list/parts = list() + var/station_evacuated = EMERGENCY_ESCAPED_OR_ENDGAMED + var/num_survivors = 0 + var/num_escapees = 0 + var/num_shuttle_escapees = 0 + + //Player status report + for(var/i in GLOB.mob_list) + var/mob/Player = i + if(Player.mind && !isnewplayer(Player)) + if(Player.stat != DEAD && !isbrain(Player)) + num_survivors++ + if(station_evacuated) //If the shuttle has already left the station + var/list/area/shuttle_areas + if(SSshuttle && SSshuttle.emergency) + shuttle_areas = SSshuttle.emergency.shuttle_areas + if(Player.onCentCom() || Player.onSyndieBase()) + num_escapees++ + if(shuttle_areas[get_area(Player)]) + num_shuttle_escapees++ + + //Round statistics report + var/datum/station_state/end_state = new /datum/station_state() + end_state.count() + var/station_integrity = min(PERCENT(GLOB.start_state.score(end_state)), 100) + + parts += "[GLOB.TAB]Shift Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]" + parts += "[GLOB.TAB]Station Integrity: [mode.station_was_nuked ? "Destroyed" : "[station_integrity]%"]" + var/total_players = GLOB.joined_player_list.len + if(total_players) + parts+= "[GLOB.TAB]Total Population: [total_players]" + if(station_evacuated) + parts += "
[GLOB.TAB]Evacuation Rate: [num_escapees] ([PERCENT(num_escapees/total_players)]%)" + parts += "[GLOB.TAB](on emergency shuttle): [num_shuttle_escapees] ([PERCENT(num_shuttle_escapees/total_players)]%)" + parts += "[GLOB.TAB]Survival Rate: [num_survivors] ([PERCENT(num_survivors/total_players)]%)" + return parts.Join("
") + +/datum/controller/subsystem/ticker/proc/show_roundend_report(client/C,common_report) + var/list/report_parts = list() + + report_parts += personal_report(C) + report_parts += common_report + + var/datum/browser/roundend_report = new(C, "roundend") + roundend_report.width = 800 + roundend_report.height = 600 + roundend_report.set_content(report_parts.Join()) + roundend_report.stylesheets = list() + roundend_report.add_stylesheet("roundend",'html/browser/roundend.css') + + roundend_report.open(0) + +/datum/controller/subsystem/ticker/proc/personal_report(client/C) + var/list/parts = list() + var/mob/M = C.mob + if(M.mind && !isnewplayer(M)) + if(M.stat != DEAD && !isbrain(M)) + if(EMERGENCY_ESCAPED_OR_ENDGAMED) + if(!M.onCentCom() || !M.onSyndieBase()) + parts += "
" + parts += "You managed to survive, but were marooned on [station_name()]..." + else + parts += "
" + parts += "You managed to survive the events on [station_name()] as [M.real_name]." + else + parts += "
" + parts += "You managed to survive the events on [station_name()] as [M.real_name]." + + else + parts += "
" + parts += "You did not survive the events on [station_name()]..." + else + parts += "
" + parts += "
" + if(GLOB.survivor_report) + parts += GLOB.survivor_report + else + parts += survivor_report() + + parts += "
" + + return parts.Join() + +/datum/controller/subsystem/ticker/proc/display_report() + GLOB.common_report = build_roundend_report() + for(var/client/C in GLOB.clients) + show_roundend_report(C,GLOB.common_report) + give_show_report_button(C) + CHECK_TICK + +/datum/controller/subsystem/ticker/proc/law_report() + var/list/parts = list() + //Silicon laws report + for (var/i in GLOB.ai_list) + var/mob/living/silicon/ai/aiPlayer = i + if(aiPlayer.mind) + parts += "[aiPlayer.name] (Played by: [aiPlayer.mind.key])'s laws [aiPlayer.stat != DEAD ? "at the end of the round" : "when it was deactivated"] were:" + parts += aiPlayer.laws.get_law_list(include_zeroth=TRUE) + + parts += "Total law changes: [aiPlayer.law_change_counter]" + + if (aiPlayer.connected_robots.len) + var/robolist = "[aiPlayer.real_name]'s minions were: " + for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots) + if(robo.mind) + robolist += "[robo.name][robo.stat?" (Deactivated) (Played by: [robo.mind.key]), ":" (Played by: [robo.mind.key]), "]" + parts += "[robolist]" + + for (var/mob/living/silicon/robot/robo in GLOB.silicon_mobs) + if (!robo.connected_ai && robo.mind) + if (robo.stat != DEAD) + parts += "[robo.name] (Played by: [robo.mind.key]) survived as an AI-less borg! Its laws were:" + else + parts += "[robo.name] (Played by: [robo.mind.key]) was unable to survive the rigors of being a cyborg without an AI. Its laws were:" + + if(robo) //How the hell do we lose robo between here and the world messages directly above this? + parts += robo.laws.get_law_list(include_zeroth=TRUE) + if(parts.len) + return "
[parts.Join("
")]
" + else + return "" + +/datum/controller/subsystem/ticker/proc/goal_report() + var/list/parts = list() + if(mode.station_goals.len) + for(var/V in mode.station_goals) + var/datum/station_goal/G = V + parts += G.get_result() + return "
    [parts.Join()]
" + +/datum/controller/subsystem/ticker/proc/medal_report() + if(GLOB.commendations.len) + var/list/parts = list() + parts += "Medal Commendations:" + for (var/com in GLOB.commendations) + parts += com + return "
[parts.Join("
")]
" + return "" + +/datum/controller/subsystem/ticker/proc/antag_report() + var/list/result = list() + var/list/all_teams = list() + var/list/all_antagonists = list() + + for(var/datum/antagonist/A in GLOB.antagonists) + all_teams |= A.get_team() + all_antagonists += A + + for(var/datum/objective_team/T in all_teams) + result += T.roundend_report() + for(var/datum/antagonist/X in all_antagonists) + if(X.get_team() == T) + all_antagonists -= X + result += " "//newline between teams + + var/currrent_category + var/datum/antagonist/previous_category + + sortTim(all_antagonists, /proc/cmp_antag_category) + + for(var/datum/antagonist/A in all_antagonists) + if(!A.show_in_roundend) + continue + if(A.roundend_category != currrent_category) + if(previous_category) + result += previous_category.roundend_report_footer() + result += "
" + result += "
" + result += A.roundend_report_header() + currrent_category = A.roundend_category + previous_category = A + result += A.roundend_report() + result += "

" + + if(all_antagonists.len) + var/datum/antagonist/last = all_antagonists[all_antagonists.len] + result += last.roundend_report_footer() + result += "
" + + return result.Join() + +/proc/cmp_antag_category(datum/antagonist/A,datum/antagonist/B) + return sorttext(B.roundend_category,A.roundend_category) + + +/datum/controller/subsystem/ticker/proc/give_show_report_button(client/C) + var/datum/action/report/R = new + C.player_details.player_actions += R + R.Grant(C.mob) + to_chat(C,"Show roundend report again") + +/datum/action/report + name = "Show roundend report" + button_icon_state = "vote" + +/datum/action/report/Trigger() + if(owner && GLOB.common_report && SSticker.current_state == GAME_STATE_FINISHED) + SSticker.show_roundend_report(owner.client,GLOB.common_report) + +/datum/action/report/IsAvailable() + return 1 + +/datum/action/report/Topic(href,href_list) + if(usr != owner) + return + if(href_list["report"]) + Trigger() + return + + +/proc/printplayer(datum/mind/ply, fleecheck) + var/text = "[ply.key] was [ply.name] the [ply.assigned_role] and" + if(ply.current) + if(ply.current.stat == DEAD) + text += " died" + else + text += " survived" + if(fleecheck) + var/turf/T = get_turf(ply.current) + if(!T || !(T.z in GLOB.station_z_levels)) + text += " while fleeing the station" + if(ply.current.real_name != ply.name) + text += " as [ply.current.real_name]" + else + text += " had their body destroyed" + return text + +/proc/printplayerlist(list/players,fleecheck) + var/list/parts = list() + + parts += "
    " + for(var/datum/mind/M in players) + parts += "
  • [printplayer(M,fleecheck)]
  • " + parts += "
" + return parts.Join() + + +/proc/printobjectives(datum/mind/ply) + var/list/objective_parts = list() + var/count = 1 + for(var/datum/objective/objective in ply.objectives) + if(objective.check_completion()) + objective_parts += "Objective #[count]: [objective.explanation_text] Success!" + else + objective_parts += "Objective #[count]: [objective.explanation_text] Fail." + count++ + return objective_parts.Join("
") diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 881bb7fcb7..11908cbdbc 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -829,6 +829,7 @@ SUBSYSTEM_DEF(ticker) world.Reboot() /datum/controller/subsystem/ticker/Shutdown() + gather_newscaster() //called here so we ensure the log is created even upon admin reboot if(!round_end_sound) round_end_sound = pick(\ 'sound/roundend/newroundsexy.ogg', diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm index 18acd9ece7..730380225d 100644 --- a/code/game/machinery/newscaster.dm +++ b/code/game/machinery/newscaster.dm @@ -22,6 +22,7 @@ GLOBAL_LIST_EMPTY(allCasters) var/creationTime var/authorCensor var/bodyCensor + var/photo_file /datum/newscaster/feed_message/proc/returnAuthor(censor) if(censor == -1) @@ -97,6 +98,7 @@ GLOBAL_LIST_EMPTY(allCasters) var/scannedUser var/isAdminMsg var/icon/img + var/photo_file /datum/newscaster/feed_network var/list/datum/newscaster/feed_channel/network_channels = list() @@ -126,6 +128,7 @@ GLOBAL_LIST_EMPTY(allCasters) if(photo) newMsg.img = photo.img newMsg.caption = photo.scribble + newMsg.photo_file = save_photo(photo.img) for(var/datum/newscaster/feed_channel/FC in network_channels) if(FC.channel_name == channel_name) FC.messages += newMsg @@ -143,6 +146,7 @@ GLOBAL_LIST_EMPTY(allCasters) wanted_issue.isAdminMsg = adminMsg if(photo) wanted_issue.img = photo.img + wanted_issue.photo_file = save_photo(photo.img) if(newMessage) for(var/obj/machinery/newscaster/N in GLOB.allCasters) N.newsAlert() @@ -157,7 +161,12 @@ GLOBAL_LIST_EMPTY(allCasters) for(var/obj/machinery/newscaster/NEWSCASTER in GLOB.allCasters) NEWSCASTER.update_icon() - +/datum/newscaster/feed_network/proc/save_photo(icon/photo) + var/photo_file = copytext(md5("\icon[photo]"), 1, 6) + if(!fexists("[GLOB.log_directory]/photos/[photo_file].png")) + var/icon/p = icon(photo, frame = 1) + fcopy(p, "[GLOB.log_directory]/photos/[photo_file].png") + return photo_file /obj/item/wallframe/newscaster name = "newscaster frame"