mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-07-15 17:15:25 +01:00
Unit Test rework & Master/Ticker update (#17912)
* Unit Test rework & Master/Ticker update * Fixes and working unit testing * Fixes * Test fixes and FA update * Fixed runtimes * Radio subsystem * move that glob wherever later * ident * CIBUILDING compile option * Fixed runtimes * Some changes to the workflow * CI Split * More split * Pathing * Linters and Annotators * ci dir fix * Missing undef fixed * Enable grep checks * More test conversions * More split * Correct file * Removes unneeded inputs * oop * More dependency changes * More conversions * Conversion fixes * Fixes * Some assert fixes * Corrects start gate * Converted some README.dms to README.mds * Removes duplicate proc * Removes unused defines * Example configs * fix dll access viol by double calling * Post-rebase fixes * Cleans up names global list * Undef restart counter * More code/game/ cleanup * Statpanel update * Skybox * add * Fix ticker * Roundend fix * Persistence dependency update * Reordering * Reordering * Reordering * Initstage fix * . * . * Reorder * Reorder * Circle * Mobs * Air * Test fix * CI Script Fix * Configs * More ticker stuff * This is now in 'reboot world' * Restart world announcements * no glob in PreInit * to define * Update * Removed old include * Make this file normal again * moved * test * shared unit testing objects * Updates batched_spritesheets and universal_icon * . * job data debug * rm that * init order * show us * . * i wonder * . * . * urg * do we not have a job ID? * . * rm sleep for now * updated rust-g linux binaries * binaries update 2 * binaries update 3 * testing something * change that * test something * . * . * . * locavar * test * move that * . * debug * don't run this test * strack trace it * cleaner * . * . * cras again * also comment this out * return to official rust g * Update robot_icons.dm * monitor the generation * . --------- Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
+99
-77
@@ -625,29 +625,69 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_HOLDER, "Show Player Panel", m
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge
|
||||
//i.e. buttons/verbs
|
||||
|
||||
#define REGULAR_RESTART "Regular Restart"
|
||||
#define REGULAR_RESTART_DELAYED "Regular Restart (with delay)"
|
||||
#define HARD_RESTART "Hard Restart (No Delay/Feedback Reason)"
|
||||
#define HARDEST_RESTART "Hardest Restart (No actions, just reboot)"
|
||||
#define TGS_RESTART "Server Restart (Kill and restart DD)"
|
||||
ADMIN_VERB(restart, R_SERVER, "Reboot World", "Restarts the world immediately.", "Server.Game")
|
||||
var/list/options = list(REGULAR_RESTART, REGULAR_RESTART_DELAYED, HARD_RESTART)
|
||||
|
||||
/datum/admins/proc/restart()
|
||||
set category = "Server.Game"
|
||||
set name = "Restart"
|
||||
set desc="Restarts the world"
|
||||
if (!check_rights_for(usr.client, R_HOLDER))
|
||||
// this option runs a codepath that can leak db connections because it skips subsystem (specifically SSdbcore) shutdown
|
||||
if(!SSdbcore.IsConnected())
|
||||
options += HARDEST_RESTART
|
||||
|
||||
if(world.TgsAvailable())
|
||||
options += TGS_RESTART;
|
||||
|
||||
if(SSticker.admin_delay_notice)
|
||||
if(alert(user, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") != "Yes")
|
||||
return FALSE
|
||||
|
||||
var/result = input(user, "Select reboot method", "World Reboot", options[1]) as null|anything in options
|
||||
if(isnull(result))
|
||||
return
|
||||
var/confirm = alert(usr, "Restart the game world?", "Restart", "Yes", "Cancel") // Not tgui_alert for safety
|
||||
if(!confirm || confirm == "Cancel")
|
||||
|
||||
feedback_add_details("admin_verb","R")
|
||||
if(blackbox)
|
||||
blackbox.save_all_data_to_sql()
|
||||
|
||||
var/init_by = "Initiated by [user.holder.fakekey ? "Admin" : user.key]."
|
||||
switch(result)
|
||||
if(REGULAR_RESTART)
|
||||
if(!user.is_localhost())
|
||||
if(alert(user, "Are you sure you want to restart the server?","This server is live", "Restart", "Cancel") != "Restart")
|
||||
return FALSE
|
||||
SSticker.Reboot(init_by, "admin reboot - by [user.key] [user.holder.fakekey ? "(stealth)" : ""]", 10)
|
||||
if(REGULAR_RESTART_DELAYED)
|
||||
var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null
|
||||
if(!delay)
|
||||
return FALSE
|
||||
if(!user.is_localhost())
|
||||
if(alert(user,"Are you sure you want to restart the server?","This server is live", "Restart", "Cancel") != "Restart")
|
||||
return FALSE
|
||||
SSticker.Reboot(init_by, "admin reboot - by [user.key] [user.holder.fakekey ? "(stealth)" : ""]", delay * 10)
|
||||
if(HARD_RESTART)
|
||||
to_chat(world, "World reboot - [init_by]")
|
||||
world.Reboot()
|
||||
if(HARDEST_RESTART)
|
||||
to_chat(world, "Hard world reboot - [init_by]")
|
||||
world.Reboot(fast_track = TRUE)
|
||||
if(TGS_RESTART)
|
||||
to_chat(world, "Server restart - [init_by]")
|
||||
world.TgsEndProcess()
|
||||
|
||||
#undef REGULAR_RESTART
|
||||
#undef REGULAR_RESTART_DELAYED
|
||||
#undef HARD_RESTART
|
||||
#undef HARDEST_RESTART
|
||||
#undef TGS_RESTART
|
||||
|
||||
ADMIN_VERB(cancel_reboot, R_SERVER, "Cancel Reboot", "Cancels a pending world reboot.", "Server.Game")
|
||||
if(!SSticker.cancel_reboot(user))
|
||||
return
|
||||
if(confirm == "Yes")
|
||||
to_world(span_danger("Restarting world!" ) + span_notice("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]!"))
|
||||
log_admin("[key_name(usr)] initiated a reboot.")
|
||||
|
||||
feedback_set_details("end_error","admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]")
|
||||
feedback_add_details("admin_verb","R") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
if(blackbox)
|
||||
blackbox.save_all_data_to_sql()
|
||||
|
||||
sleep(50)
|
||||
world.Reboot()
|
||||
|
||||
log_admin("[key_name(user)] cancelled the pending world reboot.")
|
||||
message_admins("[key_name_admin(user)] cancelled the pending world reboot.")
|
||||
|
||||
/datum/admins/proc/announce()
|
||||
set category = "Admin.Chat"
|
||||
@@ -880,7 +920,7 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
if(!SSticker.start_immediately)
|
||||
SSticker.start_immediately = TRUE
|
||||
var/msg = ""
|
||||
if(SSticker.current_state == GAME_STATE_INIT)
|
||||
if(SSticker.current_state == GAME_STATE_STARTUP)
|
||||
msg = " (The server is still setting up, but the round will be started as soon as possible.)"
|
||||
log_admin("[key_name(usr)] has started the game.[msg]")
|
||||
message_admins(span_notice("[key_name_admin(usr)] has started the game.[msg]"))
|
||||
@@ -1017,24 +1057,6 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
message_admins(span_blue("Toggled reviving to [CONFIG_GET(flag/allow_admin_rev)]."))
|
||||
feedback_add_details("admin_verb","TAR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/datum/admins/proc/immreboot()
|
||||
set category = "Server.Game"
|
||||
set desc="Reboots the server post haste"
|
||||
set name="Immediate Reboot"
|
||||
if(!check_rights_for(usr.client, R_HOLDER)) return
|
||||
if(alert(usr, "Reboot server?","Reboot!","Yes","No") != "Yes") // Not tgui_alert for safety
|
||||
return
|
||||
to_world(span_filter_system("[span_red(span_bold("Rebooting world!"))] [span_blue("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]!")]"))
|
||||
log_admin("[key_name(usr)] initiated an immediate reboot.")
|
||||
|
||||
feedback_set_details("end_error","immediate admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]")
|
||||
feedback_add_details("admin_verb","IR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
if(blackbox)
|
||||
blackbox.save_all_data_to_sql()
|
||||
|
||||
world.Reboot()
|
||||
|
||||
/datum/admins/proc/unprison(var/mob/M in GLOB.mob_list)
|
||||
set category = "Admin.Moderation"
|
||||
set name = "Unprison"
|
||||
@@ -1052,7 +1074,7 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS
|
||||
|
||||
/proc/is_special_character(var/character) // returns 1 for special characters and 2 for heroes of gamemode
|
||||
if(!ticker || !ticker.mode)
|
||||
if(!SSticker|| !SSticker.mode)
|
||||
return 0
|
||||
var/datum/mind/M
|
||||
if (ismob(character))
|
||||
@@ -1062,8 +1084,8 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
M = character
|
||||
|
||||
if(M)
|
||||
if(ticker.mode.antag_templates && ticker.mode.antag_templates.len)
|
||||
for(var/datum/antagonist/antag in ticker.mode.antag_templates)
|
||||
if(SSticker.mode.antag_templates && SSticker.mode.antag_templates.len)
|
||||
for(var/datum/antagonist/antag in SSticker.mode.antag_templates)
|
||||
if(antag.is_antagonist(M))
|
||||
return 2
|
||||
if(M.special_role)
|
||||
@@ -1197,70 +1219,70 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
set desc = "Show the current round configuration."
|
||||
set name = "Show Game Mode"
|
||||
|
||||
if(!ticker || !ticker.mode)
|
||||
if(!SSticker|| !SSticker.mode)
|
||||
tgui_alert_async(usr, "Not before roundstart!", "Alert")
|
||||
return
|
||||
|
||||
var/out = span_large(span_bold("Current mode: [ticker.mode.name] (<a href='byond://?src=\ref[ticker.mode];[HrefToken()];debug_antag=self'>[ticker.mode.config_tag]</a>)")) + "<br/>"
|
||||
var/out = span_large(span_bold("Current mode: [SSticker.mode.name] (<a href='byond://?src=\ref[SSticker.mode];[HrefToken()];debug_antag=self'>[SSticker.mode.config_tag]</a>)")) + "<br/>"
|
||||
out += "<hr>"
|
||||
|
||||
if(ticker.mode.ert_disabled)
|
||||
out += span_bold("Emergency Response Teams:") + "<a href='byond://?src=\ref[ticker.mode];[HrefToken()];toggle=ert'>disabled</a>"
|
||||
if(SSticker.mode.ert_disabled)
|
||||
out += span_bold("Emergency Response Teams:") + "<a href='byond://?src=\ref[SSticker.mode];[HrefToken()];toggle=ert'>disabled</a>"
|
||||
else
|
||||
out += span_bold("Emergency Response Teams:") + "<a href='byond://?src=\ref[ticker.mode];[HrefToken()];toggle=ert'>enabled</a>"
|
||||
out += span_bold("Emergency Response Teams:") + "<a href='byond://?src=\ref[SSticker.mode];[HrefToken()];toggle=ert'>enabled</a>"
|
||||
out += "<br/>"
|
||||
|
||||
if(ticker.mode.deny_respawn)
|
||||
out += span_bold("Respawning:") + "<a href='byond://?src=\ref[ticker.mode];[HrefToken()];toggle=respawn'>disallowed</a>"
|
||||
if(SSticker.mode.deny_respawn)
|
||||
out += span_bold("Respawning:") + "<a href='byond://?src=\ref[SSticker.mode];[HrefToken()];toggle=respawn'>disallowed</a>"
|
||||
else
|
||||
out += span_bold("Respawning:") + "<a href='byond://?src=\ref[ticker.mode];[HrefToken()];toggle=respawn'>allowed</a>"
|
||||
out += span_bold("Respawning:") + "<a href='byond://?src=\ref[SSticker.mode];[HrefToken()];toggle=respawn'>allowed</a>"
|
||||
out += "<br/>"
|
||||
|
||||
out += span_bold("Shuttle delay multiplier:") + " <a href='byond://?src=\ref[ticker.mode];[HrefToken()];set=shuttle_delay'>[ticker.mode.shuttle_delay]</a><br/>"
|
||||
out += span_bold("Shuttle delay multiplier:") + " <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];set=shuttle_delay'>[SSticker.mode.shuttle_delay]</a><br/>"
|
||||
|
||||
if(ticker.mode.auto_recall_shuttle)
|
||||
out += span_bold("Shuttle auto-recall:") + " <a href='byond://?src=\ref[ticker.mode];[HrefToken()];toggle=shuttle_recall'>enabled</a>"
|
||||
if(SSticker.mode.auto_recall_shuttle)
|
||||
out += span_bold("Shuttle auto-recall:") + " <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];toggle=shuttle_recall'>enabled</a>"
|
||||
else
|
||||
out += span_bold("Shuttle auto-recall:") + " <a href='byond://?src=\ref[ticker.mode];[HrefToken()];toggle=shuttle_recall'>disabled</a>"
|
||||
out += span_bold("Shuttle auto-recall:") + " <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];toggle=shuttle_recall'>disabled</a>"
|
||||
out += "<br/><br/>"
|
||||
|
||||
if(ticker.mode.event_delay_mod_moderate)
|
||||
out += span_bold("Moderate event time modifier:") + " <a href='byond://?src=\ref[ticker.mode];[HrefToken()];set=event_modifier_moderate'>[ticker.mode.event_delay_mod_moderate]</a><br/>"
|
||||
if(SSticker.mode.event_delay_mod_moderate)
|
||||
out += span_bold("Moderate event time modifier:") + " <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];set=event_modifier_moderate'>[SSticker.mode.event_delay_mod_moderate]</a><br/>"
|
||||
else
|
||||
out += span_bold("Moderate event time modifier:") + " <a href='byond://?src=\ref[ticker.mode];[HrefToken()];set=event_modifier_moderate'>unset</a><br/>"
|
||||
out += span_bold("Moderate event time modifier:") + " <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];set=event_modifier_moderate'>unset</a><br/>"
|
||||
|
||||
if(ticker.mode.event_delay_mod_major)
|
||||
out += span_bold("Major event time modifier:") + " <a href='byond://?src=\ref[ticker.mode];[HrefToken()];set=event_modifier_severe'>[ticker.mode.event_delay_mod_major]</a><br/>"
|
||||
if(SSticker.mode.event_delay_mod_major)
|
||||
out += span_bold("Major event time modifier:") + " <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];set=event_modifier_severe'>[SSticker.mode.event_delay_mod_major]</a><br/>"
|
||||
else
|
||||
out += span_bold("Major event time modifier:") + " <a href='byond://?src=\ref[ticker.mode];[HrefToken()];set=event_modifier_severe'>unset</a><br/>"
|
||||
out += span_bold("Major event time modifier:") + " <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];set=event_modifier_severe'>unset</a><br/>"
|
||||
|
||||
out += "<hr>"
|
||||
|
||||
if(ticker.mode.antag_tags && ticker.mode.antag_tags.len)
|
||||
if(SSticker.mode.antag_tags && SSticker.mode.antag_tags.len)
|
||||
out += span_bold("Core antag templates:") + "</br>"
|
||||
for(var/antag_tag in ticker.mode.antag_tags)
|
||||
out += "<a href='byond://?src=\ref[ticker.mode];[HrefToken()];debug_antag=[antag_tag]'>[antag_tag]</a>.</br>"
|
||||
for(var/antag_tag in SSticker.mode.antag_tags)
|
||||
out += "<a href='byond://?src=\ref[SSticker.mode];[HrefToken()];debug_antag=[antag_tag]'>[antag_tag]</a>.</br>"
|
||||
|
||||
if(ticker.mode.round_autoantag)
|
||||
out += span_bold("Autotraitor <a href='byond://?src=\ref[ticker.mode];[HrefToken()];toggle=autotraitor'>enabled</a>.")
|
||||
if(ticker.mode.antag_scaling_coeff > 0)
|
||||
out += " (scaling with <a href='byond://?src=\ref[ticker.mode];[HrefToken()];set=antag_scaling'>[ticker.mode.antag_scaling_coeff]</a>)"
|
||||
if(SSticker.mode.round_autoantag)
|
||||
out += span_bold("Autotraitor <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];toggle=autotraitor'>enabled</a>.")
|
||||
if(SSticker.mode.antag_scaling_coeff > 0)
|
||||
out += " (scaling with <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];set=antag_scaling'>[SSticker.mode.antag_scaling_coeff]</a>)"
|
||||
else
|
||||
out += " (not currently scaling, <a href='byond://?src=\ref[ticker.mode];[HrefToken()];set=antag_scaling'>set a coefficient</a>)"
|
||||
out += " (not currently scaling, <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];set=antag_scaling'>set a coefficient</a>)"
|
||||
out += "<br/>"
|
||||
else
|
||||
out += span_bold("Autotraitor <a href='byond://?src=\ref[ticker.mode];[HrefToken()];toggle=autotraitor'>disabled</a>.") + "<br/>"
|
||||
out += span_bold("Autotraitor <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];toggle=autotraitor'>disabled</a>.") + "<br/>"
|
||||
|
||||
out += span_bold("All antag ids:")
|
||||
if(ticker.mode.antag_templates && ticker.mode.antag_templates.len)
|
||||
for(var/datum/antagonist/antag in ticker.mode.antag_templates)
|
||||
if(SSticker.mode.antag_templates && SSticker.mode.antag_templates.len)
|
||||
for(var/datum/antagonist/antag in SSticker.mode.antag_templates)
|
||||
antag.update_current_antag_max()
|
||||
out += " <a href='byond://?src=\ref[ticker.mode];[HrefToken()];debug_antag=[antag.id]'>[antag.id]</a>"
|
||||
out += " <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];debug_antag=[antag.id]'>[antag.id]</a>"
|
||||
out += " ([antag.get_antag_count()]/[antag.cur_max]) "
|
||||
out += " <a href='byond://?src=\ref[ticker.mode];[HrefToken()];remove_antag_type=[antag.id]'>\[-\]</a><br/>"
|
||||
out += " <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];remove_antag_type=[antag.id]'>\[-\]</a><br/>"
|
||||
else
|
||||
out += " None."
|
||||
out += " <a href='byond://?src=\ref[ticker.mode];[HrefToken()];add_antag_type=1'>\[+\]</a><br/>"
|
||||
out += " <a href='byond://?src=\ref[SSticker.mode];[HrefToken()];add_antag_type=1'>\[+\]</a><br/>"
|
||||
|
||||
var/datum/browser/popup = new(owner, "edit_mode[src]", "Edit Game Mode")
|
||||
popup.set_content(out)
|
||||
@@ -1404,7 +1426,7 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
to_chat(usr, "Error: you are not an admin!")
|
||||
return
|
||||
|
||||
if(!ticker || !ticker.mode)
|
||||
if(!SSticker|| !SSticker.mode)
|
||||
to_chat(usr, "Mode has not started.")
|
||||
return
|
||||
|
||||
@@ -1428,12 +1450,12 @@ var/datum/announcement/minor/admin_min_announcer = new
|
||||
to_chat(usr, "Error: you are not an admin!")
|
||||
return
|
||||
|
||||
if(!ticker || !ticker.mode)
|
||||
if(!SSticker|| !SSticker.mode)
|
||||
to_chat(usr, "Mode has not started.")
|
||||
return
|
||||
|
||||
log_and_message_admins("attempting to force mode autospawn.")
|
||||
ticker.mode.try_latespawn()
|
||||
SSticker.mode.try_latespawn()
|
||||
|
||||
/datum/admins/proc/paralyze_mob(mob/living/H as mob)
|
||||
set category = "Admin.Events"
|
||||
|
||||
@@ -213,7 +213,11 @@ GLOBAL_PROTECT(protected_ranks)
|
||||
|
||||
/// (Re)Loads the admin list.
|
||||
/// returns TRUE if database admins had to be loaded from the backup json
|
||||
/proc/load_admins(no_update)
|
||||
/proc/load_admins(no_update, initial = FALSE)
|
||||
if(!initial)
|
||||
if(!global.config.PreConfigReload())
|
||||
return
|
||||
|
||||
var/dbfail
|
||||
if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect())
|
||||
message_admins("Failed to connect to database while loading admins. Loading from backup.")
|
||||
|
||||
@@ -18,7 +18,6 @@ var/list/admin_verbs_admin = list(
|
||||
/client/proc/player_panel,
|
||||
/client/proc/hide_verbs, //hides all our adminverbs,
|
||||
/client/proc/hide_most_verbs, //hides all our hideable adminverbs,
|
||||
/client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify,
|
||||
/client/proc/cmd_check_new_players, //allows us to see every new player,
|
||||
/client/proc/toggle_view_range, //changes how far we can see,
|
||||
/client/proc/cmd_admin_pm_context, //right-click adminPM interface,
|
||||
@@ -103,7 +102,6 @@ var/list/admin_verbs_fun = list(
|
||||
/datum/admins/proc/cmd_admin_dress,
|
||||
/client/proc/drop_bomb,
|
||||
/client/proc/everyone_random,
|
||||
/client/proc/cinematic,
|
||||
/datum/admins/proc/toggle_aliens,
|
||||
/datum/admins/proc/toggle_space_ninja,
|
||||
/client/proc/cmd_admin_add_freeform_ai_law,
|
||||
@@ -152,12 +150,10 @@ var/list/admin_verbs_server = list(
|
||||
/client/proc/Set_Holiday,
|
||||
/client/proc/ToRban,
|
||||
/datum/admins/proc/startnow,
|
||||
/datum/admins/proc/restart,
|
||||
/datum/admins/proc/delay,
|
||||
/datum/admins/proc/toggleaban,
|
||||
/datum/admins/proc/togglepersistence,
|
||||
/client/proc/toggle_log_hrefs,
|
||||
/datum/admins/proc/immreboot,
|
||||
/client/proc/everyone_random,
|
||||
/datum/admins/proc/toggleAI,
|
||||
/client/proc/cmd_admin_delete, //delete an instance/object/mob/etc,
|
||||
@@ -194,7 +190,6 @@ var/list/admin_verbs_debug = list(
|
||||
/client/proc/air_report,
|
||||
/client/proc/reload_admins,
|
||||
/client/proc/reload_eventMs,
|
||||
/datum/admins/proc/restart,
|
||||
/client/proc/print_random_map,
|
||||
/client/proc/create_random_map,
|
||||
/client/proc/apply_random_map,
|
||||
@@ -211,7 +206,6 @@ var/list/admin_verbs_debug = list(
|
||||
/client/proc/player_panel,
|
||||
/client/proc/hide_verbs, //hides all our adminverbs,
|
||||
/client/proc/hide_most_verbs, //hides all our hideable adminverbs,
|
||||
/client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify,
|
||||
/client/proc/cmd_check_new_players, //allows us to see every new player,
|
||||
/datum/admins/proc/view_runtimes,
|
||||
// /client/proc/show_gm_status, //We don't use SSgame_master yet.
|
||||
@@ -254,7 +248,6 @@ var/list/admin_verbs_hideable = list(
|
||||
/client/proc/object_talk,
|
||||
/datum/admins/proc/cmd_admin_dress,
|
||||
/client/proc/drop_bomb,
|
||||
/client/proc/cinematic,
|
||||
/datum/admins/proc/toggle_aliens,
|
||||
/datum/admins/proc/toggle_space_ninja,
|
||||
/client/proc/cmd_admin_add_freeform_ai_law,
|
||||
@@ -266,11 +259,9 @@ var/list/admin_verbs_hideable = list(
|
||||
/client/proc/Set_Holiday,
|
||||
/client/proc/ToRban,
|
||||
/datum/admins/proc/startnow,
|
||||
/datum/admins/proc/restart,
|
||||
/datum/admins/proc/delay,
|
||||
/datum/admins/proc/toggleaban,
|
||||
/client/proc/toggle_log_hrefs,
|
||||
/datum/admins/proc/immreboot,
|
||||
/client/proc/everyone_random,
|
||||
/datum/admins/proc/toggleAI,
|
||||
/datum/admins/proc/adrev,
|
||||
@@ -299,14 +290,12 @@ var/list/admin_verbs_hideable = list(
|
||||
var/list/admin_verbs_mod = list(
|
||||
/client/proc/cmd_admin_pm_context, //right-click adminPM interface,
|
||||
/client/proc/cmd_admin_pm_panel, //admin-pm list,
|
||||
/client/proc/debug_variables, //allows us to -see- the variables of any instance in the game.,
|
||||
/datum/admins/proc/PlayerNotes,
|
||||
/client/proc/admin_ghost, //allows us to ghost/reenter body at will,
|
||||
/client/proc/player_panel_new, //shows an interface for all players, with links to various panels,
|
||||
/client/proc/player_panel,
|
||||
/client/proc/hide_verbs, //hides all our adminverbs,
|
||||
/client/proc/hide_most_verbs, //hides all our hideable adminverbs,
|
||||
/client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify,
|
||||
/client/proc/cmd_check_new_players, //allows us to see every new player,
|
||||
/datum/admins/proc/show_player_info,
|
||||
/datum/admins/proc/show_traitor_panel,
|
||||
@@ -332,12 +321,10 @@ var/list/admin_verbs_event_manager = list(
|
||||
/client/proc/player_panel,
|
||||
/client/proc/hide_verbs, //hides all our adminverbs,
|
||||
/client/proc/hide_most_verbs, //hides all our hideable adminverbs,
|
||||
/client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify,
|
||||
/client/proc/cmd_check_new_players, //allows us to see every new player,
|
||||
/datum/admins/proc/show_player_info,
|
||||
/client/proc/dsay,
|
||||
/client/proc/cmd_admin_subtle_message,
|
||||
/client/proc/debug_variables,
|
||||
/client/proc/check_antagonists,
|
||||
/client/proc/aooc,
|
||||
/datum/admins/proc/paralyze_mob,
|
||||
@@ -439,9 +426,7 @@ var/list/admin_verbs_event_manager = list(
|
||||
/datum/admins/proc/capture_map,
|
||||
/client/proc/Set_Holiday,
|
||||
/datum/admins/proc/startnow,
|
||||
/datum/admins/proc/restart,
|
||||
/datum/admins/proc/delay,
|
||||
/datum/admins/proc/immreboot,
|
||||
/client/proc/everyone_random,
|
||||
/client/proc/cmd_admin_delete, //delete an instance/object/mob/etc,
|
||||
/client/proc/cmd_debug_del_all,
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
target.radio.syndie = 1
|
||||
target.module.channels += list("[selected_radio_channel]" = 1)
|
||||
target.radio.channels[selected_radio_channel] = target.module.channels[selected_radio_channel]
|
||||
target.radio.secure_radio_connections[selected_radio_channel] = radio_controller.add_object(target.radio, radiochannels[selected_radio_channel], RADIO_CHAT)
|
||||
target.radio.secure_radio_connections[selected_radio_channel] = SSradio.add_object(target.radio, radiochannels[selected_radio_channel], RADIO_CHAT)
|
||||
return TRUE
|
||||
if("rem_channel")
|
||||
var/selected_radio_channel = params["channel"]
|
||||
@@ -404,7 +404,7 @@
|
||||
target.radio.channels = list()
|
||||
for(var/n_chan in target.module.channels)
|
||||
target.radio.channels[n_chan] = target.module.channels[n_chan]
|
||||
radio_controller.remove_object(target.radio, radiochannels[selected_radio_channel])
|
||||
SSradio.remove_object(target.radio, radiochannels[selected_radio_channel])
|
||||
target.radio.secure_radio_connections -= selected_radio_channel
|
||||
return TRUE
|
||||
if("add_component")
|
||||
|
||||
@@ -390,9 +390,9 @@
|
||||
|
||||
|
||||
/datum/admins/proc/check_antagonists()
|
||||
if (ticker && ticker.current_state >= GAME_STATE_PLAYING)
|
||||
if (SSticker && SSticker.current_state >= GAME_STATE_PLAYING)
|
||||
var/dat = "<html><head><title>Round Status</title></head><body><h1>" + span_bold("Round Status") + "</h1>"
|
||||
dat += "Current Game Mode: " + span_bold("[ticker.mode.name]") + "<BR>"
|
||||
dat += "Current Game Mode: " + span_bold("[SSticker.mode.name]") + "<BR>"
|
||||
dat += "Round Duration: " + span_bold("[roundduration2text()]") + "<BR>"
|
||||
dat += span_bold("Emergency shuttle") + "<BR>"
|
||||
if (!emergency_shuttle.online())
|
||||
@@ -410,7 +410,7 @@
|
||||
if (emergency_shuttle.shuttle.moving_status == SHUTTLE_WARMUP)
|
||||
dat += "Launching now..."
|
||||
|
||||
dat += "<a href='byond://?src=\ref[src];[HrefToken()];delay_round_end=1'>[ticker.delay_end ? "End Round Normally" : "Delay Round End"]</a><br>"
|
||||
dat += "<a href='byond://?src=\ref[src];[HrefToken()];delay_round_end=1'>[SSticker.delay_end ? "End Round Normally" : "Delay Round End"]</a><br>"
|
||||
dat += "<hr>"
|
||||
for(var/antag_type in GLOB.all_antag_types)
|
||||
var/datum/antagonist/A = GLOB.all_antag_types[antag_type]
|
||||
|
||||
+12
-12
@@ -25,7 +25,7 @@
|
||||
if(!CheckAdminHref(href, href_list))
|
||||
return
|
||||
|
||||
if(ticker.mode && ticker.mode.check_antagonists_topic(href, href_list))
|
||||
if(SSticker.mode && SSticker.mode.check_antagonists_topic(href, href_list))
|
||||
check_antagonists()
|
||||
return
|
||||
|
||||
@@ -66,13 +66,13 @@
|
||||
else if(href_list["call_shuttle"])
|
||||
if(!check_rights(R_ADMIN|R_EVENT)) return
|
||||
|
||||
if( ticker.mode.name == "blob" )
|
||||
if( SSticker.mode.name == "blob" )
|
||||
tgui_alert_async(usr, "You can't call the shuttle during blob!")
|
||||
return
|
||||
|
||||
switch(href_list["call_shuttle"])
|
||||
if("1")
|
||||
if ((!( ticker ) || !emergency_shuttle.location()))
|
||||
if ((!( SSticker ) || !emergency_shuttle.location()))
|
||||
return
|
||||
if (emergency_shuttle.can_call())
|
||||
emergency_shuttle.call_evac()
|
||||
@@ -80,7 +80,7 @@
|
||||
message_admins(span_blue("[key_name_admin(usr)] called the Emergency Shuttle to the station."), 1)
|
||||
|
||||
if("2")
|
||||
if (!( ticker ) || !emergency_shuttle.location())
|
||||
if (!( SSticker ) || !emergency_shuttle.location())
|
||||
return
|
||||
if (emergency_shuttle.can_call())
|
||||
emergency_shuttle.call_evac()
|
||||
@@ -119,9 +119,9 @@
|
||||
else if(href_list["delay_round_end"])
|
||||
if(!check_rights(R_SERVER)) return
|
||||
|
||||
ticker.delay_end = !ticker.delay_end
|
||||
log_admin("[key_name(usr)] [ticker.delay_end ? "delayed the round end" : "has made the round end normally"].")
|
||||
message_admins(span_blue("[key_name(usr)] [ticker.delay_end ? "delayed the round end" : "has made the round end normally"]."), 1)
|
||||
SSticker.delay_end = !SSticker.delay_end
|
||||
log_admin("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].")
|
||||
message_admins(span_blue("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"]."), 1)
|
||||
href_list["secretsadmin"] = "check_antagonist"
|
||||
|
||||
else if(href_list["simplemake"])
|
||||
@@ -832,7 +832,7 @@
|
||||
else if(href_list["c_mode"])
|
||||
if(!check_rights(R_ADMIN|R_EVENT)) return
|
||||
|
||||
if(ticker && ticker.mode)
|
||||
if(SSticker && SSticker.mode)
|
||||
return tgui_alert_async(usr, "The game has already started.")
|
||||
var/dat = {"<B>What mode do you wish to play?</B><HR>"}
|
||||
for(var/mode in config.modes)
|
||||
@@ -845,7 +845,7 @@
|
||||
else if(href_list["f_secret"])
|
||||
if(!check_rights(R_ADMIN|R_EVENT)) return
|
||||
|
||||
if(ticker && ticker.mode)
|
||||
if(SSticker && SSticker.mode)
|
||||
return tgui_alert_async(usr, "The game has already started.")
|
||||
if(GLOB.master_mode != "secret")
|
||||
return tgui_alert_async(usr, "The game mode has to be secret!")
|
||||
@@ -859,7 +859,7 @@
|
||||
else if(href_list["c_mode2"])
|
||||
if(!check_rights(R_ADMIN|R_SERVER|R_EVENT)) return
|
||||
|
||||
if (ticker && ticker.mode)
|
||||
if (SSticker && SSticker.mode)
|
||||
return tgui_alert_async(usr, "The game has already started.")
|
||||
GLOB.master_mode = href_list["c_mode2"]
|
||||
log_admin("[key_name(usr)] set the mode as [config.mode_names[GLOB.master_mode]].")
|
||||
@@ -872,7 +872,7 @@
|
||||
else if(href_list["f_secret2"])
|
||||
if(!check_rights(R_ADMIN|R_SERVER|R_EVENT)) return
|
||||
|
||||
if(ticker && ticker.mode)
|
||||
if(SSticker && SSticker.mode)
|
||||
return tgui_alert_async(usr, "The game has already started.")
|
||||
if(GLOB.master_mode != "secret")
|
||||
return tgui_alert_async(usr, "The game mode has to be secret!")
|
||||
@@ -1495,7 +1495,7 @@
|
||||
else if(href_list["traitor"])
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_EVENT)) return
|
||||
|
||||
if(!ticker || !ticker.mode)
|
||||
if(!SSticker|| !SSticker.mode)
|
||||
tgui_alert_async(usr, "The game hasn't started yet!")
|
||||
return
|
||||
|
||||
|
||||
@@ -1,28 +1,10 @@
|
||||
/client/proc/cinematic(var/cinematic as anything in list("explosion",null))
|
||||
set name = "Cinematic"
|
||||
set category = "Fun.Do Not"
|
||||
set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted.
|
||||
|
||||
if(!check_rights(R_FUN))
|
||||
ADMIN_VERB(cinematic, R_FUN, "Cinematic", "Show a cinematic to all players.", ADMIN_CATEGORY_FUN)
|
||||
var/datum/cinematic/choice = tgui_input_list(
|
||||
user,
|
||||
"Chose a cinematic to play to everyone in the server.",
|
||||
"Choose Cinematic",
|
||||
sortList(subtypesof(/datum/cinematic), GLOBAL_PROC_REF(cmp_typepaths_asc)),
|
||||
)
|
||||
if(!choice || !ispath(choice, /datum/cinematic))
|
||||
return
|
||||
|
||||
if(tgui_alert(usr, "Are you sure you want to run [cinematic]?","Confirmation",list("Yes","No")) != "Yes") return
|
||||
if(!ticker) return
|
||||
switch(cinematic)
|
||||
if("explosion")
|
||||
var/input = tgui_alert(usr, "The game will be over. Are you really sure?", "Confirmation", list("Continue","Cancel"))
|
||||
if(!input || input == "Cancel")
|
||||
return
|
||||
var/parameter = tgui_input_number(src,"station_missed = ?","Enter Parameter",0,1,0)
|
||||
var/override
|
||||
switch(parameter)
|
||||
if(1)
|
||||
override = tgui_input_list(src,"mode = ?","Enter Parameter", list("mercenary","no override"))
|
||||
if(0)
|
||||
override = tgui_input_list(src,"mode = ?","Enter Parameter", list("blob","mercenary","AI malfunction","no override"))
|
||||
ticker.station_explosion_cinematic(parameter,override)
|
||||
|
||||
log_admin("[key_name(src)] launched cinematic \"[cinematic]\"")
|
||||
message_admins("[key_name_admin(src)] launched cinematic \"[cinematic]\"", 1)
|
||||
|
||||
return
|
||||
play_cinematic(choice, world)
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
set category = "Fun.Event Kit"
|
||||
set name = "Make Robot"
|
||||
|
||||
if(!ticker)
|
||||
if(!SSticker)
|
||||
tgui_alert_async(usr, "Wait until the game starts")
|
||||
return
|
||||
if(ishuman(M))
|
||||
@@ -112,7 +112,7 @@
|
||||
set category = "Fun.Event Kit"
|
||||
set name = "Make Simple Animal"
|
||||
|
||||
if(!ticker)
|
||||
if(!SSticker)
|
||||
tgui_alert_async(usr, "Wait until the game starts")
|
||||
return
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
set category = "Fun.Event Kit"
|
||||
set name = "Make Alien"
|
||||
|
||||
if(!ticker)
|
||||
if(!SSticker)
|
||||
tgui_alert_async(usr, "Wait until the game starts")
|
||||
return
|
||||
if(ishuman(M))
|
||||
@@ -277,7 +277,7 @@
|
||||
set category = "Admin.Events"
|
||||
set name = "Grant Full Access"
|
||||
|
||||
if (!ticker)
|
||||
if (!SSticker)
|
||||
tgui_alert_async(usr, "Wait until the game starts")
|
||||
return
|
||||
if (ishuman(M))
|
||||
@@ -610,7 +610,7 @@ ADMIN_VERB(cmd_assume_direct_control, (R_DEBUG|R_ADMIN|R_EVENT), "Assume Direct
|
||||
|
||||
// DNA2 - Admin Hax
|
||||
/client/proc/cmd_admin_toggle_block(var/mob/M,var/block)
|
||||
if(!ticker)
|
||||
if(!SSticker)
|
||||
tgui_alert_async(usr, "Wait until the game starts")
|
||||
return
|
||||
if(istype(M, /mob/living/carbon))
|
||||
|
||||
@@ -79,9 +79,9 @@
|
||||
set name = "Radio report"
|
||||
|
||||
var/output = "<b>Radio Report</b><hr>"
|
||||
for (var/fq in radio_controller.frequencies)
|
||||
for (var/fq in SSradio.frequencies)
|
||||
output += "<b>Freq: [fq]</b><br>"
|
||||
var/datum/radio_frequency/fqs = radio_controller.frequencies[fq]
|
||||
var/datum/radio_frequency/fqs = SSradio.frequencies[fq]
|
||||
if (!fqs)
|
||||
output += " <b>ERROR</b><br>"
|
||||
continue
|
||||
|
||||
@@ -867,7 +867,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
|
||||
set category = "Admin.Events"
|
||||
set name = "Call Shuttle"
|
||||
|
||||
if ((!( ticker ) || !emergency_shuttle.location()))
|
||||
if ((!( SSticker ) || !emergency_shuttle.location()))
|
||||
return
|
||||
|
||||
if(!check_rights(R_ADMIN)) return
|
||||
@@ -876,7 +876,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
|
||||
if(confirm != "Yes") return
|
||||
|
||||
var/choice
|
||||
if(ticker.mode.auto_recall_shuttle)
|
||||
if(SSticker.mode.auto_recall_shuttle)
|
||||
choice = tgui_input_list(usr, "The shuttle will just return if you call it. Call anyway?", "Shuttle Call", list("Confirm", "Cancel"))
|
||||
if(choice == "Confirm")
|
||||
emergency_shuttle.auto_recall = 1 //enable auto-recall
|
||||
@@ -903,7 +903,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
|
||||
|
||||
if(tgui_alert(src, "You sure?", "Confirm", list("Yes", "No")) != "Yes") return
|
||||
|
||||
if(!ticker || !emergency_shuttle.can_recall())
|
||||
if(!SSticker || !emergency_shuttle.can_recall())
|
||||
return
|
||||
|
||||
emergency_shuttle.recall()
|
||||
@@ -917,7 +917,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
|
||||
set category = "Admin.Events"
|
||||
set name = "Toggle Deny Shuttle"
|
||||
|
||||
if (!ticker)
|
||||
if (!SSticker)
|
||||
return
|
||||
|
||||
if(!check_rights(R_ADMIN)) return
|
||||
@@ -944,12 +944,12 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
|
||||
|
||||
if(!check_rights(R_FUN)) return
|
||||
|
||||
if (ticker && ticker.mode)
|
||||
if (SSticker && SSticker.mode)
|
||||
to_chat(usr, "Nope you can't do this, the game's already started. This only works before rounds!")
|
||||
return
|
||||
|
||||
if(ticker.random_players)
|
||||
ticker.random_players = 0
|
||||
if(CONFIG_GET(flag/force_random_names))
|
||||
CONFIG_SET(flag/force_random_names, FALSE)
|
||||
message_admins("Admin [key_name_admin(usr)] has disabled \"Everyone is Special\" mode.", 1)
|
||||
to_chat(usr, "Disabled.")
|
||||
return
|
||||
@@ -967,7 +967,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp
|
||||
|
||||
to_chat(usr, "<i>Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet</i>.")
|
||||
|
||||
ticker.random_players = 1
|
||||
CONFIG_SET(flag/force_random_names, TRUE)
|
||||
feedback_add_details("admin_verb","MER") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ ADMIN_VERB(secrets, R_HOLDER, "Secrets", "Abuse harder than you ever have before
|
||||
if("show_traitors_and_objectives") // Not implemented in the UI
|
||||
holder.holder.check_antagonists()
|
||||
if("show_game_mode")
|
||||
if (ticker.mode) tgui_alert_async(holder, "The game mode is [ticker.mode.name]")
|
||||
if (SSticker.mode) tgui_alert_async(holder, "The game mode is [SSticker.mode.name]")
|
||||
else tgui_alert_async(holder, "For some reason there's a ticker, but not a game mode")
|
||||
|
||||
//Buttons for debug.
|
||||
|
||||
@@ -10,7 +10,7 @@ var/const/commandos_possible = 6 //if more Commandos are needed in the future
|
||||
to_chat(src, "Only administrators may use this command.")
|
||||
return
|
||||
|
||||
if(!ticker)
|
||||
if(!SSticker)
|
||||
to_chat(usr, span_red("The game hasn't started yet!"))
|
||||
return
|
||||
|
||||
@@ -54,3 +54,153 @@ var/const/commandos_possible = 6 //if more Commandos are needed in the future
|
||||
return
|
||||
|
||||
team.attempt_random_spawn()
|
||||
|
||||
//STRIKE TEAMS
|
||||
//Thanks to Kilakk for the admin-button portion of this code.
|
||||
|
||||
GLOBAL_VAR_INIT(send_emergency_team, 0) // Used for automagic response teams; 'admin_emergency_team' for admin-spawned response teams
|
||||
|
||||
GLOBAL_VAR_INIT(ert_base_chance, 10) // Default base chance. Will be incremented by increment ERT chance.
|
||||
GLOBAL_VAR(can_call_ert)
|
||||
GLOBAL_VAR_INIT(silent_ert, 0)
|
||||
|
||||
/client/proc/response_team()
|
||||
set name = "Dispatch Emergency Response Team"
|
||||
set category = "Fun.Event Kit"
|
||||
set desc = "Send an emergency response team to the station"
|
||||
|
||||
if(!check_rights_for(src, R_HOLDER))
|
||||
to_chat(usr, span_danger("Only administrators may use this command."))
|
||||
return
|
||||
if(!SSticker)
|
||||
to_chat(usr, span_danger("The game hasn't started yet!"))
|
||||
return
|
||||
if(SSticker.current_state == 1)
|
||||
to_chat(usr, span_danger("The round hasn't started yet!"))
|
||||
return
|
||||
if(GLOB.send_emergency_team)
|
||||
to_chat(usr, span_danger("[using_map.boss_name] has already dispatched an emergency response team!"))
|
||||
return
|
||||
if(tgui_alert(usr, "Do you want to dispatch an Emergency Response Team?","ERT",list("Yes","No")) != "Yes")
|
||||
return
|
||||
if(tgui_alert(usr, "Do you want this Response Team to be announced?","ERT",list("Yes","No")) != "Yes")
|
||||
GLOB.silent_ert = 1
|
||||
if(get_security_level() != "red") // Allow admins to reconsider if the alert level isn't Red
|
||||
if(tgui_alert(usr, "The station is not in red alert. Do you still want to dispatch a response team?","ERT",list("Yes","No")) != "Yes")
|
||||
return
|
||||
if(GLOB.send_emergency_team)
|
||||
to_chat(usr, span_danger("Looks like somebody beat you to it!"))
|
||||
return
|
||||
|
||||
message_admins("[key_name_admin(usr)] is dispatching an Emergency Response Team.", 1)
|
||||
admin_chat_message(message = "[key_name(usr)] is dispatching an Emergency Response Team", color = "#CC2222") //VOREStation Add
|
||||
log_admin("[key_name(usr)] used Dispatch Response Team.")
|
||||
trigger_armed_response_team(1)
|
||||
|
||||
/client/verb/JoinResponseTeam()
|
||||
|
||||
set name = "Join Response Team"
|
||||
set category = "IC.Event"
|
||||
|
||||
if(!MayRespawn(1))
|
||||
to_chat(usr, span_warning("You cannot join the response team at this time."))
|
||||
return
|
||||
|
||||
if(isobserver(usr) || isnewplayer(usr))
|
||||
if(!GLOB.send_emergency_team)
|
||||
to_chat(usr, "No emergency response team is currently being sent.")
|
||||
return
|
||||
if(jobban_isbanned(usr, JOB_SYNDICATE) || jobban_isbanned(usr, JOB_EMERGENCY_RESPONSE_TEAM) || jobban_isbanned(usr, JOB_SECURITY_OFFICER))
|
||||
to_chat(usr, span_danger("You are jobbanned from the emergency reponse team!"))
|
||||
return
|
||||
if(ert.current_antagonists.len >= ert.hard_cap)
|
||||
to_chat(usr, "The emergency response team is already full!")
|
||||
return
|
||||
ert.create_default(usr)
|
||||
else
|
||||
to_chat(usr, "You need to be an observer or new player to use this.")
|
||||
|
||||
// returns a number of dead players in %
|
||||
/proc/percentage_dead()
|
||||
var/total = 0
|
||||
var/deadcount = 0
|
||||
for(var/mob/living/carbon/human/H in GLOB.mob_list)
|
||||
if(H.client) // Monkeys and mice don't have a client, amirite?
|
||||
if(H.stat == 2) deadcount++
|
||||
total++
|
||||
|
||||
if(total == 0) return 0
|
||||
else return round(100 * deadcount / total)
|
||||
|
||||
// counts the number of antagonists in %
|
||||
/proc/percentage_antagonists()
|
||||
var/total = 0
|
||||
var/antagonists = 0
|
||||
for(var/mob/living/carbon/human/H in GLOB.mob_list)
|
||||
if(is_special_character(H) >= 1)
|
||||
antagonists++
|
||||
total++
|
||||
|
||||
if(total == 0) return 0
|
||||
else return round(100 * antagonists / total)
|
||||
|
||||
// Increments the ERT chance automatically, so that the later it is in the round,
|
||||
// the more likely an ERT is to be able to be called.
|
||||
/proc/increment_ert_chance()
|
||||
while(GLOB.send_emergency_team == 0) // There is no ERT at the time.
|
||||
if(get_security_level() == "green")
|
||||
GLOB.ert_base_chance += 1
|
||||
if(get_security_level() == "yellow")
|
||||
GLOB.ert_base_chance += 1
|
||||
if(get_security_level() == "violet")
|
||||
GLOB.ert_base_chance += 2
|
||||
if(get_security_level() == "orange")
|
||||
GLOB.ert_base_chance += 2
|
||||
if(get_security_level() == "blue")
|
||||
GLOB.ert_base_chance += 2
|
||||
if(get_security_level() == "red")
|
||||
GLOB.ert_base_chance += 3
|
||||
if(get_security_level() == "delta")
|
||||
GLOB.ert_base_chance += 10 // Need those big guns
|
||||
sleep(600 * 3) // Minute * Number of Minutes
|
||||
|
||||
|
||||
/proc/trigger_armed_response_team(var/force = 0)
|
||||
if(!GLOB.can_call_ert && !force)
|
||||
return
|
||||
if(GLOB.send_emergency_team)
|
||||
return
|
||||
|
||||
var/send_team_chance = GLOB.ert_base_chance // Is incremented by increment_ert_chance.
|
||||
send_team_chance += 2*percentage_dead() // the more people are dead, the higher the chance
|
||||
send_team_chance += percentage_antagonists() // the more antagonists, the higher the chance
|
||||
send_team_chance = min(send_team_chance, 100)
|
||||
|
||||
if(force) send_team_chance = 100
|
||||
|
||||
// there's only a certain chance a team will be sent
|
||||
if(!prob(send_team_chance))
|
||||
command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. Unfortunately, we were unable to send one at this time.", "[using_map.boss_name]")
|
||||
GLOB.can_call_ert = 0 // Only one call per round, ladies.
|
||||
return
|
||||
if(GLOB.silent_ert == 0)
|
||||
command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. We will prepare and send one as soon as possible.", "[using_map.boss_name]")
|
||||
|
||||
GLOB.can_call_ert = 0 // Only one call per round, gentleman.
|
||||
GLOB.send_emergency_team = 1
|
||||
consider_ert_load() //VOREStation Add
|
||||
|
||||
sleep(600 * 5)
|
||||
GLOB.send_emergency_team = 0 // Can no longer join the ERT.
|
||||
|
||||
GLOBAL_VAR(ert_loaded)
|
||||
|
||||
/proc/consider_ert_load()
|
||||
if(!GLOB.ert_loaded)
|
||||
GLOB.ert_loaded = TRUE
|
||||
var/datum/map_template/MT = SSmapping.map_templates["Special Area - ERT"]
|
||||
if(!istype(MT))
|
||||
error("ERT Area is not a valid map template!")
|
||||
else
|
||||
MT.load_new_z(centered = TRUE)
|
||||
log_and_message_admins("Loaded the ERT shuttle just now.")
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
//Based on the ERT setup
|
||||
|
||||
GLOBAL_VAR_INIT(send_beruang, 0)
|
||||
GLOBAL_VAR_INIT(can_call_traders, 1)
|
||||
|
||||
/client/proc/trader_ship()
|
||||
set name = "Dispatch Beruang Trader Ship"
|
||||
set category = "Fun.Event Kit"
|
||||
set desc = "Invite players to join the Beruang."
|
||||
|
||||
if(!holder)
|
||||
to_chat(usr, span_danger("Only administrators may use this command."))
|
||||
return
|
||||
if(!SSticker)
|
||||
to_chat(usr, span_danger("The game hasn't started yet!"))
|
||||
return
|
||||
if(SSticker.current_state == 1)
|
||||
to_chat(usr, span_danger("The round hasn't started yet!"))
|
||||
return
|
||||
if(GLOB.send_beruang)
|
||||
to_chat(usr, span_danger("The Beruang has already been sent this round!"))
|
||||
return
|
||||
if(tgui_alert(usr, "Do you want to dispatch the Beruang trade ship?","Trade Ship",list("Yes","No")) != "Yes")
|
||||
return
|
||||
if(get_security_level() == "red") // Allow admins to reconsider if the alert level is Red
|
||||
if(tgui_alert(usr, "The station is in red alert. Do you still want to send traders?","Trade Ship",list("Yes","No")) != "Yes")
|
||||
return
|
||||
if(GLOB.send_beruang)
|
||||
to_chat(usr, span_danger("Looks like somebody beat you to it!"))
|
||||
return
|
||||
|
||||
message_admins("[key_name_admin(usr)] is dispatching the Beruang.", 1)
|
||||
log_admin("[key_name(usr)] used Dispatch Beruang Trader Ship.")
|
||||
trigger_trader_visit()
|
||||
|
||||
/client/verb/JoinTraders()
|
||||
|
||||
set name = "Join Trader Visit"
|
||||
set category = "IC.Event"
|
||||
|
||||
if(!MayRespawn(1))
|
||||
to_chat(usr, span_warning("You cannot join the traders."))
|
||||
return
|
||||
|
||||
if(isobserver(usr) || isnewplayer(usr))
|
||||
if(!GLOB.send_beruang)
|
||||
to_chat(usr, "The Beruang is not currently heading to the station.")
|
||||
return
|
||||
if(traders.current_antagonists.len >= traders.hard_cap)
|
||||
to_chat(usr, "The number of trader slots is already full!")
|
||||
return
|
||||
traders.create_default(usr)
|
||||
else
|
||||
to_chat(usr, "You need to be an observer or new player to use this.")
|
||||
|
||||
/proc/trigger_trader_visit()
|
||||
if(!GLOB.can_call_traders)
|
||||
return
|
||||
if(GLOB.send_beruang)
|
||||
return
|
||||
|
||||
command_announcement.Announce("Incoming cargo hauler: Beruang (Reg: VRS 22EB1F11C2).", "[station_name()] Traffic Control")
|
||||
|
||||
GLOB.can_call_traders = 0 // Only one call per round.
|
||||
GLOB.send_beruang = 1
|
||||
consider_trader_load() //VOREStation Add
|
||||
|
||||
sleep(600 * 5)
|
||||
GLOB.send_beruang = 0 // Can no longer join the traders.
|
||||
|
||||
GLOBAL_VAR(trader_loaded)
|
||||
|
||||
/proc/consider_trader_load()
|
||||
if(!GLOB.trader_loaded)
|
||||
GLOB.trader_loaded = TRUE
|
||||
var/datum/map_template/MT = SSmapping.map_templates["Special Area - Salamander Trader"] //was: "Special Area - Trader"
|
||||
if(!istype(MT))
|
||||
error("Trader is not a valid map template!")
|
||||
else
|
||||
MT.load_new_z(centered = TRUE)
|
||||
log_and_message_admins("Loaded the trade shuttle just now.")
|
||||
@@ -2,21 +2,21 @@
|
||||
set category = "Fun.Event Kit"
|
||||
set name = "Create AI Triumvirate"
|
||||
|
||||
if(ticker.current_state > GAME_STATE_PREGAME)
|
||||
if(SSticker.current_state > GAME_STATE_PREGAME)
|
||||
to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.")
|
||||
return
|
||||
|
||||
if(job_master && ticker)
|
||||
if(job_master && SSticker)
|
||||
var/datum/job/job = job_master.GetJob(JOB_AI)
|
||||
if(!job)
|
||||
to_chat(usr, "Unable to locate the AI job")
|
||||
return
|
||||
if(ticker.triai)
|
||||
ticker.triai = 0
|
||||
if(GLOB.triai)
|
||||
GLOB.triai = 0
|
||||
to_chat(usr, "Only one AI will be spawned at round start.")
|
||||
message_admins(span_blue("[key_name_admin(usr)] has toggled off triple AIs at round start."), 1)
|
||||
else
|
||||
ticker.triai = 1
|
||||
GLOB.triai = 1
|
||||
to_chat(usr, "There will be an AI Triumvirate at round start.")
|
||||
message_admins(span_blue("[key_name_admin(usr)] has toggled on triple AIs at round start."), 1)
|
||||
return
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
[Summary]
|
||||
## Summary
|
||||
|
||||
This module contains an AI implementation designed to be (at the base level) mobtype-agnostic,
|
||||
by being held inside a datum instead of being written into the mob directly. More specialized
|
||||
@@ -11,7 +10,7 @@ When designing a new mob, all that is needed to give a mob an AI is to set
|
||||
its 'ai_holder_type' variable to the path of the AI that is desired.
|
||||
|
||||
|
||||
[Seperation]
|
||||
## Seperation
|
||||
|
||||
In previous iterations of AI systems, the AI is generally written into the mob's code directly,
|
||||
which has some advantages, but often makes the code rigid, and also tied the speed of the AI
|
||||
@@ -46,7 +45,7 @@ in the future.
|
||||
delay, as the ai_holder might not exist yet.
|
||||
|
||||
|
||||
[Flow of Processing]
|
||||
## Flow of Processing
|
||||
|
||||
Terrible visual representation here;
|
||||
AI Subsystem -> Every 0.5s -> /datum/ai_holder/handle_tactics() -> switch(stance)...
|
||||
@@ -92,7 +91,7 @@ with each other, as opposed to having individual tick counters inside all of
|
||||
the ai_holder instances. It should be noted that handle_tactics() is always
|
||||
called first, before handle_strategicals() every two seconds.
|
||||
|
||||
[Process Skipping]
|
||||
## Process Skipping
|
||||
|
||||
An ai_holder object can choose to enter a 'busy' state, or a 'sleep' state,
|
||||
in order to avoid processing.
|
||||
@@ -117,7 +116,7 @@ from processing the other ai_holders until the sleep() finishes.
|
||||
Delays on the mob typically have set waitfor = FALSE, or spawn() is used.
|
||||
|
||||
|
||||
[Stances]
|
||||
## Stances
|
||||
|
||||
The AI has a large number of states that it can be in, called stances.
|
||||
The AI will act in a specific way depending on which stance it is in,
|
||||
@@ -138,7 +137,7 @@ module folder and are mostly self contained, however some files instead
|
||||
deal with general things that other stances may require, such as targeting
|
||||
or movement.
|
||||
|
||||
[Interfaces]
|
||||
## Interfaces
|
||||
|
||||
Interfaces are a concept that is used to help bridge the gap between
|
||||
the ai_holder, and its mob. Because the (base) ai_holder is explicitly
|
||||
@@ -168,7 +167,7 @@ ranged attack. For simple_mobs, they can if a ranged projectile type was set,
|
||||
where as for a human mob, it could check if a gun is in a hand. For a borg,
|
||||
it could check if a gun is inside their current module.
|
||||
|
||||
[Say List]
|
||||
## Say List
|
||||
|
||||
A /datum/say_list is a very light datum that holds a list of strings for the
|
||||
AI to have their mob say based on certain conditions, such as when threatening
|
||||
@@ -181,7 +180,7 @@ mercenaries and fake piloted mecha mobs.
|
||||
|
||||
The say_list datum is applied to the mob itself and not held inside the AI datum.
|
||||
|
||||
[Subtypes]
|
||||
## Subtypes
|
||||
|
||||
Some subtypes of ai_holder are more specialized, but remain compatible with
|
||||
most mob types. There are many different subtypes that make the AI act different
|
||||
@@ -196,6 +195,3 @@ To use a specific subtype on a mob, all that is needed is setting the mob's
|
||||
ai_holder_type to the subtype desired, and it will create that subtype when
|
||||
the mob is initialize()d. Switching to a subtype 'live' will require additional
|
||||
effort on the coder.
|
||||
|
||||
|
||||
*/
|
||||
@@ -122,14 +122,14 @@
|
||||
/obj/item/assembly/signaler/proc/set_frequency(new_frequency)
|
||||
if(!frequency)
|
||||
return
|
||||
if(!radio_controller)
|
||||
if(!SSradio)
|
||||
addtimer(CALLBACK(src, PROC_REF(radio_checkup), new_frequency), 2 SECONDS)
|
||||
return
|
||||
set_radio(new_frequency)
|
||||
|
||||
/obj/item/assembly/signaler/proc/radio_checkup(new_frequency)
|
||||
PROTECTED_PROC(TRUE)
|
||||
if(!radio_controller)
|
||||
if(!SSradio)
|
||||
return
|
||||
set_radio(new_frequency)
|
||||
|
||||
@@ -137,12 +137,12 @@
|
||||
/obj/item/assembly/signaler/proc/set_radio(new_frequency)
|
||||
PROTECTED_PROC(TRUE)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
radio_controller.remove_object(src, frequency)
|
||||
SSradio.remove_object(src, frequency)
|
||||
frequency = new_frequency
|
||||
radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT)
|
||||
radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT)
|
||||
|
||||
/obj/item/assembly/signaler/Destroy()
|
||||
if(radio_controller)
|
||||
radio_controller.remove_object(src,frequency)
|
||||
if(SSradio)
|
||||
SSradio.remove_object(src,frequency)
|
||||
frequency = 0
|
||||
. = ..()
|
||||
|
||||
@@ -1,107 +1,90 @@
|
||||
GLOBAL_LIST_EMPTY(robot_sprite_sheets)
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons
|
||||
_abstract = /datum/asset/spritesheet_batched/robot_icons
|
||||
name = "robot_icons"
|
||||
fully_generated = TRUE
|
||||
var/module_type
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/standard
|
||||
name = "robot_icons_standard"
|
||||
fully_generated = FALSE
|
||||
module_type = "Standard"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/service
|
||||
name = "robot_icons_service"
|
||||
fully_generated = FALSE
|
||||
module_type = "Service"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/clerical
|
||||
name = "robot_icons_clerical"
|
||||
fully_generated = FALSE
|
||||
module_type = "Clerical"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/clown
|
||||
name = "robot_icons_clown"
|
||||
fully_generated = FALSE
|
||||
module_type = "Clown"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/command
|
||||
name = "robot_icons_command"
|
||||
fully_generated = FALSE
|
||||
module_type = "Command"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/research
|
||||
name = "robot_icons_research"
|
||||
fully_generated = FALSE
|
||||
module_type = "Research"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/miner
|
||||
name = "robot_icons_miner"
|
||||
fully_generated = FALSE
|
||||
module_type = "Miner"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/crisis
|
||||
name = "robot_icons_crisis"
|
||||
fully_generated = FALSE
|
||||
module_type = "Crisis"
|
||||
|
||||
/* Modul not in use
|
||||
/datum/asset/spritesheet_batched/robot_icons/surgeon
|
||||
name = "robot_icons_surgeon"
|
||||
module_type = "Surgeon"
|
||||
*/
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/security
|
||||
name = "robot_icons_security"
|
||||
fully_generated = FALSE
|
||||
module_type = "Security"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/combat
|
||||
name = "robot_icons_combat"
|
||||
fully_generated = FALSE
|
||||
module_type = "Combat"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/exploration
|
||||
name = "robot_icons_exploration"
|
||||
fully_generated = FALSE
|
||||
module_type = "Exploration"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/engineering
|
||||
name = "robot_icons_engineering"
|
||||
fully_generated = FALSE
|
||||
module_type = "Engineering"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/janitor
|
||||
name = "robot_icons_janitor"
|
||||
fully_generated = FALSE
|
||||
module_type = "Janitor"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/gravekeeper
|
||||
name = "robot_icons_gravekeeper"
|
||||
fully_generated = FALSE
|
||||
module_type = "Gravekeeper"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/lost
|
||||
name = "robot_icons_lost"
|
||||
fully_generated = FALSE
|
||||
module_type = "Lost"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/protector
|
||||
name = "robot_icons_protector"
|
||||
fully_generated = FALSE
|
||||
module_type = "Protector"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/mechanist
|
||||
name = "robot_icons_mechanist"
|
||||
fully_generated = FALSE
|
||||
module_type = "Mechanist"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/combat_medic
|
||||
name = "robot_icons_combat_medic"
|
||||
fully_generated = FALSE
|
||||
module_type = "Combat Medic"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/ninja
|
||||
name = "robot_icons_ninja"
|
||||
fully_generated = FALSE
|
||||
module_type = "Ninja"
|
||||
|
||||
/datum/asset/spritesheet_batched/robot_icons/create_spritesheets()
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
var/load_immediately = FALSE
|
||||
/// If we should avoid propogating 'invalid dir' errors from rust-g. Because sometimes, you just don't know what dirs are valid.
|
||||
var/ignore_dir_errors = FALSE
|
||||
/// Avoid propogating 'Could not find associated icon state' errors because we know our input data fucking sucks
|
||||
var/ignore_associated_icon_state_errors = FALSE
|
||||
|
||||
/// Forces use of the smart cache. This is for unit tests, please respect the config <3
|
||||
var/force_cache = FALSE
|
||||
@@ -48,6 +46,7 @@
|
||||
var/cache_dmi_hashes_json = null
|
||||
/// Used to prevent async cache refresh jobs from looping on failure.
|
||||
var/cache_result = null
|
||||
var/getting_genned = FALSE
|
||||
|
||||
/datum/asset/spritesheet_batched/proc/should_load_immediately()
|
||||
#ifdef DO_NOT_DEFER_ASSETS
|
||||
@@ -78,7 +77,7 @@
|
||||
if(cached_rustg_version != rustg_version)
|
||||
log_asset("Invalidated cache for spritesheet_[name] due to rustg updating from [cached_rustg_version] to [rustg_version].")
|
||||
return CACHE_INVALID
|
||||
// Invalidate cache if the DM version changes
|
||||
// Invalidate cache if the DM version changes
|
||||
var/cached_dm_version = cache_json["dm_version"]
|
||||
if(isnull(cached_dm_version))
|
||||
log_asset("Cache for spritesheet_[name] did not contain a dm_version!")
|
||||
@@ -108,8 +107,8 @@
|
||||
if (data_out == RUSTG_JOB_ERROR)
|
||||
CRASH("Spritesheet [name] cache JOB PANIC")
|
||||
else if(!findtext(data_out, "{", 1, 2))
|
||||
rustg_file_write(cache_data, "[GLOB.log_directory]-spritesheet_cache_debug.[name].json")
|
||||
rustg_file_write(entries_json, "[GLOB.log_directory]-spritesheet_debug_[name].json")
|
||||
rustg_file_write(cache_data, "[GLOB.log_directory]/spritesheet_cache_debug.[name].json")
|
||||
rustg_file_write(entries_json, "[GLOB.log_directory]/spritesheet_debug_[name].json")
|
||||
CRASH("Spritesheet [name] cache check UNKNOWN ERROR: [data_out]")
|
||||
var/result = json_decode(data_out)
|
||||
var/fail = result["fail_reason"]
|
||||
@@ -127,6 +126,8 @@
|
||||
/datum/asset/spritesheet_batched/proc/insert_icon(sprite_name, datum/universal_icon/entry)
|
||||
if(!istext(sprite_name) || !length(sprite_name))
|
||||
CRASH("Invalid sprite_name \"[sprite_name]\" given to insert_icon()! Providing non-strings will break icon generation.")
|
||||
if(!istype(entry))
|
||||
CRASH("Invalid type provided to insert_icon()! Value: [entry] (type: [entry?.type])")
|
||||
entries[sprite_name] = entry.to_list()
|
||||
|
||||
/datum/asset/spritesheet_batched/register()
|
||||
@@ -148,7 +149,6 @@
|
||||
|
||||
/// Call insert_icon or insert_all_icons here, building a spritesheet!
|
||||
/datum/asset/spritesheet_batched/proc/create_spritesheets()
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
CRASH("create_spritesheets() not implemented for [type]!")
|
||||
|
||||
/datum/asset/spritesheet_batched/proc/insert_all_icons(prefix, icon/I, list/directions, prefix_with_dirs = TRUE)
|
||||
@@ -159,8 +159,6 @@
|
||||
directions = list(SOUTH)
|
||||
|
||||
for (var/icon_state_name in icon_states(I))
|
||||
if(icon_state_name == "")
|
||||
icon_state_name = "byond-default"
|
||||
for (var/direction in directions)
|
||||
var/prefix2 = (directions.len > 1 && prefix_with_dirs) ? "[dir2text(direction)]-" : ""
|
||||
insert_icon("[prefix][prefix2][icon_state_name]", uni_icon(I, icon_state_name, direction))
|
||||
@@ -171,6 +169,8 @@
|
||||
if(!length(entries))
|
||||
CRASH("Spritesheet [name] ([type]) is empty! What are you doing?")
|
||||
|
||||
if(getting_genned)
|
||||
stack_trace("Spritesheet batching has been called twice. This is illegal!")
|
||||
if(isnull(entries_json))
|
||||
entries_json = json_encode(entries)
|
||||
|
||||
@@ -192,15 +192,16 @@
|
||||
var/data_out
|
||||
if(yield || !isnull(job_id))
|
||||
if(isnull(job_id))
|
||||
getting_genned = TRUE
|
||||
job_id = rustg_iconforge_generate_async("data/spritesheets/", name, entries_json, do_cache, FALSE, TRUE)
|
||||
UNTIL((data_out = rustg_iconforge_check(job_id)) != RUSTG_JOB_NO_RESULTS_YET)
|
||||
getting_genned = FALSE
|
||||
else
|
||||
//rustg_file_write(entries_json, "fuckoff.json")
|
||||
data_out = rustg_iconforge_generate("data/spritesheets/", name, entries_json, do_cache, FALSE, TRUE)
|
||||
if (data_out == RUSTG_JOB_ERROR)
|
||||
CRASH("Spritesheet [name] JOB PANIC")
|
||||
else if(!findtext(data_out, "{", 1, 2))
|
||||
rustg_file_write(entries_json, "[GLOB.log_directory]-spritesheet_debug_[name].json")
|
||||
rustg_file_write(entries_json, "[GLOB.log_directory]/spritesheet_debug_[name].json")
|
||||
CRASH("Spritesheet [name] UNKNOWN ERROR: [data_out]")
|
||||
var/data = json_decode(data_out)
|
||||
sizes = data["sizes"]
|
||||
@@ -209,30 +210,31 @@
|
||||
var/dmi_hashes = data["dmi_hashes"] // this only contains values if do_cache is TRUE.
|
||||
|
||||
for(var/size_id in sizes)
|
||||
var/file_path = "data/spritesheets/[name]_[size_id].png"
|
||||
var/file_hash = rustg_hash_file("md5", file_path)
|
||||
SSassets.transport.register_asset("[name]_[size_id].png", fcopy_rsc(file_path), file_hash)
|
||||
var/res_name = "spritesheet_[name].css"
|
||||
var/fname = "data/spritesheets/[res_name]"
|
||||
var/png_name = "[name]_[size_id].png"
|
||||
var/file_directory = "data/spritesheets/[png_name]"
|
||||
var/file_hash = rustg_hash_file(RUSTG_HASH_MD5, file_directory)
|
||||
SSassets.transport.register_asset(png_name, fcopy_rsc(file_directory), file_hash)
|
||||
if(CONFIG_GET(flag/save_spritesheets))
|
||||
save_to_logs(file_name = png_name, file_location = file_directory)
|
||||
var/css_name = "spritesheet_[name].css"
|
||||
var/file_directory = "data/spritesheets/[css_name]"
|
||||
|
||||
fdel(fname)
|
||||
fdel(file_directory)
|
||||
var/css = generate_css()
|
||||
rustg_file_write(css, fname)
|
||||
var/css_hash = rustg_hash_string("md5", css)
|
||||
SSassets.transport.register_asset(res_name, fcopy_rsc(fname), file_hash=css_hash)
|
||||
rustg_file_write(css, file_directory)
|
||||
var/css_hash = rustg_hash_string(RUSTG_HASH_MD5, css)
|
||||
SSassets.transport.register_asset(css_name, fcopy_rsc(file_directory), file_hash=css_hash)
|
||||
|
||||
if(CONFIG_GET(flag/save_spritesheets))
|
||||
save_to_logs(file_name = css_name, file_location = file_directory)
|
||||
|
||||
if (do_cache)
|
||||
write_cache_meta(input_hash, dmi_hashes)
|
||||
fully_generated = TRUE
|
||||
// If we were ever in there, remove ourselves
|
||||
SSasset_loading.dequeue_asset(src)
|
||||
if(data["error"])
|
||||
var/err = data["error"]
|
||||
if(ignore_dir_errors && findtext(err, "is not in the set of valid dirs"))
|
||||
return
|
||||
if(ignore_associated_icon_state_errors && findtext(err, "Could not find associated icon state"))
|
||||
return
|
||||
CRASH("Error during spritesheet generation for [name]: [err]")
|
||||
if(data["error"] && !(ignore_dir_errors && findtext(data["error"], "is not in the set of valid dirs")))
|
||||
CRASH("Error during spritesheet generation for [name]: [data["error"]]")
|
||||
|
||||
/datum/asset/spritesheet_batched/queued_generation()
|
||||
realize_spritesheets(yield = TRUE)
|
||||
@@ -266,7 +268,7 @@
|
||||
var/size_split = splittext(size_id, "x")
|
||||
var/width = text2num(size_split[1])
|
||||
var/height = text2num(size_split[2])
|
||||
out += ".[name][size_id]{display:inline-block;width:[width]px;height:[height]px;background-image:url('[get_background_url("[name]_[size_id].png")]');background-repeat: no-repeat;}"
|
||||
out += ".[name][size_id]{display:inline-block;width:[width]px;height:[height]px;background-image:url('[get_background_url("[name]_[size_id].png")]');background-repeat:no-repeat;}"
|
||||
|
||||
for (var/sprite_id in sprites)
|
||||
var/sprite = sprites[sprite_id]
|
||||
@@ -286,7 +288,8 @@
|
||||
if(!CONFIG_GET(flag/smart_cache_assets) && !force_cache)
|
||||
return FALSE
|
||||
// this is already guaranteed to exist.
|
||||
var/css_fname = "data/spritesheets/spritesheet_[name].css"
|
||||
var/css_name = "spritesheet_[name].css"
|
||||
var/css_file_directory = "data/spritesheets/[css_name]"
|
||||
|
||||
// sizes gets filled during should_refresh()
|
||||
for(var/size_id in sizes)
|
||||
@@ -294,13 +297,16 @@
|
||||
if(!fexists(fname))
|
||||
return FALSE
|
||||
|
||||
var/css_hash = rustg_hash_file("md5", css_fname)
|
||||
SSassets.transport.register_asset("spritesheet_[name].css", fcopy_rsc(css_fname), file_hash=css_hash)
|
||||
var/css_hash = rustg_hash_file(RUSTG_HASH_MD5, css_file_directory)
|
||||
SSassets.transport.register_asset(css_name, fcopy_rsc(css_file_directory), file_hash=css_hash)
|
||||
for(var/size_id in sizes)
|
||||
var/fname = "data/spritesheets/[name]_[size_id].png"
|
||||
var/hash = rustg_hash_file("md5", fname)
|
||||
var/hash = rustg_hash_file(RUSTG_HASH_MD5, fname)
|
||||
SSassets.transport.register_asset("[name]_[size_id].png", fcopy_rsc(fname), file_hash=hash)
|
||||
|
||||
if(CONFIG_GET(flag/save_spritesheets))
|
||||
save_to_logs(file_name = css_name, file_location = css_file_directory)
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Returns the URL to put in the background:url of the CSS asset
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
/// Don't instantiate these yourself, use uni_icon.
|
||||
/datum/universal_icon/New(icon/icon_file, icon_state="", dir=null, frame=null, datum/icon_transformer/transform=null, color=null)
|
||||
#ifdef UNIT_TEST
|
||||
#ifdef UNIT_TESTS
|
||||
// This check is kinda slow and shouldn't fail unless a developer makes a mistake. So it'll get caught in unit tests.
|
||||
if(!isicon(icon_file) || !isfile(icon_file) || "[icon_file]" == "/icon")
|
||||
if(!isicon(icon_file) || !isfile(icon_file) || "[icon_file]" == "/icon" || !length("[icon_file]"))
|
||||
// bad! use 'icons/path_to_dmi.dmi' format only
|
||||
CRASH("FATAL: universal_icon was provided icon_file: [icon_file] - icons provided to batched spritesheets MUST be DMI files, they cannot be /image, /icon, or other runtime generated icons.")
|
||||
#endif
|
||||
@@ -44,10 +44,10 @@
|
||||
transform.blend_color(color, blend_mode)
|
||||
return src
|
||||
|
||||
/datum/universal_icon/proc/blend_icon(datum/universal_icon/icon_object, blend_mode)
|
||||
/datum/universal_icon/proc/blend_icon(datum/universal_icon/icon_object, blend_mode, x=1, y=1)
|
||||
if(!transform)
|
||||
transform = new
|
||||
transform.blend_icon(icon_object, blend_mode)
|
||||
transform.blend_icon(icon_object, blend_mode, x, y)
|
||||
return src
|
||||
|
||||
/datum/universal_icon/proc/scale(width, height)
|
||||
@@ -62,14 +62,116 @@
|
||||
transform.crop(x1, y1, x2, y2)
|
||||
return src
|
||||
|
||||
/// Internally performs a crop.
|
||||
/datum/universal_icon/proc/shift(dir, amount, icon_width, icon_height)
|
||||
/datum/universal_icon/proc/flip(dir)
|
||||
if(!transform)
|
||||
transform = new
|
||||
var/list/offsets = dir2offset(dir)
|
||||
var/shift_x = -offsets[1] * amount
|
||||
var/shift_y = -offsets[2] * amount
|
||||
transform.crop(1 + shift_x, 1 + shift_y, icon_width + shift_x, icon_height + shift_y)
|
||||
transform.flip(dir)
|
||||
return src
|
||||
|
||||
/datum/universal_icon/proc/rotate(angle)
|
||||
if(!transform)
|
||||
transform = new
|
||||
transform.rotate(angle)
|
||||
return src
|
||||
|
||||
/datum/universal_icon/proc/shift(dir, offset, wrap=0)
|
||||
if(!transform)
|
||||
transform = new
|
||||
transform.shift(dir, offset, wrap)
|
||||
return src
|
||||
|
||||
/datum/universal_icon/proc/swap_color(src_color, dst_color)
|
||||
if(!transform)
|
||||
transform = new
|
||||
transform.swap_color(src_color, dst_color)
|
||||
return src
|
||||
|
||||
/datum/universal_icon/proc/draw_box(color, x1, y1, x2=x1, y2=y1)
|
||||
if(!transform)
|
||||
transform = new
|
||||
transform.draw_box(color, x1, y1, x2, y2)
|
||||
return src
|
||||
|
||||
/datum/universal_icon/proc/map_colors_inferred(list/color_args)
|
||||
var/num_args = length(color_args)
|
||||
if(num_args <= 20 || num_args >= 16)
|
||||
src.map_colors_rgba(arglist(color_args))
|
||||
else if(num_args <= 12 || num_args >= 9)
|
||||
src.map_colors_rgb(arglist(color_args))
|
||||
else if(num_args == 5)
|
||||
src.map_colors_rgba_hex(arglist(color_args))
|
||||
else if(num_args == 4)
|
||||
// is there alpha in the hex?
|
||||
if(length(color_args[3]) == 7 || length(color_args[3]) == 4)
|
||||
src.map_colors_rgb_hex(arglist(color_args))
|
||||
else
|
||||
src.map_colors_rgba_hex(arglist(color_args))
|
||||
else if(num_args == 3)
|
||||
src.map_colors_rgb_hex(arglist(color_args))
|
||||
|
||||
/datum/universal_icon/proc/map_colors_rgba(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0=0, g0=0, b0=0, a0=0)
|
||||
if(!transform)
|
||||
transform = new
|
||||
transform.map_colors(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0, g0, b0, a0)
|
||||
return src
|
||||
|
||||
/datum/universal_icon/proc/map_colors_rgb(rr, rg, rb, gr, gg, gb, br, bg, bb, r0=0, g0=0, b0=0)
|
||||
if(!transform)
|
||||
transform = new
|
||||
transform.map_colors(rr, rg, rb, 0, gr, gg, gb, 0, br, bg, bb, 0, 0, 0, 0, 1, r0, g0, b0, 0)
|
||||
return src
|
||||
|
||||
/datum/universal_icon/proc/map_colors_rgb_hex(r_rgb, g_rgb, b_rgb, rgb0=rgb(0,0,0))
|
||||
if(!transform)
|
||||
transform = new
|
||||
var/rr = hex2num(copytext(r_rgb, 2, 4)) / 255
|
||||
var/rg = hex2num(copytext(r_rgb, 4, 6)) / 255
|
||||
var/rb = hex2num(copytext(r_rgb, 6, 8)) / 255
|
||||
|
||||
var/gr = hex2num(copytext(g_rgb, 2, 4)) / 255
|
||||
var/gg = hex2num(copytext(g_rgb, 4, 6)) / 255
|
||||
var/gb = hex2num(copytext(g_rgb, 6, 8)) / 255
|
||||
|
||||
var/br = hex2num(copytext(b_rgb, 2, 4)) / 255
|
||||
var/bg = hex2num(copytext(b_rgb, 4, 6)) / 255
|
||||
var/bb = hex2num(copytext(b_rgb, 6, 8)) / 255
|
||||
|
||||
var/r0 = hex2num(copytext(rgb0, 2, 4)) / 255
|
||||
var/b0 = hex2num(copytext(rgb0, 4, 6)) / 255
|
||||
var/g0 = hex2num(copytext(rgb0, 6, 8)) / 255
|
||||
|
||||
transform.map_colors(rr, rg, rb, 0, gr, gg, gb, 0, br, bg, bb, 0, 0, 0, 0, 1, r0, b0, g0, 0)
|
||||
return src
|
||||
|
||||
/datum/universal_icon/proc/map_colors_rgba_hex(r_rgba, g_rgba, b_rgba, a_rgba, rgba0="#00000000")
|
||||
if(!transform)
|
||||
transform = new
|
||||
var/rr = hex2num(copytext(r_rgba, 2, 4)) / 255
|
||||
var/rg = hex2num(copytext(r_rgba, 4, 6)) / 255
|
||||
var/rb = hex2num(copytext(r_rgba, 6, 8)) / 255
|
||||
var/ra = hex2num(copytext(r_rgba, 8, 10)) / 255
|
||||
|
||||
var/gr = hex2num(copytext(g_rgba, 2, 4)) / 255
|
||||
var/gg = hex2num(copytext(g_rgba, 4, 6)) / 255
|
||||
var/gb = hex2num(copytext(g_rgba, 6, 8)) / 255
|
||||
var/ga = hex2num(copytext(g_rgba, 8, 10)) / 255
|
||||
|
||||
var/br = hex2num(copytext(b_rgba, 2, 4)) / 255
|
||||
var/bg = hex2num(copytext(b_rgba, 4, 6)) / 255
|
||||
var/bb = hex2num(copytext(b_rgba, 6, 8)) / 255
|
||||
var/ba = hex2num(copytext(b_rgba, 8, 10)) / 255
|
||||
|
||||
var/ar = hex2num(copytext(a_rgba, 2, 4)) / 255
|
||||
var/ag = hex2num(copytext(a_rgba, 4, 6)) / 255
|
||||
var/ab = hex2num(copytext(a_rgba, 6, 8)) / 255
|
||||
var/aa = hex2num(copytext(a_rgba, 8, 10)) / 255
|
||||
|
||||
var/r0 = hex2num(copytext(rgba0, 2, 4)) / 255
|
||||
var/b0 = hex2num(copytext(rgba0, 4, 6)) / 255
|
||||
var/g0 = hex2num(copytext(rgba0, 6, 8)) / 255
|
||||
var/a0 = hex2num(copytext(rgba0, 8, 10)) / 255
|
||||
|
||||
transform.map_colors(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0, b0, g0, a0)
|
||||
return src
|
||||
|
||||
/// Internally performs a color blend.
|
||||
@@ -118,11 +220,29 @@
|
||||
if(!istype(icon_object))
|
||||
stack_trace("Invalid icon found in icon transformer during apply()! [icon_object]")
|
||||
continue
|
||||
target.Blend(icon_object.to_icon(), transform["blend_mode"])
|
||||
target.Blend(icon_object.to_icon(), transform["blend_mode"], transform["x"], transform["y"])
|
||||
if(RUSTG_ICONFORGE_SCALE)
|
||||
target.Scale(transform["width"], transform["height"])
|
||||
if(RUSTG_ICONFORGE_CROP)
|
||||
target.Crop(transform["x1"], transform["y1"], transform["x2"], transform["y2"])
|
||||
if(RUSTG_ICONFORGE_MAP_COLORS)
|
||||
target.MapColors(
|
||||
transform["rr"], transform["rg"], transform["rb"], transform["ra"],
|
||||
transform["gr"], transform["gg"], transform["gb"], transform["ga"],
|
||||
transform["br"], transform["bg"], transform["bb"], transform["ba"],
|
||||
transform["ar"], transform["ag"], transform["ab"], transform["aa"],
|
||||
transform["r0"], transform["g0"], transform["b0"], transform["a0"],
|
||||
)
|
||||
if(RUSTG_ICONFORGE_FLIP)
|
||||
target.Flip(transform["dir"])
|
||||
if(RUSTG_ICONFORGE_TURN)
|
||||
target.Turn(transform["angle"])
|
||||
if(RUSTG_ICONFORGE_SHIFT)
|
||||
target.Shift(transform["dir"], transform["offset"], transform["wrap"])
|
||||
if(RUSTG_ICONFORGE_SWAP_COLOR)
|
||||
target.SwapColor(transform["src_color"], transform["dst_color"])
|
||||
if(RUSTG_ICONFORGE_DRAW_BOX)
|
||||
target.DrawBox(transform["color"], transform["x1"], transform["y1"], transform["x2"], transform["y2"])
|
||||
return target
|
||||
|
||||
/datum/icon_transformer/proc/copy()
|
||||
@@ -134,7 +254,7 @@
|
||||
return new_transformer
|
||||
|
||||
/datum/icon_transformer/proc/blend_color(color, blend_mode)
|
||||
#ifdef UNIT_TEST
|
||||
#ifdef UNIT_TESTS
|
||||
if(!istext(color))
|
||||
CRASH("Invalid color provided to blend_color: [color]")
|
||||
if(!isnum(blend_mode))
|
||||
@@ -142,28 +262,77 @@
|
||||
#endif
|
||||
transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_COLOR, "color" = color, "blend_mode" = blend_mode))
|
||||
|
||||
/datum/icon_transformer/proc/blend_icon(datum/universal_icon/icon_object, blend_mode)
|
||||
#ifdef UNIT_TEST
|
||||
/datum/icon_transformer/proc/blend_icon(datum/universal_icon/icon_object, blend_mode, x=1, y=1)
|
||||
#ifdef UNIT_TESTS
|
||||
// icon_object's type is checked later in to_list
|
||||
if(!isnum(blend_mode))
|
||||
CRASH("Invalid blend_mode provided to blend_icon: [blend_mode]")
|
||||
if(!isnum(x))
|
||||
CRASH("Invalid x offset provided to blend_icon: [x]")
|
||||
if(!isnum(y))
|
||||
CRASH("Invalid y offset provided to blend_icon: [y]")
|
||||
#endif
|
||||
transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_ICON, "icon" = icon_object, "blend_mode" = blend_mode))
|
||||
transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_ICON, "icon" = icon_object, "blend_mode" = blend_mode, "x" = x, "y" = y))
|
||||
|
||||
/datum/icon_transformer/proc/scale(width, height)
|
||||
#ifdef UNIT_TEST
|
||||
#ifdef UNIT_TESTS
|
||||
if(!isnum(width) || !isnum(height))
|
||||
CRASH("Invalid arguments provided to scale: [width],[height]")
|
||||
#endif
|
||||
transforms += list(list("type" = RUSTG_ICONFORGE_SCALE, "width" = width, "height" = height))
|
||||
|
||||
/datum/icon_transformer/proc/crop(x1, y1, x2, y2)
|
||||
#ifdef UNIT_TEST
|
||||
#ifdef UNIT_TESTS
|
||||
if(!isnum(x1) || !isnum(y1) || !isnum(x2) || !isnum(y2))
|
||||
CRASH("Invalid arguments provided to crop: [x1],[y1],[x2],[y2]")
|
||||
#endif
|
||||
transforms += list(list("type" = RUSTG_ICONFORGE_CROP, "x1" = x1, "y1" = y1, "x2" = x2, "y2" = y2))
|
||||
|
||||
/datum/icon_transformer/proc/flip(dir)
|
||||
#ifdef UNIT_TESTS
|
||||
if(!isnum(dir))
|
||||
CRASH("Invalid arguments provided to flip: [dir]")
|
||||
#endif
|
||||
transforms += list(list("type" = RUSTG_ICONFORGE_FLIP, "dir" = dir))
|
||||
|
||||
/datum/icon_transformer/proc/rotate(angle)
|
||||
#ifdef UNIT_TESTS
|
||||
if(!isnum(angle))
|
||||
CRASH("Invalid arguments provided to rotate: [angle]")
|
||||
#endif
|
||||
transforms += list(list("type" = RUSTG_ICONFORGE_TURN, "angle" = angle))
|
||||
|
||||
/datum/icon_transformer/proc/shift(dir, offset, wrap=FALSE)
|
||||
#ifdef UNIT_TESTS
|
||||
if(!isnum(dir) || !isnum(offset) || (wrap != FALSE && wrap != TRUE))
|
||||
CRASH("Invalid arguments provided to shift: [dir],[offset],[wrap]")
|
||||
#endif
|
||||
transforms += list(list("type" = RUSTG_ICONFORGE_SHIFT, "dir" = dir, "offset" = offset, "wrap" = wrap))
|
||||
|
||||
/datum/icon_transformer/proc/swap_color(src_color, dst_color)
|
||||
#ifdef UNIT_TESTS
|
||||
if(!istext(src_color) || !istext(dst_color))
|
||||
CRASH("Invalid arguments provided to swap_color: [src_color],[dst_color]")
|
||||
#endif
|
||||
transforms += list(list("type" = RUSTG_ICONFORGE_SWAP_COLOR, "src_color" = src_color, "dst_color" = dst_color))
|
||||
|
||||
/datum/icon_transformer/proc/draw_box(color, x1, y1, x2=x1, y2=y1)
|
||||
#ifdef UNIT_TESTS
|
||||
if(!istext(color) || !isnum(x1) || !isnum(y1) || !isnum(x2) || !isnum(y2))
|
||||
CRASH("Invalid arguments provided to draw_box: [color],[x1],[y1],[x2],[y2]")
|
||||
#endif
|
||||
transforms += list(list("type" = RUSTG_ICONFORGE_DRAW_BOX, "color" = color, "x1" = x1, "y1" = y1, "x2" = x2, "y2" = y2))
|
||||
|
||||
/datum/icon_transformer/proc/map_colors(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0=0, g0=0, b0=0, a0=0)
|
||||
transforms += list(list(
|
||||
"type" = RUSTG_ICONFORGE_MAP_COLORS,
|
||||
"rr" = rr, "rg" = rg, "rb" = rb, "ra" = ra,
|
||||
"gr" = gr, "gg" = gg, "gb" = gb, "ga" = ga,
|
||||
"br" = br, "bg" = bg, "bb" = bb, "ba" = ba,
|
||||
"ar" = ar, "ag" = ag, "ab" = ab, "aa" = aa,
|
||||
"r0" = r0, "g0" = g0, "b0" = b0, "a0" = a0,
|
||||
))
|
||||
|
||||
/// Recursively converts all contained [/datum/universal_icon]s and their associated [/datum/icon_transformer]s into list form so the transforms can be JSON encoded.
|
||||
/datum/icon_transformer/proc/to_list()
|
||||
RETURN_TYPE(/list)
|
||||
@@ -204,26 +373,240 @@
|
||||
return transform
|
||||
|
||||
/// Converts a GAGS item to a universal icon by generating blend operations.
|
||||
// /proc/gags_to_universal_icon(obj/item/path)
|
||||
// RETURN_TYPE(/datum/universal_icon)
|
||||
// if(!ispath(path, /obj/item) || !initial(path.greyscale_config) || !initial(path.greyscale_colors))
|
||||
// CRASH("gags_to_universal_icon() received an invalid path!")
|
||||
// var/datum/greyscale_config/config = initial(path.greyscale_config)
|
||||
// var/colors = initial(path.greyscale_colors)
|
||||
// var/datum/universal_icon/entry = SSgreyscale.GetColoredIconEntryByType(config, colors, initial(path.icon_state))
|
||||
// return entry
|
||||
/*
|
||||
/proc/gags_to_universal_icon(atom/path)
|
||||
RETURN_TYPE(/datum/universal_icon)
|
||||
if(!ispath(path, /atom) || !initial(path.greyscale_config) || !initial(path.greyscale_colors))
|
||||
CRASH("gags_to_universal_icon() received an invalid path of \"[path]\"!")
|
||||
var/datum/greyscale_config/config = initial(path.greyscale_config)
|
||||
var/colors = initial(path.greyscale_colors)
|
||||
var/datum/universal_icon/entry = SSgreyscale.GetColoredIconByTypeUniversalIcon(config, colors, path::post_init_icon_state || path::icon_state)
|
||||
return entry
|
||||
*/
|
||||
|
||||
/// Gets the relevant universal icon for an atom, when displayed in TGUI. (see: icon_state_preview)
|
||||
/// Supports GAGS items and colored items.
|
||||
/proc/get_display_icon_for(atom/A)
|
||||
if (!ispath(A, /atom))
|
||||
/proc/get_display_icon_for(atom/atom_path)
|
||||
if (!ispath(atom_path, /atom))
|
||||
return FALSE
|
||||
var/icon_file = initial(A.icon)
|
||||
var/icon_state = initial(A.icon_state)
|
||||
// if(ispath(A, /obj/item))
|
||||
// var/obj/item/I = A
|
||||
// if(initial(I.icon_state_preview))
|
||||
// icon_state = initial(I.icon_state_preview)
|
||||
// if(initial(I.greyscale_config) && initial(I.greyscale_colors))
|
||||
// return gags_to_universal_icon(I)
|
||||
return uni_icon(icon_file, icon_state, color=initial(A.color))
|
||||
var/icon_file = atom_path::icon
|
||||
var/icon_state = atom_path::icon_state
|
||||
/*
|
||||
if(atom_path::greyscale_config && atom_path::greyscale_colors)
|
||||
return gags_to_universal_icon(atom_path)
|
||||
if(ispath(atom_path, /obj))
|
||||
var/obj/obj_path = atom_path
|
||||
if(obj_path::icon_state_preview)
|
||||
icon_state = obj_path::icon_state_preview
|
||||
*/
|
||||
return uni_icon(icon_file, icon_state, color=atom_path::color)
|
||||
|
||||
/// getFlatIcon for [/datum/universal_icon]s
|
||||
/// Still fairly slow for complex appearances due to filesystem operations. Try to avoid using it
|
||||
/proc/get_flat_uni_icon(image/appearance, defdir, deficon, defstate, defblend, start = TRUE, parentcolor)
|
||||
// Loop through the underlays, then overlays, sorting them into the layers list
|
||||
#define PROCESS_OVERLAYS_OR_UNDERLAYS(flat, process, base_layer) \
|
||||
for (var/i in 1 to process.len) { \
|
||||
var/image/current = process[i]; \
|
||||
if (!current) { \
|
||||
continue; \
|
||||
} \
|
||||
if (current.plane != FLOAT_PLANE && current.plane != appearance.plane) { \
|
||||
continue; \
|
||||
} \
|
||||
var/current_layer = current.layer; \
|
||||
if (current_layer < 0) { \
|
||||
if (current_layer <= -1000) { \
|
||||
return flat; \
|
||||
} \
|
||||
current_layer = base_layer + appearance.layer + current_layer / 1000; \
|
||||
} \
|
||||
/* If we are using topdown rendering, chop that part off so things layer together as expected */ \
|
||||
if((current_layer >= TOPDOWN_LAYER && current_layer < EFFECTS_LAYER) || current_layer > TOPDOWN_LAYER + EFFECTS_LAYER) { \
|
||||
current_layer -= TOPDOWN_LAYER; \
|
||||
} \
|
||||
for (var/index_to_compare_to in 1 to layers.len) { \
|
||||
var/compare_to = layers[index_to_compare_to]; \
|
||||
if (current_layer < layers[compare_to]) { \
|
||||
layers.Insert(index_to_compare_to, current); \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
layers[current] = current_layer; \
|
||||
}
|
||||
|
||||
var/datum/universal_icon/flat = uni_icon('icons/blanks/32x32.dmi', "nothing")
|
||||
|
||||
if(!appearance || appearance.alpha <= 0)
|
||||
return flat
|
||||
|
||||
if(start)
|
||||
if(!deficon)
|
||||
deficon = appearance.icon
|
||||
if(!defstate)
|
||||
defstate = appearance.icon_state
|
||||
if(!defblend)
|
||||
defblend = appearance.blend_mode
|
||||
|
||||
var/should_display = TRUE
|
||||
var/curicon = appearance.icon || deficon
|
||||
var/string_curicon = "[curicon]"
|
||||
var/curstate = appearance.icon_state || defstate
|
||||
// Filter out 'runtime' icons (server-generated RSC cache icons)
|
||||
// Write the icon to the filesystem so it can be used by iconforge
|
||||
if(!isfile(curicon) || !length(string_curicon))
|
||||
var/file_path_tmp = "tmp/uni_icon-tmp-[rand(1, 999)].dmi" // this filename is temporary.
|
||||
fcopy(curicon, file_path_tmp)
|
||||
var/file_hash = rustg_hash_file(RUSTG_HASH_MD5, file_path_tmp)
|
||||
// Use the hash as its new filename - this allows the uni_icon to be smart cached, because the filename will be consistent between runs if the content is the same
|
||||
var/file_path = "tmp/uni_icon-[file_hash].dmi"
|
||||
fcopy(file_path_tmp, file_path)
|
||||
fdel(file_path_tmp) // delete the old one
|
||||
curicon = file(file_path)
|
||||
|
||||
if(!icon_exists(curicon, curstate))
|
||||
if("" in icon_states_fast(curicon)) // BYOND defaulting functionality
|
||||
curstate = ""
|
||||
else
|
||||
should_display = FALSE
|
||||
|
||||
var/curdir = (!appearance.dir || appearance.dir == SOUTH) ? defdir : appearance.dir
|
||||
var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have
|
||||
|
||||
if(should_display)
|
||||
//Determines if there're directionals.
|
||||
if (curdir != SOUTH)
|
||||
// icon states either have 1, 4 or 8 dirs. We only have to check
|
||||
// one of NORTH, EAST or WEST to know that this isn't a 1-dir icon_state since they just have SOUTH.
|
||||
var/list/metadata = icon_metadata(curicon)
|
||||
if(islist(metadata))
|
||||
for(var/list/state_data as anything in metadata["states"])
|
||||
var/name = state_data["name"]
|
||||
if(name != curstate)
|
||||
continue
|
||||
var/dir_count = state_data["dirs"]
|
||||
if(dir_count == 1)
|
||||
base_icon_dir = SOUTH
|
||||
else if(!length(icon_states(icon(curicon, curstate, NORTH))))
|
||||
base_icon_dir = SOUTH
|
||||
|
||||
var/list/icon_dimensions = get_icon_dimensions(curicon)
|
||||
var/icon_width = icon_dimensions["width"]
|
||||
var/icon_height = icon_dimensions["height"]
|
||||
if(icon_width != 32 || icon_height != 32)
|
||||
flat.scale(icon_width, icon_height)
|
||||
|
||||
if(!base_icon_dir)
|
||||
base_icon_dir = curdir
|
||||
|
||||
var/curblend = appearance.blend_mode || defblend
|
||||
|
||||
|
||||
if(appearance.overlays.len || appearance.underlays.len)
|
||||
// Layers will be a sorted list of icons/overlays, based on the order in which they are displayed
|
||||
var/list/layers = list()
|
||||
var/image/copy
|
||||
if(should_display)
|
||||
// Add the atom's icon itself, without pixel_x/y offsets.
|
||||
copy = image(icon=curicon, icon_state=curstate, layer=appearance.layer, dir=base_icon_dir)
|
||||
copy.color = appearance.color
|
||||
copy.alpha = appearance.alpha
|
||||
copy.blend_mode = curblend
|
||||
layers[copy] = appearance.layer
|
||||
|
||||
PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.underlays, 0)
|
||||
PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.overlays, 1)
|
||||
|
||||
var/datum/universal_icon/add // Icon of overlay being added
|
||||
|
||||
var/list/flat_dimensions = get_icon_dimensions(flat)
|
||||
var/flatX1 = 1
|
||||
var/flatX2 = flat_dimensions["width"]
|
||||
var/flatY1 = 1
|
||||
var/flatY2 = flat_dimensions["height"]
|
||||
|
||||
var/addX1 = 0
|
||||
var/addX2 = 0
|
||||
var/addY1 = 0
|
||||
var/addY2 = 0
|
||||
|
||||
if(appearance.color)
|
||||
if(islist(appearance.color))
|
||||
flat.map_colors_inferred(appearance.color)
|
||||
else
|
||||
flat.blend_color(appearance.color, ICON_MULTIPLY)
|
||||
|
||||
if(parentcolor && !(appearance.appearance_flags & RESET_COLOR))
|
||||
if(islist(parentcolor))
|
||||
flat.map_colors_inferred(parentcolor)
|
||||
else
|
||||
flat.blend_color(parentcolor, ICON_MULTIPLY)
|
||||
|
||||
var/next_parentcolor = appearance.color || parentcolor
|
||||
|
||||
for(var/image/layer_image as anything in layers)
|
||||
if(layer_image.alpha == 0)
|
||||
continue
|
||||
|
||||
if(layer_image == copy && length("[layer_image.icon]")) // 'layer_image' is an /image based on the object being flattened, and isn't a 'runtime' icon.
|
||||
curblend = BLEND_OVERLAY
|
||||
add = uni_icon(layer_image.icon, layer_image.icon_state, base_icon_dir)
|
||||
if(appearance.color)
|
||||
if(islist(appearance.color))
|
||||
add.map_colors_inferred(appearance.color)
|
||||
else
|
||||
add.blend_color(appearance.color, ICON_MULTIPLY)
|
||||
else // 'layer_image' is an appearance object.
|
||||
add = get_flat_uni_icon(layer_image, curdir, curicon, curstate, curblend, FALSE, next_parentcolor)
|
||||
if(!add || !length(add.icon_file))
|
||||
continue
|
||||
|
||||
// Find the new dimensions of the flat icon to fit the added overlay
|
||||
var/list/add_dimensions = get_icon_dimensions(add)
|
||||
addX1 = min(flatX1, layer_image.pixel_x + layer_image.pixel_w + 1)
|
||||
addX2 = max(flatX2, layer_image.pixel_x + layer_image.pixel_w + add_dimensions["width"]) // assuming 32x32
|
||||
addY1 = min(flatY1, layer_image.pixel_y + layer_image.pixel_z + 1)
|
||||
addY2 = max(flatY2, layer_image.pixel_y + layer_image.pixel_z + add_dimensions["height"])
|
||||
|
||||
if (
|
||||
addX1 != flatX1 \
|
||||
&& addX2 != flatX2 \
|
||||
&& addY1 != flatY1 \
|
||||
&& addY2 != flatY2 \
|
||||
)
|
||||
// Resize the flattened icon so the new icon fits
|
||||
flat.crop(
|
||||
addX1 - flatX1 + 1,
|
||||
addY1 - flatY1 + 1,
|
||||
addX2 - flatX1 + 1,
|
||||
addY2 - flatY1 + 1
|
||||
)
|
||||
|
||||
flatX1 = addX1
|
||||
flatX2 = addY1
|
||||
flatY1 = addX2
|
||||
flatY2 = addY2
|
||||
|
||||
// Blend the overlay into the flattened icon
|
||||
flat.blend_icon(add, blendMode2iconMode(curblend), layer_image.pixel_x + layer_image.pixel_w + 2 - flatX1, layer_image.pixel_y + layer_image.pixel_z + 2 - flatY1)
|
||||
|
||||
if(appearance.alpha < 255)
|
||||
flat.blend_color(rgb(255, 255, 255, appearance.alpha), ICON_MULTIPLY)
|
||||
|
||||
return flat
|
||||
|
||||
else if(should_display) // There's no overlays.
|
||||
var/datum/universal_icon/final_icon = uni_icon(curicon, curstate, base_icon_dir)
|
||||
|
||||
if (appearance.alpha < 255)
|
||||
final_icon.blend_color(rgb(255,255,255, appearance.alpha), ICON_MULTIPLY)
|
||||
|
||||
if (appearance.color)
|
||||
if (islist(appearance.color))
|
||||
final_icon.map_colors_inferred(appearance.color)
|
||||
else
|
||||
final_icon.blend_color(appearance.color, ICON_MULTIPLY)
|
||||
|
||||
return final_icon
|
||||
|
||||
#undef PROCESS_OVERLAYS_OR_UNDERLAYS
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
/// asset_list - A list of asset filenames to be sent to the client. Can optionally be assoicated with the asset's asset_cache_item datum.
|
||||
/// Returns TRUE if any assets were sent.
|
||||
/datum/asset_transport/proc/send_assets(client/client, list/asset_list)
|
||||
#if defined(UNIT_TEST)
|
||||
#if defined(UNIT_TESTS)
|
||||
return
|
||||
#endif
|
||||
if (!istype(client))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/proc/createRandomZlevel()
|
||||
#ifdef UNIT_TEST
|
||||
#ifdef UNIT_TESTS
|
||||
return
|
||||
#endif
|
||||
if(GLOB.awaydestinations.len) //crude, but it saves another var! //VOREStation Edit - No loading away missions during CI testing
|
||||
|
||||
@@ -206,6 +206,14 @@
|
||||
/client/proc/_Topic(datum/hsrc, href, list/href_list)
|
||||
return hsrc.Topic(href, href_list)
|
||||
|
||||
/client/proc/is_localhost()
|
||||
var/static/localhost_addresses = list(
|
||||
"127.0.0.1",
|
||||
"::1",
|
||||
null,
|
||||
)
|
||||
return address in localhost_addresses
|
||||
|
||||
//This stops files larger than UPLOAD_LIMIT being sent from client to server via input(), client.Import() etc.
|
||||
/client/AllowUpload(filename, filelength)
|
||||
if(filelength > UPLOAD_LIMIT)
|
||||
|
||||
@@ -49,9 +49,9 @@
|
||||
var/firstspace = findtext(pref.real_name, " ")
|
||||
var/name_length = length(pref.real_name)
|
||||
if(!firstspace) //we need a surname
|
||||
pref.real_name += " [pick(last_names)]"
|
||||
pref.real_name += " [pick(GLOB.last_names)]"
|
||||
else if(firstspace == name_length)
|
||||
pref.real_name += "[pick(last_names)]"
|
||||
pref.real_name += "[pick(GLOB.last_names)]"
|
||||
|
||||
character.real_name = pref.real_name
|
||||
character.name = character.real_name
|
||||
|
||||
@@ -428,9 +428,9 @@ var/list/preferences_datums = list()
|
||||
var/firstspace = findtext(real_name, " ")
|
||||
var/name_length = length(real_name)
|
||||
if(!firstspace) //we need a surname
|
||||
real_name += " [pick(last_names)]"
|
||||
real_name += " [pick(GLOB.last_names)]"
|
||||
else if(firstspace == name_length)
|
||||
real_name += "[pick(last_names)]"
|
||||
real_name += "[pick(GLOB.last_names)]"
|
||||
character.real_name = real_name
|
||||
character.name = character.real_name
|
||||
if(character.dna)
|
||||
|
||||
@@ -127,3 +127,9 @@
|
||||
|
||||
INVOKE_ASYNC(client, TYPE_VERB_REF(/client, refresh_tgui))
|
||||
client.tgui_say?.load()
|
||||
|
||||
/// Enables flashing the window in your task tray for important events
|
||||
/datum/preference/toggle/window_flashing
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "windowflashing"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
to_chat(src, "You're already dead!")
|
||||
return
|
||||
|
||||
if (!ticker)
|
||||
if (!SSticker)
|
||||
to_chat(src, "You can't commit suicide before the game starts!")
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
to_chat(src, "You're already dead!")
|
||||
return
|
||||
|
||||
if (!ticker)
|
||||
if (!SSticker)
|
||||
to_chat(src, "You can't commit suicide before the game starts!")
|
||||
return
|
||||
|
||||
|
||||
@@ -136,17 +136,17 @@
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/Initialize(mapload)
|
||||
. = ..()
|
||||
radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT) // Makes it so you don't need to change the frequency off of default for it to work.
|
||||
radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) // Makes it so you don't need to change the frequency off of default for it to work.
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/Destroy() //Clean up your toys when you're done.
|
||||
radio_controller.remove_object(src, frequency)
|
||||
SSradio.remove_object(src, frequency)
|
||||
radio_connection = null //Don't delete this, this is a shared object.
|
||||
return ..()
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/proc/set_frequency(new_frequency)
|
||||
radio_controller.remove_object(src, frequency)
|
||||
SSradio.remove_object(src, frequency)
|
||||
frequency = new_frequency
|
||||
radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT)
|
||||
radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT)
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/attack_self(mob/user as mob, flag1)
|
||||
if(!ishuman(user))
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/// The debugger instance.
|
||||
/// This is a GLOBAL_REAL because it initializes before the MC or GLOB.
|
||||
/// Really only used to check to see if the debugger is enabled or not,
|
||||
/// and to separate debugger-related code into its own thing.
|
||||
GLOBAL_REAL(Debugger, /datum/debugger)
|
||||
|
||||
/datum/debugger
|
||||
/// Is the debugger enabled?
|
||||
VAR_FINAL/enabled = FALSE
|
||||
/// The error text, if initializing the debugger errored.
|
||||
VAR_FINAL/error
|
||||
/// The path to the auxtools debug DLL, if it sets.
|
||||
/// Defaults to the environmental variable AUXTOOLS_DEBUG_DLL.
|
||||
VAR_FINAL/dll_path
|
||||
|
||||
/datum/debugger/New(dll_path)
|
||||
if(!isnull(Debugger))
|
||||
CRASH("Attempted to initialize /datum/debugger when global.Debugger is already set!")
|
||||
Debugger = src
|
||||
#ifndef OPENDREAM_REAL
|
||||
src.dll_path = dll_path || world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
|
||||
enable()
|
||||
#endif
|
||||
|
||||
/datum/debugger/Destroy()
|
||||
#ifndef OPENDREAM_REAL
|
||||
if(enabled)
|
||||
call_ext(dll_path, "auxtools_shutdown")()
|
||||
#endif
|
||||
return ..()
|
||||
|
||||
/// Attempt to enable the debugger.
|
||||
/datum/debugger/proc/enable()
|
||||
#ifndef OPENDREAM_REAL
|
||||
if(enabled)
|
||||
CRASH("Attempted to enable debugger while its already enabled, somehow.")
|
||||
if(!dll_path)
|
||||
return FALSE
|
||||
var/result = call_ext(dll_path, "auxtools_init")()
|
||||
if(result != "SUCCESS")
|
||||
error = result
|
||||
return FALSE
|
||||
enable_debugging()
|
||||
enabled = TRUE
|
||||
return TRUE
|
||||
#else
|
||||
return FALSE
|
||||
#endif
|
||||
|
||||
/datum/debugger/vv_edit_var(var_name, var_value)
|
||||
return FALSE // no.
|
||||
|
||||
/datum/debugger/CanProcCall(procname)
|
||||
return FALSE // double no.
|
||||
@@ -0,0 +1,65 @@
|
||||
/// The byond-tracy instance.
|
||||
/// This is a GLOBAL_REAL because it is the VERY FIRST THING to initialize, even before the MC or GLOB.
|
||||
GLOBAL_REAL(Tracy, /datum/tracy)
|
||||
|
||||
/datum/tracy
|
||||
/// Is byond-tracy enabled and running?
|
||||
VAR_FINAL/enabled = FALSE
|
||||
/// The error text, if initializing byond-tracy errored.
|
||||
VAR_FINAL/error
|
||||
/// A description of what / who enabled byond-tracy.
|
||||
VAR_FINAL/init_reason
|
||||
/// A path to the file containing the output trace, if any.
|
||||
VAR_FINAL/trace_path
|
||||
|
||||
/datum/tracy/New()
|
||||
if(!isnull(Tracy))
|
||||
CRASH("Attempted to initialize /datum/tracy when global.Tracy is already set!")
|
||||
Tracy = src
|
||||
|
||||
/datum/tracy/Destroy()
|
||||
#ifndef OPENDREAM_REAL
|
||||
if(enabled)
|
||||
call_ext(TRACY_DLL_PATH, "destroy")()
|
||||
#endif
|
||||
return ..()
|
||||
|
||||
/// Tries to initialize byond-tracy.
|
||||
/datum/tracy/proc/enable(init_reason)
|
||||
#ifndef OPENDREAM_REAL
|
||||
if(enabled)
|
||||
return TRUE
|
||||
src.init_reason = init_reason
|
||||
if(!fexists(TRACY_DLL_PATH))
|
||||
error = "[TRACY_DLL_PATH] not found"
|
||||
SEND_TEXT(world.log, "Error initializing byond-tracy: [error]")
|
||||
return FALSE
|
||||
|
||||
var/init_result = call_ext(TRACY_DLL_PATH, "init")("block")
|
||||
if(length(init_result) != 0 && init_result[1] == ".") // if first character is ., then it returned the output filename
|
||||
SEND_TEXT(world.log, "byond-tracy initialized (logfile: [init_result])")
|
||||
enabled = TRUE
|
||||
trace_path = init_result
|
||||
return TRUE
|
||||
else if(init_result == "already initialized") // not gonna question it.
|
||||
enabled = TRUE
|
||||
SEND_TEXT(world.log, "byond-tracy already initialized ([trace_path ? "logfile: [trace_path]" : "no logfile"])")
|
||||
return TRUE
|
||||
else if(init_result != "0")
|
||||
error = init_result
|
||||
SEND_TEXT(world.log, "Error initializing byond-tracy: [init_result]")
|
||||
return FALSE
|
||||
else
|
||||
enabled = TRUE
|
||||
SEND_TEXT(world.log, "byond-tracy initialized (no logfile)")
|
||||
return TRUE
|
||||
#else
|
||||
error = "OpenDream not supported"
|
||||
return FALSE
|
||||
#endif
|
||||
|
||||
/datum/tracy/vv_edit_var(var_name, var_value)
|
||||
return FALSE // no.
|
||||
|
||||
/datum/tracy/CanProcCall(procname)
|
||||
return FALSE // double no.
|
||||
@@ -60,7 +60,7 @@
|
||||
if(2)
|
||||
sender = pick(300;"QuickDatingSystem",200;"Find your russian bride",50;"Tajaran beauties are waiting",50;"Find your secret skrell crush",50;"Beautiful unathi brides")
|
||||
message = pick("Your profile caught my attention and I wanted to write and say hello (QuickDating).",\
|
||||
"If you will write to me on my email [pick(first_names_female)]@[pick(last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\
|
||||
"If you will write to me on my email [pick(GLOB.first_names_female)]@[pick(GLOB.last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\
|
||||
"I want that we write each other and I hope, that you will like my profile and you will answer me (QuickDating).",\
|
||||
"You have (1) new message!",\
|
||||
"You have (2) new profile views!")
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
// Success!
|
||||
SSsupply.points += 100 * severity
|
||||
var/msg = "Great work! With those items you delivered our inventory levels all match up. "
|
||||
msg += "[capitalize(pick(first_names_female))] from accounting will have nothing to complain about. "
|
||||
msg += "[capitalize(pick(GLOB.first_names_female))] from accounting will have nothing to complain about. "
|
||||
msg += "I think you'll find a little something in your supply account."
|
||||
command_announcement.Announce(msg, my_department)
|
||||
else
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
if(2)
|
||||
sender = pick(300;"QuickDatingSystem",200;"Find your russian bride",50;"Tajaran beauties are waiting",50;"Find your secret skrell crush",50;"Beautiful unathi brides")
|
||||
message = pick("Your profile caught my attention and I wanted to write and say hello (QuickDating).",\
|
||||
"If you will write to me on my email [pick(first_names_female)]@[pick(last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\
|
||||
"If you will write to me on my email [pick(GLOB.first_names_female)]@[pick(GLOB.last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\
|
||||
"I want that we write each other and I hope, that you will like my profile and you will answer me (QuickDating).",\
|
||||
"You have (1) new message!",\
|
||||
"You have (2) new profile views!")
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
event_type = /datum/event2/event/ghost_pod_spawner/stowaway/infiltrator
|
||||
|
||||
/datum/event2/meta/stowaway/get_weight()
|
||||
if(istype(ticker.mode, /datum/game_mode/extended) && !safe_for_extended)
|
||||
if(istype(SSticker.mode, /datum/game_mode/extended) && !safe_for_extended)
|
||||
return 0
|
||||
|
||||
var/security = GLOB.metric.count_people_in_department(DEPARTMENT_SECURITY)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
var/safe_for_extended = FALSE
|
||||
|
||||
/datum/event2/meta/swarm_boarder/get_weight()
|
||||
if(istype(ticker.mode, /datum/game_mode/extended) && !safe_for_extended)
|
||||
if(istype(SSticker.mode, /datum/game_mode/extended) && !safe_for_extended)
|
||||
return 0
|
||||
|
||||
var/security = GLOB.metric.count_people_in_department(DEPARTMENT_SECURITY)
|
||||
|
||||
@@ -424,8 +424,8 @@
|
||||
addtimer(CALLBACK(src, PROC_REF(set_frequency), frequency), 40)
|
||||
|
||||
/obj/item/integrated_circuit/input/signaler/Destroy()
|
||||
if(radio_controller)
|
||||
radio_controller.remove_object(src,frequency)
|
||||
if(SSradio)
|
||||
SSradio.remove_object(src,frequency)
|
||||
frequency = 0
|
||||
. = ..()
|
||||
|
||||
@@ -452,13 +452,13 @@
|
||||
/obj/item/integrated_circuit/input/signaler/proc/set_frequency(new_frequency)
|
||||
if(!frequency)
|
||||
return
|
||||
if(!radio_controller)
|
||||
if(!SSradio)
|
||||
sleep(20)
|
||||
if(!radio_controller)
|
||||
if(!SSradio)
|
||||
return
|
||||
radio_controller.remove_object(src, frequency)
|
||||
SSradio.remove_object(src, frequency)
|
||||
frequency = new_frequency
|
||||
radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT)
|
||||
radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT)
|
||||
|
||||
/obj/item/integrated_circuit/input/signaler/receive_signal(datum/signal/signal)
|
||||
var/new_code = get_pin_data(IC_INPUT, 2)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
var/turf/affected_turf
|
||||
|
||||
/datum/lighting_object/New(turf/source)
|
||||
if(!SSlighting.subsystem_initialized)
|
||||
if(!SSlighting.initialized)
|
||||
stack_trace("lighting_object created before SSlighting up!")
|
||||
return
|
||||
if(!isturf(source))
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
|
||||
|
||||
/turf/proc/change_area(area/old_area, area/new_area)
|
||||
if(SSlighting.subsystem_initialized)
|
||||
if(SSlighting.initialized)
|
||||
if (new_area.dynamic_lighting != old_area.dynamic_lighting)
|
||||
if (new_area.dynamic_lighting)
|
||||
lighting_build_overlay()
|
||||
|
||||
@@ -10,11 +10,11 @@ GLOBAL_LIST_EMPTY(map_templates_loaded)
|
||||
|
||||
/datum/map_template/proc/on_map_loaded(z)
|
||||
//We missed air init!
|
||||
if(SSair.subsystem_initialized)
|
||||
if(SSair.initialized)
|
||||
for(var/turf/simulated/T in block(locate(1,1,z), locate(world.maxx, world.maxy, z)))
|
||||
T.update_air_properties()
|
||||
//We missed sslighting init!
|
||||
if(SSlighting.subsystem_initialized)
|
||||
if(SSlighting.initialized)
|
||||
for(var/Trf in block(locate(1,1,z), locate(world.maxx, world.maxy, z)))
|
||||
var/turf/T = Trf //faster than implicit istype with typed for loop
|
||||
T.lighting_build_overlay()
|
||||
|
||||
@@ -170,7 +170,7 @@ var/list/mining_overlay_cache = list()
|
||||
|
||||
/turf/simulated/mineral/proc/update_general()
|
||||
recalculate_directional_opacity()
|
||||
if(ticker && ticker.current_state == GAME_STATE_PLAYING)
|
||||
if(SSticker && SSticker.current_state == GAME_STATE_PLAYING)
|
||||
reconsider_lights()
|
||||
if(SSair)
|
||||
SSair.mark_for_update(src)
|
||||
|
||||
@@ -74,9 +74,9 @@
|
||||
name = M.real_name
|
||||
else
|
||||
if(gender == MALE)
|
||||
name = capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
|
||||
name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
|
||||
else
|
||||
name = capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names))
|
||||
name = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
|
||||
|
||||
mind = M.mind //we don't transfer the mind but we keep a reference to it.
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
to_chat(src, span_danger("Could not locate an observer spawn point. Use the Teleport verb to jump to the station map."))
|
||||
|
||||
if(!name) //To prevent nameless ghosts
|
||||
name = capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
|
||||
name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
|
||||
real_name = name
|
||||
animate(src, pixel_y = 2, time = 10, loop = -1)
|
||||
animate(pixel_y = default_pixel_y, time = 10, loop = -1)
|
||||
@@ -683,7 +683,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
|
||||
return 0 //something is terribly wrong
|
||||
|
||||
var/ghosts_can_write
|
||||
if(ticker.mode.name == "cult")
|
||||
if(SSticker.mode.name == "cult")
|
||||
if(cult.current_antagonists.len > CONFIG_GET(number/cult_ghostwriter_req_cultists))
|
||||
ghosts_can_write = 1
|
||||
|
||||
|
||||
@@ -116,8 +116,8 @@
|
||||
handle_regular_hud_updates()
|
||||
handle_vision()
|
||||
|
||||
if(ticker && ticker.mode)
|
||||
ticker.mode.check_win()
|
||||
if(SSticker && SSticker.mode)
|
||||
SSticker.mode.check_win()
|
||||
|
||||
|
||||
return 1
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
/obj/machinery/camera/Initialize(mapload)
|
||||
. = ..()
|
||||
//Camera must be added to global list of all cameras no matter what...
|
||||
if(cameranet.cameras_unsorted || !ticker)
|
||||
if(cameranet.cameras_unsorted || !SSticker)
|
||||
cameranet.cameras += src
|
||||
cameranet.cameras_unsorted = 1
|
||||
else
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// TURFS
|
||||
|
||||
/proc/updateVisibility(atom/A, var/opacity_check = 1)
|
||||
if(ticker)
|
||||
if(SSticker)
|
||||
for(var/datum/visualnet/VN in visual_nets)
|
||||
VN.updateVisibility(A, opacity_check)
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
/datum/visualnet/proc/updateVisibility(atom/A, var/opacity_check = 1)
|
||||
|
||||
if(!ticker || (opacity_check && !A.opacity))
|
||||
if(!SSticker || (opacity_check && !A.opacity))
|
||||
return
|
||||
majorChunkChange(A, 2)
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
/datum/language/proc/get_random_name(var/gender, name_count=2, syllable_count=4, syllable_divisor=2)
|
||||
if(!syllables || !syllables.len)
|
||||
if(gender==FEMALE)
|
||||
return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names))
|
||||
return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
|
||||
else
|
||||
return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
|
||||
return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
|
||||
|
||||
var/full_name = ""
|
||||
var/new_name = ""
|
||||
|
||||
@@ -127,8 +127,8 @@
|
||||
syllables = list("qr","qrr","xuq","qil","quum","xuqm","vol","xrim","zaoo","qu-uu","qix","qoo","zix")
|
||||
|
||||
/datum/language/skrell/get_random_name(var/gender)
|
||||
var/list/first_names = file2list('config/names/first_name_skrell.txt')
|
||||
var/list/last_names = file2list('config/names/last_name_skrell.txt')
|
||||
var/list/first_names = file2list('strings/names/first_name_skrell.txt')
|
||||
var/list/last_names = file2list('strings/names/last_name_skrell.txt')
|
||||
return "[pick(first_names)] [pick(last_names)]"
|
||||
|
||||
/datum/language/human
|
||||
@@ -152,9 +152,9 @@
|
||||
/datum/language/human/get_random_name(var/gender)
|
||||
if (prob(80))
|
||||
if(gender==FEMALE)
|
||||
return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names))
|
||||
return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
|
||||
else
|
||||
return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
|
||||
return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
|
||||
else
|
||||
return ..()
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
if(prob(70))
|
||||
return "[pick(list("PBU","HIU","SINA","ARMA","OSI"))]-[rand(100, 999)]"
|
||||
else
|
||||
return pick(ai_names)
|
||||
return pick(GLOB.ai_names)
|
||||
|
||||
/datum/language/teshari
|
||||
name = LANGUAGE_SCHECHI
|
||||
|
||||
@@ -117,11 +117,11 @@
|
||||
/datum/species/proc/get_random_name(var/gender)
|
||||
if(!name_language)
|
||||
if(gender == FEMALE)
|
||||
return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names))
|
||||
return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
|
||||
else if(gender == MALE)
|
||||
return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
|
||||
return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
|
||||
else
|
||||
return capitalize(prob(50) ? pick(first_names_male) : pick(first_names_female)) + " " + capitalize(pick(last_names))
|
||||
return capitalize(prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
|
||||
|
||||
var/datum/language/species_language = GLOB.all_languages[name_language]
|
||||
if(!species_language)
|
||||
|
||||
@@ -932,8 +932,8 @@
|
||||
|
||||
//check for nuke disks
|
||||
if(client && stat != DEAD) //if they are clientless and dead don't bother, the parent will treat them as any other container
|
||||
if(ticker && istype(ticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear
|
||||
var/datum/game_mode/nuclear/G = ticker.mode
|
||||
if(SSticker && istype(SSticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear
|
||||
var/datum/game_mode/nuclear/G = SSticker.mode
|
||||
if(G.check_mob(src))
|
||||
if(x <= TRANSITIONEDGE)
|
||||
inertia_dir = 4
|
||||
@@ -1429,7 +1429,7 @@
|
||||
"})
|
||||
|
||||
/mob/living/update_gravity(has_gravity)
|
||||
if(!ticker)
|
||||
if(!SSticker)
|
||||
return
|
||||
if(has_gravity)
|
||||
clear_alert("weightless")
|
||||
|
||||
@@ -113,11 +113,11 @@ var/list/ai_verbs_default = list(
|
||||
announcement.announcement_type = "A.I. Announcement"
|
||||
announcement.newscast = 1
|
||||
|
||||
var/list/possibleNames = ai_names
|
||||
var/list/possibleNames = GLOB.ai_names
|
||||
|
||||
var/pickedName = null
|
||||
while(!pickedName)
|
||||
pickedName = pick(ai_names)
|
||||
pickedName = pick(GLOB.ai_names)
|
||||
for (var/mob/living/silicon/ai/A in GLOB.mob_list)
|
||||
if (A.real_name == pickedName && possibleNames.len > 1) //fixing the theoretically possible infinite loop
|
||||
possibleNames -= pickedName
|
||||
|
||||
@@ -15,7 +15,7 @@ GLOBAL_LIST_EMPTY(empty_playable_ai_cores)
|
||||
set category = "OOC.Game"
|
||||
set desc = "Enter intelligence storage. This is functionally equivalent to cryo or robotic storage, freeing up your job slot."
|
||||
|
||||
if(ticker && ticker.mode && ticker.mode.name == "AI malfunction")
|
||||
if(SSticker && SSticker.mode && SSticker.mode.name == "AI malfunction")
|
||||
to_chat(src, span_danger("You cannot use this verb in malfunction. If you need to leave, please adminhelp."))
|
||||
return
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ var/datum/paiController/paiController // Global handler for pAI candidates
|
||||
GLOB.paikeys |= pai.ckey
|
||||
card.setPersonality(pai)
|
||||
if(!candidate.name)
|
||||
pai.SetName(pick(ninja_names))
|
||||
pai.SetName(pick(GLOB.ninja_names))
|
||||
else
|
||||
pai.SetName(candidate.name)
|
||||
if(candidate.description)
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
/obj/machinery/drone_fabricator/process()
|
||||
|
||||
if(ticker.current_state < GAME_STATE_PLAYING)
|
||||
if(SSticker.current_state < GAME_STATE_PLAYING)
|
||||
return
|
||||
|
||||
if(stat & NOPOWER || !produce_drones)
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
|
||||
/mob/living/silicon/robot/platform/attack_ghost(mob/observer/dead/user)
|
||||
|
||||
if(client || key || stat == DEAD || !ticker || !ticker.mode)
|
||||
if(client || key || stat == DEAD || !SSticker || !SSticker.mode)
|
||||
return ..()
|
||||
|
||||
var/confirm = tgui_alert(user, "Do you wish to take control of \the [src]?", "Platform Control", list("No", "Yes"))
|
||||
if(confirm != "Yes" || QDELETED(src) || client || key || stat == DEAD || !ticker || !ticker.mode)
|
||||
if(confirm != "Yes" || QDELETED(src) || client || key || stat == DEAD || !SSticker || !SSticker.mode)
|
||||
return ..()
|
||||
|
||||
if(jobban_isbanned(user, "Robot"))
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
var/datum/admins/is_admin = GLOB.admin_datums[src.ckey]
|
||||
if(is_admin && is_admin.check_for_rights(R_HOLDER))
|
||||
message_admins("Staff logout: [key_name(src)]") // Staff logout notice displays no matter what
|
||||
if (ticker && ticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing.
|
||||
if (SSticker && SSticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing.
|
||||
var/admins_number = GLOB.admins.len
|
||||
|
||||
if(admins_number == 0) //Apparently the admin logging out is no longer an admin at this point, so we have to check this towards 0 and not towards 1. Awell.
|
||||
|
||||
@@ -340,7 +340,7 @@
|
||||
// Try to figure out what time to use
|
||||
|
||||
// Special cases, can never respawn
|
||||
if(ticker?.mode?.deny_respawn)
|
||||
if(SSticker?.mode?.deny_respawn)
|
||||
time = -1
|
||||
else if(!CONFIG_GET(flag/abandon_allowed))
|
||||
time = -1
|
||||
@@ -348,7 +348,7 @@
|
||||
time = -1
|
||||
|
||||
// Special case for observing before game start
|
||||
else if(ticker?.current_state <= GAME_STATE_SETTING_UP)
|
||||
else if(SSticker?.current_state <= GAME_STATE_SETTING_UP)
|
||||
time = 1 MINUTE
|
||||
|
||||
// Wasn't given a time, use the config time
|
||||
@@ -377,7 +377,7 @@
|
||||
to_chat(src, span_boldnotice("You are already in the lobby!"))
|
||||
return
|
||||
|
||||
if(stat != DEAD || !ticker)
|
||||
if(stat != DEAD || !SSticker)
|
||||
to_chat(src, span_boldnotice("You must be dead to use this!"))
|
||||
return
|
||||
|
||||
|
||||
@@ -146,6 +146,9 @@
|
||||
// Used many times below, faster reference.
|
||||
var/atom/loc = my_mob.loc
|
||||
|
||||
if(HAS_TRAIT(my_mob, TRAIT_NO_TRANSFORM))
|
||||
return FALSE //This is sorta the goto stop mobs from moving trait
|
||||
|
||||
// We're controlling an object which is when admins possess an object.
|
||||
if(my_mob.control_object)
|
||||
Move_object(direct)
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
data["server_name"] = displayed_name
|
||||
data["map"] = using_map.full_name
|
||||
data["station_time"] = stationtime2text()
|
||||
data["display_loading"] = SSticker.current_state == GAME_STATE_INIT
|
||||
data["display_loading"] = SSticker.current_state == GAME_STATE_STARTUP
|
||||
data["round_start"] = !SSticker.mode || SSticker.current_state <= GAME_STATE_PREGAME
|
||||
data["round_time"] = roundduration2text()
|
||||
data["ready"] = ready
|
||||
@@ -82,7 +82,7 @@
|
||||
ViewManifest()
|
||||
return TRUE
|
||||
if("late_join")
|
||||
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
|
||||
if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING)
|
||||
to_chat(usr, span_red("The round is either not ready, or has already finished..."))
|
||||
return TRUE
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
if("observe")
|
||||
if(QDELETED(src))
|
||||
return FALSE
|
||||
if(!SSticker || SSticker.current_state == GAME_STATE_INIT)
|
||||
if(!SSticker || SSticker.current_state == GAME_STATE_STARTUP)
|
||||
to_chat(src, span_warning("The game is still setting up, please try again later."))
|
||||
return TRUE
|
||||
if(tgui_alert(src,"Are you sure you wish to observe? If you do, make sure to not use any knowledge gained from observing if you decide to join later.","Observe Round?",list("Yes","No")) == "Yes")
|
||||
@@ -160,7 +160,7 @@
|
||||
client.changes()
|
||||
return TRUE
|
||||
if("keyboard")
|
||||
if(!SSsounds.subsystem_initialized)
|
||||
if(!SSsounds.initialized)
|
||||
return
|
||||
|
||||
playsound_local(ui.user, get_sfx("keyboard"), vol = 20)
|
||||
|
||||
@@ -42,11 +42,11 @@
|
||||
// if(SSvote.mode)
|
||||
// . += "Vote: [capitalize(SSvote.mode)] Time Left: [SSvote.time_remaining] s"
|
||||
|
||||
if(SSticker.current_state == GAME_STATE_INIT)
|
||||
if(SSticker.current_state == GAME_STATE_STARTUP)
|
||||
. += "Time To Start: Server Initializing"
|
||||
|
||||
else if(SSticker.current_state == GAME_STATE_PREGAME)
|
||||
. += "Time To Start: [round(SSticker.pregame_timeleft,1)][GLOB.round_progressing ? "" : " (DELAYED)"]"
|
||||
. += "Time To Start: [round(SSticker.timeLeft, 1)][GLOB.round_progressing ? "" : " (DELAYED)"]"
|
||||
. += "Players: [totalPlayers]"
|
||||
. += "Players Ready: [totalPlayersReady]"
|
||||
totalPlayers = 0
|
||||
@@ -223,7 +223,7 @@
|
||||
/mob/new_player/proc/AttemptLateSpawn(rank,var/spawning_at)
|
||||
if (src != usr)
|
||||
return 0
|
||||
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
|
||||
if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING)
|
||||
to_chat(src, span_red("The round is either not ready, or has already finished..."))
|
||||
return 0
|
||||
if(!CONFIG_GET(flag/enter_allowed))
|
||||
@@ -297,7 +297,7 @@
|
||||
character = character.AIize(move = FALSE) // Dupe of code in /datum/controller/subsystem/ticker/proc/create_characters() for non-latespawn, unify?
|
||||
|
||||
AnnounceCyborg(character, rank, "has been transferred to the empty core in \the [character.loc.loc]")
|
||||
ticker.mode.latespawn(character)
|
||||
SSticker.mode.latespawn(character)
|
||||
|
||||
qdel(C) //Deletes empty core (really?)
|
||||
qdel(src) //Deletes new_player
|
||||
@@ -308,22 +308,22 @@
|
||||
character.buckled.loc = character.loc
|
||||
character.buckled.set_dir(character.dir)
|
||||
|
||||
ticker.mode.latespawn(character)
|
||||
SSticker.mode.latespawn(character)
|
||||
|
||||
if(rank == JOB_OUTSIDER)
|
||||
log_and_message_admins("has joined the round as non-crew. (<A href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[T.x];Y=[T.y];Z=[T.z]'>JMP</a>)",character)
|
||||
if(!(J.mob_type & JOB_SILICON))
|
||||
ticker.minds += character.mind
|
||||
SSticker.minds += character.mind
|
||||
else if(rank == JOB_ANOMALY)
|
||||
log_and_message_admins("has joined the round as anomaly. (<A href='byond://?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[T.x];Y=[T.y];Z=[T.z]'>JMP</a>)",character)
|
||||
if(!(J.mob_type & JOB_SILICON))
|
||||
ticker.minds += character.mind
|
||||
SSticker.minds += character.mind
|
||||
else if(J.mob_type & JOB_SILICON)
|
||||
AnnounceCyborg(character, rank, join_message, announce_channel, character.z)
|
||||
else
|
||||
AnnounceArrival(character, rank, join_message, announce_channel, character.z)
|
||||
GLOB.data_core.manifest_inject(character)
|
||||
ticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn
|
||||
SSticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn
|
||||
if(ishuman(character))
|
||||
if(character.client.prefs.auto_backup_implant)
|
||||
var/obj/item/implant/backup/imp = new(src)
|
||||
@@ -370,7 +370,7 @@
|
||||
qdel(src) // Delete new_player mob
|
||||
|
||||
/mob/new_player/proc/AnnounceCyborg(var/mob/living/character, var/rank, var/join_message, var/channel, var/zlevel)
|
||||
if (ticker.current_state == GAME_STATE_PLAYING)
|
||||
if (SSticker.current_state == GAME_STATE_PLAYING)
|
||||
var/list/zlevels = zlevel ? using_map.get_map_levels(zlevel, TRUE, om_range = DEFAULT_OVERMAP_RANGE) : null
|
||||
if(character.mind.role_alt_title)
|
||||
rank = character.mind.role_alt_title
|
||||
@@ -402,7 +402,7 @@
|
||||
if(!new_character)
|
||||
new_character = new(T)
|
||||
|
||||
if(ticker.random_players)
|
||||
if(CONFIG_GET(flag/force_random_names))
|
||||
new_character.gender = pick(MALE, FEMALE)
|
||||
client.prefs.real_name = random_name(new_character.gender)
|
||||
else
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
/*
|
||||
The overmap system allows adding new maps to the big 'galaxy' map.
|
||||
Idea is that new sectors can be added by just ticking in new maps and recompiling.
|
||||
Not real hot-plugging, but still pretty modular.
|
||||
It uses the fact that all ticked in .dme maps are melded together into one as different zlevels.
|
||||
Metaobjects are used to make it not affected by map order in .dme and carry some additional info.
|
||||
|
||||
*************************************************************
|
||||
Metaobject
|
||||
*************************************************************
|
||||
## Metaobject
|
||||
|
||||
/obj/effect/mapinfo, sectors.dm
|
||||
Used to build overmap in beginning, has basic information needed to create overmap objects and make shuttles work.
|
||||
Its name and icon (if non-standard) vars will be applied to resulting overmap object.
|
||||
@@ -19,9 +17,9 @@ Has two important vars:
|
||||
Object could be placed anywhere on zlevel. Should only be placed on zlevel that should appear on overmap as a separate entitety.
|
||||
Right after creation it sends itself to nullspace and creates an overmap object, corresponding to this zlevel.
|
||||
|
||||
*************************************************************
|
||||
Overmap object
|
||||
*************************************************************
|
||||
|
||||
## Overmap object
|
||||
|
||||
/obj/effect/map, sectors.dm
|
||||
Represents a zlevel on the overmap. Spawned by metaobjects at the startup.
|
||||
var/area/shuttle/shuttle_landing - keeps a reference to the area of where inbound shuttles should land
|
||||
@@ -33,9 +31,9 @@ Remember to call ..() in children, it updates ship's current sector.
|
||||
subtype /ship of this object represents spacefaring vessels.
|
||||
It has 'current_sector' var that keeps refernce to, well, sector ship currently in.
|
||||
|
||||
*************************************************************
|
||||
Helm console
|
||||
*************************************************************
|
||||
|
||||
## Helm console
|
||||
|
||||
/obj/machinery/computer/helm, helm.dm
|
||||
On creation console seeks a ship overmap object corresponding to this zlevel and links it.
|
||||
Clicking with empty hand on it starts steering, Cancel-Camera-View stops it.
|
||||
@@ -43,9 +41,9 @@ Helm console relays movement of mob to the linked overmap object.
|
||||
Helm console currently has no interface. All travel happens instanceously too.
|
||||
Sector shuttles are not supported currently, only ship shuttles.
|
||||
|
||||
*************************************************************
|
||||
Exploration shuttle terminal
|
||||
*************************************************************
|
||||
|
||||
## Exploration shuttle terminal
|
||||
|
||||
A generic shuttle controller.
|
||||
Has a var landing_type defining type of area shuttle should be landing at.
|
||||
On initalizing, checks for a shuttle corresponding to this zlevel, and creates one if it's not there.
|
||||
@@ -53,16 +51,16 @@ Changes desitnation area depending on current sector ship is in.
|
||||
Currently updating is called in attack_hand(), until a better place is found.
|
||||
Currently no modifications were made to interface to display availability of landing area in sector.
|
||||
|
||||
*************************************************************
|
||||
Landable Ships
|
||||
*************************************************************
|
||||
|
||||
## Landable Ships
|
||||
|
||||
Ship - Vessel that can move around on the overmap. It's entire z-level(s) "move" conceptually.
|
||||
Shuttles - Vessel that can jump to shuttle landmarks. Its areas move by transition_turfs.
|
||||
Landable Ship - Vessel that can do both. Sits at a special shuttle landmark for overmap movement mode.
|
||||
|
||||
*************************************************************
|
||||
Guide to how make new sector
|
||||
*************************************************************
|
||||
|
||||
## Guide to how make new sector
|
||||
|
||||
0.Map
|
||||
Remember to define shuttle areas if you want sector be accessible via shuttles.
|
||||
Currently there are no other ways to reach sectors from ships.
|
||||
@@ -73,38 +71,37 @@ Ships need engines to move. Currently there are only thermal engines.
|
||||
Thermal engines are just a unary atmopheric machine, like a vent. They need high-pressure gas input to produce more thrust.
|
||||
|
||||
|
||||
1.Metaobject
|
||||
1. Metaobject
|
||||
All vars needed for it to work could be set directly in map editor, so in most cases you won't have to define new in code.
|
||||
Remember to set landing_area var for sectors.
|
||||
|
||||
2.Overmap object
|
||||
2. Overmap object
|
||||
If you need custom behaviour on entering/leaving this sector, or restricting access to it, you can define your custom map object.
|
||||
Remember to put this new type into spawn_type var of metaobject.
|
||||
|
||||
3.Shuttle console
|
||||
3. Shuttle console
|
||||
Remember to place one on the actual shuttle too, or it won't be able to return from sector without ship-side recall.
|
||||
Remember to set landing_type var to ship-side shuttle area type.
|
||||
shuttle_tag can be set to custom name (it shows up in console interface)
|
||||
|
||||
5.Engines
|
||||
5. Engines
|
||||
Actual engines could be any type of machinery, as long as it creates a ship_engine datum for itself.
|
||||
|
||||
6.Tick map in and compile.
|
||||
6. Tick map in and compile.
|
||||
Sector should appear on overmap (in random place if you didn't set mapx,mapy)
|
||||
|
||||
|
||||
TODO:
|
||||
shuttle console:
|
||||
checking occupied pad or not with docking controllers
|
||||
?landing pad size detection
|
||||
non-zlevel overmap objects
|
||||
field generator
|
||||
meteor fields
|
||||
speed-based chance for a rock in the ship
|
||||
debris fields
|
||||
speed-based chance of
|
||||
debirs in the ship
|
||||
a drone
|
||||
EMP
|
||||
nebulaes
|
||||
*/
|
||||
TODO:
|
||||
shuttle console:
|
||||
checking occupied pad or not with docking controllers
|
||||
?landing pad size detection
|
||||
non-zlevel overmap objects
|
||||
field generator
|
||||
meteor fields
|
||||
speed-based chance for a rock in the ship
|
||||
debris fields
|
||||
speed-based chance of
|
||||
debirs in the ship
|
||||
a drone
|
||||
EMP
|
||||
nebulaes
|
||||
@@ -279,7 +279,7 @@ var/list/civilian_cartridges = list(
|
||||
|
||||
/obj/item/cartridge/proc/post_status(var/command, var/data1, var/data2)
|
||||
|
||||
var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435)
|
||||
var/datum/radio_frequency/frequency = SSradio.return_frequency(1435)
|
||||
if(!frequency) return
|
||||
|
||||
var/datum/signal/status_signal = new
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
return TRUE
|
||||
|
||||
/datum/data/pda/app/status_display/proc/post_status(var/command, var/data1, var/data2)
|
||||
var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435)
|
||||
var/datum/radio_frequency/frequency = SSradio.return_frequency(1435)
|
||||
if(!frequency)
|
||||
return
|
||||
|
||||
|
||||
+10
-10
@@ -28,15 +28,15 @@
|
||||
add_to_radio(bot_filter)
|
||||
|
||||
/obj/item/radio/integrated/Destroy()
|
||||
if(radio_controller)
|
||||
radio_controller.remove_object(src, control_freq)
|
||||
if(SSradio)
|
||||
SSradio.remove_object(src, control_freq)
|
||||
hostpda = null
|
||||
return ..()
|
||||
|
||||
/obj/item/radio/integrated/proc/post_signal(var/freq, var/key, var/value, var/key2, var/value2, var/key3, var/value3, s_filter)
|
||||
|
||||
//to_world("Post: [freq]: [key]=[value], [key2]=[value2]")
|
||||
var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq)
|
||||
var/datum/radio_frequency/frequency = SSradio.return_frequency(freq)
|
||||
|
||||
if(!frequency)
|
||||
return
|
||||
@@ -90,8 +90,8 @@
|
||||
botstatus = b.Copy()
|
||||
|
||||
/obj/item/radio/integrated/proc/add_to_radio(bot_filter) //Master filter control for bots. Must be placed in the bot's local Initialize(mapload) to support map spawned bots.
|
||||
if(radio_controller)
|
||||
radio_controller.add_object(src, control_freq, radio_filter = bot_filter)
|
||||
if(SSradio)
|
||||
SSradio.add_object(src, control_freq, radio_filter = bot_filter)
|
||||
|
||||
/*
|
||||
* Radio Cartridge, essentially a signaler.
|
||||
@@ -101,22 +101,22 @@
|
||||
var/code = 30.0
|
||||
|
||||
/obj/item/radio/integrated/signal/Destroy()
|
||||
if(radio_controller)
|
||||
radio_controller.remove_object(src, frequency)
|
||||
if(SSradio)
|
||||
SSradio.remove_object(src, frequency)
|
||||
radio_connection = null
|
||||
return ..()
|
||||
|
||||
/obj/item/radio/integrated/signal/Initialize(mapload)
|
||||
. = ..()
|
||||
if(radio_controller)
|
||||
if(SSradio)
|
||||
if(src.frequency < PUBLIC_LOW_FREQ || src.frequency > PUBLIC_HIGH_FREQ)
|
||||
src.frequency = sanitize_frequency(src.frequency)
|
||||
set_frequency(frequency)
|
||||
|
||||
/obj/item/radio/integrated/signal/set_frequency(new_frequency)
|
||||
radio_controller.remove_object(src, frequency)
|
||||
SSradio.remove_object(src, frequency)
|
||||
frequency = new_frequency
|
||||
radio_connection = radio_controller.add_object(src, frequency)
|
||||
radio_connection = SSradio.add_object(src, frequency)
|
||||
|
||||
/obj/item/radio/integrated/signal/proc/send_signal(message="ACTIVATE")
|
||||
if(last_transmission && world.time < (last_transmission + 5))
|
||||
|
||||
@@ -482,7 +482,7 @@ GLOBAL_LIST_EMPTY(apcs)
|
||||
span_warning("[user.name] has broken the charred power control board inside [name]!"),\
|
||||
span_notice("You broke the charred power control board and remove the remains."),
|
||||
"You hear a crack!")
|
||||
//ticker.mode:apcs-- //XSI said no and I agreed. -rastaf0
|
||||
//SSticker.mode:apcs-- //XSI said no and I agreed. -rastaf0
|
||||
else
|
||||
user.visible_message(\
|
||||
span_warning("[user.name] has removed the power control board from [name]!"),\
|
||||
|
||||
@@ -66,15 +66,15 @@
|
||||
|
||||
if(M.gender == MALE)
|
||||
H.gender = MALE
|
||||
H.name = pick(first_names_male)
|
||||
H.name = pick(GLOB.first_names_male)
|
||||
else if(M.gender == FEMALE)
|
||||
H.gender = FEMALE
|
||||
H.name = pick(first_names_female)
|
||||
H.name = pick(GLOB.first_names_female)
|
||||
else
|
||||
H.gender = NEUTER
|
||||
H.name = pick(first_names_female|first_names_male)
|
||||
H.name = pick(GLOB.first_names_female|GLOB.first_names_male)
|
||||
|
||||
H.name += " [pick(last_names)]"
|
||||
H.name += " [pick(GLOB.last_names)]"
|
||||
H.real_name = H.name
|
||||
|
||||
H.set_species(randomize)
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
while(reaction_occurred)
|
||||
for(var/decl/chemical_reaction/C as anything in effect_reactions)
|
||||
C.post_reaction(src)
|
||||
#ifdef UNIT_TEST
|
||||
#ifdef UNIT_TESTS
|
||||
SEND_SIGNAL(src, COMSIG_UNITTEST_DATA, list(C))
|
||||
#endif
|
||||
update_total()
|
||||
|
||||
@@ -103,8 +103,8 @@
|
||||
var/amt_produced = result_amount * reaction_progress
|
||||
if(result)
|
||||
holder.add_reagent(result, amt_produced, data, safety = 1, was_from_belly = belly_reagent)
|
||||
// #ifdef UNIT_TEST
|
||||
// log_unit_test("[name] - Reagent reaction result: [result] [amt_produced]") // Uncomment for UNIT_TEST debug assistance
|
||||
// #ifdef UNIT_TESTS
|
||||
// log_unit_test("[name] - Reagent reaction result: [result] [amt_produced]") // Uncomment for UNIT_TESTS debug assistance
|
||||
// #endif
|
||||
|
||||
on_reaction(holder, amt_produced)
|
||||
|
||||
@@ -825,8 +825,8 @@
|
||||
result_amount = 2
|
||||
log_is_important = 1
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
// If it becomes possible to make this without exploding and clearing reagents, remove the UNIT_TEST wrapper
|
||||
#ifndef UNIT_TESTS
|
||||
// If it becomes possible to make this without exploding and clearing reagents, remove the UNIT_TESTS wrapper
|
||||
/decl/chemical_reaction/instant/nitroglycerin/on_reaction(var/datum/reagents/holder, var/created_volume)
|
||||
var/datum/effect/effect/system/reagents_explosion/e = new()
|
||||
e.set_up(round (created_volume/2, 1), holder.my_atom, 0, 0)
|
||||
|
||||
@@ -70,8 +70,8 @@
|
||||
|
||||
//Add the core to the asteroid's map
|
||||
rm_controller.dbg("ZM(ga): Starting core generation for [A.coresize] size core..")
|
||||
for(var/x = 1; x <= A.coresize, x++)
|
||||
for(var/y = 1; y <= A.coresize, y++)
|
||||
for(var/x = 1, x <= A.coresize, x++)
|
||||
for(var/y = 1, y <= A.coresize, y++)
|
||||
rm_controller.dbg("ZM(ga): Doing core-relative [x],[y] at [A.coresize+x],[A.coresize+y], [A.type_wall].")
|
||||
A.spot_add(A.coresize+x, A.coresize+y, A.type_wall)
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@
|
||||
freq = text2num(freq)
|
||||
newsign.frequency = freq
|
||||
|
||||
var/datum/radio_frequency/connection = radio_controller.return_frequency(freq)
|
||||
var/datum/radio_frequency/connection = SSradio.return_frequency(freq)
|
||||
newsign.data["connection"] = connection
|
||||
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
|
||||
/obj/machinery/keycard_auth/proc/is_ert_blocked()
|
||||
if(CONFIG_GET(flag/ert_admin_call_only)) return 1
|
||||
return ticker.mode && ticker.mode.ert_disabled
|
||||
return SSticker.mode && SSticker.mode.ert_disabled
|
||||
|
||||
var/global/maint_all_access = 0
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
/datum/shuttle/autodock/ferry/emergency/New()
|
||||
..()
|
||||
radio_connection = radio_controller.add_object(src, frequency, null)
|
||||
radio_connection = SSradio.add_object(src, frequency, null)
|
||||
if(emergency_shuttle.shuttle)
|
||||
CRASH("An emergency shuttle has already been defined.")
|
||||
emergency_shuttle.shuttle = src
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
var/datum/signal/S = new()
|
||||
S.source = src
|
||||
S.data = list("command" = "supply")
|
||||
var/datum/radio_frequency/F = radio_controller.return_frequency(1435)
|
||||
var/datum/radio_frequency/F = SSradio.return_frequency(1435)
|
||||
F.post_signal(src, S)
|
||||
|
||||
//it would be cool to play a sound here
|
||||
|
||||
@@ -86,7 +86,7 @@ Targeted spells have two useful flags: INCLUDEUSER and SELECTABLE. These are exp
|
||||
possible_targets += target
|
||||
|
||||
if(spell_flags & SELECTABLE)
|
||||
for(var/i = 1; i<=max_targets, i++)
|
||||
for(var/i = 1, i<=max_targets, i++)
|
||||
if(!possible_targets.len)
|
||||
break
|
||||
var/mob/M = tgui_input_list(user, "Choose the target for the spell.", "Targeting", possible_targets)
|
||||
|
||||
@@ -94,11 +94,11 @@ GLOBAL_LIST_EMPTY(FrozenAccounts)
|
||||
/datum/article/proc/generateAuthorName()
|
||||
switch(rand(1,3))
|
||||
if (1)
|
||||
return "[consonant()]. [pick(last_names)]"
|
||||
return "[consonant()]. [pick(GLOB.last_names)]"
|
||||
if (2)
|
||||
return "[prob(50) ? pick(first_names_male) : pick(first_names_female)] [consonant()].[prob(50) ? "[consonant()]. " : null] [pick(last_names)]"
|
||||
return "[prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)] [consonant()].[prob(50) ? "[consonant()]. " : null] [pick(GLOB.last_names)]"
|
||||
if (3)
|
||||
return "[prob(50) ? pick(first_names_male) : pick(first_names_female)] \"[prob(50) ? pick(first_names_male) : pick(first_names_female)]\" [pick(last_names)]"
|
||||
return "[prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)] \"[prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)]\" [pick(GLOB.last_names)]"
|
||||
|
||||
/datum/article/proc/formatSpacetime()
|
||||
var/ticksc = round(ticks/100)
|
||||
|
||||
@@ -72,7 +72,7 @@ var/list/table_icon_cache = list()
|
||||
// reset color/alpha, since they're set for nice map previews
|
||||
color = "#ffffff"
|
||||
alpha = 255
|
||||
update_connections(ticker && ticker.current_state == GAME_STATE_PLAYING)
|
||||
update_connections(SSticker && SSticker.current_state == GAME_STATE_PLAYING)
|
||||
update_icon()
|
||||
update_desc()
|
||||
update_material()
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
confidential = FALSE
|
||||
)
|
||||
//if(isnull(Master) || !SSchat?.initialized || !MC_RUNNING(SSchat.init_stage))
|
||||
if(isnull(Master) || !SSchat?.subsystem_initialized)
|
||||
if(isnull(Master) || !SSchat?.initialized)
|
||||
to_chat_immediate(target, html, type, text, avoid_highlighting)
|
||||
return
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
return global_message_listener
|
||||
|
||||
/proc/post_status(atom/source, command, data1, data2, mob/user = null)
|
||||
var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435)
|
||||
var/datum/radio_frequency/frequency = SSradio.return_frequency(1435)
|
||||
|
||||
if(!frequency)
|
||||
return
|
||||
@@ -381,7 +381,7 @@
|
||||
PS.allowedtocall = !(PS.allowedtocall)
|
||||
|
||||
/proc/call_shuttle_proc(var/mob/user)
|
||||
if ((!( ticker ) || !emergency_shuttle.location()))
|
||||
if ((!( SSticker ) || !emergency_shuttle.location()))
|
||||
return
|
||||
|
||||
if(!GLOB.universe.OnShuttleCall(user))
|
||||
@@ -408,7 +408,7 @@
|
||||
to_chat(user, "The emergency shuttle is already on its way.")
|
||||
return
|
||||
|
||||
if(ticker.mode.name == "blob")
|
||||
if(SSticker.mode.name == "blob")
|
||||
to_chat(user, "Under directive 7-10, [station_name()] is quarantined until further notice.")
|
||||
return
|
||||
|
||||
@@ -420,7 +420,7 @@
|
||||
return
|
||||
|
||||
/proc/init_shift_change(var/mob/user, var/force = 0)
|
||||
if ((!( ticker ) || !emergency_shuttle.location()))
|
||||
if ((!( SSticker ) || !emergency_shuttle.location()))
|
||||
return
|
||||
|
||||
if(emergency_shuttle.going_to_centcom())
|
||||
@@ -445,11 +445,11 @@
|
||||
to_chat(user, "The shuttle is refueling. Please wait another [round((54000-world.time)/60)] minutes before trying again.")
|
||||
return
|
||||
|
||||
if(ticker.mode.auto_recall_shuttle)
|
||||
if(SSticker.mode.auto_recall_shuttle)
|
||||
//New version pretends to call the shuttle but cause the shuttle to return after a random duration.
|
||||
emergency_shuttle.auto_recall = 1
|
||||
|
||||
if(ticker.mode.name == "blob" || ticker.mode.name == "epidemic")
|
||||
if(SSticker.mode.name == "blob" || SSticker.mode.name == "epidemic")
|
||||
to_chat(user, "Under directive 7-10, [station_name()] is quarantined until further notice.")
|
||||
return
|
||||
|
||||
@@ -467,9 +467,9 @@
|
||||
return
|
||||
|
||||
/proc/cancel_call_proc(var/mob/user)
|
||||
if (!( ticker ) || !emergency_shuttle.can_recall())
|
||||
if (!( SSticker ) || !emergency_shuttle.can_recall())
|
||||
return
|
||||
if((ticker.mode.name == "blob")||(ticker.mode.name == "Meteor"))
|
||||
if((SSticker.mode.name == "blob")||(SSticker.mode.name == "Meteor"))
|
||||
return
|
||||
|
||||
if(!emergency_shuttle.going_to_centcom()) //check that shuttle isn't already heading to CentCom
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
if(!CONFIG_GET(flag/enter_allowed))
|
||||
to_chat(new_user, span_notice("There is an administrative lock on entering the game!"))
|
||||
return
|
||||
else if(ticker && ticker.mode && ticker.mode.explosion_in_progress)
|
||||
else if(SSticker && SSticker.mode && SSticker.mode.explosion_in_progress)
|
||||
to_chat(new_user, span_danger("The station is currently exploding. Joining would go poorly."))
|
||||
return
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Unit Tests
|
||||
|
||||
## What is unit testing?
|
||||
|
||||
Unit tests are automated code to verify that parts of the game work exactly as they should. For example, [a test to make sure that the amputation surgery actually amputates the limb](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/surgeries.dm#L1-L13). These are ran every time a PR is made, and thus are very helpful for preventing bugs from cropping up in your code that would've otherwise gone unnoticed. For example, would you have thought to check [that beach boys would still work the same after editing pizza](https://github.com/tgstation/tgstation/pull/53641#issuecomment-691384934)? If you value your time, probably not.
|
||||
|
||||
On their most basic level, when `UNIT_TESTS` is defined, all subtypes of `/datum/unit_test` will have their `Run` proc executed. From here, if `Fail` is called at any point, then the tests will report as failed.
|
||||
|
||||
## How do I write one?
|
||||
|
||||
1. Find a relevant file.
|
||||
|
||||
All unit test related code is in `code/modules/unit_tests`. If you are adding a new test for a surgery, for example, then you'd open `surgeries.dm`. If a relevant file does not exist, simply create one in this folder, then `#include` it in `_unit_tests.dm`.
|
||||
|
||||
2. Create the unit test.
|
||||
|
||||
To make a new unit test, you simply need to define a `/datum/unit_test`.
|
||||
|
||||
For example, let's suppose that we are creating a test to make sure a proc `square` correctly raises inputs to the power of two. We'd start with first:
|
||||
|
||||
```
|
||||
/datum/unit_test/square/Run()
|
||||
```
|
||||
|
||||
This defines our new unit test, `/datum/unit_test/square`. Inside this function, we're then going to run through whatever we want to check. Tests provide a few assertion functions to make this easy. For now, we're going to use `TEST_ASSERT_EQUAL`.
|
||||
|
||||
```
|
||||
/datum/unit_test/square/Run()
|
||||
TEST_ASSERT_EQUAL(square(3), 9, "square(3) did not return 9")
|
||||
TEST_ASSERT_EQUAL(square(4), 16, "square(4) did not return 16")
|
||||
```
|
||||
|
||||
As you can hopefully tell, we're simply checking if the output of `square` matches the output we are expecting. If the test fails, it'll report the error message given as well as whatever the actual output was.
|
||||
|
||||
3. Run the unit test
|
||||
|
||||
Open `code/_compile_options.dm` and uncomment the following line.
|
||||
|
||||
```
|
||||
//#define UNIT_TESTS //If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between
|
||||
```
|
||||
|
||||
There are 3 ways to run unit tests
|
||||
|
||||
- Run tgstation.dmb in Dream Daemon. Don't bother trying to connect, you won't need to. You'll be able to see the outputs of all the tests. You'll get to see which tests failed and for what reason. If they all pass, you're set!
|
||||
|
||||
- Launch game from VS Code. Launch the game as normal & you will see the output of your unit tests in your fancy chat window. This is preferred as you can use the debugger to step through each line of your unit test & can use the games inbuilt debugging tools to further aid in testing
|
||||
|
||||
- Use VS Code Tgstation Test Explorer Extension. This allows you to run tests without launching the game & can also run focused tests(either a single or a selected group)
|
||||
|
||||
## How to think about tests
|
||||
|
||||
Unit tests exist to prevent bugs that would happen in a real game. Thus, they should attempt to emulate the game world wherever possible. For example, the [quick swap sanity test](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/quick_swap_sanity.dm) emulates a _real_ scenario of the bug it fixed occurring by creating a character and giving it real items. The unrecommended alternative would be to create special test-only items. This isn't a hard rule, the [reagent method exposure tests](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/reagent_mod_expose.dm) create a test-only reagent for example, but do keep it in mind.
|
||||
|
||||
Unit tests should also be just that--testing _units_ of code. For example, instead of having one massive test for reagents, there are instead several smaller tests for testing exposure, metabolization, etc.
|
||||
|
||||
## The unit testing API
|
||||
|
||||
You can find more information about all of these from their respective doc comments, but for a brief overview:
|
||||
|
||||
`/datum/unit_test` - The base for all tests to be ran. Subtypes must override `Run()`. `New()` and `Destroy()` can be used for setup and teardown. To fail, use `TEST_FAIL(reason)`.
|
||||
|
||||
`/datum/unit_test/proc/allocate(type, ...)` - Allocates an instance of the provided type with the given arguments. Is automatically destroyed when the test is over. Commonly seen in the form of `var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human/consistent)`.
|
||||
|
||||
`TEST_FAIL(reason)` - Marks a failure at this location, but does not stop the test.
|
||||
|
||||
`TEST_ASSERT(assertion, reason)` - Stops the unit test and fails if the assertion is not met. For example: `TEST_ASSERT(powered(), "Machine is not powered")`.
|
||||
|
||||
`TEST_ASSERT_NOTNULL(a, message)` - Same as `TEST_ASSERT`, but checks if `!isnull(a)`. For example: `TEST_ASSERT_NOTNULL(myatom, "My atom was never set!")`.
|
||||
|
||||
`TEST_ASSERT_NULL(a, message)` - Same as `TEST_ASSERT`, but checks if `isnull(a)`. If not, gives a helpful message showing what `a` was. For example: `TEST_ASSERT_NULL(delme, "Delme was never cleaned up!")`.
|
||||
|
||||
`TEST_ASSERT_EQUAL(a, b, message)` - Same as `TEST_ASSERT`, but checks if `a == b`. If not, gives a helpful message showing what both `a` and `b` were. For example: `TEST_ASSERT_EQUAL(2 + 2, 4, "The universe is falling apart before our eyes!")`.
|
||||
|
||||
`TEST_ASSERT_NOTEQUAL(a, b, message)` - Same as `TEST_ASSERT_EQUAL`, but reversed.
|
||||
|
||||
`TEST_FOCUS(test_path)` - _Only_ run the test provided within the parameters. Useful for reducing noise. For example, if we only want to run our example square test, we can add `TEST_FOCUS(/datum/unit_test/square)`. Should _never_ be pushed in a pull request--you will be laughed at.
|
||||
|
||||
## Final Notes
|
||||
|
||||
- Writing tests before you attempt to fix the bug can actually speed up development a lot! It means you don't have to go in game and folllow the same exact steps manually every time. This process is known as "TDD" (test driven development). Write the test first, make sure it fails, _then_ start work on the fix/feature, and you'll know you're done when your tests pass. If you do try this, do make sure to confirm in a non-testing environment just to double check.
|
||||
- Make sure that your tests don't accidentally call RNG functions like `prob`. Since RNG is seeded during tests, you may not realize you have until someone else makes a PR and the tests fail!
|
||||
- Do your best not to change the behavior of non-testing code during tests. While it may sometimes be necessary in the case of situations such as the above, it is still a slippery slope that can lead to the code you're testing being too different from the production environment to be useful.
|
||||
@@ -0,0 +1,129 @@
|
||||
//include unit test files in this module in this ifdef
|
||||
//Keep this sorted alphabetically
|
||||
|
||||
#if defined(UNIT_TESTS) || defined(SPACEMAN_DMM)
|
||||
|
||||
/// For advanced cases, fail unconditionally but don't return (so a test can return multiple results)
|
||||
#define TEST_FAIL(reason) (Fail(reason || "No reason", __FILE__, __LINE__))
|
||||
|
||||
/// Asserts that a condition is true
|
||||
/// If the condition is not true, fails the test
|
||||
#define TEST_ASSERT(assertion, reason) if (!(assertion)) { return Fail("Assertion failed: [reason || "No reason"]", __FILE__, __LINE__) }
|
||||
|
||||
/// Asserts that a parameter is not null
|
||||
#define TEST_ASSERT_NOTNULL(a, reason) if (isnull(a)) { return Fail("Expected non-null value: [reason || "No reason"]", __FILE__, __LINE__) }
|
||||
|
||||
/// Asserts that a parameter is null
|
||||
#define TEST_ASSERT_NULL(a, reason) if (!isnull(a)) { return Fail("Expected null value but received [a]: [reason || "No reason"]", __FILE__, __LINE__) }
|
||||
|
||||
/// Asserts that the two parameters passed are equal, fails otherwise
|
||||
/// Optionally allows an additional message in the case of a failure
|
||||
#define TEST_ASSERT_EQUAL(a, b, message) do { \
|
||||
var/lhs = ##a; \
|
||||
var/rhs = ##b; \
|
||||
if (lhs != rhs) { \
|
||||
return Fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \
|
||||
} \
|
||||
} while (FALSE)
|
||||
|
||||
/// Asserts that the two parameters passed are not equal, fails otherwise
|
||||
/// Optionally allows an additional message in the case of a failure
|
||||
#define TEST_ASSERT_NOTEQUAL(a, b, message) do { \
|
||||
var/lhs = ##a; \
|
||||
var/rhs = ##b; \
|
||||
if (lhs == rhs) { \
|
||||
return Fail("Expected [isnull(lhs) ? "null" : lhs] to not be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \
|
||||
} \
|
||||
} while (FALSE)
|
||||
|
||||
/// *Only* run the test provided within the parentheses
|
||||
/// This is useful for debugging when you want to reduce noise, but should never be pushed
|
||||
/// Intended to be used in the manner of `TEST_FOCUS(/datum/unit_test/math)`
|
||||
#define TEST_FOCUS(test_path) ##test_path { focus = TRUE; }
|
||||
|
||||
/// Logs a noticable message on GitHub, but will not mark as an error.
|
||||
/// Use this when something shouldn't happen and is of note, but shouldn't block CI.
|
||||
/// Does not mark the test as failed.
|
||||
#define TEST_NOTICE(source, message) source.log_for_test((##message), "notice", __FILE__, __LINE__)
|
||||
|
||||
/// Constants indicating unit test completion status
|
||||
#define UNIT_TEST_PASSED 0
|
||||
#define UNIT_TEST_FAILED 1
|
||||
#define UNIT_TEST_SKIPPED 2
|
||||
|
||||
#define TEST_PRE 0
|
||||
#define TEST_DEFAULT 1
|
||||
/// After most test steps, used for tests that run long so shorter issues can be noticed faster
|
||||
#define TEST_LONGER 10
|
||||
/// This must be the one of last tests to run due to the inherent nature of the test iterating every single tangible atom in the game and qdeleting all of them (while taking long sleeps to make sure the garbage collector fires properly) taking a large amount of time.
|
||||
#define TEST_CREATE_AND_DESTROY 9001
|
||||
/**
|
||||
* For tests that rely on create and destroy having iterated through every (tangible) atom so they don't have to do something similar.
|
||||
* Keep in mind tho that create and destroy will absolutely break the test platform, anything that relies on its shape cannot come after it.
|
||||
*/
|
||||
#define TEST_AFTER_CREATE_AND_DESTROY INFINITY
|
||||
|
||||
/// Change color to red on ANSI terminal output, if enabled with -DANSICOLORS.
|
||||
#ifdef ANSICOLORS
|
||||
#define TEST_OUTPUT_RED(text) "\x1B\x5B1;31m[text]\x1B\x5B0m"
|
||||
#else
|
||||
#define TEST_OUTPUT_RED(text) (text)
|
||||
#endif
|
||||
/// Change color to green on ANSI terminal output, if enabled with -DANSICOLORS.
|
||||
#ifdef ANSICOLORS
|
||||
#define TEST_OUTPUT_GREEN(text) "\x1B\x5B1;32m[text]\x1B\x5B0m"
|
||||
#else
|
||||
#define TEST_OUTPUT_GREEN(text) (text)
|
||||
#endif
|
||||
/// Change color to yellow on ANSI terminal output, if enabled with -DANSICOLORS.
|
||||
#ifdef ANSICOLORS
|
||||
#define TEST_OUTPUT_YELLOW(text) "\x1B\x5B1;33m[text]\x1B\x5B0m"
|
||||
#else
|
||||
#define TEST_OUTPUT_YELLOW(text) (text)
|
||||
#endif
|
||||
/// A trait source when adding traits through unit tests
|
||||
#define TRAIT_SOURCE_UNIT_TESTS "unit_tests"
|
||||
/// Helper to allocate a new object with the implied type (the type of the variable it's assigned to) in the corner of the test room
|
||||
#define EASY_ALLOCATE(arguments...) allocate(__IMPLIED_TYPE__, run_loc_floor_bottom_left, ##arguments)
|
||||
|
||||
// BEGIN_INCLUDE
|
||||
#include "asset_smart_cache.dm"
|
||||
//#include "clothing_tests.dm" // FIXME
|
||||
#include "component_tests.dm"
|
||||
#include "cosmetic_tests.dm"
|
||||
#include "dcs_check_list_arguments.dm"
|
||||
#include "dcs_get_id_from_elements.dm"
|
||||
#include "decl_tests.dm"
|
||||
#include "disease_tests.dm"
|
||||
#include "focus_only_tests.dm"
|
||||
#include "font_awesome_icons.dm"
|
||||
#include "genetics_tests.dm"
|
||||
#include "language_tests.dm"
|
||||
#include "loadout_tests.dm"
|
||||
#include "map_tests.dm"
|
||||
#include "material_tests.dm"
|
||||
// #include "nuke_cinematic.dm" // TODO: This is probably fixed later on
|
||||
#include "poster_tests.dm"
|
||||
// #include "preferences.dm" // This unit test is missing some other stuff
|
||||
#include "reagent_tests.dm"
|
||||
#include "recipe_tests.dm"
|
||||
#include "recycler_vendor_tests.dm"
|
||||
#include "robot_tests.dm"
|
||||
#include "spritesheets.dm"
|
||||
#include "sqlite_tests.dm"
|
||||
#include "subsystem_init.dm"
|
||||
#include "tgui_create_message.dm"
|
||||
#include "timer_sanity.dm"
|
||||
#include "trait_tests.dm"
|
||||
#include "unit_test.dm"
|
||||
// #include "vore_tests.dm" // FIXME: REWRITE OR FIX THIS
|
||||
// END_INCLUDE
|
||||
#ifdef REFERENCE_TRACKING_DEBUG //Don't try and parse this file if ref tracking isn't turned on. IE: don't parse ref tracking please mr linter
|
||||
#include "find_reference_sanity.dm"
|
||||
#endif
|
||||
|
||||
#undef TEST_ASSERT
|
||||
#undef TEST_ASSERT_EQUAL
|
||||
#undef TEST_ASSERT_NOTEQUAL
|
||||
//#undef TEST_FOCUS - This define is used by vscode unit test extension to pick specific unit tests to run and appended later so needs to be used out of scope here
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
/datum/asset/spritesheet_batched/test
|
||||
name = "test"
|
||||
load_immediately = TRUE
|
||||
force_cache = TRUE
|
||||
// Don't let the asset subsystem load this. This is how we trick it.
|
||||
_abstract = /datum/asset/spritesheet_batched/test
|
||||
var/static/list/items = list(/obj/item/binoculars, /obj/item/camera, /obj/item/clothing/under/color/blue, /obj/item/clothing/under/color/black)
|
||||
|
||||
/datum/asset/spritesheet_batched/test/create_spritesheets()
|
||||
for(var/atom/item as anything in items)
|
||||
if (!ispath(item, /atom))
|
||||
return FALSE
|
||||
var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
|
||||
insert_icon(imgid, get_display_icon_for(item))
|
||||
// Get some coverage on each operation.
|
||||
var/datum/universal_icon/I = uni_icon('icons/effects/effects.dmi', "nothing")
|
||||
I.blend_icon(uni_icon('icons/effects/effects.dmi', "sparks"), ICON_OVERLAY)
|
||||
I.blend_color("#ff0000", ICON_MULTIPLY)
|
||||
I.scale(64, 64)
|
||||
I.crop(1, 1, 128, 64) // we'll test for the scale later.
|
||||
insert_icon("test", I)
|
||||
|
||||
/datum/asset/spritesheet_batched/test/unregister()
|
||||
SSassets.transport.unregister_asset("spritesheet_[name].css")
|
||||
if(length(sizes))
|
||||
for(var/size_id in sizes)
|
||||
SSassets.transport.unregister_asset("[name]_[size_id].png")
|
||||
|
||||
/datum/unit_test/test_asset_smart_cache/Run()
|
||||
fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json")
|
||||
fdel("data/spritesheets/spritesheet_test.css")
|
||||
var/datum/asset/spritesheet_batched/test/sheet = new()
|
||||
TEST_ASSERT(sheet.fully_generated, "Spritesheet not generated!")
|
||||
// Cache should be invalid initially.
|
||||
TEST_ASSERT(sheet.cache_result, "Spritesheet smart cache was VALID when it should be INVALID!")
|
||||
for(var/item in sheet.items)
|
||||
var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
|
||||
// All items should be in sprites list.
|
||||
TEST_ASSERT(imgid in sheet.sprites, "Item [item] not present in spritesheet result!")
|
||||
TEST_ASSERT("test" in sheet.sprites, "Item test not present in spritesheet result!")
|
||||
TEST_ASSERT("128x64" in sheet.sizes, "Test icon was not output as 128x64!")
|
||||
// cache wrote properly
|
||||
TEST_ASSERT(fexists("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json"), "Smart cache entry did not write!")
|
||||
// Clear it out and get ready to do it again, this time loading from cache
|
||||
sheet.unregister()
|
||||
sheet.entries = list()
|
||||
sheet.sprites = list()
|
||||
sheet.sizes = list()
|
||||
sheet.job_id = null
|
||||
sheet.cache_result = null
|
||||
sheet.cache_data = null
|
||||
sheet.cache_job_id = null
|
||||
sheet.fully_generated = FALSE
|
||||
|
||||
sheet.register()
|
||||
TEST_ASSERT(sheet.fully_generated, "Spritesheet did not load from smart cache properly!")
|
||||
// Check for CACHE_VALID
|
||||
TEST_ASSERT(!sheet.cache_result, "Spritesheet did not load from smart cache, it was invalid despite having the same input data!")
|
||||
// Cleanup files.
|
||||
fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json")
|
||||
fdel("data/spritesheets/spritesheet_test.css")
|
||||
for(var/size in sheet.sizes)
|
||||
fdel("data/spritesheets/test_[size].png")
|
||||
@@ -0,0 +1,171 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
/// MIGHT REQUIRE BIGGER REWORK
|
||||
|
||||
/// Test that checks if all clothing is valid
|
||||
/datum/unit_test/all_clothing_shall_be_valid
|
||||
var/signal_failed = FALSE
|
||||
|
||||
|
||||
/datum/unit_test/all_clothing_shall_be_valid/Run()
|
||||
var/failed = 0
|
||||
var/obj/storage = new()
|
||||
|
||||
var/list/scan = subtypesof(/obj/item/clothing)
|
||||
scan -= typesof(/obj/item/clothing/head/hood) // These are part of clothing, need to be tested uniquely
|
||||
// Remove material armors, as dev_warning cannot be used to set their name
|
||||
scan -= /obj/item/clothing/suit/armor/material
|
||||
scan -= /obj/item/clothing/head/helmet/material
|
||||
scan -= /obj/item/clothing/ears/offear // This is used for equip logic, not ingame
|
||||
scan -= /obj/item/clothing/mask/ai // Breaks unit test entirely TODO
|
||||
|
||||
var/i = 0
|
||||
var/tenths = 1
|
||||
var/a_tenth = scan.len / 10
|
||||
for(var/path as anything in scan)
|
||||
var/obj/item/clothing/C = new path(storage)
|
||||
failed += test_clothing(C)
|
||||
|
||||
if(i > tenths * a_tenth)
|
||||
//TEST_NOTICE("Clothing - Progress [tenths * 10]% - [i]/[scan.len]")
|
||||
//TEST_NOTICE("---------------------------------------------------")
|
||||
tenths++
|
||||
|
||||
if(istype(C,/obj/item/clothing/suit/storage/hooded))
|
||||
var/obj/item/clothing/suit/storage/hooded/H = C
|
||||
if(H.hood) // Testing hoods when they init
|
||||
failed += test_clothing(H.hood,storage)
|
||||
|
||||
i++
|
||||
qdel(C)
|
||||
qdel(storage)
|
||||
|
||||
if(failed)
|
||||
TEST_FAIL("One or more /obj/item/clothing items had invalid flags or icons")
|
||||
|
||||
/datum/unit_test/all_clothing_shall_be_valid/proc/test_clothing(var/obj/item/clothing/C,var/obj/storage)
|
||||
var/failed = FALSE
|
||||
|
||||
// Do not test base-types
|
||||
if(C.name == DEVELOPER_WARNING_NAME)
|
||||
return FALSE
|
||||
|
||||
// ID
|
||||
TEST_ASSERT(C.name, "[C.type]: Clothing - Missing name.")
|
||||
TEST_ASSERT(C.name != "", "[C.type]: Clothing - Empty name.")
|
||||
|
||||
// Icons
|
||||
if(!("[C.icon_state]" in cached_icon_states(C.icon)))
|
||||
if(C.icon == initial(C.icon) && C.icon_state == initial(C.icon_state))
|
||||
TEST_NOTICE("[C.type]: Clothing - Icon_state \"[C.icon_state]\" is not present in [C.icon].")
|
||||
else
|
||||
TEST_NOTICE("[C.type]: Clothing - Icon_state \"[C.icon_state]\" is not present in [C.icon]. This icon/state was changed by init. Initial icon \"[initial(C.icon)]\". initial icon_state \"[initial(C.icon_state)]\". Check code.")
|
||||
failed = TRUE
|
||||
|
||||
// Disabled, as currently not working in a presentable way, spams the CI hard, do not enable unless fixed
|
||||
#ifdef UNIT_TEST
|
||||
// Time for the most brutal part. Dressing up some mobs with set species, and checking they have art
|
||||
// An entire signal just for unittests had to be made for this!
|
||||
var/list/body_types = list(SPECIES_HUMAN,SPECIES_VOX,SPECIES_TESHARI) // Otherwise we would be here for centuries
|
||||
// **************************************************************************************************************************
|
||||
body_types = list() // DISABLED FOR NOW, No single person can resolve how many sprites are missing.
|
||||
// **************************************************************************************************************************
|
||||
if(body_types.len)
|
||||
if(C.species_restricted && C.species_restricted.len)
|
||||
if(C.species_restricted[1] == "exclude")
|
||||
for(var/B in body_types)
|
||||
if(B in C.species_restricted)
|
||||
body_types -= B
|
||||
else
|
||||
var/list/new_list = list()
|
||||
for(var/B in body_types)
|
||||
if(B in C.species_restricted)
|
||||
new_list += B
|
||||
body_types = new_list
|
||||
// Get actual species that can use this, based on the mess of restricted/excluded logic above
|
||||
var/obj/mob_storage = new()
|
||||
var/mob/living/carbon/human/H = new(mob_storage)
|
||||
RegisterSignal(H, COMSIG_UNITTEST_DATA, PROC_REF(get_signal_data))
|
||||
for(var/B in body_types)
|
||||
H.set_species(B)
|
||||
// spawn the mob, signalize it, and then give it the item to see what it gets.
|
||||
H.put_in_active_hand(C)
|
||||
H.equip_to_appropriate_slot(C)
|
||||
H.drop_from_inventory(C, storage)
|
||||
UnregisterSignal(H, COMSIG_UNITTEST_DATA)
|
||||
qdel(H)
|
||||
qdel(mob_storage)
|
||||
// We failed the mob check
|
||||
if(signal_failed)
|
||||
failed = TRUE
|
||||
#endif
|
||||
|
||||
// Temps
|
||||
TEST_ASSERT(C.min_cold_protection_temperature > 0, "[C.type]: Clothing - Cold protection was lower than 0.")
|
||||
|
||||
if(C.max_heat_protection_temperature && C.min_cold_protection_temperature && C.max_heat_protection_temperature < C.min_cold_protection_temperature)
|
||||
TEST_NOTICE("[C.type]: Clothing - Maximum heat protection was greater than minimum cold protection.")
|
||||
failed = TRUE
|
||||
|
||||
//var/valid_range = HEAD|UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
|
||||
if(C.cold_protection)
|
||||
if(islist(C.cold_protection))
|
||||
TEST_NOTICE("[C.type]: Clothing - cold_protection was defined as a list, when it is a bitflag.")
|
||||
failed = TRUE
|
||||
else if(!isnum(C.cold_protection))
|
||||
TEST_NOTICE("[C.type]: Clothing - cold_protection was defined as something other than a number, when it is a bitflag.")
|
||||
failed = TRUE
|
||||
else
|
||||
if(C.cold_protection && C.cold_protection != FULL_BODY)
|
||||
// Check flags that should be unused
|
||||
if(C.cold_protection & FACE)
|
||||
TEST_NOTICE("[C.type]: Clothing - cold_protection uses FACE bitflag, this provides no protection, use HEAD.")
|
||||
failed = TRUE
|
||||
if(C.cold_protection & EYES)
|
||||
TEST_NOTICE("[C.type]: Clothing - cold_protection uses EYES bitflag, this provides no protection, use HEAD.")
|
||||
failed = TRUE
|
||||
|
||||
if(C.heat_protection)
|
||||
if(islist(C.heat_protection))
|
||||
TEST_NOTICE("[C.type]: Clothing - heat_protection was defined as a list, when it is a bitflag.")
|
||||
failed = TRUE
|
||||
else if(!isnum(C.heat_protection))
|
||||
TEST_NOTICE("[C.type]: Clothing - heat_protection was defined as something other than a number, when it is a bitflag.")
|
||||
failed = TRUE
|
||||
else
|
||||
if(C.heat_protection && C.heat_protection != FULL_BODY)
|
||||
// Check flags that should be unused
|
||||
if(C.heat_protection & FACE)
|
||||
TEST_NOTICE("[C.type]: Clothing - heat_protection uses FACE bitflag, this provides no protection, use HEAD.")
|
||||
failed = TRUE
|
||||
if(C.heat_protection & EYES)
|
||||
TEST_NOTICE("[C.type]: Clothing - heat_protection uses EYES bitflag, this provides no protection, use HEAD.")
|
||||
failed = TRUE
|
||||
return failed
|
||||
|
||||
/datum/unit_test/all_clothing_shall_be_valid/get_signal_data(atom/source, list/data = list())
|
||||
switch(data[1])
|
||||
if("set_slot")
|
||||
var/slot_name = data[2]
|
||||
var/set_icon = data[3]
|
||||
var/set_state = data[4]
|
||||
//var/in_hands = data[5]
|
||||
var/item_path = data[6]
|
||||
var/species = data[7]
|
||||
if(!species)
|
||||
return
|
||||
if(!set_icon)
|
||||
return
|
||||
if(!set_state)
|
||||
return
|
||||
|
||||
// Ignore storage
|
||||
if(slot_name == slot_l_hand_str)
|
||||
return
|
||||
if(slot_name == slot_r_hand_str)
|
||||
return
|
||||
|
||||
// All that matters
|
||||
if(!("[set_state]" in cached_icon_states(set_icon)))
|
||||
TEST_NOTICE("[item_path]: Clothing - Testing \"[species]\" state \"[set_state]\" for slot \"[slot_name]\", but it was not in dmi \"[set_icon]\"")
|
||||
signal_failed = TRUE
|
||||
return
|
||||
@@ -0,0 +1,7 @@
|
||||
/datum/unit_test/component_duping/Run()
|
||||
var/list/bad_dms = list()
|
||||
for(var/t in typesof(/datum/component))
|
||||
var/datum/component/comp = t
|
||||
if(!isnum(initial(comp.dupe_mode)))
|
||||
bad_dms += t
|
||||
TEST_ASSERT(!length(bad_dms), "Components with invalid dupe modes: ([bad_dms.Join(",")])")
|
||||
@@ -0,0 +1,60 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
/// MIGHT REQUIRE BIGGER REWORK
|
||||
|
||||
/// Test that tests that all cosmetics have unique name entries
|
||||
/datum/unit_test/sprite_accessories_shall_be_unique
|
||||
|
||||
/datum/unit_test/sprite_accessories_shall_be_unique/Run()
|
||||
validate_accessory_list(/datum/sprite_accessory/ears)
|
||||
validate_accessory_list(/datum/sprite_accessory/facial_hair)
|
||||
validate_accessory_list(/datum/sprite_accessory/hair)
|
||||
validate_accessory_list(/datum/sprite_accessory/hair_accessory)
|
||||
validate_accessory_list(/datum/sprite_accessory/marking)
|
||||
validate_accessory_list(/datum/sprite_accessory/tail)
|
||||
validate_accessory_list(/datum/sprite_accessory/wing)
|
||||
|
||||
/datum/unit_test/sprite_accessories_shall_be_unique/proc/validate_accessory_list(var/path)
|
||||
var/list/collection = list()
|
||||
for(var/SP in subtypesof(path))
|
||||
var/datum/sprite_accessory/A = new SP()
|
||||
TEST_ASSERT(A, "[SP]: Cosmetic - Path resolved to null in list.")
|
||||
if(!A)
|
||||
continue
|
||||
|
||||
TEST_ASSERT(A.name, "[A] - [A.type]: Cosmetic - Missing name.")
|
||||
|
||||
if(A.name == DEVELOPER_WARNING_NAME)
|
||||
continue
|
||||
|
||||
TEST_ASSERT(!collection[A.name], "[A] - [A.type]: Cosmetic - Name defined twice. Original def [collection[A.name]]")
|
||||
if(!collection[A.name])
|
||||
collection[A.name] = A.type
|
||||
|
||||
if(istype(A,text2path("[path]/invisible")))
|
||||
TEST_ASSERT(!A.icon_state, "[A] - [A.type]: Cosmetic - Invisible subtype has icon_state.")
|
||||
else if(!A.icon_state)
|
||||
TEST_ASSERT(A.icon_state, "[A] - [A.type]: Cosmetic - Has no icon_state.")
|
||||
else
|
||||
// Check if valid icon
|
||||
validate_icons(A)
|
||||
|
||||
qdel(A)
|
||||
|
||||
/datum/unit_test/sprite_accessories_shall_be_unique/proc/validate_icons(datum/sprite_accessory/A)
|
||||
var/actual_icon_state = A.icon_state
|
||||
|
||||
if(istype(A,/datum/sprite_accessory/hair))
|
||||
actual_icon_state = "[A.icon_state]_s"
|
||||
TEST_ASSERT(actual_icon_state in cached_icon_states(A.icon), "[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].")
|
||||
|
||||
if(istype(A,/datum/sprite_accessory/facial_hair))
|
||||
actual_icon_state = "[A.icon_state]_s"
|
||||
TEST_ASSERT(actual_icon_state in cached_icon_states(A.icon), "[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].")
|
||||
|
||||
if(istype(A,/datum/sprite_accessory/marking))
|
||||
var/datum/sprite_accessory/marking/MA = A
|
||||
for(var/BP in MA.body_parts)
|
||||
TEST_ASSERT(BP in BP_ALL, "[A] - [A.type]: Cosmetic - Has an illegal bodypart \"[BP]\". ONLY use parts listed in BP_ALL.")
|
||||
|
||||
actual_icon_state = "[A.icon_state]-[BP]"
|
||||
TEST_ASSERT(actual_icon_state in cached_icon_states(A.icon), "[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].")
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* list arguments for bespoke elements are treated as a text ref in the ID, like any other datum.
|
||||
* Which means that, unless cached, using lists as arguments will lead to multiple instance of the same element
|
||||
* being created over and over.
|
||||
*
|
||||
* Because of how it works, this unit test checks that these list datum args
|
||||
* do not share similar contents (when rearranged in descending alpha-numerical order), to ensure that
|
||||
* the least necessary amount of elements is created. So, using static lists may not be enough,
|
||||
* for example, in the case of two different critters using the death_drops element to drop ectoplasm on death, since,
|
||||
* despite being static lists, the two are different instances assigned to different mob types.
|
||||
*
|
||||
* Most of the time, you won't encounter two different static lists with similar contents used as element args,
|
||||
* meaning using static lists is accepted. However, should that happen, it's advised to replace the instances
|
||||
* with either string_list(), string_assoc_list(), string_assoc_nested_list() or string_numbers_list(), depending on the contents of the list.
|
||||
*
|
||||
* In the case of an element where the position of the contents of each datum list argument is important,
|
||||
* ELEMENT_DONT_SORT_LIST_ARGS should be added to its flags, to prevent such issues where the contents are similar
|
||||
* when sorted, but the element instances are not.
|
||||
*
|
||||
* In the off-chance the element is not compatible with this unit test (such as for connect_loc et simila),
|
||||
* you can also use ELEMENT_NO_LIST_UNIT_TEST so that they won't be processed by this unit test at all.
|
||||
*/
|
||||
/datum/unit_test/dcs_check_list_arguments
|
||||
/**
|
||||
* This unit test requires every (unless ignored) atom to have been created at least once
|
||||
* for a more accurate search, which is why it's run after create_and_destroy is done running.
|
||||
*/
|
||||
priority = TEST_AFTER_CREATE_AND_DESTROY
|
||||
|
||||
/datum/unit_test/dcs_check_list_arguments/Run()
|
||||
var/we_failed = FALSE
|
||||
for(var/element_type in SSdcs.arguments_that_are_lists_by_element)
|
||||
// Keeps track of the lists that shouldn't be compared with again.
|
||||
var/list/to_ignore = list()
|
||||
var/list/superlist = SSdcs.arguments_that_are_lists_by_element[element_type]
|
||||
for(var/list/current as anything in superlist)
|
||||
to_ignore[current] = TRUE
|
||||
var/list/bad_lists
|
||||
for(var/list/compare as anything in superlist)
|
||||
if(to_ignore[compare])
|
||||
continue
|
||||
if(deep_compare_list(current, compare))
|
||||
if(!bad_lists)
|
||||
bad_lists = list(list(current))
|
||||
bad_lists += list(compare)
|
||||
to_ignore[compare] = TRUE
|
||||
if(bad_lists)
|
||||
we_failed = TRUE
|
||||
//Include the original, unsorted list in the report. It should be easier to find by the contributor.
|
||||
var/list/unsorted_list = superlist[current]
|
||||
TEST_FAIL("Found [length(bad_lists)] datum list arguments with similar contents for [element_type]. Contents: [json_encode(unsorted_list)].")
|
||||
///Let's avoid sending the same instructions over and over, as it's just going to clutter the CI and confuse someone.
|
||||
if(we_failed)
|
||||
TEST_FAIL("Ensure that each list is static or cached. string_list() (as well as similar procs) is your friend here.\n\
|
||||
Check the documentation from dcs_check_list_arguments.dm for more information!")
|
||||
@@ -0,0 +1,46 @@
|
||||
/// Tests that DCS' GetIdFromArguments works as expected with standard and odd cases
|
||||
/datum/unit_test/dcs_get_id_from_arguments
|
||||
|
||||
/datum/unit_test/dcs_get_id_from_arguments/Run()
|
||||
assert_equal(list(1), list(1))
|
||||
assert_equal(list(1, 2), list(1, 2))
|
||||
assert_equal(list(src), list(src))
|
||||
|
||||
assert_equal(
|
||||
list(a = "x", b = "y", c = "z"),
|
||||
list(b = "y", a = "x", c = "z"),
|
||||
list(c = "z", a = "x", b = "y"),
|
||||
)
|
||||
|
||||
TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(1, 2)), get_id_from_arguments(list(2, 1)), "Swapped arguments should not return the same id")
|
||||
TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(1, a = "x")), get_id_from_arguments(list(1)), "Named arguments were ignored when creating ids")
|
||||
TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(1, a = "x")), get_id_from_arguments(list(a = "x")), "Unnamed arguments were ignored when creating ids")
|
||||
TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(src)), get_id_from_arguments(list(world)), "References to different datums should not return the same id")
|
||||
|
||||
TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list()), SSdcs.GetIdFromArguments(list(/datum/element/dcs_get_id_from_arguments_mock_element2)), "Different elements should not match the same id")
|
||||
|
||||
/datum/unit_test/dcs_get_id_from_arguments/proc/assert_equal(reference, ...)
|
||||
var/result = get_id_from_arguments(reference)
|
||||
|
||||
// Start at 1 so the 2nd argument is 2
|
||||
var/index = 1
|
||||
|
||||
for (var/other_case in args)
|
||||
index += 1
|
||||
|
||||
var/other_result = get_id_from_arguments(other_case)
|
||||
|
||||
if (other_result == result)
|
||||
continue
|
||||
|
||||
TEST_FAIL("Case #[index] produces a different GetIdFromArguments result from the first. [other_result] != [result]")
|
||||
|
||||
/datum/unit_test/dcs_get_id_from_arguments/proc/get_id_from_arguments(list/arguments)
|
||||
return SSdcs.GetIdFromArguments(list(/datum/element/dcs_get_id_from_arguments_mock_element) + arguments)
|
||||
|
||||
// Necessary because GetIdFromArguments uses argument_hash_start_idx from an element type
|
||||
/datum/element/dcs_get_id_from_arguments_mock_element
|
||||
argument_hash_start_idx = 2
|
||||
|
||||
/datum/element/dcs_get_id_from_arguments_mock_element2
|
||||
argument_hash_start_idx = 2
|
||||
@@ -0,0 +1,19 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
|
||||
/datum/unit_test/emotes_shall_have_unique_keys/Run()
|
||||
var/list/keys = list()
|
||||
var/list/duplicates = list()
|
||||
|
||||
var/list/all_emotes = decls_repository.get_decls_of_subtype(/decl/emote)
|
||||
for(var/etype in all_emotes)
|
||||
var/decl/emote/emote = all_emotes[etype]
|
||||
if(!emote.key)
|
||||
continue
|
||||
if(emote.key in keys)
|
||||
if(!duplicates[emote.key])
|
||||
duplicates[emote.key] = list()
|
||||
duplicates[emote.key] += etype
|
||||
else
|
||||
keys += emote.key
|
||||
|
||||
TEST_ASSERT(!length(duplicates), "[length(duplicates)] emote\s had overlapping keys: [english_list(duplicates)].")
|
||||
@@ -0,0 +1,17 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
|
||||
/datum/unit_test/disease_tests/Run()
|
||||
var/list/used_ids = list()
|
||||
|
||||
for(var/datum/disease/D as anything in subtypesof(/datum/disease))
|
||||
if(initial(D.name) == DEVELOPER_WARNING_NAME)
|
||||
continue
|
||||
|
||||
TEST_ASSERT(!(initial(D.medical_name) in used_ids), "[D]: Disease - Had a reused medical name, this is used as an ID and must be unique.")
|
||||
used_ids.Add(initial(D.medical_name))
|
||||
|
||||
TEST_ASSERT_NOTNULL(initial(D.name), "[D]: Disease - Lacks a name.")
|
||||
TEST_ASSERT_NOTEQUAL(initial(D.name), "", "[D]: Disease - Lacks a name.")
|
||||
|
||||
TEST_ASSERT_NOTNULL(initial(D.desc), "[D]: Disease - Lacks a description.")
|
||||
TEST_ASSERT_NOTEQUAL(initial(D.desc), "", "[D]: Disease - Lacks a description.")
|
||||
@@ -0,0 +1,152 @@
|
||||
///Used to test the completeness of the reference finder proc.
|
||||
/datum/unit_test/find_reference_sanity
|
||||
|
||||
/atom/movable/ref_holder
|
||||
var/static/atom/movable/ref_test/static_test
|
||||
var/atom/movable/ref_test/test
|
||||
var/list/test_list = list()
|
||||
var/list/test_assoc_list = list()
|
||||
|
||||
/atom/movable/ref_holder/Destroy()
|
||||
test = null
|
||||
static_test = null
|
||||
test_list.Cut()
|
||||
test_assoc_list.Cut()
|
||||
return ..()
|
||||
|
||||
/atom/movable/ref_test
|
||||
// Gotta make sure we do a full check
|
||||
references_to_clear = INFINITY
|
||||
var/atom/movable/ref_test/self_ref
|
||||
|
||||
/atom/movable/ref_test/Destroy(force)
|
||||
self_ref = null
|
||||
return ..()
|
||||
|
||||
/datum/unit_test/find_reference_sanity/Run()
|
||||
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
|
||||
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
|
||||
SSgarbage.should_save_refs = TRUE
|
||||
|
||||
//Sanity check
|
||||
var/refcount = refcount(victim)
|
||||
TEST_ASSERT_EQUAL(refcount, 3, "Should be: test references: 0 + baseline references: 3 (victim var,loc,allocated list)")
|
||||
victim.DoSearchVar(testbed, "Sanity Check") //We increment search time to get around an optimization
|
||||
|
||||
TEST_ASSERT(!LAZYLEN(victim.found_refs), "The ref-tracking tool found a ref where none existed")
|
||||
SSgarbage.should_save_refs = FALSE
|
||||
|
||||
/datum/unit_test/find_reference_baseline/Run()
|
||||
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
|
||||
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
|
||||
SSgarbage.should_save_refs = TRUE
|
||||
|
||||
//Set up for the first round of tests
|
||||
testbed.test = victim
|
||||
testbed.test_list += victim
|
||||
testbed.test_assoc_list["baseline"] = victim
|
||||
|
||||
var/refcount = refcount(victim)
|
||||
TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)")
|
||||
victim.DoSearchVar(testbed, "First Run")
|
||||
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, "test"), "The ref-tracking tool failed to find a regular value")
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_list), "The ref-tracking tool failed to find a list entry")
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find an assoc list value")
|
||||
SSgarbage.should_save_refs = FALSE
|
||||
|
||||
/datum/unit_test/find_reference_exotic/Run()
|
||||
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
|
||||
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
|
||||
SSgarbage.should_save_refs = TRUE
|
||||
|
||||
//Second round, bit harder this time
|
||||
testbed.overlays += victim
|
||||
testbed.vis_contents += victim
|
||||
testbed.test_assoc_list[victim] = TRUE
|
||||
|
||||
var/refcount = refcount(victim)
|
||||
TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)")
|
||||
victim.DoSearchVar(testbed, "Second Run")
|
||||
|
||||
//This is another sanity check
|
||||
TEST_ASSERT(!LAZYACCESS(victim.found_refs, testbed.overlays), "The ref-tracking tool found an overlays entry? That shouldn't be possible")
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.vis_contents), "The ref-tracking tool failed to find a vis_contents entry")
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find an assoc list key")
|
||||
SSgarbage.should_save_refs = FALSE
|
||||
|
||||
/datum/unit_test/find_reference_esoteric/Run()
|
||||
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
|
||||
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
|
||||
SSgarbage.should_save_refs = TRUE
|
||||
|
||||
//Let's get a bit esoteric
|
||||
victim.self_ref = victim
|
||||
var/list/to_find = list(victim)
|
||||
testbed.test_list += list(to_find)
|
||||
var/list/to_find_assoc = list(victim)
|
||||
testbed.test_assoc_list["Nesting"] = to_find_assoc
|
||||
|
||||
var/refcount = refcount(victim)
|
||||
TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)")
|
||||
victim.DoSearchVar(victim, "Third Run Self")
|
||||
victim.DoSearchVar(testbed, "Third Run Testbed")
|
||||
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, "self_ref"), "The ref-tracking tool failed to find a self reference")
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find), "The ref-tracking tool failed to find a nested list entry")
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_assoc), "The ref-tracking tool failed to find a nested assoc list entry")
|
||||
SSgarbage.should_save_refs = FALSE
|
||||
|
||||
/datum/unit_test/find_reference_null_key_entry/Run()
|
||||
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
|
||||
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
|
||||
SSgarbage.should_save_refs = TRUE
|
||||
|
||||
//Calm before the storm
|
||||
testbed.test_assoc_list = list(null = victim)
|
||||
var/refcount = refcount(victim)
|
||||
TEST_ASSERT_EQUAL(refcount, 4, "Should be: test references: 1 + baseline references: 3 (victim var,loc,allocated list)")
|
||||
victim.DoSearchVar(testbed, "Fourth Run")
|
||||
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find a null key'd assoc list entry")
|
||||
|
||||
/datum/unit_test/find_reference_assoc_investigation/Run()
|
||||
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
|
||||
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
|
||||
SSgarbage.should_save_refs = TRUE
|
||||
|
||||
//Let's do some more complex assoc list investigation
|
||||
var/list/to_find_in_key = list(victim)
|
||||
testbed.test_assoc_list[to_find_in_key] = list("memes")
|
||||
var/list/to_find_null_assoc_nested = list(victim)
|
||||
testbed.test_assoc_list[null] = to_find_null_assoc_nested
|
||||
|
||||
var/refcount = refcount(victim)
|
||||
TEST_ASSERT_EQUAL(refcount, 5, "Should be: test references: 2 + baseline references: 3 (victim var,loc,allocated list)")
|
||||
victim.DoSearchVar(testbed, "Fifth Run")
|
||||
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_in_key), "The ref-tracking tool failed to find a nested assoc list key")
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_null_assoc_nested), "The ref-tracking tool failed to find a null key'd nested assoc list entry")
|
||||
SSgarbage.should_save_refs = FALSE
|
||||
|
||||
/datum/unit_test/find_reference_static_investigation/Run()
|
||||
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
|
||||
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
|
||||
pass(testbed)
|
||||
SSgarbage.should_save_refs = TRUE
|
||||
|
||||
//Lets check static vars now, since those can be a real headache
|
||||
testbed.static_test = victim
|
||||
|
||||
//Yes we do actually need to do this. The searcher refuses to read weird lists
|
||||
//And global.vars is a really weird list
|
||||
var/global_vars = list()
|
||||
for(var/key in global.vars)
|
||||
global_vars[key] = global.vars[key]
|
||||
|
||||
var/refcount = refcount(victim)
|
||||
TEST_ASSERT_EQUAL(refcount, 5, "Should be: test references: 2 + baseline references: 3 (victim var,loc,allocated list)")
|
||||
victim.DoSearchVar(global_vars, "Sixth Run")
|
||||
|
||||
TEST_ASSERT(LAZYACCESS(victim.found_refs, global_vars), "The ref-tracking tool failed to find a natively global variable")
|
||||
SSgarbage.should_save_refs = FALSE
|
||||
@@ -0,0 +1,61 @@
|
||||
/// These tests perform no behavior of their own, and have their tests offloaded onto other procs.
|
||||
/// This is useful in cases like in build_appearance_list where we want to know if any fail,
|
||||
/// but is not useful to right a test for.
|
||||
/// This file exists so that you can change any of these to TEST_FOCUS and only check for that test.
|
||||
/// For example, change /datum/unit_test/focus_only/invalid_overlays to TEST_FOCUS(/datum/unit_test/focus_only/invalid_overlays),
|
||||
/// and you will only test the check for invalid overlays in appearance building.
|
||||
/datum/unit_test/focus_only
|
||||
|
||||
/// Checks that every created emissive has a valid icon_state
|
||||
/datum/unit_test/focus_only/invalid_emissives
|
||||
|
||||
/// Checks that every overlay passed into build_appearance_list exists in the icon
|
||||
/datum/unit_test/focus_only/invalid_overlays
|
||||
|
||||
/// Checks that every icon sent to the research_designs spritesheet is valid
|
||||
/datum/unit_test/focus_only/invalid_research_designs
|
||||
|
||||
/// Checks that every icon sent to vending machines is valid
|
||||
/datum/unit_test/focus_only/invalid_vending_machine_icon_states
|
||||
|
||||
/// Checks that space does not initialize multiple times
|
||||
/datum/unit_test/focus_only/multiple_space_initialization
|
||||
|
||||
/// Checks that smoothing_groups and canSmoothWith are properly sorted in /atom/Initialize
|
||||
/datum/unit_test/focus_only/sorted_smoothing_groups
|
||||
|
||||
/// Checks that floor tiles are properly mapped to broken/burnt
|
||||
/datum/unit_test/focus_only/valid_turf_states
|
||||
|
||||
/// Checks that nightvision eyes have a full set of color lists
|
||||
/datum/unit_test/focus_only/nightvision_color_cutoffs
|
||||
|
||||
/// Checks that no light shares a tile/pixel offsets with another
|
||||
/datum/unit_test/focus_only/stacked_lights
|
||||
|
||||
/// Checks for bad icon / icon state setups in cooking crafting menu
|
||||
/datum/unit_test/focus_only/bad_cooking_crafting_icons
|
||||
|
||||
/// Ensures openspace never spawns on the bottom of a z stack
|
||||
/datum/unit_test/focus_only/openspace_clear
|
||||
|
||||
/// Checks to ensure that variables expected to exist in a job datum (for config reasons) actually exist
|
||||
/datum/unit_test/focus_only/missing_job_datum_variables
|
||||
|
||||
/// Checks that the contents of the fish_counts list are also present in fish_table
|
||||
/datum/unit_test/focus_only/fish_sources_tables
|
||||
|
||||
/// Checks that maploaded mobs with either the `atmos_requirements` or `body_temp_sensitive`
|
||||
/datum/unit_test/focus_only/atmos_and_temp_requirements
|
||||
|
||||
/// Ensures only whitelisted planes can have TOPDOWN_LAYERing, and vis versa
|
||||
/datum/unit_test/focus_only/topdown_filtering
|
||||
|
||||
/// Catches any invalid footstep types set for humans
|
||||
/datum/unit_test/focus_only/humanstep_validity
|
||||
|
||||
/// Checks icon states generated at runtime are valid
|
||||
/datum/unit_test/focus_only/runtime_icon_states
|
||||
|
||||
/// Checks that foodtypes are the same for food whether it's spawned or crafted (with the exact required types)
|
||||
/datum/unit_test/focus_only/check_foodtypes
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* This unit test verifies that all Font Awesome icons are present in code, and that all quirk icons are valid.
|
||||
*/
|
||||
/datum/unit_test/font_awesome_icons
|
||||
var/font_awesome_css
|
||||
var/list/allowed_icons
|
||||
|
||||
/datum/unit_test/font_awesome_icons/Run()
|
||||
var/font_awesome_file = file('html/font-awesome/css/all.min.css')
|
||||
if(isnull(font_awesome_file))
|
||||
TEST_FAIL("Font Awesome CSS file could not be found!")
|
||||
return
|
||||
|
||||
font_awesome_css = file2text(font_awesome_file)
|
||||
if(isnull(font_awesome_css))
|
||||
TEST_NOTICE(src, "Font Awesome CSS file could not be loaded.")
|
||||
return
|
||||
|
||||
load_parse_verify()
|
||||
//verify_quirk_icons()
|
||||
generate_helper_dm_file()
|
||||
|
||||
/**
|
||||
* Loads the Font Awesome CSS file, parses it into a list of icon names, and compares it to the list of icons in code.
|
||||
* If there are any differences, note them.
|
||||
*/
|
||||
/datum/unit_test/font_awesome_icons/proc/load_parse_verify()
|
||||
//log_test("CSS Actual: [length(font_awesome_css)]")
|
||||
log_unit_test("CSS Actual: [length(font_awesome_css)]")
|
||||
allowed_icons = parse_fa_css_into_icon_list(font_awesome_css)
|
||||
|
||||
/**
|
||||
* Verifies that all quirk icons are valid.
|
||||
*/
|
||||
/* NOT IMPLEMENTED
|
||||
/datum/unit_test/font_awesome_icons/proc/verify_quirk_icons()
|
||||
for(var/datum/quirk/quirk as anything in subtypesof(/datum/quirk))
|
||||
if(quirk == initial(quirk.abstract_parent_type))
|
||||
continue
|
||||
|
||||
var/quirk_icon = initial(quirk.icon)
|
||||
if(findtext(quirk_icon, "tg-") == 1) // TODO: Validate these as well
|
||||
continue
|
||||
|
||||
if(findtext(quirk_icon, " "))
|
||||
var/list/split = splittext(quirk_icon, " ")
|
||||
quirk_icon = split[length(split)] // respect modifier classes
|
||||
|
||||
if(!(quirk_icon in allowed_icons))
|
||||
TEST_FAIL("Quirk [initial(quirk.name)]([quirk]) has invalid icon: [quirk_icon]")
|
||||
*/
|
||||
|
||||
/// Parses the given Font Awesome CSS file into a list of icon names.
|
||||
/datum/unit_test/font_awesome_icons/proc/parse_fa_css_into_icon_list(css)
|
||||
css = replacetext(css, "\n", "")
|
||||
var/list/css_entries = splittext(css, "}")
|
||||
var/list/icons = list()
|
||||
for(var/entry in css_entries)
|
||||
entry = replacetext(entry, "\t", "")
|
||||
if(!length(entry))
|
||||
continue
|
||||
|
||||
var/entry_contents = splittext(entry, "{")
|
||||
var/list/entry_names = splittext(entry_contents[1], ",")
|
||||
for(var/entry_name in entry_names)
|
||||
entry_names -= entry_name
|
||||
|
||||
if(!findtext(entry_name, ":"))
|
||||
continue
|
||||
|
||||
entry_name = splittext(entry_name, ":")[1]
|
||||
if(!findtext(entry_name, ".fa-"))
|
||||
continue
|
||||
|
||||
entry_name = replacetext(entry_name, ".fa-", "fa-")
|
||||
entry_names |= entry_name
|
||||
icons |= entry_names
|
||||
|
||||
return sortList(icons)
|
||||
|
||||
/datum/unit_test/font_awesome_icons/proc/generate_helper_dm_file()
|
||||
var/list/output = list()
|
||||
output += "/* This file is automatically generated by the unit test. Do not edit it manually."
|
||||
output += " * Generating this file is done by running the unit test locally, see the fail message for more details."
|
||||
output += " * All valid font awesome icons should be here."
|
||||
output += " */"
|
||||
output += ""
|
||||
|
||||
for(var/icon in allowed_icons)
|
||||
var/icon_name = replacetext(icon, "fa-", "")
|
||||
output += "#define FA_ICON_[uppertext(replacetext(icon_name, "-", "_"))] \"[icon]\"" // #undef FA_ICON_ // we have this here to avoid define_sanity throwing a fit
|
||||
|
||||
var/output_file = "[output.Join("\n")]\n"
|
||||
rustg_file_write(output_file, "data/font_awesome_icons.dm")
|
||||
var/current = file2text('code/__DEFINES/font_awesome_icons.dm')
|
||||
if(current == output_file)
|
||||
return
|
||||
|
||||
TEST_FAIL("Font Awesome helper file is out of date. Run locally by enabling unit tests, (see _compile_options.dm) and copy 'data/font_awesome_icons.dm' to 'code/__DEFINES/font_awesome_icons.dm'")
|
||||
@@ -0,0 +1,69 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
|
||||
/// Test that there are enough free gene slots available
|
||||
/datum/unit_test/enough_free_gene_slots_must_be_available
|
||||
|
||||
/datum/unit_test/enough_free_gene_slots_must_be_available/Run()
|
||||
// Based off of traitgenes scanned on startup
|
||||
TEST_ASSERT(!(GLOB.dna_genes.len > (DNA_SE_LENGTH - 10)), "Too few geneslots are empty, minimum 10. Increase DNA_SE_LENGTH.")
|
||||
|
||||
/// Test that there is at least one positive gene
|
||||
/datum/unit_test/enough_positive_genes_must_exist
|
||||
|
||||
/datum/unit_test/enough_positive_genes_must_exist/Run()
|
||||
// Based off of traitgenes scanned on startup
|
||||
TEST_ASSERT(!(GLOB.dna_genes_good.len < 1), "Must have at least one positive gene.")
|
||||
|
||||
/// Test that there is at least one neutral gene
|
||||
/datum/unit_test/enough_neutral_genes_must_exist
|
||||
|
||||
/datum/unit_test/enough_neutral_genes_must_exist/Run()
|
||||
// Based off of traitgenes scanned on startup
|
||||
TEST_ASSERT(!(GLOB.dna_genes_neutral.len < 1), "Must have at least one neutral gene.")
|
||||
|
||||
/// Test that there is at least one bad gene
|
||||
/datum/unit_test/enough_bad_genes_must_exist
|
||||
|
||||
/datum/unit_test/enough_bad_genes_must_exist/Run()
|
||||
// Based off of traitgenes scanned on startup
|
||||
TEST_ASSERT(!(GLOB.dna_genes_bad.len < 1), "Must have at least one bad gene.")
|
||||
|
||||
/// Test that all dna injectors are valid
|
||||
/datum/unit_test/all_dna_injectors_must_be_valid
|
||||
|
||||
/datum/unit_test/all_dna_injectors_must_be_valid/Run()
|
||||
for(var/injector_path in subtypesof(/obj/item/dnainjector/set_trait))
|
||||
var/obj/item/dnainjector/D = new injector_path()
|
||||
TEST_ASSERT(D.block, "[injector_path]: Genetics - Injector could not resolve geneblock for trait. Missing traitgene?")
|
||||
qdel(D)
|
||||
|
||||
/// Test that all genes have unique names
|
||||
/datum/unit_test/all_genes_shall_have_unique_name
|
||||
|
||||
/datum/unit_test/all_genes_shall_have_unique_name/Run()
|
||||
var/collection = list()
|
||||
for(var/datum/gene/G in GLOB.dna_genes)
|
||||
TEST_ASSERT(!collection[G.name], "[G.name]: Genetics - Gene name was already in use.")
|
||||
collection[G.name] = G.name
|
||||
|
||||
/// Test that all genes should have valid activation bounds
|
||||
/datum/unit_test/genetraits_should_have_valid_dna_bounds
|
||||
|
||||
/datum/unit_test/genetraits_should_have_valid_dna_bounds/Run()
|
||||
for(var/datum/gene/trait/G in GLOB.trait_to_dna_genes)
|
||||
TEST_ASSERT(G.linked_trait, "[G.name]: Genetics - Has missing linked trait.")
|
||||
TEST_ASSERT(G.linked_trait.activity_bounds, "[G.name]: Genetics - Has no activation bounds.")
|
||||
TEST_ASSERT(G.linked_trait.activity_bounds.len, "[G.name]: Genetics - Has empty activation bounds.")
|
||||
|
||||
// DNA activation bounds. Usually they are in a list as follows:
|
||||
// [1]DNA_OFF_LOWERBOUND = 1, begining of the threshold where a gene turns off.
|
||||
// [2]DNA_OFF_UPPERBOUND = a number above 1, end of the treshold where a gene turns off.
|
||||
// [3]DNA_ON_LOWERBOUND = a number above DNA_OFF_UPPERBOUND(even if just by 1), threshold where a gene turns on.
|
||||
// [4]DNA_ON_UPPERBOUND = 4095, end of the threshold where a gene turns on.
|
||||
|
||||
var/list/bounds = G.linked_trait.activity_bounds
|
||||
TEST_ASSERT(bounds[1] > 1, "[G.name]: Genetics - DNA_OFF_LOWERBOUND, was smaller than 1.") // lowest value a gene can be to turn off
|
||||
TEST_ASSERT(bounds[2] > bounds[1], "[G.name]: Genetics - DNA_OFF_UPPERBOUND must be larger than DNA_OFF_LOWERBOUND, and never equal.")
|
||||
TEST_ASSERT(bounds[2] <= bounds[3], "[G.name]: Genetics - DNA_OFF_UPPERBOUND must be smaller than DNA_ON_LOWERBOUND, and never equal.")
|
||||
TEST_ASSERT(bounds[3] < bounds[4], "[G.name]: Genetics - DNA_ON_LOWERBOUND must be smaller than DNA_ON_UPPERBOUND, and never equal.")
|
||||
TEST_ASSERT(bounds[4] < 4095, "[G.name]: Genetics - DNA_ON_UPPERBOUND, was larger than 4095.") // highest value a gene can be to turn on
|
||||
@@ -0,0 +1,25 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
|
||||
/// Test that language entries have distinct names
|
||||
/datum/unit_test/language_test_shall_have_distinct_names
|
||||
|
||||
/datum/unit_test/language_test_shall_have_distinct_names/Run()
|
||||
if(length(GLOB.language_name_conflicts) != 0)
|
||||
var/list/name_conflict_log = list()
|
||||
for(var/conflicted_name in GLOB.language_name_conflicts)
|
||||
name_conflict_log += "+[length(GLOB.language_name_conflicts[conflicted_name])] languages with name \"[conflicted_name]\"!"
|
||||
for(var/datum/language/L in GLOB.language_name_conflicts[conflicted_name])
|
||||
name_conflict_log += "+-+[L.type]"
|
||||
TEST_FAIL("Some names are used by more than one language:\n" + name_conflict_log.Join("\n"))
|
||||
|
||||
/// Test that language entries have distinct keys
|
||||
/datum/unit_test/language_test_shall_have_distinct_keys
|
||||
|
||||
/datum/unit_test/language_test_shall_have_distinct_keys/Run()
|
||||
if(length(GLOB.language_key_conflicts) != 0)
|
||||
var/list/key_conflict_log = list()
|
||||
for(var/conflicted_key in GLOB.language_key_conflicts)
|
||||
key_conflict_log += "+[length(GLOB.language_key_conflicts[conflicted_key])] languages with key \"[conflicted_key]\"!"
|
||||
for(var/datum/language/L in GLOB.language_key_conflicts[conflicted_key])
|
||||
key_conflict_log += "+-+[L]([L.type])"
|
||||
TEST_FAIL("Some keys are used by more than one language:\n" + key_conflict_log.Join("\n"))
|
||||
@@ -0,0 +1,7 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
|
||||
/datum/unit_test/loadout_tests/Run()
|
||||
for(var/datum/gear/G as anything in subtypesof(/datum/gear))
|
||||
TEST_ASSERT(initial(G.display_name), "[G]: Loadout - Missing display name.")
|
||||
TEST_ASSERT_NOTNULL(initial(G.cost), "[G]: Loadout - Missing cost.")
|
||||
TEST_ASSERT(initial(G.path), "[G]: Loadout - Missing path definition.")
|
||||
@@ -0,0 +1,189 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
/// MIGHT REQUIRE BIGGER REWORK
|
||||
|
||||
/// Test that tests the apcs, scrubbers and vents of the defined z-levels
|
||||
/datum/unit_test/apc_area_test
|
||||
|
||||
/datum/unit_test/apc_area_test/Run()
|
||||
var/list/exempt_areas = typesof(/area/space,
|
||||
/area/syndicate_station,
|
||||
/area/skipjack_station,
|
||||
/area/solar,
|
||||
/area/shuttle,
|
||||
/area/holodeck,
|
||||
/area/supply/station,
|
||||
/area/mine,
|
||||
/area/vacant/vacant_shop,
|
||||
/area/turbolift,
|
||||
/area/submap
|
||||
)
|
||||
|
||||
var/list/exempt_from_atmos = typesof(/area/maintenance,
|
||||
/area/storage,
|
||||
/area/engineering/atmos/storage,
|
||||
/area/rnd/test_area,
|
||||
/area/construction,
|
||||
/area/server,
|
||||
/area/mine,
|
||||
/area/vacant/vacant_shop,
|
||||
/area/rnd/research_storage, // This should probably be fixed,
|
||||
/area/security/riot_control, // This should probably be fixed,
|
||||
)
|
||||
|
||||
var/list/exempt_from_apc = typesof(/area/construction,
|
||||
/area/medical/genetics,
|
||||
/area/mine,
|
||||
/area/vacant/vacant_shop
|
||||
)
|
||||
|
||||
// Some maps have areas specific to the map, so include those.
|
||||
exempt_areas += using_map.unit_test_exempt_areas.Copy()
|
||||
exempt_from_atmos += using_map.unit_test_exempt_from_atmos.Copy()
|
||||
exempt_from_apc += using_map.unit_test_exempt_from_apc.Copy()
|
||||
|
||||
var/list/zs_to_test = using_map.unit_test_z_levels || list(1) //Either you set it, or you just get z1
|
||||
|
||||
for(var/area/A in world)
|
||||
if((A.z in zs_to_test) && !(A.type in exempt_areas))
|
||||
var/bad_msg = "--------------- [A.name]([A.type])"
|
||||
|
||||
// Scan for areas with extra APCs
|
||||
if(!(A.type in exempt_from_apc))
|
||||
TEST_ASSERT_NOTNULL(A.apc, "[bad_msg] lacks an APC. (X[A.x]|Y[A.y]) - Z[A.z])")
|
||||
|
||||
if(!isnull(A.apc))
|
||||
var/list/apc_list = list()
|
||||
for(var/turf/T in get_current_area_turfs(A))
|
||||
for(var/atom/S in T.contents)
|
||||
if(istype(S,/obj/machinery/power/apc))
|
||||
apc_list.Add(S)
|
||||
if(apc_list.len > 1)
|
||||
for(var/obj/machinery/power/P in apc_list)
|
||||
TEST_FAIL("[bad_msg] has too many APCs. (X[P.x]|Y[P.y]) - Z[P.z])")
|
||||
|
||||
TEST_ASSERT(!(!A.air_scrub_info.len && !(A.type in exempt_from_atmos)), "[bad_msg] lacks an Air scrubber. (X[A.x]|Y[A.y]) - (Z[A.z])")
|
||||
TEST_ASSERT(!(!A.air_vent_info.len && !(A.type in exempt_from_atmos)), "[bad_msg] lacks an Air vent. (X[A.x]|Y[A.y]) - (Z[A.z])")
|
||||
|
||||
/// Test that tests cables on defined z-levels
|
||||
/datum/unit_test/wire_test
|
||||
var/wire_test_count = 0
|
||||
var/turf/T = null
|
||||
var/obj/structure/cable/C = null
|
||||
var/list/cable_turfs = list()
|
||||
var/list/dirs_checked = list()
|
||||
|
||||
var/list/exempt_from_wires = list()
|
||||
|
||||
/datum/unit_test/wire_test/Run()
|
||||
set background = 1
|
||||
|
||||
exempt_from_wires += using_map.unit_test_exempt_from_wires.Copy()
|
||||
|
||||
var/list/zs_to_test = using_map.unit_test_z_levels || list(1) //Either you set it, or you just get z1
|
||||
|
||||
for(var/color in GLOB.possible_cable_coil_colours)
|
||||
cable_turfs = list()
|
||||
|
||||
for(C in world)
|
||||
T = null
|
||||
|
||||
T = get_turf(C)
|
||||
var/area/A = get_area(T)
|
||||
if(T && (T.z in zs_to_test) && !(A.type in exempt_from_wires))
|
||||
if(C.color == GLOB.possible_cable_coil_colours[color])
|
||||
cable_turfs |= get_turf(C)
|
||||
|
||||
for(T in cable_turfs)
|
||||
var/bad_msg = "--------------- [T.name] \[[T.x] / [T.y] / [T.z]\] [color]"
|
||||
dirs_checked.Cut()
|
||||
for(C in T)
|
||||
wire_test_count++
|
||||
var/combined_dir = "[C.d1]-[C.d2]"
|
||||
TEST_ASSERT(!(combined_dir in dirs_checked), "[bad_msg] Contains multiple wires with same direction on top of each other.")
|
||||
TEST_ASSERT(C.dir == SOUTH, "[bad_msg] Contains wire with dir set, wires MUST face south, use icon_states.")
|
||||
dirs_checked.Add(combined_dir)
|
||||
|
||||
/// Test template no-ops on all maps
|
||||
/datum/unit_test/template_noops
|
||||
var/list/log = list()
|
||||
var/turf_noop_count = 0
|
||||
|
||||
/datum/unit_test/template_noops/Run()
|
||||
for(var/turf/template_noop/T in world)
|
||||
turf_noop_count++
|
||||
log += "+-- Template Turf @ [T.x], [T.y], [T.z] ([T.loc])"
|
||||
|
||||
var/area_noop_count = 0
|
||||
for(var/area/template_noop/A in world)
|
||||
area_noop_count++
|
||||
log += "+-- Template Area"
|
||||
|
||||
if(turf_noop_count || area_noop_count)
|
||||
TEST_FAIL("Map contained [turf_noop_count] template turfs and [area_noop_count] template areas at round-start.\n" + log.Join("\n"))
|
||||
|
||||
/// Test active edges on all maps
|
||||
/datum/unit_test/active_edges
|
||||
|
||||
/datum/unit_test/active_edges/Run()
|
||||
var/active_edges = SSair.active_edges.len
|
||||
var/list/edge_log = list()
|
||||
|
||||
if(active_edges)
|
||||
for(var/connection_edge/E in SSair.active_edges)
|
||||
var/a_temp = E.A.air.temperature
|
||||
var/a_moles = E.A.air.total_moles
|
||||
var/a_vol = E.A.air.volume
|
||||
var/a_gas = ""
|
||||
for(var/gas in E.A.air.gas)
|
||||
a_gas += "[gas]=[E.A.air.gas[gas]]"
|
||||
|
||||
var/b_temp
|
||||
var/b_moles
|
||||
var/b_vol
|
||||
var/b_gas = ""
|
||||
|
||||
// Two zones mixing
|
||||
if(istype(E, /connection_edge/zone))
|
||||
var/connection_edge/zone/Z = E
|
||||
b_temp = Z.B.air.temperature
|
||||
b_moles = Z.B.air.total_moles
|
||||
b_vol = Z.B.air.volume
|
||||
for(var/gas in Z.B.air.gas)
|
||||
b_gas += "[gas]=[Z.B.air.gas[gas]]"
|
||||
|
||||
// Zone and unsimulated turfs mixing
|
||||
if(istype(E, /connection_edge/unsimulated))
|
||||
var/connection_edge/unsimulated/U = E
|
||||
b_temp = U.B.temperature
|
||||
b_moles = "Unsim"
|
||||
b_vol = "Unsim"
|
||||
for(var/gas in U.air.gas)
|
||||
b_gas += "[gas]=[U.air.gas[gas]]"
|
||||
|
||||
edge_log += "Active Edge [E] ([E.type])"
|
||||
edge_log += "Edge side A: T:[a_temp], Mol:[a_moles], Vol:[a_vol], Gas:[a_gas]"
|
||||
edge_log += "Edge side B: T:[b_temp], Mol:[b_moles], Vol:[b_vol], Gas:[b_gas]"
|
||||
|
||||
for(var/turf/T in E.connecting_turfs)
|
||||
edge_log += "+--- Connecting Turf [T] ([T.type]) @ [T.x], [T.y], [T.z] ([T.loc])"
|
||||
|
||||
if(active_edges)
|
||||
TEST_FAIL("Maps contained [active_edges] active edges at round-start.\n" + edge_log.Join("\n"))
|
||||
|
||||
/// Test the ladders on the maps
|
||||
/datum/unit_test/ladder_test
|
||||
var/failed = FALSE
|
||||
|
||||
/datum/unit_test/ladder_test/Run()
|
||||
for(var/obj/structure/ladder/L in world)
|
||||
var/turf/T = get_turf(L)
|
||||
TEST_ASSERT(T, "[L.x].[L.y].[L.z]: Map - Ladder on invalid turf")
|
||||
if(!T)
|
||||
continue
|
||||
|
||||
if(L.allowed_directions & UP)
|
||||
TEST_ASSERT(L.target_up, "[T.x].[T.y].[T.z]: Map - Ladder allows upward movement, but had no ladder above it")
|
||||
if(L.allowed_directions & DOWN)
|
||||
TEST_ASSERT(L.target_down, "[T.x].[T.y].[T.z]: Map - Ladder allows downward movement, but had no ladder beneath it")
|
||||
|
||||
TEST_ASSERT(!T.density, "[L.x].[L.y].[L.z]: Map - Ladder is inside a wall")
|
||||
@@ -0,0 +1,17 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
|
||||
/// Test that a material should have all the name variables set
|
||||
/datum/unit_test/materials_shall_have_names
|
||||
|
||||
/datum/unit_test/materials_shall_have_names/Run()
|
||||
var/list/failures = list()
|
||||
populate_material_list()
|
||||
for(var/name in global.name_to_material)
|
||||
var/datum/material/mat = global.name_to_material[name]
|
||||
if(!mat)
|
||||
continue // how did we get here?
|
||||
if(!mat.display_name || !mat.use_name || !mat.sheet_singular_name || !mat.sheet_plural_name || !mat.sheet_collective_name)
|
||||
failures[name] = mat.type
|
||||
|
||||
if(length(failures))
|
||||
TEST_FAIL("[length(failures)] material\s had missing name strings: [english_list(failures)].")
|
||||
@@ -0,0 +1,68 @@
|
||||
// Some defines for tracking if the correct cinematic / animation is playing.
|
||||
#define PLAYING_CORRECT_ANIMATION 2
|
||||
#define PLAYING_INCORRECT_NUKE_ANIMATION 1
|
||||
#define NOT_PLAYING_ANIMATION 0
|
||||
|
||||
/**
|
||||
* Unit tests that a nuke going off plays a cinematic,
|
||||
* and that it actually kills people.
|
||||
*/
|
||||
/datum/unit_test/nuke_cinematic
|
||||
/// Used to track via signal if the correct cinematic / animation is playing.
|
||||
var/cinematic_playing = NOT_PLAYING_ANIMATION
|
||||
/// Tracks what typepath of cinematic is being played.
|
||||
var/cinematic_playing_type
|
||||
|
||||
/datum/unit_test/nuke_cinematic/Run()
|
||||
var/obj/machinery/nuclearbomb/syndicate/nuke = allocate(/obj/machinery/nuclearbomb/syndicate)
|
||||
var/mob/living/carbon/human/nuked = allocate(/mob/living/carbon/human/consistent)
|
||||
var/datum/client_interface/mock_client = new
|
||||
nuked.mock_client = mock_client
|
||||
mock_client.mob = nuked
|
||||
|
||||
var/obj/effect/landmark/observer_start/observer_point = locate(/obj/effect/landmark/observer_start) in landmarks_list
|
||||
TEST_ASSERT_NOTNULL(observer_point, "Nuke cinematic test couldn't find observer spawn to place the nuke.")
|
||||
|
||||
var/turf/turf_on_station = get_turf(observer_point)
|
||||
TEST_ASSERT(is_station_level(turf_on_station.z), "Nuke cinematic test didn't get a turf which was located on the station.")
|
||||
|
||||
nuke.forceMove(turf_on_station)
|
||||
nuked.forceMove(turf_on_station)
|
||||
|
||||
// Pause the check so we don't, y'know, end the round
|
||||
SSticker.roundend_check_paused = TRUE
|
||||
RegisterSignal(SSdcs, COMSIG_GLOB_PLAY_CINEMATIC, PROC_REF(check_cinematic))
|
||||
// actually_explode calls really_actually_explode which sleeps, so this will take a moment.
|
||||
var/nuke_result = nuke.actually_explode()
|
||||
|
||||
TEST_ASSERT_EQUAL(nuke_result, DETONATION_HIT_STATION, "A nuke went off on station, but didn't return DETONATION_HIT_STATION (4). (Got: [nuke_result])")
|
||||
TEST_ASSERT(GLOB.station_was_nuked, "A nuke went off on station, but didn't set station_was_nuked.")
|
||||
// Reset the nuke var back so we don't end the round
|
||||
GLOB.station_was_nuked = FALSE
|
||||
SSticker.roundend_check_paused = FALSE
|
||||
|
||||
switch(cinematic_playing)
|
||||
if(NOT_PLAYING_ANIMATION)
|
||||
TEST_FAIL("No nuke cinematic was played when a nuke was detonated.")
|
||||
|
||||
if(PLAYING_INCORRECT_NUKE_ANIMATION)
|
||||
TEST_FAIL("An incorrect cinematic was played on nuke detonation. (Expected: /datum/cinematic/nuke/self_destruct, Got: [cinematic_playing_type])")
|
||||
|
||||
TEST_ASSERT(QDELETED(nuked), "The nuke victim next to the nuke wasn't gibbed by the nuke.")
|
||||
TEST_ASSERT(QDELETED(nuke), "The nuke itself was not deleted after successfully exploding.")
|
||||
mock_client.mob = null
|
||||
|
||||
/// Used to track whenever a cinematic starts playing, so we can check if it's the right one.
|
||||
/datum/unit_test/nuke_cinematic/proc/check_cinematic(datum/source, datum/cinematic/playing)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
cinematic_playing_type = playing.type
|
||||
if(istype(playing, /datum/cinematic/nuke/self_destruct))
|
||||
cinematic_playing = PLAYING_CORRECT_ANIMATION
|
||||
|
||||
else if(istype(playing, /datum/cinematic/nuke))
|
||||
cinematic_playing = PLAYING_INCORRECT_NUKE_ANIMATION
|
||||
|
||||
#undef PLAYING_CORRECT_ANIMATION
|
||||
#undef PLAYING_INCORRECT_NUKE_ANIMATION
|
||||
#undef NOT_PLAYING_ANIMATION
|
||||
@@ -0,0 +1,13 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
|
||||
/datum/unit_test/posters_shall_have_legal_states/Run()
|
||||
var/list/all_posters = decls_repository.get_decls_of_type(/decl/poster)
|
||||
all_posters -= decls_repository.get_decl(/decl/poster/lewd) // Dumb exclusion for now. This really needs to become a valid poster instead of an illegaly made base type
|
||||
|
||||
for(var/path in all_posters)
|
||||
var/decl/poster/D = all_posters[path]
|
||||
var/obj/structure/sign/poster/P = /obj/structure/sign/poster // The base poster shows ALL subtypes except /lewd, so all posters should function here regardless!
|
||||
var/icon/I = initial(P.icon)
|
||||
if(D.icon_override)
|
||||
I = D.icon_override
|
||||
TEST_ASSERT(D.icon_state in cached_icon_states(I), "[D.type]: Poster - missing icon_state \"[D.icon_state]\" in \"[I]\", as [D.icon_override ? "override" : "base"] dmi.")
|
||||
@@ -0,0 +1,77 @@
|
||||
/// Requires all preferences to implement required methods.
|
||||
/datum/unit_test/preferences_implement_everything
|
||||
|
||||
/datum/unit_test/preferences_implement_everything/Run()
|
||||
var/datum/preferences/preferences = new(new /datum/client_interface)
|
||||
var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human/consistent)
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/preference = GLOB.preference_entries[preference_type]
|
||||
if (preference.savefile_identifier == PREFERENCE_CHARACTER)
|
||||
preference.apply_to_human(human, preference.create_informed_default_value(preferences))
|
||||
|
||||
if (istype(preference, /datum/preference/choiced))
|
||||
var/datum/preference/choiced/choiced_preference = preference
|
||||
choiced_preference.init_possible_values()
|
||||
|
||||
// Smoke-test is_valid
|
||||
preference.is_valid(TRUE)
|
||||
preference.is_valid("string")
|
||||
preference.is_valid(100)
|
||||
preference.is_valid(list(1, 2, 3))
|
||||
|
||||
/// Requires all preferences to have a valid, unique savefile_identifier.
|
||||
/datum/unit_test/preferences_valid_savefile_key
|
||||
|
||||
/datum/unit_test/preferences_valid_savefile_key/Run()
|
||||
var/list/known_savefile_keys = list()
|
||||
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/preference = GLOB.preference_entries[preference_type]
|
||||
if (!istext(preference.savefile_key))
|
||||
TEST_FAIL("[preference_type] has an invalid savefile_key.")
|
||||
|
||||
if (preference.savefile_key in known_savefile_keys)
|
||||
TEST_FAIL("[preference_type] has a non-unique savefile_key `[preference.savefile_key]`!")
|
||||
|
||||
known_savefile_keys += preference.savefile_key
|
||||
|
||||
/// Requires all main features have a main_feature_name
|
||||
/datum/unit_test/preferences_valid_main_feature_name
|
||||
|
||||
/datum/unit_test/preferences_valid_main_feature_name/Run()
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/choiced/preference = GLOB.preference_entries[preference_type]
|
||||
if (!istype(preference))
|
||||
continue
|
||||
|
||||
if (preference.category != PREFERENCE_CATEGORY_FEATURES && preference.category != PREFERENCE_CATEGORY_CLOTHING)
|
||||
continue
|
||||
|
||||
TEST_ASSERT(!isnull(preference.main_feature_name), "Preference [preference_type] does not have a main_feature_name set!")
|
||||
|
||||
/// Validates that every choiced preference with should_generate_icons implements icon_for,
|
||||
/// and that every one that doesn't, doesn't.
|
||||
/datum/unit_test/preferences_should_generate_icons_sanity
|
||||
|
||||
/datum/unit_test/preferences_should_generate_icons_sanity/Run()
|
||||
for (var/preference_type in GLOB.preference_entries)
|
||||
var/datum/preference/choiced/choiced_preference = GLOB.preference_entries[preference_type]
|
||||
if (!istype(choiced_preference) || choiced_preference.abstract_type == preference_type)
|
||||
continue
|
||||
|
||||
var/list/values = choiced_preference.get_choices()
|
||||
|
||||
if (choiced_preference.should_generate_icons)
|
||||
for (var/value in values)
|
||||
var/icon = choiced_preference.icon_for(value)
|
||||
TEST_ASSERT(istype(icon, /datum/universal_icon) || ispath(icon), "[preference_type] gave [icon] as an icon for [value], which is not a valid value")
|
||||
else
|
||||
var/errored = FALSE
|
||||
|
||||
try
|
||||
choiced_preference.icon_for(values[1])
|
||||
catch
|
||||
errored = TRUE
|
||||
|
||||
TEST_ASSERT(errored, "[preference_type] implemented icon_for, but does not have should_generate_icons = TRUE")
|
||||
@@ -0,0 +1,251 @@
|
||||
/// converted unit test, maybe should be fully refactored
|
||||
/// MIGHT REQUIRE BIGGER REWORK
|
||||
|
||||
/// Test that makes sure that reagent ids and names are unique
|
||||
/datum/unit_test/reagent_shall_have_unique_name_and_id
|
||||
|
||||
/datum/unit_test/reagent_shall_have_unique_name_and_id/Run()
|
||||
var/collection_name = list()
|
||||
var/collection_id = list()
|
||||
|
||||
for(var/Rpath in subtypesof(/datum/reagent))
|
||||
var/datum/reagent/R = new Rpath()
|
||||
|
||||
if(R.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden
|
||||
continue
|
||||
|
||||
TEST_ASSERT(R.name != "", "[Rpath]: Reagents - reagent name blank.")
|
||||
TEST_ASSERT_NOTEQUAL(R.id, REAGENT_ID_DEVELOPER_WARNING, "[Rpath]: Reagents - reagent ID not set.")
|
||||
TEST_ASSERT_NOTEQUAL(R.description, REAGENT_DESC_DEVELOPER_WARNING, "[Rpath]: Reagents - reagent description unset.")
|
||||
|
||||
TEST_ASSERT(R.id != "", "[Rpath]: Reagents - reagent ID blank.")
|
||||
TEST_ASSERT_EQUAL(R.id, lowertext(R.id), "[Rpath]: Reagents - Reagent ID must be all lowercase.")
|
||||
|
||||
if(!(R.wiki_flag & WIKI_SPOILER)) // If wiki hidden then don't conflict test it against name, used for intentionally copied names like beer2's
|
||||
TEST_ASSERT(!collection_name[R.name], "[Rpath]: Reagents - reagent name \"[R.name]\" is not unique, used first in [collection_name[R.name]].")
|
||||
collection_name[R.name] = R.type
|
||||
|
||||
TEST_ASSERT(!collection_id[R.id], "[Rpath]: Reagents - reagent ID \"[R.id]\" is not unique, used first in [collection_id[R.id]].")
|
||||
collection_id[R.id] = R.type
|
||||
|
||||
TEST_ASSERT(R.supply_conversion_value, "[Rpath]: Reagents - reagent ID \"[R.id]\" does not have supply_conversion_value set.")
|
||||
TEST_ASSERT(R.industrial_use && R.industrial_use != "", "[Rpath]: Reagents - reagent ID \"[R.id]\" does not have industrial_use set.")
|
||||
TEST_ASSERT_NOTEQUAL(R.description, REAGENT_DESC_DEVELOPER_WARNING, "[Rpath]: Reagents - reagent description unset.")
|
||||
|
||||
qdel(R)
|
||||
|
||||
/// Test that makes sure that chemical reactions use and produce valid reagents
|
||||
/datum/unit_test/chemical_reactions_shall_use_and_produce_valid_reagents
|
||||
|
||||
/datum/unit_test/chemical_reactions_shall_use_and_produce_valid_reagents/Run()
|
||||
var/list/collection_id = list()
|
||||
|
||||
var/list/all_reactions = decls_repository.get_decls_of_subtype(/decl/chemical_reaction)
|
||||
for(var/rtype in all_reactions)
|
||||
var/decl/chemical_reaction/CR = all_reactions[rtype]
|
||||
if(CR.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden
|
||||
continue
|
||||
|
||||
TEST_ASSERT_NOTNULL(CR.name, "[CR.type]: Reagents - chemical reaction had null name.")
|
||||
TEST_ASSERT(CR.name != "", "[CR.type]: Reagents - chemical reaction had blank name.")
|
||||
TEST_ASSERT(CR.id, "[CR.type]: Reagents - chemical reaction had invalid ID.")
|
||||
TEST_ASSERT_EQUAL(CR.id, lowertext(CR.id), "[CR.type]: Reagents - chemical reaction ID must be all lowercase.")
|
||||
TEST_ASSERT(!(CR.id in collection_id), "[CR.type]: Reagents - chemical reaction ID \"[CR.name]\" is not unique, used first in [collection_id[CR.id]].")
|
||||
|
||||
|
||||
if(!(CR.id in collection_id))
|
||||
collection_id[CR.id] = CR.type
|
||||
|
||||
TEST_ASSERT(CR.result_amount >= 0, "[CR.type]: Reagents - chemical reaction ID \"[CR.name]\" had less than 0 as as result_amount?")
|
||||
|
||||
if(CR.required_reagents && CR.required_reagents.len)
|
||||
for(var/RR in CR.required_reagents)
|
||||
TEST_ASSERT(SSchemistry.chemical_reagents[RR], "[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".")
|
||||
TEST_ASSERT(CR.required_reagents[RR] > 0, "[CR.type]: Reagents - chemical reaction had invalid required reagent amount or in invalid format \"[CR.required_reagents[RR]]\".")
|
||||
|
||||
if(CR.catalysts && CR.catalysts.len)
|
||||
for(var/RR in CR.catalysts)
|
||||
TEST_ASSERT(SSchemistry.chemical_reagents[RR], "[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".")
|
||||
TEST_ASSERT(CR.catalysts[RR] > 0, "[CR.type]: Reagents - chemical reaction had invalid catalysts amount or in invalid format \"[CR.catalysts[RR]]\".")
|
||||
|
||||
if(CR.inhibitors && CR.inhibitors.len)
|
||||
for(var/RR in CR.inhibitors)
|
||||
TEST_ASSERT(SSchemistry.chemical_reagents[RR], "[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".")
|
||||
TEST_ASSERT(CR.inhibitors[RR] > 0, "[CR.type]: Reagents - chemical reaction had invalid inhibitors amount or in invalid format \"[CR.inhibitors[RR]]\".")
|
||||
|
||||
if(CR.result)
|
||||
TEST_ASSERT(SSchemistry.chemical_reagents[CR.result], "[CR.type]: Reagents - chemical reaction had invalid result reagent ID \"[CR.result]\".")
|
||||
|
||||
/// Test that makes sure that prefilled reagent containers have valid reagents
|
||||
/datum/unit_test/prefilled_reagent_containers_shall_have_valid_reagents
|
||||
|
||||
/datum/unit_test/prefilled_reagent_containers_shall_have_valid_reagents/Run()
|
||||
var/obj/container = new /obj
|
||||
for(var/RC in subtypesof(/obj/item/reagent_containers/glass))
|
||||
var/obj/item/reagent_containers/glass/R = new RC(container)
|
||||
|
||||
if(R.prefill && R.prefill.len)
|
||||
for(var/ID in R.prefill)
|
||||
TEST_ASSERT(SSchemistry.chemical_reagents[ID], "[RC]: Reagents - reagent prefill had invalid reagent ID \"[ID]\".")
|
||||
|
||||
qdel(R)
|
||||
|
||||
for(var/DC in subtypesof(/obj/item/reagent_containers/chem_disp_cartridge))
|
||||
var/obj/item/reagent_containers/chem_disp_cartridge/D = new DC(container)
|
||||
|
||||
if(D.spawn_reagent)
|
||||
TEST_ASSERT(SSchemistry.chemical_reagents[D.spawn_reagent], "[DC]: Reagents - chemical dispenser cartridge had invalid reagent ID \"[D.spawn_reagent]\".")
|
||||
|
||||
qdel(D)
|
||||
|
||||
qdel(container)
|
||||
|
||||
/// Test that makes sure that chemical reactions do not conflict
|
||||
/datum/unit_test/chemical_reactions_shall_not_conflict
|
||||
var/obj/fake_beaker = null
|
||||
var/list/result_reactions = list()
|
||||
|
||||
/datum/unit_test/chemical_reactions_shall_not_conflict/Run()
|
||||
var/failed = FALSE
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
var/list/all_reactions = decls_repository.get_decls_of_subtype(/decl/chemical_reaction)
|
||||
for(var/rtype in all_reactions)
|
||||
var/decl/chemical_reaction/CR = all_reactions[rtype]
|
||||
|
||||
if(CR.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden
|
||||
continue
|
||||
if(!CR.name || CR.name == "" || !CR.id || CR.id == "")
|
||||
continue
|
||||
if(CR.result_amount <= 0) //Makes nothing anyway, or maybe an effect/explosion!
|
||||
continue
|
||||
if(!CR.result) // Cannot check for this
|
||||
continue
|
||||
|
||||
if(istype(CR, /decl/chemical_reaction/instant/slime))
|
||||
// slime time
|
||||
var/decl/chemical_reaction/instant/slime/SR = CR
|
||||
if(!SR.required)
|
||||
continue
|
||||
var/obj/item/slime_extract/E = new SR.required()
|
||||
qdel_swap(fake_beaker, E)
|
||||
fake_beaker.reagents.maximum_volume = 5000
|
||||
else if(istype(CR, /decl/chemical_reaction/distilling))
|
||||
// distilling
|
||||
var/obj/distilling_tester/D = new()
|
||||
qdel_swap(fake_beaker, D)
|
||||
fake_beaker.reagents.maximum_volume = 5000
|
||||
else
|
||||
// regular beaker
|
||||
qdel_swap(fake_beaker, new /obj/item/reagent_containers/glass/beaker())
|
||||
fake_beaker.reagents.maximum_volume = 5000
|
||||
|
||||
// Perform test! If it fails once, it will perform a deeper check trying to use the inhibitors of anything in the beaker
|
||||
RegisterSignal(fake_beaker.reagents, COMSIG_UNITTEST_DATA, PROC_REF(get_signal_data))
|
||||
|
||||
// Check if we failed the test with inhibitors in use, if so we absolutely couldn't make it...
|
||||
// Uncomment the UNIT_TEST section in code\modules\reagents\reactions\_reactions.dm if you require more info
|
||||
TEST_ASSERT(!perform_reaction(CR), "[CR.type]: Reagents - chemical reaction did not produce \"[CR.result]\". CONTAINS: \"[fake_beaker.reagents.get_reagents()]\"")
|
||||
UnregisterSignal(fake_beaker.reagents, COMSIG_UNITTEST_DATA)
|
||||
qdel_null(fake_beaker)
|
||||
#endif
|
||||
|
||||
if(failed)
|
||||
TEST_FAIL("One or more /decl/chemical_reaction subtypes conflict with another reaction.")
|
||||
|
||||
/datum/unit_test/chemical_reactions_shall_not_conflict/proc/perform_reaction(var/decl/chemical_reaction/CR, var/list/inhib = list())
|
||||
var/scale = 1
|
||||
if(CR.result_amount < 1)
|
||||
scale = 1 / CR.result_amount // Create at least 1 unit
|
||||
|
||||
// Weird loop here, but this is used to test both instant and distilling reactions
|
||||
// Instants will meet the while() condition on the first loop and go to the next stuff
|
||||
// but distilling will repeat over and over until the temperature test is finished!
|
||||
var/temp_test = 0
|
||||
do
|
||||
// clear for inhibitor searches
|
||||
fake_beaker.reagents.clear_reagents()
|
||||
result_reactions.Cut()
|
||||
|
||||
if(inhib.len) // taken from argument and not reaction! Put in FIRST!
|
||||
for(var/RR in inhib)
|
||||
fake_beaker.reagents.add_reagent(RR, inhib[RR] * scale)
|
||||
if(CR.catalysts) // Required for reaction
|
||||
for(var/RR in CR.catalysts)
|
||||
fake_beaker.reagents.add_reagent(RR, CR.catalysts[RR] * scale)
|
||||
if(CR.required_reagents)
|
||||
for(var/RR in CR.required_reagents)
|
||||
fake_beaker.reagents.add_reagent(RR, CR.required_reagents[RR] * scale)
|
||||
|
||||
if(!istype(CR, /decl/chemical_reaction/distilling))
|
||||
break // Skip the next section if we're not distilling
|
||||
|
||||
// Check distillation at 10 points along its temperature range!
|
||||
// This is so multiple reactions with the same requirements, but different temps, can be tested.
|
||||
temp_test += 0.1
|
||||
var/obj/distilling_tester/DD = fake_beaker
|
||||
DD.test_distilling(CR,temp_test)
|
||||
|
||||
if(fake_beaker.reagents.has_reagent(CR.result))
|
||||
return FALSE // Distilling success
|
||||
|
||||
while(temp_test > 1)
|
||||
|
||||
// Check beaker to see if we reached our goal!
|
||||
if(fake_beaker.reagents.has_reagent(CR.result))
|
||||
return FALSE // INSTANT SUCCESS!
|
||||
|
||||
if(inhib.len)
|
||||
// We've checked with inhibitors, so we're already in inhibitor checking phase.
|
||||
// So we've absolutely failed this time. There is no way to make this...
|
||||
return TRUE
|
||||
|
||||
if(!result_reactions.len)
|
||||
// Nothing to check for inhibitors...
|
||||
for(var/decl/chemical_reaction/test_react in result_reactions)
|
||||
TEST_FAIL("[CR.type]: Reagents - Used [test_react] but failed.")
|
||||
return TRUE
|
||||
|
||||
// Otherwise we check the resulting reagents and use their inhibitor this time!
|
||||
for(var/decl/chemical_reaction/test_react in result_reactions)
|
||||
if(!test_react)
|
||||
continue
|
||||
if(!test_react.inhibitors.len)
|
||||
continue
|
||||
// Test one by one
|
||||
for(var/each in test_react.inhibitors)
|
||||
if(!perform_reaction(CR, list("[each]" = test_react.inhibitors["[each]"])))
|
||||
return FALSE // SUCCESS using an inhibitor!
|
||||
// Test all at once
|
||||
if(!perform_reaction(CR, test_react.inhibitors))
|
||||
return FALSE // SUCCESS using all inhibitors!
|
||||
|
||||
// No inhibiting reagent worked...
|
||||
for(var/decl/chemical_reaction/test_react in result_reactions)
|
||||
TEST_FAIL("[CR.type]: Reagents - Used [test_react] but failed.")
|
||||
return TRUE
|
||||
|
||||
/datum/unit_test/chemical_reactions_shall_not_conflict/proc/get_signal_data(atom/source, list/data = list())
|
||||
result_reactions.Add(data[1]) // Append the reactions that happened, then use that to check their inhibitors
|
||||
|
||||
/// Test that makes sure that chemical grinding has valid results
|
||||
/datum/unit_test/chemical_grinding_must_produce_valid_results
|
||||
|
||||
/datum/unit_test/chemical_grinding_must_produce_valid_results/Run()
|
||||
for(var/grind in GLOB.sheet_reagents + GLOB.ore_reagents)
|
||||
var/list/results = GLOB.sheet_reagents[grind]
|
||||
|
||||
if(!results)
|
||||
results = GLOB.ore_reagents[grind]
|
||||
|
||||
// Cursed test
|
||||
TEST_ASSERT(!(!results || !islist(results)), "[grind]: Reagents - Grinding result had invalid list.")
|
||||
if(!results || !islist(results))
|
||||
continue
|
||||
|
||||
TEST_ASSERT(results.len, "[grind]: Reagents - Grinding result had empty.")
|
||||
if(!results.len)
|
||||
continue
|
||||
|
||||
for(var/reg_id in results)
|
||||
TEST_ASSERT(SSchemistry.chemical_reagents[reg_id], "[grind]: Reagents - Grinding result had invalid reagent id \"[reg_id]\".")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user