diff --git a/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm b/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm index a063d88a252..3add952e8e2 100644 --- a/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm +++ b/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm @@ -152,12 +152,7 @@ /turf/simulated/floor/wood, /area/ruin/unpowered) "az" = ( -/obj/item/spellbook/oneuse/charge{ - desc = "For the technology-hating wizard."; - spell = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech; - spellname = "Disable Technology"; - used = 1 - }, +/obj/item/spellbook/oneuse/emp/used, /turf/simulated/floor/wood, /area/ruin/unpowered) "aA" = ( diff --git a/code/__DEFINES/logs.dm b/code/__DEFINES/logs.dm index 25d1fac7323..96fac582283 100644 --- a/code/__DEFINES/logs.dm +++ b/code/__DEFINES/logs.dm @@ -4,3 +4,8 @@ #define SAY_LOG "Say" #define EMOTE_LOG "Emote" #define MISC_LOG "Misc" +#define DEADCHAT_LOG "Deadchat" +#define OOC_LOG "OOC" +#define LOOC_LOG "LOOC" + +#define ALL_LOGS list(ATTACK_LOG, DEFENSE_LOG, CONVERSION_LOG, SAY_LOG, EMOTE_LOG, DEADCHAT_LOG, OOC_LOG, LOOC_LOG, MISC_LOG) diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index bd077204f00..181d2a5140c 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -425,66 +425,8 @@ if(pressure <= LAVALAND_EQUIPMENT_EFFECT_PRESSURE) . = TRUE -proc/pollCandidates(Question, be_special_type, antag_age_check = FALSE, poll_time = 300, ignore_respawnability = FALSE, min_hours = 0, flashwindow = TRUE, check_antaghud = TRUE) - var/roletext = be_special_type ? get_roletext(be_special_type) : null - var/list/mob/dead/observer/candidates = list() - var/time_passed = world.time - if(!Question) - Question = "Would you like to be a special role?" - - for(var/mob/dead/observer/G in (ignore_respawnability ? GLOB.player_list : GLOB.respawnable_list)) - if(!G.key || !G.client) - continue - if(be_special_type) - if(!(be_special_type in G.client.prefs.be_special)) - continue - if(antag_age_check) - if(!player_old_enough_antag(G.client, be_special_type)) - continue - if(roletext) - if(jobban_isbanned(G, roletext) || jobban_isbanned(G, "Syndicate")) - continue - if(config.use_exp_restrictions && min_hours) - if(G.client.get_exp_type_num(EXP_TYPE_LIVING) < min_hours * 60) - continue - if(check_antaghud && cannotPossess(G)) - continue - spawn(0) - G << 'sound/misc/notice2.ogg'//Alerting them to their consideration - if(flashwindow) - window_flash(G.client) - var/ans = alert(G,Question,"Please answer in [poll_time/10] seconds!","No","Yes","Not This Round") - if(!G?.client) - return - switch(ans) - if("Yes") - to_chat(G, "Choice registered: Yes.") - if((world.time-time_passed)>poll_time)//If more than 30 game seconds passed. - to_chat(G, "Sorry, you were too late for the consideration!") - G << 'sound/machines/buzz-sigh.ogg' - return - candidates += G - if("No") - to_chat(G, "Choice registered: No.") - return - if("Not This Round") - to_chat(G, "Choice registered: No.") - to_chat(G, "You will no longer receive notifications for the role '[roletext]' for the rest of the round.") - G.client.prefs.be_special -= be_special_type - return - else - return - sleep(poll_time) - - //Check all our candidates, to make sure they didn't log off during the 30 second wait period. - for(var/mob/dead/observer/G in candidates) - if(!G.key || !G.client) - candidates.Remove(G) - - return candidates - -/proc/pollCandidatesWithVeto(adminclient, adminusr, max_slots, Question, be_special_type, antag_age_check = 0, poll_time = 300, ignore_respawnability = 0, min_hours = 0, flashwindow = TRUE, check_antaghud = TRUE) - var/list/willing_ghosts = pollCandidates(Question, be_special_type, antag_age_check, poll_time, ignore_respawnability, min_hours, flashwindow, check_antaghud) +/proc/pollCandidatesWithVeto(adminclient, adminusr, max_slots, Question, be_special_type, antag_age_check = FALSE, poll_time = 300, ignore_respawnability = FALSE, min_hours = FALSE, flashwindow = TRUE, check_antaghud = TRUE, source) + var/list/willing_ghosts = SSghost_spawns.poll_candidates(Question, be_special_type, antag_age_check, poll_time, ignore_respawnability, min_hours, flashwindow, check_antaghud, source) var/list/selected_ghosts = list() if(!willing_ghosts.len) return selected_ghosts diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index f9b9850bd2d..c1cf5d53520 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -693,6 +693,9 @@ proc/dd_sortedObjectList(list/incoming) // Lazying Episode 3 #define LAZYSET(L, K, V) LAZYINITLIST(L); L[K] = V; +/// Returns whether a numerical index is within a given list's bounds. Faster than isnull(LAZYACCESS(L, I)). +#define ISINDEXSAFE(L, I) (I >= 1 && I <= length(L)) + //same, but returns nothing and acts on list in place /proc/shuffle_inplace(list/L) if(!L) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index d6caa29f670..fc0f7520e0e 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1919,16 +1919,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) return pois -/* - * Returns all player mobs in an assoc list with ckey as key and the mob as value -*/ -/proc/get_assoc_mob_list_by_ckey() - var/list/mob/mobs = list() - for(var/i in GLOB.player_list) - var/mob/M = i - mobs[M.ckey] = M - return mobs - /proc/flash_color(mob_or_client, flash_color="#960000", flash_time=20) var/client/C if(istype(mob_or_client, /mob)) diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index f5f71e65c57..50e70bc62a4 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -34,4 +34,6 @@ GLOBAL_PROTECT(IClog) GLOBAL_LIST_EMPTY(OOClog) GLOBAL_PROTECT(OOClog) +GLOBAL_DATUM_INIT(logging, /datum/logging, new /datum/logging()) + GLOBAL_LIST_INIT(investigate_log_subjects, list("notes", "watchlist", "hrefs")) diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index a93e65c762b..531f510e3cb 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -3,7 +3,7 @@ //PUBLIC - call these wherever you want -/mob/proc/throw_alert(category, type, severity, obj/new_master, override = FALSE) +/mob/proc/throw_alert(category, type, severity, obj/new_master, override = FALSE, timeout_override, no_anim) /* Proc to create or update an alert. Returns the alert if the alert is new or updated, 0 if it was thrown already @@ -59,14 +59,16 @@ LAZYSET(alerts, category, alert) // This also creates the list if it doesn't exist if(client && hud_used) hud_used.reorganize_alerts() - alert.transform = matrix(32, 6, MATRIX_TRANSLATE) - animate(alert, transform = matrix(), time = 2.5, easing = CUBIC_EASING) - if(alert.timeout) - spawn(alert.timeout) - if(alert.timeout && alerts[category] == alert && world.time >= alert.timeout) - clear_alert(category) - alert.timeout = world.time + alert.timeout - world.tick_lag + if(!no_anim) + alert.transform = matrix(32, 6, MATRIX_TRANSLATE) + animate(alert, transform = matrix(), time = 2.5, easing = CUBIC_EASING) + + var/timeout = timeout_override || alert.timeout + if(timeout) + addtimer(CALLBACK(alert, /obj/screen/alert/.proc/do_timeout, src, category), timeout) + alert.timeout = world.time + timeout - world.tick_lag + return alert // Proc to clear an existing alert. @@ -94,7 +96,6 @@ var/alerttooltipstyle = "" var/override_alerts = FALSE //If it is overriding other alerts of the same type - /obj/screen/alert/MouseEntered(location,control,params) openToolTip(usr, src, params, title = name, content = desc, theme = alerttooltipstyle) @@ -102,6 +103,12 @@ /obj/screen/alert/MouseExited() closeToolTip(usr) +/obj/screen/alert/proc/do_timeout(mob/M, category) + if(!M || !M.alerts) + return + + if(timeout && M.alerts[category] == src && world.time >= timeout) + M.clear_alert(category) //Gas alerts /obj/screen/alert/not_enough_oxy @@ -507,6 +514,34 @@ so as to remain in compliance with the most up-to-date laws." timeout = 300 var/atom/target = null var/action = NOTIFY_JUMP + var/show_time_left = FALSE // If true you need to call START_PROCESSING manually + var/image/time_left_overlay // The last image showing the time left + var/datum/candidate_poll/poll // If set, on Click() it'll register the player as a candidate + +/obj/screen/alert/notify_action/process() + if(show_time_left) + var/timeleft = timeout - world.time + if(timeleft <= 0) + return PROCESS_KILL + + if(time_left_overlay) + overlays -= time_left_overlay + + var/obj/O = new + O.maptext = "[CEILING(timeleft / 10, 1)]" + O.maptext_width = O.maptext_height = 128 + var/matrix/M = new + M.Translate(4, 16) + O.transform = M + + var/image/I = image(O) + I.layer = FLOAT_LAYER + I.plane = FLOAT_PLANE + 1 + overlays += I + + time_left_overlay = I + qdel(O) + ..() /obj/screen/alert/notify_action/Destroy() target = null @@ -515,20 +550,48 @@ so as to remain in compliance with the most up-to-date laws." /obj/screen/alert/notify_action/Click() if(!usr || !usr.client) return - if(!target) - return var/mob/dead/observer/G = usr if(!istype(G)) return - switch(action) - if(NOTIFY_ATTACK) - target.attack_ghost(G) - if(NOTIFY_JUMP) - var/turf/T = get_turf(target) - if(T && isturf(T)) - G.loc = T - if(NOTIFY_FOLLOW) - G.ManualFollow(target) + + if(poll) + if(poll.sign_up(G)) + // Add a small overlay to indicate we've signed up + display_signed_up() + else if(target) + switch(action) + if(NOTIFY_ATTACK) + target.attack_ghost(G) + if(NOTIFY_JUMP) + var/turf/T = get_turf(target) + if(T && isturf(T)) + G.loc = T + if(NOTIFY_FOLLOW) + G.ManualFollow(target) + +/obj/screen/alert/notify_action/proc/display_signed_up() + var/image/I = image('icons/mob/screen_gen.dmi', icon_state = "selector") + I.layer = FLOAT_LAYER + I.plane = FLOAT_PLANE + 2 + overlays += I + +/obj/screen/alert/notify_action/proc/display_stacks(stacks = 1) + if(stacks <= 1) + return + + var/obj/O = new + O.maptext = "[stacks]x" + O.maptext_width = O.maptext_height = 128 + var/matrix/M = new + M.Translate(4, 2) + O.transform = M + + var/image/I = image(O) + I.layer = FLOAT_LAYER + I.plane = FLOAT_PLANE + 1 + overlays += I + + qdel(O) /obj/screen/alert/notify_soulstone name = "Soul Stone" diff --git a/code/controllers/subsystem/ghost_spawns.dm b/code/controllers/subsystem/ghost_spawns.dm new file mode 100644 index 00000000000..6063d6995e5 --- /dev/null +++ b/code/controllers/subsystem/ghost_spawns.dm @@ -0,0 +1,268 @@ +SUBSYSTEM_DEF(ghost_spawns) + name = "Ghost Spawns" + init_order = INIT_ORDER_EVENTS + flags = SS_BACKGROUND + wait = 1 SECONDS + runlevels = RUNLEVEL_GAME + offline_implications = "Ghosts will no longer be able to respawn as event mobs (Blob, etc..). Shuttle call recommended." + + /// List of polls currently ongoing, to be checked on next fire() + var/list/datum/candidate_poll/currently_polling + /// Whether there are active polls or not + var/polls_active = FALSE + /// Number of polls performed since the start + var/total_polls = 0 + /// The poll that's closest to finishing + var/datum/candidate_poll/next_poll_to_finish + +/datum/controller/subsystem/ghost_spawns/fire() + if(!polls_active) + return + if(!currently_polling) // if polls_active is TRUE then this shouldn't happen, but still.. + currently_polling = list() + + for(var/poll in currently_polling) + var/datum/candidate_poll/P = poll + if(P.time_left() <= 0) + polling_finished(P) + +/** + * Polls for candidates with a question and a preview of the role + * + * This proc replaces /proc/pollCandidates. + * Should NEVER be used in a proc that has waitfor set to FALSE/0 (due to #define UNTIL) + * Arguments: + * * question - The question to ask to potential candidates + * * role - The role to poll for. Should be a ROLE_x enum. If set, potential candidates who aren't eligible will be ignored + * * antag_age_check - Whether to filter out potential candidates who don't have an old enough account + * * poll_time - How long to poll for in deciseconds + * * ignore_respawnability - Whether to ignore the player's respawnability + * * min_hours - The amount of hours needed for a potential candidate to be eligible + * * flash_window - Whether the poll should flash a potential candidate's game window + * * check_antaghud - Whether to filter out potential candidates who enabled AntagHUD + * * source - The atom, atom prototype, icon or mutable appearance to display as an icon in the alert + */ +/datum/controller/subsystem/ghost_spawns/proc/poll_candidates(question = "Would you like to play a special role?", role, antag_age_check = FALSE, poll_time = 30 SECONDS, ignore_respawnability = FALSE, min_hours = 0, flash_window = TRUE, check_antaghud = TRUE, source) + log_debug("Polling candidates [role ? "for [get_roletext(role)]" : "\"[question]\""] for [poll_time / 10] seconds") + + // Start firing + polls_active = TRUE + total_polls++ + + var/datum/candidate_poll/P = new(role, question, poll_time) + LAZYADD(currently_polling, P) + + // We're the poll closest to completion + if(!next_poll_to_finish || poll_time < next_poll_to_finish.time_left()) + next_poll_to_finish = P + + var/category = "[P.hash]_notify_action" + + for(var/mob/dead/observer/M in (ignore_respawnability ? GLOB.player_list : GLOB.respawnable_list)) + if(!is_eligible(M)) + continue + + SEND_SOUND(M, 'sound/misc/notice2.ogg') + if(flash_window) + window_flash(M.client) + + // If we somehow send two polls for the same mob type, but with a duration on the second one shorter than the time left on the first one, + // we need to keep the first one's timeout rather than use the shorter one + var/obj/screen/alert/notify_action/current_alert = LAZYACCESS(M.alerts, category) + var/alert_time = poll_time + var/alert_poll = P + if(current_alert && current_alert.timeout > (world.time + poll_time - world.tick_lag)) + alert_time = current_alert.timeout - world.time + world.tick_lag + alert_poll = current_alert.poll + + // Send them an on-screen alert + var/obj/screen/alert/notify_action/A = M.throw_alert(category, /obj/screen/alert/notify_action, timeout_override = alert_time, no_anim = TRUE) + if(!A) + continue + + A.icon = ui_style2icon(M.client?.prefs.UI_style) + A.name = "Looking for candidates" + A.desc = "[question]\n\n(expires in [poll_time / 10] seconds)" + A.show_time_left = TRUE + A.poll = alert_poll + + // Sign up inheritance and stacking + var/inherited_sign_up = FALSE + var/num_stack = 1 + for(var/existing_poll in currently_polling) + var/datum/candidate_poll/P2 = existing_poll + if(P != P2 && P.hash == P2.hash) + // If there's already a poll for an identical mob type ongoing and the client is signed up for it, sign them up for this one + if(!inherited_sign_up && (M in P2.signed_up) && P.sign_up(M, TRUE)) + A.display_signed_up() + inherited_sign_up = TRUE + // This number is used to display the number of polls the alert regroups + num_stack++ + if(num_stack > 1) + A.display_stacks(num_stack) + + // Image to display + var/image/I + if(source) + if(!ispath(source)) + var/atom/S = source + var/old_layer = S.layer + var/old_plane = S.plane + + S.layer = FLOAT_LAYER + S.plane = FLOAT_PLANE + A.overlays += S + S.layer = old_layer + S.plane = old_plane + else + I = image(source, layer = FLOAT_LAYER, dir = SOUTH) + else + // Just use a generic image + I = image('icons/effects/effects.dmi', icon_state = "static", layer = FLOAT_LAYER, dir = SOUTH) + + if(I) + I.layer = FLOAT_LAYER + I.plane = FLOAT_PLANE + A.overlays += I + + // Start processing it so it updates visually the timer + START_PROCESSING(SSprocessing, A) + A.process() + + // Sleep until the time is up + UNTIL(P.finished) + return P.signed_up + +/** + * Returns whether an observer is eligible to be an event mob + * + * Arguments: + * * M - The mob to check eligibility + * * role - The role to check eligibility for. Checks 1. the client has enabled the role 2. the account's age for this role if antag_age_check is TRUE + * * antag_age_check - Whether to check the account's age or not for the given role. + * * role_text - The role's clean text. Used for checking job bans to determine eligibility + * * min_hours - The amount of minimum hours the client needs before being eligible + * * check_antaghud - Whether to consider a client who enabled AntagHUD ineligible or not + */ +/datum/controller/subsystem/ghost_spawns/proc/is_eligible(mob/M, role, antag_age_check, role_text, min_hours, check_antaghud) + . = FALSE + if(!M.key || !M.client) + return + if(role) + if(!(role in M.client.prefs.be_special)) + return + if(antag_age_check) + if(!player_old_enough_antag(M.client, role)) + return + if(role_text) + if(jobban_isbanned(M, role_text) || jobban_isbanned(M, "Syndicate")) + return + if(config.use_exp_restrictions && min_hours) + if(M.client.get_exp_type_num(EXP_TYPE_LIVING) < min_hours * 60) + return + if(check_antaghud && cannotPossess(M)) + return + + return TRUE + +/** + * Called by the subsystem when a poll's timer runs out + * + * Can be called manually to finish a poll prematurely + * Arguments: + * * P - The poll to finish + */ +/datum/controller/subsystem/ghost_spawns/proc/polling_finished(datum/candidate_poll/P) + // Trim players who aren't eligible anymore + var/len_pre_trim = length(P.signed_up) + P.trim_candidates() + log_debug("Candidate poll [P.role ? "for [get_roletext(P.role)]" : "\"[P.question]\""] finished. [len_pre_trim] players signed up, [length(P.signed_up)] after trimming") + + P.finished = TRUE + currently_polling -= P + + // Determine which is the next poll closest the completion or "disable" firing if there's none + if(!length(currently_polling)) + polls_active = FALSE + next_poll_to_finish = null + else if(P == next_poll_to_finish) + next_poll_to_finish = null + for(var/poll in currently_polling) + var/datum/candidate_poll/P2 = poll + if(!next_poll_to_finish || P2.time_left() < next_poll_to_finish.time_left()) + next_poll_to_finish = P2 + +/datum/controller/subsystem/ghost_spawns/stat_entry(msg) + msg += "Active: [length(currently_polling)] | Total: [total_polls]" + if(next_poll_to_finish) + msg += " | Next: [DisplayTimeText(next_poll_to_finish.time_left())] ([length(next_poll_to_finish.signed_up)] candidates)" + ..(msg) + +// The datum that describes one instance of candidate polling +/datum/candidate_poll + var/role // The role the poll is for + var/question // The question asked to observers + var/duration // The duration of the poll + var/list/mob/dead/observer/signed_up // The players who signed up to this poll + var/time_started // The world.time at which the poll was created + var/finished = FALSE // Whether the polling is finished + var/hash // Used to categorize in the alerts system + +/datum/candidate_poll/New(polled_role, polled_question, poll_duration) + role = polled_role + question = polled_question + duration = poll_duration + signed_up = list() + time_started = world.time + hash = copytext(md5("[question]_[role ? role : "0"]"), 1, 7) + return ..() + +/** + * Attempts to sign a (controlled) mob up + * + * Will fail if the mob is already signed up or the poll's timer ran out. + * Does not check for eligibility + * Arguments: + * * M - The (controlled) mob to sign up + * * silent - Whether no messages should appear or not. If not TRUE, signing up to this poll will also sign the mob up for identical polls + */ +/datum/candidate_poll/proc/sign_up(mob/dead/observer/M, silent = FALSE) + . = FALSE + if(!istype(M) || !M.key || !M.client) + return + if(M in signed_up) + if(!silent) + to_chat(M, "You have already signed up for this!") + return + if(time_left() <= 0) + if(!silent) + to_chat(M, "Sorry, you were too late for the consideration!") + SEND_SOUND(M, 'sound/machines/buzz-sigh.ogg') + return + + signed_up += M + if(!silent) + to_chat(M, "You have signed up for this role! A candidate will be picked randomly soon..") + // Sign them up for any other polls with the same mob type + for(var/existing_poll in SSghost_spawns.currently_polling) + var/datum/candidate_poll/P = existing_poll + if(src != P && hash == P.hash && !(M in P.signed_up)) + P.sign_up(M, TRUE) + + return TRUE + +/** + * Deletes any candidates who may have disconnected from the list + */ +/datum/candidate_poll/proc/trim_candidates() + listclearnulls(signed_up) + for(var/mob in signed_up) + var/mob/M = mob + if(!M.key || !M.client) + signed_up -= M + +/** + * Returns the time left for a poll + */ +/datum/candidate_poll/proc/time_left() + return duration - (world.time - time_started) diff --git a/code/controllers/subsystem/jobs.dm b/code/controllers/subsystem/jobs.dm index ef89317d0f5..3adfdf3972f 100644 --- a/code/controllers/subsystem/jobs.dm +++ b/code/controllers/subsystem/jobs.dm @@ -27,7 +27,7 @@ SUBSYSTEM_DEF(jobs) /datum/controller/subsystem/jobs/fire() if(!config.sql_enabled || !config.use_exp_tracking) return - update_exp(5,0) + INVOKE_ASYNC(GLOBAL_PROC, /.proc/update_exp, 5, 0) /datum/controller/subsystem/jobs/proc/SetupOccupations(var/list/faction = list("Station")) occupations = list() @@ -644,6 +644,27 @@ SUBSYSTEM_DEF(jobs) oldjobdatum.current_positions-- newjobdatum.current_positions++ +/datum/controller/subsystem/jobs/proc/notify_dept_head(jobtitle, antext) + // Used to notify the department head of jobtitle X that their employee was brigged, demoted or terminated + if(!jobtitle || !antext) + return + var/datum/job/tgt_job = GetJob(jobtitle) + if(!tgt_job) + return + if(!tgt_job.department_head[1]) + return + var/boss_title = tgt_job.department_head[1] + var/obj/item/pda/target_pda + for(var/obj/item/pda/check_pda in GLOB.PDAs) + if(check_pda.ownrank == boss_title) + target_pda = check_pda + break + if(!target_pda) + return + var/datum/data/pda/app/messenger/PM = target_pda.find_program(/datum/data/pda/app/messenger) + if(PM && PM.can_receive()) + PM.notify("Automated Notification: \"[antext]\" (Unable to Reply)") + /datum/controller/subsystem/jobs/proc/fetch_transfer_record_html(var/centcom) var/record_html = "" diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 5d0a53c221e..e53c91ac20d 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -61,7 +61,7 @@ SUBSYSTEM_DEF(ticker) if(GAME_STATE_STARTUP) // This is ran as soon as the MC starts firing, and should only run ONCE, unless startup fails round_start_time = world.time + (config.pregame_timestart * 10) - to_chat(world, "Welcome to the pre-game lobby!") + to_chat(world, "Welcome to the pre-game lobby!") to_chat(world, "Please, setup your character and select ready. Game will start in [config.pregame_timestart] seconds") current_state = GAME_STATE_PREGAME fire() // TG says this is a good idea @@ -217,11 +217,11 @@ SUBSYSTEM_DEF(ticker) for(var/obj/effect/landmark/spacepod/random/R in L) qdel(R) - to_chat(world, "Enjoy the game!") + to_chat(world, "Enjoy the game!") world << sound('sound/AI/welcome.ogg')// Skie if(SSholiday.holidays) - to_chat(world, "and...") + to_chat(world, "and...") for(var/holidayname in SSholiday.holidays) var/datum/holiday/holiday = SSholiday.holidays[holidayname] to_chat(world, "

[holiday.greet()]

") diff --git a/code/controllers/subsystem/tickets/mentor_tickets.dm b/code/controllers/subsystem/tickets/mentor_tickets.dm index 3fd003418a4..af4ea2e914d 100644 --- a/code/controllers/subsystem/tickets/mentor_tickets.dm +++ b/code/controllers/subsystem/tickets/mentor_tickets.dm @@ -6,17 +6,23 @@ GLOBAL_REAL(SSmentor_tickets, /datum/controller/subsystem/tickets/mentor_tickets /datum/controller/subsystem/tickets/mentor_tickets name = "Mentor Tickets" + offline_implications = "Mentor tickets will no longer be marked as stale. No immediate action is needed." ticket_system_name = "Mentor Tickets" ticket_name = "Mentor Ticket" span_class = "mentorhelp" + other_ticket_name = "Admin" + other_ticket_permission = R_ADMIN close_rights = R_MENTOR | R_ADMIN - offline_implications = "Mentor tickets will no longer be marked as stale. No immediate action is needed." - -/datum/controller/subsystem/tickets/mentor_tickets/message_staff(var/msg) - message_mentorTicket(msg) + rights_needed = R_MENTOR | R_ADMIN | R_MOD /datum/controller/subsystem/tickets/mentor_tickets/Initialize() close_messages = list("- [ticket_name] Closed -", "Please try to be as descriptive as possible in mentor helps. Mentors do not know the full situation you're in and need more information to give you a helpful response.", "Your [ticket_name] has now been closed.") return ..() + +/datum/controller/subsystem/tickets/mentor_tickets/message_staff(msg) + message_mentorTicket(msg) + +/datum/controller/subsystem/tickets/mentor_tickets/create_other_system_ticket(datum/ticket/T) + SStickets.newTicket(T.clientName, T.content, T.title) diff --git a/code/controllers/subsystem/tickets/tickets.dm b/code/controllers/subsystem/tickets/tickets.dm index 879ee35abee..64f91e96b6c 100644 --- a/code/controllers/subsystem/tickets/tickets.dm +++ b/code/controllers/subsystem/tickets/tickets.dm @@ -12,18 +12,23 @@ SUBSYSTEM_DEF(tickets) name = "Admin Tickets" - var/span_class = "adminticket" - var/ticket_system_name = "Admin Tickets" - var/ticket_name = "Admin Ticket" - var/close_rights = R_ADMIN - var/list/close_messages init_order = INIT_ORDER_TICKETS wait = 300 priority = FIRE_PRIORITY_TICKETS offline_implications = "Admin tickets will no longer be marked as stale. No immediate action is needed." - flags = SS_BACKGROUND + var/span_class = "adminticket" + var/ticket_system_name = "Admin Tickets" + var/ticket_name = "Admin Ticket" + var/close_rights = R_ADMIN + var/rights_needed = R_ADMIN | R_MOD + + /// The name of the other ticket type to convert to + var/other_ticket_name = "Mentor" + /// Which permission to look for when seeing if there is staff available for the other ticket type + var/other_ticket_permission = R_MENTOR + var/list/close_messages var/list/allTickets = list() //make it here because someone might ahelp before the system has initialized var/ticketCounter = 1 @@ -114,9 +119,37 @@ SUBSYSTEM_DEF(tickets) to_chat_safe(returnClient(N), "Your [ticket_name] has now been resolved.") return TRUE +/datum/controller/subsystem/tickets/proc/convert_to_other_ticket(ticketId) + if(!check_rights(rights_needed)) + return + if(alert("Are you sure to convert this ticket to an '[other_ticket_name]' ticket?",,"Yes","No") != "Yes") + return + if(!other_ticket_system_staff_check()) + return + var/datum/ticket/T = allTickets[ticketId] + convert_ticket(T) + +/datum/controller/subsystem/tickets/proc/other_ticket_system_staff_check() + var/list/staff = staff_countup(other_ticket_permission) + if(!staff[1]) + if(alert("No active staff online to answer the ticket. Are you sure you want to convert the ticket?",, "No", "Yes") != "Yes") + return FALSE + return TRUE + +/datum/controller/subsystem/tickets/proc/convert_ticket(datum/ticket/T) + T.ticketState = TICKET_CLOSED + var/client/C = usr.client + to_chat_safe(T.clientName, list("[key_name_hidden(C)] has converted your ticket to a [other_ticket_name] ticket.",\ + "Be sure to use the correct type of help next time!")) + message_staff("[C] has converted ticket number [T.ticketNum] to a [other_ticket_name] ticket.") + log_game("[C] has converted ticket number [T.ticketNum] to a [other_ticket_name] ticket.") + create_other_system_ticket(T) + +/datum/controller/subsystem/tickets/proc/create_other_system_ticket(datum/ticket/T) + SSmentor_tickets.newTicket(T.clientName, T.content, T.title) /datum/controller/subsystem/tickets/proc/autoRespond(N) - if(!check_rights(R_ADMIN|R_MOD)) + if(!check_rights(rights_needed)) return var/datum/ticket/T = allTickets[N] @@ -158,14 +191,17 @@ SUBSYSTEM_DEF(tickets) resolveTicket(N) message_staff("[C] has auto responded to [T.clientName]\'s adminhelp with: [message_key] ") log_game("[C] has auto responded to [T.clientName]\'s adminhelp with: [response_phrases[message_key]]") + if("Mentorhelp") + convert_ticket(T) else var/msg_sound = sound('sound/effects/adminhelp.ogg') SEND_SOUND(returnClient(N), msg_sound) - to_chat(returnClient(N), "[key_name_hidden(C)] is autoresponding with: [response_phrases[message_key]]")//for this we want the full value of whatever key this is to tell the player so we do response_phrases[message_key] + to_chat_safe(returnClient(N), "[key_name_hidden(C)] is autoresponding with: [response_phrases[message_key]]")//for this we want the full value of whatever key this is to tell the player so we do response_phrases[message_key] message_staff("[C] has auto responded to [T.clientName]\'s adminhelp with: [message_key] ") //we want to use the short named keys for this instead of the full sentence which is why we just do message_key T.lastStaffResponse = "Autoresponse: [message_key]" resolveTicket(N) log_game("[C] has auto responded to [T.clientName]\'s adminhelp with: [response_phrases[message_key]]") + //Set ticket state with key N to closed /datum/controller/subsystem/tickets/proc/closeTicket(N) var/datum/ticket/T = allTickets[N] @@ -353,7 +389,7 @@ UI STUFF dat += "" dat += "
[T.content[i]]


" - dat += "Re-Open[check_rights(R_ADMIN|R_MOD, 0) ? "Auto": ""]Resolve

" + dat += "Re-Open[check_rights(rights_needed, 0) ? "Auto": ""]Resolve

" if(!T.staffAssigned) dat += "No staff member assigned to this [ticket_name] - Take Ticket
" @@ -368,6 +404,7 @@ UI STUFF dat += "

" dat += "Close Ticket" + dat += "Convert Ticket" var/datum/browser/popup = new(user, "[ticket_system_name]detail", "[ticket_system_name] #[T.ticketNum]", 1000, 600) popup.set_content(dat) @@ -450,7 +487,6 @@ UI STUFF if(closeTicket(indexNum)) showDetailUI(usr, indexNum) - if(href_list["detailreopen"]) var/indexNum = text2num(href_list["detailreopen"]) if(openTicket(indexNum)) @@ -470,6 +506,10 @@ UI STUFF var/indexNum = text2num(href_list["autorespond"]) autoRespond(indexNum) + if(href_list["convert_ticket"]) + var/indexNum = text2num(href_list["convert_ticket"]) + convert_to_other_ticket(indexNum) + if(href_list["resolveall"]) if(ticket_system_name == "Mentor Tickets") usr.client.resolveAllMentorTickets() diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm index 3eaed85c303..abb0b604069 100644 --- a/code/controllers/verbs.dm +++ b/code/controllers/verbs.dm @@ -7,7 +7,7 @@ set name = "Restart Controller" set desc = "Restart one of the various periodic loop controllers for the game (be careful!)" - if(!holder) + if(!check_rights(R_DEBUG)) return switch(controller) if("Master") @@ -26,7 +26,8 @@ set name = "Debug Controller" set desc = "Debug the various periodic loop controllers for the game (be careful!)" - if(!holder) return + if(!check_rights(R_DEBUG)) + return switch(controller) if("failsafe") debug_variables(Failsafe) diff --git a/code/datums/log_viewer.dm b/code/datums/log_viewer.dm index 72872e93a3c..5f7084a738b 100644 --- a/code/datums/log_viewer.dm +++ b/code/datums/log_viewer.dm @@ -1,16 +1,23 @@ -#define ALL_LOGS list(ATTACK_LOG, DEFENSE_LOG, CONVERSION_LOG, SAY_LOG, EMOTE_LOG, MISC_LOG) +#define UPDATE_CKEY_MOB(__ckey) var/mob/result = selected_ckeys_mobs[__ckey];\ +if(!result || result.ckey != __ckey){\ + result = get_mob_by_ckey(__ckey);\ + selected_ckeys_mobs[__ckey] = result;\ +} /datum/log_viewer var/time_from = 0 var/time_to = 4 HOURS // 4 Hours should be enough. INFINITY would screw the UI up - var/list/selected_mobs = list() // The mobs in question + var/list/selected_mobs = list() // The mobs in question. + var/list/selected_ckeys = list() // The ckeys selected to search for. Will show all mobs the ckey is attached to + var/list/mob/selected_ckeys_mobs = list() var/list/selected_log_types = ALL_LOGS // The log types being searched for - var/list/log_records = list() // Found and sorted records /datum/log_viewer/proc/clear_all() selected_mobs.Cut() selected_log_types = ALL_LOGS + selected_ckeys.Cut() + selected_ckeys_mobs.Cut() time_from = initial(time_from) time_to = initial(time_to) log_records.Cut() @@ -19,13 +26,17 @@ /datum/log_viewer/proc/search() log_records.Cut() // Empty the old results var/list/invalid_mobs = list() + var/list/ckeys = selected_ckeys.Copy() for(var/i in selected_mobs) var/mob/M = i - if(!M || QDELETED(M)) + if(!M || QDELETED(M) || !M.last_known_ckey) invalid_mobs |= M continue + ckeys |= M.last_known_ckey + + for(var/ckey in ckeys) for(var/log_type in selected_log_types) - var/list/logs = M.logs[log_type] + var/list/logs = GLOB.logging.get_logs_by_type(ckey, log_type) var/len_logs = length(logs) if(len_logs) var/start_index = get_earliest_log_index(logs) @@ -95,13 +106,19 @@ if(!mobs?.len) return for(var/i in mobs) - var/mob/M = i - if(istype(M)) - selected_mobs |= M + add_mob(usr, i, FALSE) -/datum/log_viewer/proc/add_mob(mob/user, mob/M) +/datum/log_viewer/proc/add_ckey(mob/user, ckey) + if(!user || !user) + return + selected_ckeys |= ckey + UPDATE_CKEY_MOB(ckey) + show_ui(user) + +/datum/log_viewer/proc/add_mob(mob/user, mob/M, show_the_ui = TRUE) if(!M || !user) return + selected_mobs |= M show_ui(user) @@ -112,7 +129,7 @@ var/trStyle = "border-top:1px solid; border-bottom:1px solid; padding-top: 5px; padding-bottom: 5px;" var/dat dat += "" - dat += "
" + dat += "
" dat += "Time Search Range: [gameTimestamp(wtime = time_from)]" dat += " To: [gameTimestamp(wtime = time_to)]" dat += "
" @@ -125,19 +142,24 @@ continue dat += "[get_display_name(M)]" dat += "Add Mob" - dat += "Add Mob (by ckey)" dat += "Clear All Mobs" dat += "
" + dat += "Ckeys being used:" + for(var/ckey in selected_ckeys) + dat += "[get_ckey_name(ckey)]" + dat += "Add ckey" + dat += "Clear All ckeys" + dat += "
" + dat += "Log Types:" - for(var/i in all_log_types) - var/log_type = i + for(var/log_type in all_log_types) var/enabled = (log_type in selected_log_types) var/text var/style if(enabled) text = "[log_type]" - style = "background: [get_logtype_color(i)]" + style = "background: [get_logtype_color(log_type)]" else text = log_type @@ -153,7 +175,7 @@ var/tdStyleType = "width:80px; text-align:center;" var/tdStyleWho = "width:400px; text-align:center;" var/tdStyleWhere = "width:150px; text-align:center;" - dat += "
" + dat += "
" dat += "" dat += "" for(var/i in log_records) @@ -207,15 +229,20 @@ selected_mobs.Cut() show_ui(usr) return + if(href_list["clear_ckeys"]) + selected_ckeys.Cut() + selected_ckeys_mobs.Cut() + show_ui(usr) + return if(href_list["add_mob"]) var/list/mobs = getpois(TRUE, TRUE) var/datum/async_input/A = input_autocomplete_async(usr, "Please, select a mob: ", mobs) A.on_close(CALLBACK(src, .proc/add_mob, usr)) return - if(href_list["add_mob_ckey"]) - var/list/mobs = get_assoc_mob_list_by_ckey() - var/datum/async_input/A = input_autocomplete_async(usr, "Please, select a ckey: ", mobs) - A.on_close(CALLBACK(src, .proc/add_mob, usr)) + if(href_list["add_ckey"]) + var/list/ckeys = GLOB.logging.get_ckeys_logged() + var/datum/async_input/A = input_autocomplete_async(usr, "Please, select a ckey: ", ckeys) + A.on_close(CALLBACK(src, .proc/add_ckey, usr)) return if(href_list["remove_mob"]) var/mob/M = locate(href_list["remove_mob"]) @@ -223,6 +250,10 @@ selected_mobs -= M show_ui(usr) return + if(href_list["remove_ckey"]) + selected_ckeys -= href_list["remove_ckey"] + show_ui(usr) + return if(href_list["toggle_log_type"]) var/log_type = href_list["toggle_log_type"] if(log_type in selected_log_types) @@ -246,6 +277,12 @@ return "deepskyblue" if(MISC_LOG) return "gray" + if(DEADCHAT_LOG) + return "#cc00c6" + if(OOC_LOG) + return "#002eb8" + if(LOOC_LOG) + return "#6699CC" return "slategray" /datum/log_viewer/proc/get_display_name(mob/M) @@ -254,4 +291,12 @@ name = "[name] ([M.real_name])" if(isobserver(M)) name = "[name] (DEAD)" - return name + return "\[[M.last_known_ckey]\] [name]" + +/datum/log_viewer/proc/get_ckey_name(ckey) + UPDATE_CKEY_MOB(ckey) + var/mob/M = selected_ckeys_mobs[ckey] + + return get_display_name(M) + +#undef UPDATE_CKEY_MOB diff --git a/code/datums/logging.dm b/code/datums/logging.dm new file mode 100644 index 00000000000..bac26610e5e --- /dev/null +++ b/code/datums/logging.dm @@ -0,0 +1,47 @@ +/datum/logging + var/list/datum/log_record/logs = list() // Assoc list of assoc lists (ckey, (log_type, list/logs)) + +/datum/logging/proc/add_log(ckey, datum/log_record/log) + if(!ckey) + log_debug("GLOB.logging.add_log called with an invalid ckey") + return + + if(!logs[ckey]) + logs[ckey] = list() + + var/list/log_types_list = logs[ckey] + + if(!log_types_list[log.log_type]) + log_types_list[log.log_type] = list() + + var/list/datum/log_record/log_records = log_types_list[log.log_type] + log_records.Add(log) + +/datum/logging/proc/get_ckeys_logged() + var/list/ckeys = list() + for(var/ckey in logs) + ckeys.Add(ckey) + return ckeys + +/* Returns the logs of a given ckey and log_type + * If no logs exist it will return an empty list +*/ +/datum/logging/proc/get_logs_by_type(ckey, log_type) + if(!ckey) + log_debug("GLOB.logging.get_logs_by_type called with an invalid ckey") + return + if(!log_type || !(log_type in ALL_LOGS)) + log_debug("GLOB.logging.get_logs_by_type called with an invalid log_type '[log_type]'") + return + + var/list/log_types_list = logs[ckey] + // Check if logs exist for the ckey + if(!length(log_types_list)) + return list() + + var/list/datum/log_record/log_records = log_types_list[log_type] + + // Check if logs exist for this type + if(!log_records) + return list() + return log_records diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 103865bee94..0c992bbb32c 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -100,14 +100,6 @@ current.mind = null leave_all_huds() //leave all the huds in the old body, so it won't get huds if somebody else enters it - for(var/log_type in current.logs) // Copy the old logs - var/list/logs = current.logs[log_type] - if(new_character.logs[log_type]) - new_character.logs[log_type] += logs.Copy() // Append the old ones - new_character.logs[log_type] = sortTim(new_character.logs[log_type], /proc/compare_log_record) // Sort them on time - else - new_character.logs[log_type] = logs.Copy() // Just copy them - SSnanoui.user_transferred(current, new_character) if(new_character.mind) //remove any mind currently in our new body's mind variable diff --git a/code/datums/outfits/outfit_admin.dm b/code/datums/outfits/outfit_admin.dm index 14bc959fbb1..2b48d5da5f7 100644 --- a/code/datums/outfits/outfit_admin.dm +++ b/code/datums/outfits/outfit_admin.dm @@ -222,10 +222,10 @@ name = "NT Undercover Operative" // Disguised NT special forces, sent to quietly eliminate or keep tabs on people in high positions (e.g: captain) - uniform = /obj/item/clothing/under/color/black + uniform = /obj/item/clothing/under/color/random back = /obj/item/storage/backpack belt = /obj/item/storage/belt/utility/full/multitool - gloves = /obj/item/clothing/gloves/combat + gloves = /obj/item/clothing/gloves/color/yellow shoes = /obj/item/clothing/shoes/chameleon/noslip l_ear = /obj/item/radio/headset/centcom id = /obj/item/card/id diff --git a/code/datums/spell.dm b/code/datums/spell.dm index 3855dbcfac3..cb10700e5c6 100644 --- a/code/datums/spell.dm +++ b/code/datums/spell.dm @@ -444,6 +444,11 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) return +// Normally, AoE spells will generate an attack log for every turf they loop over, while searching for targets. +// With this override, all /aoe_turf type spells will only generate 1 log, saying that the user has cast the spell. +/obj/effect/proc_holder/spell/aoe_turf/perform(list/targets, recharge, mob/user, make_attack_logs) + add_attack_logs(user, null, "Cast the AoE spell [name]", ATKLOG_ALL) + return ..(targets, recharge, user, FALSE) /obj/effect/proc_holder/spell/targeted/proc/los_check(mob/A,mob/B) //Checks for obstacles from A to B diff --git a/code/datums/spells/knock.dm b/code/datums/spells/knock.dm index 88f5eec9474..e4c652ae1d3 100644 --- a/code/datums/spells/knock.dm +++ b/code/datums/spells/knock.dm @@ -13,11 +13,6 @@ action_icon_state = "knock" sound = 'sound/magic/knock.ogg' -// Knock doesn't need to generate an attack log for every turf, set `make_attack_logs` to FALSE and just create a custom one. -/obj/effect/proc_holder/spell/aoe_turf/knock/perform(list/targets, recharge, mob/user) - add_attack_logs(user, user, "cast the spell [name]", ATKLOG_ALL) - return ..(targets, recharge, user, make_attack_logs = FALSE) - /obj/effect/proc_holder/spell/aoe_turf/knock/cast(list/targets, mob/user = usr) for(var/turf/T in targets) for(var/obj/machinery/door/door in T.contents) diff --git a/code/defines/vox_sounds.dm b/code/defines/vox_sounds.dm index 1d7bb9753a0..5b44f80895b 100644 --- a/code/defines/vox_sounds.dm +++ b/code/defines/vox_sounds.dm @@ -1,7 +1,16 @@ // List is required to compile the resources into the game when it loads. // Dynamically loading it has bad results with sounds overtaking each other, even with the wait variable. -GLOBAL_LIST_INIT(vox_sounds, list("," = 'sound/vox_fem/,.ogg', +GLOBAL_LIST_INIT(vox_alerts, list( +"bizwarn" = 'sound/vox_fem/bizwarn.ogg', +"bloop" = 'sound/vox_fem/bloop.ogg', +"buzwarn" = 'sound/vox_fem/buzwarn.ogg', +"dadeda" = 'sound/vox_fem/dadeda.ogg', +"deeoo" = 'sound/vox_fem/deeoo.ogg' +)) + +GLOBAL_LIST_INIT(vox_sounds, list( +"," = 'sound/vox_fem/,.ogg', "." = 'sound/vox_fem/..ogg', "a" = 'sound/vox_fem/a.ogg', "abortions" = 'sound/vox_fem/abortions.ogg', diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 00009c8eeca..ade06f58b35 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -61,7 +61,7 @@ /atom/movable/proc/get_cell() return -/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) +/atom/movable/proc/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE) if(QDELETED(AM)) return FALSE if(!(AM.can_be_pulled(src, state, force))) @@ -87,7 +87,7 @@ if(ismob(AM)) var/mob/M = AM add_attack_logs(src, M, "passively grabbed", ATKLOG_ALMOSTALL) - if(!supress_message) + if(show_message) visible_message("[src] has grabbed [M] passively!") return TRUE @@ -120,12 +120,18 @@ if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move. pulledby.stop_pulling() -/atom/movable/proc/can_be_pulled(user, grab_state, force) +/atom/movable/proc/can_be_pulled(user, grab_state, force, show_message = FALSE) if(src == user || !isturf(loc)) return FALSE - if(anchored || throwing) + if(anchored || move_resist == INFINITY) + if(show_message) + to_chat(user, "[src] appears to be anchored to the ground!") + return FALSE + if(throwing) return FALSE if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) + if(show_message) + to_chat(user, "[src] is too heavy to pull!") return FALSE return TRUE diff --git a/code/game/gamemodes/blob/blobs/core.dm b/code/game/gamemodes/blob/blobs/core.dm index be794b9092b..b4c01609653 100644 --- a/code/game/gamemodes/blob/blobs/core.dm +++ b/code/game/gamemodes/blob/blobs/core.dm @@ -104,10 +104,18 @@ var/mob/C = null var/list/candidates = list() if(!new_overmind) + // Create icon + var/mutable_appearance/MA = new + var/mutable_appearance/MA1 = new(icon, "blob") + MA1.color = overmind ? overmind.blob_reagent_datum.color : color + MA.overlays += MA1 + var/mutable_appearance/MA2 = new(icon, "blob_core_overlay") + MA.overlays += MA2 + // sendit if(is_offspring) - candidates = pollCandidates("Do you want to play as a blob offspring?", ROLE_BLOB, 1) + candidates = SSghost_spawns.poll_candidates("Do you want to play as a blob offspring?", ROLE_BLOB, TRUE, source = MA) else - candidates = pollCandidates("Do you want to play as a blob?", ROLE_BLOB, 1) + candidates = SSghost_spawns.poll_candidates("Do you want to play as a blob?", ROLE_BLOB, TRUE, source = MA) if(length(candidates)) C = pick(candidates) diff --git a/code/game/gamemodes/blob/powers.dm b/code/game/gamemodes/blob/powers.dm index 6f2eed39499..ec354d4d1c4 100644 --- a/code/game/gamemodes/blob/powers.dm +++ b/code/game/gamemodes/blob/powers.dm @@ -237,7 +237,7 @@ blobber.AIStatus = AI_OFF blobber.LoseTarget() spawn() - var/list/candidates = pollCandidates("Do you want to play as a blobbernaut?", ROLE_BLOB, 1, 100) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a blobbernaut?", ROLE_BLOB, TRUE, 10 SECONDS, source = blobber) if(candidates.len) var/mob/C = pick(candidates) if(C) diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm index 388b16d71af..f1f3bc1c72c 100644 --- a/code/game/gamemodes/cult/cult_items.dm +++ b/code/game/gamemodes/cult/cult_items.dm @@ -140,6 +140,7 @@ body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS allowed = list(/obj/item/tome,/obj/item/melee/cultblade) var/current_charges = 3 + var/shield_state = "shield-cult" hoodtype = /obj/item/clothing/head/hooded/cult_hoodie /obj/item/clothing/head/hooded/cult_hoodie @@ -164,13 +165,18 @@ if(current_charges) owner.visible_message("\The [attack_text] is deflected in a burst of blood-red sparks!") current_charges-- + playsound(loc, "sparks", 100, 1) new /obj/effect/temp_visual/cult/sparks(get_turf(owner)) if(!current_charges) owner.visible_message("The runed shield around [owner] suddenly disappears!") + shield_state = "broken" owner.update_inv_wear_suit() return 1 return 0 +/obj/item/clothing/suit/hooded/cultrobes/cult_shield/special_overlays() + return mutable_appearance('icons/effects/cult_effects.dmi', shield_state, MOB_LAYER + 0.01) + /obj/item/clothing/suit/hooded/cultrobes/berserker name = "flagellant's robes" desc = "Blood-soaked robes infused with dark magic; allows the user to move at inhuman speeds, but at the cost of increased damage." diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 57a77167346..1e2dd3d554b 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -424,9 +424,9 @@ proc/display_roundstart_logout_report() return nukecode /datum/game_mode/proc/replace_jobbanned_player(mob/living/M, role_type) - var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a [role_type]?", role_type, 0, 100) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a [role_type]?", role_type, FALSE, 10 SECONDS) var/mob/dead/observer/theghost = null - if(candidates.len) + if(length(candidates)) theghost = pick(candidates) to_chat(M, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") message_admins("[key_name_admin(theghost)] has taken control of ([key_name_admin(M)]) to replace a jobbanned player.") diff --git a/code/game/gamemodes/miniantags/guardian/guardian.dm b/code/game/gamemodes/miniantags/guardian/guardian.dm index f647ae5a6d1..0bb48fc55ed 100644 --- a/code/game/gamemodes/miniantags/guardian/guardian.dm +++ b/code/game/gamemodes/miniantags/guardian/guardian.dm @@ -246,7 +246,7 @@ src.verbs -= /mob/living/proc/guardian_reset for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.mob_list) if(G.summoner == src) - var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as [G.real_name]?", ROLE_GUARDIAN, 0, 100) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as [G.real_name]?", ROLE_GUARDIAN, FALSE, 10 SECONDS, source = G) var/mob/dead/observer/new_stand = null if(candidates.len) new_stand = pick(candidates) @@ -312,7 +312,7 @@ used = FALSE return to_chat(user, "[use_message]") - var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as the [mob_name] of [user.real_name]?", ROLE_GUARDIAN, 0, 100) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as the [mob_name] of [user.real_name]?", ROLE_GUARDIAN, FALSE, 10 SECONDS, source = /mob/living/simple_animal/hostile/guardian) var/mob/dead/observer/theghost = null if(candidates.len) diff --git a/code/game/gamemodes/miniantags/guardian/types/ranged.dm b/code/game/gamemodes/miniantags/guardian/types/ranged.dm index 496e31d1bd3..dab7dfb1eca 100644 --- a/code/game/gamemodes/miniantags/guardian/types/ranged.dm +++ b/code/game/gamemodes/miniantags/guardian/types/ranged.dm @@ -1,7 +1,7 @@ /obj/item/projectile/guardian name = "crystal spray" icon_state = "guardian" - damage = 5 + damage = 25 damage_type = BRUTE armour_penetration = 100 @@ -11,7 +11,7 @@ melee_damage_upper = 10 damage_transfer = 0.9 projectiletype = /obj/item/projectile/guardian - ranged_cooldown_time = 1 //fast! + ranged_cooldown_time = 5 //fast! projectilesound = 'sound/effects/hit_on_shattered_glass.ogg' ranged = 1 range = 13 diff --git a/code/game/gamemodes/miniantags/morph/morph_event.dm b/code/game/gamemodes/miniantags/morph/morph_event.dm index e9465492a27..7fc9e134195 100644 --- a/code/game/gamemodes/miniantags/morph/morph_event.dm +++ b/code/game/gamemodes/miniantags/morph/morph_event.dm @@ -3,7 +3,7 @@ /datum/event/spawn_morph/proc/get_morph() spawn() - var/list/candidates = pollCandidates("Do you want to play as a morph?", ROLE_MORPH, 1) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a morph?", ROLE_MORPH, TRUE, source = /mob/living/simple_animal/hostile/morph) if(!candidates.len) key_of_morph = null return kill() diff --git a/code/game/gamemodes/miniantags/revenant/revenant.dm b/code/game/gamemodes/miniantags/revenant/revenant.dm index 637ab69bf04..d4e912aa781 100644 --- a/code/game/gamemodes/miniantags/revenant/revenant.dm +++ b/code/game/gamemodes/miniantags/revenant/revenant.dm @@ -141,7 +141,7 @@ giveObjectivesandGoals() giveSpells() else - var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a revenant?", poll_time = 15 SECONDS) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a revenant?", poll_time = 15 SECONDS, source = /mob/living/simple_animal/revenant) var/mob/dead/observer/theghost = null if(candidates.len) theghost = pick(candidates) @@ -397,7 +397,7 @@ spawn() if(!key_of_revenant) message_admins("The new revenant's old client either could not be found or is in a new, living mob - grabbing a random candidate instead...") - var/list/candidates = pollCandidates("Do you want to play as a revenant?", ROLE_REVENANT, 1) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a revenant?", ROLE_REVENANT, TRUE, source = /mob/living/simple_animal/revenant) if(!candidates.len) qdel(R) message_admins("No candidates were found for the new revenant. Oh well!") diff --git a/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm b/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm index 5e5f0f87a79..aa76f3620ac 100644 --- a/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm +++ b/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm @@ -13,7 +13,7 @@ return spawn() - var/list/candidates = pollCandidates("Do you want to play as a revenant?", ROLE_REVENANT, 1) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a revenant?", ROLE_REVENANT, TRUE, source = /mob/living/simple_animal/revenant) if(!candidates.len) key_of_revenant = null return kill() diff --git a/code/game/gamemodes/miniantags/slaughter/slaughter.dm b/code/game/gamemodes/miniantags/slaughter/slaughter.dm index d7e53bdae28..e58be201521 100644 --- a/code/game/gamemodes/miniantags/slaughter/slaughter.dm +++ b/code/game/gamemodes/miniantags/slaughter/slaughter.dm @@ -157,7 +157,7 @@ /mob/living/simple_animal/slaughter/cult/New() ..() spawn(5) - var/list/demon_candidates = pollCandidates("Do you want to play as a slaughter demon?", ROLE_DEMON, 1, 100) + var/list/demon_candidates = SSghost_spawns.poll_candidates("Do you want to play as a slaughter demon?", ROLE_DEMON, TRUE, 10 SECONDS, source = /mob/living/simple_animal/slaughter/cult) if(!demon_candidates.len) visible_message("[src] disappears in a flash of red light!") qdel(src) diff --git a/code/game/gamemodes/shadowling/shadowling.dm b/code/game/gamemodes/shadowling/shadowling.dm index b46bc3c6c51..b51810e4967 100644 --- a/code/game/gamemodes/shadowling/shadowling.dm +++ b/code/game/gamemodes/shadowling/shadowling.dm @@ -74,7 +74,7 @@ Made by Xhuis required_enemies = 2 recommended_enemies = 2 restricted_jobs = list("AI", "Cyborg") - protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate", "Brig Physician", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer") + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Head of Personnel", "Captain", "Blueshield", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate", "Brig Physician", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer") /datum/game_mode/shadowling/announce() to_chat(world, "The current game mode is - Shadowling!") diff --git a/code/game/gamemodes/wizard/artefact.dm b/code/game/gamemodes/wizard/artefact.dm index a81b12ed041..b3092b04e3b 100644 --- a/code/game/gamemodes/wizard/artefact.dm +++ b/code/game/gamemodes/wizard/artefact.dm @@ -50,7 +50,8 @@ to_chat(H, "You already used this contract!") return used = 1 - var/list/candidates = pollCandidates("Do you want to play as the wizard apprentice of [H.real_name]?", ROLE_WIZARD, 1) + var/mutable_appearance/source = new('icons/obj/cardboard_cutout.dmi', "cutout_wizard") + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as the wizard apprentice of [H.real_name]?", ROLE_WIZARD, TRUE, source = source) if(candidates.len) var/mob/C = pick(candidates) new /obj/effect/particle_effect/smoke(H.loc) @@ -307,7 +308,8 @@ GLOBAL_LIST_EMPTY(multiverse) if(M.assigned == assigned) M.cooldown = cooldown - var/list/candidates = pollCandidates("Do you want to play as the wizard apprentice of [user.real_name]?", ROLE_WIZARD, 1, 100) + var/mutable_appearance/source = new('icons/obj/cardboard_cutout.dmi', "cutout_wizard") + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as the wizard apprentice of [user.real_name]?", ROLE_WIZARD, TRUE, 10 SECONDS, source = source) if(candidates.len) var/mob/C = pick(candidates) spawn_copy(C.client, get_turf(user.loc), user) diff --git a/code/game/gamemodes/wizard/raginmages.dm b/code/game/gamemodes/wizard/raginmages.dm index cc52b431249..b189c98524f 100644 --- a/code/game/gamemodes/wizard/raginmages.dm +++ b/code/game/gamemodes/wizard/raginmages.dm @@ -118,7 +118,8 @@ return FALSE making_mage = TRUE - var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a raging Space Wizard?", ROLE_WIZARD, TRUE, poll_time = 20 SECONDS) + var/mutable_appearance/source = new('icons/obj/cardboard_cutout.dmi', "cutout_wizard") + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a raging Space Wizard?", ROLE_WIZARD, TRUE, poll_time = 20 SECONDS, source = source) var/mob/dead/observer/harry = null message_admins("SWF is still pissed, sending another wizard - [max_mages - mages_made] left.") diff --git a/code/game/gamemodes/wizard/soulstone.dm b/code/game/gamemodes/wizard/soulstone.dm index 4bda254d930..aab4318c774 100644 --- a/code/game/gamemodes/wizard/soulstone.dm +++ b/code/game/gamemodes/wizard/soulstone.dm @@ -363,7 +363,7 @@ break if(!chosen_ghost) //Failing that, we grab a ghost - var/list/consenting_candidates = pollCandidates("Would you like to play as a Shade?", ROLE_CULTIST, FALSE, poll_time = 100) + var/list/consenting_candidates = SSghost_spawns.poll_candidates("Would you like to play as a Shade?", ROLE_CULTIST, FALSE, poll_time = 10 SECONDS, source = /mob/living/simple_animal/shade) if(consenting_candidates.len) chosen_ghost = pick(consenting_candidates) if(!T) diff --git a/code/game/jobs/job_exp.dm b/code/game/jobs/job_exp.dm index f0e6f9bff42..aa9114c2963 100644 --- a/code/game/jobs/job_exp.dm +++ b/code/game/jobs/job_exp.dm @@ -224,19 +224,17 @@ GLOBAL_LIST_INIT(role_playtime_requirements, list( else return "none" -/proc/update_exp(var/mins, var/ann = 0) - if(!establish_db_connection()) - return -1 - spawn(0) - for(var/client/L in GLOB.clients) - if(L.inactivity >= (10 MINUTES)) - continue - spawn(0) - L.update_exp_client(mins, ann) - sleep(10) +/proc/update_exp(mins = 0, ann = 0) + if(!GLOB.dbcon.IsConnected()) + return + for(var/client/L in GLOB.clients) + if(L.inactivity >= (10 MINUTES)) + continue + L.update_exp_client(mins, ann) + CHECK_TICK -/client/proc/update_exp_client(var/minutes, var/announce_changes = 0) - if(!src ||!ckey) +/client/proc/update_exp_client(minutes = 0, announce_changes = 0) + if(!src || !ckey || !GLOB.dbcon.IsConnected()) return var/DBQuery/exp_read = GLOB.dbcon.NewQuery("SELECT exp FROM [format_table_name("player")] WHERE ckey='[ckey]'") if(!exp_read.Execute()) diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm index 8d6c545e0a8..98f56feeb6a 100644 --- a/code/game/machinery/computer/card.dm +++ b/code/game/machinery/computer/card.dm @@ -390,7 +390,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) if(!job_in_department(SSjobs.GetJob(t1))) return 0 if(t1 == "Custom") - var/temp_t = sanitize(copytext(input("Enter a custom job assignment.","Assignment"),1,MAX_MESSAGE_LEN)) + var/temp_t = sanitize(reject_bad_name(copytext(input("Enter a custom job assignment.", "Assignment"), 1, MAX_MESSAGE_LEN), TRUE)) //let custom jobs function as an impromptu alt title, mainly for sechuds if(temp_t && modify) SSjobs.log_job_transfer(modify.registered_name, modify.getRankAndAssignment(), temp_t, scan.registered_name) @@ -419,7 +419,8 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) message_admins("[key_name_admin(usr)] has reassigned \"[modify.registered_name]\" from \"[jobnamedata]\" to \"[t1]\".") SSjobs.log_job_transfer(modify.registered_name, jobnamedata, t1, scan.registered_name) - SSjobs.slot_job_transfer(modify.rank, t1) + if(modify.owner_uid) + SSjobs.slot_job_transfer(modify.rank, t1) var/mob/living/carbon/human/H = modify.getPlayer() if(istype(H)) @@ -436,7 +437,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) if(is_authenticated(usr) && !target_dept) var/t2 = modify if((modify == t2 && (in_range(src, usr) || (istype(usr, /mob/living/silicon))) && istype(loc, /turf))) - var/temp_name = reject_bad_name(href_list["reg"]) + var/temp_name = reject_bad_name(href_list["reg"], TRUE) if(temp_name) modify.registered_name = temp_name else @@ -465,6 +466,8 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) if(is_authenticated(usr) && !target_dept) var/delcount = SSjobs.delete_log_records(scan.registered_name, TRUE) if(delcount) + message_admins("[key_name_admin(usr)] has wiped all ID computer logs.") + usr.create_log(MISC_LOG, "wiped all ID computer logs.") playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) SSnanoui.update_uis(src) @@ -504,9 +507,16 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) if("terminate") if(is_authenticated(usr) && !target_dept) var/jobnamedata = modify.getRankAndAssignment() - log_game("[key_name(usr)] has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\".") - message_admins("[key_name_admin(usr)] has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\".") + var/reason = sanitize(copytext(input("Enter legal reason for termination. Enter nothing to cancel.", "Employment Termination"), 1, MAX_MESSAGE_LEN)) + if(!reason || !is_authenticated(usr) || !modify) + return FALSE + var/m_ckey = modify.getPlayerCkey() + var/m_ckey_text = m_ckey ? "([m_ckey])" : "(no ckey)" + log_game("[key_name(usr)] has terminated the employment of \"[modify.registered_name]\" [m_ckey_text] the \"[jobnamedata]\" for: \"[reason]\".") + message_admins("[key_name_admin(usr)] has terminated the employment of \"[modify.registered_name]\" [m_ckey_text] the \"[jobnamedata]\" for: \"[reason]\".") + usr.create_log(MISC_LOG, "terminated the employment of \"[modify.registered_name]\" [m_ckey_text] the \"[jobnamedata]\"") SSjobs.log_job_transfer(modify.registered_name, jobnamedata, "Terminated", scan.registered_name) + SSjobs.notify_dept_head(modify.rank, "[scan.registered_name] has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\" for \"[reason]\".") modify.assignment = "Terminated" modify.access = list() @@ -518,16 +528,20 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) if(!job_in_department(SSjobs.GetJob(modify.rank), FALSE)) visible_message("[src]: Heads may only demote members of their own department.") return 0 - + var/reason = sanitize(copytext(input("Enter legal reason for demotion. Enter nothing to cancel.","Legal Demotion"),1,MAX_MESSAGE_LEN)) + if(!reason || !is_authenticated(usr) || !modify) + return 0 var/list/access = list() var/datum/job/jobdatum = new /datum/job/civilian access = jobdatum.get_access() - var/jobnamedata = modify.getRankAndAssignment() - log_game("[key_name(usr)] has demoted \"[modify.registered_name]\" the \"[jobnamedata]\" to \"Civilian (Demoted)\".") - message_admins("[key_name_admin(usr)] has demoted \"[modify.registered_name]\" the \"[jobnamedata]\" to \"Civilian (Demoted)\".") + var/m_ckey = modify.getPlayerCkey() + var/m_ckey_text = m_ckey ? "([m_ckey])" : "(no ckey)" + log_game("[key_name(usr)] has demoted \"[modify.registered_name]\" the \"[jobnamedata]\" [m_ckey_text] to \"Civilian (Demoted)\" for: \"[reason]\".") + message_admins("[key_name_admin(usr)] has demoted \"[modify.registered_name]\" the \"[jobnamedata]\" [m_ckey_text] to \"Civilian (Demoted)\" for: \"[reason]\".") + usr.create_log(MISC_LOG, "demoted \"[modify.registered_name]\" [m_ckey_text] the \"[jobnamedata]\"") SSjobs.log_job_transfer(modify.registered_name, jobnamedata, "Demoted", scan.registered_name) - + SSjobs.notify_dept_head(modify.rank, "[scan.registered_name] has demoted \"[modify.registered_name]\" the \"[jobnamedata]\" for \"[reason]\".") modify.access = access modify.rank = "Civilian" modify.assignment = "Demoted" diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index 9970974e4ff..36eba1a6ee5 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -86,8 +86,8 @@ Arresting Officer: [usr.name].[R ? "" : " Detainee record not found, manual record update required."]" Radio.autosay(announcetext, name, "Security", list(z)) - if(prisoner_trank != "unknown") - notify_dept_head(prisoner_trank, announcetext) + if(prisoner_trank != "unknown" && prisoner_trank != "Civilian") + SSjobs.notify_dept_head(prisoner_trank, announcetext) if(R) prisoner = R @@ -104,31 +104,6 @@ update_all_mob_security_hud() return 1 - -/obj/machinery/door_timer/proc/notify_dept_head(jobtitle, antext) - if(!jobtitle || !antext) - return - if(jobtitle == "Civilian") - // Don't notify the HoP about greytiding civilians - return - var/datum/job/brigged_job = SSjobs.GetJob(jobtitle) - if(!brigged_job) - return - if(!brigged_job.department_head[1]) - return - var/boss_title = brigged_job.department_head[1] - - var/obj/item/pda/target_pda - for(var/obj/item/pda/check_pda in GLOB.PDAs) - if(check_pda.ownrank == boss_title) - target_pda = check_pda - if(!target_pda) - return - var/datum/data/pda/app/messenger/PM = target_pda.find_program(/datum/data/pda/app/messenger) - if(PM && PM.can_receive()) - PM.notify("Message from Brig Timer (Automated), \"[antext]\" (Unable to Reply)") - - /obj/machinery/door_timer/Initialize() ..() diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index 82b473b50f0..6fb34254f3a 100644 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -1,15 +1,17 @@ +#define RECHARGER_POWER_USAGE_GUN 250 +#define RECHARGER_POWER_USAGE_MISC 200 + /obj/machinery/recharger name = "recharger" icon = 'icons/obj/stationobjs.dmi' icon_state = "recharger0" desc = "A charging dock for energy based weaponry." - anchored = 1 + anchored = TRUE use_power = IDLE_POWER_USE idle_power_usage = 4 active_power_usage = 200 pass_flags = PASSTABLE - var/obj/item/charging = null - var/using_power = FALSE + var/list/allowed_devices = list(/obj/item/gun/energy, /obj/item/melee/baton, /obj/item/modular_computer, /obj/item/rcs, /obj/item/bodyanalyzer) var/icon_state_off = "rechargeroff" var/icon_state_charged = "recharger2" @@ -17,6 +19,9 @@ var/icon_state_idle = "recharger0" var/recharge_coeff = 1 + var/obj/item/charging = null // The item that is being charged + var/using_power = FALSE // Whether the recharger is actually transferring power or not, used for icon + /obj/machinery/recharger/New() ..() component_parts = list() @@ -34,32 +39,32 @@ if(allowed) if(anchored) if(charging) - return 1 + return TRUE //Checks to make sure he's not in space doing it, and that the area got proper power. var/area/a = get_area(src) - if(!isarea(a) || a.power_equip == 0) + if(!isarea(a) || !a.power_equip) to_chat(user, "[src] blinks red as you try to insert [G].") - return 1 + return TRUE if(istype(G, /obj/item/gun/energy)) var/obj/item/gun/energy/E = G if(!E.can_charge) to_chat(user, "Your gun has no external power connector.") - return 1 + return TRUE if(!user.drop_item()) - return 1 + return TRUE G.forceMove(src) charging = G use_power = ACTIVE_POWER_USE + using_power = check_cell_needs_recharging(get_cell_from(G)) update_icon() else to_chat(user, "[src] isn't connected to anything!") - return 1 + return TRUE return ..() - /obj/machinery/recharger/crowbar_act(mob/user, obj/item/I) if(panel_open && !charging && default_deconstruction_crowbar(user, I)) return TRUE @@ -106,57 +111,15 @@ if(stat & (NOPOWER|BROKEN) || !anchored) return - using_power = FALSE - if(charging) - if(istype(charging, /obj/item/gun/energy)) - var/obj/item/gun/energy/E = charging - if(E.cell.charge < E.cell.maxcharge) - E.cell.give(E.cell.chargerate * recharge_coeff) - E.on_recharge() - use_power(250) - using_power = TRUE - - - if(istype(charging, /obj/item/melee/baton)) - var/obj/item/melee/baton/B = charging - if(B.cell) - if(B.cell.give(B.cell.chargerate)) - use_power(200) - using_power = TRUE - - if(istype(charging, /obj/item/modular_computer)) - var/obj/item/modular_computer/C = charging - var/obj/item/computer_hardware/battery/battery_module = C.all_components[MC_CELL] - if(battery_module) - var/obj/item/computer_hardware/battery/B = battery_module - if(B.battery) - if(B.battery.charge < B.battery.maxcharge) - B.battery.give(B.battery.chargerate) - use_power(200) - using_power = TRUE - - if(istype(charging, /obj/item/rcs)) - var/obj/item/rcs/R = charging - if(R.rcell) - if(R.rcell.give(R.rcell.chargerate)) - use_power(200) - using_power = TRUE - - if(istype(charging, /obj/item/bodyanalyzer)) - var/obj/item/bodyanalyzer/B = charging - if(B.cell) - if(B.cell.give(B.cell.chargerate)) - use_power(200) - using_power = TRUE - - update_icon(using_power) + using_power = try_recharging_if_possible() + update_icon() /obj/machinery/recharger/emp_act(severity) if(stat & (NOPOWER|BROKEN) || !anchored) ..(severity) return - if(istype(charging, /obj/item/gun/energy)) + if(istype(charging, /obj/item/gun/energy)) var/obj/item/gun/energy/E = charging if(E.cell) E.cell.emp_act(severity) @@ -167,7 +130,11 @@ B.cell.charge = 0 ..(severity) -/obj/machinery/recharger/update_icon(using_power = FALSE) //we have an update_icon() in addition to the stuff in process to make it feel a tiny bit snappier. +/obj/machinery/recharger/power_change() + ..() + update_icon() + +/obj/machinery/recharger/update_icon() //we have an update_icon() in addition to the stuff in process to make it feel a tiny bit snappier. if(stat & (NOPOWER|BROKEN) || !anchored) icon_state = icon_state_off return @@ -179,6 +146,55 @@ return icon_state = icon_state_idle +/obj/machinery/recharger/proc/get_cell_from(obj/item/I) + if(istype(I, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = I + return E.cell + + if(istype(I, /obj/item/melee/baton)) + var/obj/item/melee/baton/B = I + return B.cell + + if(istype(I, /obj/item/modular_computer)) + var/obj/item/modular_computer/C = I + var/obj/item/computer_hardware/battery/B = C.all_components[MC_CELL] + if(B) + return B.battery + + if(istype(I, /obj/item/rcs)) + var/obj/item/rcs/R = I + return R.rcell + + if(istype(I, /obj/item/bodyanalyzer)) + var/obj/item/bodyanalyzer/B = I + return B.cell + + return null + +/obj/machinery/recharger/proc/check_cell_needs_recharging(obj/item/stock_parts/cell/C) + if(!C || C.charge >= C.maxcharge) + return FALSE + return TRUE + +/obj/machinery/recharger/proc/recharge_cell(obj/item/stock_parts/cell/C, power_usage) + C.give(C.chargerate * recharge_coeff) + use_power(power_usage) + +/obj/machinery/recharger/proc/try_recharging_if_possible() + var/obj/item/stock_parts/cell/C = get_cell_from(charging) + if(!check_cell_needs_recharging(C)) + return FALSE + + if(istype(charging, /obj/item/gun/energy)) + recharge_cell(C, RECHARGER_POWER_USAGE_GUN) + + var/obj/item/gun/energy/E = charging + E.on_recharge() + else + recharge_cell(C, RECHARGER_POWER_USAGE_MISC) + + return TRUE + /obj/machinery/recharger/examine(mob/user) . = ..() if(charging && (!in_range(user, src) && !issilicon(user) && !isobserver(user))) @@ -204,3 +220,6 @@ icon_state_idle = "wrecharger0" icon_state_charging = "wrecharger1" icon_state_charged = "wrecharger2" + +#undef RECHARGER_POWER_USAGE_GUN +#undef RECHARGER_POWER_USAGE_MISC diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index bbf8564ba1b..d7a2213c5b7 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -404,7 +404,7 @@ cashmoney.use(currently_vending.price) // Vending machines have no idea who paid with cash - credit_purchase("(cash)") + GLOB.vendor_account.credit(currently_vending.price, "Sale of [currently_vending.name]", name, "(cash)") return 1 /** @@ -444,26 +444,12 @@ return 0 else // Okay to move the money at this point - var/paid = customer_account.charge(currently_vending.price, GLOB.vendor_account, + customer_account.charge(currently_vending.price, GLOB.vendor_account, "Purchase of [currently_vending.name]", name, GLOB.vendor_account.owner_name, "Sale of [currently_vending.name]", customer_account.owner_name) - if(paid) - // Give the vendor the money. We use the account owner name, which means - // that purchases made with stolen/borrowed card will look like the card - // owner made them - credit_purchase(customer_account.owner_name) - return paid + return TRUE -/** - * Add money for current purchase to the vendor account. - * - * Called after the money has already been taken from the customer. - */ -/obj/machinery/vending/proc/credit_purchase(var/target as text) - GLOB.vendor_account.money += currently_vending.price - GLOB.vendor_account.credit(currently_vending.price, "Sale of [currently_vending.name]", - name, target) /obj/machinery/vending/attack_ai(mob/user) return attack_hand(user) @@ -1481,6 +1467,7 @@ /obj/item/clothing/under/victsuit/redblk = 1, /obj/item/clothing/under/victsuit/red = 1, /obj/item/clothing/suit/tailcoat = 1, + /obj/item/clothing/under/tourist_suit = 1, /obj/item/clothing/suit/draculacoat = 1, /obj/item/clothing/head/zepelli = 1, /obj/item/clothing/under/redhawaiianshirt = 1, diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index 9dd1c771b17..639687a45e9 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -58,7 +58,7 @@ icon_state = "eggs" var/amount_grown = 0 var/player_spiders = 0 - var/list/faction = list() + var/list/faction = list("spiders") /obj/structure/spider/eggcluster/New() ..() @@ -90,7 +90,7 @@ var/obj/machinery/atmospherics/unary/vent_pump/entry_vent var/travelling_in_vent = 0 var/player_spiders = 0 - var/list/faction = list() + var/list/faction = list("spiders") var/selecting_player = 0 /obj/structure/spider/spiderling/New() @@ -180,7 +180,7 @@ if(player_spiders && !selecting_player) selecting_player = 1 spawn() - var/list/candidates = pollCandidates("Do you want to play as a spider?", ROLE_GSPIDER, 1) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a giant spider?", ROLE_GSPIDER, TRUE, source = S) if(candidates.len) var/mob/C = pick(candidates) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index baaf0cab16b..edcc6620531 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -2,6 +2,8 @@ GLOBAL_DATUM_INIT(fire_overlay, /image, image("icon" = 'icons/goonstation/effect /obj/item name = "item" icon = 'icons/obj/items.dmi' + + move_resist = null // Set in the Initialise depending on the item size. Unless it's overriden by a specific item var/discrete = 0 // used in item_attack.dm to make an item not show an attack message to viewers var/image/blood_overlay = null //this saves our blood splatter overlay, which will be processed not to go over the edges of the sprite var/blood_overlay_color = null @@ -113,6 +115,23 @@ GLOBAL_DATUM_INIT(fire_overlay, /image, image("icon" = 'icons/goonstation/effect hitsound = 'sound/items/welder.ogg' if(damtype == "brute") hitsound = "swing_hit" + if(!move_resist) + determine_move_resist() + +/obj/item/proc/determine_move_resist() + switch(w_class) + if(WEIGHT_CLASS_TINY) + move_resist = MOVE_FORCE_EXTREMELY_WEAK + if(WEIGHT_CLASS_SMALL) + move_resist = MOVE_FORCE_VERY_WEAK + if(WEIGHT_CLASS_NORMAL) + move_resist = MOVE_FORCE_WEAK + if(WEIGHT_CLASS_BULKY) + move_resist = MOVE_FORCE_NORMAL + if(WEIGHT_CLASS_HUGE) + move_resist = MOVE_FORCE_NORMAL + if(WEIGHT_CLASS_GIGANTIC) + move_resist = MOVE_FORCE_NORMAL /obj/item/Destroy() flags &= ~DROPDEL //prevent reqdels diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm index 7a6b41966a8..e96fceb8b6c 100644 --- a/code/game/objects/items/devices/lightreplacer.dm +++ b/code/game/objects/items/devices/lightreplacer.dm @@ -191,7 +191,7 @@ if(CanUse(U)) if(!Use(U)) return - to_chat(U, "You replace [target.fitting] with [src].") + to_chat(U, "You replace the light [target.fitting] with [src].") if(target.status != LIGHT_EMPTY) AddShards(1, U) diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 66f35a169f9..d3d61233f97 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -344,56 +344,56 @@ /obj/item/toy/prize/ripley name = "toy ripley" - desc = "Mini-Mecha action figure! Collect them all! 1/11." + desc = "Mini-Mecha action figure! Collect them all! 1/11. This one is a ripley, a mining and engineering mecha." /obj/item/toy/prize/fireripley name = "toy firefighting ripley" - desc = "Mini-Mecha action figure! Collect them all! 2/11." + desc = "Mini-Mecha action figure! Collect them all! 2/11. This one is a firefighter ripley, a fireproof mining and engineering mecha." icon_state = "fireripleytoy" /obj/item/toy/prize/deathripley name = "toy deathsquad ripley" - desc = "Mini-Mecha action figure! Collect them all! 3/11." + desc = "Mini-Mecha action figure! Collect them all! 3/11. This one is the black ripley used by the hero of DeathSquad, that TV drama about loose-cannon ERT officers!" icon_state = "deathripleytoy" /obj/item/toy/prize/gygax name = "toy gygax" - desc = "Mini-Mecha action figure! Collect them all! 4/11." + desc = "Mini-Mecha action figure! Collect them all! 4/11. This one is the speedy gygax combat mecha. Zoom zoom, pew pew!" icon_state = "gygaxtoy" /obj/item/toy/prize/durand name = "toy durand" - desc = "Mini-Mecha action figure! Collect them all! 5/11." + desc = "Mini-Mecha action figure! Collect them all! 5/11. This one is the heavy durand combat mecha. Stomp stomp!" icon_state = "durandprize" /obj/item/toy/prize/honk name = "toy H.O.N.K." - desc = "Mini-Mecha action figure! Collect them all! 6/11." + desc = "Mini-Mecha action figure! Collect them all! 6/11. This one is the infamous H.O.N.K mech!" icon_state = "honkprize" /obj/item/toy/prize/marauder name = "toy marauder" - desc = "Mini-Mecha action figure! Collect them all! 7/11." + desc = "Mini-Mecha action figure! Collect them all! 7/11. This one is the powerful marauder combat mecha! Run for cover!" icon_state = "marauderprize" /obj/item/toy/prize/seraph name = "toy seraph" - desc = "Mini-Mecha action figure! Collect them all! 8/11." + desc = "Mini-Mecha action figure! Collect them all! 8/11. This one is the powerful seraph combat mecha! Someone's in trouble!" icon_state = "seraphprize" /obj/item/toy/prize/mauler name = "toy mauler" - desc = "Mini-Mecha action figure! Collect them all! 9/11." + desc = "Mini-Mecha action figure! Collect them all! 9/11. This one is the deadly mauler combat mecha! Look out!" icon_state = "maulerprize" /obj/item/toy/prize/odysseus name = "toy odysseus" - desc = "Mini-Mecha action figure! Collect them all! 10/11." + desc = "Mini-Mecha action figure! Collect them all! 10/11. This one is the spindly, syringe-firing odysseus medical mecha." icon_state = "odysseusprize" /obj/item/toy/prize/phazon name = "toy phazon" - desc = "Mini-Mecha action figure! Collect them all! 11/11." + desc = "Mini-Mecha action figure! Collect them all! 11/11. This one is the mysterious Phazon combat mecha! Nobody's safe!" icon_state = "phazonprize" @@ -1591,182 +1591,217 @@ obj/item/toy/cards/deck/syndicate/black /obj/item/toy/figure/cmo name = "Chief Medical Officer action figure" + desc = "The ever-suffering CMO, from Space Life's SS12 figurine collection." icon_state = "cmo" toysay = "Suit sensors!" /obj/item/toy/figure/assistant name = "Assistant action figure" + desc = "The faceless, hairless scourge of the station, from Space Life's SS12 figurine collection." icon_state = "assistant" toysay = "Grey tide station wide!" /obj/item/toy/figure/atmos name = "Atmospheric Technician action figure" + desc = "The faithful atmospheric technician, from Space Life's SS12 figurine collection." icon_state = "atmos" toysay = "Glory to Atmosia!" /obj/item/toy/figure/bartender name = "Bartender action figure" + desc = "The suave bartender, from Space Life's SS12 figurine collection." icon_state = "bartender" toysay = "Wheres my monkey?" /obj/item/toy/figure/borg name = "Cyborg action figure" + desc = "The iron-willed cyborg, from Space Life's SS12 figurine collection." icon_state = "borg" toysay = "I. LIVE. AGAIN." /obj/item/toy/figure/botanist name = "Botanist action figure" + desc = "The drug-addicted botanist, from Space Life's SS12 figurine collection." icon_state = "botanist" toysay = "Dude, I see colors..." /obj/item/toy/figure/captain name = "Captain action figure" + desc = "The inept captain, from Space Life's SS12 figurine collection." icon_state = "captain" toysay = "Crew, the Nuke Disk is safely up my ass." /obj/item/toy/figure/cargotech name = "Cargo Technician action figure" + desc = "The hard-working cargo tech, from Space Life's SS12 figurine collection." icon_state = "cargotech" toysay = "For Cargonia!" /obj/item/toy/figure/ce name = "Chief Engineer action figure" + desc = "The expert Chief Engineer, from Space Life's SS12 figurine collection." icon_state = "ce" toysay = "Wire the solars!" /obj/item/toy/figure/chaplain name = "Chaplain action figure" + desc = "The obsessed Chaplain, from Space Life's SS12 figurine collection." icon_state = "chaplain" toysay = "Gods make me a killing machine please!" /obj/item/toy/figure/chef name = "Chef action figure" + desc = "The cannibalistic chef, from Space Life's SS12 figurine collection." icon_state = "chef" toysay = "I swear it's not human meat." /obj/item/toy/figure/chemist name = "Chemist action figure" + desc = "The legally dubious Chemist, from Space Life's SS12 figurine collection." icon_state = "chemist" toysay = "Get your pills!" /obj/item/toy/figure/clown name = "Clown action figure" + desc = "The mischevious Clown, from Space Life's SS12 figurine collection." icon_state = "clown" toysay = "Honk!" /obj/item/toy/figure/ian name = "Ian action figure" + desc = "The adorable corgi, from Space Life's SS12 figurine collection." icon_state = "ian" toysay = "Arf!" /obj/item/toy/figure/detective name = "Detective action figure" + desc = "The clever detective, from Space Life's SS12 figurine collection." icon_state = "detective" toysay = "This airlock has grey jumpsuit and insulated glove fibers on it." /obj/item/toy/figure/dsquad name = "Death Squad Officer action figure" + desc = "It's a member of the DeathSquad, a TV drama where loose-cannon ERT officers face up against the threats of the galaxy! It's from Space Life's special edition SS12 figurine collection." icon_state = "dsquad" toysay = "Eliminate all threats!" /obj/item/toy/figure/engineer name = "Engineer action figure" + desc = "The frantic engineer, from Space Life's SS12 figurine collection." icon_state = "engineer" toysay = "Oh god, the singularity is loose!" /obj/item/toy/figure/geneticist name = "Geneticist action figure" + desc = "The balding geneticist, from Space Life's SS12 figurine collection." icon_state = "geneticist" toysay = "I'm not qualified for this job." /obj/item/toy/figure/hop name = "Head of Personnel action figure" + desc = "The officious Head of Personnel, from Space Life's SS12 figurine collection." icon_state = "hop" - toysay = "Giving out all access!" + toysay = "Papers, please!" /obj/item/toy/figure/hos name = "Head of Security action figure" + desc = "The bloodlust-filled Head of Security, from Space Life's SS12 figurine collection." icon_state = "hos" - toysay = "I'm here to win, anything else is secondary." + toysay = "Space law? What?" /obj/item/toy/figure/qm name = "Quartermaster action figure" + desc = "The nationalistic Quartermaster, from Space Life's SS12 figurine collection." icon_state = "qm" toysay = "Hail Cargonia!" /obj/item/toy/figure/janitor name = "Janitor action figure" + desc = "The water-using Janitor, from Space Life's SS12 figurine collection." icon_state = "janitor" toysay = "Look at the signs, you idiot." /obj/item/toy/figure/lawyer name = "Internal Affairs Agent action figure" + desc = "The unappreciated Internal Affairs Agent, from Space Life's SS12 figurine collection." icon_state = "lawyer" toysay = "Standard Operating Procedure says they're guilty! Hacking is proof they're an Enemy of the Corporation!" /obj/item/toy/figure/librarian name = "Librarian action figure" + desc = "The quiet Librarian, from Space Life's SS12 figurine collection." icon_state = "librarian" toysay = "One day while..." /obj/item/toy/figure/md name = "Medical Doctor action figure" + desc = "The stressed-out doctor, from Space Life's SS12 figurine collection." icon_state = "md" toysay = "The patient is already dead!" /obj/item/toy/figure/mime name = "Mime action figure" - desc = "A \"Space Life\" brand Mime action figure." + desc = "... from Space Life's SS12 figurine collection." icon_state = "mime" toysay = "..." /obj/item/toy/figure/miner name = "Shaft Miner action figure" + desc = "The gun-toting Shaft Miner, from Space Life's SS12 figurine collection." icon_state = "miner" toysay = "Oh god it's eating my intestines!" /obj/item/toy/figure/ninja name = "Ninja action figure" + desc = "It's the mysterious ninja! It's from Space Life's special edition SS12 figurine collection." icon_state = "ninja" toysay = "Oh god! Stop shooting, I'm friendly!" /obj/item/toy/figure/wizard name = "Wizard action figure" + desc = "It's the deadly, spell-slinging wizard! It's from Space Life's special edition SS12 figurine collection." icon_state = "wizard" toysay = "Ei Nath!" /obj/item/toy/figure/rd name = "Research Director action figure" + desc = "The ambitious RD, from Space Life's SS12 figurine collection." icon_state = "rd" toysay = "Blowing all of the borgs!" /obj/item/toy/figure/roboticist name = "Roboticist action figure" + desc = "The skillful Roboticist, from Space Life's SS12 figurine collection." icon_state = "roboticist" toysay = "He asked to be borged!" /obj/item/toy/figure/scientist name = "Scientist action figure" + desc = "The mad Scientist, from Space Life's SS12 figurine collection." icon_state = "scientist" toysay = "Someone else must have made those bombs!" /obj/item/toy/figure/syndie name = "Nuclear Operative action figure" + desc = "It's the red-suited Nuclear Operative! It's from Space Life's special edition SS12 figurine collection." icon_state = "syndie" toysay = "Get that fucking disk!" /obj/item/toy/figure/secofficer name = "Security Officer action figure" + desc = "The power-tripping Security Officer, from Space Life's SS12 figurine collection." icon_state = "secofficer" toysay = "I am the law!" /obj/item/toy/figure/virologist name = "Virologist action figure" + desc = "The pandemic-starting Virologist, from Space Life's SS12 figurine collection." icon_state = "virologist" - toysay = "The cure is potassium!" + toysay = "It's not my virus!" /obj/item/toy/figure/warden name = "Warden action figure" + desc = "The amnesiac Warden, from Space Life's SS12 figurine collection." icon_state = "warden" toysay = "Execute him for breaking in!" diff --git a/code/game/objects/items/weapons/batons.dm b/code/game/objects/items/weapons/batons.dm new file mode 100644 index 00000000000..34d2bdd7ac8 --- /dev/null +++ b/code/game/objects/items/weapons/batons.dm @@ -0,0 +1,133 @@ +/// Delay in deci-seconds between two non-lethal attacks +#define BATON_STUN_COOLDOWN 4 SECONDS +/// Force of the telescopic baton when deployed +#define BATON_TELESCOPIC_FORCE_DEPLOYED 10 + +/** + * # Police Baton + * + * Knocks down the hit mob when not on harm intent and when [/obj/item/melee/classic_baton/on] is TRUE + * + * A non-lethal attack has a cooldown to avoid spamming + */ +/obj/item/melee/classic_baton + name = "police baton" + desc = "A wooden truncheon for beating criminal scum." + icon_state = "baton" + item_state = "classic_baton" + slot_flags = SLOT_BELT + force = 12 //9 hit crit + w_class = WEIGHT_CLASS_NORMAL + /// Whether the baton is on cooldown + var/on_cooldown = FALSE + /// Whether the baton is toggled on (to allow attacking) + var/on = TRUE + +/obj/item/melee/classic_baton/attack(mob/living/target, mob/living/user) + if(!on) + return ..() + + add_fingerprint(user) + if((CLUMSY in user.mutations) && prob(50)) + user.visible_message("[user] accidentally clubs [user.p_them()]self with [src]!", \ + "You accidentally club yourself with [src]!") + user.Weaken(force * 3) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + H.apply_damage(force * 2, BRUTE, "head") + else + user.take_organ_damage(force * 2) + return + + if(user.a_intent == INTENT_HARM || isrobot(target)) // Lethal attack or it's a borg (can't knock them down!) + return ..() + else if(!on_cooldown) // Non-lethal attack - knock them down + // Check for shield/countering + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) + return + if(check_martial_counter(H, user)) + return + // Visuals and sound + user.do_attack_animation(target) + playsound(target, 'sound/effects/woodhit.ogg', 75, TRUE, -1) + add_attack_logs(user, target, "Stunned with [src]") + target.visible_message("[user] has knocked down [target] with \the [src]!", \ + "[user] has knocked down [target] with \the [src]!") + // Hit 'em + target.LAssailant = iscarbon(user) ? user : null + target.Weaken(3) + on_cooldown = TRUE + addtimer(CALLBACK(src, .proc/cooldown_finished), BATON_STUN_COOLDOWN) + +/** + * Called some time after a non-lethal attack + */ +/obj/item/melee/classic_baton/proc/cooldown_finished() + on_cooldown = FALSE + +/** + * # Fancy Cane + */ +/obj/item/melee/classic_baton/ntcane + name = "fancy cane" + desc = "A cane with special engraving on it. It seems well suited for fending off assailants..." + icon_state = "cane_nt" + item_state = "cane_nt" + needs_permit = FALSE + +/obj/item/melee/classic_baton/ntcane/is_crutch() + return TRUE + +/** + * # Telescopic Baton + */ +/obj/item/melee/classic_baton/telescopic + name = "telescopic baton" + desc = "A compact yet robust personal defense weapon. Can be concealed when folded." + icon_state = "telebaton_0" + item_state = null + slot_flags = SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + needs_permit = FALSE + force = 0 + on = FALSE + /// Attack verbs when concealed (created on Initialize) + var/static/list/attack_verb_off + /// Attack verbs when extended (created on Initialize) + var/static/list/attack_verb_on + +/obj/item/melee/classic_baton/telescopic/Initialize(mapload) + . = ..() + if(!attack_verb_off) + attack_verb_off = list("hit", "poked") + attack_verb_on = list("smacked", "struck", "cracked", "beaten") + attack_verb = on ? attack_verb_on : attack_verb_off + +/obj/item/melee/classic_baton/telescopic/attack_self(mob/user) + on = !on + icon_state = "telebaton_[on]" + if(on) + to_chat(user, "You extend the baton.") + item_state = "nullrod" + w_class = WEIGHT_CLASS_BULKY //doesnt fit in backpack when its on for balance + force = BATON_TELESCOPIC_FORCE_DEPLOYED //stunbaton damage + attack_verb = attack_verb_on + else + to_chat(user, "You collapse the baton.") + item_state = null //no sprite for concealment even when in hand + slot_flags = SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + force = 0 //not so robust now + attack_verb = attack_verb_off + // Update mob hand visuals + if(ishuman(user)) + var/mob/living/carbon/human/H = user + H.update_inv_l_hand() + H.update_inv_r_hand() + playsound(loc, 'sound/weapons/batonextend.ogg', 50, TRUE) + add_fingerprint(user) + +#undef BATON_STUN_COOLDOWN +#undef BATON_TELESCOPIC_FORCE_DEPLOYED diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm index dc26b14e24b..b780a837559 100644 --- a/code/game/objects/items/weapons/cards_ids.dm +++ b/code/game/objects/items/weapons/cards_ids.dm @@ -212,6 +212,11 @@ return M owner_ckey = null +/obj/item/card/id/proc/getPlayerCkey() + var/mob/living/carbon/human/H = getPlayer() + if(istype(H)) + return H.ckey + /obj/item/card/id/proc/is_untrackable() return untrackable diff --git a/code/game/objects/items/weapons/cosmetics.dm b/code/game/objects/items/weapons/cosmetics.dm index 42f65141094..fcfe183dddf 100644 --- a/code/game/objects/items/weapons/cosmetics.dm +++ b/code/game/objects/items/weapons/cosmetics.dm @@ -55,16 +55,15 @@ /obj/item/lipstick/random/Initialize(mapload) . = ..() - var/lscolor = pick(lipstick_colors) // A random color is picked from the var defined initially in a new var. - colour = lipstick_colors[lscolor] // The color of the lipstick is pulled from the new variable (right hand side, HTML & Hex RGB) - name = "[lscolor] lipstick" // The new variable is also used to match the name to the color of the lipstick. Kudos to Desolate & Lemon + colour = pick(lipstick_colors) + name = "[colour] lipstick" /obj/item/lipstick/attack_self(mob/user) cut_overlays() to_chat(user, "You twist \the [src] [open ? "closed" : "open"].") open = !open if(open) - var/image/colored = mutable_appearance('icons/obj/items.dmi', "lipstick_uncap_color") + var/mutable_appearance/colored = mutable_appearance('icons/obj/items.dmi', "lipstick_uncap_color") colored.color = lipstick_colors[colour] icon_state = "lipstick_uncap" add_overlay(colored) diff --git a/code/game/objects/items/weapons/grenades/flashbang.dm b/code/game/objects/items/weapons/grenades/flashbang.dm index b3028f13c90..13f9dd8f49d 100644 --- a/code/game/objects/items/weapons/grenades/flashbang.dm +++ b/code/game/objects/items/weapons/grenades/flashbang.dm @@ -5,39 +5,51 @@ origin_tech = "materials=2;combat=3" light_power = 10 light_color = LIGHT_COLOR_WHITE - var/light_time = 2 - var/range = 7 + + var/light_time = 0.2 SECONDS // The duration the area is illuminated + var/range = 7 // The range in tiles of the flashbang /obj/item/grenade/flashbang/prime() update_mob() - var/flashbang_turf = get_turf(src) - if(!flashbang_turf) - return + var/turf/T = get_turf(src) + if(T) + // VFX and SFX + do_sparks(rand(5, 9), FALSE, src) + playsound(T, 'sound/effects/bang.ogg', 100, TRUE) + new /obj/effect/dummy/lighting_obj(T, light_color, range + 2, light_power, light_time) - set_light(7) - - do_sparks(rand(5, 9), FALSE, src) - playsound(flashbang_turf, 'sound/effects/bang.ogg', 25, 1) - bang(flashbang_turf, src, range) - - for(var/obj/structure/blob/B in hear(8, flashbang_turf)) //Blob damage here - var/damage = round(30 / (get_dist(B, get_turf(src)) + 1)) - B.take_damage(damage, BURN, "melee", 0) - - spawn(light_time) - qdel(src) + // Stunning & damaging mechanic + bang(T, src, range) + qdel(src) +/** + * Creates a flashing effect that blinds and deafens mobs within range + * + * Also damages blobs + * Arguments: + * * T - The turf to flash + * * A - The flashing atom + * * range - The range in tiles of the flash + * * flash - Whether to flash (blind) + * * bang - Whether to bang (deafen) + */ /proc/bang(turf/T, atom/A, range = 7, flash = TRUE, bang = TRUE) + // Blob damage + for(var/obj/structure/blob/B in hear(range + 1, T)) + var/damage = round(30 / (get_dist(B, T) + 1)) + B.take_damage(damage, BURN, "melee", FALSE) + + // Flashing mechanic + var/source_turf = get_turf(A) for(var/mob/living/M in hearers(range, T)) if(M.stat == DEAD) continue M.show_message("BANG", 2) - //Checking for protections - var/ear_safety = M.check_ear_prot() - var/distance = max(1, get_dist(get_turf(A), get_turf(M))) + var/distance = max(1, get_dist(source_turf, get_turf(M))) + var/stun_amount = max(10 / distance, 3) - //Flash + // Flash if(flash) if(M.weakeyes) M.visible_message("[M] screams and collapses!") @@ -49,21 +61,20 @@ var/mob/living/carbon/human/H = M var/obj/item/organ/internal/eyes/E = H.get_int_organ(/obj/item/organ/internal/eyes) if(E) - E.receive_damage(8, 1) - + E.receive_damage(8, TRUE) if(M.flash_eyes(affect_silicon = TRUE)) - M.Stun(max(10 / distance, 3)) - M.Weaken(max(10 / distance, 3)) + M.Stun(stun_amount) + M.Weaken(stun_amount) - - //Bang + // Bang + var/ear_safety = M.check_ear_prot() if(bang) - if(!distance || A.loc == M || A.loc == M.loc) //Holding on person or being exactly where lies is significantly more dangerous and voids protection + if(!distance || A.loc == M || A.loc == M.loc) // Holding on person or being exactly where lies is significantly more dangerous and voids protection M.Stun(10) M.Weaken(10) if(!ear_safety) - M.Stun(max(10 / distance, 3)) - M.Weaken(max(10 / distance, 3)) + M.Stun(stun_amount) + M.Weaken(stun_amount) M.AdjustEarDamage(rand(0, 5), 15) if(iscarbon(M)) var/mob/living/carbon/C = M @@ -74,6 +85,5 @@ if(prob(ears.ear_damage - 5)) to_chat(M, "You can't hear anything!") M.BecomeDeaf() - else - if(ears.ear_damage >= 5) - to_chat(M, "Your ears start to ring!") + else if(ears.ear_damage >= 5) + to_chat(M, "Your ears start to ring!") diff --git a/code/game/objects/items/weapons/holy_weapons.dm b/code/game/objects/items/weapons/holy_weapons.dm index 6423bbeb6ff..974bd3b677f 100644 --- a/code/game/objects/items/weapons/holy_weapons.dm +++ b/code/game/objects/items/weapons/holy_weapons.dm @@ -255,7 +255,7 @@ possessed = TRUE - var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as the spirit of [user.real_name]'s blade?", ROLE_PAI, 0, 100) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as the spirit of [user.real_name]'s blade?", ROLE_PAI, FALSE, 10 SECONDS, source = src) var/mob/dead/observer/theghost = null if(candidates.len) diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm index 403088e2b71..e31e98789a3 100644 --- a/code/game/objects/items/weapons/storage/belt.dm +++ b/code/game/objects/items/weapons/storage/belt.dm @@ -204,8 +204,10 @@ /obj/item/flashlight/pen, /obj/item/seeds, /obj/item/wirecutters, - /obj/item/wrench, - ) + /obj/item/wrench, + /obj/item/reagent_containers/spray/weedspray, + /obj/item/reagent_containers/spray/pestspray + ) /obj/item/storage/belt/security name = "security belt" diff --git a/code/game/objects/items/weapons/swords_axes_etc.dm b/code/game/objects/items/weapons/swords_axes_etc.dm deleted file mode 100644 index e75935a3cc2..00000000000 --- a/code/game/objects/items/weapons/swords_axes_etc.dm +++ /dev/null @@ -1,119 +0,0 @@ -/* Weapons - * Contains: - * Banhammer - * Classic Baton - */ - -/* - * Banhammer - */ -/obj/item/banhammer/attack(mob/M, mob/user) - to_chat(M, " You have been banned FOR NO REISIN by [user]") - to_chat(user, " You have BANNED [M]") - playsound(loc, 'sound/effects/adminhelp.ogg', 15) //keep it at 15% volume so people don't jump out of their skin too much - -/* - * Classic Baton - */ - -/obj/item/melee/classic_baton - name = "police baton" - desc = "A wooden truncheon for beating criminal scum." - icon_state = "baton" - item_state = "classic_baton" - slot_flags = SLOT_BELT - force = 12 //9 hit crit - w_class = WEIGHT_CLASS_NORMAL - var/cooldown = 0 - var/on = 1 - -/obj/item/melee/classic_baton/attack(mob/target as mob, mob/living/user as mob) - if(on) - add_fingerprint(user) - if((CLUMSY in user.mutations) && prob(50)) - to_chat(user, "You club yourself over the head.") - user.Weaken(3 * force) - if(ishuman(user)) - var/mob/living/carbon/human/H = user - H.apply_damage(2*force, BRUTE, "head") - else - user.take_organ_damage(2*force) - return - if(isrobot(target)) - ..() - return - if(!isliving(target)) - return - if(user.a_intent == INTENT_HARM) - if(!..()) return - if(!isrobot(target)) return - else - if(cooldown <= 0) - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) - return - if(check_martial_counter(H, user)) - return - playsound(get_turf(src), 'sound/effects/woodhit.ogg', 75, 1, -1) - target.Weaken(3) - add_attack_logs(user, target, "Stunned with [src]") - add_fingerprint(user) - target.visible_message("[user] has knocked down [target] with \the [src]!", \ - "[user] has knocked down [target] with \the [src]!") - if(!iscarbon(user)) - target.LAssailant = null - else - target.LAssailant = user - cooldown = 1 - spawn(40) - cooldown = 0 - return - else - return ..() - -/obj/item/melee/classic_baton/ntcane - name = "fancy cane" - desc = "A cane with special engraving on it. It seems well suited for fending off assailants..." - icon_state = "cane_nt" - item_state = "cane_nt" - needs_permit = 0 - -/obj/item/melee/classic_baton/ntcane/is_crutch() - return 1 - -//Telescopic baton -/obj/item/melee/classic_baton/telescopic - name = "telescopic baton" - desc = "A compact yet robust personal defense weapon. Can be concealed when folded." - icon_state = "telebaton_0" - item_state = null - slot_flags = SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - needs_permit = 0 - force = 0 - on = 0 - -/obj/item/melee/classic_baton/telescopic/attack_self(mob/user as mob) - on = !on - if(on) - to_chat(user, "You extend the baton.") - icon_state = "telebaton_1" - item_state = "nullrod" - w_class = WEIGHT_CLASS_BULKY //doesnt fit in backpack when its on for balance - force = 10 //stunbaton damage - attack_verb = list("smacked", "struck", "cracked", "beaten") - else - to_chat(user, "You collapse the baton.") - icon_state = "telebaton_0" - item_state = null //no sprite for concealment even when in hand - slot_flags = SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - force = 0 //not so robust now - attack_verb = list("hit", "poked") - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - H.update_inv_l_hand() - H.update_inv_r_hand() - playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1) - add_fingerprint(user) diff --git a/code/game/objects/items/weapons/weaponry.dm b/code/game/objects/items/weapons/weaponry.dm index 32158a9a3ab..e5f903a1853 100644 --- a/code/game/objects/items/weapons/weaponry.dm +++ b/code/game/objects/items/weapons/weaponry.dm @@ -1,3 +1,6 @@ +/** + * # Banhammer + */ /obj/item/banhammer desc = "A banhammer" name = "banhammer" @@ -13,11 +16,15 @@ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) resistance_flags = FIRE_PROOF - /obj/item/banhammer/suicide_act(mob/user) to_chat(viewers(user), "[user] is hitting [user.p_them()]self with the [src.name]! It looks like [user.p_theyre()] trying to ban [user.p_them()]self from life.") return BRUTELOSS|FIRELOSS|TOXLOSS|OXYLOSS +/obj/item/banhammer/attack(mob/M, mob/user) + to_chat(M, " You have been banned FOR NO REISIN by [user]") + to_chat(user, " You have BANNED [M]") + playsound(loc, 'sound/effects/adminhelp.ogg', 15) //keep it at 15% volume so people don't jump out of their skin too much + /obj/item/sord name = "\improper SORD" desc = "This thing is so unspeakably shitty you are having a hard time even holding it." diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 6db1846e872..138c77f92eb 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -25,9 +25,13 @@ ..() spawn(1) if(!opened) // if closed, any item at the crate's loc is put in the contents + var/itemcount = 0 for(var/obj/item/I in loc) if(I.density || I.anchored || I == src) continue I.forceMove(src) + // Ensure the storage cap is respected + if(++itemcount >= storage_capacity) + break // Fix for #383 - C4 deleting fridges with corpses /obj/structure/closet/Destroy() diff --git a/code/game/objects/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm index cd7b3525478..33298669157 100644 --- a/code/game/objects/structures/extinguisher.dm +++ b/code/game/objects/structures/extinguisher.dm @@ -40,7 +40,7 @@ return if(!in_range(src, user)) return - if(!iscarbon(usr)) + if(!iscarbon(usr) && !isrobot(usr)) return playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) opened = !opened diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm index 824159487eb..04ee5901054 100644 --- a/code/game/verbs/ooc.dm +++ b/code/game/verbs/ooc.dm @@ -1,6 +1,6 @@ -GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") +GLOBAL_VAR_INIT(normal_ooc_colour, "#275FC5") GLOBAL_VAR_INIT(member_ooc_colour, "#035417") -GLOBAL_VAR_INIT(mentor_ooc_colour, "#0099cc") +GLOBAL_VAR_INIT(mentor_ooc_colour, "#00B0EB") GLOBAL_VAR_INIT(moderator_ooc_colour, "#184880") GLOBAL_VAR_INIT(admin_ooc_colour, "#b82e00") @@ -53,6 +53,7 @@ GLOBAL_VAR_INIT(admin_ooc_colour, "#b82e00") return log_ooc(msg, src) + mob.create_log(OOC_LOG, msg) var/display_colour = GLOB.normal_ooc_colour if(holder && !holder.fakekey) @@ -205,7 +206,7 @@ GLOBAL_VAR_INIT(admin_ooc_colour, "#b82e00") return log_looc(msg, src) - + mob.create_log(LOOC_LOG, msg) var/mob/source = mob.get_looc_source() var/list/heard = get_mobs_in_view(7, source) diff --git a/code/modules/admin/DB ban/functions.dm b/code/modules/admin/DB ban/functions.dm index 09df94dc06e..1f55fa11f70 100644 --- a/code/modules/admin/DB ban/functions.dm +++ b/code/modules/admin/DB ban/functions.dm @@ -299,7 +299,8 @@ datum/admins/proc/DB_ban_edit(var/banid = null, var/param = null) datum/admins/proc/DB_ban_unban_by_id(var/id) - if(!check_rights(R_BAN)) return + if(!check_rights(R_BAN)) + return var/sql = "SELECT ckey FROM [format_table_name("ban")] WHERE id = [id]" @@ -343,9 +344,9 @@ datum/admins/proc/DB_ban_unban_by_id(var/id) /client/proc/DB_ban_panel() set category = "Admin" set name = "Banning Panel" - set desc = "Edit admin permissions" + set desc = "DB Ban Panel" - if(!holder) + if(!check_rights(R_BAN)) return holder.DB_ban_panel() @@ -356,7 +357,8 @@ datum/admins/proc/DB_ban_unban_by_id(var/id) if(!usr.client) return - if(!check_rights(R_BAN)) return + if(!check_rights(R_BAN)) + return establish_db_connection() if(!GLOB.dbcon.IsConnected()) diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 958dedae362..07dd4975ddb 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -69,7 +69,10 @@ GLOBAL_VAR_INIT(nologevent, 0) body += "Options panel for [M]" if(M.client) body += " played by [M.client] " - body += "\[[M.client.holder ? M.client.holder.rank : "Player"]\] " + if(check_rights(R_PERMISSIONS, 0)) + body += "\[[M.client.holder ? M.client.holder.rank : "Player"]\] " + else + body += "\[[M.client.holder ? M.client.holder.rank : "Player"]\] " body += "\[" + M.client.get_exp_type(EXP_TYPE_CREW) + " as [EXP_TYPE_CREW]\]" if(isnewplayer(M)) @@ -110,17 +113,18 @@ GLOBAL_VAR_INIT(nologevent, 0) else body += "Add to Watchlist " - if(M.client) body += "| Prison | " body += "\ Send back to Lobby | " + body += "\ Erase Flavor Text | " + body += "\ Use Random Name | " var/muted = M.client.prefs.muted body += {"
Mute: - \[IC | - OOC | - PRAY | - ADMINHELP | - DEADCHAT\] - (toggle all) + \[IC | + OOC | + PRAY | + ADMINHELP | + DEADCHAT\] + (toggle all) "} var/jumptoeye = "" @@ -281,7 +285,7 @@ GLOBAL_VAR_INIT(nologevent, 0) /datum/admins/proc/vpn_whitelist() set category = "Admin" set name = "VPN Ckey Whitelist" - if(!check_rights(R_ADMIN)) + if(!check_rights(R_BAN)) return var/key = stripped_input(usr, "Enter ckey to add/remove, or leave blank to cancel:", "VPN Whitelist add/remove", max_length=32) if(key) @@ -700,7 +704,7 @@ GLOBAL_VAR_INIT(nologevent, 0) var/msg = "" if(SSticker.current_state == GAME_STATE_STARTUP) msg = " (The server is still setting up, but the round will be started as soon as possible.)" - message_admins("[usr.key] has started the game.[msg]") + message_admins("[usr.key] has started the game.[msg]") feedback_add_details("admin_verb","SN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! return 1 else @@ -761,19 +765,6 @@ GLOBAL_VAR_INIT(nologevent, 0) world.update_status() feedback_add_details("admin_verb","TR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/admins/proc/toggle_aliens() - set category = "Event" - set desc="Toggle alien mobs" - set name="Toggle Aliens" - - if(!check_rights(R_EVENT)) - return - - GLOB.aliens_allowed = !GLOB.aliens_allowed - log_admin("[key_name(usr)] toggled aliens to [GLOB.aliens_allowed].") - message_admins("[key_name_admin(usr)] toggled aliens [GLOB.aliens_allowed ? "on" : "off"].") - feedback_add_details("admin_verb","TA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - /datum/admins/proc/delay() set category = "Server" set desc="Delay the game start/end" diff --git a/code/modules/admin/admin_investigate.dm b/code/modules/admin/admin_investigate.dm index a627c96148f..527540e9490 100644 --- a/code/modules/admin/admin_investigate.dm +++ b/code/modules/admin/admin_investigate.dm @@ -33,7 +33,8 @@ /client/proc/investigate_show( subject in GLOB.investigate_log_subjects ) set name = "Investigate" set category = "Admin" - if(!holder) return + if(!check_rights(R_ADMIN)) + return switch(subject) if("notes") show_note() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index b7f6a9d6bc9..b3957a86b44 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -10,11 +10,8 @@ GLOBAL_LIST_INIT(admin_verbs_default, list( GLOBAL_LIST_INIT(admin_verbs_admin, list( /client/proc/check_antagonists, /*shows all antags*/ /datum/admins/proc/show_player_panel, - /client/proc/player_panel, /*shows an interface for all players, with links to various panels (old style)*/ /client/proc/player_panel_new, /*shows an interface for all players, with links to various panels*/ /client/proc/invisimin, /*allows our mob to go invisible/visible*/ - /datum/admins/proc/toggleenter, /*toggles whether people can join the current game*/ - /datum/admins/proc/toggleguests, /*toggles whether guests can join the current game*/ /datum/admins/proc/announce, /*priority announce something to all clients.*/ /client/proc/colorooc, /*allows us to set a custom colour for everything we say in ooc*/ /client/proc/resetcolorooc, /*allows us to set a reset our ooc color*/ @@ -23,7 +20,6 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list( /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ /client/proc/cmd_admin_pm_by_key_panel, /*admin-pm list by key*/ - /client/proc/cmd_admin_subtle_message, /*send an message to somebody as a 'voice in their head'*/ /client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/ /client/proc/cmd_admin_check_contents, /*displays the contents of an instance*/ /client/proc/cmd_admin_open_logging_view, @@ -55,36 +51,30 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list( /datum/admins/proc/PlayerNotes, /client/proc/cmd_mentor_say, /datum/admins/proc/show_player_notes, - /datum/admins/proc/vpn_whitelist, /client/proc/free_slot, /*frees slot for chosen job*/ /client/proc/toggleattacklogs, /client/proc/toggleadminlogs, /client/proc/toggledebuglogs, /client/proc/update_mob_sprite, - /client/proc/toggledrones, /client/proc/man_up, /client/proc/global_man_up, /client/proc/delbook, /client/proc/view_flagged_books, + /client/proc/view_asays, /client/proc/empty_ai_core_toggle_latejoin, /client/proc/aooc, /client/proc/freeze, - /client/proc/alt_check, /client/proc/secrets, - /client/proc/change_human_appearance_admin, /* Allows an admin to change the basic appearance of human-based mobs */ - /client/proc/change_human_appearance_self, /* Allows the human-based mob itself to change its basic appearance */ /client/proc/debug_variables, /client/proc/reset_all_tcs, /*resets all telecomms scripts*/ /client/proc/toggle_mentor_chat, /client/proc/toggle_advanced_interaction, /*toggle admin ability to interact with not only machines, but also atoms such as buttons and doors*/ - /client/proc/list_ssds_afks, - /client/proc/cmd_admin_headset_message, - /client/proc/spawn_floor_cluwne + /client/proc/list_ssds_afks )) GLOBAL_LIST_INIT(admin_verbs_ban, list( - /client/proc/unban_panel, - /client/proc/jobbans, - /client/proc/stickybanpanel + /client/proc/ban_panel, + /client/proc/stickybanpanel, + /datum/admins/proc/vpn_whitelist )) GLOBAL_LIST_INIT(admin_verbs_sounds, list( /client/proc/play_local_sound, @@ -100,7 +90,6 @@ GLOBAL_LIST_INIT(admin_verbs_event, list( /client/proc/drop_bomb, /client/proc/cinematic, /client/proc/one_click_antag, - /datum/admins/proc/toggle_aliens, /client/proc/cmd_admin_add_freeform_ai_law, /client/proc/cmd_admin_add_random_ai_law, /client/proc/make_sound, @@ -110,6 +99,7 @@ GLOBAL_LIST_INIT(admin_verbs_event, list( /client/proc/show_tip, /client/proc/cmd_admin_change_custom_event, /datum/admins/proc/access_news_network, /*allows access of newscasters*/ + /client/proc/cmd_admin_subtle_message, /*send an message to somebody as a 'voice in their head'*/ /client/proc/cmd_admin_direct_narrate, /*send text directly to a player with no padding. Useful for narratives and fluff-text*/ /client/proc/cmd_admin_world_narrate, /*sends text to all players with no padding*/ /client/proc/response_team, // Response Teams admin verb @@ -117,7 +107,10 @@ GLOBAL_LIST_INIT(admin_verbs_event, list( /client/proc/fax_panel, /client/proc/event_manager_panel, /client/proc/modify_goals, - /client/proc/outfit_manager + /client/proc/outfit_manager, + /client/proc/cmd_admin_headset_message, + /client/proc/change_human_appearance_admin, /* Allows an admin to change the basic appearance of human-based mobs */ + /client/proc/change_human_appearance_self /* Allows the human-based mob itself to change its basic appearance */ )) GLOBAL_LIST_INIT(admin_verbs_spawn, list( @@ -126,24 +119,27 @@ GLOBAL_LIST_INIT(admin_verbs_spawn, list( /client/proc/admin_deserialize )) GLOBAL_LIST_INIT(admin_verbs_server, list( + /client/proc/reload_admins, /client/proc/Set_Holiday, /datum/admins/proc/startnow, /datum/admins/proc/restart, /datum/admins/proc/delay, /datum/admins/proc/toggleaban, + /datum/admins/proc/toggleenter, /*toggles whether people can join the current game*/ + /datum/admins/proc/toggleguests, /*toggles whether guests can join the current game*/ /client/proc/toggle_log_hrefs, /client/proc/everyone_random, /datum/admins/proc/toggleAI, /client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/ - /client/proc/cmd_debug_del_all, /client/proc/cmd_debug_del_sing, - /datum/admins/proc/toggle_aliens, /client/proc/delbook, /client/proc/view_flagged_books, + /client/proc/view_asays, /client/proc/toggle_antagHUD_use, /client/proc/toggle_antagHUD_restrictions, /client/proc/set_ooc, - /client/proc/reset_ooc + /client/proc/reset_ooc, + /client/proc/toggledrones )) GLOBAL_LIST_INIT(admin_verbs_debug, list( /client/proc/cmd_admin_list_open_jobs, @@ -152,9 +148,7 @@ GLOBAL_LIST_INIT(admin_verbs_debug, list( /client/proc/debug_controller, /client/proc/cmd_debug_mob_lists, /client/proc/cmd_admin_delete, - /client/proc/cmd_debug_del_all, /client/proc/cmd_debug_del_sing, - /client/proc/reload_admins, /client/proc/restart_controller, /client/proc/enable_debug_verbs, /client/proc/toggledebuglogs, @@ -197,7 +191,7 @@ GLOBAL_LIST_INIT(admin_verbs_mod, list( /client/proc/player_panel_new, /client/proc/dsay, /datum/admins/proc/show_player_panel, - /client/proc/jobbans, + /client/proc/ban_panel, /client/proc/debug_variables /*allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify*/ )) GLOBAL_LIST_INIT(admin_verbs_mentor, list( @@ -367,19 +361,8 @@ GLOBAL_LIST_INIT(admin_verbs_ticket, list( to_chat(mob, "Invisimin on. You are now as invisible as a ghost.") mob.remove_from_all_data_huds() -/client/proc/player_panel() - set name = "Player Panel" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - holder.player_panel_old() - feedback_add_details("admin_verb","PP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - /client/proc/player_panel_new() - set name = "Player Panel New" + set name = "Player Panel" set category = "Admin" if(!check_rights(R_ADMIN|R_MOD)) @@ -401,22 +384,8 @@ GLOBAL_LIST_INIT(admin_verbs_ticket, list( feedback_add_details("admin_verb","CHA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! return -/client/proc/jobbans() - set name = "Display Job bans" - set category = "Admin" - - if(!check_rights(R_ADMIN|R_MOD)) - return - - if(config.ban_legacy_system) - holder.Jobbans() - else - holder.DB_ban_panel() - feedback_add_details("admin_verb","VJB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/unban_panel() - set name = "Unban Panel" +/client/proc/ban_panel() + set name = "Ban Panel" set category = "Admin" if(!check_rights(R_BAN)) @@ -809,7 +778,7 @@ GLOBAL_LIST_INIT(admin_verbs_ticket, list( set desc = "Allows you to change the mob appearance" set category = null - if(!check_rights(R_ADMIN)) + if(!check_rights(R_EVENT)) return if(!istype(H)) @@ -835,7 +804,7 @@ GLOBAL_LIST_INIT(admin_verbs_ticket, list( set desc = "Allows the mob to change its appearance" set category = null - if(!check_rights(R_ADMIN)) + if(!check_rights(R_EVENT)) return if(!istype(H)) @@ -980,8 +949,8 @@ GLOBAL_LIST_INIT(admin_verbs_ticket, list( else to_chat(usr, "You now won't get debug log messages") -/client/proc/man_up(mob/T as mob in GLOB.mob_list) - set category = "Admin" +/client/proc/man_up(mob/T as mob in GLOB.player_list) + set category = null set name = "Man Up" set desc = "Tells mob to man up and deal with it." diff --git a/code/modules/admin/banjob.dm b/code/modules/admin/banjob.dm index 2122f88f4ed..699eca075bb 100644 --- a/code/modules/admin/banjob.dm +++ b/code/modules/admin/banjob.dm @@ -42,20 +42,6 @@ GLOBAL_DATUM_INIT(jobban_regex, /regex, regex("(\[\\S]+) - (\[^#]+\[^# ])(?: ## else return 0 -/* -DEBUG -/mob/verb/list_all_jobbans() - set name = "list all jobbans" - - for(var/s in jobban_keylist) - to_chat(world, s) - -/mob/verb/reload_jobbans() - set name = "reload jobbans" - - jobban_loadbanfile() -*/ - /proc/jobban_loadbanfile() if(config.ban_legacy_system) var/savefile/S=new("data/job_full.ban") diff --git a/code/modules/admin/machine_upgrade.dm b/code/modules/admin/machine_upgrade.dm index 1e569b51d2d..4f38f70613d 100644 --- a/code/modules/admin/machine_upgrade.dm +++ b/code/modules/admin/machine_upgrade.dm @@ -1,20 +1,20 @@ /proc/machine_upgrade(obj/machinery/M in world) set name = "Tweak Component Ratings" - set category = "Debug" + set category = null - if(!check_rights(R_DEBUG)) + if(!check_rights(R_DEBUG)) return - + if(!istype(M)) to_chat(usr, "This can only be used on subtypes of /obj/machinery.") return - + var/new_rating = input("Enter new rating:","Num") as num if(!isnull(new_rating) && M.component_parts) for(var/obj/item/stock_parts/P in M.component_parts) P.rating = new_rating M.RefreshParts() - + message_admins("[key_name_admin(usr)] has set the component rating of [M] to [new_rating]") log_admin("[key_name(usr)] has set the component rating of [M] to [new_rating]") diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index 1c6bab4d3cd..c2248261baa 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -332,65 +332,6 @@ usr << browse(dat, "window=players;size=600x480") -//The old one -/datum/admins/proc/player_panel_old() - if(!usr.client.holder) - return - var/dat = "Player Menu" - dat += "
WhenTypeWhoWhatTargetWhere
" - //add to this if wanting to add back in IP checking - //add if you want to know their ip to the lists below - var/list/mobs = sortmobs() - - for(var/mob/M in mobs) - if(!M.ckey) continue - - dat += "" - if(isAI(M)) - dat += "" - else if(isrobot(M)) - dat += "" - else if(issmall(M)) - dat += "" - else if(ishuman(M)) - dat += "" - else if(istype(M, /mob/living/silicon/pai)) - dat += "" - else if(isnewplayer(M)) - dat += "" - else if(isobserver(M)) - dat += "" - else if(isalien(M)) - dat += "" - else - dat += "" - - - if(istype(M,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = M - if(H.mind && H.mind.assigned_role) - dat += "" - else - dat += "" - - - dat += {" - - - "} - switch(is_special_character(M)) - if(0) - dat += {""} - if(1) - dat += {""} - if(2) - dat += {""} - - dat += "
NameReal NameAssigned JobKeyOptionsPMTraitor?
IP:(IP: [M.lastKnownIP])
[M.name]AICyborgMonkey[M.real_name]pAINew PlayerGhostAlienUnknown[H.mind.assigned_role]NA[(M.client ? "[M.client]" : "No client")]XPMTraitor?Traitor?Traitor?
" - - usr << browse(dat, "window=players;size=640x480") - - /datum/admins/proc/check_antagonists_line(mob/M, caption = "", close = 1) var/logout_status diff --git a/code/modules/admin/tickets/adminticketsverbs.dm b/code/modules/admin/tickets/adminticketsverbs.dm index 406019745f8..4e8e79f2a26 100644 --- a/code/modules/admin/tickets/adminticketsverbs.dm +++ b/code/modules/admin/tickets/adminticketsverbs.dm @@ -5,7 +5,7 @@ set name = "Open Admin Ticket Interface" set category = "Admin" - if(!holder || !check_rights(R_ADMIN)) + if(!check_rights(R_ADMIN)) return SStickets.showUI(usr) @@ -14,7 +14,7 @@ set name = "Resolve All Open Admin Tickets" set category = null - if(!holder || !check_rights(R_ADMIN)) + if(!check_rights(R_ADMIN)) return if(alert("Are you sure you want to resolve ALL open admin tickets?","Resolve all open admin tickets?","Yes","No") != "Yes") diff --git a/code/modules/admin/tickets/mentorticketsverbs.dm b/code/modules/admin/tickets/mentorticketsverbs.dm index d65f4a30220..c7bec3e043d 100644 --- a/code/modules/admin/tickets/mentorticketsverbs.dm +++ b/code/modules/admin/tickets/mentorticketsverbs.dm @@ -5,7 +5,7 @@ set name = "Open Mentor Ticket Interface" set category = "Admin" - if(!holder || !check_rights(R_MENTOR|R_ADMIN)) + if(!check_rights(R_MENTOR|R_ADMIN)) return SSmentor_tickets.showUI(usr) @@ -14,7 +14,7 @@ set name = "Resolve All Open Mentor Tickets" set category = null - if(!holder || !check_rights(R_ADMIN)) + if(!check_rights(R_ADMIN)) return if(alert("Are you sure you want to resolve ALL open mentor tickets?","Resolve all open mentor tickets?","Yes","No") != "Yes") diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 1d2c75aff0a..e340921bef6 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -14,7 +14,7 @@ if(!check_rights(R_ADMIN|R_MOD)) return var/client/C = locateUID(href_list["rejectadminhelp"]) - if(!C) + if(!isclient(C)) return C << 'sound/effects/adminhelp.ogg' @@ -114,39 +114,39 @@ switch(bantype) if(BANTYPE_PERMA) if(!banckey || !banreason) - to_chat(usr, "Not enough parameters (Requires ckey and reason)") + to_chat(usr, "Not enough parameters (Requires ckey and reason)") return banduration = null banjob = null if(BANTYPE_TEMP) if(!banckey || !banreason || !banduration) - to_chat(usr, "Not enough parameters (Requires ckey, reason and duration)") + to_chat(usr, "Not enough parameters (Requires ckey, reason and duration)") return banjob = null if(BANTYPE_JOB_PERMA) if(!banckey || !banreason || !banjob) - to_chat(usr, "Not enough parameters (Requires ckey, reason and job)") + to_chat(usr, "Not enough parameters (Requires ckey, reason and job)") return banduration = null if(BANTYPE_JOB_TEMP) if(!banckey || !banreason || !banjob || !banduration) - to_chat(usr, "Not enough parameters (Requires ckey, reason and job)") + to_chat(usr, "Not enough parameters (Requires ckey, reason and job)") return if(BANTYPE_APPEARANCE) if(!banckey || !banreason) - to_chat(usr, "Not enough parameters (Requires ckey and reason)") + to_chat(usr, "Not enough parameters (Requires ckey and reason)") return banduration = null banjob = null if(BANTYPE_ADMIN_PERMA) if(!banckey || !banreason) - to_chat(usr, "Not enough parameters (Requires ckey and reason)") + to_chat(usr, "Not enough parameters (Requires ckey and reason)") return banduration = null banjob = null if(BANTYPE_ADMIN_TEMP) if(!banckey || !banreason || !banduration) - to_chat(usr, "Not enough parameters (Requires ckey, reason and duration)") + to_chat(usr, "Not enough parameters (Requires ckey, reason and duration)") return banjob = null @@ -324,8 +324,8 @@ if(!check_rights(R_SPAWN)) return var/mob/M = locateUID(href_list["mob"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return var/delmob = 0 @@ -436,18 +436,18 @@ if(!check_rights(R_BAN)) return var/mob/M = locateUID(href_list["appearanceban"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return if(!M.ckey) //sanity - to_chat(usr, "This mob has no ckey") + to_chat(usr, "This mob has no ckey") return var/ban_ckey_param = href_list["dbbanaddckey"] var/banreason = appearance_isbanned(M) if(banreason) /* if(!config.ban_legacy_system) - to_chat(usr, "Unfortunately, database based unbanning cannot be done through this panel") + to_chat(usr, "Unfortunately, database based unbanning cannot be done through this panel") DB_ban_panel(M.ckey) return */ switch(alert("Reason: '[banreason]' Remove appearance ban?","Please Confirm","Yes","No")) @@ -458,7 +458,7 @@ DB_ban_unban(M.ckey, BANTYPE_APPEARANCE) appearance_unban(M) message_admins("[key_name_admin(usr)] removed [key_name_admin(M)]'s appearance ban", 1) - to_chat(M, "[usr.client.ckey] has removed your appearance ban.") + to_chat(M, "[usr.client.ckey] has removed your appearance ban.") else switch(alert("Appearance ban [M.ckey]?",,"Yes","No", "Cancel")) if("Yes") @@ -473,7 +473,7 @@ appearance_fullban(M, "[reason]; By [usr.ckey] on [time2text(world.realtime)]") add_note(M.ckey, "Appearance banned - [reason]", null, usr.ckey, 0) message_admins("[key_name_admin(usr)] appearance banned [key_name_admin(M)]", 1) - to_chat(M, "You have been appearance banned by [usr.client.ckey].") + to_chat(M, "You have been appearance banned by [usr.client.ckey].") to_chat(M, "The reason is: [reason]") to_chat(M, "Appearance ban can be lifted only upon request.") if(config.banappeals) @@ -487,15 +487,15 @@ // if(!check_rights(R_BAN)) return var/mob/M = locateUID(href_list["jobban2"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return if(!M.ckey) //sanity - to_chat(usr, "This mob has no ckey") + to_chat(usr, "This mob has no ckey") return if(!SSjobs) - to_chat(usr, "SSjobs has not been setup!") + to_chat(usr, "SSjobs has not been setup!") return var/dat = "" @@ -735,8 +735,8 @@ if(!check_rights(R_BAN)) return var/mob/M = locateUID(href_list["jobban4"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return if(M != usr) //we can jobban ourselves @@ -747,7 +747,7 @@ var/ban_ckey_param = href_list["dbbanaddckey"] if(!SSjobs) - to_chat(usr, "SSjobs has not been setup!") + to_chat(usr, "SSjobs has not been setup!") return //get jobs for department if specified, otherwise just returnt he one job in a list. @@ -840,7 +840,7 @@ msg += ", [job]" add_note(M.ckey, "Banned from [msg] - [reason]", null, usr.ckey, 0) message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg] for [mins] minutes", 1) - to_chat(M, "You have been jobbanned by [usr.client.ckey] from: [msg].") + to_chat(M, "You have been jobbanned by [usr.client.ckey] from: [msg].") to_chat(M, "The reason is: [reason]") to_chat(M, "This jobban will be lifted in [mins] minutes.") href_list["jobban2"] = 1 // lets it fall through and refresh @@ -861,7 +861,7 @@ else msg += ", [job]" add_note(M.ckey, "Banned from [msg] - [reason]", null, usr.ckey, 0) message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg]", 1) - to_chat(M, "You have been jobbanned by [usr.client.ckey] from: [msg].") + to_chat(M, "You have been jobbanned by [usr.client.ckey] from: [msg].") to_chat(M, "The reason is: [reason]") to_chat(M, "Jobban can be lifted only upon request.") href_list["jobban2"] = 1 // lets it fall through and refresh @@ -873,7 +873,7 @@ //all jobs in joblist are banned already OR we didn't give a reason (implying they shouldn't be banned) if(joblist.len) //at least 1 banned job exists in joblist so we have stuff to unban. if(!config.ban_legacy_system) - to_chat(usr, "Unfortunately, database based unbanning cannot be done through this panel") + to_chat(usr, "Unfortunately, database based unbanning cannot be done through this panel") DB_ban_panel(M.ckey) return var/msg @@ -894,22 +894,28 @@ continue if(msg) message_admins("[key_name_admin(usr)] unbanned [key_name_admin(M)] from [msg]", 1) - to_chat(M, "You have been un-jobbanned by [usr.client.ckey] from [msg].") + to_chat(M, "You have been un-jobbanned by [usr.client.ckey] from [msg].") href_list["jobban2"] = 1 // lets it fall through and refresh return 1 return 0 //we didn't do anything! else if(href_list["boot2"]) var/mob/M = locateUID(href_list["boot2"]) - if(ismob(M)) - if(M.client && M.client.holder && (M.client.holder.rights & R_BAN)) - to_chat(usr, "[key_name_admin(M)] cannot be kicked from the server.") + if(!ismob(M)) + return + var/client/C = M.client + if(C == null) + to_chat(usr, "Mob has no client to kick.") + return + if(alert("Kick [C.ckey]?",,"Yes","No") == "Yes") + if(C && C.holder && (C.holder.rights & R_BAN)) + to_chat(usr, "[key_name_admin(C)] cannot be kicked from the server.") return - to_chat(M, "You have been kicked from the server") - log_admin("[key_name(usr)] booted [key_name(M)].") - message_admins("[key_name_admin(usr)] booted [key_name_admin(M)].", 1) - //M.client = null - qdel(M.client) + to_chat(C, "You have been kicked from the server") + log_admin("[key_name(usr)] booted [key_name(C)].") + message_admins("[key_name_admin(usr)] booted [key_name_admin(C)].", 1) + //C = null + qdel(C) else if(href_list["open_logging_view"]) var/mob/M = locateUID(href_list["open_logging_view"]) @@ -986,7 +992,7 @@ if(!check_rights(R_BAN)) return var/mob/M = locateUID(href_list["newban"]) - if(!ismob(M)) + if(!istype(M, /mob)) return var/ban_ckey_param = href_list["dbbanaddckey"] @@ -1002,7 +1008,7 @@ M = admin_ban_mobsearch(M, ban_ckey_param, usr) AddBan(M.ckey, M.computer_id, reason, usr.ckey, 1, mins) ban_unban_log_save("[usr.client.ckey] has banned [M.ckey]. - Reason: [reason] - This will be removed in [mins] minutes.") - to_chat(M, "You have been banned by [usr.client.ckey].\nReason: [reason].") + to_chat(M, "You have been banned by [usr.client.ckey].\nReason: [reason].") to_chat(M, "This is a temporary ban, it will be removed in [mins] minutes.") feedback_inc("ban_tmp",1) DB_ban_record(BANTYPE_TEMP, M, mins, reason) @@ -1022,7 +1028,7 @@ if(!reason) return AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0, M.lastKnownIP) - to_chat(M, "You have been banned by [usr.client.ckey].\nReason: [reason].") + to_chat(M, "You have been banned by [usr.client.ckey].\nReason: [reason].") to_chat(M, "This ban does not expire automatically and must be appealed.") if(M.client) M.client.link_forum_account(TRUE) @@ -1090,7 +1096,7 @@ return var/mob/M = locateUID(href_list["mute"]) - if(!ismob(M)) return + if(!istype(M, /mob)) return if(!M.client) return var/mute_type = href_list["mute_type"] @@ -1104,7 +1110,7 @@ if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - var/dat = {"What mode do you wish to play?
"} + var/dat = {"What mode do you wish to play?
"} for(var/mode in config.modes) dat += {"[config.mode_names[mode]]
"} dat += {"Secret
"} @@ -1119,7 +1125,7 @@ return alert(usr, "The game has already started.", null, null, null, null) if(GLOB.master_mode != "secret") return alert(usr, "The game mode has to be secret!", null, null, null, null) - var/dat = {"What game mode do you want to force secret to be? Use this if you want to change the game mode, but want the players to believe it's secret. This will only work if the current game mode is secret.
"} + var/dat = {"What game mode do you want to force secret to be? Use this if you want to change the game mode, but want the players to believe it's secret. This will only work if the current game mode is secret.
"} for(var/mode in config.modes) dat += {"[config.mode_names[mode]]
"} dat += {"Random (default)
"} @@ -1157,7 +1163,7 @@ var/mob/living/carbon/human/H = locateUID(href_list["monkeyone"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(alert(usr, "Confirm make monkey?",, "Yes", "No") != "Yes") return @@ -1172,7 +1178,7 @@ var/mob/living/carbon/human/H = locateUID(href_list["corgione"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(alert(usr, "Confirm make corgi?",, "Yes", "No") != "Yes") @@ -1187,7 +1193,7 @@ var/mob/living/carbon/human/H = locateUID(href_list["makePAI"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(alert(usr, "Confirm make pai?",, "Yes", "No") != "Yes") return @@ -1210,8 +1216,9 @@ if(!check_rights(R_SERVER|R_EVENT)) return var/mob/M = locateUID(href_list["forcespeech"]) - if(!ismob(M)) - to_chat(usr, "this can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return var/speech = input("What will [key_name(M)] say?.", "Force speech", "")// Don't need to sanitize, since it does that in say(), we also trust our admins. if(!speech) return @@ -1227,11 +1234,11 @@ return var/mob/M = locateUID(href_list["sendtoprison"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") return var/turf/prison_cell = pick(GLOB.prisonwarp) @@ -1290,6 +1297,71 @@ NP.ckey = M.ckey qdel(M) + else if(href_list["eraseflavortext"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locateUID(href_list["eraseflavortext"]) + + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + if(!M.client) + to_chat(usr, "[M] doesn't seem to have an active client.") + return + + if(M.flavor_text == "" && M.client.prefs.flavor_text == "") + to_chat(usr, "[M] has no flavor text set.") + return + + if(alert(usr, "Erase [key_name(M)]'s flavor text?", "Message", "Yes", "No") != "Yes") + return + + log_admin("[key_name(usr)] has erased [key_name(M)]'s flavor text.") + message_admins("[key_name_admin(usr)] has erased [key_name_admin(M)]'s flavor text.") + + // Clears the mob's flavor text + M.flavor_text = "" + + // Clear and save the DB character's flavor text + M.client.prefs.flavor_text = "" + M.client.prefs.save_character(M.client) + + else if(href_list["userandomname"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locateUID(href_list["userandomname"]) + + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + if(!M.client) + to_chat(usr, "[M] doesn't seem to have an active client.") + return + + if(alert(usr, "Force [key_name(M)] to use a random name?", "Message", "Yes", "No") != "Yes") + return + + log_admin("[key_name(usr)] has forced [key_name(M)] to use a random name.") + message_admins("[key_name_admin(usr)] has forced [key_name_admin(M)] to use a random name.") + + // Update the mob's name with a random one straight away + var/random_name = random_name(M.client.prefs.gender, M.client.prefs.species) + M.rename_character(M.real_name, random_name) + + // Save that random name for next rounds + M.client.prefs.real_name = random_name + M.client.prefs.save_character(M.client) + + else if(href_list["asays"]) + if(!check_rights(R_ADMIN)) + return + + usr.client.view_asays() + else if(href_list["tdome1"]) if(!check_rights(R_SERVER|R_EVENT)) return @@ -1297,11 +1369,11 @@ return var/mob/M = locateUID(href_list["tdome1"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") return for(var/obj/item/I in M) @@ -1327,11 +1399,11 @@ return var/mob/M = locateUID(href_list["tdome2"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") return for(var/obj/item/I in M) @@ -1357,11 +1429,11 @@ return var/mob/M = locateUID(href_list["tdomeadmin"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") return M.Paralyse(5) @@ -1379,11 +1451,11 @@ return var/mob/M = locateUID(href_list["tdomeobserve"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") return for(var/obj/item/I in M) @@ -1413,11 +1485,11 @@ return var/mob/M = locateUID(href_list["aroomwarp"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") return M.Paralyse(5) @@ -1434,7 +1506,7 @@ var/mob/living/L = locateUID(href_list["revive"]) if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /mob/living") + to_chat(usr, "This can only be used on instances of type /mob/living") return L.revive() @@ -1446,7 +1518,7 @@ var/mob/living/carbon/human/H = locateUID(href_list["makeai"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(alert(usr, "Confirm make ai?",, "Yes", "No") != "Yes") @@ -1462,7 +1534,7 @@ var/mob/living/carbon/human/H = locateUID(href_list["makealien"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(alert(usr, "Confirm make alien?",, "Yes", "No") != "Yes") return @@ -1474,7 +1546,7 @@ var/mob/living/carbon/human/H = locateUID(href_list["makeslime"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(alert(usr, "Confirm make slime?",, "Yes", "No") != "Yes") return @@ -1486,7 +1558,7 @@ var/mob/living/carbon/human/H = locateUID(href_list["makesuper"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(alert(usr, "Confirm make superhero?",, "Yes", "No") != "Yes") @@ -1499,7 +1571,7 @@ var/mob/living/carbon/human/H = locateUID(href_list["makerobot"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(alert(usr, "Confirm make robot?",, "Yes", "No") != "Yes") return @@ -1511,7 +1583,7 @@ var/mob/M = locateUID(href_list["makeanimal"]) if(isnewplayer(M)) - to_chat(usr, "This cannot be used on instances of type /mob/new_player") + to_chat(usr, "This cannot be used on instances of type /mob/new_player") return if(alert(usr, "Confirm make animal?",, "Yes", "No") != "Yes") return @@ -1524,10 +1596,14 @@ var/mob/dead/observer/G = locateUID(href_list["incarn_ghost"]) if(!istype(G)) - to_chat(usr, "This will only work on /mob/dead/observer") + to_chat(usr, "This will only work on /mob/dead/observer") + return var/posttransformoutfit = usr.client.robust_dress_shop() + if(!posttransformoutfit) + return + var/mob/living/carbon/human/H = G.incarnate_ghost() if(posttransformoutfit && istype(H)) @@ -1541,7 +1617,7 @@ var/mob/living/carbon/human/H = locateUID(href_list["togmutate"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return var/block=text2num(href_list["block"]) //testing("togmutate([href_list["block"]] -> [block])") @@ -1551,6 +1627,11 @@ else if(href_list["adminplayeropts"]) var/mob/M = locateUID(href_list["adminplayeropts"]) + + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return + show_player_panel(M) else if(href_list["adminplayerobservefollow"]) @@ -1560,6 +1641,11 @@ return C.admin_ghost() var/mob/M = locateUID(href_list["adminplayerobservefollow"]) + + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return + var/mob/dead/observer/A = C.mob sleep(2) A.ManualFollow(M) @@ -1588,6 +1674,12 @@ return SStickets.autoRespond(index) + if(href_list["convert_ticket"]) + var/indexNum = text2num(href_list["convert_ticket"]) + if(href_list["is_mhelp"]) + SSmentor_tickets.convert_to_other_ticket(indexNum) + else + SStickets.convert_to_other_ticket(indexNum) else if(href_list["cult_nextobj"]) if(alert(usr, "Validate the current Cult objective and unlock the next one?", "Cult Cheat Code", "Yes", "No") != "Yes") return @@ -1608,10 +1700,10 @@ for(var/datum/mind/H in SSticker.mode.cult) if (H.current) - to_chat(H.current, "[SSticker.cultdat.entity_name] murmurs, [input]") + to_chat(H.current, "[SSticker.cultdat.entity_name] murmurs, [input]") for(var/mob/dead/observer/O in GLOB.player_list) - to_chat(O, "[SSticker.cultdat.entity_name] murmurs, [input]") + to_chat(O, "[SSticker.cultdat.entity_name] murmurs, [input]") message_admins("Admin [key_name_admin(usr)] has talked with the Voice of [SSticker.cultdat.entity_name].") log_admin("[key_name(usr)] Voice of [SSticker.cultdat.entity_name]: [input]") @@ -1633,6 +1725,11 @@ else if(href_list["adminmoreinfo"]) var/mob/M = locateUID(href_list["adminmoreinfo"]) + + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return + admin_mob_info(M) else if(href_list["adminspawncookie"]) @@ -1640,7 +1737,7 @@ var/mob/living/carbon/human/H = locateUID(href_list["adminspawncookie"]) if(!ishuman(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return H.equip_to_slot_or_del( new /obj/item/reagent_containers/food/snacks/cookie(H), slot_l_hand ) @@ -1664,7 +1761,7 @@ var/mob/living/M = locateUID(href_list["BlueSpaceArtillery"]) if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living") + to_chat(usr, "This can only be used on instances of type /mob/living") return if(alert(owner, "Are you sure you wish to hit [key_name(M)] with Bluespace Artillery?", "Confirm Firing?" , "Yes" , "No") != "Yes") @@ -1702,6 +1799,11 @@ return var/mob/M = locateUID(href_list["CentcommReply"]) + + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return + usr.client.admin_headset_message(M, "Centcomm") else if(href_list["SyndicateReply"]) @@ -1709,6 +1811,11 @@ return var/mob/M = locateUID(href_list["SyndicateReply"]) + + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return + usr.client.admin_headset_message(M, "Syndicate") else if(href_list["HeadsetMessage"]) @@ -1716,6 +1823,11 @@ return var/mob/M = locateUID(href_list["HeadsetMessage"]) + + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return + usr.client.admin_headset_message(M) else if(href_list["EvilFax"]) @@ -1723,7 +1835,7 @@ return var/mob/living/carbon/human/H = locateUID(href_list["EvilFax"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return var/etypes = list("Borgification", "Corgification", "Death By Fire", "Total Brain Death", "Honk Tumor", "Cluwne", "Demote", "Demote with Bot", "Revoke Fax Access", "Angry Fax Machine") var/eviltype = input(src.owner, "Which type of evil fax do you wish to send [H]?","Its good to be baaaad...", "") as null|anything in etypes @@ -1757,12 +1869,12 @@ P.ico = new P.ico += "paper_stamp-[stampvalue]" P.overlays += stampoverlay - P.stamps += "
" + P.stamps += "
" P.update_icon() P.faxmachineid = fax.UID() P.loc = fax.loc // Do not use fax.receivefax(P) here, as it won't preserve the type. Physically teleporting the fax paper is required. if(istype(H) && H.stat == CONSCIOUS && (istype(H.l_ear, /obj/item/radio/headset) || istype(H.r_ear, /obj/item/radio/headset))) - to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") + to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") to_chat(src.owner, "You sent a [eviltype] fax to [H]") log_admin("[key_name(src.owner)] sent [key_name(H)] a [eviltype] fax") message_admins("[key_name_admin(src.owner)] replied to [key_name_admin(H)] with a [eviltype] fax") @@ -1771,7 +1883,7 @@ return var/mob/living/M = locateUID(href_list["Bless"]) if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob/living") + to_chat(usr, "This can only be used on instances of type /mob/living") return var/btypes = list("To Arrivals", "Moderate Heal") var/mob/living/carbon/human/H @@ -1838,7 +1950,7 @@ var/petchoice = input("Select pet type", "Pets") as null|anything in pets if(isnull(petchoice)) return - var/list/mob/dead/observer/candidates = pollCandidates("Play as the special event pet [H]?", poll_time = 200, min_hours = 10) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Play as the special event pet [H]?", poll_time = 20 SECONDS, min_hours = 10, source = petchoice) var/mob/dead/observer/theghost = null if(candidates.len) var/mob/living/simple_animal/pet/P = new petchoice(H.loc) @@ -1891,7 +2003,7 @@ var/mob/living/M = locateUID(href_list["Smite"]) var/mob/living/carbon/human/H if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob/living") + to_chat(usr, "This can only be used on instances of type /mob/living") return var/ptypes = list("Lightning bolt", "Fire Death", "Gib") if(ishuman(M)) @@ -2002,7 +2114,7 @@ var/datum/antagonist/traitor/T = new() T.give_objectives = FALSE to_chat(newtraitormind.current, "ATTENTION: It is time to pay your debt to the Syndicate...") - to_chat(newtraitormind.current, "Goal: KILL [H.real_name], currently in [get_area(H.loc)]") + to_chat(newtraitormind.current, "Goal: KILL [H.real_name], currently in [get_area(H.loc)]") newtraitormind.add_antag_datum(T) else to_chat(usr, "ERROR: Unable to find any valid candidate to send after [H].") @@ -2031,10 +2143,10 @@ return var/mob/living/carbon/human/H = locateUID(href_list["cryossd"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(!href_list["cryoafk"] && !isLivingSSD(H)) - to_chat(usr, "This can only be used on living, SSD players.") + to_chat(usr, "This can only be used on living, SSD players.") return if(istype(H.loc, /obj/machinery/cryopod)) var/obj/machinery/cryopod/P = H.loc @@ -2054,28 +2166,28 @@ return var/mob/living/carbon/human/H = locateUID(href_list["FaxReplyTemplate"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return var/obj/item/paper/P = new /obj/item/paper(null) var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) P.name = "Central Command - paper" var/stypes = list("Handle it yourselves!","Illegible fax","Fax not signed","Not Right Now","You are wasting our time", "Keep up the good work", "ERT Instructions") var/stype = input(src.owner, "Which type of standard reply do you wish to send to [H]?","Choose your paperwork", "") as null|anything in stypes - var/tmsg = "



Nanotrasen Science Station [GLOB.using_map.station_short]


NAS Trurl Communications Department Report


" + var/tmsg = "



Nanotrasen Science Station [GLOB.using_map.station_short]


NAS Trurl Communications Department Report


" if(stype == "Handle it yourselves!") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

Please proceed in accordance with Standard Operating Procedure and/or Space Law. You are fully trained to handle this situation without Central Command intervention.

This is an automatic message." + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

Please proceed in accordance with Standard Operating Procedure and/or Space Law. You are fully trained to handle this situation without Central Command intervention.

This is an automatic message." else if(stype == "Illegible fax") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

Your fax's grammar, syntax and/or typography are of a sub-par level and do not allow us to understand the contents of the message.

Please consult your nearest dictionary and/or thesaurus and try again.

This is an automatic message." + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

Your fax's grammar, syntax and/or typography are of a sub-par level and do not allow us to understand the contents of the message.

Please consult your nearest dictionary and/or thesaurus and try again.

This is an automatic message." else if(stype == "Fax not signed") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

Your fax has not been correctly signed and, as such, we cannot verify your identity.

Please sign your faxes before sending them so that we may verify your identity.

This is an automatic message." + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

Your fax has not been correctly signed and, as such, we cannot verify your identity.

Please sign your faxes before sending them so that we may verify your identity.

This is an automatic message." else if(stype == "Not Right Now") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

Due to pressing concerns of a matter above your current paygrade, we are unable to provide assistance in whatever matter your fax referenced.

This can be either due to a power outage, bureaucratic audit, pest infestation, Ascendance Event, corgi outbreak, or any other situation that would affect the proper functioning of the NAS Trurl.

Please try again later.

This is an automatic message." + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

Due to pressing concerns of a matter above your current paygrade, we are unable to provide assistance in whatever matter your fax referenced.

This can be either due to a power outage, bureaucratic audit, pest infestation, Ascendance Event, corgi outbreak, or any other situation that would affect the proper functioning of the NAS Trurl.

Please try again later.

This is an automatic message." else if(stype == "You are wasting our time") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

In the interest of preventing further mismanagement of company resources, please avoid wasting our time with such petty drivel.

Do kindly remember that we expect our workforce to maintain at least a semi-decent level of profesionalism. Do not test our patience.

This is an automatic message." + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

In the interest of preventing further mismanagement of company resources, please avoid wasting our time with such petty drivel.

Do kindly remember that we expect our workforce to maintain at least a semi-decent level of profesionalism. Do not test our patience.

This is an automatic message." else if(stype == "Keep up the good work") tmsg += "Greetings, esteemed crewmember. Your fax has been received successfully by NAS Trurl Fax Registration.

We at the NAS Trurl appreciate the good work that you have done here, and sincerely recommend that you continue such a display of dedication to the company.

This is absolutely not an automated message." else if(stype == "ERT Instructions") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

Please utilize the Card Swipers if you wish to call for an ERT.

This is an automated message." + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

Please utilize the Card Swipers if you wish to call for an ERT.

This is an automated message." else return tmsg += "
" @@ -2096,11 +2208,11 @@ P.ico = new P.ico += "paper_stamp-[stampvalue]" P.overlays += stampoverlay - P.stamps += "
" + P.stamps += "
" P.update_icon() fax.receivefax(P) if(istype(H) && H.stat == CONSCIOUS && (istype(H.l_ear, /obj/item/radio/headset) || istype(H.r_ear, /obj/item/radio/headset))) - to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") + to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") to_chat(src.owner, "You sent a standard '[stype]' fax to [H]") log_admin("[key_name(src.owner)] sent [key_name(H)] a standard '[stype]' fax") message_admins("[key_name_admin(src.owner)] replied to [key_name_admin(H)] with a standard '[stype]' fax") @@ -2108,10 +2220,10 @@ else if(href_list["HONKReply"]) var/mob/living/carbon/human/H = locateUID(href_list["HONKReply"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(!istype(H.l_ear, /obj/item/radio/headset) && !istype(H.r_ear, /obj/item/radio/headset)) - to_chat(usr, "The person you are trying to contact is not wearing a headset") + to_chat(usr, "The person you are trying to contact is not wearing a headset") return var/input = input(src.owner, "Please enter a message to reply to [key_name(H)] via [H.p_their()] headset.","Outgoing message from HONKplanet", "") @@ -2128,13 +2240,13 @@ if(alert(src.owner, "Accept or Deny ERT request?", "CentComm Response", "Accept", "Deny") == "Deny") var/mob/living/carbon/human/H = locateUID(href_list["ErtReply"]) if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") return if(H.stat != 0) - to_chat(usr, "The person you are trying to contact is not conscious.") + to_chat(usr, "The person you are trying to contact is not conscious.") return if(!istype(H.l_ear, /obj/item/radio/headset) && !istype(H.r_ear, /obj/item/radio/headset)) - to_chat(usr, "The person you are trying to contact is not wearing a headset") + to_chat(usr, "The person you are trying to contact is not wearing a headset") return var/input = input(src.owner, "Please enter a reason for denying [key_name(H)]'s ERT request.","Outgoing message from CentComm", "") @@ -2142,7 +2254,7 @@ GLOB.ert_request_answered = TRUE to_chat(src.owner, "You sent [input] to [H] via a secure channel.") log_admin("[src.owner] denied [key_name(H)]'s ERT request with the message [input].") - to_chat(H, "Incoming priority transmission from Central Command. Message as follows, Your ERT request has been denied for the following reasons: [input].") + to_chat(H, "Incoming priority transmission from Central Command. Message as follows, Your ERT request has been denied for the following reasons: [input].") else src.owner.response_team() @@ -2297,14 +2409,14 @@ P.stamped = new P.stamped += /obj/item/stamp/centcom P.overlays += stampoverlay - P.stamps += "
" + P.stamps += "
" else if(stamptype == "text") if(!P.stamped) P.stamped = new P.stamped += /obj/item/stamp P.overlays += stampoverlay - P.stamps += "
[stampvalue]" + P.stamps += "
[stampvalue]" if(destination != "All Departments") if(!fax.receivefax(P)) @@ -2334,7 +2446,7 @@ if(notify == "Yes") var/mob/living/carbon/human/H = sender if(istype(H) && H.stat == CONSCIOUS && (istype(H.l_ear, /obj/item/radio/headset) || istype(H.r_ear, /obj/item/radio/headset))) - to_chat(sender, "Your headset pings, notifying you that a reply to your fax has arrived.") + to_chat(sender, "Your headset pings, notifying you that a reply to your fax has arrived.") if(sender) log_admin("[key_name(src.owner)] replied to a fax message from [key_name(sender)]: [input]") message_admins("[key_name_admin(src.owner)] replied to a fax message from [key_name_admin(sender)] (VIEW).", 1) @@ -2353,8 +2465,8 @@ if(!check_rights(R_ADMIN)) return var/mob/M = locateUID(href_list["getplaytimewindow"]) - if(!M) - to_chat(usr, "ERROR: Mob not found.") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return cmd_mentor_show_exp_panel(M.client) @@ -2362,6 +2474,9 @@ if(!check_rights(R_ADMIN)) return var/mob/M = locateUID(href_list["jumpto"]) + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return usr.client.jumptomob(M) else if(href_list["getmob"]) @@ -2369,24 +2484,37 @@ if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") return var/mob/M = locateUID(href_list["getmob"]) + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return usr.client.Getmob(M) else if(href_list["sendmob"]) if(!check_rights(R_ADMIN)) return var/mob/M = locateUID(href_list["sendmob"]) + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return usr.client.sendmob(M) else if(href_list["narrateto"]) if(!check_rights(R_ADMIN)) return var/mob/M = locateUID(href_list["narrateto"]) + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return usr.client.cmd_admin_direct_narrate(M) else if(href_list["subtlemessage"]) - if(!check_rights(R_ADMIN)) return + if(!check_rights(R_EVENT)) + return var/mob/M = locateUID(href_list["subtlemessage"]) + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") + return usr.client.cmd_admin_subtle_message(M) else if(href_list["traitor"]) @@ -2397,8 +2525,8 @@ return var/mob/M = locateUID(href_list["traitor"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.") + if(!istype(M, /mob)) + to_chat(usr, "This can only be used on instances of type /mob") return show_traitor_panel(M) @@ -2467,7 +2595,7 @@ switch(where) if("inhand") if(!iscarbon(usr) && !isrobot(usr)) - to_chat(usr, "Can only spawn in hand when you're a carbon mob or cyborg.") + to_chat(usr, "Can only spawn in hand when you're a carbon mob or cyborg.") where = "onfloor" target = usr @@ -2479,10 +2607,10 @@ target = locate(loc.x + X,loc.y + Y,loc.z + Z) if("inmarked") if(!marked_datum) - to_chat(usr, "You don't have any object marked. Abandoning spawn.") + to_chat(usr, "You don't have any object marked. Abandoning spawn.") return else if(!istype(marked_datum,/atom)) - to_chat(usr, "The object you have marked cannot be used as a target. Target must be of type /atom. Abandoning spawn.") + to_chat(usr, "The object you have marked cannot be used as a target. Target must be of type /atom. Abandoning spawn.") return else target = marked_datum @@ -2547,7 +2675,7 @@ message_admins("[key_name_admin(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") log_admin("[key_name(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") else - to_chat(usr, "You may only use this when the game is running.") + to_chat(usr, "You may only use this when the game is running.") else if(href_list["memoeditlist"]) if(!check_rights(R_SERVER)) return @@ -2627,7 +2755,7 @@ feedback_add_details("admin_secrets_fun_used","TriAI") if("gravity") if(!(SSticker && SSticker.mode)) - to_chat(usr, "Please wait until the game starts! Not sure how it will work otherwise.") + to_chat(usr, "Please wait until the game starts! Not sure how it will work otherwise.") return GLOB.gravity_is_on = !GLOB.gravity_is_on for(var/area/A in world) @@ -2964,7 +3092,7 @@ if(usr) log_admin("[key_name(usr)] used secret [href_list["secretsfun"]]") if(ok) - to_chat(world, text("A secret has been activated by []!", usr.key)) + to_chat(world, text("A secret has been activated by []!", usr.key)) else if(href_list["secretsadmin"]) if(!check_rights(R_ADMIN)) return @@ -2972,17 +3100,17 @@ var/ok = 0 switch(href_list["secretsadmin"]) if("list_signalers") - var/dat = "Showing last [length(GLOB.lastsignalers)] signalers.
" + var/dat = "Showing last [length(GLOB.lastsignalers)] signalers.
" for(var/sig in GLOB.lastsignalers) dat += "[sig]
" usr << browse(dat, "window=lastsignalers;size=800x500") if("list_lawchanges") - var/dat = "Showing last [length(GLOB.lawchanges)] law changes.
" + var/dat = "Showing last [length(GLOB.lawchanges)] law changes.
" for(var/sig in GLOB.lawchanges) dat += "[sig]
" usr << browse(dat, "window=lawchanges;size=800x500") if("list_job_debug") - var/dat = "Job Debug info.
" + var/dat = "Job Debug info.
" if(SSjobs) for(var/line in SSjobs.job_debug) dat += "[line]
" @@ -3000,7 +3128,7 @@ alert("The game mode is [SSticker.mode.name]") else alert("For some reason there's a ticker, but not a game mode") if("manifest") - var/dat = "Showing Crew Manifest.
" + var/dat = "Showing Crew Manifest.
" dat += "" for(var/thing in GLOB.human_list) var/mob/living/carbon/human/H = thing @@ -3011,7 +3139,7 @@ if("check_antagonist") check_antagonists() if("DNA") - var/dat = "Showing DNA from blood.
" + var/dat = "Showing DNA from blood.
" dat += "
NamePosition
" for(var/thing in GLOB.human_list) var/mob/living/carbon/human/H = thing @@ -3020,7 +3148,7 @@ dat += "
NameDNABlood Type
" usr << browse(dat, "window=DNA;size=440x410") if("fingerprints") - var/dat = "Showing Fingerprints.
" + var/dat = "Showing Fingerprints.
" dat += "" for(var/thing in GLOB.human_list) var/mob/living/carbon/human/H = thing @@ -3055,14 +3183,14 @@ if(usr) log_admin("[key_name(usr)] used secret [href_list["secretsadmin"]]") if(ok) - to_chat(world, text("A secret has been activated by []!", usr.key)) + to_chat(world, text("A secret has been activated by []!", usr.key)) else if(href_list["secretscoder"]) if(!check_rights(R_DEBUG)) return switch(href_list["secretscoder"]) if("spawn_objects") - var/dat = "Admin Log
" + var/dat = "Admin Log
" for(var/l in GLOB.admin_log) dat += "
  • [l]
  • " if(!GLOB.admin_log.len) @@ -3232,27 +3360,27 @@ else if(href_list["ac_censor_channel_author"]) var/datum/feed_channel/FC = locate(href_list["ac_censor_channel_author"]) - if(FC.author != "\[REDACTED\]") + if(FC.author != "\[REDACTED\]") FC.backup_author = FC.author - FC.author = "\[REDACTED\]" + FC.author = "\[REDACTED\]" else FC.author = FC.backup_author src.access_news_network() else if(href_list["ac_censor_channel_story_author"]) var/datum/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"]) - if(MSG.author != "\[REDACTED\]") + if(MSG.author != "\[REDACTED\]") MSG.backup_author = MSG.author - MSG.author = "\[REDACTED\]" + MSG.author = "\[REDACTED\]" else MSG.author = MSG.backup_author src.access_news_network() else if(href_list["ac_censor_channel_story_body"]) var/datum/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"]) - if(MSG.body != "\[REDACTED\]") + if(MSG.body != "\[REDACTED\]") MSG.backup_body = MSG.body - MSG.body = "\[REDACTED\]" + MSG.body = "\[REDACTED\]" else MSG.body = MSG.backup_body src.access_news_network() @@ -3448,14 +3576,15 @@ return var/datum/outfit/O = hunter_outfits[dresscode] message_admins("[key_name_admin(mob)] is sending a ([dresscode]) to [killthem ? "assassinate" : "protect"] [key_name_admin(H)]...") - var/list/candidates = pollCandidates("Play as a [killthem ? "murderous" : "protective"] [dresscode]?", ROLE_TRAITOR, 1) + var/mutable_appearance/source = new('icons/obj/cardboard_cutout.dmi', "cutout_traitor") + var/list/candidates = SSghost_spawns.poll_candidates("Play as a [killthem ? "murderous" : "protective"] [dresscode]?", ROLE_TRAITOR, TRUE, source = source) if(!candidates.len) - to_chat(usr, "ERROR: Could not create eventmob. No valid candidates.") + to_chat(usr, "ERROR: Could not create eventmob. No valid candidates.") return var/mob/C = pick(candidates) var/key_of_hunter = C.key if(!key_of_hunter) - to_chat(usr, "ERROR: Could not create eventmob. Could not pick key.") + to_chat(usr, "ERROR: Could not create eventmob. Could not pick key.") return var/datum/mind/hunter_mind = new /datum/mind(key_of_hunter) hunter_mind.active = 1 @@ -3486,9 +3615,9 @@ hunter_mind.objectives += protect_objective SSticker.mode.traitors |= hunter_mob.mind to_chat(hunter_mob, "ATTENTION: You are now on a mission!") - to_chat(hunter_mob, "Goal: [killthem ? "MURDER" : "PROTECT"] [H.real_name], currently in [get_area(H.loc)]. ") + to_chat(hunter_mob, "Goal: [killthem ? "MURDER" : "PROTECT"] [H.real_name], currently in [get_area(H.loc)]."); if(killthem) - to_chat(hunter_mob, "If you kill [H.p_them()], [H.p_they()] cannot be revived.") + to_chat(hunter_mob, "If you kill [H.p_them()], [H.p_they()] cannot be revived."); hunter_mob.mind.special_role = SPECIAL_ROLE_TRAITOR var/datum/atom_hud/antag/tatorhud = GLOB.huds[ANTAG_HUD_TRAITOR] tatorhud.join_hud(hunter_mob) diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index f74b8c9d030..0b88d6030bb 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -80,7 +80,7 @@ GLOBAL_LIST_INIT(adminhelp_ignored_words, list("unknown","the","a","an","of","mo mobs_found += found if(!ai_found && isAI(found)) ai_found = 1 - msg += "[original_word] " + msg += "[original_word] " continue msg += "[original_word] " @@ -125,22 +125,28 @@ GLOBAL_LIST_INIT(adminhelp_ignored_words, list("unknown","the","a","an","of","mo ticketNum = T.ticketNum // ticketNum is the number of their ticket. T.addResponse(src, msg) - msg = "[span][selected_type]: [key_name(src, TRUE, selected_type)] ([ADMIN_QUE(mob,"?")]) ([ADMIN_PP(mob,"PP")]) ([ADMIN_VV(mob,"VV")]) ([ADMIN_TP(mob,"TP")]) ([ADMIN_SM(mob,"SM")]) ([admin_jump_link(mob)]) (TICKET) [ai_found ? "(CL)" : ""] (TAKE) (RESOLVE) [isMhelp ? "" : "(AUTO)"] : [span][msg]" + var/finalised_msg = "[span][selected_type]: [key_name(src, TRUE, selected_type)] " + finalised_msg += "([ADMIN_QUE(mob,"?")]) ([ADMIN_PP(mob,"PP")]) ([ADMIN_VV(mob,"VV")]) ([ADMIN_TP(mob,"TP")]) ([ADMIN_SM(mob,"SM")]) " + finalised_msg += "([admin_jump_link(mob)]) (TICKET) " + finalised_msg += "[ai_found ? "(CL)" : ""] (TAKE) " + finalised_msg += "(RESOLVE) [isMhelp ? "" : "(AUTO)"] " + finalised_msg += "(CONVERT) : [span][msg]" + if(isMhelp) //Open a new adminticket and inform the user. - SSmentor_tickets.newTicket(src, prunedmsg, msg) + SSmentor_tickets.newTicket(src, prunedmsg, finalised_msg) for(var/client/X in mentorholders + modholders + adminholders) if(X.prefs.sound & SOUND_MENTORHELP) - X << 'sound/effects/adminhelp.ogg' - to_chat(X, msg) + SEND_SOUND(X, 'sound/effects/adminhelp.ogg') + to_chat(X, finalised_msg) else //Ahelp //Open a new adminticket and inform the user. - SStickets.newTicket(src, prunedmsg, msg) + SStickets.newTicket(src, prunedmsg, finalised_msg) for(var/client/X in modholders + adminholders) if(X.prefs.sound & SOUND_ADMINHELP) - X << 'sound/effects/adminhelp.ogg' + SEND_SOUND(X, 'sound/effects/adminhelp.ogg') window_flash(X) - to_chat(X, msg) + to_chat(X, finalised_msg) diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm index ca83b5bad08..e5e5dae03b5 100644 --- a/code/modules/admin/verbs/adminjump.dm +++ b/code/modules/admin/verbs/adminjump.dm @@ -117,7 +117,7 @@ feedback_add_details("admin_verb","JK") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/Getmob(var/mob/M in GLOB.mob_list) - set category = "Admin" + set category = null set name = "Get Mob" set desc = "Mob to teleport" @@ -134,7 +134,7 @@ feedback_add_details("admin_verb","GM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/Getkey() - set category = "Admin" + set category = null set name = "Get Key" set desc = "Key to teleport" diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index dba9a5c26aa..ce5f06053eb 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -2,19 +2,19 @@ /client/proc/cmd_admin_pm_context(mob/M as mob in GLOB.mob_list) set category = null set name = "Admin PM Mob" - if(!holder) - to_chat(src, "Error: Admin-PM-Context: Only administrators may use this command.") + if(!check_rights(R_ADMIN|R_MENTOR)) + return + if(!ismob(M) || !M.client) return - if( !ismob(M) || !M.client ) return cmd_admin_pm(M.client,null) feedback_add_details("admin_verb","APMM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + //shows a list of clients we could send PMs to, then forwards our choice to cmd_admin_pm /client/proc/cmd_admin_pm_panel() set category = "Admin" set name = "Admin PM Name" - if(!holder) - to_chat(src, "Error: Admin-PM-Panel: Only administrators may use this command.") + if(!check_rights(R_ADMIN|R_MENTOR)) return var/list/client/targets[0] for(var/client/T) @@ -36,8 +36,7 @@ /client/proc/cmd_admin_pm_by_key_panel() set category = "Admin" set name = "Admin PM Key" - if(!holder) - to_chat(src, "Error: Admin-PM-Panel: Only administrators may use this command.") + if(!check_rights(R_ADMIN|R_MENTOR)) return var/list/client/targets[0] for(var/client/T) diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm index 5a0425af30a..431633309a9 100644 --- a/code/modules/admin/verbs/adminsay.dm +++ b/code/modules/admin/verbs/adminsay.dm @@ -7,6 +7,8 @@ msg = sanitize(copytext(msg, 1, MAX_MESSAGE_LEN)) if(!msg) return + var/datum/asays/asay = new(usr.ckey, usr.client.holder.rank, msg, world.timeofday) + GLOB.asays += asay log_adminsay(msg, src) if(check_rights(R_ADMIN,0)) diff --git a/code/modules/admin/verbs/alt_check.dm b/code/modules/admin/verbs/alt_check.dm deleted file mode 100644 index c8060f22036..00000000000 --- a/code/modules/admin/verbs/alt_check.dm +++ /dev/null @@ -1,20 +0,0 @@ -/client/proc/alt_check() - set category = "Admin" - set name = "Alt Account Checker" - - var/dat = {"Just to be sure you should try to also look up computer IDs/IPs on the server logs for a second opinion. -
    Additionally make an attempt to introduce new players to the server -
    "} - - if(GLOB.dbcon.IsConnected()) - for(var/client/C in GLOB.clients) - dat += "

    [C.ckey] (Player Age: [C.player_age]) - [C.computer_id] / [C.address]
    " - if(C.related_accounts_cid.len) - dat += "--Accounts associated with CID: " - dat += "[jointext(C.related_accounts_cid, " - ")]
    " - if(C.related_accounts_ip.len) - dat += "--Accounts associated with IP: " - dat += "[jointext(C.related_accounts_ip, " - ")] " - usr << browse(dat, "window=alt_panel;size=640x480") - return - diff --git a/code/modules/admin/verbs/asays.dm b/code/modules/admin/verbs/asays.dm new file mode 100644 index 00000000000..e713d2b1bcc --- /dev/null +++ b/code/modules/admin/verbs/asays.dm @@ -0,0 +1,70 @@ +GLOBAL_LIST_EMPTY(asays) + +/datum/asays + var/ckey + var/rank + var/message + var/time + +/datum/asays/New(ckey = "", rank = "", message = "", time = 0) + src.ckey = ckey + src.rank = rank + src.message = message + src.time = time + +/client/proc/view_asays() + set name = "Asays" + set desc = "View Asays from the current round." + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + var/list/output = list({" + + Refresh +

    NameFingerprints
    + "}) + + // Header & body start + output += {" + + + + + + + + + "} + + for(var/datum/asays/A in GLOB.asays) + var/timestr = time2text(A.time, "hh:mm:ss") + output += {" + + + + + + "} + + output += {" + +
    TimeCkeyMessage
    [timestr][A.ckey] ([A.rank])[A.message]
    "} + + var/datum/browser/popup = new(src, "asays", "
    Current Round Asays
    ", 1200, 825) + popup.set_content(output.Join()) + popup.open(0) diff --git a/code/modules/admin/verbs/custom_event.dm b/code/modules/admin/verbs/custom_event.dm index b3d1543aedc..1284f9e0faf 100644 --- a/code/modules/admin/verbs/custom_event.dm +++ b/code/modules/admin/verbs/custom_event.dm @@ -3,8 +3,7 @@ set category = "Event" set name = "Change Custom Event" - if(!holder) - to_chat(src, "Only administrators may use this command.") + if(!check_rights(R_EVENT)) return var/input = input(usr, "Enter the description of the custom event. Be descriptive. To cancel the event, make this blank or hit cancel.", "Custom Event", GLOB.custom_event_msg) as message|null diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 03b9dfb60e7..d2c05ff4f6c 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -168,7 +168,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) #endif /client/proc/callproc_datum(var/A as null|area|mob|obj|turf) - set category = "Debug" + set category = null set name = "Atom ProcCall" if(!check_rights(R_PROCCALL)) @@ -418,25 +418,6 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) else alert("Invalid mob") -//TODO: merge the vievars version into this or something maybe mayhaps -/client/proc/cmd_debug_del_all() - set category = "Debug" - set name = "Del-All" - - if(!check_rights(R_DEBUG)) - return - - // to prevent REALLY stupid deletions - var/blocked = list(/mob/living, /mob/living/carbon, /mob/living/carbon/human, /mob/dead, /mob/dead/observer, /mob/living/silicon, /mob/living/silicon/robot, /mob/living/silicon/ai) - var/hsbitem = input(usr, "Choose an object to delete.", "Delete:") as null|anything in subtypesof(/obj) + subtypesof(/mob) - blocked - if(hsbitem) - for(var/atom/O in world) - if(istype(O, hsbitem)) - qdel(O) - log_admin("[key_name(src)] has deleted all instances of [hsbitem].") - message_admins("[key_name_admin(src)] has deleted all instances of [hsbitem].", 0) - feedback_add_details("admin_verb","DELA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - /client/proc/cmd_debug_del_sing() set category = "Debug" set name = "Del Singulo / Tesla" diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm index 78f2647e429..2ffb4659486 100644 --- a/code/modules/admin/verbs/diagnostics.dm +++ b/code/modules/admin/verbs/diagnostics.dm @@ -108,7 +108,7 @@ /client/proc/reload_admins() set name = "Reload Admins" - set category = "Debug" + set category = "Server" if(!check_rights(R_SERVER)) return diff --git a/code/modules/admin/verbs/gimmick_team.dm b/code/modules/admin/verbs/gimmick_team.dm index 37afe35ef6a..3403a4417cf 100644 --- a/code/modules/admin/verbs/gimmick_team.dm +++ b/code/modules/admin/verbs/gimmick_team.dm @@ -39,6 +39,7 @@ if(alert("Do you want these characters automatically classified as antagonists?",,"Yes","No")=="Yes") is_syndicate = 1 + var/datum/outfit/O = outfit_list[dresscode] var/list/players_to_spawn = list() if(pick_manually) var/list/possible_ghosts = list() @@ -52,14 +53,12 @@ players_to_spawn += candidate else to_chat(src, "Polling candidates...") - players_to_spawn = pollCandidates("Do you want to play as an event character?") + players_to_spawn = SSghost_spawns.poll_candidates("Do you want to play as \a [O.name]?") if(!players_to_spawn.len) to_chat(src, "Nobody volunteered.") return 0 - var/datum/outfit/O = outfit_list[dresscode] - var/players_spawned = 0 for(var/mob/thisplayer in players_to_spawn) var/mob/living/carbon/human/H = new /mob/living/carbon/human(T) diff --git a/code/modules/admin/verbs/infiltratorteam_syndicate.dm b/code/modules/admin/verbs/infiltratorteam_syndicate.dm index 54ddf85045e..d4ea1a0dd2e 100644 --- a/code/modules/admin/verbs/infiltratorteam_syndicate.dm +++ b/code/modules/admin/verbs/infiltratorteam_syndicate.dm @@ -55,7 +55,8 @@ GLOBAL_VAR_INIT(sent_syndicate_infiltration_team, 0) infiltrators += candidate else to_chat(src, "Polling candidates...") - infiltrators = pollCandidates("Do you want to play as a SYNDICATE INFILTRATOR?", ROLE_TRAITOR, 1) + var/mutable_appearance/ma = new('icons/mob/simple_human.dmi', "syndicate") + infiltrators = SSghost_spawns.poll_candidates("Do you want to play as a SYNDICATE INFILTRATOR?", ROLE_TRAITOR, TRUE, source = ma) if(!infiltrators.len) to_chat(src, "Nobody volunteered.") diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm index edbca5a842f..0dc3dfc1791 100644 --- a/code/modules/admin/verbs/map_template_loadverb.dm +++ b/code/modules/admin/verbs/map_template_loadverb.dm @@ -2,8 +2,9 @@ set category = "Debug" set name = "Map template - Place" - if(!holder) + if(!check_rights(R_DEBUG)) return + var/datum/map_template/template var/map = input(usr, "Choose a Map Template to place at your CURRENT LOCATION","Place Map Template") as null|anything in GLOB.map_templates @@ -36,6 +37,9 @@ set category = "Debug" set name = "Map Template - Upload" + if(!check_rights(R_DEBUG)) + return + var/map = input(usr, "Choose a Map Template to upload to template storage","Upload Map Template") as null|file if(!map) return diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm index 3e89a6ac18d..5d9d218c90e 100644 --- a/code/modules/admin/verbs/one_click_antag.dm +++ b/code/modules/admin/verbs/one_click_antag.dm @@ -137,7 +137,8 @@ client/proc/one_click_antag() var/confirm = alert("Are you sure?", "Confirm creation", "Yes", "No") if(confirm != "Yes") return 0 - var/list/candidates = pollCandidates("Do you wish to be considered for the position of a Wizard Foundation 'diplomat'?", "wizard") + var/mutable_appearance/ma = new('icons/mob/simple_human.dmi', "wizard") + var/list/candidates = SSghost_spawns.poll_candidates("Do you wish to be considered for the position of a Wizard Foundation 'diplomat'?", "wizard", source = ma) log_admin("[key_name(owner)] tried making a Wizard with One-Click-Antag") message_admins("[key_name_admin(owner)] tried making a Wizard with One-Click-Antag") diff --git a/code/modules/admin/verbs/possess.dm b/code/modules/admin/verbs/possess.dm index 5cf4c34ffe2..3c1d0820dd7 100644 --- a/code/modules/admin/verbs/possess.dm +++ b/code/modules/admin/verbs/possess.dm @@ -2,6 +2,9 @@ set name = "Possess Obj" set category = null + if(!check_rights(R_POSSESS)) + return + if(istype(O,/obj/singularity)) if(config.forbid_singulo_possession) to_chat(usr, "It is forbidden to possess singularities.") @@ -35,6 +38,9 @@ set category = null //usr.loc = get_turf(usr) + if(!check_rights(R_POSSESS)) + return + if(usr.control_object && usr.name_archive) //if you have a name archived and if you are actually relassing an object usr.real_name = usr.name_archive usr.name = usr.real_name diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 7e9f397a855..3864f573597 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -51,7 +51,7 @@ if(!ismob(M)) return - if(!check_rights(R_SERVER|R_EVENT)) + if(!check_rights(R_EVENT)) return var/msg = clean_input("Message:", text("Subtle PM to [M.key]")) @@ -123,7 +123,7 @@ feedback_add_details("admin_verb","GLN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/cmd_admin_direct_narrate(var/mob/M) // Targetted narrate -- TLE - set category = "Event" + set category = null set name = "Direct Narrate" if(!check_rights(R_SERVER|R_EVENT)) @@ -158,7 +158,7 @@ /client/proc/admin_headset_message(mob/M in GLOB.mob_list, sender = null) var/mob/living/carbon/human/H = M - if(!check_rights(R_ADMIN)) + if(!check_rights(R_EVENT)) return if(!istype(H)) @@ -572,7 +572,7 @@ Traitors and the like can also be revived with the previous role mostly intact. feedback_add_details("admin_verb","IONC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/cmd_admin_rejuvenate(mob/living/M as mob in GLOB.mob_list) - set category = "Event" + set category = null set name = "Rejuvenate" if(!check_rights(R_REJUVINATE)) @@ -638,7 +638,7 @@ Traitors and the like can also be revived with the previous role mostly intact. /client/proc/cmd_admin_delete(atom/A as obj|mob|turf in view()) - set category = "Admin" + set category = null set name = "Delete" if(!check_rights(R_ADMIN)) @@ -1078,12 +1078,12 @@ Traitors and the like can also be revived with the previous role mostly intact. message_admins("Admin [key_name_admin(usr)] has disabled ERT calling.", 1) /client/proc/show_tip() - set category = "Admin" + set category = "Event" set name = "Show Custom Tip" set desc = "Sends a tip (that you specify) to all players. After all \ you're the experienced player here." - if(!check_rights(R_ADMIN)) + if(!check_rights(R_EVENT)) return var/input = input(usr, "Please specify your tip that you want to send to the players.", "Tip", "") as message|null diff --git a/code/modules/admin/verbs/spawnfloorcluwne.dm b/code/modules/admin/verbs/spawnfloorcluwne.dm deleted file mode 100644 index 37fd3057af4..00000000000 --- a/code/modules/admin/verbs/spawnfloorcluwne.dm +++ /dev/null @@ -1,30 +0,0 @@ -/client/proc/spawn_floor_cluwne() - set category = "Event" - set name = "Unleash Floor Cluwne" - set desc = "Pick a specific target or just let it select randomly and spawn the floor cluwne mob on the station. Be warned: spawning more than one may cause issues!" - var/mob/living/carbon/human/target - - if(!check_rights(R_EVENT)) - return - - var/confirm = alert("Are you sure you want to release a floor cluwne and kill a lot of people?", "Confirm Massacre", "Yes", "No") - if(confirm == "Yes") - - var/turf/T = get_turf(usr) - var/list/potential_targets = list() - for(var/mob/M in GLOB.player_list) - var/mob/living/carbon/human/H = M - if(!istype(H)) - continue - if(H.mind.assigned_role == "Cluwne") - continue - potential_targets += H - if(!potential_targets.len) //You're probably the only player on this damn station, spawn it yourself - to_chat(src, "No valid targets!") - return - target = input("Any specific target in mind? Please note only live, non cluwned, human targets are valid.", "Target", target) as null|anything in potential_targets - var/mob/living/simple_animal/hostile/floor_cluwne/FC = new /mob/living/simple_animal/hostile/floor_cluwne(T) - if(target) - FC.Acquire_Victim(target) - log_admin("[key_name(usr)] spawned floor cluwne[target ? ", initially targetting [target]": null].") - message_admins("[key_name(usr)] spawned floor cluwne[target ? ", initially targetting [target]": null].") diff --git a/code/modules/admin/verbs/striketeam.dm b/code/modules/admin/verbs/striketeam.dm index 0b84c96e71a..b3ea2924709 100644 --- a/code/modules/admin/verbs/striketeam.dm +++ b/code/modules/admin/verbs/striketeam.dm @@ -37,7 +37,8 @@ GLOBAL_VAR_INIT(sent_strike_team, 0) break // Find ghosts willing to be DS - var/list/commando_ghosts = pollCandidatesWithVeto(src, usr, COMMANDOS_POSSIBLE, "Join the DeathSquad?",, 21, 600, 1, GLOB.role_playtime_requirements[ROLE_DEATHSQUAD], TRUE, FALSE) + var/mutable_appearance/source = new('icons/obj/cardboard_cutout.dmi', "cutout_deathsquad") + var/list/commando_ghosts = pollCandidatesWithVeto(src, usr, COMMANDOS_POSSIBLE, "Join the DeathSquad?",, 21, 60 SECONDS, TRUE, GLOB.role_playtime_requirements[ROLE_DEATHSQUAD], TRUE, FALSE, source = source) if(!commando_ghosts.len) to_chat(usr, "Nobody volunteered to join the DeathSquad.") return diff --git a/code/modules/admin/verbs/striketeam_syndicate.dm b/code/modules/admin/verbs/striketeam_syndicate.dm index 12df7467cbc..405c1d3c3d2 100644 --- a/code/modules/admin/verbs/striketeam_syndicate.dm +++ b/code/modules/admin/verbs/striketeam_syndicate.dm @@ -45,7 +45,7 @@ GLOBAL_VAR_INIT(sent_syndicate_strike_team, 0) break // Find ghosts willing to be SST - var/list/commando_ghosts = pollCandidatesWithVeto(src, usr, SYNDICATE_COMMANDOS_POSSIBLE, "Join the Syndicate Strike Team?",, 21, 600, 1, GLOB.role_playtime_requirements[ROLE_DEATHSQUAD], TRUE, FALSE) + var/list/commando_ghosts = pollCandidatesWithVeto(src, usr, SYNDICATE_COMMANDOS_POSSIBLE, "Join the Syndicate Strike Team?",, 21, 60 SECONDS, TRUE, GLOB.role_playtime_requirements[ROLE_DEATHSQUAD], TRUE, FALSE) if(!commando_ghosts.len) to_chat(usr, "Nobody volunteered to join the SST.") return diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 4a6fe7d6640..41333760f2d 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -73,7 +73,7 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/replace_banned_player() set waitfor = FALSE - var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a [name]?", job_rank, TRUE, 50) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a [name]?", job_rank, TRUE, 5 SECONDS) if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) to_chat(owner, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index 8e29326632f..82f492d214b 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -46,7 +46,8 @@ checking = TRUE to_chat(user, "You activate [src] and wait for confirmation.") - var/list/nuke_candidates = pollCandidates("Do you want to play as a [rolename]?", ROLE_OPERATIVE, TRUE, 150) + var/mutable_appearance/ma = new('icons/mob/simple_human.dmi', "syndicate_space_sword") + var/list/nuke_candidates = SSghost_spawns.poll_candidates("Do you want to play as a [rolename]?", ROLE_OPERATIVE, TRUE, 15 SECONDS, source = ma) if(LAZYLEN(nuke_candidates)) checking = FALSE if(QDELETED(src) || !check_usability(user)) @@ -180,7 +181,7 @@ var/type = "slaughter" if(demon_type == /mob/living/simple_animal/slaughter/laughter) type = "laughter" - var/list/candidates = pollCandidates("Do you want to play as a [type] demon summoned by [user.real_name]?", ROLE_DEMON, 1, 100) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a [type] demon summoned by [user.real_name]?", ROLE_DEMON, TRUE, 10 SECONDS, source = demon_type) if(candidates.len > 0) var/mob/C = pick(candidates) @@ -252,7 +253,7 @@ used = TRUE to_chat(user, "You break the seal on the bottle, calling upon the dire sludge to awaken...") - var/list/candidates = pollCandidates("Do you want to play as a magical morph awakened by [user.real_name]?", ROLE_MORPH, 1, 100) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a magical morph awakened by [user.real_name]?", ROLE_MORPH, 1, 10 SECONDS, source = morph_type) if(candidates.len > 0) var/mob/C = pick(candidates) diff --git a/code/modules/awaymissions/mission_code/academy.dm b/code/modules/awaymissions/mission_code/academy.dm index 7cddddd8b08..65c64edd83f 100644 --- a/code/modules/awaymissions/mission_code/academy.dm +++ b/code/modules/awaymissions/mission_code/academy.dm @@ -193,7 +193,7 @@ servant_mind.objectives += O servant_mind.transfer_to(H) - var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as the servant of [user.real_name]?", ROLE_WIZARD, poll_time = 300) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as the servant of [user.real_name]?", ROLE_WIZARD, poll_time = 30 SECONDS, source = H) if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") diff --git a/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm b/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm index c5b56805213..75fbbf698e6 100644 --- a/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm +++ b/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm @@ -4,10 +4,18 @@ name = "Mission Briefing" info = "To the Magnificent Z.A.P.
    A small mining base has been created within our territory by wandless scum. Send them a message from the wizard federation they will not forget. I know your kind is rather fragile, but a group of lightly armed miners should not pose any threat to you at all. Just be warned they have a security cyborg for self defence, you might want to tune your spells to that threat. I look forward to hearing of your success.
    Grand Magus Abra the Wonderous" +/obj/item/spellbook/oneuse/emp + spell = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech + spellname = "Disable Technology" + icon_state = "bookcharge" //it's a lightning bolt, seems appropriate enough + desc = "For the tech-hating wizard on the go." + +/obj/item/spellbook/oneuse/emp/used + used = TRUE //spawns used /obj/effect/spawner/lootdrop/wizardcrash loot = list( - /obj/item/guardiancreator = 1, //jackpot. + /obj/item/guardiancreator = 1, //jackpot. /obj/item/spellbook/oneuse/knock = 1, //tresspassing charges incoming /obj/item/gun/magic/wand/resurrection = 1, //medbay's best friend /obj/item/spellbook/oneuse/charge = 20, //and now for less useful stuff to dilute the good loot chances diff --git a/code/modules/client/preference/preferences_toggles.dm b/code/modules/client/preference/preferences_toggles.dm index 5534e18e253..0f4317e3ce2 100644 --- a/code/modules/client/preference/preferences_toggles.dm +++ b/code/modules/client/preference/preferences_toggles.dm @@ -30,7 +30,8 @@ set name = "Show/Hide RadioChatter" set category = "Preferences" set desc = "Toggle seeing radiochatter from radios and speakers" - if(!holder) return + if(!check_rights(R_ADMIN)) + return prefs.toggles ^= CHAT_RADIO prefs.save_preferences(src) to_chat(usr, "You will [(prefs.toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from radios or speakers") @@ -49,7 +50,8 @@ set name = "Hear/Silence Admin Bwoinks" set category = "Preferences" set desc = "Toggle hearing a notification when admin PMs are recieved" - if(!holder) return + if(!check_rights(R_ADMIN)) + return prefs.sound ^= SOUND_ADMINHELP prefs.save_preferences(src) to_chat(usr, "You will [(prefs.sound & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.") @@ -59,7 +61,7 @@ set name = "Hear/Silence Mentorhelp Bwoinks" set category = "Preferences" set desc = "Toggle hearing a notification when mentorhelps are recieved" - if(!holder) + if(!check_rights(R_ADMIN|R_MENTOR)) return prefs.sound ^= SOUND_MENTORHELP prefs.save_preferences(src) @@ -296,7 +298,7 @@ to_chat(src, "As a ghost, you will now [(prefs.toggles & CHAT_GHOSTPDA) ? "see all PDA messages" : "no longer see PDA messages"].") prefs.save_preferences(src) feedback_add_details("admin_verb","TGP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - + /client/verb/silence_current_midi() set name = "Silence Current Midi" set category = "Preferences" diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index a9b4eab329e..9a2afbc8a60 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -838,3 +838,10 @@ icon_state = "cuban_suit" item_state = "cuban_suit" item_color = "cuban_suit" + +/obj/item/clothing/under/tourist_suit + name = "tourist outfit" + desc = "A light blue shirt with brown shorts. Feels oddly spooky." + icon_state = "tourist" + icon_state = "tourist" + item_color = "tourist" diff --git a/code/modules/events/abductor.dm b/code/modules/events/abductor.dm index 7fe60e8b77f..4ad8fb4eb90 100644 --- a/code/modules/events/abductor.dm +++ b/code/modules/events/abductor.dm @@ -9,7 +9,7 @@ makeAbductorTeam() /datum/event/abductor/proc/makeAbductorTeam() - var/list/mob/dead/observer/candidates = pollCandidates("Do you wish to be considered for an Abductor Team?", ROLE_ABDUCTOR, 1) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you wish to be considered for an Abductor Team?", ROLE_ABDUCTOR, TRUE) if(candidates.len >= 2) //Oh god why we can't have static functions diff --git a/code/modules/events/alien_infestation.dm b/code/modules/events/alien_infestation.dm index 8c01d5c7cc2..23e4e266819 100644 --- a/code/modules/events/alien_infestation.dm +++ b/code/modules/events/alien_infestation.dm @@ -23,7 +23,7 @@ vents += temp_vent spawn() - var/list/candidates = pollCandidates("Do you want to play as an alien?", ROLE_ALIEN, 1) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as an alien?", ROLE_ALIEN, TRUE, source = /mob/living/carbon/alien/larva) while(spawncount > 0 && vents.len && candidates.len) var/obj/vent = pick_n_take(vents) diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm index caec11de9f2..de2da9fbf64 100644 --- a/code/modules/events/blob.dm +++ b/code/modules/events/blob.dm @@ -12,14 +12,14 @@ if(!T) return kill() - var/list/candidates = pollCandidates("Do you want to play as a blob infested mouse?", ROLE_BLOB, 1) - if(!candidates.len) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a blob infested mouse?", ROLE_BLOB, TRUE, source = /mob/living/simple_animal/mouse/blobinfected) + if(!length(candidates)) return kill() var/list/vents = list() for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in GLOB.all_vent_pumps) if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) - if(temp_vent.parent.other_atmosmch.len > 50) + if(length(temp_vent.parent.other_atmosmch) > 50) vents += temp_vent var/obj/vent = pick(vents) diff --git a/code/modules/events/event_procs.dm b/code/modules/events/event_procs.dm index c5b7c893c26..cc00f05723f 100644 --- a/code/modules/events/event_procs.dm +++ b/code/modules/events/event_procs.dm @@ -1,9 +1,9 @@ /client/proc/forceEvent(var/type in SSevents.allEvents) - set name = "Trigger Event (Debug Only)" + set name = "Trigger Event" set category = "Debug" - if(!holder) + if(!check_rights(R_EVENT)) return if(ispath(type)) diff --git a/code/modules/events/sentience.dm b/code/modules/events/sentience.dm index ab8a405785f..7007a99f0ec 100644 --- a/code/modules/events/sentience.dm +++ b/code/modules/events/sentience.dm @@ -3,8 +3,7 @@ /datum/event/sentience/start() processing = FALSE //so it won't fire again in next tick - var/ghostmsg = "Do you want to awaken as a sentient being?" - var/list/candidates = pollCandidates(ghostmsg, ROLE_SENTIENT, 1) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to awaken as a sentient being?", ROLE_SENTIENT, TRUE) var/list/potential = list() var/sentience_type = SENTIENCE_ORGANIC diff --git a/code/modules/events/slaughterevent.dm b/code/modules/events/slaughterevent.dm index 91918894cfd..85f0f8aa555 100644 --- a/code/modules/events/slaughterevent.dm +++ b/code/modules/events/slaughterevent.dm @@ -3,7 +3,7 @@ /datum/event/spawn_slaughter/proc/get_slaughter(var/end_if_fail = 0) spawn() - var/list/candidates = pollCandidates("Do you want to play as a slaughter demon?", ROLE_DEMON, 1) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a slaughter demon?", ROLE_DEMON, TRUE, source = /mob/living/simple_animal/slaughter) if(!candidates.len) key_of_slaughter = null return kill() diff --git a/code/modules/events/traders.dm b/code/modules/events/traders.dm index 0beeb3691e2..d4ae1de3b9c 100644 --- a/code/modules/events/traders.dm +++ b/code/modules/events/traders.dm @@ -34,7 +34,7 @@ GLOBAL_LIST_INIT(unused_trade_stations, list("sol")) trader_objectives = forge_trader_objectives() spawn() - var/list/candidates = pollCandidates("Do you want to play as a trader?", ROLE_TRADER, 1) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a Sol Trader?", ROLE_TRADER, TRUE) var/index = 1 while(spawn_count > 0 && candidates.len > 0) if(index > spawnlocs.len) diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm index 0abd8782452..f6bb3c8e1c4 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm @@ -1,51 +1,73 @@ -/* SmartFridge. Much todo -*/ +#define SMART_FRIDGE_LOCK_SHORTED -1 + +/** + * # Smart Fridge + * + * Stores items of a specified type. + */ /obj/machinery/smartfridge name = "\improper SmartFridge" icon = 'icons/obj/vending.dmi' icon_state = "smartfridge" layer = 2.9 - density = 1 - anchored = 1 + density = TRUE + anchored = TRUE use_power = IDLE_POWER_USE idle_power_usage = 5 active_power_usage = 100 + /// The maximum number of items the fridge can hold. Multiplicated by the matter bin component's rating. var/max_n_of_items = 1500 - var/item_quants = list() + /// Associative list (/text => /number) tracking the amounts of a specific item held by the fridge. + var/list/item_quants + /// How long in ticks the fridge is electrified for. Decrements every process. var/seconds_electrified = 0 + /// Whether the fridge should randomly shoot held items at a nearby living target or not. var/shoot_inventory = FALSE + /// Whether the fridge is locked. Used for the secure variant of the fridge. var/locked = FALSE + /// Whether the fridge requires ID scanning. Used for the secure variant of the fridge. var/scan_id = TRUE + /// Whether the fridge is considered secure. Used for wiring and display. var/is_secure = FALSE + /// Whether the fridge can dry its' contents. Used for display. var/can_dry = FALSE + /// Whether the fridge is currently drying. Used by [drying racks][/obj/machinery/smartfridge/drying_rack]. var/drying = FALSE + /// Whether the fridge's contents are visible on the world icon. var/visible_contents = TRUE - var/datum/wires/smartfridge/wires = null + /// The wires controlling the fridge. + var/datum/wires/smartfridge/wires + /// Typecache of accepted item types, init it in [/obj/machinery/smartfridge/Initialize]. + var/list/accepted_items_typecache -/obj/machinery/smartfridge/New() - ..() +/obj/machinery/smartfridge/Initialize(mapload) + . = ..() + item_quants = list() + // Reagents create_reagents() reagents.set_reacting(FALSE) + // Components component_parts = list() var/obj/item/circuitboard/smartfridge/board = new(null) board.set_type(type) component_parts += board component_parts += new /obj/item/stock_parts/matter_bin(null) RefreshParts() - -/obj/machinery/smartfridge/RefreshParts() - for(var/obj/item/stock_parts/matter_bin/B in component_parts) - max_n_of_items = 1500 * B.rating - -/obj/machinery/smartfridge/secure - is_secure = 1 - -/obj/machinery/smartfridge/New() - ..() + // Wires if(is_secure) wires = new/datum/wires/smartfridge/secure(src) else wires = new/datum/wires/smartfridge(src) + // Accepted items + accepted_items_typecache = typecacheof(list( + /obj/item/reagent_containers/food/snacks/grown, + /obj/item/seeds, + /obj/item/grown, + )) + +/obj/machinery/smartfridge/RefreshParts() + for(var/obj/item/stock_parts/matter_bin/B in component_parts) + max_n_of_items = 1500 * B.rating /obj/machinery/smartfridge/Destroy() SStgui.close_uis(wires) @@ -54,154 +76,6 @@ A.forceMove(loc) return ..() -/obj/machinery/smartfridge/proc/accept_check(obj/item/O) - if(istype(O,/obj/item/reagent_containers/food/snacks/grown/) || istype(O,/obj/item/seeds/) || istype(O,/obj/item/grown/)) - return 1 - return 0 - -/obj/machinery/smartfridge/seeds - name = "\improper MegaSeed Servitor" - desc = "When you need seeds fast!" - icon = 'icons/obj/vending.dmi' - icon_state = "seeds" - -/obj/machinery/smartfridge/seeds/accept_check(obj/item/O) - if(istype(O,/obj/item/seeds/)) - return 1 - return 0 - -/obj/machinery/smartfridge/medbay - name = "\improper Refrigerated Medicine Storage" - desc = "A refrigerated storage unit for storing medicine and chemicals." - icon_state = "smartfridge" //To fix the icon in the map editor. - -/obj/machinery/smartfridge/medbay/accept_check(obj/item/O) - if(istype(O,/obj/item/reagent_containers/glass)) - return 1 - if(istype(O,/obj/item/reagent_containers/iv_bag)) - return 1 - if(istype(O,/obj/item/storage/pill_bottle)) - return 1 - if(ispill(O)) - return 1 - return 0 - -/obj/machinery/smartfridge/secure/extract - name = "\improper Slime Extract Storage" - desc = "A refrigerated storage unit for slime extracts" - req_access_txt = "47" - -/obj/machinery/smartfridge/secure/extract/accept_check(obj/item/O) - if(istype(O,/obj/item/slime_extract)) - return 1 - return 0 - -/obj/machinery/smartfridge/secure/medbay - name = "\improper Secure Refrigerated Medicine Storage" - desc = "A refrigerated storage unit for storing medicine and chemicals." - icon_state = "smartfridge" //To fix the icon in the map editor. - req_one_access_txt = "5;33" - -/obj/machinery/smartfridge/secure/medbay/accept_check(obj/item/O) - if(istype(O,/obj/item/reagent_containers/glass)) - return 1 - if(istype(O,/obj/item/reagent_containers/iv_bag)) - return 1 - if(istype(O,/obj/item/storage/pill_bottle)) - return 1 - if(ispill(O)) - return 1 - return 0 - -/obj/machinery/smartfridge/secure/chemistry - name = "\improper Smart Chemical Storage" - desc = "A refrigerated storage unit for medicine and chemical storage." - icon_state = "smartfridge" //To fix the icon in the map editor. - req_access_txt = "33" - var/list/spawn_meds = list() - -/obj/machinery/smartfridge/secure/chemistry/New() - ..() - for(var/typekey in spawn_meds) - var/amount = spawn_meds[typekey] - if(isnull(amount)) amount = 1 - while(amount) - var/obj/item/I = new typekey(src) - if(item_quants[I.name]) - item_quants[I.name]++ - else - item_quants[I.name] = 1 - SSnanoui.update_uis(src) - amount-- - update_icon() - -/obj/machinery/smartfridge/secure/chemistry/accept_check(obj/item/O) - if(istype(O,/obj/item/storage/pill_bottle) || istype(O,/obj/item/reagent_containers)) - return 1 - return 0 - -/obj/machinery/smartfridge/secure/chemistry/preloaded - spawn_meds = list( - /obj/item/reagent_containers/food/pill/epinephrine = 12, - /obj/item/reagent_containers/food/pill/charcoal = 5, - /obj/item/reagent_containers/glass/bottle/epinephrine = 1, - /obj/item/reagent_containers/glass/bottle/charcoal = 1) - -/obj/machinery/smartfridge/secure/chemistry/preloaded/syndicate - req_access_txt = null - req_access = list(ACCESS_SYNDICATE) - -/obj/machinery/smartfridge/disks - name = "disk compartmentalizer" - desc = "A machine capable of storing a variety of disks. Denoted by most as the DSU (disk storage unit)." - icon_state = "disktoaster" - pass_flags = PASSTABLE - visible_contents = FALSE - -/obj/machinery/smartfridge/disks/accept_check(obj/item/O) - return istype(O, /obj/item/disk) - -// ---------------------------- -// Virology Medical Smartfridge -// ---------------------------- -/obj/machinery/smartfridge/secure/chemistry/virology - name = "Smart Virus Storage" - desc = "A refrigerated storage unit for volatile sample storage." - req_access_txt = "39" - spawn_meds = list(/obj/item/reagent_containers/syringe/antiviral = 4, - /obj/item/reagent_containers/glass/bottle/cold = 1, - /obj/item/reagent_containers/glass/bottle/flu_virion = 1, - /obj/item/reagent_containers/glass/bottle/mutagen = 1, - /obj/item/reagent_containers/glass/bottle/plasma = 1, - /obj/item/reagent_containers/glass/bottle/diphenhydramine = 1) - -/obj/machinery/smartfridge/secure/chemistry/virology/accept_check(obj/item/O) - if(istype(O, /obj/item/reagent_containers/syringe) || istype(O, /obj/item/reagent_containers/glass/bottle) || istype(O, /obj/item/reagent_containers/glass/beaker)) - return 1 - return 0 - -/obj/machinery/smartfridge/secure/chemistry/virology/preloaded - spawn_meds = list( - /obj/item/reagent_containers/syringe/antiviral = 4, - /obj/item/reagent_containers/glass/bottle/cold = 1, - /obj/item/reagent_containers/glass/bottle/flu_virion = 1, - /obj/item/reagent_containers/glass/bottle/mutagen = 1, - /obj/item/reagent_containers/glass/bottle/plasma = 1, - /obj/item/reagent_containers/glass/bottle/reagent/synaptizine = 1, - /obj/item/reagent_containers/glass/bottle/reagent/formaldehyde = 1) - -/obj/machinery/smartfridge/secure/chemistry/virology/preloaded/syndicate - req_access_txt = null - req_access = list(ACCESS_SYNDICATE) - -/obj/machinery/smartfridge/drinks - name = "\improper Drink Showcase" - desc = "A refrigerated storage unit for tasty tasty alcohol." - -/obj/machinery/smartfridge/drinks/accept_check(obj/item/O) - if(istype(O,/obj/item/reagent_containers/glass) || istype(O,/obj/item/reagent_containers/food/drinks) || istype(O,/obj/item/reagent_containers/food/condiment)) - return 1 - /obj/machinery/smartfridge/process() if(stat & (BROKEN|NOPOWER)) return @@ -217,52 +91,57 @@ update_icon() /obj/machinery/smartfridge/update_icon() + var/prefix = initial(icon_state) if(stat & (BROKEN|NOPOWER)) - icon_state = "[initial(icon_state)]-off" + icon_state = "[prefix]-off" else if(visible_contents) - switch(contents.len) + switch(length(contents)) if(0) - icon_state = "[initial(icon_state)]" + icon_state = "[prefix]" if(1 to 25) - icon_state = "[initial(icon_state)]1" + icon_state = "[prefix]1" if(26 to 75) - icon_state = "[initial(icon_state)]2" + icon_state = "[prefix]2" if(76 to INFINITY) - icon_state = "[initial(icon_state)]3" + icon_state = "[prefix]3" else - icon_state = "[initial(icon_state)]" + icon_state = "[prefix]" -/******************* -* Item Adding -********************/ - -/obj/machinery/smartfridge/default_deconstruction_screwdriver(mob/user, obj/item/screwdriver/S) - . = ..(user, icon_state, icon_state, S) +// Interactions +/obj/machinery/smartfridge/screwdriver_act(mob/living/user, obj/item/I) + . = default_deconstruction_screwdriver(user, icon_state, icon_state, I) + if(!.) + return overlays.Cut() if(panel_open) overlays += image(icon, "[initial(icon_state)]-panel") -/obj/machinery/smartfridge/attackby(obj/item/O, var/mob/user) - if(default_deconstruction_screwdriver(user, O)) - return - - if(exchange_parts(user, O)) - return - - if(default_unfasten_wrench(user, O)) +/obj/machinery/smartfridge/wrench_act(mob/living/user, obj/item/I) + . = default_unfasten_wrench(user, I) + if(.) power_change() - return - if(default_deconstruction_crowbar(user, O)) - return +/obj/machinery/smartfridge/crowbar_act(mob/living/user, obj/item/I) + . = default_deconstruction_crowbar(user, I) - if(istype(O, /obj/item/multitool)||istype(O, /obj/item/wirecutters)) - if(panel_open) - attack_hand(user) - return +/obj/machinery/smartfridge/wirecutter_act(mob/living/user, obj/item/I) + if(panel_open) + attack_hand(user) + return TRUE + return ..() - if(stat & NOPOWER) +/obj/machinery/smartfridge/multitool_act(mob/living/user, obj/item/I) + if(panel_open) + attack_hand(user) + return TRUE + return ..() + +/obj/machinery/smartfridge/attackby(obj/item/O, var/mob/user) + if(exchange_parts(user, O)) + SSnanoui.update_uis(src) + return + if(stat & (BROKEN|NOPOWER)) to_chat(user, "\The [src] is unpowered and useless.") return @@ -270,92 +149,63 @@ user.visible_message("[user] has added \the [O] to \the [src].", "You add \the [O] to \the [src].") SSnanoui.update_uis(src) update_icon() - else if(istype(O, /obj/item/storage/bag)) var/obj/item/storage/bag/P = O - var/plants_loaded = 0 + var/items_loaded = 0 for(var/obj/G in P.contents) if(load(G, user)) - plants_loaded++ - if(plants_loaded) + items_loaded++ + if(items_loaded) user.visible_message("[user] loads \the [src] with \the [P].", "You load \the [src] with \the [P].") - if(P.contents.len > 0) - to_chat(user, "Some items are refused.") - - SSnanoui.update_uis(src) - update_icon() - + SSnanoui.update_uis(src) + update_icon() + var/failed = length(P.contents) + if(failed) + to_chat(user, "[failed] item\s [failed == 1 ? "is" : "are"] refused.") else if(!istype(O, /obj/item/card/emag)) to_chat(user, "\The [src] smartly refuses [O].") - return 1 - -/obj/machinery/smartfridge/proc/load(obj/I, mob/user) - if(accept_check(I)) - if(contents.len >= max_n_of_items) - to_chat(user, "\The [src] is full.") - return 0 - else - if(istype(I.loc, /obj/item/storage)) - var/obj/item/storage/S = I.loc - S.remove_from_storage(I, src) - else if(istype(I.loc, /mob)) - var/mob/M = I.loc - if(M.get_active_hand() == I) - if(!M.drop_item()) - to_chat(user, "\The [I] is stuck to you!") - return 0 - else - M.unEquip(I) - I.forceMove(src) - else - I.forceMove(src) - - if(item_quants[I.name]) - item_quants[I.name]++ - else - item_quants[I.name] = 1 - return 1 - return 0 + return TRUE /obj/machinery/smartfridge/attack_ai(mob/user) - return 0 + return FALSE /obj/machinery/smartfridge/attack_ghost(mob/user) return attack_hand(user) /obj/machinery/smartfridge/attack_hand(mob/user) - if(stat & (NOPOWER|BROKEN)) + if(stat & (BROKEN|NOPOWER)) return wires.Interact(user) ui_interact(user) + return ..() //Drag pill bottle to fridge to empty it into the fridge /obj/machinery/smartfridge/MouseDrop_T(obj/over_object, mob/user) if(!istype(over_object, /obj/item/storage/pill_bottle)) //Only pill bottles, please return - - if(stat & NOPOWER) + if(stat & (BROKEN|NOPOWER)) to_chat(user, "\The [src] is unpowered and useless.") return var/obj/item/storage/box/pillbottles/P = over_object + if(!length(P.contents)) + to_chat(user, "\The [P] is empty.") + return + var/items_loaded = 0 for(var/obj/G in P.contents) if(load(G, user)) items_loaded++ if(items_loaded) - user.visible_message( \ - "[user] empties \the [P] into \the [src].", \ - "You empty \the [P] into \the [src].") - if(P.contents.len > 0) - to_chat(user, "Some items are refused.") - SSnanoui.update_uis(src) + user.visible_message("[user] empties \the [P] into \the [src].", "You empty \the [P] into \the [src].") + SSnanoui.update_uis(src) + update_icon() + var/failed = length(P.contents) + if(failed) + to_chat(user, "[failed] item\s [failed == 1 ? "is" : "are"] refused.") -/******************* -* SmartFridge Menu -********************/ - -/obj/machinery/smartfridge/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1) +// UI +/obj/machinery/smartfridge/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = TRUE) user.set_machine(src) ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) @@ -375,13 +225,13 @@ data["drying"] = drying var/list/items[0] - for(var/i=1 to length(item_quants)) + for(var/i in 1 to length(item_quants)) var/K = item_quants[i] var/count = item_quants[K] if(count > 0) items.Add(list(list("display_name" = html_encode(capitalize(K)), "vend" = i, "quantity" = count))) - if(items.len > 0) + if(length(items)) data["contents"] = items return data @@ -403,6 +253,8 @@ if(href_list["vend"]) var/index = text2num(href_list["vend"]) var/amount = text2num(href_list["amount"]) + if(isnull(index) || !ISINDEXSAFE(item_quants, index) || isnull(amount)) + return FALSE var/K = item_quants[index] var/count = item_quants[K] @@ -430,36 +282,343 @@ if(i <= 0) return TRUE return TRUE + return FALSE +/** + * Tries to load an item if it is accepted by [/obj/machinery/smartfridge/proc/accept_check]. + * + * Arguments: + * * I - The item to load. + * * user - The user trying to load the item. + */ +/obj/machinery/smartfridge/proc/load(obj/I, mob/user) + if(accept_check(I)) + if(length(contents) >= max_n_of_items) + to_chat(user, "\The [src] is full.") + return FALSE + else + if(istype(I.loc, /obj/item/storage)) + var/obj/item/storage/S = I.loc + S.remove_from_storage(I, src) + else if(ismob(I.loc)) + var/mob/M = I.loc + if(M.get_active_hand() == I) + if(!M.drop_item()) + to_chat(user, "\The [I] is stuck to you!") + return FALSE + else + M.unEquip(I) + I.forceMove(src) + else + I.forceMove(src) + + item_quants[I.name] += 1 + return TRUE + return FALSE + +/** + * Tries to shoot a random at a nearby living mob. + */ /obj/machinery/smartfridge/proc/throw_item() - var/obj/throw_item = null - var/mob/living/target = locate() in view(7,src) + var/obj/item/throw_item = null + var/mob/living/target = locate() in view(7, src) if(!target) - return 0 + return FALSE for(var/O in item_quants) if(item_quants[O] <= 0) //Try to use a record that actually has something to dump. continue - item_quants[O]-- - for(var/obj/T in contents) - if(T.name == O) - T.forceMove(loc) - throw_item = T + for(var/obj/I in contents) + if(I.name == O) + I.forceMove(loc) + throw_item = I update_icon() break - break if(!throw_item) - return 0 - spawn(0) - throw_item.throw_at(target,16,3,src) - visible_message("[src] launches [throw_item.name] at [target.name]!") - return 1 + return FALSE -// ---------------------------- -// Drying Rack 'smartfridge' -// ---------------------------- + INVOKE_ASYNC(throw_item, /atom/movable.proc/throw_at, target, 16, 3, src) + visible_message("[src] launches [throw_item.name] at [target.name]!") + return TRUE + +/** + * Returns whether the smart fridge can accept the given item. + * + * By default checks if the item is in [the typecache][/obj/machinery/smartfridge/var/accepted_items_typecache]. + * Arguments: + * * O - The item to check. + */ +/obj/machinery/smartfridge/proc/accept_check(obj/item/O) + return is_type_in_typecache(O, accepted_items_typecache) + +/** + * # Secure Fridge + * + * Secure variant of the [Smart Fridge][/obj/machinery/smartfridge]. + * Can be emagged and EMP'd to short the lock. + */ +/obj/machinery/smartfridge/secure + is_secure = TRUE + +/obj/machinery/smartfridge/secure/emag_act(mob/user) + emagged = TRUE + locked = SMART_FRIDGE_LOCK_SHORTED + to_chat(user, "You short out the product lock on \the [src].") + +/obj/machinery/smartfridge/secure/emp_act(severity) + if(!emagged && locked != SMART_FRIDGE_LOCK_SHORTED && prob(40 / severity)) + playsound(loc, 'sound/effects/sparks4.ogg', 60, TRUE) + emagged = TRUE + locked = SMART_FRIDGE_LOCK_SHORTED + +/obj/machinery/smartfridge/secure/Topic(href, href_list) + if(stat & (BROKEN|NOPOWER)) + return FALSE + + if(href_list["vend"] && (usr.contents.Find(src) || Adjacent(usr))) + if(!emagged && locked != SMART_FRIDGE_LOCK_SHORTED && scan_id && !allowed(usr)) + to_chat(usr, "Access denied.") + SSnanoui.update_uis(src) + return FALSE + + return ..() + +/** + * # Seed Storage + * + * Seeds variant of the [Smart Fridge][/obj/machinery/smartfridge]. + * Formerly known as MegaSeed Servitor, but renamed to avoid confusion with the [vending machine][/obj/machinery/vending/hydroseeds]. + */ +/obj/machinery/smartfridge/seeds + name = "\improper Seed Storage" + desc = "When you need seeds fast!" + icon = 'icons/obj/vending.dmi' + icon_state = "seeds" + +/obj/machinery/smartfridge/seeds/Initialize(mapload) + . = ..() + accepted_items_typecache = typecacheof(list( + /obj/item/seeds + )) + +/** + * # Refrigerated Medicine Storage + * + * Medical variant of the [Smart Fridge][/obj/machinery/smartfridge]. + */ +/obj/machinery/smartfridge/medbay + name = "\improper Refrigerated Medicine Storage" + desc = "A refrigerated storage unit for storing medicine and chemicals." + icon_state = "smartfridge" //To fix the icon in the map editor. + +/obj/machinery/smartfridge/medbay/Initialize(mapload) + . = ..() + accepted_items_typecache = typecacheof(list( + /obj/item/reagent_containers/glass, + /obj/item/reagent_containers/iv_bag, + /obj/item/reagent_containers/applicator, + /obj/item/storage/pill_bottle, + /obj/item/reagent_containers/food/pill, + )) + +/** + * # Slime Extract Storage + * + * Secure, Xenobiology variant of the [Smart Fridge][/obj/machinery/smartfridge]. + */ +/obj/machinery/smartfridge/secure/extract + name = "\improper Slime Extract Storage" + desc = "A refrigerated storage unit for slime extracts" + +/obj/machinery/smartfridge/secure/extract/Initialize(mapload) + . = ..() + req_access_txt = "[ACCESS_RESEARCH]" + accepted_items_typecache = typecacheof(list( + /obj/item/slime_extract + )) + +/** + * # Secure Refrigerated Medicine Storage + * + * Secure, Medical variant of the [Smart Fridge][/obj/machinery/smartfridge]. + */ +/obj/machinery/smartfridge/secure/medbay + name = "\improper Secure Refrigerated Medicine Storage" + desc = "A refrigerated storage unit for storing medicine and chemicals." + icon_state = "smartfridge" //To fix the icon in the map editor. + req_one_access_txt = "5;33" + +/obj/machinery/smartfridge/secure/medbay/Initialize(mapload) + . = ..() + accepted_items_typecache = typecacheof(list( + /obj/item/reagent_containers/glass, + /obj/item/reagent_containers/iv_bag, + /obj/item/reagent_containers/applicator, + /obj/item/storage/pill_bottle, + /obj/item/reagent_containers/food/pill, + )) + +/** + * # Smart Chemical Storage + * + * Secure, Chemistry variant of the [Smart Fridge][/obj/machinery/smartfridge]. + */ +/obj/machinery/smartfridge/secure/chemistry + name = "\improper Smart Chemical Storage" + desc = "A refrigerated storage unit for medicine and chemical storage." + icon_state = "smartfridge" //To fix the icon in the map editor. + /// Associative list (/obj/item => /number) representing the items the fridge should initially contain. + var/list/spawn_meds + +/obj/machinery/smartfridge/secure/chemistry/Initialize(mapload) + . = ..() + req_access_txt = "[ACCESS_CHEMISTRY]" + // Spawn initial chemicals + if(mapload) + LAZYINITLIST(spawn_meds) + for(var/typekey in spawn_meds) + var/amount = spawn_meds[typekey] || 1 + while(amount--) + var/obj/item/I = new typekey(src) + item_quants[I.name] += 1 + update_icon() + // Accepted items + accepted_items_typecache = typecacheof(list( + /obj/item/storage/pill_bottle, + /obj/item/reagent_containers, + )) + +/** + * # Smart Chemical Storage (Preloaded) + * + * A [Smart Chemical Storage][/obj/machinery/smartfridge/secure/chemistry] but with some items already in. + */ +/obj/machinery/smartfridge/secure/chemistry/preloaded + // I exist! + +/obj/machinery/smartfridge/secure/chemistry/preloaded/Initialize(mapload) + spawn_meds = list( + /obj/item/reagent_containers/food/pill/epinephrine = 12, + /obj/item/reagent_containers/food/pill/charcoal = 5, + /obj/item/reagent_containers/glass/bottle/epinephrine = 1, + /obj/item/reagent_containers/glass/bottle/charcoal = 1, + ) + . = ..() + +/** + * # Smart Chemical Storage (Preloaded, Syndicate) + * + * A [Smart Chemical Storage (Preloaded)][/obj/machinery/smartfridge/secure/chemistry/preloaded] but with exclusive access to Syndicate. + */ +/obj/machinery/smartfridge/secure/chemistry/preloaded/syndicate + req_access_txt = null + +/obj/machinery/smartfridge/secure/chemistry/preloaded/syndicate/Initialize(mapload) + . = ..() + req_access = list(ACCESS_SYNDICATE) + +/** + * # Disk Compartmentalizer + * + * Disk variant of the [Smart Fridge][/obj/machinery/smartfridge]. + */ +/obj/machinery/smartfridge/disks + name = "disk compartmentalizer" + desc = "A machine capable of storing a variety of disks. Denoted by most as the DSU (disk storage unit)." + icon_state = "disktoaster" + pass_flags = PASSTABLE + visible_contents = FALSE + +/obj/machinery/smartfridge/disks/Initialize(mapload) + . = ..() + accepted_items_typecache = typecacheof(list( + /obj/item/disk, + )) + +/** + * # Smart Virus Storage + * + * Secure, Virology variant of the [Smart Chemical Storage][/obj/machinery/smartfridge/secure/chemistry]. + * Comes with some items. + */ +/obj/machinery/smartfridge/secure/chemistry/virology + name = "\improper Smart Virus Storage" + desc = "A refrigerated storage unit for volatile sample storage." + +/obj/machinery/smartfridge/secure/chemistry/virology/Initialize(mapload) + spawn_meds = list( + /obj/item/reagent_containers/syringe/antiviral = 4, + /obj/item/reagent_containers/glass/bottle/cold = 1, + /obj/item/reagent_containers/glass/bottle/flu_virion = 1, + /obj/item/reagent_containers/glass/bottle/mutagen = 1, + /obj/item/reagent_containers/glass/bottle/plasma = 1, + /obj/item/reagent_containers/glass/bottle/diphenhydramine = 1 + ) + . = ..() + req_access_txt = "[ACCESS_VIROLOGY]" + accepted_items_typecache = typecacheof(list( + /obj/item/reagent_containers/syringe, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/glass/beaker, + )) + +/** + * # Smart Virus Storage (Preloaded) + * + * A [Smart Virus Storage][/obj/machinery/smartfridge/secure/chemistry/virology] but with some additional items. + */ +/obj/machinery/smartfridge/secure/chemistry/virology/preloaded + // I exist! + +/obj/machinery/smartfridge/secure/chemistry/virology/preloaded/Initialize(mapload) + spawn_meds = list( + /obj/item/reagent_containers/syringe/antiviral = 4, + /obj/item/reagent_containers/glass/bottle/cold = 1, + /obj/item/reagent_containers/glass/bottle/flu_virion = 1, + /obj/item/reagent_containers/glass/bottle/mutagen = 1, + /obj/item/reagent_containers/glass/bottle/plasma = 1, + /obj/item/reagent_containers/glass/bottle/reagent/synaptizine = 1, + /obj/item/reagent_containers/glass/bottle/reagent/formaldehyde = 1 + ) + . = ..() + +/** + * # Smart Virus Storage (Preloaded, Syndicate) + * + * A [Smart Virus Storage (Preloaded)][/obj/machinery/smartfridge/secure/chemistry/virology/preloaded] but with exclusive access to Syndicate. + */ +/obj/machinery/smartfridge/secure/chemistry/virology/preloaded/syndicate + req_access_txt = null + +/obj/machinery/smartfridge/secure/chemistry/virology/preloaded/syndicate/Initialize(mapload) + . = ..() + req_access = list(ACCESS_SYNDICATE) + +/** + * # Drink Showcase + * + * Drink variant of the [Smart Fridge][/obj/machinery/smartfridge]. + */ +/obj/machinery/smartfridge/drinks + name = "\improper Drink Showcase" + desc = "A refrigerated storage unit for tasty tasty alcohol." + +/obj/machinery/smartfridge/drinks/Initialize(mapload) + . = ..() + accepted_items_typecache = typecacheof(list( + /obj/item/reagent_containers/glass, + /obj/item/reagent_containers/food/drinks, + /obj/item/reagent_containers/food/condiment, + )) + +/** + * # Drying Rack + * + * Variant of the [Smart Fridge][/obj/machinery/smartfridge] for drying stuff. + * Doesn't have components. + */ /obj/machinery/smartfridge/drying_rack name = "drying rack" desc = "A wooden contraption, used to dry plant products, food and leather." @@ -471,11 +630,16 @@ can_dry = TRUE visible_contents = FALSE -/obj/machinery/smartfridge/drying_rack/New() - ..() - if(component_parts && component_parts.len) - component_parts.Cut() +/obj/machinery/smartfridge/drying_rack/Initialize(mapload) + . = ..() + // Remove components, this is wood duh + QDEL_LIST(component_parts) component_parts = null + // Accepted items + accepted_items_typecache = typecacheof(list( + /obj/item/reagent_containers/food/snacks, + /obj/item/stack/sheet/wetleather, + )) /obj/machinery/smartfridge/drying_rack/on_deconstruction() new /obj/item/stack/sheet/wood(loc, 10) @@ -484,34 +648,6 @@ /obj/machinery/smartfridge/drying_rack/RefreshParts() return -/obj/machinery/smartfridge/drying_rack/default_deconstruction_screwdriver() - return - -/obj/machinery/smartfridge/drying_rack/exchange_parts() - return - -/obj/machinery/smartfridge/drying_rack/spawn_frame() - return - -/obj/machinery/smartfridge/drying_rack/default_deconstruction_crowbar(user, obj/item/crowbar/C, ignore_panel = 1) - ..() - -/obj/machinery/smartfridge/drying_rack/Topic(href, href_list) - if(..()) - return 1 - if(href_list["dryingOn"]) - drying = TRUE - use_power = ACTIVE_POWER_USE - update_icon() - return 1 - - if(href_list["dryingOff"]) - drying = FALSE - use_power = IDLE_POWER_USE - update_icon() - return 1 - return 0 - /obj/machinery/smartfridge/drying_rack/power_change() if(powered() && anchored) stat &= ~NOPOWER @@ -520,35 +656,67 @@ toggle_drying(TRUE) update_icon() -/obj/machinery/smartfridge/drying_rack/load(obj/I, mob/user) //For updating the filled overlay +/obj/machinery/smartfridge/drying_rack/screwdriver_act(mob/living/user, obj/item/I) + return + +/obj/machinery/smartfridge/drying_rack/exchange_parts() + return + +/obj/machinery/smartfridge/drying_rack/spawn_frame() + return + +/obj/machinery/smartfridge/drying_rack/crowbar_act(mob/living/user, obj/item/I) + . = default_deconstruction_crowbar(user, I, TRUE) + +/obj/machinery/smartfridge/drying_rack/emp_act(severity) + ..() + atmos_spawn_air(LINDA_SPAWN_HEAT) + +/obj/machinery/smartfridge/drying_rack/Topic(href, href_list) if(..()) + return TRUE + + if(href_list["dryingOn"]) + drying = TRUE + use_power = ACTIVE_POWER_USE update_icon() - return 1 + return TRUE + + if(href_list["dryingOff"]) + drying = FALSE + use_power = IDLE_POWER_USE + update_icon() + return TRUE + + return FALSE /obj/machinery/smartfridge/drying_rack/update_icon() ..() - overlays.Cut() if(drying) overlays += "drying_rack_drying" - if(contents.len) + if(length(contents)) overlays += "drying_rack_filled" /obj/machinery/smartfridge/drying_rack/process() ..() - if(drying) - if(rack_dry())//no need to update unless something got dried - update_icon() + if(drying && rack_dry())//no need to update unless something got dried + update_icon() /obj/machinery/smartfridge/drying_rack/accept_check(obj/item/O) + . = ..() + // If it's a food, reject non driable ones if(istype(O, /obj/item/reagent_containers/food/snacks)) var/obj/item/reagent_containers/food/snacks/S = O - if(S.dried_type) - return TRUE - if(istype(O, /obj/item/stack/sheet/wetleather)) - return TRUE - return FALSE + if(!S.dried_type) + return FALSE +/** + * Toggles the drying process. + * + * Arguments: + * * forceoff - Whether to force turn off the drying rack. + */ /obj/machinery/smartfridge/drying_rack/proc/toggle_drying(forceoff) if(drying || forceoff) drying = FALSE @@ -558,6 +726,9 @@ use_power = ACTIVE_POWER_USE update_icon() +/** + * Called in [/obj/machinery/smartfridge/drying_rack/process] to dry the contents. + */ /obj/machinery/smartfridge/drying_rack/proc/rack_dry() for(var/obj/item/reagent_containers/food/snacks/S in contents) if(S.dried_type == S.type)//if the dried type is the same as the object's type, don't bother creating a whole new item... @@ -581,30 +752,4 @@ return TRUE return FALSE -/obj/machinery/smartfridge/drying_rack/emp_act(severity) - ..() - atmos_spawn_air(LINDA_SPAWN_HEAT) - -/************************ -* Secure SmartFridges -*************************/ -/obj/machinery/smartfridge/secure/emag_act(mob/user) - emagged = 1 - locked = -1 - to_chat(user, "You short out the product lock on [src].") - -/obj/machinery/smartfridge/secure/emp_act(severity) - if(prob(40/severity) && (!emagged) && (locked != -1)) - playsound(loc, 'sound/effects/sparks4.ogg', 60, 1) - emagged = 1 - locked = -1 - -/obj/machinery/smartfridge/secure/Topic(href, href_list) - if(stat & (NOPOWER|BROKEN)) - return 0 - if(usr.contents.Find(src) || (in_range(src, usr) && istype(loc, /turf))) - if(!allowed(usr) && !emagged && locked != -1 && scan_id && href_list["vend"]) - to_chat(usr, "Access denied.") - SSnanoui.update_uis(src) - return 0 - return ..() +#undef SMART_FRIDGE_LOCK_SHORTED diff --git a/code/modules/library/admin.dm b/code/modules/library/admin.dm index db907fc20ba..b56aa6994cf 100644 --- a/code/modules/library/admin.dm +++ b/code/modules/library/admin.dm @@ -3,8 +3,7 @@ set desc = "Permamently deletes a book from the database." set category = "Admin" - if(!holder) - to_chat(src, "Only administrators may use this command.") + if(!check_rights(R_ADMIN)) return var/isbn = input("ISBN number?", "Delete Book") as num | null @@ -25,8 +24,7 @@ set desc = "View books flagged for content." set category = "Admin" - if(!holder) - to_chat(src, "Only administrators may use this command.") + if(!check_rights(R_ADMIN)) return holder.view_flagged_books() diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 336ebdb8b67..31ff0098335 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -53,7 +53,6 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) if(ismob(body)) T = get_turf(body) //Where is the body located? attack_log_old = body.attack_log_old //preserve our attack logs by copying them to our ghost - logs = body.logs.Copy() var/mutable_appearance/MA = copy_appearance(body) if(body.mind && body.mind.name) diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index 2b07e10291f..410c485f15e 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -47,7 +47,7 @@ . = trim(. + trim(msg)) . += "\"" -/mob/proc/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency) +/mob/proc/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE) if(!client) return 0 @@ -74,7 +74,7 @@ return 0 var/speaker_name = speaker.name - if(ishuman(speaker)) + if(use_voice && ishuman(speaker)) var/mob/living/carbon/human/H = speaker speaker_name = H.GetVoice() @@ -99,9 +99,9 @@ if(speaker == src) to_chat(src, "You cannot hear yourself speak!") else - to_chat(src, "[speaker_name][speaker.GetAltName()] talks but you cannot hear [speaker.p_them()].") + to_chat(src, "[speaker.name] talks but you cannot hear [speaker.p_them()].") else - to_chat(src, "[speaker_name][speaker.GetAltName()] [track][message]") + to_chat(src, "[speaker_name][use_voice ? speaker.GetAltName() : ""] [track][message]") if(speech_sound && (get_dist(speaker, src) <= world.view && src.z == speaker.z)) var/turf/source = speaker? get_turf(speaker) : get_turf(src) playsound_local(source, speech_sound, sound_vol, 1, sound_frequency) diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm index aeee677b905..f281b8abec3 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -81,7 +81,7 @@ /mob/living/carbon/alien/larva/show_inv(mob/user as mob) return -/mob/living/carbon/alien/larva/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) +/mob/living/carbon/alien/larva/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE) return FALSE /* Commented out because it's duplicated in life.dm diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm index 5d828e7c181..79ed90a61b8 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -70,7 +70,7 @@ return polling = 1 spawn() - var/list/candidates = pollCandidates("Do you want to play as an alien?", ROLE_ALIEN, 0) + var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as an alien?", ROLE_ALIEN, FALSE, source = /mob/living/carbon/alien/larva) var/mob/C = null // To stop clientless larva, we will check that our host has a client diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index e07930f1241..d495ca3d2f4 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1447,12 +1447,13 @@ var/obj/item/organ/internal/eyes/eyes = get_int_organ(/obj/item/organ/internal/eyes) var/obj/item/organ/internal/cyberimp/eyes/eye_implant = get_int_organ(/obj/item/organ/internal/cyberimp/eyes) if(istype(dna.species) && dna.species.eyes) - var/icon/eyes_icon = new /icon('icons/mob/human_face.dmi', dna.species.eyes) + var/icon/eyes_icon if(eye_implant) //Eye implants override native DNA eye colo(u)r eyes_icon = eye_implant.generate_icon() else if(eyes) eyes_icon = eyes.generate_icon() else //Error 404: Eyes not found! + eyes_icon = new('icons/mob/human_face.dmi', dna.species.eyes) eyes_icon.Blend("#800000", ICON_ADD) return eyes_icon diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index f5496652dad..e667833dfe3 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -273,11 +273,6 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) overlays_standing[UNDERWEAR_LAYER] = mutable_appearance(underwear_standing, layer = -UNDERWEAR_LAYER) apply_overlay(UNDERWEAR_LAYER) - if(lip_style && (LIPS in dna.species.species_traits)) - var/mutable_appearance/lips = mutable_appearance('icons/mob/human_face.dmi', "lips_[lip_style]_s") - lips.color = lip_color - standing += lips - overlays_standing[BODY_LAYER] = standing apply_overlay(BODY_LAYER) //tail @@ -1303,6 +1298,11 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) else . += "#000000" + if(lip_color && (LIPS in dna.species.species_traits)) + . += "[lip_color]" + else + . += "#000000" + for(var/organ_tag in dna.species.has_limbs) var/obj/item/organ/external/part = bodyparts_by_name[organ_tag] if(isnull(part)) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 93a3b6f14cd..c05aae7e664 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -3,8 +3,29 @@ var/datum/atom_hud/data/human/medical/advanced/medhud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] medhud.add_to_hud(src) faction += "\ref[src]" + determine_move_and_pull_forces() GLOB.mob_living_list += src +// Used to determine the forces dependend on the mob size +// Will only change the force if the force was not set in the mob type itself +/mob/living/proc/determine_move_and_pull_forces() + var/value + switch(mob_size) + if(MOB_SIZE_TINY) + value = MOVE_FORCE_EXTREMELY_WEAK + if(MOB_SIZE_SMALL) + value = MOVE_FORCE_WEAK + if(MOB_SIZE_HUMAN) + value = MOVE_FORCE_NORMAL + if(MOB_SIZE_LARGE) + value = MOVE_FORCE_NORMAL // For now + if(!move_force) + move_force = value + if(!pull_force) + pull_force = value + if(!move_resist) + move_resist = value + /mob/living/prepare_huds() ..() prepare_data_huds() @@ -203,7 +224,7 @@ set category = "Object" if(istype(AM) && Adjacent(AM)) - start_pulling(AM) + start_pulling(AM, show_message = TRUE) else stop_pulling() @@ -921,10 +942,10 @@ return 0 return 1 -/mob/living/start_pulling(atom/movable/AM, state, force = pull_force, supress_message = FALSE) +/mob/living/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE) if(!AM || !src) return FALSE - if(!(AM.can_be_pulled(src, state, force))) + if(!(AM.can_be_pulled(src, state, force, show_message))) return FALSE if(incapacitated()) return diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 1f0f1cfa0f4..4da8aa97e06 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -219,7 +219,7 @@ fire_stacks += L.fire_stacks IgniteMob() -/mob/living/can_be_pulled(user, grab_state, force) +/mob/living/can_be_pulled(user, grab_state, force, show_message = FALSE) return ..() && !(buckled && buckled.buckle_prevents_pull) /mob/living/water_act(volume, temperature, source, method = REAGENT_TOUCH) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index e230f7a2b99..c3575f4a7b5 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -2,6 +2,11 @@ see_invisible = SEE_INVISIBLE_LIVING pressure_resistance = 10 + // Will be determined based on mob size if left null. Done in living/proc/determine_move_and_pull_forces() + move_resist = null + move_force = null + pull_force = null + //Health and life related vars var/maxHealth = 100 //Maximum health that should be possible. var/health = 100 //A mob's health diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 630b2223321..d4546f4f6bc 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -451,14 +451,14 @@ proc/get_radio_key_from_channel(var/channel) var/speech_bubble_test = say_test(message) for(var/mob/M in listening) - M.hear_say(message_pieces, verb, italics, src) + M.hear_say(message_pieces, verb, italics, src, use_voice = FALSE) if(M.client) speech_bubble_recipients.Add(M.client) if(eavesdropping.len) stars_all(message_pieces) //hopefully passing the message twice through stars() won't hurt... I guess if you already don't understand the language, when they speak it too quietly to hear normally you would be able to catch even less. for(var/mob/M in eavesdropping) - M.hear_say(message_pieces, verb, italics, src) + M.hear_say(message_pieces, verb, italics, src, use_voice = FALSE) if(M.client) speech_bubble_recipients.Add(M.client) diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index d23fedb09bc..faacf9ba359 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -142,7 +142,7 @@ acceleration = !acceleration to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].") -/mob/camera/aiEye/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency) +/mob/camera/aiEye/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE) if(relay_speech) if(istype(ai)) ai.relay_speech(speaker, message_pieces, verb) diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm index 599063c2724..a4e90b50f8e 100644 --- a/code/modules/mob/living/silicon/ai/say.dm +++ b/code/modules/mob/living/silicon/ai/say.dm @@ -80,17 +80,26 @@ GLOBAL_VAR_INIT(announcing_vox, 0) // Stores the time of the last announcement
  • Do not use punctuation as you would normally, if you want a pause you can use the full stop and comma characters by separating them with spaces, like so: 'Alpha . Test , Bravo'.
  • \ WARNING:
    Misuse of the announcement system will get you job banned.
    " - var/index = 0 - for(var/word in GLOB.vox_sounds) - index++ - dat += "[capitalize(word)]" - if(index != GLOB.vox_sounds.len) - dat += " / " + // Show alert and voice sounds separately + var/vox_words = GLOB.vox_sounds - GLOB.vox_alerts + dat = help_format(GLOB.vox_alerts, dat) + dat = help_format(vox_words, dat) var/datum/browser/popup = new(src, "announce_help", "Announcement Help", 500, 400) popup.set_content(dat) popup.open() +/mob/living/silicon/ai/proc/help_format(word_list, dat) + var/index = 0 + for(var/word in word_list) + index++ + dat += "[capitalize(word)]" + if(index != length(word_list)) + dat += " / " + else + dat += "
    " + return dat + /mob/living/silicon/ai/proc/ai_announcement() if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) return diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 2a5a7b0aa08..db95f18c210 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -517,7 +517,7 @@ /mob/living/silicon/pai/Bumped() return -/mob/living/silicon/pai/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) +/mob/living/silicon/pai/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE) return FALSE /mob/living/silicon/pai/update_canmove(delay_action_updates = 0) diff --git a/code/modules/mob/living/silicon/robot/drone/drone.dm b/code/modules/mob/living/silicon/robot/drone/drone.dm index 113fbd87bae..270606361cb 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone.dm @@ -19,6 +19,7 @@ ventcrawler = 2 magpulse = 1 mob_size = MOB_SIZE_SMALL + pull_force = MOVE_FORCE_VERY_WEAK // Can only drag small items modules_break = FALSE @@ -35,6 +36,14 @@ var/reboot_cooldown = 60 // one minute var/last_reboot var/emagged_time + var/list/pullable_drone_items = list( + /obj/item/pipe, + /obj/structure/disposalconstruct, + /obj/item/stack/cable_coil, + /obj/item/stack/rods, + /obj/item/stack/sheet, + /obj/item/stack/tile + ) holder_type = /obj/item/holder/drone // var/sprite[0] @@ -69,6 +78,10 @@ verbs -= /mob/living/silicon/robot/verb/Namepick module = new /obj/item/robot_module/drone(src) + //Allows Drones to hear the Engineering channel. + module.channels = list("Engineering" = 1) + radio.recalculateChannels() + //Grab stacks. stack_metal = locate(/obj/item/stack/sheet/metal/cyborg) in src.module stack_wood = locate(/obj/item/stack/sheet/wood) in src.module @@ -154,15 +167,17 @@ return else - user.visible_message("\the [user] swipes [user.p_their()] ID card through [src], attempting to shut it down.", "You swipe your ID card through \the [src], attempting to shut it down.") + var/confirm = alert("Using your ID on a Maintenance Drone will shut it down, are you sure you want to do this?", "Disable Drone", "Yes", "No") + if(confirm == ("Yes") && (user in range(3, src))) + user.visible_message("\the [user] swipes [user.p_their()] ID card through [src], attempting to shut it down.", "You swipe your ID card through \the [src], attempting to shut it down.") - if(emagged) - return + if(emagged) + return - if(allowed(W)) - shut_down() - else - to_chat(user, "Access denied.") + if(allowed(W)) + shut_down() + else + to_chat(user, "Access denied.") return @@ -321,20 +336,22 @@ /mob/living/silicon/robot/drone/Bumped(atom/movable/AM) return -/mob/living/silicon/robot/drone/start_pulling(var/atom/movable/AM) +/mob/living/silicon/robot/drone/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE) + + if(is_type_in_list(AM, pullable_drone_items)) + ..(AM, force = INFINITY) // Drone power! Makes them able to drag pipes and such - if(istype(AM,/obj/item/pipe) || istype(AM,/obj/structure/disposalconstruct)) - ..() else if(istype(AM,/obj/item)) var/obj/item/O = AM if(O.w_class > WEIGHT_CLASS_SMALL) - to_chat(src, "You are too small to pull that.") + if(show_message) + to_chat(src, "You are too small to pull that.") return else ..() else - to_chat(src, "You are too small to pull that.") - return + if(show_message) + to_chat(src, "You are too small to pull that.") /mob/living/silicon/robot/drone/add_robot_verbs() src.verbs |= silicon_subsystems diff --git a/code/modules/mob/living/silicon/robot/drone/drone_items.dm b/code/modules/mob/living/silicon/robot/drone/drone_items.dm index a0582c135e4..16e81f53276 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_items.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_items.dm @@ -8,7 +8,6 @@ //Has a list of items that it can hold. var/list/can_hold = list( - /obj/item/stock_parts/cell, /obj/item/firealarm_electronics, /obj/item/airalarm_electronics, /obj/item/airlock_electronics, @@ -24,6 +23,8 @@ /obj/item/mounted/frame/firealarm, /obj/item/mounted/frame/newscaster_frame, /obj/item/mounted/frame/intercom, + /obj/item/mounted/frame/extinguisher, + /obj/item/mounted/frame/light_switch, /obj/item/rack_parts, /obj/item/camera_assembly, /obj/item/tank, diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 57a275a1592..f6952bd06d2 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1438,7 +1438,9 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( default_cell_type = /obj/item/stock_parts/cell/bluespace /mob/living/silicon/robot/destroyer/init(alien = FALSE, mob/living/silicon/ai/ai_to_sync_to = null) - ..() + aiCamera = new/obj/item/camera/siliconcam/robot_camera(src) + additional_law_channels["Binary"] = ":b " + laws = new /datum/ai_laws/deathsquad module = new /obj/item/robot_module/destroyer(src) module.add_languages(src) module.add_subsystems_and_actions(src) @@ -1447,6 +1449,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( qdel(radio) radio = new /obj/item/radio/borg/ert/specops(src) radio.recalculateChannels() + playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0) /mob/living/silicon/robot/destroyer/borg_icons() if(base_icon == "") diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index ea8e949f1c7..58e3ad54089 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -126,6 +126,7 @@ gold_core_spawnable = FRIENDLY_SPAWN blood_volume = BLOOD_VOLUME_NORMAL var/obj/item/udder/udder = null + gender = FEMALE /mob/living/simple_animal/cow/Initialize() udder = new() diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm index 2881141f4dd..be3f985db2e 100644 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm @@ -85,8 +85,11 @@ get_scooped(M) ..() -/mob/living/simple_animal/mouse/start_pulling(var/atom/movable/AM)//Prevents mouse from pulling things - to_chat(src, "You are too small to pull anything.") +/mob/living/simple_animal/mouse/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE)//Prevents mouse from pulling things + if(istype(AM, /obj/item/reagent_containers/food/snacks/cheesewedge)) + return ..() // Get dem + if(show_message) + to_chat(src, "You are too small to pull anything except cheese.") return /mob/living/simple_animal/mouse/Crossed(AM as mob|obj, oldloc) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/fish.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/fish.dm index f44e6c1f88a..ca6987d735c 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/fish.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/fish.dm @@ -26,21 +26,34 @@ flying = 1 var/carp_color = "carp" //holder for icon set - var/list/icon_sets = list("carp", "blue", "yellow", "grape", "rust", "teal", "purple") + var/static/list/carp_colors = list(\ + "lightpurple" = "#c3b9f1", \ + "lightpink" = "#da77a8", \ + "green" = "#70ff25", \ + "grape" = "#df0afb", \ + "swamp" = "#e5e75a", \ + "turquoise" = "#04e1ed", \ + "brown" = "#ca805a", \ + "teal" = "#20e28e", \ + "lightblue" = "#4d88cc", \ + "rusty" = "#dd5f34", \ + "beige" = "#bbaeaf", \ + "yellow" = "#f3ca4a", \ + "blue" = "#09bae1", \ + "palegreen" = "#7ef099", \ + ) /mob/living/simple_animal/hostile/retaliate/carp/Initialize(mapload) . = ..() carp_randomify() update_icons() -/mob/living/simple_animal/hostile/retaliate/carp/proc/carp_randomify() - if(prob(1)) - carp_color = pick("white", "black") - else - carp_color = pick(icon_sets) - icon_state = "[carp_color]" - icon_living = "[carp_color]" - icon_dead = "[carp_color]_dead" +/mob/living/simple_animal/hostile/retaliate/carp/proc/carp_randomify(rarechance) + // Simplified version of: /mob/living/simple_animal/hostile/carp/proc/carp_randomify(rarechance) + var/our_color + our_color = pick(carp_colors) + add_atom_colour(carp_colors[our_color], FIXED_COLOUR_PRIORITY) + regenerate_icons() /mob/living/simple_animal/hostile/retaliate/carp/koi name = "space koi" @@ -61,13 +74,15 @@ maxbodytemp = 1500 gold_core_spawnable = HOSTILE_SPAWN + var/randomize_icon = TRUE /mob/living/simple_animal/hostile/retaliate/carp/koi/Initialize(mapload) . = ..() - var/koinum = rand(1, 4) - icon_state = "koi[koinum]" - icon_living = "koi[koinum]" - icon_dead = "koi[koinum]-dead" + if(randomize_icon) + var/koinum = rand(1, 4) + icon_state = "koi[koinum]" + icon_living = "koi[koinum]" + icon_dead = "koi[koinum]-dead" /mob/living/simple_animal/hostile/retaliate/carp/koi/Process_Spacemove(var/movement_dir) return TRUE @@ -76,3 +91,4 @@ icon_state = "koi5" icon_living = "koi5" icon_dead = "koi5-dead" + randomize_icon = FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/green.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/green.dm index e7daced65ff..1946f1a32ee 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/green.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/green.dm @@ -51,6 +51,10 @@ if(!(eggtype in eggtypes)) to_chat(src, "Unrecognized egg type.") return 0 + if(fed < feedings_to_lay) + // We have to check this again after the popup, to account for people spam-clicking the button, then doing all the popups at once. + to_chat(src, "You must wrap more humanoid prey before you can do this!") + return visible_message("[src] lays a cluster of eggs.") if(eggtype == TS_DESC_RED) DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/red, 1) diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm index bf3c98bb86a..4b18626a687 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm @@ -287,6 +287,10 @@ if(eggtype == null || numlings == null) to_chat(src, "Cancelled.") return + if(canlay < numlings) + // We have to check this again after the popups, to account for people spam-clicking the button, then doing all the popups at once. + to_chat(src, "Too soon to do this again!") + return canlay -= numlings eggslaid += numlings if(eggtype == TS_DESC_RED) diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 41786017ea2..97dbd831dd0 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -710,7 +710,7 @@ ears.talk_into(src, message_pieces, message_mode, verb) used_radios += ears -/mob/living/simple_animal/parrot/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency) +/mob/living/simple_animal/parrot/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE) if(speaker != src && prob(50)) parrot_hear(html_decode(multilingual_to_message(message_pieces))) ..() diff --git a/code/modules/mob/living/simple_animal/posessed_object.dm b/code/modules/mob/living/simple_animal/posessed_object.dm index c3cb61d5970..729790d098c 100644 --- a/code/modules/mob/living/simple_animal/posessed_object.dm +++ b/code/modules/mob/living/simple_animal/posessed_object.dm @@ -32,8 +32,9 @@ animate_ghostly_presence(src, -1, 20, 1) // Restart the floating animation after the attack animation, as it will be cancelled. -/mob/living/simple_animal/possessed_object/start_pulling(var/atom/movable/AM) // Silly motherfuckers think they can pull things. - to_chat(src, "You are unable to pull [AM]!") +/mob/living/simple_animal/possessed_object/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE) // Silly motherfuckers think they can pull things. + if(show_message) + to_chat(src, "You are unable to pull [AM]!") /mob/living/simple_animal/possessed_object/ghost() // Ghosting will return the object to normal, and will not disqualify the ghoster from various mid-round antag positions. diff --git a/code/modules/mob/living/simple_animal/slime/say.dm b/code/modules/mob/living/simple_animal/slime/say.dm index 74241dc676c..8679d64a699 100644 --- a/code/modules/mob/living/simple_animal/slime/say.dm +++ b/code/modules/mob/living/simple_animal/slime/say.dm @@ -9,7 +9,7 @@ return verb -/mob/living/simple_animal/slime/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency) +/mob/living/simple_animal/slime/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE) if(speaker != src && !stat) if(speaker in Friends) speech_buffer = list() diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index 15f483bef76..1b67b53119a 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -265,7 +265,7 @@ /mob/living/simple_animal/slime/unEquip(obj/item/I, force) return -/mob/living/simple_animal/slime/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) +/mob/living/simple_animal/slime/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE) return /mob/living/simple_animal/slime/attack_ui(slot) diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 5345bda5b94..c222f76eadb 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -28,6 +28,7 @@ /mob/Login() GLOB.player_list |= src + last_known_ckey = ckey update_Login_details() world.update_status() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 1c7f8fa6ae9..848606be3ec 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -19,7 +19,6 @@ for(var/datum/alternate_appearance/AA in viewing_alternate_appearances) AA.viewers -= src viewing_alternate_appearances = null - logs.Cut() LAssailant = null return ..() @@ -578,6 +577,8 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \ changeNext_move(CLICK_CD_POINT) var/obj/P = new /obj/effect/temp_visual/point(tile) P.invisibility = invisibility + P.pixel_x = A.pixel_x + P.pixel_y = A.pixel_y return 1 /mob/proc/ret_grab(obj/effect/list_container/mobl/L as obj, flag) @@ -1238,10 +1239,13 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \ create_log_in_list(debug_log, text, collapse, world.timeofday) /mob/proc/create_log(log_type, what, target = null, turf/where = get_turf(src)) - LAZYINITLIST(logs[log_type]) - var/list/log_list = logs[log_type] + if(!ckey) + return + var/real_ckey = ckey + if(ckey[1] == "@") // Admin aghosting will do this + real_ckey = copytext(ckey, 2) var/datum/log_record/record = new(log_type, src, what, target, where, world.time) - log_list.Add(record) + GLOB.logging.add_log(real_ckey, record) /proc/create_log_in_list(list/target, text, collapse = TRUE, last_log)//forgive me code gods for this shitcode proc //this proc enables lovely stuff like an attack log that looks like this: "[18:20:29-18:20:45]21x John Smith attacked Andrew Jackson with a crowbar." diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 1477be47d89..80404460b50 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -36,7 +36,7 @@ var/list/attack_log_old = list( ) var/list/debug_log = null - var/list/logs = list() // Logs for each log type defined in __DEFINES/logs.dm + var/last_known_ckey = null // Used in logging var/last_log = 0 var/obj/machinery/machine = null diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 5fb5bcb06c8..ff01fbe5cc9 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -113,7 +113,7 @@ var/question = "Do you want to play as [M.real_name ? M.real_name : M.name][M.job ? " ([M.job])" : ""]" if(alert("Do you want to show the antag status?","Show antag status","Yes","No") == "Yes") question += ", [M.mind?.special_role ? M.mind?.special_role : "No special role"]" - var/list/mob/dead/observer/candidates = pollCandidates("[question]?", poll_time = 100, min_hours = minhours) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("[question]?", poll_time = 10 SECONDS, min_hours = minhours, source = M) var/mob/dead/observer/theghost = null if(LAZYLEN(candidates)) diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 0417e28f546..41032185c3b 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -55,6 +55,7 @@ return say_dead_direct("[pick("complains", "moans", "whines", "laments", "blubbers", "salts")], \"[message]\"", src) + create_log(DEADCHAT_LOG, message) /mob/proc/say_understands(var/mob/other, var/datum/language/speaking = null) if(stat == DEAD) diff --git a/code/modules/modular_computers/file_system/programs/command/card.dm b/code/modules/modular_computers/file_system/programs/command/card.dm index b85fb710e38..cae4b5ed2e2 100644 --- a/code/modules/modular_computers/file_system/programs/command/card.dm +++ b/code/modules/modular_computers/file_system/programs/command/card.dm @@ -222,7 +222,7 @@ if(is_authenticated(usr) && modify) var/t1 = href_list["assign_target"] if(t1 == "Custom") - var/temp_t = sanitize(copytext(input("Enter a custom job assignment.","Assignment"),1,MAX_MESSAGE_LEN)) + var/temp_t = sanitize(reject_bad_name(copytext(input("Enter a custom job assignment.", "Assignment"), 1, MAX_MESSAGE_LEN), TRUE)) //let custom jobs function as an impromptu alt title, mainly for sechuds if(temp_t && modify) SSjobs.log_job_transfer(modify.registered_name, modify.getRankAndAssignment(), temp_t, scan.registered_name) @@ -251,7 +251,8 @@ message_admins("[key_name_admin(usr)] has reassigned \"[modify.registered_name]\" from \"[jobnamedata]\" to \"[t1]\".") SSjobs.log_job_transfer(modify.registered_name, jobnamedata, t1, scan.registered_name) - SSjobs.slot_job_transfer(modify.rank, t1) + if(modify.owner_uid) + SSjobs.slot_job_transfer(modify.rank, t1) var/mob/living/carbon/human/H = modify.getPlayer() if(istype(H)) @@ -266,7 +267,7 @@ if("PRG_reg") if(is_authenticated(usr)) - var/temp_name = reject_bad_name(href_list["reg"]) + var/temp_name = reject_bad_name(href_list["reg"], TRUE) if(temp_name) modify.registered_name = temp_name else @@ -290,6 +291,8 @@ if(is_authenticated(usr)) var/delcount = SSjobs.delete_log_records(scan.registered_name, TRUE) if(delcount) + message_admins("[key_name_admin(usr)] has wiped all ID computer logs.") + usr.create_log(MISC_LOG, "wiped all ID computer logs.") playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) if("PRG_print") @@ -331,9 +334,14 @@ if("PRG_terminate") if(is_authenticated(usr)) var/jobnamedata = modify.getRankAndAssignment() - log_game("[key_name(usr)] has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\".") - message_admins("[key_name_admin(usr)] has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\".") + var/reason = sanitize(copytext(input("Enter legal reason for termination. Enter nothing to cancel.", "Employment Termination"), 1, MAX_MESSAGE_LEN)) + if(!reason || !is_authenticated(usr) || !modify) + return + log_game("[key_name(usr)] has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\" for: \"[reason]\".") + message_admins("[key_name_admin(usr)] has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\" for: \"[reason]\".") + usr.create_log(MISC_LOG, "terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\"") SSjobs.log_job_transfer(modify.registered_name, jobnamedata, "Terminated", scan.registered_name) + SSjobs.notify_dept_head(modify.rank, "[scan.registered_name] has terminated the employment of \"[modify.registered_name]\" the \"[jobnamedata]\" for \"[reason]\".") modify.assignment = "Terminated" modify.access = list() diff --git a/code/modules/ninja/suit/suit_initialisation.dm b/code/modules/ninja/suit/suit_initialisation.dm index 59303b4707c..b9200d0e771 100644 --- a/code/modules/ninja/suit/suit_initialisation.dm +++ b/code/modules/ninja/suit/suit_initialisation.dm @@ -14,30 +14,30 @@ suitBusy = 1 if(suitActive && (alert("Confirm suit systems shutdown? This cannot be halted once it has started.", "Confirm Shutdown", "Yes", "No") == "Yes")) - to_chat(usr, "Now de-initializing...") + to_chat(usr, "Now de-initializing...") sleep(15) - to_chat(usr, "Logging off, [usr.real_name]. Shutting down SpiderOS.") + to_chat(usr, "Logging off, [usr.real_name]. Shutting down SpiderOS.") sleep(10) - to_chat(usr, "Primary system status: OFFLINE.\nBackup system status: OFFLINE.") + to_chat(usr, "Primary system status: OFFLINE.\nBackup system status: OFFLINE.") sleep(5) - to_chat(usr, "VOID-shift device status: OFFLINE.\nCLOAK-tech device status: OFFLINE.") + to_chat(usr, "VOID-shift device status: OFFLINE.\nCLOAK-tech device status: OFFLINE.") //TODO: Shut down any active abilities sleep(10) - to_chat(usr, "Disconnecting neural-net interface... Success.") + to_chat(usr, "Disconnecting neural-net interface... Success.") QDEL_NULL(usr.hud_used) usr.create_mob_hud() usr.regenerate_icons() sleep(5) - to_chat(usr, "Disengaging neural-net interface... Success.") + to_chat(usr, "Disengaging neural-net interface... Success.") sleep(10) - to_chat(usr, "Unsecuring external locking mechanism...\nNeural-net abolished.\nOperation status: FINISHED.") + to_chat(usr, "Unsecuring external locking mechanism...\nNeural-net abolished.\nOperation status: FINISHED.") //TODO: Grant verbs toggle_suit_lock(usr) usr.regenerate_icons() @@ -45,24 +45,24 @@ suitActive = 0 else if(!suitActive) // Activate the suit. - to_chat(usr, "Now initializing...") + to_chat(usr, "Now initializing...") sleep(15) - to_chat(usr, "Now establishing neural-net interface...") + to_chat(usr, "Now establishing neural-net interface...") if(usr.mind.special_role != "Ninja") to_chat(usr, "FĆAL �Rr�R: ŧer nt recgnized, c-cntr-r䣧-ç äcked.") return sleep(10) - to_chat(usr, "Neural-net established. Now monitoring brainwave pattern. \nBrainwave pattern GREEN, proceeding.") + to_chat(usr, "Neural-net established. Now monitoring brainwave pattern. \nBrainwave pattern GREEN, proceeding.") sleep(10) - to_chat(usr, "Securing external locking mechanism...") + to_chat(usr, "Securing external locking mechanism...") if(!toggle_suit_lock(usr)) return sleep(5) - to_chat(usr, "Suit secured, extending neural-net interface...") + to_chat(usr, "Suit secured, extending neural-net interface...") QDEL_NULL(usr.hud_used) usr.hud_used = new /datum/hud/human(usr, 'icons/mob/screen_ninja.dmi', "#ffffff", 255) @@ -71,22 +71,22 @@ usr.regenerate_icons() sleep(10) - to_chat(usr, "VOID-shift device status: ONLINE.\nCLOAK-tech device status:ONLINE") + to_chat(usr, "VOID-shift device status: ONLINE.\nCLOAK-tech device status:ONLINE") sleep(5) - to_chat(usr, "Primary system status: ONLINE.\nBackup system status: ONLINE.") + to_chat(usr, "Primary system status: ONLINE.\nBackup system status: ONLINE.") if(suitCell) - to_chat(usr, "Current energy capacity: [suitCell.charge]/[suitCell.maxcharge].") + to_chat(usr, "Current energy capacity: [suitCell.charge]/[suitCell.maxcharge].") sleep(10) - to_chat(usr, "All systems operational. Welcome to SpiderOS, [usr.real_name].") + to_chat(usr, "All systems operational. Welcome to SpiderOS, [usr.real_name].") //TODO: Grant ninja verbs here. suitBusy = 0 suitActive = 1 else suitBusy = 0 - to_chat(usr, "NOTICE: Suit de-activation protocals aborted.") + to_chat(usr, "NOTICE: Suit de-activation protocals aborted.") else to_chat(usr, "FĆAL �Rr�R: ŧer nt recgnized, c-cntr-r䣧-ç äcked.") return diff --git a/code/modules/paperwork/ticketmachine.dm b/code/modules/paperwork/ticketmachine.dm index 2234e2911a6..0492d3da694 100644 --- a/code/modules/paperwork/ticketmachine.dm +++ b/code/modules/paperwork/ticketmachine.dm @@ -7,6 +7,7 @@ icon_state = "ticketmachine" desc = "A marvel of bureaucratic engineering encased in an efficient plastic shell. It can be refilled with a hand labeler refill roll and linked to buttons with a multitool." density = FALSE + anchored = TRUE maptext_height = 26 maptext_width = 32 maptext_x = 7 diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 4aee2398b68..080114554bb 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -270,7 +270,6 @@ M.mind.transfer_to(new_mob) else new_mob.attack_log_old = M.attack_log_old.Copy() - new_mob.logs = M.logs.Copy() new_mob.key = M.key to_chat(new_mob, "Your form morphs into that of a [randomize].") diff --git a/code/modules/reagents/chemistry/reagents/toxins.dm b/code/modules/reagents/chemistry/reagents/toxins.dm index c6ff39aaead..b015b75be2a 100644 --- a/code/modules/reagents/chemistry/reagents/toxins.dm +++ b/code/modules/reagents/chemistry/reagents/toxins.dm @@ -1237,6 +1237,7 @@ description = "An advanced corruptive toxin produced by something terrible." reagent_state = LIQUID color = "#5EFF3B" //RGB: 94, 255, 59 + can_synth = FALSE taste_description = "decay" /datum/reagent/gluttonytoxin/reaction_mob(mob/living/L, method=REAGENT_TOUCH, reac_volume) diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index 7c35730f4aa..ac7249a8b63 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -198,7 +198,7 @@ being_used = 1 var/ghostmsg = "Play as [SM.name], pet of [user.name]?" - var/list/candidates = pollCandidates(ghostmsg, ROLE_SENTIENT, 0, 100) + var/list/candidates = SSghost_spawns.poll_candidates(ghostmsg, ROLE_SENTIENT, FALSE, 10 SECONDS, source = M) if(!src) return diff --git a/code/modules/response_team/ert.dm b/code/modules/response_team/ert.dm index a25be3814a7..f2d559e8b1f 100644 --- a/code/modules/response_team/ert.dm +++ b/code/modules/response_team/ert.dm @@ -63,7 +63,7 @@ GLOBAL_VAR_INIT(ert_request_answered, FALSE) GLOB.active_team.setSlots(commander_slots, security_slots, medical_slots, engineering_slots, janitor_slots, paranormal_slots, cyborg_slots) GLOB.send_emergency_team = TRUE - var/list/ert_candidates = shuffle(pollCandidates("Join the Emergency Response Team?",, GLOB.responseteam_age, 600, 1, GLOB.role_playtime_requirements[ROLE_ERT])) + var/list/ert_candidates = shuffle(SSghost_spawns.poll_candidates("Join the Emergency Response Team?",, GLOB.responseteam_age, 60 SECONDS, TRUE, GLOB.role_playtime_requirements[ROLE_ERT])) if(!ert_candidates.len) GLOB.active_team.cannot_send_team() GLOB.send_emergency_team = FALSE diff --git a/code/modules/surgery/organs/organ_icon.dm b/code/modules/surgery/organs/organ_icon.dm index 2a3b94ca249..b41ef824508 100644 --- a/code/modules/surgery/organs/organ_icon.dm +++ b/code/modules/surgery/organs/organ_icon.dm @@ -107,7 +107,10 @@ GLOBAL_LIST_EMPTY(limb_icon_cache) add_overlay(eyes_icon) if(owner.lip_style && (LIPS in dna.species.species_traits)) - add_overlay(mutable_appearance('icons/mob/human_face.dmi', "lips_[owner.lip_style]_s")) //Hefty icon not necessary. + var/icon/lips_icon = new('icons/mob/human_face.dmi', "lips_[owner.lip_style]_s") + lips_icon.Blend(owner.lip_color, ICON_MULTIPLY) + mob_icon.Blend(lips_icon, ICON_OVERLAY) + add_overlay(lips_icon) var/head_marking = owner.m_styles["head"] if(head_marking) diff --git a/goon/browserassets/css/browserOutput-dark.css b/goon/browserassets/css/browserOutput-dark.css index 8b162e5b336..8c177840a1b 100644 --- a/goon/browserassets/css/browserOutput-dark.css +++ b/goon/browserassets/css/browserOutput-dark.css @@ -250,6 +250,7 @@ em {font-style: normal; font-weight: bold;} {color: #638500; text-decoration: underline;} .motd a, .motd a:link, .motd a:visited, .motd a:active, .motd a:hover {color: #638500;} +.darkmblue {color: #6685f5;} .prefix { font-weight: bold;} .ooc { font-weight: bold;} .looc {color: #6699CC;} @@ -261,20 +262,20 @@ em {font-style: normal; font-weight: bold;} .mentorhelp {color: #0077bb; font-weight: bold;} .adminhelp {color: #aa0000; font-weight: bold;} .playerreply {color: #8800bb; font-weight: bold;} -.pmsend {color: #0000ff;} +.pmsend {color: #6685f5;} .name { font-weight: bold;} .say {} .yell { font-weight: bold;} .siliconsay {font-family: 'Courier New', Courier, monospace;} -.deadsay {color: #cc00c6;} +.deadsay {color: #B800B1;} .radio {color: #408010;} .deptradio {color: #993399;} -.comradio {color: #2040ff;} +.comradio {color: #526aff;} .syndradio {color: #993F40;} .dsquadradio {color: #998599;} .resteamradio {color: #18BC46;} -.airadio {color: #FF00FF;} -.centradio {color: #5C5C7C;} +.airadio {color: #FF94FF;} +.centradio {color: #78789B;} .secradio {color: #CF0000;} .engradio {color: #A66300;} .medradio {color: #009190;} @@ -286,7 +287,7 @@ em {font-style: normal; font-weight: bold;} .mentor_channel_admin {color: #A35CFF; font-weight: bold;} .djradio {color: #996600;} .binaryradio {color: #1B00FB; font-family: 'Courier New', Courier, monospace;} -.mommiradio {color: #1B00AB;} +.mommiradio {color: #6685f5;} .alert {color: #ff0000;} h1.alert, h2.alert {color: #FFF;} .ghostalert {color: #cc00c6; font-style: italic; font-weight: bold;} @@ -304,10 +305,10 @@ h1.alert, h2.alert {color: #FFF;} .userdanger {color: #ff0000; font-weight: bold; font-size: 120%;} .biggerdanger {color: #ff0000; font-weight: bold; font-size: 150%;} -.info {color: #0044DD;} -.notice {color: #0044DD;} -.bnotice {color: #0044DD; font-weight: bold;} -.boldnotice {color: #0044DD; font-weight: bold;} +.info {color: #6685f5;} +.notice {color: #6685f5;} +.bnotice {color: #6685f5; font-weight: bold;} +.boldnotice {color: #6685f5; font-weight: bold;} .suicide {color: #ff5050; font-style: italic;} .green {color: #03bb39;} .announce {color: #228b22; font-weight: bold;} @@ -358,7 +359,7 @@ h1.alert, h2.alert {color: #FFF;} .cultlarge {color: #A60000; font-weight: bold; font-size: 120%;} .narsie {color: #A60000; font-weight: bold; font-size: 300%;} .narsiesmall {color: #A60000; font-weight: bold; font-size: 200%;} -.interface {color: #330033;} +.interface {color: #9031C4;} .big {font-size: 150%;} .reallybig {font-size: 175%;} .greentext {color: #00FF00; font-size: 150%;} @@ -366,7 +367,7 @@ h1.alert, h2.alert {color: #FFF;} .bold {font-weight: bold;} .center {text-align: center;} .red {color: #FF0000;} -.purple {color: #5e2d79;} +.purple {color: #9031C4;} .skeleton {color: #C8C8C8; font-weight: bold; font-style: italic;} .gutter {color: #7092BE; font-family: "Trebuchet MS", cursive, sans-serif;} .orange {color: #ffa500;} @@ -374,14 +375,14 @@ h1.alert, h2.alert {color: #FFF;} .orangeb {color: #ffa500; font-weight: bold;} .resonate {color: #298F85;} -.revennotice {color: #1d29C3;} -.revenboldnotice {color: #1d29C3; font-weight: bold;} -.revenbignotice {color: #1d29C3; font-weight: bold; font-size: 120%;} +.revennotice {color: #6685F5;} +.revenboldnotice {color: #6685F5; font-weight: bold;} +.revenbignotice {color: #6685F5; font-weight: bold; font-size: 120%;} .revenminor {color: #823abb} .revenwarning {color: #760fbb; font-style: italic;} .revendanger {color: #760fbb; font-weight: bold; font-size: 120%;} -.specialnotice {color: #36525e; font-weight: bold; font-size: 120%;} +.specialnotice {color: #4A6F82; font-weight: bold; font-size: 120%;} /* /vg/ */ .good {color: green;} @@ -398,7 +399,7 @@ h1.alert, h2.alert {color: #FFF;} .connectionClosed, .fatalError {background: red; color: white; padding: 5px;} .connectionClosed.restored {background: green;} -.internal.boldnshit {color: blue; font-weight: bold;} +.internal.boldnshit {color: #6685f5; font-weight: bold;} /* HELPER CLASSES */ .text-normal {font-weight: normal; font-style: normal;} diff --git a/goon/browserassets/css/browserOutput.css b/goon/browserassets/css/browserOutput.css index 61307508c9b..0d7d987b0e7 100644 --- a/goon/browserassets/css/browserOutput.css +++ b/goon/browserassets/css/browserOutput.css @@ -247,6 +247,7 @@ em {font-style: normal; font-weight: bold;} {color: #638500; text-decoration: underline;} .motd a, .motd a:link, .motd a:visited, .motd a:active, .motd a:hover {color: #638500;} +.darkmblue {color: #0000ff;} .prefix { font-weight: bold;} .ooc { font-weight: bold;} .looc {color: #6699CC;} diff --git a/html/browser/marked-paradise.js b/html/browser/marked-paradise.js index 03519139409..a92cadb1cfe 100644 --- a/html/browser/marked-paradise.js +++ b/html/browser/marked-paradise.js @@ -4,24 +4,22 @@ var $ = document.querySelector.bind(document); function parse(node) { - for (var i = 0; i < node.childNodes.length; i++) { - parse(node.childNodes[i]); - } - - if (!node.innerHTML) { - return; - } - - node.innerHTML = marked(node.innerHTML.replace(/
    /gi, '\n').replace(/\t/gi, ''), { breaks: false, gfm: false }) - // marked.js wraps content into

    tags, which is looks atrocious when we call it recursively. - // The following line unwraps it. - if (node.children.length == 1) { - node.innerHTML = node.children[0].innerHTML; - } + for (var i = 0; i < node.childNodes.length; i++) + parse(node.childNodes[i]); + + if (!node.innerHTML) + return; + + if (node.children.length == 0) { + node.innerHTML = marked(node.innerHTML.replace(/
    /gi, '\n').replace(/\t/gi, ''), { breaks: false, gfm: false }); + // marked.js wraps content into

    tags, which is looks atrocious when we call it recursively. + // The following line unwraps it. + if (node.children.length == 1 && node.children[0].tagName == "P") + node.innerHTML = node.children[0].innerHTML; + } } - + window.onload = function() { - if ($('#markdown')) { - parse($('#markdown')); - } + if ($('#markdown')) + parse($('#markdown')); } diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi index 92006f54cc2..07593d09c0c 100644 Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ diff --git a/icons/mob/species/grey/helmet.dmi b/icons/mob/species/grey/helmet.dmi index cf244cec6b2..3e592369adc 100644 Binary files a/icons/mob/species/grey/helmet.dmi and b/icons/mob/species/grey/helmet.dmi differ diff --git a/icons/obj/clothing/uniforms.dmi b/icons/obj/clothing/uniforms.dmi index bb7a1ce9188..39789326c49 100644 Binary files a/icons/obj/clothing/uniforms.dmi and b/icons/obj/clothing/uniforms.dmi differ diff --git a/icons/obj/contraband.dmi b/icons/obj/contraband.dmi index 97a11d05cad..444863abd71 100644 Binary files a/icons/obj/contraband.dmi and b/icons/obj/contraband.dmi differ diff --git a/icons/obj/hydroponics/equipment.dmi b/icons/obj/hydroponics/equipment.dmi index 9ad33b40e9d..3cb9e63cb38 100644 Binary files a/icons/obj/hydroponics/equipment.dmi and b/icons/obj/hydroponics/equipment.dmi differ diff --git a/paradise.dme b/paradise.dme index 4c21ce6008d..215328b0b81 100644 --- a/paradise.dme +++ b/paradise.dme @@ -220,6 +220,7 @@ #include "code\controllers\subsystem\events.dm" #include "code\controllers\subsystem\fires.dm" #include "code\controllers\subsystem\garbage.dm" +#include "code\controllers\subsystem\ghost_spawns.dm" #include "code\controllers\subsystem\holiday.dm" #include "code\controllers\subsystem\icon_smooth.dm" #include "code\controllers\subsystem\idlenpcpool.dm" @@ -272,6 +273,7 @@ #include "code\datums\hud.dm" #include "code\datums\log_record.dm" #include "code\datums\log_viewer.dm" +#include "code\datums\logging.dm" #include "code\datums\mind.dm" #include "code\datums\mixed.dm" #include "code\datums\mutable_appearance.dm" @@ -938,6 +940,7 @@ #include "code\game\objects\items\tools\wrench.dm" #include "code\game\objects\items\weapons\AI_modules.dm" #include "code\game\objects\items\weapons\alien_specific.dm" +#include "code\game\objects\items\weapons\batons.dm" #include "code\game\objects\items\weapons\bee_briefcase.dm" #include "code\game\objects\items\weapons\cards_ids.dm" #include "code\game\objects\items\weapons\cash.dm" @@ -986,7 +989,6 @@ #include "code\game\objects\items\weapons\staff.dm" #include "code\game\objects\items\weapons\stock_parts.dm" #include "code\game\objects\items\weapons\stunbaton.dm" -#include "code\game\objects\items\weapons\swords_axes_etc.dm" #include "code\game\objects\items\weapons\tape.dm" #include "code\game\objects\items\weapons\teleportation.dm" #include "code\game\objects\items\weapons\teleprod.dm" @@ -1204,8 +1206,8 @@ #include "code\modules\admin\verbs\adminjump.dm" #include "code\modules\admin\verbs\adminpm.dm" #include "code\modules\admin\verbs\adminsay.dm" -#include "code\modules\admin\verbs\alt_check.dm" #include "code\modules\admin\verbs\antag-ooc.dm" +#include "code\modules\admin\verbs\asays.dm" #include "code\modules\admin\verbs\atmosdebug.dm" #include "code\modules\admin\verbs\BrokenInhands.dm" #include "code\modules\admin\verbs\cinematic.dm" @@ -1233,7 +1235,6 @@ #include "code\modules\admin\verbs\randomverbs.dm" #include "code\modules\admin\verbs\serialization.dm" #include "code\modules\admin\verbs\space_transitions.dm" -#include "code\modules\admin\verbs\spawnfloorcluwne.dm" #include "code\modules\admin\verbs\striketeam.dm" #include "code\modules\admin\verbs\striketeam_syndicate.dm" #include "code\modules\admin\verbs\ticklag.dm" diff --git a/strings/tips.txt b/strings/tips.txt index b2df1c17482..4218e01bbfe 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -1,4 +1,4 @@ -Where the space map levels connect is randomized every round, but are otherwise kept consistent within rounds. +Space map levels' connections are randomized between rounds, but are otherwise kept consistent in the same round. You can catch thrown items by toggling on your throw mode with an empty hand active. To crack the safe in the vault, use a stethoscope or thermal drill. You can climb onto a table by dragging yourself onto one. This takes some time. Clicking on a table that someone else is climbing onto will knock them down. @@ -9,8 +9,8 @@ You can change the control scheme by pressing tab. One uses WASD for movement, w Firesuits and winter coats offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all. Glass shards can be welded to make glass, and metal rods can be welded to make metal. Ores can be welded too, but this takes a lot of fuel. If you need to drag multiple people either to safety or to space, bring a locker over and stuff them all in before hauling them off. -You can grab someone by clicking on them with the grab intent, then upgrade the grab by clicking on them once more. An aggressive grab will momentarily stun someone, allow you to place them on a table by clicking on it, or throw them by toggling on throwing. -Holding alt and left clicking a tile will allow you to see its contents in the top right window pane, which is much faster than right clicking. +You can grab someone by clicking on them with the grab intent, then upgrade the grab by clicking on the grab itself. An aggressive grab will allow you to place them on a table by clicking on it, or throw them by toggling on throwing. +Holding alt and left clicking a tile will allow you to see its contents in the top right window panel, which is much faster than right clicking. The resist button will allow you to resist out of handcuffs, being buckled to a chair or bed, out of locked lockers and more. Whenever you're stuck, try resisting! You can move an item out of the way by dragging it and then clicking on an adjacent tile with an empty hand. You can recolor certain items like jumpsuits and gloves in washing machines by also throwing in a crayon. @@ -21,7 +21,7 @@ When in doubt about technicial issues, clear your cache (byond launcher > cogwhe Most things have special interactions with your middle mouse button, alt, shift, and control click. Experiment! If you find yourself in a fistfight with another player, running away and calling for help is a perfectly viable option. Different weapons have different strengths. Some weapons, such as spears, floor tiles, and throwing stars, deal more damage when thrown compared to when attacked normally. -A thrown glass of water can make a slippery tile, allowing you to slow down your pursuers in a pinch. +Clicking on a tile on harm intent with a glass of water can make a slippery tile, allowing you to slow down your pursuers in a pinch. When dealing with security, you can often get your sentence negated entirely through cooperation and deception. The P2P chat function found on tablet computers allows for a stealthy way to communicate with people. We were all new once, be patient and guide new players in the right direction. @@ -36,8 +36,8 @@ As the Captain, you have absolute access and control over the station, but this As the Chief Medical Officer, your hypospray is like a refillable instant injection syringe that can hold 30 units and the unlike standard hypospray, yours is able to be filled with harmful reagents and injects without telling anyone what have you exactly injected the person with. As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and geneticists during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting. As a Medical Doctor, you can surgically implant or extract things from people's chests. This can range from putting in a bomb to pulling out an alien larva. -As a Medical Doctor, you must target the correct limb and be on help intent when trying to perform surgery on someone. Using disarm intent will intentionally fail the surgery step. -As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and aren't revivable or clonable. +As a Medical Doctor, you must target the correct limb and be on help intent when trying to perform surgery on someone. +As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and aren't revivable or clonable right now, but they might be clonable later. As a Medical Doctor, treating plasmamen is not impossible! Salbutamol stops them from suffocating and showers stop them from burning alive. You can even perform surgery on them by doing the procedure on a roller bed under a shower. As a Chemist, there are dozens of chemicals that can heal, and even more that can cause harm. Experiment! As a Chemist, some chemicals can only be synthesized by heating up the contents with a chemical heater or manually with lighters and similar tools. @@ -50,7 +50,7 @@ As the Research Director, you can take AIs out of their cores by loading them in As the Research Director, you can lock down cyborgs instead of blowing them up. Then you can have their laws reset or if that doesn't work, safely dismantled. As the Research Director, you can spy on and even forge PDA communications with the message monitor console! The key is in your office. As a Scientist, you can maximize the number of uses you get out of a slime by feeding it slime steroid, created from purple slimes, while alive. You can then apply extract enhancer, created from cerulean slimes, on each extract. -As a Scientist, you can disable anomalies by scanning them with an analyzer, then send a signal on the frequency it gives you with a remote signaling device, or if researched, hit the anomaly with an anomaly analyzer. This will leave behind an anomaly core, which can be used to construct a Phazon mech or reactive armors! +As a Scientist, you can disable anomalies by scanning them with an analyzer, then send a signal on the frequency it gives you with a remote signaling device. This will leave behind an anomaly core, which can be used to construct a Phazon mech! As a Scientist, researchable stock parts can seriously improve the efficiency and speed of machines around the station. In some cases, it can even unlock new functions. As a Roboticist, keep an ear out for anomaly announcements. If you get your hands on an anomaly core, you can build a Phazon mech! As a Roboticist, you can repair your cyborgs with a welding tool. If they have taken burn damage, you can remove their battery, expose the wiring with a screwdriver and replace their wires with a cable coil. @@ -59,12 +59,12 @@ As a Roboticist, you can augment people with cyborg limbs. Augmented limbs can e As the AI, you can click on people's names to look at them. This only works if there are cameras that can see them, they aren't wearing agent IDs, or aren't using digital camouflage as changelings. As the AI, you can quickly open and close doors by holding shift while clicking them, bolt them when holding ctrl, and even shock them while holding alt. As the AI, you can take pictures with your camera and upload them to newscasters. -As a Cyborg, choose your module carefully, as only cutting and mending your reset wire or using a cyborg reset module will let you repick it. If possible, refrain from choosing a module until a situation that requires one occurs. +As a Cyborg, choose your module carefully, as only a cyborg reset module will let you repick it. As a Cyborg, you are immune to most forms of stunning, and excel at almost everything far better than humans. However, flashes can easily stunlock you and you cannot do any precision work as you lack hands. As a Cyborg, you are impervious to fires and heat. If you are rogue, you can release plasma fires everywhere and walk through them without a care in the world! As a Cyborg, you are extremely vulnerable to EMPs as EMPs both stun you and damage you. The ion rifle in the armory or a traitor with an EMP kit can kill you in seconds. -As an Engineering Cyborg, you can attach air alarm/fire alarm/APC frames to walls by placing them on the floor and using a screwdriver on them. -As a Medical Cyborg, you can fully perform surgery and even augment people. +As an Engineering Cyborg, you can attach air alarm/fire alarm/APC frames to walls by using your magnetic gripper. +As a Medical Cyborg, you can partially perform surgery, as you cannot replace organs, but you cannot fail any surgery steps. As a Janitor Cyborg, you are the bane of all slaughter demons. Cleaning up blood stains will severely gimp them. As the Chief Engineer, you can rename areas or create entirely new ones using your station blueprints. As the Chief Engineer, your hardsuit is significantly better than everybody else's. It has the best features of both engineering and atmospherics hardsuits, boasting nigh-invulnerability to radiation and all atmospheric conditions. @@ -73,10 +73,10 @@ As an Engineer, you can electrify grilles by placing wire "nodes" beneath them: As an Engineer, return to Engineering once in a while to check on the engine and SMES cells. It's always a good idea to make sure containment isn't compromised. As an Engineer, you can power the station solely with the solar arrays. They will provide just enough electricity to power the station, however their output is still much worse compared to the true engine. As an Engineer, you can cool a supermatter shard by spraying it with a fire extinguisher. Only for the brave! -As an Engineer, you can repair windows by using a welding tool on them while on any intent other than harm. +As an Engineer, you can repair windows by using a welding tool on them while on help intent. As an Engineer, you can lock APCs, fire alarms, emitters, and radiation collectors using your ID card to prevent others from disabling them. As an Engineer, don't underestimate the humble P.A.C.M.A.N. generators. With upgraded parts, a couple units working in tandem are sufficient to take over for an exploded engine or shattered solars. -As an Engineer, you can pry open secure storage blast doors by disabling the engine room APC's main breaker. This is obviously a bad idea if the engine is running. +As an Engineer, you can pry open secure storage blast doors by turning off the secure storage APC's enviroment power channel. As an Atmospheric Technician, look into replacing your gas pumps with volumetric gas pumps, as those move air in flat numerical amounts, rather than percentages which leave trace gases. As an Atmospheric Technician, you are better suited to fighting fires than anyone else. As such, you have access to better firesuits, backpack firefighter tanks, and a completely heat and fire proof hardsuit. As an Atmospheric Technician, your backpack firefighter tank can launch cryofrost. This resin will extinguish fires and very quickly; it's ideal for plasma fires! @@ -88,7 +88,7 @@ As the Warden, keep a close eye on the armory at all times, as it is a favored s As the Warden, if a prisoner's crimes are heinous enough you can put them in permabrig or the gulag. Make sure to check on them once in a while! As the Warden, you can implant criminals you suspect might re-offend with devices that will track their location and allow you to remotely inject them with disabling chemicals. As a Security Officer, communicate and coordinate with your fellow officers using the security channel (:s) to avoid confusion. -As a Security Officer, your sechuds or HUDsunglasses can not only see crewmates' job assignments and criminal status, but also if they are mindshield implanted. You can tell by the flashing blue outline around their job icon. +As a Security Officer, your sechuds or HUDsunglasses let you see crewmates' job assignments, their criminal status, and whether they have a mindshield or not. A flashing green border around their job icon means they are mindshielded. As a Security Officer, mindshield implants can only prevent someone from being turned into a cultist. It will not de-cult them if they have already been converted. As a Security Officer, examining someone while wearing sechuds or HUDsunglasses will let you set their arrest level, which will cause Beepsky and other security bots to chase after them. As the Detective, keep in mind that people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department. @@ -111,7 +111,7 @@ As the Chaplain, you are much more likely to get a response by praying to the go As a Botanist, you can hack the MegaSeed Vendor to get access to more exotic seeds. These seeds can alternatively be ordered from cargo. As a Botanist, you can mutate the plants growing in your hydroponics trays with unstable mutagen or, as an alternative, crude radioactives from chemistry to get special variations. As a Botanist, you should look into increasing the potency of your plants. This increases the size, amount of chemicals, points gained from grinding them in the biogenerator, and lets people know you are a proficient botanist. -As a Botanist, you can combine production trait chemicals just like a Chemist. Chlorine (blumpkin) + radium and phosphorus (glowshrooms) equals unstable mutagen! +As a Botanist, you can combine production trait chemicals just like a Chemist. Chlorine and plasma (blumpkin) + radium (glowshrooms) equals unstable mutagen! As the Chef, you can create a very wide variety of food with the crafting menu. You can find it by looking for the hammer icon near your intents. As the Chef, you can rename your custom made food with a pen. As the Chef, if you are low on ingredients, consider making something that has several servings to last longer among the crew. @@ -121,8 +121,8 @@ As a Janitor, mousetraps can be used to create bombs or booby-trap containers. As the Librarian, be sure to keep the shelves stocked and the library clean for crew. As a Cargo Technician, you can hack MULEbots to make them faster, run over people in their way, and even let you ride them! As a Cargo Technician, you can order contraband items from the supply shuttle console by de-constructing it and using a multitool on the circuit board, the re-assembling it. -As a Cargo Technician, you can earn more cargo points by shipping back crates from maintenance, liquid containers, plasma sheets, rare seeds from hydroponics, and more! -As a Shaft Miner, the western side of the Asteroid has a lot more rare minerals than on the east, but is a lot more dangerous. +As a Cargo Technician, you can earn more cargo points by shipping back crates from maintenance, plasma sheets, rare seeds from hydroponics, technology disks from R&D, and more! +As a Shaft Miner, the north side of Lavaland has a lot more rare minerals than on the south, but is a lot more dangerous. As a Shaft Miner, always have a GPS on you, so a fellow miner or cyborg can come to save you if you die. As a Traitor, the cryptographic sequencer (emag) can not only open doors, but also lockers, crates, APCs and more. It can hack cyborgs, and even cause bots to go berserk. Use it on the right machines, and you can even order more traitor gear or contact the Syndicate. Experiment! As a Traitor, subverting the AI to serve you can make it an extremely powerful ally. However, be careful of the wording in the laws you give it, as it may use your poorly written laws against you! @@ -130,14 +130,14 @@ As a Traitor, the Captain and the Head of Security are two of the most difficult As a Traitor, you can manufacture and recycle revolver bullets at a hacked autolathe, making the revolver an extremely powerful tool. As a Traitor, you may sometimes be assigned to hunt other traitors, and in turn be hunted by others. As a Traitor, the syndicate encryption key is very useful for coordinating plans with your fellow traitors -- or, of course, betraying them. -As a Traitor, plasma can be injected into many things to sabotage them. Power cells, light bulbs, welding tools, cigars and e-cigs will all explode when used. +As a Traitor, plasma can be injected into many things to sabotage them. Power cells, light bulbs, cigars and e-cigs will all explode when used. As a Traitor, if you can find another Traitor and pool your TC you can buy a mega surplus crate, which costs 40TC but contains a lot of random syndicate gear. As a Nuclear Operative, communication is key! Use ; to speak to your fellow operatives and coordinate an attack plan. As a Nuclear Operative, you should look into purchasing a syndicate cyborg, as they can provide heavy fire support, full access, are immune to conventional stuns, and can easily take down the AI. As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire. As a Nuclear Operative, you might end up in a situation where the AI has bolted you into a room. Having some spare C4 in your pocket can save your life. As a Monkey, you can still wear a few human items, such as backpacks, gas masks, and hats, and still have two free hands. -As the Malfunctioning AI, you can shunt to an APC if the situation gets bad. This disables your doomsday device if it is active. +As the Malfunctioning AI, you can shunt to an APC if the situation gets bad. As the Malfunctioning AI, you should either order your cyborgs to dismantle the robotics console or blow it up yourself in order to protect them. As an Alien, your melee prowess is unmatched, but your ranged abilities are sorely lacking. Make use of corners to force a melee confrontation! As an Alien, you take double damage from all burn attacks, such as lasers, welding tools, and fires. Furthermore, fire can destroy your resin and eggs. Expose areas to space to starve away any flamethrower fires before they can do damage! @@ -155,11 +155,10 @@ As a Changeling, the Extract DNA sting counts for your genome absorb objective, As a Changeling, you can absorb someone by strangling them and using the Absorb verb; this gives you the ability to rechoose your powers, the DNA of whoever you absorbed, the memory of the absorbed, and some samples of things the absorbed said. As a Cultist, do not cause too much chaos before your objective is completed. If the shuttle gets called too soon, you may not have enough time to win. As a Cultist, your team starts off very weak, but if necessary can quickly convert everything they have into raw power. Make sure you have the numbers and equipment to support going loud, or the cult will fall flat on its face. -As a Cultist, the Blood Boil rune will deal massive amounts of brute damage to non-cultists, and some damage to fellow cultists of Nar'Sie nearby, but will create a fire where the rune stands on use. +As a Cultist, the Blood Boil rune will deal massive amounts of brute damage to non-cultists, and some damage to fellow cultists nearby, but will create a fire where the rune stands on use. As a Cultist, you can create an army of manifested goons using a combination of the Manifest rune, which creates homunculi from ghosts, and the Blood Drain rune, which drains life from anyone standing on any blood drain rune. -As a Cultist, check the alert in the upper-right of your screen for all the details about your cult's current status and objective. You can deconvert Cultists by feeding them large amounts of holy water. -The Chaplain can bless any container with water by hitting it with their bible. Holy water has a myriad of uses against both cults and large amounts of it are a great contributor to success against them. +The Chaplain can bless any container with water by hitting it with their bible. Holy water has a myriad of uses against cults and large amounts of it are a great contributor to success against them. As a Wizard, you can turn people to stone, then animate the resulting statue with a staff of animation to create an extremely powerful minion, for all of 5 minutes at least. As a Wizard, the fireball spell performs very poorly at close range, as it can easily catch you in the blast. It is best used as a form of artillery down long hallways. As a Wizard, summoning guns will give everyone anything from a floral somatoray to a pulse rifle. Use at your own risk! @@ -170,11 +169,10 @@ As an Abductor Agent, the combat mode vest has much higher resistance to every k As an Abductor, the baton can cycle between four modes: stun, sleep, cuff and probe. As a Revenant, the Chaplain is your worst enemy, as they can damage you massively with the null rod and make large swaths of the station impassable with holy water. As a Revenant, your essence is also your health, so revealing yourself in front of humans to harvest the essence of the living is much safer if you've already stocked up on essences from poorly guarded corpses. -As a Revenant, your Defile ability removes holy water from tiles in a small radius, along with salt, allowing you to reclaim the station from the chaplain if they've been covering the station in holy water. It can also be used to open morgue trays! +As a Revenant, your Defile ability removes holy water from tiles in a small radius, allowing you to reclaim the station from the chaplain if they've been covering the station in holy water. As a Revenant, your Overload Lights ability will only shock humans with lights if the lights are still on after a brief delay. As a Revenant, your Malfunction ability in general damages machinery and mechanical objects, possibly even emagging some objects. Experiment! -As a Revenant, the illness inflicted on humans by Blight can be easily cured by lying down or with holy water, making it best used on targets that have no time to lie down, such as humans in combat. -As a Revenant, fastmos is your friend. Breaking windows to space can cause massive damage and mayhem. +As a Revenant, space is your friend. Breaking windows to space can cause massive damage and mayhem. As a Swarmer, you can deconstruct more things than you think. Try deconstructing light switches, buttons, air alarms and more. Experiment! As a Swarmer, you can teleport fellow swarmers away if you think they are in danger. As a Ghost, you can double click on just about anything to follow it. Or just warp around! @@ -202,4 +200,4 @@ You can make lasertag turrets, for the ultimate lasertag tournament. Blob structures take half damage from brute damage. Use lasers. You can hide paper in vents, but you have to use a screwdriver to open it first. While the Standard Operating Procedures aren't fully rules, they are there for safety and professional reasons. -Killing the Wizard usually ends the round, unless they are a lich or Space Wizard Federation is RAGING. \ No newline at end of file +Killing the Wizard usually ends the round, unless they are a lich or Space Wizard Federation is RAGING.