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:
Selis
2025-08-10 01:37:23 +02:00
committed by GitHub
parent 5e54e17ebd
commit f4bf017921
382 changed files with 10193 additions and 4421 deletions
+99 -77
View File
@@ -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"
+5 -1
View File
@@ -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.")
-15
View File
@@ -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,
+2 -2
View File
@@ -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")
+3 -3
View File
@@ -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
View File
@@ -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
+9 -27
View File
@@ -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)
+5 -5
View File
@@ -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))
+2 -2
View File
@@ -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 += "&nbsp;&nbsp;<b>ERROR</b><br>"
continue
+8 -8
View File
@@ -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!
+1 -1
View File
@@ -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.
+151 -1
View File
@@ -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.")
+81
View File
@@ -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.")
+5 -5
View File
@@ -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.
*/
+6 -6
View File
@@ -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 -1
View File
@@ -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
+8
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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))
+54
View File
@@ -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.
+65
View File
@@ -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.
+1 -1
View File
@@ -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!")
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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()
+2 -2
View File
@@ -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()
+1 -1
View File
@@ -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)
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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 = ""
+5 -5
View File
@@ -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)
+3 -3
View File
@@ -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")
+2 -2
View File
@@ -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"))
+1 -1
View File
@@ -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.
+3 -3
View File
@@ -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
+3
View File
@@ -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)
+4 -4
View File
@@ -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)
+10 -10
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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))
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+3 -3
View File
@@ -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)
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -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
+8 -8
View File
@@ -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
+1 -1
View File
@@ -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
+83
View File
@@ -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.
+129
View File
@@ -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")
+171
View File
@@ -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(",")])")
+60
View File
@@ -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
+19
View File
@@ -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)].")
+17
View File
@@ -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'")
+69
View File
@@ -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
+25
View File
@@ -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"))
+7
View File
@@ -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.")
+189
View File
@@ -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")
+17
View File
@@ -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)].")
+68
View File
@@ -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
+13
View File
@@ -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.")
+77
View File
@@ -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")
+251
View File
@@ -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