Files
Paradise/code/controllers/subsystem/non_firing/SSchangelog.dm
Contrabang 3f87165a03 CI now bans files with the same name (PART 2) (#21051)
* CI change

* world.dm

* .dme world.dm

* subsystem renaming

* .dme for subsystems

* ai_laws.dm

* armor.dm

* emote.dm

* logging.dm

* spell.dm

* air_alarm.dm

* crew.dm

* decal.dm

* emissive_blocker.dm

* footstep.dm

* spawner.dm

* fire.dm

* carbon.dm

* living.dm

* mob.dm

* movement.dm

* thermal_drill.dm

* plasmamen.dm

* lavaland.dm

* chaplain.dm

* lightning.dm

* magnet.dm

* mimic.dm

* wizard.dm

* morph.dm

* vampire.dm

* click.dm

* self.dm

* radiation_storm.dm

* airlock.dm

* autolathe.dm

* mulebot.dm

* nuclearbomb.dm

* particle_accelerator.dm

* smartfridge.dm

* syndicatebomb.dm

* vending.dm

* wires.dm

* sound.dm

* mining.dm

* syndicate_space_base.dm

* monkey.dm

* guardian.dm

* bomb.dm

* standard.dm

* nuclear.dm

* pinpointer.dm

* access.dm

* departments.dm

* job.dm

* science.dm

* buttons.dm

* cloning.dm

* igniter.dm

* wishgranter.dm

* atmos_control.dm

* message.dm

* power_monitor.dm

* mecha.dm

* combat.dm

* mining_tools.dm

* meteors.dm

* spiders.dm

* contraband.dm

* aliens.dm

* uplinks.dm

* voice.dm

* intercom.dm

* lights.dm

* robot_items.dm

* mineral.dm

* dice.dm

* extinguisher.dm

* paint.dm

* signs.dm

* staff.dm

* smokebomb.dm

* boxes.dm

* random.dm

* janicart.dm

* statue.dm

* cargo.dm

* asteroid.dm

* headslug.dm

* fulton.dm

* atmospherics.dm

* pump.dm

* corpse.dm

* oldstation.dm

* gps.dm

* preferences.dm

* clothing.dm

* ears.dm

* glasses.dm

* boxing.dm

* color.dm

* renames ninja gear files

* recipes.dm

* error_handler.dm

* anomaly.dm

* floorcluwne.dm

* undead.dm

* overmind.dm

* shield.dm

* bottle.dm

* organ.dm

* piano.dm

* plasma_fist.dm

* language.dm

* mob_defines.dm

* mob_helpers.dm

* damage_procs.dm

* _defines.dm

* empress.dm and queen.dm

* brain.dm

* organ file renaming

* subsystems.dm

* constructs.dm

* bot.dm

* pet.dm

* nature.dm

* magic.dm

* colors.dm

* drugs.dm

* medicine.dm

* toxins.dm

* shuttle.dm

* surgery.dm

* moves a bunch of define files

* traits.dm

* names.dm

* other_mobs.dm

* flags.dm

* some final define files

* well turns out contractor_pinpointer.dm was  taken

* I forgot to remove this file

* how in the hell did this get unticked

* I DID INCLUDE IT, but there was a "w" there

* swaps the world definitions

* camera renamed to SScamera

* examine -> alien_examine
2023-06-02 14:30:17 -05:00

237 lines
8.7 KiB
Plaintext

/*
Subsystem core for ParadiseSS13 changelogs
Author: AffectedArc07
Basically this SS extracts changelogs from the past 30 days from the database, and cleanly formats them into HTML that the players can see
It only runs the extraction on initialize to ensure that the changelog doesnt change mid round, and to reduce the amount of DB calls that need to be done
The changelog entries are generated from the PHP scripts in tools/githubChangelogProcessor.php
This SS also handles the checking of player CL dates and informing them if it has changed
*/
SUBSYSTEM_DEF(changelog)
name = "Changelog"
flags = SS_NO_FIRE
var/current_cl_timestamp = "0" // Timestamp is seconds since UNIX epoch (1st January 1970). ITs also a string because BYOND doesnt like big numbers.
var/ss_ready = FALSE // Is the SS ready? We dont want to run procs if we have not generated yet
var/list/client/startup_clients_open = list() // Clients who connected before initialization who need the CL opening
var/list/changelog_data = list() // Parsed changelog data
/datum/controller/subsystem/changelog/Initialize()
// This entire subsystem relies on SQL being here.
if(!SSdbcore.IsConnected())
return
var/datum/db_query/latest_cl_date = SSdbcore.NewQuery("SELECT CAST(UNIX_TIMESTAMP(date_merged) AS CHAR) AS ut FROM changelog ORDER BY date_merged DESC LIMIT 1")
if(!latest_cl_date.warn_execute())
qdel(latest_cl_date)
// Abort if we cant do this
return
while(latest_cl_date.NextRow())
current_cl_timestamp = latest_cl_date.item[1]
qdel(latest_cl_date)
if(!GenerateChangelogData()) // if this failed to generate
to_chat(world, "<span class='alert'>WARNING: Changelog failed to generate. Please inform a coder/server dev</span>")
return ..()
ss_ready = TRUE
// Update buttons for those who logged in
for(var/client/C as anything in GLOB.clients)
UpdatePlayerChangelogButton(C)
// Now we can alert anyone who wanted to check the changelog
for(var/client/C as anything in startup_clients_open)
OpenChangelog(C)
/datum/controller/subsystem/changelog/proc/UpdatePlayerChangelogDate(client/C)
if(!ss_ready)
return // Only return here, we dont have to worry about a queue list because this will be called from ShowChangelog()
if(C.prefs.toggles & PREFTOGGLE_UI_DARKMODE)
winset(C, "rpane.changelog", "background-color=#40628a;font-color=#ffffff;font-style=none")
else
winset(C, "rpane.changelog", "background-color=none;font-style=none")
C.prefs.lastchangelog = current_cl_timestamp
var/datum/db_query/updatePlayerCLTime = SSdbcore.NewQuery(
"UPDATE player SET lastchangelog=:lastchangelog WHERE ckey=:ckey", list(
"lastchangelog" = current_cl_timestamp,
"ckey" = C.ckey
)
)
// We dont do anything with this query so we dont care about errors too much
updatePlayerCLTime.warn_execute()
qdel(updatePlayerCLTime)
/datum/controller/subsystem/changelog/proc/UpdatePlayerChangelogButton(client/C)
// If SQL aint even enabled, or we aint ready just set the button to default style
if(!SSdbcore.IsConnected() || !ss_ready)
if(C.prefs.toggles & PREFTOGGLE_UI_DARKMODE)
winset(C, "rpane.changelog", "background-color=#40628a;text-color=#FFFFFF")
else
winset(C, "rpane.changelog", "background-color=none;text-color=#000000")
return
// If we are ready, process the button style
if(C.prefs.lastchangelog != current_cl_timestamp)
winset(C, "rpane.changelog", "background-color=#bb7700;text-color=#FFFFFF;font-style=bold")
to_chat(C, "<span class='boldnotice'>Changelog has changed since your last visit.</span>")
else
if(C.prefs.toggles & PREFTOGGLE_UI_DARKMODE)
winset(C, "rpane.changelog", "background-color=#40628a;text-color=#FFFFFF")
else
winset(C, "rpane.changelog", "background-color=none;text-color=#000000")
/datum/controller/subsystem/changelog/proc/OpenChangelog(client/C)
// If SQL isnt enabled, dont even queue them, just tell them it wont work
if(!SSdbcore.IsConnected())
to_chat(C, "<span class='notice'>This server is not running with an SQL backend. Changelog is unavailable.</span>")
return
// If SQL is enabled but we aint ready, queue them up
if(!ss_ready)
startup_clients_open |= C
to_chat(C, "<span class='notice'>The changelog system is still initializing. The changelog will open for you once it has initialized.</span>")
return
UpdatePlayerChangelogDate(C)
UpdatePlayerChangelogButton(C)
ui_interact(C.mob)
/client/verb/changes()
set name = "Changelog"
set desc = "View the changelog."
set category = "OOC"
// Just invoke the actual CL thing
SSchangelog.OpenChangelog(src)
// This proc is the star of the show
/datum/controller/subsystem/changelog/proc/GenerateChangelogData()
// This value will be returned if the proc crashes
. = FALSE
var/list/prs_to_process = list()
// Grab all from last 30 days
var/datum/db_query/pr_list_query = SSdbcore.NewQuery("SELECT DISTINCT pr_number FROM changelog WHERE date_merged BETWEEN NOW() - INTERVAL 30 DAY AND NOW() ORDER BY date_merged DESC")
if(!pr_list_query.warn_execute())
qdel(pr_list_query)
return FALSE
while(pr_list_query.NextRow())
prs_to_process += text2num(pr_list_query.item[1])
qdel(pr_list_query)
// We put all these queries into a list so we can batch-execute them to avoid excess delays
// We index these based on PR numbers. MAKE SURE YOU USE STRING INDICIES IN THIS IF YOU EVER TWEAK IT -aa
var/list/datum/db_query/meta_queries = list()
var/list/datum/db_query/entry_queries = list()
// Create some queries for each PR
for(var/pr_number in prs_to_process)
var/datum/db_query/pr_meta = SSdbcore.NewQuery(
"SELECT author, DATE_FORMAT(date_merged, '%Y-%m-%d at %T') AS date, CAST(UNIX_TIMESTAMP(date_merged) AS CHAR) AS ts FROM changelog WHERE pr_number = :prnum LIMIT 1",
list("prnum" = pr_number)
)
// MAKE SURE YOU CAST TO STRINGS HERE!
meta_queries["[pr_number]"] = pr_meta
var/datum/db_query/pr_cl_entries = SSdbcore.NewQuery(
"SELECT cl_type, cl_entry FROM changelog WHERE pr_number = :prnum",
list("prnum" = pr_number)
)
// And here
entry_queries["[pr_number]"] = pr_cl_entries
ASSERT(length(meta_queries) == length(entry_queries)) // If these dont add up, something went very wrong
// Explanation for parameters:
// TRUE: We want warnings if these fail
// FALSE: Do NOT qdel() queries here, otherwise they wont be read. At all.
// TRUE: This is an assoc list, so it needs to prepare for that
SSdbcore.MassExecute(meta_queries, TRUE, FALSE, TRUE)
SSdbcore.MassExecute(entry_queries, TRUE, FALSE, TRUE)
for(var/pr_number in prs_to_process)
var/list/this_pr = list()
this_pr["num"] = pr_number
// Assemble metadata
while(meta_queries["[pr_number]"].NextRow())
this_pr["author"] = meta_queries["[pr_number]"].item[1]
this_pr["merge_date"] = meta_queries["[pr_number]"].item[2]
this_pr["merge_ts"] = meta_queries["[pr_number]"].item[3]
var/list/cl_entries = list()
while(entry_queries["[pr_number]"].NextRow())
var/list/this_entry = list()
this_entry["etype"] = entry_queries["[pr_number]"].item[1]
this_entry["etext"] = entry_queries["[pr_number]"].item[2]
cl_entries += list(this_entry) // Double list required or it merges them
this_pr["entries"] = cl_entries
changelog_data += list(this_pr)
// Cleanup queries
QDEL_LIST_ASSOC_VAL(meta_queries)
QDEL_LIST_ASSOC_VAL(entry_queries)
// Make sure we return TRUE so we know it worked
return TRUE
/datum/controller/subsystem/changelog/ui_static_data(mob/user)
var/list/data = list()
data["cl_data"] = changelog_data
data["last_cl"] = user.client.prefs.lastchangelog_2
return data
/datum/controller/subsystem/changelog/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "ChangelogView", name, 750, 800, master_ui, state)
ui.set_autoupdate(FALSE)
ui.open()
// Topic handler so that PRs and forums and stuff open in another window
/datum/controller/subsystem/changelog/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
return
. = TRUE
switch(action)
// Takes a PR number as argument
if("open_pr")
var/pr_num = params["pr_number"]
if(GLOB.configuration.url.github_url)
if(alert("This will open PR #[pr_num] in your browser. Are you sure?", "Open PR", "Yes", "No") == "No")
return
// If the github URL in the config has a trailing slash, it doesnt matter here, thankfully github accepts having a double slash: https://github.com/org/repo//pull/1
var/url = "[GLOB.configuration.url.github_url]/pull/[pr_num]"
usr << link(url)
return
to_chat(usr, "<span class='danger'>The GitHub URL is not set in the server configuration. PRs cannot be opened from changelog view. Please inform the server host.</span>")