diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e10a9db90c833..915dda724d53c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -75,12 +75,13 @@ # ninjanomnom -/code/__DEFINES/dcs/ @ninjanomnom /code/controllers/subsystem/dcs.dm @ninjanomnom -/code/controllers/subsystem/shuttle.dm @ninjanomnom -/code/datums/components/ @ninjanomnom -/code/datums/elements/ @ninjanomnom -/code/modules/shuttle/ @ninjanomnom +/code/datums/signals.dm @ninjanomnom +/code/datums/components/_component.dm @ninjanomnom +/code/datums/elements/_element.dm @ninjanomnom +/code/datums/greyscale/_greyscale_config.dm @ninjanomnom +/code/datums/greyscale/json_reader.dm @ninjanomnom +/code/datums/greyscale/layer.dm @ninjanomnom # Ryll-Ryll/Shaps @@ -124,8 +125,8 @@ # tralezab /code/__DEFINES/basic_mobs.dm @tralezab -/code/datums/ai @tralezab -/code/modules/mob/living/basic/ @tralezab +/code/datums/ai/ @tralezab +/code/modules/religion/ @tralezab # Watermelon914 @@ -141,6 +142,7 @@ /code/datums/armor/ @ZephyrTFA /code/modules/admin/verbs/ @ZephyrTFA /code/modules/logging/ @ZephyrTFA +/tools/ci/check_grep.sh @ZephyrTFA # CONTRIBUTORS @@ -185,19 +187,29 @@ /SQL/ @Jordie0608 @MrStonedOne /_maps/ @EOBGames @Maurukas @MMMiracles @san7890 @ShizCalev + /icons/ @Imaginos16 @Krysonism @Twaticus @Wallemations /icons/ass/ @Ghilker @tralezab /code/__DEFINES/atmospherics/ @Ghilker @LemonInTheDark + /code/__HELPERS/logging/ @dragomagol @ZephyrTFA + /code/controllers/subsystem/air.dm @LemonInTheDark @MrStonedOne + /code/modules/atmospherics/ @Ghilker @LemonInTheDark + /code/modules/client/preferences.dm @Mothblocks @ZephyrTFA /code/modules/client/preferences_savefile.dm @Mothblocks @ZephyrTFA + /code/modules/jobs/job_types/chief_medical_officer.dm @ExcessiveUseOfCobblestone @Ryll-Ryll /code/modules/jobs/job_types/medical_doctor.dm @ExcessiveUseOfCobblestone @Ryll-Ryll /code/modules/jobs/job_types/paramedic.dm @ExcessiveUseOfCobblestone @Ryll-Ryll + +/code/modules/mob/living/basic/ @Jacquerel @san7890 @tralezab + /code/modules/surgery/ @ExcessiveUseOfCobblestone @Ryll-Ryll + /tools/build/ @MrStonedOne @stylemistake /tools/tgs_scripts/ @Cyberboss @MrStonedOne diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4704e8c20ed33..904de8b36c9a9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,6 +3,11 @@ name: Bug report about: Create a report to help reproduce and fix the issue --- + +## Issue Summary + + + ## Round ID: [span_name("[signal.format_target()]")]: \"[signal.format_message()]\"") + var/ghost_message = span_game_say("[span_name("[source]")] [rigged ? "(as [span_name(fake_name)]) Rigged " : ""]PDA Message --> [span_name("[signal.format_target()]")]: \"[signal.format_message()]\"") var/list/message_listeners = GLOB.dead_player_list + GLOB.current_observers_list for(var/mob/listener as anything in message_listeners) if(!(get_chat_toggles(listener) & CHAT_GHOSTPDA)) continue - to_chat(listener, "[FOLLOW_LINK(listener, sender)] [ghost_message]") + to_chat(listener, "[FOLLOW_LINK(listener, source)] [ghost_message]") - to_chat(sender, span_info("PDA message sent to [signal.format_target()]: \"[message]\"")) + if(sender) + to_chat(sender, span_info("PDA message sent to [signal.format_target()]: \"[message]\"")) if (alert_able && !alert_silenced) computer.send_sound() COOLDOWN_START(src, last_text, 1 SECONDS) + SEND_SIGNAL(computer, COMSIG_MODULAR_PDA_MESSAGE_SENT, source, signal) + selected_image = null return TRUE @@ -637,6 +662,7 @@ var/sender_ref = signal.data["ref"] + // don't create a new chat for rigged messages, make it a one off notif if(!is_rigged) var/datum/pda_message/message = new(signal.data["message"], FALSE, station_time_timestamp(PDA_MESSAGE_TIMESTAMP_FORMAT), signal.data["photo"], signal.data["everyone"]) @@ -657,6 +683,14 @@ if(computer.loc && isliving(computer.loc)) receievers += computer.loc + // resolving w/o nullcheck here, assume the messenger exists if a real person sent a message + var/datum/computer_file/program/messenger/sender_messenger = chat.recipient?.resolve() + + var/sender_title = is_fake_user ? STRINGIFY_PDA_TARGET(fake_name, fake_job) : get_messenger_name(sender_messenger) + var/sender_name = is_fake_user ? fake_name : sender_messenger.computer.saved_identification + + SEND_SIGNAL(computer, COMSIG_MODULAR_PDA_MESSAGE_RECEIVED, signal, fake_job || sender_messenger?.computer.saved_job , sender_name) + for(var/mob/living/messaged_mob as anything in receievers) if(messaged_mob.stat >= UNCONSCIOUS) continue @@ -670,11 +704,6 @@ else reply = "(Reply)" - // resolving w/o nullcheck here, assume the messenger exists if a real person sent a message - var/datum/computer_file/program/messenger/sender_messenger = chat.recipient?.resolve() - - var/sender_title = is_fake_user ? STRINGIFY_PDA_TARGET(fake_name, fake_job) : get_messenger_name(sender_messenger) - var/sender_name = is_fake_user ? fake_name : sender_messenger.computer.saved_identification if (isAI(messaged_mob)) sender_title = "[sender_title]" diff --git a/code/modules/modular_computers/file_system/programs/newscasterapp.dm b/code/modules/modular_computers/file_system/programs/newscasterapp.dm index 47e4f65d48f01..ed1c440f411cd 100644 --- a/code/modules/modular_computers/file_system/programs/newscasterapp.dm +++ b/code/modules/modular_computers/file_system/programs/newscasterapp.dm @@ -1,13 +1,12 @@ /datum/computer_file/program/newscaster filename = "newscasterapp" filedesc = "Newscaster" - transfer_access = list(ACCESS_LIBRARY) - category = PROGRAM_CATEGORY_CREW - program_icon_state = "bountyboard" + download_access = list(ACCESS_LIBRARY) + downloader_category = PROGRAM_CATEGORY_GAMES + program_open_overlay = "bountyboard" extended_desc = "This program allows any user to access the Newscaster network from anywhere." size = 2 - requires_ntnet = TRUE - available_on_ntnet = TRUE + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET tgui_id = "NtosNewscaster" program_icon = "newspaper" ///The UI we use for the newscaster @@ -28,4 +27,5 @@ return newscaster_ui.ui_static_data(user) /datum/computer_file/program/newscaster/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + . = ..() return newscaster_ui.ui_act(action, params, ui, state) diff --git a/code/modules/modular_computers/file_system/programs/notepad.dm b/code/modules/modular_computers/file_system/programs/notepad.dm index 01afaa08c19e0..95def6f8e9643 100644 --- a/code/modules/modular_computers/file_system/programs/notepad.dm +++ b/code/modules/modular_computers/file_system/programs/notepad.dm @@ -1,13 +1,14 @@ /datum/computer_file/program/notepad filename = "notepad" filedesc = "Notepad" - category = PROGRAM_CATEGORY_MISC - program_icon_state = "generic" + downloader_category = PROGRAM_CATEGORY_DEVICE + program_open_overlay = "generic" extended_desc = "Jot down your work-safe thoughts and what not." size = 2 tgui_id = "NtosNotepad" program_icon = "book" - usage_flags = PROGRAM_TABLET + can_run_on_flags = PROGRAM_ALL + circuit_comp_type = /obj/item/circuit_component/mod_program/notepad var/written_note = "Congratulations on your station upgrading to the new NtOS and Thinktronic based collaboration effort, \ bringing you the best in electronics and software since 2467!\n\ @@ -19,7 +20,8 @@ Quarter - Either sides of Aft\n\ Bow - Either sides of Fore" -/datum/computer_file/program/notepad/ui_act(action, list/params, datum/tgui/ui) +/datum/computer_file/program/notepad/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() switch(action) if("UpdateNote") written_note = params["newnote"] @@ -31,3 +33,39 @@ data["note"] = written_note return data + +/obj/item/circuit_component/mod_program/notepad + associated_program = /datum/computer_file/program/notepad + ///When the input is received, the written note will be set to its value. + var/datum/port/input/set_text + ///The written note output, sent everytime notes are updated. + var/datum/port/output/updated_text + ///Pinged whenever the text is updated + var/datum/port/output/updated + +/obj/item/circuit_component/mod_program/notepad/populate_ports() + . = ..() + set_text = add_input_port("Set Notes", PORT_TYPE_STRING) + updated_text = add_output_port("Notes", PORT_TYPE_STRING) + updated = add_output_port("Updated", PORT_TYPE_SIGNAL) + +/obj/item/circuit_component/mod_program/notepad/register_shell(atom/movable/shell) + . = ..() + RegisterSignal(associated_program, COMSIG_UI_ACT, PROC_REF(on_note_updated)) + +/obj/item/circuit_component/mod_program/notepad/unregister_shell() + UnregisterSignal(associated_program, COMSIG_UI_ACT) + return ..() + +/obj/item/circuit_component/mod_program/notepad/proc/on_note_updated(datum/source, mob/user, action, list/params) + SIGNAL_HANDLER + if(action == "UpdateNote") + updated_text.set_output(params["newnote"]) + updated.set_output(COMPONENT_SIGNAL) + +/obj/item/circuit_component/mod_program/notepad/input_received(datum/port/port) + var/datum/computer_file/program/notepad/pad = associated_program + pad.written_note = set_text.value + SStgui.update_uis(pad.computer) + updated_text.set_output(pad.written_note) + updated.set_output(COMPONENT_SIGNAL) diff --git a/code/modules/modular_computers/file_system/programs/nt_pay.dm b/code/modules/modular_computers/file_system/programs/nt_pay.dm index 8724375d07bba..4e3fa5d3fb718 100644 --- a/code/modules/modular_computers/file_system/programs/nt_pay.dm +++ b/code/modules/modular_computers/file_system/programs/nt_pay.dm @@ -1,52 +1,33 @@ +#define NT_PAY_STATUS_NO_ACCOUNT 0 +#define NT_PAY_STATUS_DEPT_ACCOUNT 1 +#define NT_PAY_STATUS_INVALID_TOKEN 2 +#define NT_PAY_SATUS_SENDER_IS_RECEIVER 3 +#define NT_PAY_STATUS_INVALID_MONEY 4 +#define NT_PAY_STATUS_SUCCESS 5 + /datum/computer_file/program/nt_pay filename = "ntpay" filedesc = "Nanotrasen Pay System" - category = PROGRAM_CATEGORY_MISC - program_icon_state = "generic" + downloader_category = PROGRAM_CATEGORY_DEVICE + program_open_overlay = "generic" extended_desc = "An application that locally (in your sector) helps to transfer money or track your expenses and profits." size = 2 tgui_id = "NtosPay" program_icon = "money-bill-wave" - usage_flags = PROGRAM_ALL + can_run_on_flags = PROGRAM_ALL + circuit_comp_type = /obj/item/circuit_component/mod_program/nt_pay ///Reference to the currently logged in user. var/datum/bank_account/current_user - ///Pay token, by which we can send credits - var/token - ///Amount of credits, which we sends - var/money_to_send = 0 ///Pay token what we want to find var/wanted_token -/datum/computer_file/program/nt_pay/ui_act(action, list/params, datum/tgui/ui) +/datum/computer_file/program/nt_pay/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() switch(action) if("Transaction") - if(IS_DEPARTMENTAL_ACCOUNT(current_user)) - return to_chat(usr, span_notice("The app is unable to withdraw from that card.")) - - token = params["token"] - money_to_send = params["amount"] - var/datum/bank_account/recipient - if(!token) - return to_chat(usr, span_notice("You need to enter your transfer target's pay token.")) - if(!money_to_send) - return to_chat(usr, span_notice("You need to specify how much you're sending.")) - if(token == current_user.pay_token) - return to_chat(usr, span_notice("You can't send credits to yourself.")) - - for(var/account as anything in SSeconomy.bank_accounts_by_id) - var/datum/bank_account/acc = SSeconomy.bank_accounts_by_id[account] - if(acc.pay_token == token) - recipient = acc - break - - if(!recipient) - return to_chat(usr, span_notice("The app can't find who you're trying to pay. Did you enter the pay token right?")) - if(!current_user.has_money(money_to_send) || money_to_send < 1) - return current_user.bank_card_talk("You cannot afford it.") - - recipient.bank_card_talk("You received [money_to_send] credit(s). Reason: transfer from [current_user.account_holder]") - recipient.transfer_money(current_user, money_to_send) - current_user.bank_card_talk("You send [money_to_send] credit(s) to [recipient.account_holder]. Now you have [current_user.account_balance] credit(s)") + var/token = params["token"] + var/money_to_send = params["amount"] + make_payment(token, money_to_send, usr) if("GetPayToken") wanted_token = null @@ -58,8 +39,6 @@ if(!wanted_token) return wanted_token = "Account \"[params["wanted_name"]]\" not found." - - /datum/computer_file/program/nt_pay/ui_data(mob/user) var/list/data = list() @@ -74,3 +53,136 @@ data["transaction_list"] = current_user.transaction_history return data + +///Wrapper and signal for the main payment function of this program +/datum/computer_file/program/nt_pay/proc/make_payment(token, money_to_send, mob/user) + var/payment_result = _pay(token, money_to_send, user) + SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_NT_PAY_RESULT, payment_result) + +/datum/computer_file/program/nt_pay/proc/_pay(token, money_to_send, mob/user) + money_to_send = round(money_to_send) + + if(IS_DEPARTMENTAL_ACCOUNT(current_user)) + if(user) + to_chat(user, span_notice("The app is unable to withdraw from that card.")) + return NT_PAY_STATUS_DEPT_ACCOUNT + + var/datum/bank_account/recipient + if(!token) + if(user) + to_chat(user, span_notice("You need to enter your transfer target's pay token.")) + return NT_PAY_STATUS_INVALID_TOKEN + if(money_to_send <= 0) + if(user) + to_chat(user, span_notice("You need to specify how much you're sending.")) + return NT_PAY_STATUS_INVALID_MONEY + if(token == current_user.pay_token) + if(user) + to_chat(user, span_notice("You can't send credits to yourself.")) + return NT_PAY_SATUS_SENDER_IS_RECEIVER + + for(var/account as anything in SSeconomy.bank_accounts_by_id) + var/datum/bank_account/acc = SSeconomy.bank_accounts_by_id[account] + if(acc.pay_token == token) + recipient = acc + break + + if(!recipient) + if(user) + to_chat(user, span_notice("The app can't find who you're trying to pay. Did you enter the pay token right?")) + return NT_PAY_STATUS_INVALID_TOKEN + if(!current_user.has_money(money_to_send) || money_to_send < 1) + current_user.bank_card_talk("You cannot afford it.") + return NT_PAY_STATUS_INVALID_MONEY + + recipient.bank_card_talk("You received [money_to_send] credit(s). Reason: transfer from [current_user.account_holder]") + recipient.transfer_money(current_user, money_to_send) + for(var/obj/item/card/id/id_card as anything in recipient.bank_cards) + SEND_SIGNAL(id_card, COMSIG_ID_CARD_NTPAY_MONEY_RECEIVED, computer, money_to_send) + + current_user.bank_card_talk("You send [money_to_send] credit(s) to [recipient.account_holder]. Now you have [current_user.account_balance] credit(s)") + + return NT_PAY_STATUS_SUCCESS + + +/obj/item/circuit_component/mod_program/nt_pay + associated_program = /datum/computer_file/program/nt_pay + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL + + ///Circuit variables. This one is for the token we want to pay + var/datum/port/input/token_port + ///The port for the money to send + var/datum/port/input/money_port + ///Let's us know if the payment has gone through or not. + var/datum/port/output/payment_status + ///The device from which the payment was received + var/datum/port/output/payment_device + ///Amount of a received payment + var/datum/port/output/payment_amount + ///Pinged whether a payment is received + var/datum/port/output/payment_received + +/obj/item/circuit_component/mod_program/nt_pay/register_shell(atom/movable/shell) + . = ..() + var/obj/item/modular_computer/modpc = associated_program.computer + RegisterSignal(modpc, COMSIG_MODULAR_COMPUTER_NT_PAY_RESULT, PROC_REF(on_payment_done)) + RegisterSignal(modpc, COMSIG_MODULAR_COMPUTER_INSERTED_ID, PROC_REF(register_id)) + if(modpc.computer_id_slot) + register_id(inserted_id = modpc.computer_id_slot) + +/obj/item/circuit_component/mod_program/nt_pay/unregister_shell() + var/obj/item/modular_computer/modpc = associated_program.computer + UnregisterSignal(modpc, list(COMSIG_MODULAR_COMPUTER_NT_PAY_RESULT, COMSIG_MODULAR_COMPUTER_INSERTED_ID)) + if(modpc.computer_id_slot) + UnregisterSignal(modpc.computer_id_slot, list(COMSIG_ID_CARD_NTPAY_MONEY_RECEIVED, COMSIG_MOVABLE_MOVED)) + return ..() + +/obj/item/circuit_component/mod_program/nt_pay/proc/register_id(datum/source, obj/item/card/inserted_id, mob/user) + SIGNAL_HANDLER + RegisterSignal(inserted_id, COMSIG_ID_CARD_NTPAY_MONEY_RECEIVED, PROC_REF(on_payment_received)) + RegisterSignal(inserted_id, COMSIG_MOVABLE_MOVED, PROC_REF(unregister_id)) + +/obj/item/circuit_component/mod_program/nt_pay/proc/unregister_id(obj/item/card/gone) + SIGNAL_HANDLER + UnregisterSignal(gone, list(COMSIG_ID_CARD_NTPAY_MONEY_RECEIVED, COMSIG_MOVABLE_MOVED)) + +/obj/item/circuit_component/mod_program/nt_pay/populate_ports() + . = ..() + token_port = add_input_port("Token", PORT_TYPE_STRING) + money_port = add_input_port("Amount", PORT_TYPE_NUMBER) + payment_status = add_output_port("Status", PORT_TYPE_NUMBER) + payment_device = add_output_port("Payment Sender", PORT_TYPE_ATOM) + payment_amount = add_output_port("Received Amount", PORT_TYPE_NUMBER) + payment_received = add_output_port("Received Payment", PORT_TYPE_SIGNAL) + +/obj/item/circuit_component/mod_program/nt_pay/get_ui_notices() + . = ..() + . += create_ui_notice("Outputs require inserted ID", "orange") + . += create_ui_notice("NT-Pay Statuses:") + . += create_ui_notice("Success - [NT_PAY_STATUS_SUCCESS]", "green") + . += create_ui_notice("Fail (No Account) - [NT_PAY_STATUS_NO_ACCOUNT]", "red") + . += create_ui_notice("Fail (Dept Account) - [NT_PAY_STATUS_DEPT_ACCOUNT]", "red") + . += create_ui_notice("Fail (Invalid Token) - [NT_PAY_STATUS_INVALID_TOKEN]", "red") + . += create_ui_notice("Fail (Sender = Receiver) - [NT_PAY_SATUS_SENDER_IS_RECEIVER]", "red") + . += create_ui_notice("Fail (Invalid Amount) - [NT_PAY_STATUS_INVALID_MONEY]", "red") + +/obj/item/circuit_component/mod_program/nt_pay/input_received(datum/port/port) + var/datum/computer_file/program/nt_pay/program = associated_program + program.make_payment(token_port.value, money_port.value) + +/obj/item/circuit_component/mod_program/nt_pay/proc/on_payment_done(datum/source, payment_result) + SIGNAL_HANDLER + payment_status.set_output(payment_result) + +/obj/item/circuit_component/mod_program/nt_pay/proc/on_payment_received(datum/source, obj/item/modular_computer/computer, money_received) + SIGNAL_HANDLER + payment_device.set_output(computer) + payment_amount.set_output(money_received) + payment_received.set_output(COMPONENT_SIGNAL) + +#undef NT_PAY_STATUS_NO_ACCOUNT +#undef NT_PAY_STATUS_DEPT_ACCOUNT +#undef NT_PAY_STATUS_INVALID_TOKEN +#undef NT_PAY_SATUS_SENDER_IS_RECEIVER +#undef NT_PAY_STATUS_INVALID_MONEY +#undef NT_PAY_STATUS_SUCCESS diff --git a/code/modules/modular_computers/file_system/programs/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/ntdownloader.dm index efa61b2630cd5..3fbc7843f1883 100644 --- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm +++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm @@ -1,36 +1,39 @@ /datum/computer_file/program/ntnetdownload filename = "ntsoftwarehub" filedesc = "NT Software Hub" - program_icon_state = "generic" + program_open_overlay = "generic" extended_desc = "This program allows downloads of software from official NT repositories" undeletable = TRUE size = 4 - requires_ntnet = TRUE - available_on_ntnet = FALSE - ui_header = "downloader_finished.gif" + program_flags = PROGRAM_REQUIRES_NTNET tgui_id = "NtosNetDownloader" program_icon = "download" - var/datum/computer_file/program/downloaded_file = null + ///The program currently being downloaded. + var/datum/computer_file/program/downloaded_file + ///Boolean on whether the `downloaded_file` is being downloaded from the Syndicate store, + ///in which case it will appear as 'ENCRYPTED' in logs, rather than display file name. var/hacked_download = FALSE - var/download_completion = FALSE //GQ of downloaded data. - var/download_netspeed = 0 - var/downloaderror = "" - var/list/main_repo - var/list/antag_repo - - var/list/show_categories = list( - PROGRAM_CATEGORY_CREW, - PROGRAM_CATEGORY_ENGI, - PROGRAM_CATEGORY_SCI, - PROGRAM_CATEGORY_SUPL, - PROGRAM_CATEGORY_MISC, + ///How much of the data has been downloaded. + var/download_completion + ///The error message being displayed to the user, if necessary. Null if there isn't one. + var/downloaderror + + ///The list of categories to display in the UI, in order of which they appear. + var/static/list/show_categories = list( + PROGRAM_CATEGORY_DEVICE, + PROGRAM_CATEGORY_EQUIPMENT, + PROGRAM_CATEGORY_GAMES, + PROGRAM_CATEGORY_SECURITY, + PROGRAM_CATEGORY_ENGINEERING, + PROGRAM_CATEGORY_SUPPLY, + PROGRAM_CATEGORY_SCIENCE, ) -/datum/computer_file/program/ntnetdownload/on_start() +/datum/computer_file/program/ntnetdownload/kill_program(mob/user) + abort_file_download() + ui_header = null . = ..() - main_repo = SSmodular_computers.available_station_software - antag_repo = SSmodular_computers.available_antag_software /datum/computer_file/program/ntnetdownload/proc/begin_file_download(filename) if(downloaded_file) @@ -42,7 +45,7 @@ return FALSE // Attempting to download antag only program, but without having emagged/syndicate computer. No. - if(PRG.available_on_syndinet && !(computer.obj_flags & EMAGGED)) + if((PRG.program_flags & PROGRAM_ON_SYNDINET_STORE) && !(computer.obj_flags & EMAGGED)) return FALSE if(!computer || !computer.can_store_file(PRG)) @@ -50,10 +53,10 @@ ui_header = "downloader_running.gif" - if(PRG in main_repo) + if(PRG in SSmodular_computers.available_station_software) generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from NTNet Software Repository.") hacked_download = FALSE - else if(PRG in antag_repo) + else if(PRG in SSmodular_computers.available_antag_software) generate_network_log("Began downloading file **ENCRYPTED**.[PRG.filetype] from unspecified server.") hacked_download = TRUE else @@ -68,7 +71,7 @@ generate_network_log("Aborted download of file [hacked_download ? "**ENCRYPTED**" : "[downloaded_file.filename].[downloaded_file.filetype]"].") downloaded_file = null download_completion = FALSE - ui_header = "downloader_finished.gif" + ui_header = null /datum/computer_file/program/ntnetdownload/proc/complete_file_download() if(!downloaded_file) @@ -87,18 +90,22 @@ if(download_completion >= downloaded_file.size) complete_file_download() // Download speed according to connectivity state. NTNet server is assumed to be on unlimited speed so we're limited by our local connectivity - download_netspeed = 0 + var/download_netspeed // Speed defines are found in misc.dm switch(ntnet_status) - if(1) + if(NTNET_LOW_SIGNAL) download_netspeed = NTNETSPEED_LOWSIGNAL - if(2) + if(NTNET_GOOD_SIGNAL) download_netspeed = NTNETSPEED_HIGHSIGNAL - if(3) + if(NTNET_ETHERNET_SIGNAL) download_netspeed = NTNETSPEED_ETHERNET - download_completion += download_netspeed + if(download_netspeed) + if(HAS_TRAIT(computer, TRAIT_MODPC_HALVED_DOWNLOAD_SPEED)) + download_netspeed *= 0.5 + download_completion += download_netspeed /datum/computer_file/program/ntnetdownload/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + . = ..() switch(action) if("PRG_downloadfile") if(!downloaded_file) @@ -107,7 +114,6 @@ if("PRG_reseterror") if(downloaderror) download_completion = FALSE - download_netspeed = FALSE downloaded_file = null downloaderror = "" return TRUE @@ -125,43 +131,34 @@ data["downloadname"] = downloaded_file.filename data["downloaddesc"] = downloaded_file.filedesc data["downloadsize"] = downloaded_file.size - data["downloadspeed"] = download_netspeed data["downloadcompletion"] = round(download_completion, 0.1) data["disk_size"] = computer.max_capacity data["disk_used"] = computer.used_capacity data["emagged"] = (computer.obj_flags & EMAGGED) - var/list/repo = antag_repo | main_repo - var/list/program_categories = list() + var/list/repo = SSmodular_computers.available_antag_software | SSmodular_computers.available_station_software for(var/datum/computer_file/program/programs as anything in repo) - if(!(programs.category in program_categories)) - program_categories.Add(programs.category) data["programs"] += list(list( "icon" = programs.program_icon, "filename" = programs.filename, "filedesc" = programs.filedesc, "fileinfo" = programs.extended_desc, - "category" = programs.category, + "category" = programs.downloader_category, "installed" = !!computer.find_file_by_name(programs.filename), "compatible" = check_compatibility(programs), "size" = programs.size, - "access" = (computer.obj_flags & EMAGGED) && programs.available_on_syndinet ? TRUE : programs.can_run(user, transfer = TRUE, access = access), - "verifiedsource" = programs.available_on_ntnet, + "access" = programs.can_run(user, downloading = TRUE, access = access), + "verifiedsource" = !!(programs.program_flags & PROGRAM_ON_NTNET_STORE), )) - data["categories"] = show_categories & program_categories + data["categories"] = show_categories return data -/datum/computer_file/program/ntnetdownload/proc/check_compatibility(datum/computer_file/program/P) - var/hardflag = computer.hardware_flag - - if(P?.is_supported_by_hardware(hardware_flag = hardflag, loud = FALSE)) - return TRUE - return FALSE - -/datum/computer_file/program/ntnetdownload/kill_program(mob/user) - abort_file_download() - return ..() +///Checks if a provided `program_to_check` is compatible to be downloaded on our computer. +/datum/computer_file/program/ntnetdownload/proc/check_compatibility(datum/computer_file/program/program_to_check) + if(!program_to_check || !program_to_check.is_supported_by_hardware(hardware_flag = computer.hardware_flag, loud = FALSE)) + return FALSE + return TRUE diff --git a/code/modules/modular_computers/file_system/programs/portrait_printer.dm b/code/modules/modular_computers/file_system/programs/portrait_printer.dm index 68c94e87e8d32..0e69dd4969da7 100644 --- a/code/modules/modular_computers/file_system/programs/portrait_printer.dm +++ b/code/modules/modular_computers/file_system/programs/portrait_printer.dm @@ -12,12 +12,12 @@ /datum/computer_file/program/portrait_printer filename = "PortraitPrinter" filedesc = "Marlowe Treeby's Art Galaxy" - category = PROGRAM_CATEGORY_CREW - program_icon_state = "dummy" + downloader_category = PROGRAM_CATEGORY_EQUIPMENT + program_open_overlay = "dummy" extended_desc = "This program connects to a Spinward Sector community art site for viewing and printing art." - transfer_access = list(ACCESS_LIBRARY) - usage_flags = PROGRAM_CONSOLE - requires_ntnet = TRUE + download_access = list(ACCESS_LIBRARY) + can_run_on_flags = PROGRAM_CONSOLE + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET size = 9 tgui_id = "NtosPortraitPrinter" program_icon = "paint-brush" @@ -44,6 +44,7 @@ ) /datum/computer_file/program/portrait_printer/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + . = ..() switch(action) if("search") if(search_string != params["to_search"]) diff --git a/code/modules/modular_computers/file_system/programs/powermonitor.dm b/code/modules/modular_computers/file_system/programs/powermonitor.dm index e82821d75e4f2..c5a8eba952b2a 100644 --- a/code/modules/modular_computers/file_system/programs/powermonitor.dm +++ b/code/modules/modular_computers/file_system/programs/powermonitor.dm @@ -3,13 +3,13 @@ /datum/computer_file/program/power_monitor filename = "ampcheck" filedesc = "AmpCheck" - category = PROGRAM_CATEGORY_ENGI - program_icon_state = "power_monitor" + downloader_category = PROGRAM_CATEGORY_ENGINEERING + program_open_overlay = "power_monitor" extended_desc = "This program connects to sensors around the station to provide information about electrical systems" ui_header = "power_norm.gif" - transfer_access = list(ACCESS_ENGINEERING) - usage_flags = PROGRAM_CONSOLE - requires_ntnet = FALSE + download_access = list(ACCESS_ENGINEERING) + can_run_on_flags = PROGRAM_CONSOLE + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET size = 8 tgui_id = "NtosPowerMonitor" program_icon = "plug" diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm index c230614339d49..b506777f3de7a 100644 --- a/code/modules/modular_computers/file_system/programs/radar.dm +++ b/code/modules/modular_computers/file_system/programs/radar.dm @@ -1,12 +1,21 @@ +///The selected target is not trackable +#define RADAR_NOT_TRACKABLE 0 +///The selected target is trackable +#define RADAR_TRACKABLE 1 +///The selected target is trackable, even if subtypes would normally consider it untrackable. +#define RADAR_TRACKABLE_ANYWAY 2 + +///If the target is something it shouldn't be normally tracking, this is the maximum distance within with it an be tracked. +#define MAX_RADAR_CIRCUIT_DISTANCE 18 + /datum/computer_file/program/radar //generic parent that handles most of the process filename = "genericfinder" filedesc = "debug_finder" - category = PROGRAM_CATEGORY_CREW + downloader_category = PROGRAM_CATEGORY_EQUIPMENT ui_header = "borg_mon.gif" //DEBUG -- new icon before PR - program_icon_state = "radarntos" - requires_ntnet = TRUE - available_on_ntnet = FALSE - usage_flags = PROGRAM_LAPTOP | PROGRAM_TABLET + program_open_overlay = "radarntos" + program_flags = PROGRAM_REQUIRES_NTNET + can_run_on_flags = PROGRAM_LAPTOP | PROGRAM_PDA size = 5 tgui_id = "NtosRadar" ///List of trackable entities. Updated by the scan() proc. @@ -15,7 +24,7 @@ var/selected ///Used to store when the next scan is available. COOLDOWN_DECLARE(next_scan) - ///Used to keep track of the last value program_icon_state was set to, to prevent constant unnecessary update_appearance() calls + ///Used to keep track of the last value program_open_overlay was set to, to prevent constant unnecessary update_appearance() calls var/last_icon_state = "" ///Used by the tgui interface, themed NT or Syndicate. var/arrowstyle = "ntosradarpointer.png" @@ -59,12 +68,13 @@ return data /datum/computer_file/program/radar/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) - + . = ..() switch(action) if("selecttarget") var/selected_new_ref = params["ref"] if(selected_new_ref in trackable_object_refs()) selected = selected_new_ref + SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_RADAR_SELECTED, selected) return TRUE if("scan") @@ -134,13 +144,22 @@ **arg1 is the atom being evaluated. */ /datum/computer_file/program/radar/proc/trackable(atom/movable/signal) - if(!signal || !computer) - return FALSE + SHOULD_CALL_PARENT(TRUE) + if(isnull(signal) || isnull(computer)) + return RADAR_NOT_TRACKABLE var/turf/here = get_turf(computer) var/turf/there = get_turf(signal) if(!here || !there) - return FALSE //I was still getting a runtime even after the above check while scanning, so fuck it - return (there.z == here.z) || (is_station_level(here.z) && is_station_level(there.z)) + return RADAR_NOT_TRACKABLE //I was still getting a runtime even after the above check while scanning, so fuck it + if(there.z != here.z && (!is_station_level(here.z) || !is_station_level(there.z))) + return RADAR_NOT_TRACKABLE + var/trackable_signal = SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_RADAR_TRACKABLE, signal, here, there) + switch(trackable_signal) + if(COMPONENT_RADAR_TRACK_ANYWAY) + return RADAR_TRACKABLE_ANYWAY + if(COMPONENT_RADAR_DONT_TRACK) + return RADAR_NOT_TRACKABLE + return RADAR_TRACKABLE /** * @@ -169,22 +188,25 @@ *return an atom reference. */ /datum/computer_file/program/radar/proc/find_atom() - return + SHOULD_CALL_PARENT(TRUE) + var/list/atom_container = list(null) + SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_RADAR_FIND_ATOM, atom_container) + return atom_container[1] //We use SSfastprocess for the program icon state because it runs faster than process_tick() does. /datum/computer_file/program/radar/process() if(computer.active_program != src) - STOP_PROCESSING(SSfastprocess, src) //We're not the active program, it's time to stop. - return + //We're not the active program, it's time to stop. + return PROCESS_KILL if(!selected) return var/atom/movable/signal = find_atom() if(!trackable(signal)) - program_icon_state = "[initial(program_icon_state)]lost" - if(last_icon_state != program_icon_state) + program_open_overlay = "[initial(program_open_overlay)]lost" + if(last_icon_state != program_open_overlay) computer.update_appearance() - last_icon_state = program_icon_state + last_icon_state = program_open_overlay return var/here_turf = get_turf(computer) @@ -192,17 +214,17 @@ var/trackdistance = get_dist_euclidian(here_turf, target_turf) switch(trackdistance) if(0) - program_icon_state = "[initial(program_icon_state)]direct" + program_open_overlay = "[initial(program_open_overlay)]direct" if(1 to 12) - program_icon_state = "[initial(program_icon_state)]close" + program_open_overlay = "[initial(program_open_overlay)]close" if(13 to 24) - program_icon_state = "[initial(program_icon_state)]medium" + program_open_overlay = "[initial(program_open_overlay)]medium" if(25 to INFINITY) - program_icon_state = "[initial(program_icon_state)]far" + program_open_overlay = "[initial(program_open_overlay)]far" - if(last_icon_state != program_icon_state) + if(last_icon_state != program_open_overlay) computer.update_appearance() - last_icon_state = program_icon_state + last_icon_state = program_open_overlay computer.setDir(get_dir(here_turf, target_turf)) //We can use process_tick to restart fast processing, since the computer will be running this constantly either way. @@ -219,13 +241,13 @@ filename = "lifeline" filedesc = "Lifeline" extended_desc = "This program allows for tracking of crew members via their suit sensors." - requires_ntnet = TRUE - transfer_access = list(ACCESS_MEDICAL) - available_on_ntnet = TRUE + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET + download_access = list(ACCESS_MEDICAL) program_icon = "heartbeat" + circuit_comp_type = /obj/item/circuit_component/mod_program/radar/medical /datum/computer_file/program/radar/lifeline/find_atom() - return locate(selected) in GLOB.human_list + return ..() || (locate(selected) in GLOB.human_list) /datum/computer_file/program/radar/lifeline/scan() objects = list() @@ -245,29 +267,31 @@ objects += list(crewinfo) /datum/computer_file/program/radar/lifeline/trackable(mob/living/carbon/human/humanoid) + . = ..() + if(. == RADAR_TRACKABLE_ANYWAY) + return RADAR_TRACKABLE_ANYWAY if(!humanoid || !istype(humanoid)) - return FALSE - if(..()) - if (istype(humanoid.w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/uniform = humanoid.w_uniform - if(uniform.has_sensor && uniform.sensor_mode >= SENSOR_COORDS) // Suit sensors must be on maximum - return TRUE - return FALSE + return RADAR_NOT_TRACKABLE + if(!istype(humanoid.w_uniform, /obj/item/clothing/under)) + return RADAR_NOT_TRACKABLE + var/obj/item/clothing/under/uniform = humanoid.w_uniform + if(uniform.has_sensor && uniform.sensor_mode >= SENSOR_COORDS) // Suit sensors must be on maximum + return RADAR_TRACKABLE ///Tracks all janitor equipment /datum/computer_file/program/radar/custodial_locator filename = "custodiallocator" filedesc = "Custodial Locator" extended_desc = "This program allows for tracking of custodial equipment." - requires_ntnet = TRUE - transfer_access = list(ACCESS_JANITOR) - available_on_ntnet = TRUE + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET + download_access = list(ACCESS_JANITOR) program_icon = "broom" size = 2 detomatix_resistance = DETOMATIX_RESIST_MINOR + circuit_comp_type = /obj/item/circuit_component/mod_program/radar/janitor /datum/computer_file/program/radar/custodial_locator/find_atom() - return locate(selected) in GLOB.janitor_devices + return ..() || (locate(selected) in GLOB.janitor_devices) /datum/computer_file/program/radar/custodial_locator/scan() objects = list() @@ -284,8 +308,8 @@ var/obj/structure/mop_bucket/janitorialcart/janicart = custodial_tools tool_name = "[janicart.name] - Water level: [janicart.reagents.total_volume] / [janicart.reagents.maximum_volume]" - if(istype(custodial_tools, /mob/living/simple_animal/bot/cleanbot)) - var/mob/living/simple_animal/bot/cleanbot/cleanbots = custodial_tools + if(istype(custodial_tools, /mob/living/basic/bot/cleanbot)) + var/mob/living/basic/bot/cleanbot/cleanbots = custodial_tools tool_name = "[cleanbots.name] - [cleanbots.bot_mode_flags & BOT_MODE_ON ? "Online" : "Offline"]" var/list/tool_information = list( @@ -302,16 +326,14 @@ /datum/computer_file/program/radar/fission360 filename = "fission360" filedesc = "Fission360" - category = PROGRAM_CATEGORY_MISC - program_icon_state = "radarsyndicate" + program_open_overlay = "radarsyndicate" extended_desc = "This program allows for tracking of nuclear authorization disks and warheads." - requires_ntnet = FALSE - available_on_ntnet = FALSE - available_on_syndinet = TRUE + program_flags = PROGRAM_ON_SYNDINET_STORE tgui_id = "NtosRadarSyndicate" program_icon = "bomb" arrowstyle = "ntosradarpointerS.png" pointercolor = "red" + circuit_comp_type = /obj/item/circuit_component/mod_program/radar/nukie /datum/computer_file/program/radar/fission360/on_start(mob/living/user) . = ..() @@ -329,7 +351,7 @@ return ..() /datum/computer_file/program/radar/fission360/find_atom() - return SSpoints_of_interest.get_poi_atom_by_ref(selected) + return ..() || SSpoints_of_interest.get_poi_atom_by_ref(selected) /datum/computer_file/program/radar/fission360/scan() objects = list() @@ -385,3 +407,131 @@ span_danger("[computer] vibrates and lets out an ominous alarm. Uh oh."), span_notice("[computer] begins to vibrate rapidly. Wonder what that means..."), ) + + +/** + * Base circuit for the radar program. + * The abstract radar doesn't have this, nor this one is associated to it, so + * make sure to specify the associate_program and circuit_comp_type of subtypes, + */ +/obj/item/circuit_component/mod_program/radar + + ///The target to track + var/datum/port/input/target + ///The selected target, from the app + var/datum/port/output/selected_by_app + /// The result from the output + var/datum/port/output/x_pos + var/datum/port/output/y_pos + + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + +/obj/item/circuit_component/mod_program/radar/populate_ports() + . = ..() + target = add_input_port("Target", PORT_TYPE_ATOM) + selected_by_app = add_output_port("Selected From Program", PORT_TYPE_ATOM) + x_pos = add_output_port("X", PORT_TYPE_NUMBER) + y_pos = add_output_port("Y", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/mod_program/radar/register_shell(atom/movable/shell) + . = ..() + RegisterSignal(associated_program.computer, COMSIG_MODULAR_COMPUTER_RADAR_TRACKABLE, PROC_REF(can_track)) + RegisterSignal(associated_program.computer, COMSIG_MODULAR_COMPUTER_RADAR_FIND_ATOM, PROC_REF(get_atom)) + RegisterSignal(associated_program.computer, COMSIG_MODULAR_COMPUTER_RADAR_SELECTED, PROC_REF(on_selected)) + +/obj/item/circuit_component/mod_program/radar/unregister_shell() + UnregisterSignal(associated_program.computer, list( + COMSIG_MODULAR_COMPUTER_RADAR_TRACKABLE, + COMSIG_MODULAR_COMPUTER_RADAR_FIND_ATOM, + COMSIG_MODULAR_COMPUTER_RADAR_SELECTED, + )) + return ..() + +/obj/item/circuit_component/mod_program/radar/get_ui_notices() + . = ..() + . += create_ui_notice("Max range for unsupported entities: [MAX_RADAR_CIRCUIT_DISTANCE] tiles", "orange", FA_ICON_BULLSEYE) + +///Set the selected ref of the program to the target (if it exists) and update the x/y pos ports (if trackable) when triggered. +/obj/item/circuit_component/mod_program/radar/input_received(datum/port/port) + var/datum/computer_file/program/radar/radar = associated_program + var/atom/radar_atom = radar.find_atom() + if(target.value != radar_atom) + radar.selected = REF(target.value) + SStgui.update_uis(radar.computer) + if(radar.trackable(radar_atom)) + var/turf/turf = get_turf(radar_atom) + x_pos.set_output(turf.x) + y_pos.set_output(turf.y) + else + x_pos.set_output(null) + y_pos.set_output(null) + +/** + * Check if we can track the object. When making different definitions of this proc for subtypes, include typical + * targets as an exception to this (e.g humans for lifeline) so that even if they're coming from a circuit input + * they won't get filtered by the maximum distance, because they're "supported entities". + */ +/obj/item/circuit_component/mod_program/radar/proc/can_track(datum/source, atom/signal, signal_turf, computer_turf) + SIGNAL_HANDLER + if(target.value && get_dist_euclidian(computer_turf, signal_turf) > MAX_RADAR_CIRCUIT_DISTANCE) + return COMPONENT_RADAR_DONT_TRACK + return COMPONENT_RADAR_TRACK_ANYWAY + +///Return the value of the target port. +/obj/item/circuit_component/mod_program/radar/proc/get_atom(datum/source, list/atom_container) + SIGNAL_HANDLER + atom_container[1] = target.value + +/** + * When a target is selected by the app, reset the target port, update the x/pos ports (if trackable) + * and set selected_by_app port to the target atom. + */ +/obj/item/circuit_component/mod_program/radar/proc/on_selected(datum/source, selected_ref) + SIGNAL_HANDLER + target.set_value(null) + var/datum/computer_file/program/radar/radar = associated_program + var/atom/selected_atom = radar.find_atom() + selected_by_app.set_output(selected_atom) + if(radar.trackable(selected_atom)) + var/turf/turf = get_turf(radar.selected) + x_pos.set_output(turf.x) + y_pos.set_output(turf.y) + else + x_pos.set_output(null) + y_pos.set_output(null) + + trigger_output.set_output(COMPONENT_SIGNAL) + + +/obj/item/circuit_component/mod_program/radar/medical + associated_program = /datum/computer_file/program/radar/lifeline + +/obj/item/circuit_component/mod_program/radar/medical/can_track(datum/source, atom/signal, signal_turf, computer_turf) + if(target.value in GLOB.human_list) + return NONE + return ..() + +/obj/item/circuit_component/mod_program/radar/janitor + associated_program = /datum/computer_file/program/radar/custodial_locator + +/obj/item/circuit_component/mod_program/radar/janitor/can_track(datum/source, atom/signal, signal_turf, computer_turf) + if(target.value in GLOB.janitor_devices) + return NONE + return ..() +/obj/item/circuit_component/mod_program/radar/nukie + associated_program = /datum/computer_file/program/radar/fission360 + +/obj/item/circuit_component/mod_program/radar/nukie/can_track(datum/source, atom/signal, signal_turf, computer_turf) + if(target.value in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb)) + return NONE + if(target.value in SSpoints_of_interest.real_nuclear_disks) + return NONE + if(target.value == SSshuttle.getShuttle("syndicate")) + return NONE + return ..() + +#undef MAX_RADAR_CIRCUIT_DISTANCE + +#undef RADAR_NOT_TRACKABLE +#undef RADAR_TRACKABLE +#undef RADAR_TRACKABLE_ANYWAY diff --git a/code/modules/modular_computers/file_system/programs/records.dm b/code/modules/modular_computers/file_system/programs/records.dm index 960702d608cc4..063c19d35e18b 100644 --- a/code/modules/modular_computers/file_system/programs/records.dm +++ b/code/modules/modular_computers/file_system/programs/records.dm @@ -2,13 +2,13 @@ filename = "ntrecords" filedesc = "Records" extended_desc = "Allows the user to view several basic records from the crew." - category = PROGRAM_CATEGORY_MISC + downloader_category = PROGRAM_CATEGORY_SECURITY program_icon = "clipboard" - program_icon_state = "crew" + program_open_overlay = "crew" tgui_id = "NtosRecords" size = 4 - usage_flags = PROGRAM_TABLET | PROGRAM_LAPTOP - available_on_ntnet = FALSE + can_run_on_flags = PROGRAM_PDA | PROGRAM_LAPTOP + program_flags = NONE detomatix_resistance = DETOMATIX_RESIST_MINOR var/mode @@ -18,16 +18,16 @@ filename = "medrecords" program_icon = "book-medical" extended_desc = "Allows the user to view several basic medical records from the crew." - transfer_access = list(ACCESS_MEDICAL, ACCESS_FLAG_COMMAND) - available_on_ntnet = TRUE + download_access = list(ACCESS_MEDICAL, ACCESS_FLAG_COMMAND) + program_flags = PROGRAM_ON_NTNET_STORE mode = "medical" /datum/computer_file/program/records/security filedesc = "Security Records" filename = "secrecords" extended_desc = "Allows the user to view several basic security records from the crew." - transfer_access = list(ACCESS_SECURITY, ACCESS_FLAG_COMMAND) - available_on_ntnet = TRUE + download_access = list(ACCESS_SECURITY, ACCESS_FLAG_COMMAND) + program_flags = PROGRAM_ON_NTNET_STORE mode = "security" /datum/computer_file/program/records/proc/GetRecordsReadable() @@ -45,6 +45,7 @@ current_record["rank"] = person.rank current_record["species"] = person.species current_record["wanted"] = person.wanted_status + current_record["voice"] = person.voice all_records += list(current_record) if("medical") diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm index cf094e683a983..68f7ba3ddef05 100644 --- a/code/modules/modular_computers/file_system/programs/robocontrol.dm +++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm @@ -2,10 +2,10 @@ /datum/computer_file/program/robocontrol filename = "botkeeper" filedesc = "BotKeeper" - category = PROGRAM_CATEGORY_SCI - program_icon_state = "robot" + downloader_category = PROGRAM_CATEGORY_SCIENCE + program_open_overlay = "robot" extended_desc = "A remote controller used for giving basic commands to non-sentient robots." - requires_ntnet = TRUE + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET size = 6 tgui_id = "NtosRoboControl" program_icon = "robot" @@ -49,6 +49,7 @@ var/mob/living/simple_animal/bot/mulebot/simple_mulebot = simple_bot mulelist += list(list( "name" = simple_mulebot.name, + "id" = simple_mulebot.id, "dest" = simple_mulebot.destination, "power" = simple_mulebot.cell ? simple_mulebot.cell.percent() : 0, "home" = simple_mulebot.home_destination, @@ -56,13 +57,12 @@ "autoPickup" = simple_mulebot.auto_pickup, "reportDelivery" = simple_mulebot.report_delivery, "mule_ref" = REF(simple_mulebot), + "load" = simple_mulebot.get_load_name(), )) - if(simple_mulebot.load) - data["load"] = simple_mulebot.load.name newbot["mule_check"] = TRUE botlist += list(newbot) - for(var/mob/living/simple_animal/drone/all_drones as anything in GLOB.drones_list) + for(var/mob/living/basic/drone/all_drones as anything in GLOB.drones_list) if(all_drones.hacked) continue if(!is_valid_z_level(current_turf, get_turf(all_drones))) @@ -83,7 +83,8 @@ return data -/datum/computer_file/program/robocontrol/ui_act(action, list/params, datum/tgui/ui) +/datum/computer_file/program/robocontrol/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() var/mob/current_user = ui.user var/obj/item/card/id/id_card = computer?.computer_id_slot diff --git a/code/modules/modular_computers/file_system/programs/robotact.dm b/code/modules/modular_computers/file_system/programs/robotact.dm index e64691ccba2a2..8a2a824d004d2 100644 --- a/code/modules/modular_computers/file_system/programs/robotact.dm +++ b/code/modules/modular_computers/file_system/programs/robotact.dm @@ -1,14 +1,13 @@ /datum/computer_file/program/robotact filename = "robotact" filedesc = "RoboTact" - category = PROGRAM_CATEGORY_SCI + downloader_category = PROGRAM_CATEGORY_SCIENCE extended_desc = "A built-in app for cyborg self-management and diagnostics." ui_header = "robotact.gif" //DEBUG -- new icon before PR - program_icon_state = "command" - requires_ntnet = FALSE - available_on_ntnet = FALSE + program_open_overlay = "command" + program_flags = NONE undeletable = TRUE - usage_flags = PROGRAM_TABLET + can_run_on_flags = PROGRAM_PDA size = 5 tgui_id = "NtosRobotact" program_icon = "terminal" @@ -21,7 +20,7 @@ if(.) var/obj/item/modular_computer/pda/silicon/tablet = computer if(tablet.device_theme == PDA_THEME_SYNDICATE) - program_icon_state = "command-syndicate" + program_open_overlay = "command-syndicate" return TRUE return FALSE @@ -85,6 +84,7 @@ return data /datum/computer_file/program/robotact/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + . = ..() //Implied type, memes var/obj/item/modular_computer/pda/silicon/tablet = computer var/mob/living/silicon/robot/cyborg = tablet.silicon_owner diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm index bba55b4474efb..2b3b8f6ea8c95 100644 --- a/code/modules/modular_computers/file_system/programs/secureye.dm +++ b/code/modules/modular_computers/file_system/programs/secureye.dm @@ -3,13 +3,13 @@ /datum/computer_file/program/secureye filename = "secureye" filedesc = "SecurEye" - category = PROGRAM_CATEGORY_MISC + downloader_category = PROGRAM_CATEGORY_SECURITY ui_header = "borg_mon.gif" - program_icon_state = "generic" + program_open_overlay = "generic" extended_desc = "This program allows access to standard security camera networks." - requires_ntnet = TRUE - transfer_access = list(ACCESS_SECURITY) - usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET + download_access = list(ACCESS_SECURITY) + can_run_on_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP size = 5 tgui_id = "NtosSecurEye" program_icon = "eye" @@ -39,12 +39,9 @@ filename = "syndeye" filedesc = "SyndEye" extended_desc = "This program allows for illegal access to security camera networks." - transfer_access = list() - available_on_ntnet = FALSE - available_on_syndinet = TRUE - requires_ntnet = FALSE - usage_flags = PROGRAM_ALL - unique_copy = TRUE + download_access = list() + can_run_on_flags = PROGRAM_ALL + program_flags = PROGRAM_ON_SYNDINET_STORE | PROGRAM_UNIQUE_COPY network = list("ss13", "mine", "rd", "labor", "ordnance", "minisat") spying = TRUE @@ -64,7 +61,6 @@ cam_background = new cam_background.assigned_map = map_name cam_background.del_on_map_removal = FALSE - RegisterSignal(src, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target)) /datum/computer_file/program/secureye/Destroy() QDEL_NULL(cam_screen) @@ -100,18 +96,19 @@ /datum/computer_file/program/secureye/ui_data() var/list/data = list() - data["network"] = network data["activeCamera"] = null var/obj/machinery/camera/active_camera = camera_ref?.resolve() if(active_camera) data["activeCamera"] = list( name = active_camera.c_tag, + ref = REF(active_camera), status = active_camera.status, ) return data /datum/computer_file/program/secureye/ui_static_data(mob/user) var/list/data = list() + data["network"] = network data["mapRef"] = cam_screen.assigned_map data["can_spy"] = !!spying var/list/cameras = get_camera_list(network) @@ -120,6 +117,7 @@ var/obj/machinery/camera/C = cameras[i] data["cameras"] += list(list( name = C.c_tag, + ref = REF(C), )) return data @@ -130,16 +128,17 @@ return switch(action) if("switch_camera") - var/c_tag = format_text(params["name"]) - var/list/cameras = get_camera_list(network) - var/obj/machinery/camera/selected_camera = cameras[c_tag] - camera_ref = WEAKREF(selected_camera) + var/obj/machinery/camera/selected_camera = locate(params["camera"]) in GLOB.cameranet.cameras + if(selected_camera) + camera_ref = WEAKREF(selected_camera) + else + camera_ref = null if(!spying) playsound(computer, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) - if(!selected_camera) + if(isnull(camera_ref)) return TRUE - if(internal_tracker && internal_tracker.tracking) - internal_tracker.set_tracking(FALSE) + if(internal_tracker) + internal_tracker.reset_tracking() update_active_camera_screen() return TRUE @@ -147,7 +146,8 @@ if("start_tracking") if(!internal_tracker) internal_tracker = new(src) - internal_tracker.set_tracked_mob(usr) + RegisterSignal(internal_tracker, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target)) + internal_tracker.track_input(usr) return TRUE /datum/computer_file/program/secureye/proc/on_track_target(datum/trackable/source, mob/living/target) @@ -169,8 +169,8 @@ /datum/computer_file/program/secureye/ui_close(mob/user) . = ..() //don't track anyone while we're shutting off. - if(internal_tracker && internal_tracker.tracking) - internal_tracker.set_tracking(FALSE) + if(internal_tracker) + internal_tracker.reset_tracking() var/user_ref = REF(user) var/is_living = isliving(user) // Living creature or not, we remove you anyway. diff --git a/code/modules/modular_computers/file_system/programs/signalcommander.dm b/code/modules/modular_computers/file_system/programs/signalcommander.dm index 6d636bab370a9..1e6e3e54051fb 100644 --- a/code/modules/modular_computers/file_system/programs/signalcommander.dm +++ b/code/modules/modular_computers/file_system/programs/signalcommander.dm @@ -1,19 +1,25 @@ /datum/computer_file/program/signal_commander filename = "signaler" filedesc = "SignalCommander" - category = PROGRAM_CATEGORY_MISC - program_icon_state = "signal" + downloader_category = PROGRAM_CATEGORY_EQUIPMENT + program_open_overlay = "signal" extended_desc = "A small built-in frequency app that sends out signaller signals with the appropriate hardware." size = 2 tgui_id = "NtosSignaler" program_icon = "satellite-dish" - usage_flags = PROGRAM_TABLET | PROGRAM_LAPTOP + can_run_on_flags = PROGRAM_PDA | PROGRAM_LAPTOP + program_flags = /datum/computer_file/program::program_flags | PROGRAM_CIRCUITS_RUN_WHEN_CLOSED + circuit_comp_type = /obj/item/circuit_component/mod_program/signaler ///What is the saved signal frequency? var/signal_frequency = FREQ_SIGNALER /// What is the saved signal code? var/signal_code = DEFAULT_SIGNALER_CODE /// Radio connection datum used by signalers. var/datum/radio_frequency/radio_connection + /// How long do we cooldown before we can send another signal? + var/signal_cooldown_time = 1 SECONDS + /// Cooldown store + COOLDOWN_DECLARE(signal_cooldown) /datum/computer_file/program/signal_commander/on_start(mob/living/user) . = ..() @@ -26,15 +32,17 @@ /datum/computer_file/program/signal_commander/ui_data(mob/user) var/list/data = list() data["frequency"] = signal_frequency + data["cooldown"] = signal_cooldown_time data["code"] = signal_code data["minFrequency"] = MIN_FREE_FREQ data["maxFrequency"] = MAX_FREE_FREQ return data -/datum/computer_file/program/signal_commander/ui_act(action, list/params) +/datum/computer_file/program/signal_commander/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() switch(action) if("signal") - INVOKE_ASYNC(src, PROC_REF(signal)) + INVOKE_ASYNC(src, PROC_REF(signal), usr) . = TRUE if("freq") var/new_signal_frequency = sanitize_frequency(unformat_frequency(params["freq"]), TRUE) @@ -51,23 +59,65 @@ signal_code = initial(signal_code) . = TRUE -/datum/computer_file/program/signal_commander/proc/signal() +/datum/computer_file/program/signal_commander/proc/signal(atom/source) if(!radio_connection) return + var/mob/user + var/obj/item/circuit_component/signaling + if(ismob(source)) + user = source + else if(istype(source, /obj/item/circuit_component)) + signaling = source + + if(!COOLDOWN_FINISHED(src, signal_cooldown)) + if(user) + computer.balloon_alert(user, "cooling down!") + return + + COOLDOWN_START(src, signal_cooldown, signal_cooldown_time) + if(user) + computer.balloon_alert(user, "signaled") + var/time = time2text(world.realtime,"hh:mm:ss") var/turf/T = get_turf(computer) + var/user_deets + if(signaling) + user_deets = "[signaling.parent.get_creator()]" + else + user_deets = "[key_name(usr)]" + var/logging_data = "[time] : [user_deets] used the computer '[initial(computer.name)]' @ location ([T.x],[T.y],[T.z]) : [format_frequency(signal_frequency)]/[signal_code]" + add_to_signaler_investigate_log(logging_data) - var/logging_data - if(usr) - logging_data = "[time] : [usr.key] used [computer] @ location ([T.x],[T.y],[T.z]) : [format_frequency(signal_frequency)]/[signal_code]" - GLOB.lastsignalers.Add(logging_data) - - var/datum/signal/signal = new(list("code" = signal_code), logging_data = logging_data) + var/datum/signal/signal = new(list("code" = signal_code, "key" = signaling?.parent.owner_id), logging_data = logging_data) radio_connection.post_signal(computer, signal) /datum/computer_file/program/signal_commander/proc/set_frequency(new_frequency) SSradio.remove_object(computer, signal_frequency) signal_frequency = new_frequency radio_connection = SSradio.add_object(computer, signal_frequency, RADIO_SIGNALER) - return + +/obj/item/circuit_component/mod_program/signaler + associated_program = /datum/computer_file/program/signal_commander + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL + + /// Frequency input + var/datum/port/input/freq + /// Signal input + var/datum/port/input/code + +/obj/item/circuit_component/mod_program/signaler/populate_ports() + . = ..() + freq = add_input_port("Frequency", PORT_TYPE_NUMBER, trigger = PROC_REF(set_freq), default = FREQ_SIGNALER) + code = add_input_port("Code", PORT_TYPE_NUMBER, trigger = PROC_REF(set_code), default = DEFAULT_SIGNALER_CODE) + +/obj/item/circuit_component/mod_program/signaler/proc/set_freq(datum/port/port) + var/datum/computer_file/program/signal_commander/signaler = associated_program + signaler.set_frequency(clamp(freq.value, MIN_FREE_FREQ, MAX_FREE_FREQ)) + +/obj/item/circuit_component/mod_program/signaler/proc/set_code(datum/port/port) + var/datum/computer_file/program/signal_commander/signaler = associated_program + signaler.signal_code = round(clamp(code.value, 1, 100)) + +/obj/item/circuit_component/mod_program/signaler/input_received(datum/port/port) + INVOKE_ASYNC(associated_program, TYPE_PROC_REF(/datum/computer_file/program/signal_commander, signal), src) diff --git a/code/modules/modular_computers/file_system/programs/skill_tracker.dm b/code/modules/modular_computers/file_system/programs/skill_tracker.dm index c68cffb337401..bd208dcef524b 100644 --- a/code/modules/modular_computers/file_system/programs/skill_tracker.dm +++ b/code/modules/modular_computers/file_system/programs/skill_tracker.dm @@ -1,13 +1,13 @@ /datum/computer_file/program/skill_tracker filename = "skilltracker" filedesc = "ExperTrak Skill Tracker" - category = PROGRAM_CATEGORY_MISC - program_icon_state = "generic" + downloader_category = PROGRAM_CATEGORY_DEVICE + program_open_overlay = "generic" extended_desc = "Scan and view your current marketable job skills." size = 2 tgui_id = "NtosSkillTracker" program_icon = "medal" - usage_flags = PROGRAM_TABLET // Must be a handheld device to read read your chakras or whatever + can_run_on_flags = PROGRAM_PDA // Must be a handheld device to read read your chakras or whatever /datum/computer_file/program/skill_tracker/ui_data(mob/user) var/list/data = list() @@ -50,7 +50,8 @@ return null -/datum/computer_file/program/skill_tracker/ui_act(action, params, datum/tgui/ui) +/datum/computer_file/program/skill_tracker/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + . = ..() switch(action) if("PRG_reward") var/skill_type = find_skilltype(params["skill"]) diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm index 0ba8a72140779..72ab4d094c084 100644 --- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm @@ -1,12 +1,12 @@ /datum/computer_file/program/supermatter_monitor filename = "ntcims" filedesc = "NT CIMS" - category = PROGRAM_CATEGORY_ENGI + downloader_category = PROGRAM_CATEGORY_ENGINEERING ui_header = "smmon_0.gif" - program_icon_state = "smmon_0" + program_open_overlay = "smmon_0" extended_desc = "Crystal Integrity Monitoring System, connects to specially calibrated supermatter sensors to provide information on the status of supermatter-based engines." - requires_ntnet = TRUE - transfer_access = list(ACCESS_CONSTRUCTION) + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET + download_access = list(ACCESS_CONSTRUCTION) size = 5 tgui_id = "NtosSupermatter" program_icon = "radiation" @@ -55,6 +55,7 @@ return data /datum/computer_file/program/supermatter_monitor/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + . = ..() switch(action) if("PRG_refresh") refresh() @@ -109,6 +110,6 @@ if(last_status != new_status) last_status = new_status ui_header = "smmon_[last_status].gif" - program_icon_state = "smmon_[last_status]" + program_open_overlay = "smmon_[last_status]" if(istype(computer)) computer.update_appearance() diff --git a/code/modules/modular_computers/file_system/programs/statusdisplay.dm b/code/modules/modular_computers/file_system/programs/statusdisplay.dm index d55bafb2e9c19..6136ab9355b59 100644 --- a/code/modules/modular_computers/file_system/programs/statusdisplay.dm +++ b/code/modules/modular_computers/file_system/programs/statusdisplay.dm @@ -2,15 +2,15 @@ filename = "statusdisplay" filedesc = "Status Display" program_icon = "signal" - program_icon_state = "generic" - requires_ntnet = TRUE + program_open_overlay = "generic" size = 1 + circuit_comp_type = /obj/item/circuit_component/mod_program/status extended_desc = "An app used to change the message on the station status displays." tgui_id = "NtosStatus" - usage_flags = PROGRAM_ALL - available_on_ntnet = FALSE + can_run_on_flags = PROGRAM_ALL + program_flags = PROGRAM_REQUIRES_NTNET var/upper_text = "" var/lower_text = "" @@ -43,16 +43,16 @@ * * upper - Top text * * lower - Bottom text */ -/datum/computer_file/program/status/proc/post_message(upper, lower) +/datum/computer_file/program/status/proc/post_message(upper, lower, log_usr = key_name(usr)) post_status("message", upper, lower) - log_game("[key_name(usr)] has changed the station status display message to \"[upper] [lower]\" [loc_name(usr)]") + log_game("[log_usr] has changed the station status display message to \"[upper] [lower]\" [loc_name(usr)]") /** * Post a picture to status displays * Arguments: * * picture - The picture name */ -/datum/computer_file/program/status/proc/post_picture(picture) +/datum/computer_file/program/status/proc/post_picture(picture, log_usr = key_name(usr)) if (!(picture in GLOB.status_display_approved_pictures)) return if(picture in GLOB.status_display_state_pictures) @@ -71,9 +71,10 @@ else post_status("alert", picture) - log_game("[key_name(usr)] has changed the station status display message to \"[picture]\" [loc_name(usr)]") + log_game("[log_usr] has changed the station status display message to \"[picture]\" [loc_name(usr)]") -/datum/computer_file/program/status/ui_act(action, list/params, datum/tgui/ui) +/datum/computer_file/program/status/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() switch(action) if("setStatusMessage") upper_text = reject_bad_text(params["upperText"] || "", MAX_STATUS_LINE_LENGTH) @@ -95,3 +96,31 @@ data["lowerText"] = lower_text return data + + +/obj/item/circuit_component/mod_program/status + associated_program = /datum/computer_file/program/status + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + + ///When the trigger is signaled, this will be the upper text of status displays. + var/datum/port/input/upper_text + ///When the trigger is signaled, this will be the bottom text. + var/datum/port/input/bottom_text + ///A list port that, when signaled, will set the status image to one of its values + var/datum/port/input/status_display_pics + +/obj/item/circuit_component/mod_program/status/populate_ports() + . = ..() + upper_text = add_input_port("Upper text", PORT_TYPE_STRING) + bottom_text = add_input_port("Bottom text", PORT_TYPE_STRING) + +/obj/item/circuit_component/mod_program/status/populate_options() + status_display_pics = add_option_port("Set Status Display Picture", GLOB.status_display_approved_pictures, trigger = PROC_REF(set_picture)) + +/obj/item/circuit_component/mod_program/status/proc/set_picture(datum/port/port) + var/datum/computer_file/program/status/status = associated_program + INVOKE_ASYNC(status, TYPE_PROC_REF(/datum/computer_file/program/status, post_picture), status_display_pics.value, parent.get_creator()) + +/obj/item/circuit_component/mod_program/status/input_received(datum/port/port) + var/datum/computer_file/program/status/status = associated_program + INVOKE_ASYNC(status, TYPE_PROC_REF(/datum/computer_file/program/status, post_message), upper_text.value, bottom_text.value, parent.get_creator()) diff --git a/code/modules/modular_computers/file_system/programs/techweb.dm b/code/modules/modular_computers/file_system/programs/techweb.dm index 9c097b2fb9b02..bf9e7b1e9b8b9 100644 --- a/code/modules/modular_computers/file_system/programs/techweb.dm +++ b/code/modules/modular_computers/file_system/programs/techweb.dm @@ -1,15 +1,15 @@ /datum/computer_file/program/science filename = "experi_track" filedesc = "Nanotrasen Science Hub" - category = PROGRAM_CATEGORY_SCI - program_icon_state = "research" + downloader_category = PROGRAM_CATEGORY_SCIENCE + program_open_overlay = "research" extended_desc = "Connect to the internal science server in order to assist in station research efforts." - requires_ntnet = TRUE + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET size = 10 tgui_id = "NtosTechweb" program_icon = "atom" - required_access = list(ACCESS_COMMAND, ACCESS_RESEARCH) - transfer_access = list(ACCESS_RESEARCH) + run_access = list(ACCESS_COMMAND, ACCESS_RESEARCH) + download_access = list(ACCESS_RESEARCH) /// Reference to global science techweb var/datum/techweb/stored_research /// Access needed to lock/unlock the console @@ -21,10 +21,10 @@ /// Sequence var for the id cache var/id_cache_seq = 1 -/datum/computer_file/program/science/on_start(mob/living/user) +/datum/computer_file/program/science/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing) . = ..() if(!CONFIG_GET(flag/no_default_techweb_link) && !stored_research) - CONNECT_TO_RND_SERVER_ROUNDSTART(stored_research, src) + CONNECT_TO_RND_SERVER_ROUNDSTART(stored_research, computer) /datum/computer_file/program/science/application_attackby(obj/item/attacking_item, mob/living/user) if(!istype(attacking_item, /obj/item/multitool)) @@ -88,7 +88,8 @@ ) return data -/datum/computer_file/program/science/ui_act(action, list/params) +/datum/computer_file/program/science/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() // Check if the console is locked to block any actions occuring if (locked && action != "toggleLock") computer.say("Console is locked, cannot perform further actions.") @@ -110,7 +111,8 @@ /datum/computer_file/program/science/ui_static_data(mob/user) . = list( - "static_data" = list() + "static_data" = list(), + "point_types_abbreviations" = SSresearch.point_types, ) // Build node cache... diff --git a/code/modules/modular_computers/file_system/programs/theme_selector.dm b/code/modules/modular_computers/file_system/programs/theme_selector.dm index 9bc15a1a00b90..6190f9b15abaf 100644 --- a/code/modules/modular_computers/file_system/programs/theme_selector.dm +++ b/code/modules/modular_computers/file_system/programs/theme_selector.dm @@ -2,12 +2,10 @@ filename = "themeify" filedesc = "Themeify" extended_desc = "This program allows configuration of your device's theme." - program_icon_state = "generic" + program_open_overlay = "generic" undeletable = TRUE size = 0 - header_program = TRUE - available_on_ntnet = TRUE - requires_ntnet = FALSE + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_HEADER tgui_id = "NtosThemeConfigure" program_icon = "paint-roller" @@ -25,6 +23,7 @@ return data /datum/computer_file/program/themeify/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + . = ..() switch(action) if("PRG_change_theme") var/selected_theme = params["selected_theme"] diff --git a/code/modules/modular_computers/file_system/programs/wirecarp.dm b/code/modules/modular_computers/file_system/programs/wirecarp.dm index 712d1e92cdafe..f5a0a374aac44 100644 --- a/code/modules/modular_computers/file_system/programs/wirecarp.dm +++ b/code/modules/modular_computers/file_system/programs/wirecarp.dm @@ -1,17 +1,18 @@ /datum/computer_file/program/ntnetmonitor filename = "wirecarp" filedesc = "WireCarp" - category = PROGRAM_CATEGORY_MISC - program_icon_state = "comm_monitor" + downloader_category = PROGRAM_CATEGORY_SECURITY + program_open_overlay = "comm_monitor" extended_desc = "This program monitors stationwide NTNet network, provides access to logging systems, and allows for configuration changes" size = 12 - requires_ntnet = TRUE - required_access = list(ACCESS_NETWORK) //NETWORK CONTROL IS A MORE SECURE PROGRAM. - available_on_ntnet = TRUE + run_access = list(ACCESS_NETWORK) //NETWORK CONTROL IS A MORE SECURE PROGRAM. + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET tgui_id = "NtosNetMonitor" program_icon = "network-wired" + circuit_comp_type = /obj/item/circuit_component/mod_program/ntnetmonitor -/datum/computer_file/program/ntnetmonitor/ui_act(action, list/params, datum/tgui/ui) +/datum/computer_file/program/ntnetmonitor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() switch(action) if("resetIDS") SSmodular_computers.intrusion_detection_alarm = FALSE @@ -20,7 +21,7 @@ SSmodular_computers.intrusion_detection_enabled = !SSmodular_computers.intrusion_detection_enabled return TRUE if("toggle_relay") - var/obj/machinery/ntnet_relay/target_relay = locate(params["ref"]) in GLOB.ntnet_relays + var/obj/machinery/ntnet_relay/target_relay = locate(params["ref"]) in SSmachines.get_machines_by_type(/obj/machinery/ntnet_relay) if(!istype(target_relay)) return target_relay.set_relay_enabled(!target_relay.relay_enabled) @@ -39,7 +40,7 @@ var/list/data = list() data["ntnetrelays"] = list() - for(var/obj/machinery/ntnet_relay/relays as anything in GLOB.ntnet_relays) + for(var/obj/machinery/ntnet_relay/relays as anything in SSmachines.get_machines_by_type(/obj/machinery/ntnet_relay)) var/list/relay_data = list() relay_data["is_operational"] = !!relays.is_operational relay_data["name"] = relays.name @@ -51,7 +52,7 @@ data["idsalarm"] = SSmodular_computers.intrusion_detection_alarm data["ntnetlogs"] = list() - for(var/i in SSmodular_computers.logs) + for(var/i in SSmodular_computers.modpc_logs) data["ntnetlogs"] += list(list("entry" = i)) data["tablets"] = list() @@ -67,3 +68,65 @@ data["tablets"] += list(tablet_data) return data + +/obj/item/circuit_component/mod_program/ntnetmonitor + associated_program = /datum/computer_file/program/ntnetmonitor + circuit_flags = CIRCUIT_FLAG_OUTPUT_SIGNAL + ///The stored NTnet relay or PDA to be used as the target of triggers + var/datum/port/input/target + ///Sets `intrusion_detection_alarm` when triggered + var/datum/port/input/toggle_ids + ///Toggles the target ntnet relay on/off when triggered + var/datum/port/input/toggle_relay + ///Purges modpc logs when triggered + var/datum/port/input/purge_logs + ///Toggles the spam mode of the target PDA when triggered + var/datum/port/input/toggle_mass_pda + ///Toggle mime mode of the target PDA when triggered + var/datum/port/input/toggle_mime_mode + ///Returns a list of all PDA Messengers when the "Get Messengers" input is pinged + var/datum/port/output/all_messengers + ///See above + var/datum/port/input/get_pdas + +/obj/item/circuit_component/mod_program/ntnetmonitor/populate_ports() + . = ..() + target = add_input_port("Target Messenger/Relay", PORT_TYPE_ATOM) + toggle_ids = add_input_port("Toggle IDS Status", PORT_TYPE_SIGNAL, trigger = PROC_REF(toggle_ids)) + toggle_relay = add_input_port("Toggle NTnet Relay", PORT_TYPE_SIGNAL, trigger = PROC_REF(toggle_relay)) + purge_logs = add_input_port("Purge Logs", PORT_TYPE_SIGNAL, trigger = PROC_REF(purge_logs)) + toggle_mass_pda = add_input_port("Toggle Mass Messenger", PORT_TYPE_SIGNAL, trigger = PROC_REF(toggle_pda_stuff)) + toggle_mime_mode = add_input_port("Toggle Mime Mode", PORT_TYPE_SIGNAL, trigger = PROC_REF(toggle_pda_stuff)) + get_pdas = add_input_port("Get PDAs", PORT_TYPE_SIGNAL, trigger = PROC_REF(get_pdas)) + all_messengers = add_output_port("List of PDAs", PORT_TYPE_LIST(PORT_TYPE_ATOM)) + +/obj/item/circuit_component/mod_program/ntnetmonitor/proc/get_pdas(datum/port/port) + var/list/computers_with_messenger = list() + for(var/messenger_ref as anything in GLOB.pda_messengers) + var/datum/computer_file/program/messenger/messenger = GLOB.pda_messengers[messenger_ref] + computers_with_messenger |= WEAKREF(messenger.computer) + all_messengers.set_output(computers_with_messenger) + +/obj/item/circuit_component/mod_program/ntnetmonitor/proc/toggle_ids(datum/port/port) + SSmodular_computers.intrusion_detection_enabled = !SSmodular_computers.intrusion_detection_enabled + +/obj/item/circuit_component/mod_program/ntnetmonitor/proc/toggle_relay(datum/port/port) + var/obj/machinery/ntnet_relay/target_relay = target.value + if(!istype(target_relay)) + return + target_relay.set_relay_enabled(!target_relay.relay_enabled) + +/obj/item/circuit_component/mod_program/ntnetmonitor/proc/purge_logs(datum/port/port) + SSmodular_computers.purge_logs() + +/obj/item/circuit_component/mod_program/ntnetmonitor/proc/toggle_pda_stuff(datum/port/port) + var/obj/item/modular_computer/computer = target.value + if(!istype(computer)) + return + var/datum/computer_file/program/messenger/target_messenger = locate() in computer.stored_files + if(isnull(target_messenger)) + return + if(COMPONENT_TRIGGERED_BY(toggle_mass_pda, port)) + target_messenger.spam_mode = !target_messenger.spam_mode + if(COMPONENT_TRIGGERED_BY(toggle_mime_mode, port)) + target_messenger.mime_mode = !target_messenger.mime_mode diff --git a/code/modules/movespeed/modifiers/items.dm b/code/modules/movespeed/modifiers/items.dm index 433200e322319..6bdf2f31760d5 100644 --- a/code/modules/movespeed/modifiers/items.dm +++ b/code/modules/movespeed/modifiers/items.dm @@ -17,5 +17,12 @@ /datum/movespeed_modifier/sphere multiplicative_slowdown = -0.5 +/datum/movespeed_modifier/hook_jawed + multiplicative_slowdown = 4 + /datum/movespeed_modifier/shooting_assistant multiplicative_slowdown = 0.5 + +/datum/movespeed_modifier/binocs_wielded + multiplicative_slowdown = 1.5 + diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm index 59b514a3d5772..b782f2fc9593d 100644 --- a/code/modules/movespeed/modifiers/mobs.dm +++ b/code/modules/movespeed/modifiers/mobs.dm @@ -81,11 +81,8 @@ blacklisted_movetypes = FLOATING variable = TRUE -/datum/movespeed_modifier/shove - multiplicative_slowdown = SHOVE_SLOWDOWN_STRENGTH - -/datum/movespeed_modifier/borg_throw - multiplicative_slowdown = 0.9 +/datum/movespeed_modifier/staggered + multiplicative_slowdown = STAGGERED_SLOWDOWN_STRENGTH /datum/movespeed_modifier/human_carry multiplicative_slowdown = HUMAN_CARRY_SLOWDOWN @@ -112,6 +109,9 @@ /datum/movespeed_modifier/average_web multiplicative_slowdown = 1.2 +/datum/movespeed_modifier/below_average_web + multiplicative_slowdown = 2.5 + /datum/movespeed_modifier/slow_web multiplicative_slowdown = 5 diff --git a/code/modules/movespeed/modifiers/status_effects.dm b/code/modules/movespeed/modifiers/status_effects.dm index e8aad88c50d3c..4768f66a544f4 100644 --- a/code/modules/movespeed/modifiers/status_effects.dm +++ b/code/modules/movespeed/modifiers/status_effects.dm @@ -37,3 +37,22 @@ /datum/movespeed_modifier/status_effect/tired_post_charge multiplicative_slowdown = 3 + +/// Get slower the more gold is in your system. +/datum/movespeed_modifier/status_effect/midas_blight + id = MOVESPEED_ID_MIDAS_BLIGHT + +/datum/movespeed_modifier/status_effect/midas_blight/soft + multiplicative_slowdown = 0.25 + +/datum/movespeed_modifier/status_effect/midas_blight/medium + multiplicative_slowdown = 0.75 + +/datum/movespeed_modifier/status_effect/midas_blight/hard + multiplicative_slowdown = 1.5 + +/datum/movespeed_modifier/status_effect/midas_blight/gold + multiplicative_slowdown = 2 + +/datum/movespeed_modifier/status_effect/guardian_shield + multiplicative_slowdown = 1 diff --git a/code/modules/pai/camera.dm b/code/modules/pai/camera.dm index a091b208638f6..319f20e369990 100644 --- a/code/modules/pai/camera.dm +++ b/code/modules/pai/camera.dm @@ -1,10 +1,3 @@ -/mob/living/silicon/pai/ClickOn(atom/target, params) - . = ..() - if(aicamera && aicamera.in_camera_mode) - aicamera.toggle_camera_mode(sound = FALSE) - aicamera.captureimage(target, usr) - return TRUE - /obj/item/camera/siliconcam/pai_camera name = "pAI photo camera" light_color = COLOR_PAI_GREEN @@ -13,7 +6,7 @@ var/number = length(stored) picture.picture_name = "Image [number] (taken by [loc.name])" stored[picture] = TRUE - playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, TRUE, -3) + playsound(src, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, TRUE, -3) balloon_alert(user, "image recorded") /** diff --git a/code/modules/pai/card.dm b/code/modules/pai/card.dm index a652b745c9e50..da3bfe4e0ce14 100644 --- a/code/modules/pai/card.dm +++ b/code/modules/pai/card.dm @@ -26,7 +26,6 @@ if(!pai.encrypt_mod) to_chat(user, span_alert("Encryption Key ports not configured.")) return - user.set_machine(src) pai.radio.attackby(used, user, params) to_chat(user, span_notice("You insert [used] into the [src].")) return @@ -35,7 +34,6 @@ /obj/item/pai_card/attack_self(mob/user) if(!in_range(src, user)) return - user.set_machine(src) ui_interact(user) /obj/item/pai_card/Destroy() @@ -234,7 +232,16 @@ playsound(src, 'sound/machines/ping.ogg', 20, TRUE) balloon_alert(user, "pAI assistance requested") var/mutable_appearance/alert_overlay = mutable_appearance('icons/obj/aicards.dmi', "pai") - notify_ghosts("[user] is requesting a pAI companion! Use the pAI button to submit yourself as one.", source = user, alert_overlay = alert_overlay, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "pAI Request!", ignore_key = POLL_IGNORE_PAI) + + notify_ghosts( + "[user] is requesting a pAI companion! Use the pAI button to submit yourself as one.", + source = user, + header = "pAI Request!", + alert_overlay = alert_overlay, + notify_flags = NOTIFY_CATEGORY_NOFLASH, + ignore_key = POLL_IGNORE_PAI, + ) + addtimer(VARSET_CALLBACK(src, request_spam, FALSE), PAI_SPAM_TIME, TIMER_UNIQUE | TIMER_STOPPABLE | TIMER_CLIENT_TIME | TIMER_DELETE_ME) return TRUE diff --git a/code/modules/pai/debug.dm b/code/modules/pai/debug.dm index dde6fc4be058a..089dcedfabba5 100644 --- a/code/modules/pai/debug.dm +++ b/code/modules/pai/debug.dm @@ -28,7 +28,7 @@ card.set_personality(pai) if(SSpai.candidates[key]) SSpai.candidates -= key - SSblackbox.record_feedback("tally", "admin_verb", 1, "Make pAI") // If you are copy-pasting this, ensure the 4th parameter is unique to the new proc! + BLACKBOX_LOG_ADMIN_VERB("Make pAI") /** * Creates a new pAI. diff --git a/code/modules/pai/defense.dm b/code/modules/pai/defense.dm index 3a888fa9c83a1..61fadf820bfcf 100644 --- a/code/modules/pai/defense.dm +++ b/code/modules/pai/defense.dm @@ -56,11 +56,11 @@ if(user.put_in_hands(card)) user.visible_message(span_notice("[user] promptly scoops up [user.p_their()] pAI's card.")) -/mob/living/silicon/pai/bullet_act(obj/projectile/Proj) - if(Proj.stun) +/mob/living/silicon/pai/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) + . = ..() + if(. == BULLET_ACT_HIT && (hitting_projectile.stun || hitting_projectile.paralyze)) fold_in(force = TRUE) - src.visible_message(span_warning("The electrically-charged projectile disrupts [src]'s holomatrix, forcing [src] to fold in!")) - . = ..(Proj) + visible_message(span_warning("The electrically-charged projectile disrupts [src]'s holomatrix, forcing [p_them()] to fold in!")) /mob/living/silicon/pai/ignite_mob(silent) return FALSE @@ -73,17 +73,17 @@ to_chat(src, span_userdanger("The impact degrades your holochassis!")) return amount -/mob/living/silicon/pai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - return take_holo_damage(amount) - -/mob/living/silicon/pai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - return take_holo_damage(amount) +/// Called when we take burn or brute damage, pass it to the shell instead +/mob/living/silicon/pai/proc/on_shell_damaged(datum/hurt, type, amount, forced) + SIGNAL_HANDLER + take_holo_damage(amount) + return COMPONENT_IGNORE_CHANGE -/mob/living/silicon/pai/adjustStaminaLoss(amount, updating_stamina, forced = FALSE, required_biotype) - if(forced) - take_holo_damage(amount) - else - take_holo_damage(amount * 0.25) +/// Called when we take stamina damage, pass it to the shell instead +/mob/living/silicon/pai/proc/on_shell_weakened(datum/hurt, type, amount, forced) + SIGNAL_HANDLER + take_holo_damage(amount * ((forced) ? 1 : 0.25)) + return COMPONENT_IGNORE_CHANGE /mob/living/silicon/pai/getBruteLoss() return HOLOCHASSIS_MAX_HEALTH - holochassis_health diff --git a/code/modules/pai/hud.dm b/code/modules/pai/hud.dm index 530780758d249..1a71b5235b610 100644 --- a/code/modules/pai/hud.dm +++ b/code/modules/pai/hud.dm @@ -94,6 +94,7 @@ if(LAZYACCESS(modifiers, RIGHT_CLICK)) pAI.host_scan(PAI_SCAN_MASTER) return TRUE + /atom/movable/screen/pai/crew_manifest name = "Crew Manifest" icon_state = "manifest" @@ -147,7 +148,8 @@ required_software = "Photography Module" /atom/movable/screen/pai/image_take/Click() - if(!..()) + . = ..() + if(!.) return var/mob/living/silicon/pai/pAI = usr pAI.aicamera.toggle_camera_mode(usr) diff --git a/code/modules/pai/pai.dm b/code/modules/pai/pai.dm index 490e614b51f8b..d5403f28e6af0 100644 --- a/code/modules/pai/pai.dm +++ b/code/modules/pai/pai.dm @@ -16,7 +16,7 @@ light_flags = LIGHT_ATTACHED light_on = FALSE light_range = 3 - light_system = MOVABLE_LIGHT + light_system = OVERLAY_LIGHT maxHealth = 500 mob_size = MOB_SIZE_TINY mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT @@ -31,7 +31,7 @@ /// If someone has enabled/disabled the pAIs ability to holo var/can_holo = TRUE - /// Whether this pAI can recieve radio messages + /// Whether this pAI can receive radio messages var/can_receive = TRUE /// Whether this pAI can transmit radio messages var/can_transmit = TRUE @@ -71,8 +71,6 @@ // Onboard Items /// Atmospheric analyzer var/obj/item/analyzer/atmos_analyzer - /// Health analyzer - var/obj/item/healthanalyzer/host_scan /// GPS var/obj/item/gps/pai/internal_gps /// Music Synthesizer @@ -82,6 +80,9 @@ /// Remote signaler var/obj/item/assembly/signaler/internal/signaler + ///The messeenger ability that pAIs get when they are put in a PDA. + var/datum/action/innate/pai/messenger/messenger_ability + // Static lists /// List of all available downloads var/static/list/available_software = list( @@ -111,6 +112,7 @@ "crow" = TRUE, "duffel" = TRUE, "fox" = FALSE, + "frog" = TRUE, "hawk" = FALSE, "lizard" = FALSE, "monkey" = TRUE, @@ -150,9 +152,9 @@ return ..(target, action_bitflags) /mob/living/silicon/pai/Destroy() + QDEL_NULL(messenger_ability) QDEL_NULL(atmos_analyzer) QDEL_NULL(hacking_cable) - QDEL_NULL(host_scan) QDEL_NULL(instrument) QDEL_NULL(internal_gps) QDEL_NULL(newscaster) @@ -192,8 +194,6 @@ atmos_analyzer = null else if(gone == aicamera) aicamera = null - else if(gone == host_scan) - host_scan = null else if(gone == internal_gps) internal_gps = null else if(gone == instrument) @@ -215,6 +215,8 @@ /mob/living/silicon/pai/Initialize(mapload) . = ..() + if(istype(loc, /obj/item/modular_computer)) + give_messenger_ability() START_PROCESSING(SSfastprocess, src) GLOB.pai_list += src make_laws() @@ -234,6 +236,8 @@ update_appearance(UPDATE_DESC) RegisterSignal(src, COMSIG_LIVING_CULT_SACRIFICED, PROC_REF(on_cult_sacrificed)) + RegisterSignals(src, list(COMSIG_LIVING_ADJUST_BRUTE_DAMAGE, COMSIG_LIVING_ADJUST_BURN_DAMAGE), PROC_REF(on_shell_damaged)) + RegisterSignal(src, COMSIG_LIVING_ADJUST_STAMINA_DAMAGE, PROC_REF(on_shell_weakened)) /mob/living/silicon/pai/make_laws() laws = new /datum/ai_laws/pai() @@ -269,6 +273,15 @@ held_state = "[chassis]" return ..() +/mob/living/silicon/pai/set_stat(new_stat) + . = ..() + update_stat() + +/mob/living/silicon/pai/on_knockedout_trait_loss(datum/source) + . = ..() + set_stat(CONSCIOUS) + update_stat() + /** * Resolves the weakref of the pai's master. * If the master has been deleted, calls reset_software(). @@ -458,3 +471,14 @@ if (new_distance < HOLOFORM_MIN_RANGE || new_distance > HOLOFORM_MAX_RANGE) return leash.set_distance(new_distance) + +///Gives the messenger ability to the pAI, creating a new one if it doesn't have one already. +/mob/living/silicon/pai/proc/give_messenger_ability() + if(!messenger_ability) + messenger_ability = new(src) + messenger_ability.Grant(src) + +///Removes the messenger ability from the pAI, but does not delete it. +/mob/living/silicon/pai/proc/remove_messenger_ability() + if(messenger_ability) + messenger_ability.Remove(src) diff --git a/code/modules/pai/software.dm b/code/modules/pai/software.dm index 103056a5535b3..9876df5a2646a 100644 --- a/code/modules/pai/software.dm +++ b/code/modules/pai/software.dm @@ -38,7 +38,7 @@ return TRUE // Software related ui actions if(available_software[action] && !installed_software.Find(action)) - balloon_alert(usr, "software unavailable") + balloon_alert(ui.user, "software unavailable!") return FALSE switch(action) if("Atmospheric Sensor") @@ -116,8 +116,6 @@ atmos_analyzer = new(src) if("Digital Messenger") create_modularInterface() - if("Host Scan") - host_scan = new(src) if("Internal GPS") internal_gps = new(src) if("Music Synthesizer") @@ -193,28 +191,27 @@ * @returns {boolean} - TRUE if the scan was successful, FALSE otherwise. */ /mob/living/silicon/pai/proc/host_scan(mode) - if(isnull(mode)) - return FALSE - if(mode == PAI_SCAN_TARGET) - var/mob/living/target = get_holder() - if(!target || !isliving(target)) - balloon_alert(src, "not being carried") - return FALSE - host_scan.attack(target, src) - return TRUE - if(mode == PAI_SCAN_MASTER) - if(!master_ref) - balloon_alert(src, "no master detected") - return FALSE - var/mob/living/resolved_master = find_master() - if(!resolved_master) - balloon_alert(src, "cannot locate master") - return FALSE - if(!is_valid_z_level(get_turf(src), get_turf(resolved_master))) - balloon_alert(src, "master out of range") - return FALSE - host_scan.attack(resolved_master, src) - return TRUE + switch(mode) + if(PAI_SCAN_TARGET) + var/mob/living/target = get_holder() + if(!isliving(target)) + balloon_alert(src, "not being carried!") + return FALSE + healthscan(src, target) + return TRUE + + if(PAI_SCAN_MASTER) + var/mob/living/resolved_master = find_master() + if(isnull(resolved_master)) + balloon_alert(src, "no master detected!") + return FALSE + if(!is_valid_z_level(get_turf(src), get_turf(resolved_master))) + balloon_alert(src, "master out of range!") + return FALSE + healthscan(src, resolved_master) + return TRUE + + stack_trace("Invalid mode passed to host scan: [mode || "null"]") return FALSE /** diff --git a/code/modules/paperwork/desk_bell.dm b/code/modules/paperwork/desk_bell.dm index fda6b21295269..e193bbc98b102 100644 --- a/code/modules/paperwork/desk_bell.dm +++ b/code/modules/paperwork/desk_bell.dm @@ -69,7 +69,7 @@ playsound(user, 'sound/items/change_drill.ogg', 50, vary = TRUE) broken_ringer = FALSE times_rang = 0 - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS return FALSE return ..() @@ -84,7 +84,7 @@ new/obj/item/stack/sheet/iron(drop_location()) new/obj/item/stack/sheet/iron(drop_location()) qdel(src) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS return ..() /// Check if the clapper breaks, and if it does, break it diff --git a/code/modules/paperwork/fax.dm b/code/modules/paperwork/fax.dm index 055ac8bba4e6a..f9eafa901aa51 100644 --- a/code/modules/paperwork/fax.dm +++ b/code/modules/paperwork/fax.dm @@ -6,6 +6,7 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department icon = 'icons/obj/machines/fax.dmi' icon_state = "fax" density = TRUE + anchored_tabletop_offset = 6 power_channel = AREA_USAGE_EQUIP max_integrity = 100 pass_flags = PASSTABLE @@ -119,7 +120,7 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department /obj/machinery/fax/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /** * Open and close the wire panel. @@ -138,16 +139,16 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department return var/new_fax_name = tgui_input_text(user, "Enter a new name for the fax machine.", "New Fax Name", , 128) if (!new_fax_name) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS if (new_fax_name != fax_name) if (fax_name_exist(new_fax_name)) // Being able to set the same name as another fax machine will give a lot of gimmicks for the traitor. if (syndicate_network != TRUE && !(obj_flags & EMAGGED)) to_chat(user, span_warning("There is already a fax machine with this name on the network.")) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS user.log_message("renamed [fax_name] (fax machine) to [new_fax_name].", LOG_GAME) fax_name = new_fax_name - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/fax/attackby(obj/item/item, mob/user, params) if (jammed && clear_jam(item, user)) @@ -173,7 +174,7 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department var/obj/item/reagent_containers/spray/clean_spray = item if(!clean_spray.reagents.has_reagent(/datum/reagent/space_cleaner, clean_spray.amount_per_transfer_from_this)) return FALSE - clean_spray.reagents.remove_reagent(/datum/reagent/space_cleaner, clean_spray.amount_per_transfer_from_this, 1) + clean_spray.reagents.remove_reagent(/datum/reagent/space_cleaner, clean_spray.amount_per_transfer_from_this) playsound(loc, 'sound/effects/spray3.ogg', 50, TRUE, MEDIUM_RANGE_SOUND_EXTRARANGE) user.visible_message(span_notice("[user] cleans \the [src]."), span_notice("You clean \the [src].")) jammed = FALSE @@ -536,11 +537,12 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department target_fax.receive(fax_item, sender) else if(force) //no fax machines but we really gotte send? SEND A FAX MACHINE - var/obj/machinery/fax/new_fax_machine = new () - send_supply_pod_to_area(new_fax_machine, area_type, force_pod_type) + var/obj/machinery/fax/new_fax_machine = new() + if(!send_supply_pod_to_area(new_fax_machine, area_type, force_pod_type)) + stack_trace("Attempted to forcibly send a fax to [area_type], however the area does not exist or has no valid dropoff spot for a fax machine") + return FALSE addtimer(CALLBACK(new_fax_machine, TYPE_PROC_REF(/obj/machinery/fax, receive), fax_item, sender), 10 SECONDS) else return FALSE return TRUE - diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm index cb6aae768fa10..140bdffcf8767 100644 --- a/code/modules/paperwork/filingcabinet.dm +++ b/code/modules/paperwork/filingcabinet.dm @@ -38,7 +38,7 @@ I.forceMove(src) /obj/structure/filingcabinet/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) new /obj/item/stack/sheet/iron(loc, 2) for(var/obj/item/I in src) I.forceMove(loc) @@ -193,9 +193,9 @@ GLOBAL_LIST_EMPTY(employmentCabinets) /obj/structure/filingcabinet/employment/proc/fillCurrent() //This proc fills the cabinet with the current crew. for(var/datum/record/locked/target in GLOB.manifest.locked) - var/datum/mind/mind_ref = target.mind_ref - if(mind_ref && ishuman(mind_ref.current)) - addFile(mind_ref.current) + var/datum/mind/filed_mind = target.mind_ref.resolve() + if(filed_mind && ishuman(filed_mind.current)) + addFile(filed_mind.current) /obj/structure/filingcabinet/employment/proc/addFile(mob/living/carbon/human/employee) new /obj/item/paper/employment_contract(src, employee.mind.name) diff --git a/code/modules/paperwork/paper_cutter.dm b/code/modules/paperwork/paper_cutter.dm index 9586ec6e86184..9878249a6d12d 100644 --- a/code/modules/paperwork/paper_cutter.dm +++ b/code/modules/paperwork/paper_cutter.dm @@ -109,7 +109,7 @@ tool.play_tool_sound(src) balloon_alert(user, "[blade_secured ? "un" : ""]secured") blade_secured = !blade_secured - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/item/papercutter/attackby(obj/item/inserted_item, mob/user, params) if(istype(inserted_item, /obj/item/paper)) diff --git a/code/modules/paperwork/paper_premade.dm b/code/modules/paperwork/paper_premade.dm index bb38a27d38d75..b72ce806dd3a7 100644 --- a/code/modules/paperwork/paper_premade.dm +++ b/code/modules/paperwork/paper_premade.dm @@ -72,6 +72,19 @@
Keep this manual for your records, failure to do so will void your 2 day limited liability warranty from Nanotrasen."} +/obj/item/paper/fluff/jobs/engineering/frequencies + name = "Station Frequencies" + default_raw_text = {"Please remember the frequencies of each radio channel used on station: + * AI Private - 144.7 + * Command - 135.3 + * Common - 145.9 + * Engineering - 135.7 + * Medical - 135.5 + * Science - 135.1 + * Security - 135.9 + * Service - 134.9 + * Supply - 134.7"} + /obj/item/paper/fluff/jobs/security/beepsky_mom name = "Note from Beepsky's Mom" default_raw_text = "01001001 00100000 01101000 01101111 01110000 01100101 00100000 01111001 01101111 01110101 00100000 01110011 01110100 01100001 01111001 00100000 01110011 01100001 01100110 01100101 00101110 00100000 01001100 01101111 01110110 01100101 00101100 00100000 01101101 01101111 01101101 00101110" @@ -154,8 +167,8 @@ /////////// Lavaland /obj/item/paper/fluff/stations/lavaland/orm_notice - name = "URGENT!" - default_raw_text = "A hastily written note has been scribbled here...

Please use the ore redemption machine in the cargo office for smelting. PLEASE!

--The Research Staff" + name = "URGENT! RENOVATIONS!" + default_raw_text = "A hastily written note has been scribbled here...

Please use the ore redemption machine smelter and refinery in the cargo office for smelting. PLEASE! Leave boulders alone for the BRM to pick up!

--The Research Staff" /////////// Space Ruins diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm index 318fca21f5773..9971c0da7f7bb 100644 --- a/code/modules/paperwork/paperbin.dm +++ b/code/modules/paperwork/paperbin.dm @@ -54,12 +54,26 @@ droppoint = drop_location() if(collapse) visible_message(span_warning("The stack of paper collapses!")) - for(var/atom/movable/movable_atom in contents) - movable_atom.forceMove(droppoint) - if(!movable_atom.pixel_y) - movable_atom.pixel_y = rand(-3,3) - if(!movable_atom.pixel_x) - movable_atom.pixel_x = rand(-3,3) + for(var/obj/item/paper/stacked_paper in paper_stack) //first, dump all of the paper that already exists + stacked_paper.forceMove(droppoint) + if(!stacked_paper.pixel_y) + stacked_paper.pixel_y = rand(-3,3) + if(!stacked_paper.pixel_x) + stacked_paper.pixel_x = rand(-3,3) + paper_stack -= stacked_paper + total_paper -= 1 + for(var/i in 1 to total_paper) //second, generate new paper for the remainder + var/obj/item/paper/new_paper = generate_paper() + new_paper.forceMove(droppoint) + if(!new_paper.pixel_y) + new_paper.pixel_y = rand(-3,3) + if(!new_paper.pixel_x) + new_paper.pixel_x = rand(-3,3) + if(bin_pen) + var/obj/item/pen/pen = bin_pen + pen.forceMove(droppoint) + bin_pen = null + total_paper = 0 update_appearance() /obj/item/paper_bin/fire_act(exposed_temperature, exposed_volume) @@ -212,6 +226,8 @@ /obj/item/paper_bin/bundlenatural/dump_contents(atom/droppoint) . = ..() + binding_cable.forceMove(droppoint) + binding_cable = null qdel(src) /obj/item/paper_bin/bundlenatural/update_overlays() @@ -225,7 +241,7 @@ deconstruct(FALSE) /obj/item/paper_bin/bundlenatural/deconstruct(disassembled) - dump_contents() + dump_contents(drop_location()) return ..() /obj/item/paper_bin/bundlenatural/fire_act(exposed_temperature, exposed_volume) diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index 08d34dca863da..d3688ff2156ac 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -88,7 +88,7 @@ return ..() -/obj/item/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin=FALSE, diagonals_first = FALSE, datum/callback/callback, quickstart = TRUE) +/obj/item/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin=FALSE, diagonals_first = FALSE, datum/callback/callback, gentle, quickstart = TRUE) . = ..(target, range, speed, thrower, FALSE, diagonals_first, callback, quickstart = quickstart) /obj/item/paperplane/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 05606ac3a2ef1..10a15e88196cc 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -31,6 +31,38 @@ var/requires_gravity = TRUE // can you use this to write in zero-g embedding = list(embed_chance = 50) sharpness = SHARP_POINTY + var/dart_insert_icon = 'icons/obj/weapons/guns/toy.dmi' + var/dart_insert_casing_icon_state = "overlay_pen" + var/dart_insert_projectile_icon_state = "overlay_pen_proj" + +/obj/item/pen/Initialize(mapload) + . = ..() + AddComponent(/datum/component/dart_insert, \ + dart_insert_icon, \ + dart_insert_casing_icon_state, \ + dart_insert_icon, \ + dart_insert_projectile_icon_state, \ + CALLBACK(src, PROC_REF(get_dart_var_modifiers))\ + ) + RegisterSignal(src, COMSIG_DART_INSERT_ADDED, PROC_REF(on_inserted_into_dart)) + RegisterSignal(src, COMSIG_DART_INSERT_REMOVED, PROC_REF(on_removed_from_dart)) + +/obj/item/pen/proc/on_inserted_into_dart(datum/source, obj/projectile/dart, mob/user, embedded = FALSE) + SIGNAL_HANDLER + +/obj/item/pen/proc/get_dart_var_modifiers() + return list( + "damage" = max(5, throwforce), + "speed" = max(0, throw_speed - 3), + "embedding" = embedding, + "armour_penetration" = armour_penetration, + "wound_bonus" = wound_bonus, + "bare_wound_bonus" = bare_wound_bonus, + "demolition_mod" = demolition_mod, + ) + +/obj/item/pen/proc/on_removed_from_dart(datum/source, obj/projectile/dart, mob/user) + SIGNAL_HANDLER /obj/item/pen/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is scribbling numbers all over [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit sudoku...")) @@ -69,7 +101,7 @@ if("#FF0000") colour = "#00FF00" chosen_color = "green" - throw_speed = initial(throw_speed) + throw_speed-- if("#00FF00") colour = "#0000FF" chosen_color = "blue" @@ -84,6 +116,8 @@ icon_state = "pen-fountain" font = FOUNTAIN_PEN_FONT requires_gravity = FALSE // fancy spess pens + dart_insert_casing_icon_state = "overlay_fountainpen" + dart_insert_projectile_icon_state = "overlay_fountainpen_proj" /obj/item/pen/charcoal name = "charcoal stylus" @@ -113,13 +147,23 @@ custom_materials = list(/datum/material/gold = SMALL_MATERIAL_AMOUNT*7.5) sharpness = SHARP_EDGED resistance_flags = FIRE_PROOF - unique_reskin = list("Oak" = "pen-fountain-o", - "Gold" = "pen-fountain-g", - "Rosewood" = "pen-fountain-r", - "Black and Silver" = "pen-fountain-b", - "Command Blue" = "pen-fountain-cb" - ) + unique_reskin = list( + "Oak" = "pen-fountain-o", + "Gold" = "pen-fountain-g", + "Rosewood" = "pen-fountain-r", + "Black and Silver" = "pen-fountain-b", + "Command Blue" = "pen-fountain-cb" + ) embedding = list("embed_chance" = 75) + dart_insert_casing_icon_state = "overlay_fountainpen_gold" + dart_insert_projectile_icon_state = "overlay_fountainpen_gold_proj" + var/list/overlay_reskin = list( + "Oak" = "overlay_fountainpen_gold", + "Gold" = "overlay_fountainpen_gold", + "Rosewood" = "overlay_fountainpen_gold", + "Black and Silver" = "overlay_fountainpen", + "Command Blue" = "overlay_fountainpen_gold" + ) /obj/item/pen/fountain/captain/Initialize(mapload) . = ..() @@ -128,12 +172,19 @@ effectiveness = 115, \ ) //the pen is mightier than the sword + RegisterSignal(src, COMSIG_DART_INSERT_PARENT_RESKINNED, PROC_REF(reskin_dart_insert)) /obj/item/pen/fountain/captain/reskin_obj(mob/M) ..() if(current_skin) desc = "It's an expensive [current_skin] fountain pen. The nib is quite sharp." +/obj/item/pen/fountain/captain/proc/reskin_dart_insert(datum/component/dart_insert/insert_comp) + if(!istype(insert_comp)) //You really shouldn't be sending this signal from anything other than a dart_insert component + return + insert_comp.casing_overlay_icon_state = overlay_reskin[current_skin] + insert_comp.projectile_overlay_icon_state = "[overlay_reskin[current_skin]]_proj" + /obj/item/pen/attack_self(mob/living/carbon/user) . = ..() if(.) @@ -185,7 +236,7 @@ label.remove_label() label.apply_label() to_chat(user, span_notice("You have successfully renamed \the [oldname] to [O].")) - O.renamedByPlayer = TRUE + ADD_TRAIT(O, TRAIT_WAS_RENAMED, PEN_LABEL_TRAIT) O.update_appearance(UPDATE_ICON) if(penchoice == "Description") @@ -198,7 +249,7 @@ else O.AddComponent(/datum/component/rename, O.name, input) to_chat(user, span_notice("You have successfully changed [O]'s description.")) - O.renamedByPlayer = TRUE + ADD_TRAIT(O, TRAIT_WAS_RENAMED, PEN_LABEL_TRAIT) O.update_appearance(UPDATE_ICON) if(penchoice == "Reset") @@ -214,7 +265,7 @@ label.apply_label() to_chat(user, span_notice("You have successfully reset [O]'s name and description.")) - O.renamedByPlayer = FALSE + REMOVE_TRAIT(O, TRAIT_WAS_RENAMED, PEN_LABEL_TRAIT) O.update_appearance(UPDATE_ICON) /obj/item/pen/get_writing_implement_details() @@ -247,6 +298,23 @@ reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 15) reagents.add_reagent(/datum/reagent/toxin/staminatoxin, 10) +/obj/item/pen/sleepy/on_inserted_into_dart(datum/source, obj/item/ammo_casing/dart, mob/user) + . = ..() + var/obj/projectile/proj = dart.loaded_projectile + RegisterSignal(proj, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_dart_hit)) + +/obj/item/pen/sleepy/on_removed_from_dart(datum/source, obj/item/ammo_casing/dart, obj/projectile/proj, mob/user) + . = ..() + if(istype(proj)) + UnregisterSignal(proj, COMSIG_PROJECTILE_SELF_ON_HIT) + +/obj/item/pen/sleepy/proc/on_dart_hit(datum/source, atom/movable/firer, atom/target, angle, hit_limb, blocked) + SIGNAL_HANDLER + var/mob/living/carbon/carbon_target = target + if(!istype(carbon_target) || blocked == 100) + return + if(carbon_target.can_inject(target_zone = hit_limb)) + reagents.trans_to(carbon_target, reagents.total_volume, transferred_by = firer, methods = INJECT) /* * (Alan) Edaggers */ @@ -257,11 +325,12 @@ armour_penetration = 20 bare_wound_bonus = 10 item_flags = NO_BLOOD_ON_ITEM - light_system = MOVABLE_LIGHT + light_system = OVERLAY_LIGHT light_range = 1.5 light_power = 0.75 light_color = COLOR_SOFT_RED light_on = FALSE + dart_insert_projectile_icon_state = "overlay_edagger" /// The real name of our item when extended. var/hidden_name = "energy dagger" /// The real desc of our item when extended. @@ -287,6 +356,62 @@ RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform)) RegisterSignal(src, COMSIG_DETECTIVE_SCANNED, PROC_REF(on_scan)) +/obj/item/pen/edagger/on_inserted_into_dart(datum/source, obj/item/ammo_casing/dart, mob/user) + . = ..() + var/datum/component/transforming/transform_comp = GetComponent(/datum/component/transforming) + if(HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) + transform_comp.do_transform(src, user) + RegisterSignal(dart.loaded_projectile, COMSIG_PROJECTILE_FIRE, PROC_REF(on_containing_dart_fired)) + RegisterSignal(dart.loaded_projectile, COMSIG_PROJECTILE_ON_SPAWN_DROP, PROC_REF(on_containing_dart_drop)) + RegisterSignal(dart.loaded_projectile, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED, PROC_REF(on_containing_dart_embedded)) + +/obj/item/pen/edagger/on_removed_from_dart(datum/source, obj/item/ammo_casing/dart, obj/projectile/projectile, mob/user) + . = ..() + if(istype(dart)) + UnregisterSignal(dart, list(COMSIG_ITEM_UNEMBEDDED, COMSIG_ITEM_FAILED_EMBED)) + if(istype(projectile)) + UnregisterSignal(projectile, list(COMSIG_PROJECTILE_FIRE, COMSIG_PROJECTILE_ON_SPAWN_DROP, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED)) + +/obj/item/pen/edagger/get_dart_var_modifiers() + . = ..() + var/datum/component/transforming/transform_comp = GetComponent(/datum/component/transforming) + .["damage"] = max(5, transform_comp.throwforce_on) + .["speed"] = max(0, transform_comp.throw_speed_on - 3) + var/list/embed_params = .["embedding"] + embed_params["embed_chance"] = 100 + +/obj/item/pen/edagger/proc/on_containing_dart_fired(obj/projectile/source) + SIGNAL_HANDLER + playsound(source, 'sound/weapons/saberon.ogg', 5, TRUE) + var/datum/component/transforming/transform_comp = GetComponent(/datum/component/transforming) + source.hitsound = transform_comp.hitsound_on + source.set_light(light_range, light_power, light_color, l_on = TRUE) + +/obj/item/pen/edagger/proc/on_containing_dart_drop(datum/source, obj/item/ammo_casing/new_casing) + SIGNAL_HANDLER + playsound(new_casing, 'sound/weapons/saberoff.ogg', 5, TRUE) + +/obj/item/pen/edagger/proc/on_containing_dart_embedded(datum/source, obj/item/ammo_casing/new_casing) + SIGNAL_HANDLER + RegisterSignal(new_casing, COMSIG_ITEM_UNEMBEDDED, PROC_REF(on_embedded_removed)) + RegisterSignal(new_casing, COMSIG_ITEM_FAILED_EMBED, PROC_REF(on_containing_dart_failed_embed)) + +/obj/item/pen/edagger/proc/on_containing_dart_failed_embed(obj/item/ammo_casing/source) + SIGNAL_HANDLER + playsound(source, 'sound/weapons/saberoff.ogg', 5, TRUE) + UnregisterSignal(source, list(COMSIG_ITEM_UNEMBEDDED, COMSIG_ITEM_FAILED_EMBED)) + +/obj/item/pen/edagger/proc/on_embedded_removed(obj/item/ammo_casing/source, mob/living/carbon/victim) + SIGNAL_HANDLER + playsound(source, 'sound/weapons/saberoff.ogg', 5, TRUE) + UnregisterSignal(source, list(COMSIG_ITEM_UNEMBEDDED, COMSIG_ITEM_FAILED_EMBED)) + victim.visible_message( + message = span_warning("The blade of the [hidden_name] retracts as the [source.name] is removed from [victim]!"), + self_message = span_warning("The blade of the [hidden_name] retracts as the [source.name] is removed from you!"), + blind_message = span_warning("You hear an energy blade retract!"), + vision_distance = 1 + ) + /obj/item/pen/edagger/suicide_act(mob/living/user) if(HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) user.visible_message(span_suicide("[user] forcefully rams the pen into their mouth!")) @@ -348,6 +473,25 @@ toolspeed = 10 //You will never willingly choose to use one of these over a shovel. font = FOUNTAIN_PEN_FONT colour = "#0000FF" + dart_insert_casing_icon_state = "overlay_survivalpen" + dart_insert_projectile_icon_state = "overlay_survivalpen_proj" + +/obj/item/pen/survival/on_inserted_into_dart(datum/source, obj/item/ammo_casing/dart, mob/user) + . = ..() + RegisterSignal(dart.loaded_projectile, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_dart_hit)) + +/obj/item/pen/survival/on_removed_from_dart(datum/source, obj/item/ammo_casing/dart, obj/projectile/proj, mob/user) + . = ..() + if(istype(proj)) + UnregisterSignal(proj, COMSIG_PROJECTILE_SELF_ON_HIT) + +/obj/item/pen/survival/proc/on_dart_hit(obj/projectile/source, atom/movable/firer, atom/target) + var/turf/target_turf = get_turf(target) + if(!target_turf) + target_turf = get_turf(src) + if(ismineralturf(target_turf)) + var/turf/closed/mineral/mineral_turf = target_turf + mineral_turf.gets_drilled(firer, TRUE) /obj/item/pen/destroyer name = "Fine Tipped Pen" @@ -362,6 +506,7 @@ desc = "A pen with an extendable screwdriver tip. This one has a yellow cap." icon_state = "pendriver" toolspeed = 1.2 // gotta have some downside + dart_insert_projectile_icon_state = "overlay_pendriver" /obj/item/pen/screwdriver/get_all_tool_behaviours() return list(TOOL_SCREWDRIVER) @@ -400,3 +545,44 @@ . = ..() icon_state = "[initial(icon_state)][HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE) ? "_out" : null]" inhand_icon_state = initial(inhand_icon_state) //since transforming component switches the icon. + +//The Security holopen +/obj/item/pen/red/security + name = "security pen" + desc = "This is a red ink pen exclusively provided to members of the Security Department. Its opposite end features a built-in holographic projector designed for issuing arrest prompts to individuals." + icon_state = "pen_sec" + COOLDOWN_DECLARE(holosign_cooldown) + +/obj/item/pen/red/security/examine(mob/user) + . = ..() + . += span_notice("To initiate the surrender prompt, simply click on an individual within your proximity.") + +//Code from the medical penlight +/obj/item/pen/red/security/afterattack(atom/target, mob/living/user, proximity) + . = ..() + if(!COOLDOWN_FINISHED(src, holosign_cooldown)) + balloon_alert(user, "not ready!") + return + + var/target_turf = get_turf(target) + var/mob/living/living_target = locate(/mob/living) in target_turf + + if(!living_target || (living_target == user)) + return + + living_target.apply_status_effect(/datum/status_effect/surrender_timed) + to_chat(living_target, span_userdanger("[user] requests your immediate surrender! You are given 30 seconds to comply!")) + new /obj/effect/temp_visual/security_holosign(target_turf, user) //produce a holographic glow + COOLDOWN_START(src, holosign_cooldown, 30 SECONDS) + +/obj/effect/temp_visual/security_holosign + name = "security holosign" + desc = "A small holographic glow that indicates you're under arrest." + icon_state = "sec_holo" + duration = 60 + +/obj/effect/temp_visual/security_holosign/Initialize(mapload, creator) + . = ..() + playsound(loc, 'sound/machines/chime.ogg', 50, FALSE) //make some noise! + if(creator) + visible_message(span_danger("[creator] created a security hologram!")) diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm index 0f122c104eb81..a6cb83aafc0e1 100644 --- a/code/modules/paperwork/photocopier.dm +++ b/code/modules/paperwork/photocopier.dm @@ -91,6 +91,7 @@ GLOBAL_LIST_INIT(paper_blanks, init_paper_blanks()) . = ..() toner_cartridge = new(src) setup_components() + AddElement(/datum/element/elevation, pixel_shift = 8) //enough to look like your bums are on the machine. /// Simply adds the necessary components for this to function. /obj/machinery/photocopier/proc/setup_components() @@ -282,6 +283,9 @@ GLOBAL_LIST_INIT(paper_blanks, init_paper_blanks()) /// Will invoke `do_copy_loop` asynchronously. Passes the supplied arguments on to it. /obj/machinery/photocopier/proc/do_copies(datum/callback/copy_cb, mob/user, paper_use, toner_use, copies_amount) + if(machine_stat & (BROKEN|NOPOWER)) + return + busy = TRUE update_use_power(ACTIVE_POWER_USE) // fucking god proc @@ -535,7 +539,7 @@ GLOBAL_LIST_INIT(paper_blanks, init_paper_blanks()) /obj/machinery/photocopier/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/photocopier/attackby(obj/item/object, mob/user, params) if(istype(object, /obj/item/paper) || istype(object, /obj/item/photo) || istype(object, /obj/item/documents)) @@ -673,7 +677,7 @@ GLOBAL_LIST_INIT(paper_blanks, init_paper_blanks()) /obj/item/toner name = "toner cartridge" desc = "A small, lightweight cartridge of Nanotrasen ValueBrand toner. Fits photocopiers and autopainters alike." - icon = 'icons/obj/device.dmi' + icon = 'icons/obj/service/bureaucracy.dmi' icon_state = "tonercartridge" grind_results = list(/datum/reagent/iodine = 40, /datum/reagent/iron = 10) var/charges = 5 diff --git a/code/modules/paperwork/ticketmachine.dm b/code/modules/paperwork/ticketmachine.dm index a5902a9df5a20..56b7343995a2c 100644 --- a/code/modules/paperwork/ticketmachine.dm +++ b/code/modules/paperwork/ticketmachine.dm @@ -39,10 +39,8 @@ tickets.Cut() return ..() -/obj/machinery/ticket_machine/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/wallframe/ticket_machine(loc) - qdel(src) +/obj/machinery/ticket_machine/on_deconstruction(disassembled = TRUE) + new /obj/item/wallframe/ticket_machine(loc) MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/ticket_machine, 32) diff --git a/code/modules/photography/_pictures.dm b/code/modules/photography/_pictures.dm index 8c949892dbf44..45fa5654ad922 100644 --- a/code/modules/photography/_pictures.dm +++ b/code/modules/photography/_pictures.dm @@ -5,6 +5,8 @@ var/list/mobs_seen = list() /// List of weakrefs pointing at dead mobs that appear in this photo var/list/dead_seen = list() + /// List of strings of face-visible humans in this photo + var/list/names_seen = list() var/caption var/icon/picture_image var/icon/picture_icon @@ -16,7 +18,7 @@ ///Was this image capable of seeing ghosts? var/see_ghosts = CAMERA_NO_GHOSTS -/datum/picture/New(name, desc, mobs_spotted, dead_spotted, image, icon, size_x, size_y, bp, caption_, autogenerate_icon, can_see_ghosts) +/datum/picture/New(name, desc, mobs_spotted, dead_spotted, names, image, icon, size_x, size_y, bp, caption_, autogenerate_icon, can_see_ghosts) if(!isnull(name)) picture_name = name if(!isnull(desc)) @@ -27,6 +29,9 @@ if(!isnull(dead_spotted)) for(var/mob/seen as anything in dead_spotted) dead_seen += WEAKREF(seen) + if(!isnull(names)) + for(var/seen in names) + names_seen += seen if(!isnull(image)) picture_image = image if(!isnull(icon)) diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm index 1c0e360ed7585..3f721c1cefc3b 100644 --- a/code/modules/photography/camera/camera.dm +++ b/code/modules/photography/camera/camera.dm @@ -10,13 +10,13 @@ worn_icon_state = "camera" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - light_system = MOVABLE_LIGHT //Used as a flash here. + light_system = OVERLAY_LIGHT //Used as a flash here. light_range = 8 light_color = COLOR_WHITE light_power = FLASH_LIGHT_POWER light_on = FALSE w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_NECK custom_materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*0.5, /datum/material/glass = SMALL_MATERIAL_AMOUNT*1.5) custom_price = PAYCHECK_CREW * 2 @@ -121,6 +121,9 @@ return FALSE else if(!(get_turf(target) in get_hear(world.view, user))) return FALSE + else if(isliving(loc)) + if(!(get_turf(target) in view(world.view, loc))) + return FALSE else //user is an atom or null if(!(get_turf(target) in view(world.view, user || src))) return FALSE @@ -188,6 +191,8 @@ var/list/mobs = list() var/blueprints = FALSE var/clone_area = SSmapping.request_turf_block_reservation(size_x * 2 + 1, size_y * 2 + 1, 1) + ///list of human names taken on picture + var/list/names = list() var/width = size_x * 2 + 1 var/height = size_y * 2 + 1 @@ -218,8 +223,11 @@ var/icon/get_icon = camera_get_icon(turfs, target_turf, psize_x, psize_y, clone_area, size_x, size_y, (size_x * 2 + 1), (size_y * 2 + 1)) qdel(clone_area) get_icon.Blend("#000", ICON_UNDERLAY) + for(var/mob/living/carbon/human/person in mobs) + if(person.is_face_visible()) + names += "[person.name]" - var/datum/picture/picture = new("picture", desc.Join(" "), mobs_spotted, dead_spotted, get_icon, null, psize_x, psize_y, blueprints, can_see_ghosts = see_ghosts) + var/datum/picture/picture = new("picture", desc.Join(" "), mobs_spotted, dead_spotted, names, get_icon, null, psize_x, psize_y, blueprints, can_see_ghosts = see_ghosts) after_picture(user, picture) SEND_SIGNAL(src, COMSIG_CAMERA_IMAGE_CAPTURED, target, user) blending = FALSE @@ -236,27 +244,30 @@ printpicture(user, picture) /obj/item/camera/proc/printpicture(mob/user, datum/picture/picture) //Normal camera proc for creating photos - if(!user) - return pictures_left-- var/obj/item/photo/new_photo = new(get_turf(src), picture) - if(in_range(new_photo, user) && user.put_in_hands(new_photo)) //needed because of TK - to_chat(user, span_notice("[pictures_left] photos left.")) - - if(can_customise) - var/customise = tgui_alert(user, "Do you want to customize the photo?", "Customization", list("Yes", "No")) - if(customise == "Yes") - var/name1 = tgui_input_text(user, "Set a name for this photo, or leave blank.", "Name", max_length = 32) - var/desc1 = tgui_input_text(user, "Set a description to add to photo, or leave blank.", "Description", max_length = 128) - var/caption = tgui_input_text(user, "Set a caption for this photo, or leave blank.", "Caption", max_length = 256) - if(name1) - picture.picture_name = name1 - if(desc1) - picture.picture_desc = "[desc1] - [picture.picture_desc]" - if(caption) - picture.caption = caption - else if(default_picture_name) - picture.picture_name = default_picture_name + if(user) + if(in_range(new_photo, user) && user.put_in_hands(new_photo)) //needed because of TK + to_chat(user, span_notice("[pictures_left] photos left.")) + + if(can_customise) + var/customise = tgui_alert(user, "Do you want to customize the photo?", "Customization", list("Yes", "No")) + if(customise == "Yes") + var/name1 = tgui_input_text(user, "Set a name for this photo, or leave blank.", "Name", max_length = 32) + var/desc1 = tgui_input_text(user, "Set a description to add to photo, or leave blank.", "Description", max_length = 128) + var/caption = tgui_input_text(user, "Set a caption for this photo, or leave blank.", "Caption", max_length = 256) + if(name1) + picture.picture_name = name1 + if(desc1) + picture.picture_desc = "[desc1] - [picture.picture_desc]" + if(caption) + picture.caption = caption + else if(default_picture_name) + picture.picture_name = default_picture_name + else if(isliving(loc)) + var/mob/living/holder = loc + if(holder.put_in_hands(new_photo)) + to_chat(holder, span_notice("[pictures_left] photos left.")) new_photo.set_picture(picture, TRUE, TRUE) if(CONFIG_GET(flag/picture_logging_camera)) @@ -322,6 +333,6 @@ return if(!camera.can_target(target)) return - INVOKE_ASYNC(camera, TYPE_PROC_REF(/obj/item/camera, captureimage), target, null, camera.picture_size_y - 1, camera.picture_size_y - 1) + INVOKE_ASYNC(camera, TYPE_PROC_REF(/obj/item/camera, captureimage), target, null, camera.picture_size_x - 1, camera.picture_size_y - 1) #undef CAMERA_PICTURE_SIZE_HARD_LIMIT diff --git a/code/modules/photography/camera/other.dm b/code/modules/photography/camera/other.dm index e9aa5d94e597a..166517d055fba 100644 --- a/code/modules/photography/camera/other.dm +++ b/code/modules/photography/camera/other.dm @@ -9,13 +9,15 @@ continue // time to steal your soul - if(istype(target, /mob/living/simple_animal/revenant)) - var/mob/living/simple_animal/revenant/peek_a_boo = target - peek_a_boo.reveal(2 SECONDS) // no hiding - if(!peek_a_boo.unstun_time) - peek_a_boo.stun(2 SECONDS) - target.visible_message(span_warning("[target] violently flinches!"), \ - span_revendanger("You feel your essence draining away from having your picture taken!")) + if(istype(target, /mob/living/basic/revenant)) + var/mob/living/basic/revenant/peek_a_boo = target + peek_a_boo.apply_status_effect(/datum/status_effect/revenant/revealed, 2 SECONDS) // no hiding + peek_a_boo.apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS) + + target.visible_message( + span_warning("[target] violently flinches!"), + span_revendanger("You feel your essence draining away from having your picture taken!"), + ) target.apply_damage(rand(10, 15)) /obj/item/camera/spooky/badmin diff --git a/code/modules/photography/camera/silicon_camera.dm b/code/modules/photography/camera/silicon_camera.dm index 836ecc2690a16..9cdbee1bc2b7a 100644 --- a/code/modules/photography/camera/silicon_camera.dm +++ b/code/modules/photography/camera/silicon_camera.dm @@ -1,23 +1,47 @@ /obj/item/camera/siliconcam name = "silicon photo camera" - var/in_camera_mode = FALSE + resistance_flags = INDESTRUCTIBLE + /// List of all pictures taken by this camera. var/list/datum/picture/stored = list() -/obj/item/camera/siliconcam/ai_camera - name = "AI photo camera" - flash_enabled = FALSE +/// Checks if we can take a picture at this moment. Returns TRUE if we can, FALSE if we can't. +/obj/item/camera/siliconcam/proc/can_take_picture(mob/living/silicon/clicker) + if(clicker.stat != CONSCIOUS || clicker.incapacitated()) + return FALSE + return TRUE + +/obj/item/camera/siliconcam/proc/InterceptClickOn(mob/living/silicon/clicker, params, atom/clicked_on) + if(!can_take_picture(clicker)) + return + clicker.face_atom(clicked_on) + captureimage(clicked_on, clicker) + toggle_camera_mode(clicker, sound = FALSE) +/// Toggles the camera mode on or off. +/// If sound is TRUE, plays a sound effect and displays a message on successful toggle /obj/item/camera/siliconcam/proc/toggle_camera_mode(mob/user, sound = TRUE) - in_camera_mode = !in_camera_mode + if(user.click_intercept == src) + user.click_intercept = null + + else if(isnull(user.click_intercept)) + user.click_intercept = src + + else + // Trying to turn on camera mode while you have another click intercept active, such as malf abilities + if(sound) + balloon_alert(user, "can't enable camera mode!") + playsound(user, 'sound/machines/buzz-sigh.ogg', 25, TRUE) + return + if(sound) - playsound(src, 'sound/items/wirecutter.ogg', 50, TRUE) - to_chat(user, span_notice("Camera mode: [in_camera_mode ? "Activated" : "Deactivated"].")) + playsound(user, 'sound/items/wirecutter.ogg', 50, TRUE) + balloon_alert(user, "camera mode [user.click_intercept == src ? "activated" : "deactivated"]") /obj/item/camera/siliconcam/proc/selectpicture(mob/user) RETURN_TYPE(/datum/picture) if(!length(stored)) - to_chat(user, span_notice("ERROR: No stored photos located.")) + user.balloon_alert(user, "no stored photos!") return var/list/nametemp = list() var/list/temp = list() @@ -25,9 +49,7 @@ nametemp += stored_photo.picture_name temp[stored_photo.picture_name] = stored_photo var/find = tgui_input_list(user, "Select image", "Storage", nametemp) - if(isnull(find)) - return - if(isnull(temp[find])) + if(isnull(find) || isnull(temp[find])) return return temp[find] @@ -36,48 +58,70 @@ if(istype(selection)) show_picture(user, selection) +/obj/item/camera/siliconcam/ai_camera + name = "AI photo camera" + flash_enabled = FALSE + +/obj/item/camera/siliconcam/ai_camera/can_take_picture(mob/living/silicon/ai/clicker) + if(clicker.control_disabled) + return FALSE + return ..() + +/obj/item/camera/siliconcam/ai_camera/balloon_alert(mob/viewer, text) + if(isAI(loc)) + // redirects balloon alerts on us to balloon alerts on our ai eye + var/mob/living/silicon/ai/ai = loc + return ai.eyeobj.balloon_alert(viewer, text) + + return ..() + /obj/item/camera/siliconcam/ai_camera/after_picture(mob/user, datum/picture/picture) var/number = length(stored) picture.picture_name = "Image [number] (taken by [loc.name])" stored[picture] = TRUE - to_chat(user, span_notice("Image recorded.")) + balloon_alert(user, "image recorded") + user.playsound_local(get_turf(user), pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 50, TRUE, -3) /obj/item/camera/siliconcam/robot_camera name = "Cyborg photo camera" var/printcost = 2 -/obj/item/camera/siliconcam/robot_camera/after_picture(mob/user, datum/picture/picture) - var/mob/living/silicon/robot/C = loc - if(istype(C) && istype(C.connected_ai)) - var/number = C.connected_ai.aicamera.stored.len +/obj/item/camera/siliconcam/robot_camera/can_take_picture(mob/living/silicon/robot/clicker) + if(clicker.lockcharge) + return FALSE + return ..() + +/obj/item/camera/siliconcam/robot_camera/after_picture(mob/living/silicon/robot/user, datum/picture/picture) + if(istype(user) && istype(user.connected_ai)) + var/number = user.connected_ai.aicamera.stored.len picture.picture_name = "Image [number] (taken by [loc.name])" - C.connected_ai.aicamera.stored[picture] = TRUE - to_chat(usr, span_notice("Image recorded and saved to remote database.")) + user.connected_ai.aicamera.stored[picture] = TRUE + balloon_alert(user, "image recorded and uploaded") else var/number = stored.len picture.picture_name = "Image [number] (taken by [loc.name])" stored[picture] = TRUE - to_chat(usr, span_notice("Image recorded and saved to local storage. Upload will happen automatically if unit is lawsynced.")) + balloon_alert(user, "image recorded and saved locally") + playsound(src, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, TRUE, -3) -/obj/item/camera/siliconcam/robot_camera/selectpicture(mob/user) - var/mob/living/silicon/robot/R = loc - if(istype(R) && R.connected_ai) - R.picturesync() - return R.connected_ai.aicamera.selectpicture(user) - else - return ..() +/obj/item/camera/siliconcam/robot_camera/selectpicture(mob/living/silicon/robot/user) + if(istype(user) && user.connected_ai) + user.picturesync() + return user.connected_ai.aicamera.selectpicture(user) + return ..() -/obj/item/camera/siliconcam/robot_camera/proc/borgprint(mob/user) - var/mob/living/silicon/robot/C = loc - if(!istype(C) || C.toner < 20) - to_chat(user, span_warning("Insufficent toner to print image.")) +/obj/item/camera/siliconcam/robot_camera/proc/borgprint(mob/living/silicon/robot/user) + if(!istype(user) || user.toner < printcost) + balloon_alert(user, "not enough toner!") return var/datum/picture/selection = selectpicture(user) if(!istype(selection)) - to_chat(user, span_warning("Invalid Image.")) + balloon_alert(user, "invalid image!") return - var/obj/item/photo/p = new /obj/item/photo(C.loc, selection) - p.pixel_x = p.base_pixel_x + rand(-10, 10) - p.pixel_y = p.base_pixel_y + rand(-10, 10) - C.toner -= printcost //All fun allowed. - user.visible_message(span_notice("[C.name] spits out a photograph from a narrow slot on its chassis."), span_notice("You print a photograph.")) + var/obj/item/photo/printed = new(user.drop_location(), selection) + printed.pixel_x = printed.base_pixel_x + rand(-10, 10) + printed.pixel_y = printed.base_pixel_y + rand(-10, 10) + user.toner -= printcost //All fun allowed. + user.visible_message(span_notice("[user.name] spits out a photograph from a narrow slot on its chassis."), span_notice("You print a photograph.")) + balloon_alert(user, "photograph printed") + playsound(src, 'sound/items/taperecorder/taperecorder_print.ogg', 50, TRUE, -3) diff --git a/code/modules/photography/photos/album.dm b/code/modules/photography/photos/album.dm index 35d7f27017cdb..ddc896fe758fb 100644 --- a/code/modules/photography/photos/album.dm +++ b/code/modules/photography/photos/album.dm @@ -9,6 +9,7 @@ inhand_icon_state = "album" lefthand_file = 'icons/mob/inhands/items/books_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/books_righthand.dmi' + storage_type = /datum/storage/photo_album resistance_flags = FLAMMABLE w_class = WEIGHT_CLASS_SMALL flags_1 = PREVENT_CONTENTS_EXPLOSION_1 @@ -16,13 +17,11 @@ /obj/item/storage/photo_album/Initialize(mapload) . = ..() - atom_storage.set_holdable(list(/obj/item/photo)) - atom_storage.max_total_storage = 42 - atom_storage.max_slots = 21 - LAZYADD(SSpersistence.photo_albums, src) + if (!SSpersistence.initialized) + LAZYADD(SSpersistence.queued_photo_albums, src) /obj/item/storage/photo_album/Destroy() - LAZYREMOVE(SSpersistence.photo_albums, src) + LAZYREMOVE(SSpersistence.queued_photo_albums, src) return ..() /obj/item/storage/photo_album/proc/get_picture_id_list() @@ -41,9 +40,9 @@ //Manual loading, DO NOT USE FOR HARDCODED/MAPPED IN ALBUMS. This is for if an album needs to be loaded mid-round from an ID. /obj/item/storage/photo_album/proc/persistence_load() - var/list/data = SSpersistence.get_photo_albums() - if(data[persistence_id]) - populate_from_id_list(data[persistence_id]) + var/list/data = SSpersistence.photo_albums_database.get_key(persistence_id) + if (!isnull(data)) + populate_from_id_list(data) /obj/item/storage/photo_album/proc/populate_from_id_list(list/ids) var/list/current_ids = get_picture_id_list() @@ -55,6 +54,32 @@ if(!atom_storage?.attempt_insert(P, override = TRUE)) qdel(P) +/datum/storage/photo_album + max_total_storage = 42 + max_slots = 21 + +/datum/storage/photo_album/New( + atom/parent, + max_slots, + max_specific_storage, + max_total_storage, +) + . = ..() + set_holdable(/obj/item/photo) + +/datum/storage/photo_album/proc/save_everything() + var/obj/item/storage/photo_album/album = parent + ASSERT(istype(album)) + SSpersistence.photo_albums_database.set_key(album.persistence_id, album.get_picture_id_list()) + +/datum/storage/photo_album/handle_enter(datum/source, obj/item/arrived) + . = ..() + save_everything() + +/datum/storage/photo_album/handle_exit(datum/source, obj/item/gone) + . = ..() + save_everything() + /obj/item/storage/photo_album/hos name = "photo album (Head of Security)" icon_state = "album_blue" diff --git a/code/modules/photography/photos/frame.dm b/code/modules/photography/photos/frame.dm index c42664af269d9..4fbe3e034d88c 100644 --- a/code/modules/photography/photos/frame.dm +++ b/code/modules/photography/photos/frame.dm @@ -56,7 +56,7 @@ var/obj/structure/sign/picture_frame/PF = O PF.copy_overlays(src) if(displayed) - PF.framed = displayed + PF.set_and_save_framed(displayed) if(contents.len) var/obj/item/I = pick(contents) I.forceMove(PF) @@ -70,27 +70,19 @@ resistance_flags = FLAMMABLE var/obj/item/photo/framed var/persistence_id - var/del_id_on_destroy = FALSE var/art_value = OK_ART var/can_decon = TRUE -#define FRAME_DEFINE(id) /obj/structure/sign/picture_frame/##id/persistence_id = #id - -//Put default persistent frame defines here! - -#undef FRAME_DEFINE - /obj/structure/sign/picture_frame/Initialize(mapload, dir, building) . = ..() AddElement(/datum/element/art, art_value) - LAZYADD(SSpersistence.photo_frames, src) + if (!SSpersistence.initialized) + LAZYADD(SSpersistence.queued_photo_frames, src) if(dir) setDir(dir) /obj/structure/sign/picture_frame/Destroy() - LAZYREMOVE(SSpersistence.photo_frames, src) - if(persistence_id && del_id_on_destroy) - SSpersistence.remove_photo_frames(persistence_id) + LAZYREMOVE(SSpersistence.queued_photo_frames, src) return ..() /obj/structure/sign/picture_frame/proc/get_photo_id() @@ -99,9 +91,9 @@ //Manual loading, DO NOT USE FOR HARDCODED/MAPPED IN ALBUMS. This is for if an album needs to be loaded mid-round from an ID. /obj/structure/sign/picture_frame/proc/persistence_load() - var/list/data = SSpersistence.get_photo_frames() - if(data[persistence_id]) - load_from_id(data[persistence_id]) + var/list/data = SSpersistence.photo_frames_database.get_key(persistence_id) + if(!isnull(data)) + load_from_id(data) /obj/structure/sign/picture_frame/proc/load_from_id(id) var/obj/item/photo/old/P = load_photo_from_disk(id) @@ -113,6 +105,15 @@ framed = P update_appearance() +/// Given a photo (or null), will change the contained picture, and queue a persistent save. +/obj/structure/sign/picture_frame/proc/set_and_save_framed(obj/item/photo/photo) + framed = photo + + if (isnull(persistence_id)) + return + + SSpersistence.photo_frames_database.set_key(persistence_id, photo?.picture?.id) + /obj/structure/sign/picture_frame/examine(mob/user) . = ..() if(in_range(src, user)) @@ -141,9 +142,9 @@ tool.play_tool_sound(src) framed.forceMove(drop_location()) user.visible_message(span_warning("[user] cuts away [framed] from [src]!")) - framed = null + set_and_save_framed(null) update_appearance() - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/structure/sign/picture_frame/attackby(obj/item/I, mob/user, params) @@ -155,7 +156,7 @@ var/obj/item/photo/P = I if(!user.transferItemToLoc(P, src)) return - framed = P + set_and_save_framed(P) update_appearance() return TRUE ..() @@ -173,11 +174,11 @@ . += framed /obj/structure/sign/picture_frame/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) var/obj/item/wallframe/picture/F = new /obj/item/wallframe/picture(loc) if(framed) F.displayed = framed - framed = null + set_and_save_framed(null) if(contents.len) var/obj/item/I = pick(contents) I.forceMove(F) @@ -277,7 +278,6 @@ /obj/structure/sign/picture_frame/portrait/bar persistence_id = "frame_bar" - del_id_on_destroy = TRUE ///Generates a persistence id unique to the current map. Every bar should feel a little bit different after all. /obj/structure/sign/picture_frame/portrait/bar/Initialize(mapload) diff --git a/code/modules/photography/photos/photo.dm b/code/modules/photography/photos/photo.dm index 9be79a5826653..b34ff459c0075 100644 --- a/code/modules/photography/photos/photo.dm +++ b/code/modules/photography/photos/photo.dm @@ -54,11 +54,11 @@ icon = I return ..() -/obj/item/photo/suicide_act(mob/living/carbon/user) +/obj/item/photo/suicide_act(mob/living/carbon/human/user) user.visible_message(span_suicide("[user] is taking one last look at \the [src]! It looks like [user.p_theyre()] giving in to death!"))//when you wanna look at photo of waifu one last time before you die... - if (user.gender == MALE) + if (!ishuman(user) || user.physique == MALE) playsound(user, 'sound/voice/human/manlaugh1.ogg', 50, TRUE)//EVERY TIME I DO IT MAKES ME LAUGH - else if (user.gender == FEMALE) + else playsound(user, 'sound/voice/human/womanlaugh.ogg', 50, TRUE) return OXYLOSS diff --git a/code/modules/plumbing/plumbers/_plumb_machinery.dm b/code/modules/plumbing/plumbers/_plumb_machinery.dm index be75cf20479dc..dcfa5faac5cbb 100644 --- a/code/modules/plumbing/plumbers/_plumb_machinery.dm +++ b/code/modules/plumbing/plumbers/_plumb_machinery.dm @@ -32,7 +32,7 @@ /obj/machinery/plumbing/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/plumbing/plunger_act(obj/item/plunger/P, mob/living/user, reinforced) to_chat(user, span_notice("You start furiously plunging [name].")) diff --git a/code/modules/plumbing/plumbers/acclimator.dm b/code/modules/plumbing/plumbers/acclimator.dm index da5c4529a4230..850ebaaa3ed1c 100644 --- a/code/modules/plumbing/plumbers/acclimator.dm +++ b/code/modules/plumbing/plumbers/acclimator.dm @@ -3,6 +3,9 @@ #define HEATING "Heating" #define NEUTRAL "Neutral" +///cool/heat power. converts temperature into joules +#define HEATER_COEFFICIENT 0.05 + ///this the plumbing version of a heater/freezer. /obj/machinery/plumbing/acclimator name = "chemical acclimator" @@ -17,15 +20,11 @@ var/target_temperature = 300 ///I cant find a good name for this. Basically if target is 300, and this is 10, it will still target 300 but will start emptying itself at 290 and 310. var/allowed_temperature_difference = 1 - ///cool/heat power - var/heater_coefficient = 0.05 ///Are we turned on or off? this is from the on and off button var/enabled = TRUE ///COOLING, HEATING or NEUTRAL. We track this for change, so we dont needlessly update our icon var/acclimate_state - /**We can't take anything in, at least till we're emptied. Down side of the round robin chem transfer, otherwise while emptying 5u of an unreacted chem gets added, - and you get nasty leftovers - */ + ///When conditions are met we send out the stored reagents var/emptying = FALSE /obj/machinery/plumbing/acclimator/Initialize(mapload, bolt, layer) @@ -54,7 +53,7 @@ emptying = TRUE if(!emptying) //suspend heating/cooling during emptying phase - reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater + reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * HEATER_COEFFICIENT * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater reagents.handle_reactions() use_power(active_power_usage * seconds_per_tick) else if(acclimate_state != NEUTRAL) @@ -111,3 +110,4 @@ #undef COOLING #undef HEATING #undef NEUTRAL +#undef HEATER_COEFFICIENT diff --git a/code/modules/plumbing/plumbers/bottler.dm b/code/modules/plumbing/plumbers/bottler.dm index 7ce4c2f55765d..8e0158b61da78 100644 --- a/code/modules/plumbing/plumbers/bottler.dm +++ b/code/modules/plumbing/plumbers/bottler.dm @@ -34,23 +34,24 @@ ///changes the tile array /obj/machinery/plumbing/bottler/setDir(newdir) . = ..() + var/turf/target_turf = get_turf(src) switch(dir) if(NORTH) - goodspot = get_step(get_turf(src), NORTH) - inputspot = get_step(get_turf(src), SOUTH) - badspot = get_step(get_turf(src), EAST) + goodspot = get_step(target_turf, NORTH) + inputspot = get_step(target_turf, SOUTH) + badspot = get_step(target_turf, EAST) if(SOUTH) - goodspot = get_step(get_turf(src), SOUTH) - inputspot = get_step(get_turf(src), NORTH) - badspot = get_step(get_turf(src), WEST) + goodspot = get_step(target_turf, SOUTH) + inputspot = get_step(target_turf, NORTH) + badspot = get_step(target_turf, WEST) if(WEST) - goodspot = get_step(get_turf(src), WEST) - inputspot = get_step(get_turf(src), EAST) - badspot = get_step(get_turf(src), NORTH) + goodspot = get_step(target_turf, WEST) + inputspot = get_step(target_turf, EAST) + badspot = get_step(target_turf, NORTH) if(EAST) - goodspot = get_step(get_turf(src), EAST) - inputspot = get_step(get_turf(src), WEST) - badspot = get_step(get_turf(src), SOUTH) + goodspot = get_step(target_turf, EAST) + inputspot = get_step(target_turf, WEST) + badspot = get_step(target_turf, SOUTH) //If by some miracle if( ( !valid_output_configuration ) && ( goodspot != null && inputspot != null && badspot != null ) ) @@ -63,7 +64,7 @@ if(!valid_output_configuration) to_chat(user, span_warning("A flashing notification on the screen reads: \"Output location error!\"")) return . - var/new_amount = tgui_input_number(user, "Set Amount to Fill", "Desired Amount", max_value = 100) + var/new_amount = tgui_input_number(user, "Set Amount to Fill", "Desired Amount", max_value = reagents.maximum_volume, round_value = TRUE) if(!new_amount || QDELETED(user) || QDELETED(src) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) return . wanted_amount = new_amount @@ -81,19 +82,22 @@ if(reagents.total_volume >= wanted_amount && anchored && length(inputspot.contents)) use_power(active_power_usage * seconds_per_tick) var/obj/AM = pick(inputspot.contents)///pick a reagent_container that could be used - if((is_reagent_container(AM) && !istype(AM, /obj/item/reagent_containers/hypospray/medipen)) || istype(AM, /obj/item/ammo_casing/shotgun/dart)) - var/obj/item/reagent_containers/B = AM + //allowed containers + var/static/list/allowed_containers = list( + /obj/item/reagent_containers/cup, + /obj/item/ammo_casing/shotgun/dart, + ) + if(is_type_in_list(AM, allowed_containers)) + var/obj/item/B = AM ///see if it would overflow else inject if((B.reagents.total_volume + wanted_amount) <= B.reagents.maximum_volume) - reagents.trans_to(B, wanted_amount, transferred_by = src) + reagents.trans_to(B, wanted_amount) B.forceMove(goodspot) return ///glass was full so we move it away AM.forceMove(badspot) - if(istype(AM, /obj/item/slime_extract)) ///slime extracts need inject + else if(istype(AM, /obj/item/slime_extract)) ///slime extracts need inject AM.forceMove(goodspot) - reagents.trans_to(AM, wanted_amount, transferred_by = src, methods = INJECT) - return - if(istype(AM, /obj/item/slimecross/industrial)) ///no need to move slimecross industrial things - reagents.trans_to(AM, wanted_amount, transferred_by = src, methods = INJECT) - return + reagents.trans_to(AM, wanted_amount, methods = INJECT) + else if(istype(AM, /obj/item/slimecross/industrial)) ///no need to move slimecross industrial things + reagents.trans_to(AM, wanted_amount, methods = INJECT) diff --git a/code/modules/plumbing/plumbers/filter.dm b/code/modules/plumbing/plumbers/filter.dm index 4e4a282bd1dcd..633f70830f016 100644 --- a/code/modules/plumbing/plumbers/filter.dm +++ b/code/modules/plumbing/plumbers/filter.dm @@ -38,11 +38,12 @@ switch(action) if("add") var/which = params["which"] - var/selected_reagent = tgui_input_list(usr, "Select [which] reagent", "Reagent", GLOB.chemical_name_list) + + var/selected_reagent = tgui_input_list(usr, "Select [which] reagent", "Reagent", GLOB.name2reagent) if(!selected_reagent) return TRUE - var/chem_id = get_chem_id(selected_reagent) + var/datum/reagent/chem_id = GLOB.name2reagent[selected_reagent] if(!chem_id) return TRUE diff --git a/code/modules/plumbing/plumbers/iv_drip.dm b/code/modules/plumbing/plumbers/iv_drip.dm new file mode 100644 index 0000000000000..bbdb80f57b16d --- /dev/null +++ b/code/modules/plumbing/plumbers/iv_drip.dm @@ -0,0 +1,40 @@ +///modified IV that can be anchored and takes plumbing in- and output +/obj/machinery/iv_drip/plumbing + name = "automated IV drip" + desc = "A modified IV drip with plumbing connects. Reagents received from the connect are injected directly into their bloodstream, blood that is drawn goes to the internal storage and then into the ducting." + icon_state = "plumb" + base_icon_state = "plumb" + density = TRUE + use_internal_storage = TRUE + +/obj/machinery/iv_drip/plumbing/Initialize(mapload, bolt, layer) + . = ..() + AddComponent(/datum/component/plumbing/iv_drip, bolt, layer) + AddComponent(/datum/component/simple_rotation) + +/obj/machinery/iv_drip/plumbing/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(attached) + context[SCREENTIP_CONTEXT_RMB] = "Take needle out" + else if(reagent_container && !use_internal_storage) + context[SCREENTIP_CONTEXT_RMB] = "Eject container" + else if(!inject_only) + context[SCREENTIP_CONTEXT_RMB] = "Change direction" + + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/iv_drip/plumbing/plunger_act(obj/item/plunger/P, mob/living/user, reinforced) + to_chat(user, span_notice("You start furiously plunging [name].")) + if(do_after(user, 30, target = src)) + to_chat(user, span_notice("You finish plunging the [name].")) + reagents.expose(get_turf(src), TOUCH) //splash on the floor + reagents.clear_reagents() + +/obj/machinery/iv_drip/plumbing/can_use_alt_click(mob/user) + return FALSE //Alt click is used for rotation + +/obj/machinery/iv_drip/plumbing/wrench_act(mob/living/user, obj/item/tool) + if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/iv_drip/plumbing/on_deconstruction(disassembled) + qdel(src) diff --git a/code/modules/plumbing/plumbers/pill_press.dm b/code/modules/plumbing/plumbers/pill_press.dm index df6a627b10359..26835e7c92d6f 100644 --- a/code/modules/plumbing/plumbers/pill_press.dm +++ b/code/modules/plumbing/plumbers/pill_press.dm @@ -1,3 +1,10 @@ +///the minimum size of a pill or patch +#define MIN_VOLUME 5 +///the maximum size a pill or patch can be +#define MAX_VOLUME 50 +///max amount of pills allowed on our tile before we start storing them instead +#define MAX_FLOOR_PRODUCTS 10 + ///We take a constant input of reagents, and produce a pill once a set volume is reached /obj/machinery/plumbing/pill_press name = "chemical press" @@ -5,107 +12,104 @@ icon_state = "pill_press" active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 2 - ///maximum size of a pill - var/max_pill_volume = 50 - ///maximum size of a patch - var/max_patch_volume = 40 - ///maximum size of a bottle - var/max_bottle_volume = 50 - ///current operating product (pills or patches) + /// current operating product (pills or patches) var/product = "pill" - ///the minimum size a pill or patch can be - var/min_volume = 5 - ///the maximum size a pill or patch can be - var/max_volume = 50 - ///selected size of the product + /// selected size of the product var/current_volume = 10 - ///prefix for the product name + /// prefix for the product name var/product_name = "factory" - ///the icon_state number for the pill. - var/pill_number = RANDOM_PILL_STYLE - ///list of id's and icons for the pill selection of the ui - var/list/pill_styles - /// Currently selected patch style - var/patch_style = DEFAULT_PATCH_STYLE - /// List of available patch styles for UI - var/list/patch_styles - ///list of products stored in the machine, so we dont have 610 pills on one tile + /// All packaging types wrapped up in 1 big list + var/static/list/packaging_types = null + ///The type of packaging to use + var/packaging_type + ///Category of packaging + var/packaging_category + /// list of products stored in the machine, so we dont have 610 pills on one tile var/list/stored_products = list() - ///max amount of pills allowed on our tile before we start storing them instead - var/max_floor_products = 10 - -/obj/machinery/plumbing/pill_press/examine(mob/user) - . = ..() - . += span_notice("The [name] currently has [stored_products.len] stored. There needs to be less than [max_floor_products] on the floor to continue dispensing.") /obj/machinery/plumbing/pill_press/Initialize(mapload, bolt, layer) . = ..() + if(!packaging_types) + var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/chemmaster) + + var/list/types = list( + CAT_PILLS = GLOB.reagent_containers[CAT_PILLS], + CAT_PATCHES = GLOB.reagent_containers[CAT_PATCHES], + "Bottles" = list(/obj/item/reagent_containers/cup/bottle), + ) + + packaging_types = list() + for(var/category in types) + var/list/packages = types[category] + + var/list/category_item = list("cat_name" = category) + for(var/obj/item/reagent_containers/container as anything in packages) + var/list/package_item = list( + "class_name" = assets.icon_class_name(sanitize_css_class_name("[container]")), + "ref" = REF(container) + ) + category_item["products"] += list(package_item) + + packaging_types += list(category_item) + + packaging_type = REF(GLOB.reagent_containers[CAT_PILLS][1]) + decode_category() + AddComponent(/datum/component/plumbing/simple_demand, bolt, layer) +/obj/machinery/plumbing/pill_press/examine(mob/user) + . = ..() + . += span_notice("The [name] currently has [stored_products.len] stored. There needs to be less than [MAX_FLOOR_PRODUCTS] on the floor to continue dispensing.") + +/// decode product category from it's type path and returns the decoded typepath +/obj/machinery/plumbing/pill_press/proc/decode_category() + var/obj/item/reagent_containers/container = locate(packaging_type) + if(ispath(container, /obj/item/reagent_containers/pill/patch)) + packaging_category = CAT_PATCHES + else if(ispath(container, /obj/item/reagent_containers/pill)) + packaging_category = CAT_PILLS + else + packaging_category = "Bottles" + return container + /obj/machinery/plumbing/pill_press/process(seconds_per_tick) if(machine_stat & NOPOWER) return + + //shift & check to account for floating point inaccuracies if(reagents.total_volume >= current_volume) - if (product == "pill") - var/obj/item/reagent_containers/pill/P = new(src) - reagents.trans_to(P, current_volume) - P.name = trim("[product_name] pill") - stored_products += P - if(pill_number == RANDOM_PILL_STYLE) - P.icon_state = "pill[rand(1,21)]" + var/obj/item/reagent_containers/container = locate(packaging_type) + container = new container(src) + var/suffix + switch(packaging_category) + if(CAT_PILLS) + suffix = "Pill" + if(CAT_PATCHES) + suffix = "Patch" else - P.icon_state = "pill[pill_number]" - if(P.icon_state == "pill4") //mirrored from chem masters - P.desc = "A tablet or capsule, but not just any, a red one, one taken by the ones not scared of knowledge, freedom, uncertainty and the brutal truths of reality." - else if (product == "patch") - var/obj/item/reagent_containers/pill/patch/P = new(src) - reagents.trans_to(P, current_volume) - P.name = trim("[product_name] patch") - P.icon_state = patch_style - stored_products += P - else if (product == "bottle") - var/obj/item/reagent_containers/cup/bottle/P = new(src) - reagents.trans_to(P, current_volume) - P.name = trim("[product_name] bottle") - stored_products += P + suffix = "Bottle" + container.name = "[product_name] [suffix]" + reagents.trans_to(container, current_volume) + stored_products += container + + //dispense stored products on the floor if(stored_products.len) var/pill_amount = 0 - for(var/thing in loc) - if(!istype(thing, /obj/item/reagent_containers/cup/bottle) && !istype(thing, /obj/item/reagent_containers/pill)) - continue + for(var/obj/item/reagent_containers/thing in loc) pill_amount++ - if(pill_amount >= max_floor_products) //too much so just stop + if(pill_amount >= MAX_FLOOR_PRODUCTS) //too much so just stop break - if(pill_amount < max_floor_products && anchored) + if(pill_amount < MAX_FLOOR_PRODUCTS && anchored) var/atom/movable/AM = stored_products[1] //AM because forceMove is all we need stored_products -= AM AM.forceMove(drop_location()) use_power(active_power_usage * seconds_per_tick) -/obj/machinery/plumbing/pill_press/proc/load_styles() - //expertly copypasted from chemmasters - var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/pills) - pill_styles = list() - for (var/x in 1 to PILL_STYLE_COUNT) - var/list/SL = list() - SL["id"] = x - SL["class_name"] = assets.icon_class_name("pill[x]") - pill_styles += list(SL) - var/datum/asset/spritesheet/simple/patches_assets = get_asset_datum(/datum/asset/spritesheet/simple/patches) - patch_styles = list() - for (var/raw_patch_style in PATCH_STYLE_LIST) - //adding class_name for use in UI - var/list/patch_style = list() - patch_style["style"] = raw_patch_style - patch_style["class_name"] = patches_assets.icon_class_name(raw_patch_style) - patch_styles += list(patch_style) - /obj/machinery/plumbing/pill_press/ui_assets(mob/user) return list( - get_asset_datum(/datum/asset/spritesheet/simple/pills), - get_asset_datum(/datum/asset/spritesheet/simple/patches), + get_asset_datum(/datum/asset/spritesheet/chemmaster) ) /obj/machinery/plumbing/pill_press/ui_interact(mob/user, datum/tgui/ui) @@ -114,45 +118,45 @@ ui = new(user, src, "ChemPress", name) ui.open() +/obj/machinery/plumbing/pill_press/ui_static_data(mob/user) + var/list/data = list() + + data["min_volume"] = MIN_VOLUME + data["max_volume"] = MAX_VOLUME + data["packaging_types"] = packaging_types + + return data + /obj/machinery/plumbing/pill_press/ui_data(mob/user) - if(!pill_styles || !patch_styles) - load_styles() var/list/data = list() - data["pill_style"] = pill_number + data["current_volume"] = current_volume data["product_name"] = product_name - data["pill_styles"] = pill_styles - data["product"] = product - data["min_volume"] = min_volume - data["max_volume"] = max_volume - data["patch_style"] = patch_style - data["patch_styles"] = patch_styles + data["packaging_type"] = packaging_type + data["packaging_category"] = packaging_category + return data /obj/machinery/plumbing/pill_press/ui_act(action, params) . = ..() if(.) return + . = TRUE switch(action) - if("change_pill_style") - pill_number = clamp(text2num(params["id"]), 1 , PILL_STYLE_COUNT) if("change_current_volume") - current_volume = clamp(text2num(params["volume"]), min_volume, max_volume) + current_volume = round(clamp(text2num(params["volume"]), MIN_VOLUME, MAX_VOLUME)) if("change_product_name") var/formatted_name = html_encode(params["name"]) if (length(formatted_name) > MAX_NAME_LEN) - product_name = copytext(formatted_name, 1, MAX_NAME_LEN+1) + product_name = copytext(formatted_name, 1, MAX_NAME_LEN + 1) else product_name = formatted_name if("change_product") - product = params["product"] - if (product == "pill") - max_volume = max_pill_volume - else if (product == "patch") - max_volume = max_patch_volume - else if (product == "bottle") - max_volume = max_bottle_volume - current_volume = clamp(current_volume, min_volume, max_volume) - if("change_patch_style") - patch_style = params["patch_style"] + packaging_type = params["ref"] + var/obj/item/reagent_containers/container = decode_category() + current_volume = clamp(current_volume, MIN_VOLUME, initial(container.volume)) + +#undef MIN_VOLUME +#undef MAX_VOLUME +#undef MAX_FLOOR_PRODUCTS diff --git a/code/modules/plumbing/plumbers/plumbing_buffer.dm b/code/modules/plumbing/plumbers/plumbing_buffer.dm index b2bb21bc24e36..7b3ef306d0419 100644 --- a/code/modules/plumbing/plumbers/plumbing_buffer.dm +++ b/code/modules/plumbing/plumbers/plumbing_buffer.dm @@ -33,11 +33,11 @@ SIGNAL_HANDLER if(!buffer_net) return - if(reagents.total_volume + CHEMICAL_QUANTISATION_LEVEL >= activation_volume && mode == UNREADY) + if(reagents.total_volume >= activation_volume && mode == UNREADY) mode = IDLE buffer_net.check_active() - else if(reagents.total_volume + CHEMICAL_QUANTISATION_LEVEL < activation_volume && mode != UNREADY) + else if(reagents.total_volume < activation_volume && mode != UNREADY) mode = UNREADY buffer_net.check_active() diff --git a/code/modules/plumbing/plumbers/reaction_chamber.dm b/code/modules/plumbing/plumbers/reaction_chamber.dm index 689d043418b49..2b56bfb4ae6c9 100644 --- a/code/modules/plumbing/plumbers/reaction_chamber.dm +++ b/code/modules/plumbing/plumbers/reaction_chamber.dm @@ -1,5 +1,8 @@ ///a reaction chamber for plumbing. pretty much everything can react, but this one keeps the reagents separated and only reacts under your given terms +/// coefficient to convert temperature to joules. same lvl as acclimator +#define HEATER_COEFFICIENT 0.05 + /obj/machinery/plumbing/reaction_chamber name = "mixing chamber" desc = "Keeps chemicals separated until given conditions are met." @@ -19,9 +22,6 @@ ///towards which temperature do we build (except during draining)? var/target_temperature = 300 - ///cool/heat power - var/heater_coefficient = 0.05 //same lvl as acclimator - /obj/machinery/plumbing/reaction_chamber/Initialize(mapload, bolt, layer) . = ..() @@ -35,30 +35,45 @@ /// Handles properly detaching signal hooks. /obj/machinery/plumbing/reaction_chamber/proc/on_reagents_del(datum/reagents/reagents) SIGNAL_HANDLER + UnregisterSignal(reagents, list(COMSIG_REAGENTS_REM_REAGENT, COMSIG_REAGENTS_DEL_REAGENT, COMSIG_REAGENTS_CLEAR_REAGENTS, COMSIG_REAGENTS_REACTED, COMSIG_QDELETING)) return NONE /// Handles stopping the emptying process when the chamber empties. /obj/machinery/plumbing/reaction_chamber/proc/on_reagent_change(datum/reagents/holder, ...) SIGNAL_HANDLER - if(holder.total_volume == 0 && emptying) //we were emptying, but now we aren't + + if(!holder.total_volume && emptying) //we were emptying, but now we aren't emptying = FALSE holder.flags |= NO_REACT return NONE /obj/machinery/plumbing/reaction_chamber/process(seconds_per_tick) - if(!emptying || reagents.is_reacting) //suspend heating/cooling during emptying phase - reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater - reagents.handle_reactions() + //half the power for getting reagents in + var/power_usage = active_power_usage * 0.5 + + if(!emptying || reagents.is_reacting) + //adjust temperature of final solution + var/temp_diff = target_temperature - reagents.chem_temp + if(abs(temp_diff) > 0.01) //if we are not close enough keep going + reagents.adjust_thermal_energy(temp_diff * HEATER_COEFFICIENT * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater - use_power(active_power_usage * seconds_per_tick) + //do other stuff with final solution + handle_reagents(seconds_per_tick) + + //full power for doing reactions + power_usage *= 2 + + use_power(power_usage * seconds_per_tick) + +///For subtypes that want to do additional reagent handling +/obj/machinery/plumbing/reaction_chamber/proc/handle_reagents(seconds_per_tick) + return /obj/machinery/plumbing/reaction_chamber/power_change() . = ..() - if(use_power != NO_POWER_USE) - icon_state = initial(icon_state) + "_on" - else - icon_state = initial(icon_state) + + icon_state = initial(icon_state) + "[use_power != NO_POWER_USE ? "_on" : ""]" /obj/machinery/plumbing/reaction_chamber/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) @@ -67,69 +82,78 @@ ui.open() /obj/machinery/plumbing/reaction_chamber/ui_data(mob/user) - var/list/data = list() + . = list() var/list/reagents_data = list() for(var/datum/reagent/required_reagent as anything in required_reagents) //make a list where the key is text, because that looks alot better in the ui than a typepath var/list/reagent_data = list() reagent_data["name"] = initial(required_reagent.name) - reagent_data["required_reagent"] = required_reagents[required_reagent] + reagent_data["volume"] = required_reagents[required_reagent] reagents_data += list(reagent_data) - data["reagents"] = reagents_data - data["emptying"] = emptying - data["temperature"] = round(reagents.chem_temp, 0.1) - data["targetTemp"] = target_temperature - data["isReacting"] = reagents.is_reacting - return data + .["reagents"] = reagents_data + .["emptying"] = emptying + .["temperature"] = round(reagents.chem_temp, 0.1) + .["targetTemp"] = target_temperature + .["isReacting"] = reagents.is_reacting -/obj/machinery/plumbing/reaction_chamber/ui_act(action, params) +/obj/machinery/plumbing/reaction_chamber/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return TRUE - . = FALSE switch(action) if("add") - var/selected_reagent = tgui_input_list(usr, "Select reagent", "Reagent", GLOB.chemical_name_list) + var/selected_reagent = tgui_input_list(ui.user, "Select reagent", "Reagent", GLOB.name2reagent) if(!selected_reagent) - return TRUE + return FALSE - var/input_reagent = get_chem_id(selected_reagent) + var/datum/reagent/input_reagent = GLOB.name2reagent[selected_reagent] if(!input_reagent) - return TRUE + return FALSE if(!required_reagents.Find(input_reagent)) var/input_amount = text2num(params["amount"]) - if(input_amount) + if(!isnull(input_amount)) required_reagents[input_reagent] = input_amount - - . = TRUE + return TRUE + return FALSE if("remove") var/reagent = get_chem_id(params["chem"]) if(reagent) required_reagents.Remove(reagent) - . = TRUE + return TRUE + return FALSE if("temperature") var/target = text2num(params["target"]) - if(target != null) - target_temperature=clamp(target, 0, 1000) - .=TRUE + if(!isnull(target)) + target_temperature = clamp(target, 0, 1000) + return TRUE + return FALSE + + var/result = handle_ui_act(action, params, ui, state) + if(isnull(result)) + result = FALSE + return result + +/// For custom handling of ui actions from inside a subtype +/obj/machinery/plumbing/reaction_chamber/proc/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + return null ///Chemistry version of reaction chamber that allows for acid and base buffers to be used while reacting /obj/machinery/plumbing/reaction_chamber/chem name = "reaction chamber" - ///If above this pH, we start dumping buffer into it - var/acidic_limit = 9 ///If below this pH, we start dumping buffer into it - var/alkaline_limit = 5 + var/acidic_limit = 5 + ///If above this pH, we start dumping acid into it + var/alkaline_limit = 9 - ///Beaker that holds the acidic buffer. I don't want to deal with snowflaking so it's just a separate thing. It's a small (50u) beaker + ///beaker that holds the acidic buffer(50u) var/obj/item/reagent_containers/cup/beaker/acidic_beaker - ///beaker that holds the alkaline buffer. + ///beaker that holds the alkaline buffer(50u). var/obj/item/reagent_containers/cup/beaker/alkaline_beaker /obj/machinery/plumbing/reaction_chamber/chem/Initialize(mapload, bolt, layer) @@ -147,13 +171,34 @@ QDEL_NULL(alkaline_beaker) return ..() -/obj/machinery/plumbing/reaction_chamber/chem/process(seconds_per_tick) - //add acidic/alkaine buffer if over/under limit - if(reagents.is_reacting && reagents.ph < alkaline_limit) - alkaline_beaker.reagents.trans_to(reagents, 1 * seconds_per_tick) - if(reagents.is_reacting && reagents.ph > acidic_limit) - acidic_beaker.reagents.trans_to(reagents, 1 * seconds_per_tick) - ..() +/obj/machinery/plumbing/reaction_chamber/chem/handle_reagents(seconds_per_tick) + if(reagents.ph < acidic_limit || reagents.ph > alkaline_limit) + //no power + if(machine_stat & NOPOWER) + return + + //nothing to react with + var/num_of_reagents = length(reagents.reagent_list) + if(!num_of_reagents) + return + + /** + * figure out which buffer to transfer to restore balance + * if solution is getting too basic(high ph) add some acid to lower it's value + * else if solution is getting too acidic(low ph) add some base to increase it's value + */ + var/datum/reagents/buffer = reagents.ph > alkaline_limit ? acidic_beaker.reagents : alkaline_beaker.reagents + if(!buffer.total_volume) + return + + //transfer buffer and handle reactions + var/ph_change = max((reagents.ph > alkaline_limit ? (reagents.ph - alkaline_limit) : (acidic_limit - reagents.ph)), 0.25) + var/buffer_amount = ((ph_change * reagents.total_volume) / (BUFFER_IONIZING_STRENGTH * num_of_reagents)) * seconds_per_tick + if(!buffer.trans_to(reagents, buffer_amount)) + return + + //some power for accurate ph balancing & keep track of attempts made + use_power(active_power_usage * 0.03 * buffer_amount) /obj/machinery/plumbing/reaction_chamber/chem/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) @@ -167,16 +212,15 @@ .["reagentAcidic"] = acidic_limit .["reagentAlkaline"] = alkaline_limit -/obj/machinery/plumbing/reaction_chamber/chem/ui_act(action, params) - . = ..() - if (.) - return +/obj/machinery/plumbing/reaction_chamber/chem/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + . = TRUE switch(action) if("acidic") - acidic_limit = round(text2num(params["target"])) + acidic_limit = clamp(round(text2num(params["target"])), CHEMICAL_MIN_PH, alkaline_limit - 1) if("alkaline") - alkaline_limit = round(text2num(params["target"])) - - return TRUE + alkaline_limit = clamp(round(text2num(params["target"])), acidic_limit + 1, CHEMICAL_MAX_PH) + else + return FALSE +#undef HEATER_COEFFICIENT diff --git a/code/modules/plumbing/plumbers/synthesizer.dm b/code/modules/plumbing/plumbers/synthesizer.dm index a4521dd9b2e5a..0e9cb0c1b1125 100644 --- a/code/modules/plumbing/plumbers/synthesizer.dm +++ b/code/modules/plumbing/plumbers/synthesizer.dm @@ -2,7 +2,6 @@ /obj/machinery/plumbing/synthesizer name = "chemical synthesizer" desc = "Produces a single chemical at a given volume. Must be plumbed. Most effective when working in unison with other chemical synthesizers, heaters and filters." - icon_state = "synthesizer" icon = 'icons/obj/pipes_n_cables/hydrochem/plumbers.dmi' active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 2 @@ -10,11 +9,11 @@ ///Amount we produce for every process. Ideally keep under 5 since thats currently the standard duct capacity var/amount = 1 ///I track them here because I have no idea how I'd make tgui loop like that - var/static/list/possible_amounts = list(0,1,2,3,4,5) + var/static/list/possible_amounts = list(0, 1, 2, 3, 4, 5) ///The reagent we are producing. We are a typepath, but are also typecast because there's several occations where we need to use initial. var/datum/reagent/reagent_id = null ///straight up copied from chem dispenser. Being a subtype would be extremely tedious and making it global would restrict potential subtypes using different dispensable_reagents - var/list/dispensable_reagents = list( + var/static/list/default_reagents = list( /datum/reagent/aluminium, /datum/reagent/bromine, /datum/reagent/carbon, @@ -41,18 +40,24 @@ /datum/reagent/water, /datum/reagent/fuel, ) + //reagents this synthesizer can dispense + var/list/dispensable_reagents /obj/machinery/plumbing/synthesizer/Initialize(mapload, bolt, layer) . = ..() AddComponent(/datum/component/plumbing/simple_supply, bolt, layer) + dispensable_reagents = default_reagents /obj/machinery/plumbing/synthesizer/process(seconds_per_tick) if(machine_stat & NOPOWER || !reagent_id || !amount) return - if(reagents.total_volume >= amount*seconds_per_tick*0.5) //otherwise we get leftovers, and we need this to be precise + + //otherwise we get leftovers, and we need this to be precise + if(reagents.total_volume >= amount) return - reagents.add_reagent(reagent_id, amount*seconds_per_tick*0.5) - use_power(active_power_usage * amount * seconds_per_tick * 0.5) + reagents.add_reagent(reagent_id, amount) + + use_power(active_power_usage) /obj/machinery/plumbing/synthesizer/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) @@ -60,8 +65,13 @@ ui = new(user, src, "ChemSynthesizer", name) ui.open() +/obj/machinery/plumbing/synthesizer/ui_static_data(mob/user) + . = ..() + .["possible_amounts"] = possible_amounts + /obj/machinery/plumbing/synthesizer/ui_data(mob/user) - var/list/data = list() + . = list() + .["amount"] = amount var/is_hallucinating = FALSE if(isliving(user)) @@ -69,36 +79,35 @@ is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) var/list/chemicals = list() - for(var/A in dispensable_reagents) - var/datum/reagent/R = GLOB.chemical_reagents_list[A] - if(R) - var/chemname = R.name + for(var/reagentID in dispensable_reagents) + var/datum/reagent/reagent = GLOB.chemical_reagents_list[reagentID] + if(reagent) + var/chemname = reagent.name if(is_hallucinating && prob(5)) chemname = "[pick_list_replacements("hallucination.json", "chemicals")]" - chemicals.Add(list(list("title" = chemname, "id" = ckey(R.name)))) - data["chemicals"] = chemicals - data["amount"] = amount - data["possible_amounts"] = possible_amounts + chemicals += list(list("title" = chemname, "id" = reagent.name)) + .["chemicals"] = chemicals - data["current_reagent"] = ckey(initial(reagent_id.name)) - return data + .["current_reagent"] = initial(reagent_id.name) /obj/machinery/plumbing/synthesizer/ui_act(action, params) . = ..() if(.) return - . = TRUE + switch(action) if("amount") var/new_amount = text2num(params["target"]) if(new_amount in possible_amounts) amount = new_amount . = TRUE + if("select") var/new_reagent = GLOB.name2reagent[params["reagent"]] if(new_reagent in dispensable_reagents) reagent_id = new_reagent . = TRUE + update_appearance() reagents.clear_reagents() @@ -114,7 +123,7 @@ icon_state = "synthesizer_soda" //Copied from soda dispenser - dispensable_reagents = list( + var/static/list/soda_reagents = list( /datum/reagent/consumable/coffee, /datum/reagent/consumable/space_cola, /datum/reagent/consumable/cream, @@ -140,6 +149,11 @@ /datum/reagent/water, ) +/obj/machinery/plumbing/synthesizer/soda/Initialize(mapload, bolt, layer) + . = ..() + + dispensable_reagents = soda_reagents + /obj/machinery/plumbing/synthesizer/beer name = "beer synthesizer" desc = "Produces a single chemical at a given volume. Must be plumbed." @@ -147,7 +161,7 @@ icon_state = "synthesizer_booze" //Copied from beer dispenser - dispensable_reagents = list( + var/static/list/beer_reagents = list( /datum/reagent/consumable/ethanol/absinthe, /datum/reagent/consumable/ethanol/ale, /datum/reagent/consumable/ethanol/applejack, @@ -172,3 +186,7 @@ /datum/reagent/consumable/ethanol/wine, ) +/obj/machinery/plumbing/synthesizer/beer/Initialize(mapload, bolt, layer) + . = ..() + + dispensable_reagents = beer_reagents diff --git a/code/modules/plumbing/plumbers/teleporter.dm b/code/modules/plumbing/plumbers/teleporter.dm index a8e6e7ae3ac55..7bb098eae4e06 100644 --- a/code/modules/plumbing/plumbers/teleporter.dm +++ b/code/modules/plumbing/plumbers/teleporter.dm @@ -45,7 +45,7 @@ ///Transfer reagents and display a flashing icon /obj/machinery/plumbing/sender/proc/teleport_chemicals(obj/machinery/plumbing/receiver/R, amount) flick(initial(icon_state) + "_flash", src) - reagents.trans_to(R, amount, round_robin = TRUE) + reagents.trans_to(R, amount) ///A bluespace output pipe for plumbing. Supports multiple recipients. Must be constructed with a circuit board /obj/machinery/plumbing/receiver diff --git a/code/modules/research/xenobiology/vatgrowing/vatgrower.dm b/code/modules/plumbing/plumbers/vatgrower.dm similarity index 100% rename from code/modules/research/xenobiology/vatgrowing/vatgrower.dm rename to code/modules/plumbing/plumbers/vatgrower.dm diff --git a/code/modules/point/point.dm b/code/modules/point/point.dm index 3a3d97e25655b..6e61b1154d59c 100644 --- a/code/modules/point/point.dm +++ b/code/modules/point/point.dm @@ -77,7 +77,7 @@ abstract_move(get_turf(src)) pixel_x = old_loc.pixel_x pixel_y = old_loc.pixel_y - invisibility = set_invis + SetInvisibility(set_invis) #undef POINT_TIME diff --git a/code/modules/power/apc/apc_appearance.dm b/code/modules/power/apc/apc_appearance.dm index 06e0452efada7..41547288a0b73 100644 --- a/code/modules/power/apc/apc_appearance.dm +++ b/code/modules/power/apc/apc_appearance.dm @@ -18,11 +18,6 @@ set_light(light_on_range) return - if(update_state & UPSTATE_BLUESCREEN) - set_light_color(LIGHT_COLOR_BLUE) - set_light(light_on_range) - return - set_light(0) /obj/machinery/power/apc/update_icon_state() @@ -39,9 +34,6 @@ if(update_state & UPSTATE_BROKE) icon_state = "apc-b" return ..() - if(update_state & UPSTATE_BLUESCREEN) - icon_state = "apcemag" - return ..() if(update_state & UPSTATE_WIREEXP) icon_state = "apcewires" return ..() @@ -85,8 +77,6 @@ if(cell) new_update_state |= UPSTATE_CELL_IN - else if((obj_flags & EMAGGED) || malfai) - new_update_state |= UPSTATE_BLUESCREEN else if(panel_open) new_update_state |= UPSTATE_WIREEXP @@ -116,3 +106,16 @@ // Used in process so it doesn't update the icon too much /obj/machinery/power/apc/proc/queue_icon_update() icon_update_needed = TRUE + +// Shows a dark-blue interface for a moment. Shouldn't appear on cameras. +/obj/machinery/power/apc/proc/flicker_hacked_icon() + var/image/hacker_image = image(icon = 'icons/obj/machines/wallmounts.dmi', loc = src, icon_state = "apcemag", layer = FLOAT_LAYER) + var/list/mobs_to_show = list() + // Collecting mobs the APC can see for this animation, rather than mobs that can see the APC. Important distinction, intended such that mobs on camera / with XRAY cannot see the flicker. + for(var/mob/viewer in view(src)) + if(viewer.client) + mobs_to_show += viewer.client + if(malfai?.client) + mobs_to_show |= malfai.client + flick_overlay_global(hacker_image, mobs_to_show, 1 SECONDS) + hacked_flicker_counter = rand(3, 5) //The counter is decrimented in the process() proc, which runs every two seconds. diff --git a/code/modules/power/apc/apc_attack.dm b/code/modules/power/apc/apc_attack.dm index 3ff3d640c6240..509eb4f05b90d 100644 --- a/code/modules/power/apc/apc_attack.dm +++ b/code/modules/power/apc/apc_attack.dm @@ -260,10 +260,6 @@ user.visible_message(span_notice("[user] removes \the [cell] from [src]!")) balloon_alert(user, "cell removed") user.put_in_hands(cell) - cell.update_appearance() - cell = null - charging = APC_NOT_CHARGING - update_appearance() return if((machine_stat & MAINT) && !opened) //no board; no interface return @@ -303,6 +299,8 @@ return TRUE /obj/machinery/power/apc/proc/set_broken() + if(machine_stat & BROKEN) + return if(malfai && operating) malfai.malf_picker.processing_time = clamp(malfai.malf_picker.processing_time - 10,0,1000) operating = FALSE diff --git a/code/modules/power/apc/apc_main.dm b/code/modules/power/apc/apc_main.dm index 9bfdaf1e249b8..c36d9c108e28c 100644 --- a/code/modules/power/apc/apc_main.dm +++ b/code/modules/power/apc/apc_main.dm @@ -71,6 +71,8 @@ var/malfhack = FALSE //New var for my changes to AI malf. --NeoFite ///Reference to our ai hacker var/mob/living/silicon/ai/malfai = null //See above --NeoFite + ///Counter for displaying the hacked overlay to mobs within view + var/hacked_flicker_counter = 0 ///State of the electronics inside (missing, installed, secured) var/has_electronics = APC_ELECTRONICS_MISSING ///used for the Blackout malf module @@ -154,6 +156,10 @@ offset_old = pixel_x pixel_x = -APC_PIXEL_OFFSET + hud_list = list( + MALF_APC_HUD = image(icon = 'icons/mob/huds/hud.dmi', icon_state = "apc_hacked", pixel_x = src.pixel_x, pixel_y = src.pixel_y) + ) + //Assign it to its area. If mappers already assigned an area string fast load the area from it else get the current area var/area/our_area = get_area(loc) if(areastring) @@ -227,7 +233,6 @@ QDEL_NULL(cell) if(terminal) disconnect_terminal() - return ..() /obj/machinery/power/apc/proc/assign_to_area(area/target_area = get_area(src)) @@ -264,6 +269,7 @@ /obj/machinery/power/apc/Exited(atom/movable/gone, direction) . = ..() if(gone == cell) + cell.update_appearance() cell = null charging = APC_NOT_CHARGING update_appearance() @@ -293,9 +299,7 @@ else . += "The cover is closed." -/obj/machinery/power/apc/deconstruct(disassembled = TRUE) - if(flags_1 & NODECONSTRUCT_1) - return +/obj/machinery/power/apc/on_deconstruction(disassembled = TRUE) if(!(machine_stat & BROKEN)) set_broken() if(opened != APC_COVER_REMOVED) @@ -457,11 +461,13 @@ update() if("emergency_lighting") emergency_lights = !emergency_lights - for(var/obj/machinery/light/L in area) - if(!initial(L.no_low_power)) //If there was an override set on creation, keep that override - L.no_low_power = emergency_lights - INVOKE_ASYNC(L, TYPE_PROC_REF(/obj/machinery/light/, update), FALSE) - CHECK_TICK + for (var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists()) + for(var/turf/area_turf as anything in zlevel_turfs) + for(var/obj/machinery/light/area_light in area_turf) + if(!initial(area_light.no_low_power)) //If there was an override set on creation, keep that override + area_light.no_low_power = emergency_lights + INVOKE_ASYNC(area_light, TYPE_PROC_REF(/obj/machinery/light/, update), FALSE) + CHECK_TICK return TRUE /obj/machinery/power/apc/ui_close(mob/user) @@ -481,6 +487,11 @@ force_update = TRUE return + if(obj_flags & EMAGGED || malfai) + hacked_flicker_counter = hacked_flicker_counter - 1 + if(hacked_flicker_counter <= 0) + flicker_hacked_icon() + //dont use any power from that channel if we shut that power channel off lastused_light = APC_CHANNEL_IS_ON(lighting) ? area.power_usage[AREA_USAGE_LIGHT] + area.power_usage[AREA_USAGE_STATIC_LIGHT] : 0 lastused_equip = APC_CHANNEL_IS_ON(equipment) ? area.power_usage[AREA_USAGE_EQUIP] + area.power_usage[AREA_USAGE_STATIC_EQUIP] : 0 @@ -654,10 +665,12 @@ INVOKE_ASYNC(src, PROC_REF(break_lights)) /obj/machinery/power/apc/proc/break_lights() - for(var/obj/machinery/light/breaked_light in area) - breaked_light.on = TRUE - breaked_light.break_light_tube() - stoplag() + for (var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists()) + for(var/turf/area_turf as anything in zlevel_turfs) + for(var/obj/machinery/light/breaked_light in area_turf) + breaked_light.on = TRUE + breaked_light.break_light_tube() + stoplag() /obj/machinery/power/apc/should_atmos_process(datum/gas_mixture/air, exposed_temperature) return (exposed_temperature > 2000) diff --git a/code/modules/power/apc/apc_malf.dm b/code/modules/power/apc/apc_malf.dm index f13b588842a8b..62134de146e82 100644 --- a/code/modules/power/apc/apc_malf.dm +++ b/code/modules/power/apc/apc_malf.dm @@ -37,7 +37,7 @@ if(!is_station_level(z)) return malf.ShutOffDoomsdayDevice() - occupier = new /mob/living/silicon/ai(src, malf.laws, malf) //DEAR GOD WHY? //IKR???? + occupier = new /mob/living/silicon/ai(src, malf.laws.copy_lawset(), malf) //DEAR GOD WHY? //IKR???? occupier.adjustOxyLoss(malf.getOxyLoss()) if(!findtext(occupier.name, "APC Copy")) occupier.name = "[malf.name] APC Copy" @@ -69,7 +69,7 @@ if(forced) occupier.forceMove(drop_location()) INVOKE_ASYNC(occupier, TYPE_PROC_REF(/mob/living, death)) - occupier.gib() + occupier.gib(DROP_ALL_REMAINS) if(!occupier.nuking) //Pinpointers go back to tracking the nuke disk, as long as the AI (somehow) isn't mid-nuking. for(var/obj/item/pinpointer/nuke/disk_pinpointers in GLOB.pinpointer_list) diff --git a/code/modules/power/apc/apc_power_proc.dm b/code/modules/power/apc/apc_power_proc.dm index b49c0ba0a74d9..52a671f00f5ae 100644 --- a/code/modules/power/apc/apc_power_proc.dm +++ b/code/modules/power/apc/apc_power_proc.dm @@ -138,8 +138,10 @@ if(nightshift_lights == on) return //no change nightshift_lights = on - for(var/obj/machinery/light/night_light in area) - if(night_light.nightshift_allowed) - night_light.nightshift_enabled = nightshift_lights - night_light.update(FALSE) - CHECK_TICK + for (var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists()) + for(var/turf/area_turf as anything in zlevel_turfs) + for(var/obj/machinery/light/night_light in area_turf) + if(night_light.nightshift_allowed) + night_light.nightshift_enabled = nightshift_lights + night_light.update(FALSE) + CHECK_TICK diff --git a/code/modules/power/apc/apc_tool_act.dm b/code/modules/power/apc/apc_tool_act.dm index 8884786a4eb0f..e712f47446481 100644 --- a/code/modules/power/apc/apc_tool_act.dm +++ b/code/modules/power/apc/apc_tool_act.dm @@ -77,6 +77,7 @@ return toggle_panel_open() balloon_alert(user, "wires [panel_open ? "exposed" : "unexposed"]") + W.play_tool_sound(src) update_appearance() return @@ -130,7 +131,7 @@ if(welder.use_tool(src, user, 4 SECONDS, volume = 50)) update_integrity(min(atom_integrity += 50,max_integrity)) balloon_alert(user, "repaired") - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS //disassembling the frame if(!opened || has_electronics || terminal) @@ -161,26 +162,25 @@ if(machine_stat & BROKEN) balloon_alert(user, "frame is too damaged!") return FALSE - return list("mode" = RCD_WALLFRAME, "delay" = 2 SECONDS, "cost" = 1) + return list("delay" = 2 SECONDS, "cost" = 1) if(!cell) if(machine_stat & MAINT) balloon_alert(user, "no board for a cell!") return FALSE - return list("mode" = RCD_WALLFRAME, "delay" = 5 SECONDS, "cost" = 10) + return list("delay" = 5 SECONDS, "cost" = 10) balloon_alert(user, "has both board and cell!") return FALSE -/obj/machinery/power/apc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - if(!(the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS) || passed_mode != RCD_WALLFRAME) +/obj/machinery/power/apc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) + if(!(the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS) || rcd_data["[RCD_DESIGN_MODE]"] != RCD_WALLFRAME) return FALSE if(!has_electronics) if(machine_stat & BROKEN) balloon_alert(user, "frame is too damaged!") return - user.visible_message(span_notice("[user] fabricates a circuit and places it into [src].")) balloon_alert(user, "control board placed") has_electronics = TRUE locked = TRUE @@ -194,8 +194,7 @@ C.forceMove(src) cell = C chargecount = 0 - user.visible_message(span_notice("[user] fabricates a weak power cell and places it into [src]."), \ - span_warning("Your [the_rcd.name] whirrs with strain as you create a weak power cell and place it into [src]!")) + balloon_alert(user, "power cell installed") update_appearance() return TRUE @@ -222,6 +221,7 @@ locked = FALSE balloon_alert(user, "interface damaged") update_appearance() + flicker_hacked_icon() return TRUE // damage and destruction acts diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index 07948e3c4d08f..4480dc981efc3 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -142,7 +142,7 @@ GLOBAL_LIST_INIT(wire_node_generating_types, typecacheof(list(/obj/structure/gri return ..() // then go ahead and delete the cable /obj/structure/cable/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) var/obj/item/stack/cable_coil/cable = new(drop_location(), 1) cable.set_cable_color(cable_color) qdel(src) @@ -439,7 +439,7 @@ GLOBAL_LIST_INIT(wire_node_generating_types, typecacheof(list(/obj/structure/gri throw_speed = 3 throw_range = 5 mats_per_unit = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*0.1, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.1) - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BELT attack_verb_continuous = list("whips", "lashes", "disciplines", "flogs") attack_verb_simple = list("whip", "lash", "discipline", "flog") @@ -464,7 +464,7 @@ GLOBAL_LIST_INIT(wire_node_generating_types, typecacheof(list(/obj/structure/gri /obj/item/stack/cable_coil/examine(mob/user) . = ..() - . += "Ctrl+Click to change the layer you are placing on." + . += "Use it in hand to change the layer you are placing on, amongst other things." /obj/item/stack/cable_coil/update_name() . = ..() diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index aa7b216d39182..ec6c23b00c6f6 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -20,18 +20,18 @@ throw_speed = 2 throw_range = 5 w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*7, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5) + grind_results = list(/datum/reagent/lithium = 15, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) ///Current charge in cell units var/charge = 0 ///Maximum charge in cell units - var/maxcharge = 1000 - custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*7, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5) - grind_results = list(/datum/reagent/lithium = 15, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) + var/maxcharge = STANDARD_CELL_CHARGE ///If the cell has been booby-trapped by injecting it with plasma. Chance on use() to explode. var/rigged = FALSE ///If the power cell was damaged by an explosion, chance for it to become corrupted and function the same as rigged. var/corrupted = FALSE ///how much power is given every tick in a recharger - var/chargerate = 100 + var/chargerate = STANDARD_CELL_CHARGE * 0.1 ///If true, the cell will state it's maximum charge in it's description var/ratingdesc = TRUE ///If it's a grown that acts as a battery, add a wire overlay to it. @@ -51,7 +51,7 @@ create_reagents(5, INJECTABLE | DRAINABLE) if (override_maxcharge) maxcharge = override_maxcharge - rating = max(round(maxcharge / 10000, 1), 1) + rating = max(round(maxcharge / (STANDARD_CELL_CHARGE * 10), 1), 1) if(!charge) charge = maxcharge if(empty) @@ -82,7 +82,7 @@ . = COMPONENT_ITEM_CHARGED if(prob(80)) - maxcharge -= 200 + maxcharge -= STANDARD_CELL_CHARGE * 0.2 if(maxcharge <= 1) // Div by 0 protection maxcharge = 1 @@ -281,7 +281,7 @@ /obj/item/stock_parts/cell/crap name = "\improper Nanotrasen brand rechargeable AA battery" desc = "You can't top the plasma top." //TOTALLY TRADEMARK INFRINGEMENT - maxcharge = 500 + maxcharge = STANDARD_CELL_CHARGE * 0.5 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.4) /obj/item/stock_parts/cell/crap/empty @@ -290,18 +290,18 @@ /obj/item/stock_parts/cell/upgraded name = "upgraded power cell" desc = "A power cell with a slightly higher capacity than normal!" - maxcharge = 2500 + maxcharge = STANDARD_CELL_CHARGE * 2.5 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5) - chargerate = 1000 + chargerate = STANDARD_CELL_CHARGE /obj/item/stock_parts/cell/upgraded/plus name = "upgraded power cell+" desc = "A power cell with an even higher capacity than the base model!" - maxcharge = 5000 + maxcharge = STANDARD_CELL_CHARGE * 5 /obj/item/stock_parts/cell/secborg name = "security borg rechargeable D battery" - maxcharge = 600 //600 max charge / 100 charge per shot = six shots + maxcharge = STANDARD_CELL_CHARGE * 0.6 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.4) /obj/item/stock_parts/cell/secborg/empty @@ -309,38 +309,38 @@ /obj/item/stock_parts/cell/mini_egun name = "miniature energy gun power cell" - maxcharge = 600 + maxcharge = STANDARD_CELL_CHARGE * 0.6 /obj/item/stock_parts/cell/hos_gun name = "X-01 multiphase energy gun power cell" - maxcharge = 1200 + maxcharge = STANDARD_CELL_CHARGE * 1.2 /obj/item/stock_parts/cell/pulse //200 pulse shots name = "pulse rifle power cell" - maxcharge = 40000 - chargerate = 1500 + maxcharge = STANDARD_CELL_CHARGE * 40 + chargerate = STANDARD_CELL_CHARGE * 1.5 /obj/item/stock_parts/cell/pulse/carbine //25 pulse shots name = "pulse carbine power cell" - maxcharge = 5000 + maxcharge = STANDARD_CELL_CHARGE * 5 /obj/item/stock_parts/cell/pulse/pistol //10 pulse shots name = "pulse pistol power cell" - maxcharge = 2000 + maxcharge = STANDARD_CELL_CHARGE * 2 /obj/item/stock_parts/cell/ninja name = "black power cell" icon_state = "bscell" - maxcharge = 10000 + maxcharge = STANDARD_CELL_CHARGE * 10 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.6) - chargerate = 2000 + chargerate = STANDARD_CELL_CHARGE * 2 /obj/item/stock_parts/cell/high name = "high-capacity power cell" icon_state = "hcell" - maxcharge = 10000 + maxcharge = STANDARD_CELL_CHARGE * 10 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.6) - chargerate = 1500 + chargerate = STANDARD_CELL_CHARGE * 1.5 /obj/item/stock_parts/cell/high/empty empty = TRUE @@ -348,9 +348,9 @@ /obj/item/stock_parts/cell/super name = "super-capacity power cell" icon_state = "scell" - maxcharge = 20000 + maxcharge = STANDARD_CELL_CHARGE * 20 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT * 3) - chargerate = 2000 + chargerate = STANDARD_CELL_CHARGE * 2 /obj/item/stock_parts/cell/super/empty empty = TRUE @@ -358,9 +358,9 @@ /obj/item/stock_parts/cell/hyper name = "hyper-capacity power cell" icon_state = "hpcell" - maxcharge = 30000 + maxcharge = STANDARD_CELL_CHARGE * 30 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT * 4) - chargerate = 3000 + chargerate = STANDARD_CELL_CHARGE * 3 /obj/item/stock_parts/cell/hyper/empty empty = TRUE @@ -369,9 +369,9 @@ name = "bluespace power cell" desc = "A rechargeable transdimensional power cell." icon_state = "bscell" - maxcharge = 40000 + maxcharge = STANDARD_CELL_CHARGE * 40 custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*6) - chargerate = 4000 + chargerate = STANDARD_CELL_CHARGE * 4 /obj/item/stock_parts/cell/bluespace/empty empty = TRUE @@ -392,7 +392,7 @@ desc = "An alien power cell that produces energy seemingly out of nowhere." icon = 'icons/obj/antags/abductor.dmi' icon_state = "cell" - maxcharge = 50000 + maxcharge = STANDARD_CELL_CHARGE * 50 ratingdesc = FALSE /obj/item/stock_parts/cell/infinite/abductor/Initialize(mapload) @@ -405,7 +405,7 @@ icon = 'icons/obj/service/hydroponics/harvest.dmi' icon_state = "potato" charge = 100 - maxcharge = 300 + maxcharge = STANDARD_CELL_CHARGE * 0.3 charge_light_type = null connector_type = null custom_materials = null @@ -415,7 +415,7 @@ /obj/item/stock_parts/cell/emproof name = "\improper EMP-proof cell" desc = "An EMP-proof cell." - maxcharge = 500 + maxcharge = STANDARD_CELL_CHARGE * 0.5 /obj/item/stock_parts/cell/emproof/Initialize(mapload) AddElement(/datum/element/empprotection, EMP_PROTECT_SELF) @@ -433,15 +433,15 @@ icon = 'icons/mob/simple/slimes.dmi' icon_state = "yellow slime extract" custom_materials = null - maxcharge = 5000 + maxcharge = STANDARD_CELL_CHARGE * 5 charge_light_type = null connector_type = "slimecore" /obj/item/stock_parts/cell/beam_rifle name = "beam rifle capacitor" desc = "A high powered capacitor that can provide huge amounts of energy in an instant." - maxcharge = 50000 - chargerate = 5000 //Extremely energy intensive + maxcharge = STANDARD_CELL_CHARGE * 50 + chargerate = STANDARD_CELL_CHARGE * 5 //Extremely energy intensive /obj/item/stock_parts/cell/beam_rifle/corrupt() return @@ -455,7 +455,7 @@ /obj/item/stock_parts/cell/emergency_light name = "miniature power cell" desc = "A tiny power cell with a very low power capacity. Used in light fixtures to power them in the event of an outage." - maxcharge = 120 //Emergency lights use 0.2 W per tick, meaning ~10 minutes of emergency power from a cell + maxcharge = STANDARD_CELL_CHARGE * 0.12 //Emergency lights use 0.2 W per tick, meaning ~10 minutes of emergency power from a cell custom_materials = list(/datum/material/glass = SMALL_MATERIAL_AMOUNT*0.2) w_class = WEIGHT_CLASS_TINY @@ -470,7 +470,7 @@ name = "crystal power cell" desc = "A very high power cell made from crystallized plasma" icon_state = "crystal_cell" - maxcharge = 50000 + maxcharge = STANDARD_CELL_CHARGE * 50 chargerate = 0 charge_light_type = null connector_type = "crystal" @@ -478,7 +478,7 @@ grind_results = null /obj/item/stock_parts/cell/inducer_supply - maxcharge = 5000 + maxcharge = STANDARD_CELL_CHARGE * 5 #undef CELL_DRAIN_TIME #undef CELL_POWER_GAIN diff --git a/code/modules/power/floodlight.dm b/code/modules/power/floodlight.dm index f36c9b1303866..d3c5c1de569ae 100644 --- a/code/modules/power/floodlight.dm +++ b/code/modules/power/floodlight.dm @@ -120,6 +120,12 @@ return ..() +/obj/structure/floodlight_frame/completed + name = "floodlight frame" + desc = "A bare metal frame that looks like a floodlight. Requires a light tube to complete." + icon_state = "floodlight_c3" + state = FLOODLIGHT_NEEDS_LIGHTS + /obj/machinery/power/floodlight name = "floodlight" desc = "A pole with powerful mounted lights on it. Due to its high power draw, it must be powered by a direct connection to a wire node." @@ -255,7 +261,7 @@ connect_to_network() else disconnect_from_network() - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/power/floodlight/screwdriver_act(mob/living/user, obj/item/tool) . = ..() diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm deleted file mode 100644 index 6fa17d8dbe91a..0000000000000 --- a/code/modules/power/generator.dm +++ /dev/null @@ -1,232 +0,0 @@ -/obj/machinery/power/generator - name = "thermoelectric generator" - desc = "It's a high efficiency thermoelectric generator." - icon_state = "teg" - density = TRUE - use_power = NO_POWER_USE - - circuit = /obj/item/circuitboard/machine/generator - - var/obj/machinery/atmospherics/components/binary/circulator/cold_circ - var/obj/machinery/atmospherics/components/binary/circulator/hot_circ - - var/lastgen = 0 - var/lastgenlev = -1 - var/lastcirc = "00" - - -/obj/machinery/power/generator/Initialize(mapload) - . = ..() - AddComponent(/datum/component/simple_rotation) - find_circs() - connect_to_network() - SSair.start_processing_machine(src) - update_appearance() - -/obj/machinery/power/generator/Destroy() - kill_circs() - SSair.stop_processing_machine(src) - return ..() - -/obj/machinery/power/generator/update_overlays() - . = ..() - if(machine_stat & (NOPOWER|BROKEN)) - return - - var/L = min(round(lastgenlev / 100000), 11) - if(L != 0) - . += mutable_appearance('icons/obj/machines/engine/other.dmi', "teg-op[L]") - if(hot_circ && cold_circ) - . += "teg-oc[lastcirc]" - - -#define GENRATE 800 // generator output coefficient from Q - -/obj/machinery/power/generator/process_atmos() - - if(!cold_circ || !hot_circ) - return - - if(powernet) - var/datum/gas_mixture/cold_air = cold_circ.return_transfer_air() - var/datum/gas_mixture/hot_air = hot_circ.return_transfer_air() - - if(cold_air && hot_air) - - var/cold_air_heat_capacity = cold_air.heat_capacity() - var/hot_air_heat_capacity = hot_air.heat_capacity() - - var/delta_temperature = hot_air.temperature - cold_air.temperature - - - if(delta_temperature > 0 && cold_air_heat_capacity > 0 && hot_air_heat_capacity > 0) - var/efficiency = 0.65 - - var/energy_transfer = delta_temperature*hot_air_heat_capacity*cold_air_heat_capacity/(hot_air_heat_capacity+cold_air_heat_capacity) - - var/heat = energy_transfer*(1-efficiency) - lastgen += energy_transfer*efficiency - - hot_air.temperature = hot_air.temperature - energy_transfer/hot_air_heat_capacity - cold_air.temperature = cold_air.temperature + heat/cold_air_heat_capacity - - //add_avail(lastgen) This is done in process now - // update icon overlays only if displayed level has changed - - if(hot_air) - var/datum/gas_mixture/hot_circ_air1 = hot_circ.airs[1] - hot_circ_air1.merge(hot_air) - - if(cold_air) - var/datum/gas_mixture/cold_circ_air1 = cold_circ.airs[1] - cold_circ_air1.merge(cold_air) - - update_appearance() - - var/circ = "[cold_circ?.last_pressure_delta > 0 ? "1" : "0"][hot_circ?.last_pressure_delta > 0 ? "1" : "0"]" - if(circ != lastcirc) - lastcirc = circ - update_appearance() - - src.updateDialog() - -/obj/machinery/power/generator/process() - //Setting this number higher just makes the change in power output slower, it doesnt actualy reduce power output cause **math** - var/power_output = round(lastgen / 10) - add_avail(power_output) - lastgenlev = power_output - lastgen -= power_output - ..() - -/obj/machinery/power/generator/proc/get_menu(include_link = TRUE) - var/t = "" - if(!powernet) - t += "Unable to connect to the power network!" - else if(cold_circ && hot_circ) - var/datum/gas_mixture/cold_circ_air1 = cold_circ.airs[1] - var/datum/gas_mixture/cold_circ_air2 = cold_circ.airs[2] - var/datum/gas_mixture/hot_circ_air1 = hot_circ.airs[1] - var/datum/gas_mixture/hot_circ_air2 = hot_circ.airs[2] - - t += "
" - - t += "Output: [display_power(lastgenlev)]" - - t += "
" - - t += "Cold loop
" - t += "Temperature Inlet: [round(cold_circ_air2.temperature, 0.1)] K / Outlet: [round(cold_circ_air1.temperature, 0.1)] K
" - t += "Pressure Inlet: [round(cold_circ_air2.return_pressure(), 0.1)] kPa / Outlet: [round(cold_circ_air1.return_pressure(), 0.1)] kPa
" - - t += "Hot loop
" - t += "Temperature Inlet: [round(hot_circ_air2.temperature, 0.1)] K / Outlet: [round(hot_circ_air1.temperature, 0.1)] K
" - t += "Pressure Inlet: [round(hot_circ_air2.return_pressure(), 0.1)] kPa / Outlet: [round(hot_circ_air1.return_pressure(), 0.1)] kPa
" - - t += "
" - else if(!hot_circ && cold_circ) - t += "Unable to locate hot circulator!" - else if(hot_circ && !cold_circ) - t += "Unable to locate cold circulator!" - else - t += "Unable to locate any parts!" - if(include_link) - t += "
Close" - - return t - -/obj/machinery/power/generator/ui_interact(mob/user) - . = ..() - var/datum/browser/popup = new(user, "teg", "Thermo-Electric Generator", 460, 300) - popup.set_content(get_menu()) - popup.open() - -/obj/machinery/power/generator/Topic(href, href_list) - if(..()) - return - if( href_list["close"] ) - usr << browse(null, "window=teg") - usr.unset_machine() - return FALSE - return TRUE - - - -/obj/machinery/power/generator/proc/find_circs() - kill_circs() - var/list/circs = list() - var/obj/machinery/atmospherics/components/binary/circulator/C - var/circpath = /obj/machinery/atmospherics/components/binary/circulator - if(dir == NORTH || dir == SOUTH) - C = locate(circpath) in get_step(src, EAST) - if(C && C.dir == WEST) - circs += C - - C = locate(circpath) in get_step(src, WEST) - if(C && C.dir == EAST) - circs += C - - else - C = locate(circpath) in get_step(src, NORTH) - if(C && C.dir == SOUTH) - circs += C - - C = locate(circpath) in get_step(src, SOUTH) - if(C && C.dir == NORTH) - circs += C - - if(circs.len) - for(C in circs) - if(C.mode == CIRCULATOR_COLD && !cold_circ) - cold_circ = C - C.generator = src - else if(C.mode == CIRCULATOR_HOT && !hot_circ) - hot_circ = C - C.generator = src - -/obj/machinery/power/generator/wrench_act(mob/living/user, obj/item/I) - . = ..() - if(!panel_open) - return - set_anchored(!anchored) - I.play_tool_sound(src) - if(!anchored) - kill_circs() - connect_to_network() - to_chat(user, span_notice("You [anchored?"secure":"unsecure"] [src].")) - return TRUE - -/obj/machinery/power/generator/multitool_act(mob/living/user, obj/item/I) - . = ..() - if(!anchored) - return - find_circs() - to_chat(user, span_notice("You update [src]'s circulator links.")) - return TRUE - -/obj/machinery/power/generator/screwdriver_act(mob/user, obj/item/I) - if(..()) - return TRUE - toggle_panel_open() - I.play_tool_sound(src) - to_chat(user, span_notice("You [panel_open?"open":"close"] the panel on [src].")) - return TRUE - -/obj/machinery/power/generator/crowbar_act(mob/user, obj/item/I) - default_deconstruction_crowbar(I) - return TRUE - -/obj/machinery/power/generator/AltClick(mob/user) - return ..() // This hotkey is BLACKLISTED since it's used by /datum/component/simple_rotation - -/obj/machinery/power/generator/on_deconstruction() - kill_circs() - -/obj/machinery/power/generator/proc/kill_circs() - if(hot_circ) - hot_circ.generator = null - hot_circ = null - if(cold_circ) - cold_circ.generator = null - cold_circ = null - -#undef GENRATE diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm index 4d32559ce7192..1e561c6030792 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -190,7 +190,6 @@ GLOBAL_LIST_EMPTY(gravity_generators) if(count <= 3) // Their sprite is the top part of the generator part.set_density(FALSE) part.layer = WALL_OBJ_LAYER - SET_PLANE(part, GAME_PLANE_UPPER, our_turf) part.sprite_number = count part.main_part = src generator_parts += part diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm index 747010fc6c006..45a8c71d7652a 100644 --- a/code/modules/power/lighting/light.dm +++ b/code/modules/power/lighting/light.dm @@ -5,7 +5,6 @@ icon_state = "tube" desc = "A lighting fixture." layer = WALL_OBJ_LAYER - plane = GAME_PLANE_UPPER max_integrity = 100 use_power = ACTIVE_POWER_USE idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.02 @@ -73,7 +72,9 @@ ///The minimum value for the light's power in low power mode var/bulb_low_power_pow_min = 0.5 ///The Light range to use when working in fire alarm status - var/fire_brightness = 4 + var/fire_brightness = 9 + ///The Light power to use when working in fire alarm status + var/fire_power = 0.5 ///The Light colour to use when working in fire alarm status var/fire_colour = COLOR_FIRE_LIGHT_RED @@ -101,7 +102,7 @@ qdel(on_turf) if(!mapload) //sync up nightshift lighting for player made lights - var/area/our_area = get_room_area(src) + var/area/our_area = get_room_area() var/obj/machinery/power/apc/temp_apc = our_area.apc nightshift_enabled = temp_apc?.nightshift_lights @@ -131,7 +132,7 @@ update(trigger = FALSE) /obj/machinery/light/Destroy() - var/area/local_area = get_room_area(src) + var/area/local_area = get_room_area() if(local_area) on = FALSE QDEL_NULL(cell) @@ -154,7 +155,7 @@ /obj/machinery/light/update_icon_state() switch(status) // set icon_states if(LIGHT_OK) - var/area/local_area =get_room_area(src) + var/area/local_area = get_room_area() if(low_power_mode || major_emergency || (local_area?.fire)) icon_state = "[base_state]_emergency" else @@ -174,7 +175,7 @@ . += emissive_appearance(overlay_icon, "[base_state]", src, alpha = src.alpha) - var/area/local_area = get_room_area(src) + var/area/local_area = get_room_area() if(low_power_mode || major_emergency || (local_area?.fire)) . += mutable_appearance(overlay_icon, "[base_state]_emergency") @@ -190,7 +191,7 @@ . = ..() if(!.) return - var/area/our_area = get_room_area(src) + var/area/our_area = get_room_area() RegisterSignal(our_area, COMSIG_AREA_FIRE_CHANGED, PROC_REF(handle_fire)) /obj/machinery/light/on_enter_area(datum/source, area/area_to_register) @@ -220,9 +221,10 @@ color_set = color if(reagents) START_PROCESSING(SSmachines, src) - var/area/local_area =get_room_area(src) + var/area/local_area = get_room_area() if (local_area?.fire) color_set = fire_colour + power_set = fire_power brightness_set = fire_brightness else if (nightshift_enabled) brightness_set = nightshift_brightness @@ -262,7 +264,7 @@ static_power_used = 0 else if(on) //Light is on, just recalculate usage var/static_power_used_new = 0 - var/area/local_area = get_room_area(src) + var/area/local_area = get_room_area() if (nightshift_enabled && !local_area?.fire) static_power_used_new = nightshift_brightness * nightshift_light_power * power_consumption_rate else @@ -377,15 +379,12 @@ deconstruct() return to_chat(user, span_userdanger("You stick \the [tool] into the light socket!")) - if(has_power() && (tool.flags_1 & CONDUCT_1)) + if(has_power() && (tool.obj_flags & CONDUCTS_ELECTRICITY)) do_sparks(3, TRUE, src) if (prob(75)) electrocute_mob(user, get_area(src), src, (rand(7,10) * 0.1), TRUE) -/obj/machinery/light/deconstruct(disassembled = TRUE) - if(flags_1 & NODECONSTRUCT_1) - qdel(src) - return +/obj/machinery/light/on_deconstruction(disassembled) var/obj/structure/light_construct/new_light = null var/current_stage = 2 if(!disassembled) @@ -414,13 +413,12 @@ new_light.cell = real_cell real_cell.forceMove(new_light) cell = null - qdel(src) /obj/machinery/light/attacked_by(obj/item/attacking_object, mob/living/user) ..() if(status != LIGHT_BROKEN && status != LIGHT_EMPTY) return - if(!on || !(attacking_object.flags_1 & CONDUCT_1)) + if(!on || !(attacking_object.obj_flags & CONDUCTS_ELECTRICITY)) return if(prob(12)) electrocute_mob(user, get_area(src), src, 0.3, TRUE) @@ -447,13 +445,13 @@ // returns if the light has power /but/ is manually turned off // if a light is turned off, it won't activate emergency power /obj/machinery/light/proc/turned_off() - var/area/local_area = get_room_area(src) + var/area/local_area = get_room_area() return !local_area.lightswitch && local_area.power_light || flickering // returns whether this light has power // true if area has power and lightswitch is on /obj/machinery/light/proc/has_power() - var/area/local_area =get_room_area(src) + var/area/local_area = get_room_area() return local_area.lightswitch && local_area.power_light // returns whether this light has emergency power @@ -655,7 +653,7 @@ // called when area power state changes /obj/machinery/light/power_change() SHOULD_CALL_PARENT(FALSE) - var/area/local_area =get_room_area(src) + var/area/local_area = get_room_area() set_on(local_area.lightswitch && local_area.power_light) // called when heated @@ -716,7 +714,7 @@ light_type = /obj/item/light/bulb fitting = "bulb" nightshift_brightness = 3 - fire_brightness = 2 + fire_brightness = 4.5 /obj/machinery/light/floor/get_light_offset() return list(0, 0) diff --git a/code/modules/power/lighting/light_construct.dm b/code/modules/power/lighting/light_construct.dm index 05d9533c79ea7..905ae72c2e38b 100644 --- a/code/modules/power/lighting/light_construct.dm +++ b/code/modules/power/lighting/light_construct.dm @@ -5,7 +5,6 @@ icon_state = "tube-construct-stage1" anchored = TRUE layer = WALL_OBJ_LAYER - plane = GAME_PLANE_UPPER max_integrity = 200 armor_type = /datum/armor/structure_light_construct @@ -164,7 +163,7 @@ qdel(src) /obj/structure/light_construct/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) new /obj/item/stack/sheet/iron(loc, sheets_refunded) qdel(src) diff --git a/code/modules/power/lighting/light_items.dm b/code/modules/power/lighting/light_items.dm index 9f2bff9cdca36..5e9df6ee432ee 100644 --- a/code/modules/power/lighting/light_items.dm +++ b/code/modules/power/lighting/light_items.dm @@ -116,7 +116,7 @@ if(!isliving(moving_atom)) return var/mob/living/moving_mob = moving_atom - if(!(moving_mob.movement_type & (FLYING|FLOATING)) || moving_mob.buckled) + if(!(moving_mob.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || moving_mob.buckled) playsound(src, 'sound/effects/footstep/glass_step.ogg', HAS_TRAIT(moving_mob, TRAIT_LIGHT_STEP) ? 30 : 50, TRUE) if(status == LIGHT_BURNED || status == LIGHT_OK) shatter(moving_mob) diff --git a/code/modules/power/lighting/light_mapping_helpers.dm b/code/modules/power/lighting/light_mapping_helpers.dm index a2171c8897c89..db11c77fbefb3 100644 --- a/code/modules/power/lighting/light_mapping_helpers.dm +++ b/code/modules/power/lighting/light_mapping_helpers.dm @@ -39,7 +39,7 @@ /obj/machinery/light/red/dim brightness = 4 bulb_power = 0.7 - fire_brightness = 2 + fire_brightness = 4.5 /obj/machinery/light/blacklight bulb_colour = "#A700FF" @@ -58,7 +58,7 @@ fitting = "bulb" brightness = 4 nightshift_brightness = 4 - fire_brightness = 3 + fire_brightness = 4.5 bulb_colour = "#FFD6AA" fire_colour = "#bd3f46" desc = "A small lighting fixture." @@ -85,13 +85,13 @@ /obj/machinery/light/small/red/dim brightness = 2 bulb_power = 0.8 - fire_brightness = 2 + fire_brightness = 2.5 /obj/machinery/light/small/blacklight bulb_colour = "#A700FF" nightshift_allowed = FALSE brightness = 4 - fire_brightness = 3 + fire_brightness = 4.5 fire_colour = "#d400ff" // -------- Directional presets diff --git a/code/modules/power/pipecleaners.dm b/code/modules/power/pipecleaners.dm index f052913c2e733..2f18bab660a1c 100644 --- a/code/modules/power/pipecleaners.dm +++ b/code/modules/power/pipecleaners.dm @@ -107,7 +107,7 @@ By design, d1 is the smallest direction and d2 is the highest return ..() // then go ahead and delete the pipe_cleaner /obj/structure/pipe_cleaner/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) var/turf/T = get_turf(loc) if(T) stored.forceMove(T) @@ -197,7 +197,7 @@ By design, d1 is the smallest direction and d2 is the highest throw_speed = 3 throw_range = 5 mats_per_unit = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*0.1, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.1) - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BELT attack_verb_continuous = list("whips", "lashes", "disciplines", "flogs") attack_verb_simple = list("whip", "lash", "discipline", "flog") diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index 39c3e53614403..f12da549da358 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -60,6 +60,8 @@ return can_change_cable_layer /obj/machinery/power/multitool_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(!can_change_cable_layer || !cable_layer_change_checks(user, tool)) return @@ -69,7 +71,7 @@ cable_layer = GLOB.cable_name_to_layer[choice] balloon_alert(user, "now operating on the [choice]") - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/power/multitool_act_secondary(mob/living/user, obj/item/tool) return multitool_act(user, tool) @@ -141,17 +143,17 @@ * - Amount: How much power the APC's cell is to be costed. */ /obj/machinery/proc/directly_use_power(amount) - var/area/A = get_area(src) - var/obj/machinery/power/apc/local_apc - if(!A) - return FALSE - local_apc = A.apc - if(!local_apc) + var/area/my_area = get_area(src) + if(isnull(my_area)) + stack_trace("machinery is somehow not in an area, nullspace?") return FALSE - if(!local_apc.cell) + if(!my_area.requires_power) + return TRUE + + var/obj/machinery/power/apc/my_apc = my_area.apc + if(isnull(my_apc)) return FALSE - local_apc.cell.use(amount) - return TRUE + return my_apc.cell.use(amount) /** * Attempts to draw power directly from the APC's Powernet rather than the APC's battery. For high-draw machines, like the cell charger diff --git a/code/modules/power/rtg.dm b/code/modules/power/rtg.dm index c49bc455165fa..f79eb808a8756 100644 --- a/code/modules/power/rtg.dm +++ b/code/modules/power/rtg.dm @@ -22,7 +22,6 @@ connect_to_network() /obj/machinery/power/rtg/process() - ..() add_avail(power_gen) /obj/machinery/power/rtg/RefreshParts() @@ -71,7 +70,7 @@ visible_message(span_danger("\The [src] lets out a shower of sparks as it starts to lose stability!"),\ span_hear("You hear a loud electrical crack!")) playsound(src.loc, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5) - tesla_zap(src, 5, power_gen * 0.05) + tesla_zap(source = src, zap_range = 5, power = power_gen * 20) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), src, 2, 3, 4, null, 8), 10 SECONDS) // Not a normal explosion. /obj/machinery/power/rtg/abductor/bullet_act(obj/projectile/Proj) diff --git a/code/modules/power/singularity/containment_field.dm b/code/modules/power/singularity/containment_field.dm index 52be991cb69d9..d9fc0671e31d7 100644 --- a/code/modules/power/singularity/containment_field.dm +++ b/code/modules/power/singularity/containment_field.dm @@ -123,7 +123,7 @@ if(isliving(mover)) shock(mover) return - if(ismachinery(mover) || isstructure(mover) || ismecha(mover)) + if(ismachinery(mover) || isstructure(mover) || isvehicle(mover)) bump_field(mover) return diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index c23c5782af159..c8fba21c09f5a 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -141,6 +141,9 @@ if(!active || !powernet) icon_state = base_icon_state return ..() + if(panel_open) + icon_state = "[base_icon_state]_open" + return ..() icon_state = avail(active_power_usage) ? icon_state_on : icon_state_underpowered return ..() @@ -263,7 +266,7 @@ /obj/machinery/power/emitter/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/power/emitter/welder_act(mob/living/user, obj/item/item) ..() @@ -310,7 +313,7 @@ /obj/machinery/power/emitter/screwdriver_act(mob/living/user, obj/item/item) if(..()) return TRUE - default_deconstruction_screwdriver(user, "emitter_open", "emitter", item) + default_deconstruction_screwdriver(user, "[base_icon_state]_open", base_icon_state, item) return TRUE /// Attempt to toggle the controls lock of the emitter @@ -422,7 +425,7 @@ return buckled_mob.forceMove(get_turf(src)) ..() - playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) + playsound(src, 'sound/mecha/mechmove01.ogg', 50, TRUE) buckled_mob.pixel_y = 14 layer = 4.1 if(buckled_mob.client) @@ -432,7 +435,7 @@ auto.Grant(buckled_mob, src) /datum/action/innate/proto_emitter - check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED ///Stores the emitter the user is currently buckled on var/obj/machinery/power/emitter/prototype/proto_emitter ///Stores the mob instance that is buckled to the emitter diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm index 80e5faeede255..5f17e0101c03f 100644 --- a/code/modules/power/singularity/field_generator.dm +++ b/code/modules/power/singularity/field_generator.dm @@ -128,7 +128,7 @@ no power level overlay is currently in the overlays list. /obj/machinery/field/generator/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/field/generator/welder_act(mob/living/user, obj/item/welder) . = ..() diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm index 99dd421452cf3..2de87210798d4 100644 --- a/code/modules/power/singularity/narsie.dm +++ b/code/modules/power/singularity/narsie.dm @@ -5,6 +5,8 @@ #define NARSIE_MESMERIZE_EFFECT 60 #define NARSIE_SINGULARITY_SIZE 12 +#define ADMIN_WARNING_MESSAGE "Invoking this will begin the Nar'Sie roundender. Assume that this WILL end the round in a few minutes. Are you sure?" + /// Nar'Sie, the God of the blood cultists /obj/narsie name = "Nar'Sie" @@ -39,6 +41,19 @@ /obj/narsie/Initialize(mapload) . = ..() + narsie_spawn_animation() + +/obj/narsie/Destroy() + if (GLOB.cult_narsie == src) + fall_of_the_harbinger() + GLOB.cult_narsie = null + + return ..() + +/// This proc sets up all of Nar'Sie's abilities, stats, and begins her round-ending capabilities. She does not do anything unless this proc is invoked. +/// This is only meant to be invoked after this instance is initialized in specific pro-sumer procs, as it WILL derail the entire round. +/obj/narsie/proc/start_ending_the_round() + GLOB.cult_narsie = src SSpoints_of_interest.make_point_of_interest(src) singularity = WEAKREF(AddComponent( @@ -58,10 +73,14 @@ var/area/area = get_area(src) if(area) var/mutable_appearance/alert_overlay = mutable_appearance('icons/effects/cult/effects.dmi', "ghostalertsie") - notify_ghosts("Nar'Sie has risen in [area]. Reach out to the Geometer to be given a new shell for your soul.", source = src, alert_overlay = alert_overlay, action = NOTIFY_ATTACK) - narsie_spawn_animation() + notify_ghosts( + "Nar'Sie has risen in [area]. Reach out to the Geometer to be given a new shell for your soul.", + source = src, + header = "Nar'Sie has risen!", + click_interact = TRUE, + alert_overlay = alert_overlay, + ) - GLOB.cult_narsie = src var/list/all_cults = list() for (var/datum/antagonist/cult/cultist in GLOB.antagonists) @@ -88,10 +107,8 @@ soul_goal = round(1 + LAZYLEN(souls_needed) * 0.75) INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(begin_the_end)) -/obj/narsie/Destroy() - send_to_playing_players(span_narsie("\"[pick("Nooooo...", "Not die. How-", "Die. Mort-", "Sas tyen re-")]\"")) - sound_to_playing_players('sound/magic/demon_dies.ogg', 50) - +/// Cleans up all of Nar'Sie's abilities, stats, and ends her round-ending capabilities. This should only be called if `start_ending_the_round()` successfully started. +/obj/narsie/proc/fall_of_the_harbinger() var/list/all_cults = list() for (var/datum/antagonist/cult/cultist in GLOB.antagonists) @@ -106,13 +123,31 @@ summon_objective.summoned = FALSE summon_objective.killed = TRUE - if (GLOB.cult_narsie == src) - GLOB.cult_narsie = null + send_to_playing_players(span_narsie(span_bold(pick("Nooooo...", "Not die. How-", "Die. Mort-", "Sas tyen re-")))) + sound_to_playing_players('sound/magic/demon_dies.ogg', 50) - return ..() +/obj/narsie/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION("", "---------") + VV_DROPDOWN_OPTION(VV_HK_BEGIN_NARSIE_ROUNDEND, "Begin Nar'Sie Roundender") + +/obj/narsie/vv_do_topic(list/href_list) + . = ..() + + if(!.) + return + + if(isnull(usr) || !href_list[VV_HK_BEGIN_NARSIE_ROUNDEND] || !check_rights(R_FUN, show_msg = TRUE)) + return + + if(tgui_alert(usr, ADMIN_WARNING_MESSAGE, "Begin Nar'Sie Roundender", list("I'm Sure", "Abort")) != "I'm Sure") + return + + log_admin("[key_name(usr)] has triggered the Nar'Sie roundender.") + start_ending_the_round() /obj/narsie/attack_ghost(mob/user) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, user, cultoverride = TRUE, loc_override = loc) + makeNewConstruct(/mob/living/basic/construct/harvester, user, cultoverride = TRUE, loc_override = loc) /obj/narsie/process() var/datum/component/singularity/singularity_component = singularity.resolve() @@ -218,21 +253,25 @@ ///First crew last second win check and flufftext for [/proc/begin_the_end()] /proc/narsie_end_begin_check() if(QDELETED(GLOB.cult_narsie)) // uno - priority_announce("Status report? We detected an anomaly, but it disappeared almost immediately.","Central Command Higher Dimensional Affairs", 'sound/misc/notice1.ogg') + priority_announce("Status report? We detected an anomaly, but it disappeared almost immediately.","[command_name()] Higher Dimensional Affairs", 'sound/misc/notice1.ogg') GLOB.cult_narsie = null addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cult_ending_helper), CULT_FAILURE_NARSIE_KILLED), 2 SECONDS) return - priority_announce("An acausal dimensional event has been detected in your sector. Event has been flagged EXTINCTION-CLASS. Directing all available assets toward simulating solutions. SOLUTION ETA: 60 SECONDS.","Central Command Higher Dimensional Affairs", 'sound/misc/airraid.ogg') + priority_announce( + text = "An acausal dimensional event has been detected in your sector. Event has been flagged EXTINCTION-CLASS. Directing all available assets toward simulating solutions. SOLUTION ETA: 60 SECONDS.", + title = "[command_name()] Higher Dimensional Affairs", + sound = 'sound/misc/airraid.ogg', + ) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(narsie_end_second_check)), 50 SECONDS) ///Second crew last second win check and flufftext for [/proc/begin_the_end()] /proc/narsie_end_second_check() if(QDELETED(GLOB.cult_narsie)) // dos - priority_announce("Simulations aborted, sensors report that the acasual event is normalizing. Good work, crew.","Central Command Higher Dimensional Affairs", 'sound/misc/notice1.ogg') + priority_announce("Simulations aborted, sensors report that the acasual event is normalizing. Good work, crew.","[command_name()] Higher Dimensional Affairs", 'sound/misc/notice1.ogg') GLOB.cult_narsie = null addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cult_ending_helper), CULT_FAILURE_NARSIE_KILLED), 2 SECONDS) return - priority_announce("Simulations on acausal dimensional event complete. Deploying solution package now. Deployment ETA: ONE MINUTE. ","Central Command Higher Dimensional Affairs") + priority_announce("Simulations on acausal dimensional event complete. Deploying solution package now. Deployment ETA: ONE MINUTE. ","[command_name()] Higher Dimensional Affairs") addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(narsie_start_destroy_station)), 5 SECONDS) ///security level and shuttle lockdowns for [/proc/begin_the_end()] @@ -245,7 +284,7 @@ ///Third crew last second win check and flufftext for [/proc/begin_the_end()] /proc/narsie_apocalypse() if(QDELETED(GLOB.cult_narsie)) // tres - priority_announce("Normalization detected! Abort the solution package!","Central Command Higher Dimensional Affairs", 'sound/misc/notice1.ogg') + priority_announce("Normalization detected! Abort the solution package!","[command_name()] Higher Dimensional Affairs", 'sound/misc/notice1.ogg') SSshuttle.clearHostileEnvironment(GLOB.cult_narsie) GLOB.cult_narsie = null addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(narsie_last_second_win)), 2 SECONDS) @@ -289,3 +328,5 @@ #undef NARSIE_MESMERIZE_CHANCE #undef NARSIE_MESMERIZE_EFFECT #undef NARSIE_SINGULARITY_SIZE + +#undef ADMIN_WARNING_MESSAGE diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 0a797a2d7d681..b21b26dcea0ae 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -79,14 +79,11 @@ notify_ghosts( ghost_notification_message, source = src, - action = NOTIFY_ORBIT, - flashwindow = FALSE, - ghost_sound = 'sound/machines/warning-buzzer.ogg', header = ghost_notification_message, - notify_volume = 75 + ghost_sound = 'sound/machines/warning-buzzer.ogg', + notify_volume = 75, ) - /obj/singularity/Destroy() STOP_PROCESSING(SSsinguloprocess, src) return ..() @@ -267,6 +264,11 @@ new_consume_range = 5 dissipate = FALSE + if(temp_allowed_size == STAGE_SIX) + AddComponent(/datum/component/vision_hurting) + else + qdel(GetComponent(/datum/component/vision_hurting)) + var/datum/component/singularity/resolved_singularity = singularity_component.resolve() if (!isnull(resolved_singularity)) resolved_singularity.consume_range = new_consume_range diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm index a4e24c5a7287b..308b8bc246519 100644 --- a/code/modules/power/smes.dm +++ b/code/modules/power/smes.dm @@ -189,7 +189,7 @@ return ..() -/obj/machinery/power/smes/on_deconstruction() +/obj/machinery/power/smes/on_deconstruction(disassembled) for(var/obj/item/stock_parts/cell/cell in component_parts) cell.charge = (charge / capacity) * cell.maxcharge diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm index f79e75fdaac93..c62552913bbbe 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar.dm @@ -121,18 +121,16 @@ visually_turn(new_angle) azimuth_current = new_angle -/obj/machinery/power/solar/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(disassembled) - var/obj/item/solar_assembly/S = locate() in src - if(S) - S.forceMove(loc) - S.give_glass(machine_stat & BROKEN) - else - playsound(src, SFX_SHATTER, 70, TRUE) - new /obj/item/shard(src.loc) - new /obj/item/shard(src.loc) - qdel(src) +/obj/machinery/power/solar/on_deconstruction(disassembled) + if(disassembled) + var/obj/item/solar_assembly/S = locate() in src + if(S) + S.forceMove(loc) + S.give_glass(machine_stat & BROKEN) + else + playsound(src, SFX_SHATTER, 70, TRUE) + new /obj/item/shard(src.loc) + new /obj/item/shard(src.loc) /obj/machinery/power/solar/update_overlays() . = ..() diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 2cc473789147b..c7bba14e49c5e 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -53,8 +53,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) var/damage_archived = 0 var/list/damage_factors - /// How much extra power does the main zap generate. - var/zap_multiplier = 1 + /// The zap power transmission over internal energy. W/MeV. + var/zap_transmission_rate = BASE_POWER_TRANSMISSION_RATE var/list/zap_factors /// The temperature at which we start taking damage @@ -95,7 +95,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) /// How much power decay is negated. Complete power decay negation at 1. var/gas_powerloss_inhibition = 0 /// Affects the amount of power the main SM zap makes. - var/gas_power_transmission = 0 + var/gas_power_transmission_rate = 0 /// Affects the power gain the SM experiances from heat. var/gas_heat_power_generation = 0 @@ -109,7 +109,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) var/external_damage_immediate = 0 ///The cutoff for a bolt jumping, grows with heat, lowers with higher mol count, - var/zap_cutoff = 1500 + var/zap_cutoff = 1.2e6 ///How much the bullets damage should be multiplied by when it is added to the internal variables var/bullet_energy = SUPERMATTER_DEFAULT_BULLET_ENERGY ///How much hallucination should we produce per unit of power? @@ -153,6 +153,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) ///Stores the time of when the last zap occurred var/last_power_zap = 0 + var/last_high_energy_zap = 0 ///Do we show this crystal in the CIMS modular program var/include_in_cims = TRUE @@ -176,6 +177,10 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) /// If a sliver of the supermatter has been removed. Almost certainly by a traitor. Lowers the delamination countdown time. var/supermatter_sliver_removed = FALSE + + /// If the SM is decorated with holiday lights + var/holiday_lights = FALSE + /// Cooldown for sending emergency alerts to the common radio channel COOLDOWN_DECLARE(common_radio_cooldown) @@ -202,7 +207,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) RegisterSignal(src, COMSIG_ATOM_BSA_BEAM, PROC_REF(force_delam)) RegisterSignal(src, COMSIG_ATOM_TIMESTOP_FREEZE, PROC_REF(time_frozen)) RegisterSignal(src, COMSIG_ATOM_TIMESTOP_UNFREEZE, PROC_REF(time_unfrozen)) - + RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(eat_bullets)) var/static/list/loc_connections = list( COMSIG_TURF_INDUSTRIAL_LIFT_ENTER = PROC_REF(tram_contents_consume), ) @@ -211,6 +216,9 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) AddComponent(/datum/component/supermatter_crystal, CALLBACK(src, PROC_REF(wrench_act_callback)), CALLBACK(src, PROC_REF(consume_callback))) soundloop = new(src, TRUE) + if(!isnull(check_holidays(FESTIVE_SEASON))) + holiday_lights() + if (!moveable) move_resist = MOVE_FORCE_OVERPOWERING // Avoid being moved by statues or other memes @@ -242,8 +250,18 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) /obj/machinery/power/supermatter_crystal/examine(mob/user) . = ..() var/immune = HAS_MIND_TRAIT(user, TRAIT_MADNESS_IMMUNE) - if(isliving(user) && !immune && (get_dist(user, src) < SM_HALLUCINATION_RANGE(internal_energy))) - . += span_danger("You get headaches just from looking at it.") + if(isliving(user)) + if (!immune && (get_dist(user, src) < SM_HALLUCINATION_RANGE(internal_energy))) + . += span_danger("You get headaches just from looking at it.") + var/mob/living/living_user = user + if (HAS_TRAIT(user, TRAIT_REMOTE_TASTING)) + to_chat(user, span_warning("The taste is overwhelming and indescribable!")) + living_user.electrocute_act(shock_damage = 15, source = src, flags = SHOCK_KNOCKDOWN | SHOCK_NOGLOVES) + . += span_notice("It could use a little more Sodium Chloride...") + + if(holiday_lights) + . += span_notice("Radiating both festive cheer and actual radiation, it has a dazzling spectacle lights wrapped lovingly around the base transforming it from a potential doomsday device into a cosmic yuletide centerpiece.") + . += delamination_strategy.examine(src) return . @@ -275,7 +293,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) // PART 3: POWER PROCESSING internal_energy_factors = calculate_internal_energy() - zap_factors = calculate_zap_multiplier() + zap_factors = calculate_zap_transmission_rate() if(internal_energy && (last_power_zap + (4 - internal_energy * 0.001) SECONDS) < world.time) playsound(src, 'sound/weapons/emitter2.ogg', 70, TRUE) hue_angle_shift = clamp(903 * log(10, (internal_energy + 8000)) - 3590, -50, 240) @@ -286,9 +304,9 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) supermatter_zap( zapstart = src, range = 3, - zap_str = 1.25 * internal_energy * zap_multiplier * delta_time, + zap_str = internal_energy * zap_transmission_rate * delta_time, zap_flags = ZAP_SUPERMATTER_FLAGS, - zap_cutoff = 300 * delta_time, + zap_cutoff = 2.4e5 * delta_time, power_level = internal_energy, color = zap_color, ) @@ -300,7 +318,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) damage_factors = calculate_damage() if(damage == 0) // Clear any in game forced delams if on full health. set_delam(SM_DELAM_PRIO_IN_GAME, SM_DELAM_STRATEGY_PURGE) - else + else if(!final_countdown) set_delam(SM_DELAM_PRIO_NONE, SM_DELAM_STRATEGY_PURGE) // This one cant clear any forced delams. delamination_strategy.delam_progress(src) if(damage > explosion_point && !final_countdown) @@ -374,15 +392,20 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) "name" = factor, "amount" = amount * -1 )) + var/list/internal_energy_si_derived_data = siunit_isolated(internal_energy * 1e6, "eV", 3) data["internal_energy"] = internal_energy + data["internal_energy_coefficient"] = internal_energy_si_derived_data[SI_COEFFICIENT] + data["internal_energy_unit"] = internal_energy_si_derived_data[SI_UNIT] data["internal_energy_factors"] = list() for (var/factor in internal_energy_factors) + var/list/internal_energy_factor_si_derived_data = siunit_isolated(internal_energy_factors[factor] * 1e6, "eV", 3) var/amount = round(internal_energy_factors[factor], 0.01) if(!amount) continue data["internal_energy_factors"] += list(list( "name" = factor, - "amount" = amount + "amount" = internal_energy_factor_si_derived_data[SI_COEFFICIENT], + "unit" = internal_energy_factor_si_derived_data[SI_UNIT], )) data["temp_limit"] = temp_limit data["temp_limit_factors"] = list() @@ -392,7 +415,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) continue data["temp_limit_factors"] += list(list( "name" = factor, - "amount" = amount + "amount" = amount, )) data["waste_multiplier"] = waste_multiplier data["waste_multiplier_factors"] = list() @@ -402,18 +425,42 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) continue data["waste_multiplier_factors"] += list(list( "name" = factor, - "amount" = amount + "amount" = amount, )) - data["zap_multiplier"] = zap_multiplier - data["zap_multiplier_factors"] = list() + + data["zap_transmission_factors"] = list() for (var/factor in zap_factors) - var/amount = round(zap_factors[factor], 0.01) - if(!amount) + var/list/zap_factor_si_derived_data = siunit_isolated(zap_factors[factor] * internal_energy, "W", 2) + if(!zap_factor_si_derived_data[SI_COEFFICIENT]) continue - data["zap_multiplier_factors"] += list(list( + data["zap_transmission_factors"] += list(list( "name" = factor, - "amount" = amount + "amount" = zap_factor_si_derived_data[SI_COEFFICIENT], + "unit" = zap_factor_si_derived_data[SI_UNIT], + )) + + ///Add high energy bonus to the zap transmission data so we can accurately measure our power generation from zaps. + var/high_energy_bonus = 0 + var/zap_transmission = zap_transmission_rate * internal_energy + var/zap_power_multiplier = 1 + if(internal_energy > POWER_PENALTY_THRESHOLD) //Supermatter zaps multiply power internally under some conditions for some reason, so we'll snowflake this for now. + ///Power multiplier bonus applied to all zaps. Zap power generation doubles when it reaches 7GeV and 9GeV. + zap_power_multiplier *= 2 ** clamp(round((internal_energy - POWER_PENALTY_THRESHOLD) / 2000), 0, 2) + ///The supermatter releases additional zaps after 5GeV, with more at 7GeV and 9GeV. + var/additional_zap_bonus = clamp(internal_energy * 3200, 6.4e6, 3.2e7) * clamp(round(INVERSE_LERP(1000, 3000, internal_energy)), 1, 4) + high_energy_bonus = (zap_transmission + additional_zap_bonus) * zap_power_multiplier - zap_transmission + var/list/zap_factor_si_derived_data = siunit_isolated(high_energy_bonus, "W", 2) + data["zap_transmission_factors"] += list(list( + "name" = "High Energy Bonus", + "amount" = zap_factor_si_derived_data[SI_COEFFICIENT], + "unit" = zap_factor_si_derived_data[SI_UNIT], )) + + var/list/zap_transmission_si_derived_data = siunit_isolated(zap_transmission + high_energy_bonus, "W", 2) + data["zap_transmission"] = zap_transmission + high_energy_bonus + data["zap_transmission_coefficient"] = zap_transmission_si_derived_data[SI_COEFFICIENT] + data["zap_transmission_unit"] = zap_transmission_si_derived_data[SI_UNIT] + data["absorbed_ratio"] = absorption_ratio var/list/formatted_gas_percentage = list() for (var/datum/gas/gas_path as anything in subtypesof(/datum/gas)) @@ -459,6 +506,13 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) . += mutable_appearance(icon = icon, icon_state = "[base_icon_state]-psy", layer = FLOAT_LAYER - 1, alpha = psy_coeff * 255) if(delamination_strategy) . += delamination_strategy.overlays(src) + if(holiday_lights) + if(istype(src, /obj/machinery/power/supermatter_crystal/shard)) + . += mutable_appearance(icon, "holiday_lights_shard") + . += emissive_appearance(icon, "holiday_lights_shard_e", src, alpha = src.alpha) + else + . += mutable_appearance(icon, "holiday_lights") + . += emissive_appearance(icon, "holiday_lights_e", src, alpha = src.alpha) return . /obj/machinery/power/supermatter_crystal/update_icon(updates) @@ -504,9 +558,12 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) final_countdown = TRUE - notify_ghosts("[src] has begun the delamination process!", source = src, header = "Meltdown Incoming") + notify_ghosts( + "[src] has begun the delamination process!", + source = src, + header = "Meltdown Incoming", + ) - var/datum/sm_delam/last_delamination_strategy = delamination_strategy var/list/count_down_messages = delamination_strategy.count_down_messages() radio.talk_into( @@ -529,10 +586,6 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) ) for(var/i in delamination_countdown_time to 0 step -10) - if(last_delamination_strategy != delamination_strategy) - count_down_messages = delamination_strategy.count_down_messages() - last_delamination_strategy = delamination_strategy - var/message var/healed = FALSE @@ -576,7 +629,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) * * Updates: * [/obj/machinery/power/supermatter_crystal/var/list/gas_percentage] - * [/obj/machinery/power/supermatter_crystal/var/gas_power_transmission] + * [/obj/machinery/power/supermatter_crystal/var/gas_power_transmission_rate] * [/obj/machinery/power/supermatter_crystal/var/gas_heat_modifier] * [/obj/machinery/power/supermatter_crystal/var/gas_heat_resistance] * [/obj/machinery/power/supermatter_crystal/var/gas_heat_power_generation] @@ -589,7 +642,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) return gas_percentage = list() - gas_power_transmission = 0 + gas_power_transmission_rate = 0 gas_heat_modifier = 0 gas_heat_resistance = 0 gas_heat_power_generation = 0 @@ -606,7 +659,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) var/datum/sm_gas/sm_gas = current_gas_behavior[gas_path] if(!sm_gas) continue - gas_power_transmission += sm_gas.power_transmission * gas_percentage[gas_path] + gas_power_transmission_rate += sm_gas.power_transmission * gas_percentage[gas_path] gas_heat_modifier += sm_gas.heat_modifier * gas_percentage[gas_path] gas_heat_resistance += sm_gas.heat_resistance * gas_percentage[gas_path] gas_heat_power_generation += sm_gas.heat_power_generation * gas_percentage[gas_path] @@ -636,7 +689,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) external_power_trickle -= min(additive_power[SM_POWER_EXTERNAL_TRICKLE], external_power_trickle) additive_power[SM_POWER_EXTERNAL_IMMEDIATE] = external_power_immediate external_power_immediate = 0 - additive_power[SM_POWER_HEAT] = gas_heat_power_generation * absorbed_gasmix.temperature / 6 + additive_power[SM_POWER_HEAT] = gas_heat_power_generation * absorbed_gasmix.temperature * GAS_HEAT_POWER_SCALING_COEFFICIENT additive_power[SM_POWER_HEAT] && log_activation(who = "environmental factors") // I'm sorry for this, but we need to calculate power lost immediately after power gain. @@ -659,6 +712,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) if(internal_energy && !activation_logged) stack_trace("Supermatter powered for the first time without being logged. Internal energy factors: [json_encode(internal_energy_factors)]") activation_logged = TRUE // so we dont spam the log. + else if(!internal_energy) + last_power_zap = world.time return additive_power /** Log when the supermatter is activated for the first time. @@ -684,24 +739,24 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) activation_logged = TRUE /** - * Perform calculation for the main zap power multiplier. + * Perform calculation for the main zap power transmission rate in W/MeV. * Description of each factors can be found in the defines. * * Updates: - * [/obj/machinery/power/supermatter_crystal/var/zap_multiplier] + * [/obj/machinery/power/supermatter_crystal/var/zap_transmission_rate] * * Returns: The factors that have influenced the calculation. list[FACTOR_DEFINE] = number */ -/obj/machinery/power/supermatter_crystal/proc/calculate_zap_multiplier() - var/list/additive_transmission = list() - additive_transmission[SM_ZAP_BASE] = 1 - additive_transmission[SM_ZAP_GAS] = gas_power_transmission +/obj/machinery/power/supermatter_crystal/proc/calculate_zap_transmission_rate() + var/list/additive_transmission_rate = list() + additive_transmission_rate[SM_ZAP_BASE] = BASE_POWER_TRANSMISSION_RATE + additive_transmission_rate[SM_ZAP_GAS] = BASE_POWER_TRANSMISSION_RATE * gas_power_transmission_rate - zap_multiplier = 0 - for (var/transmission_types in additive_transmission) - zap_multiplier += additive_transmission[transmission_types] - zap_multiplier = max(zap_multiplier, 0) - return additive_transmission + zap_transmission_rate = 0 + for (var/transmission_types in additive_transmission_rate) + zap_transmission_rate += additive_transmission_rate[transmission_types] + zap_transmission_rate = max(zap_transmission_rate, 0) + return additive_transmission_rate /** * Perform calculation for the waste multiplier. @@ -835,7 +890,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) delamination_strategy.on_select(src) return TRUE -/obj/machinery/proc/supermatter_zap(atom/zapstart = src, range = 5, zap_str = 4000, zap_flags = ZAP_SUPERMATTER_FLAGS, list/targets_hit = list(), zap_cutoff = 1500, power_level = 0, zap_icon = DEFAULT_ZAP_ICON_STATE, color = null) +/obj/machinery/proc/supermatter_zap(atom/zapstart = src, range = 5, zap_str = 3.2e6, zap_flags = ZAP_SUPERMATTER_FLAGS, list/targets_hit = list(), zap_cutoff = 1.2e6, power_level = 0, zap_icon = DEFAULT_ZAP_ICON_STATE, color = null) if(QDELETED(zapstart)) return . = zapstart.dir @@ -930,13 +985,13 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) //Going boom should be rareish if(prob(80)) zap_flags &= ~ZAP_MACHINE_EXPLOSIVE - if(target_type == COIL) - var/multi = 2 - switch(power_level)//Between 7k and 9k it's 4, above that it's 8 + if(target_type == COIL || target_type == ROD) + var/multi = 1 + switch(power_level)//Between 7k and 9k it's 2, above that it's 4 if(SEVERE_POWER_PENALTY_THRESHOLD to CRITICAL_POWER_PENALTY_THRESHOLD) - multi = 4 + multi = 2 if(CRITICAL_POWER_PENALTY_THRESHOLD to INFINITY) - multi = 8 + multi = 4 if(zap_flags & ZAP_SUPERMATTER_FLAGS) var/remaining_power = target.zap_act(zap_str * multi, zap_flags) zap_str = remaining_power / multi //Coils should take a lot out of the power of the zap @@ -982,6 +1037,29 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) COOLDOWN_START(src, common_radio_cooldown, SUPERMATTER_COMMON_RADIO_DELAY) return TRUE +/obj/machinery/power/supermatter_crystal/proc/holiday_lights() + holiday_lights = TRUE + RegisterSignal(src, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(holiday_item_interaction)) + update_appearance() + +/// Consume the santa hat and add it as an overlay +/obj/machinery/power/supermatter_crystal/proc/holiday_item_interaction(source, mob/living/user, obj/item/item, list/modifiers) + SIGNAL_HANDLER + if(istype(item, /obj/item/clothing/head/costume/santa)) + QDEL_NULL(item) + RegisterSignal(src, COMSIG_ATOM_EXAMINE, PROC_REF(holiday_hat_examine)) + if(istype(src, /obj/machinery/power/supermatter_crystal/shard)) + add_overlay(mutable_appearance(icon, "santa_hat_shard")) + else + add_overlay(mutable_appearance(icon, "santa_hat")) + return COMPONENT_CANCEL_ATTACK_CHAIN + return NONE + +/// Adds the hat flavor text when examined +/obj/machinery/power/supermatter_crystal/proc/holiday_hat_examine(atom/source, mob/user, list/examine_list) + SIGNAL_HANDLER + examine_list += span_info("There's a santa hat placed atop it. How it got there without being dusted is a mystery.") + #undef BIKE #undef COIL #undef ROD diff --git a/code/modules/power/supermatter/supermatter_delamination/_sm_delam.dm b/code/modules/power/supermatter/supermatter_delamination/_sm_delam.dm index be085f281c904..2e4d0671a9d11 100644 --- a/code/modules/power/supermatter/supermatter_delamination/_sm_delam.dm +++ b/code/modules/power/supermatter/supermatter_delamination/_sm_delam.dm @@ -15,7 +15,7 @@ GLOBAL_LIST_INIT(sm_delam_list, list( /datum/sm_delam/proc/can_select(obj/machinery/power/supermatter_crystal/sm) return FALSE -#define ROUNDCOUNT_ENGINE_JUST_EXPLODED 0 +#define ROUNDCOUNT_ENGINE_JUST_EXPLODED -1 /// Called when the count down has been finished, do the nasty work. /// [/obj/machinery/power/supermatter_crystal/proc/count_down] diff --git a/code/modules/power/supermatter/supermatter_delamination/cascade_delam.dm b/code/modules/power/supermatter/supermatter_delamination/cascade_delam.dm index 5d8cfecaa40d2..a9c7a87045da3 100644 --- a/code/modules/power/supermatter/supermatter_delamination/cascade_delam.dm +++ b/code/modules/power/supermatter/supermatter_delamination/cascade_delam.dm @@ -28,8 +28,7 @@ "Something feels very off.", "A drowning sense of dread washes over you.", ) - for(var/mob/victim as anything in GLOB.player_list) - to_chat(victim, span_danger(pick(messages))) + dispatch_announcement_to_players(span_danger(pick(messages)), should_play_sound = FALSE) return TRUE diff --git a/code/modules/power/supermatter/supermatter_delamination/cascade_delam_objects.dm b/code/modules/power/supermatter/supermatter_delamination/cascade_delam_objects.dm index 34a7d6f420aef..d19d17452e221 100644 --- a/code/modules/power/supermatter/supermatter_delamination/cascade_delam_objects.dm +++ b/code/modules/power/supermatter/supermatter_delamination/cascade_delam_objects.dm @@ -36,6 +36,9 @@ if(our_turf) our_turf.opacity = FALSE + // Ideally this'd be part of the SM component, but the SM itself snowflakes bullets (emitters are bullets). + RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(eat_bullets)) + /obj/crystal_mass/process() if(!COOLDOWN_FINISHED(src, sm_wall_cooldown)) @@ -70,9 +73,18 @@ new /obj/crystal_mass(next_turf, get_dir(next_turf, src)) -/obj/crystal_mass/bullet_act(obj/projectile/projectile) - visible_message(span_notice("[src] is unscathed!")) - return BULLET_ACT_HIT +/obj/crystal_mass/proc/eat_bullets(datum/source, obj/projectile/hitting_projectile) + SIGNAL_HANDLER + + visible_message( + span_warning("[hitting_projectile] flies into [src] with a loud crack, before rapidly flashing into ash."), + null, + span_hear("You hear a loud crack as you are washed with a wave of heat."), + ) + + playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE) + qdel(hitting_projectile) + return COMPONENT_BULLET_BLOCKED /obj/crystal_mass/singularity_act() return @@ -166,4 +178,3 @@ span_hear("You hear a loud crack as a small distortion passes through you.")) qdel(consumed_object) - diff --git a/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm b/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm index 25283c8d09ae9..a6c3f171b61af 100644 --- a/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm +++ b/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm @@ -137,8 +137,13 @@ // say goodbye to that shuttle of yours if(SSshuttle.emergency.mode != SHUTTLE_ESCAPE) - priority_announce("Fatal error occurred in emergency shuttle uplink during transit. Unable to reestablish connection.", - "Emergency Shuttle Uplink Alert", 'sound/misc/announce_dig.ogg') + priority_announce( + text = "Fatal error occurred in emergency shuttle uplink during transit. Unable to reestablish connection.", + title = "Shuttle Failure", + sound = 'sound/misc/announce_dig.ogg', + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "grey", + ) else // except if you are on it already, then you are safe c: minor_announce("ERROR: Corruption detected in navigation protocols. Connection with Transponder #XCC-P5831-ES13 lost. \ diff --git a/code/modules/power/supermatter/supermatter_extra_effects.dm b/code/modules/power/supermatter/supermatter_extra_effects.dm index 7fe56d9c2d498..efd84c677fafa 100644 --- a/code/modules/power/supermatter/supermatter_extra_effects.dm +++ b/code/modules/power/supermatter/supermatter_extra_effects.dm @@ -91,6 +91,7 @@ /obj/machinery/power/supermatter_crystal/proc/handle_high_power() if(internal_energy <= POWER_PENALTY_THRESHOLD && damage <= danger_point) //If the power is above 5000 or if the damage is above 550 + last_high_energy_zap = world.time //Prevent oddly high initial zap due to high energy zaps not getting triggered via too low energy. return var/range = 4 zap_cutoff = 1500 @@ -99,7 +100,7 @@ var/temp = absorbed_gasmix.temperature if(pressure > 0 && temp > 0) //You may be able to freeze the zapstate of the engine with good planning, we'll see - zap_cutoff = clamp(3000 - (internal_energy * total_moles / 10) / temp, 350, 3000)//If the core is cold, it's easier to jump, ditto if there are a lot of mols + zap_cutoff = clamp(1.2e6 - (internal_energy * total_moles * 40) / temp, 1.4e5, 1.2e6)//If the core is cold, it's easier to jump, ditto if there are a lot of mols //We should always be able to zap our way out of the default enclosure //See supermatter_zap() for more details range = clamp(internal_energy / pressure * 10, 2, 7) @@ -128,9 +129,10 @@ if(zap_count >= 1) playsound(loc, 'sound/weapons/emitter2.ogg', 100, TRUE, extrarange = 10) + var/delta_time = min((world.time - last_high_energy_zap) * 0.1, 16) for(var/i in 1 to zap_count) - supermatter_zap(src, range, clamp(internal_energy*2, 4000, 20000), flags, zap_cutoff = src.zap_cutoff, power_level = internal_energy, zap_icon = src.zap_icon) - + supermatter_zap(src, range, clamp(internal_energy * 3200, 6.4e6, 3.2e7) * delta_time, flags, zap_cutoff = src.zap_cutoff * delta_time, power_level = internal_energy, zap_icon = src.zap_icon) + last_high_energy_zap = world.time if(prob(5)) supermatter_anomaly_gen(src, FLUX_ANOMALY, rand(5, 10)) if(prob(5)) diff --git a/code/modules/power/supermatter/supermatter_gas.dm b/code/modules/power/supermatter/supermatter_gas.dm index 141f78a38b8e2..df8ef8e5b4fa8 100644 --- a/code/modules/power/supermatter/supermatter_gas.dm +++ b/code/modules/power/supermatter/supermatter_gas.dm @@ -17,33 +17,40 @@ // Positive is true if more of the amount is a good thing. var/list/numeric_data = list() if(sm_gas.power_transmission) + var/list/si_derived_data = siunit_isolated(sm_gas.power_transmission * BASE_POWER_TRANSMISSION_RATE, "W/MeV", 2) numeric_data += list(list( - "name" = "Power Transmission", - "amount" = sm_gas.power_transmission, + "name" = "Power Transmission Bonus", + "amount" = si_derived_data["coefficient"], + "unit" = si_derived_data["unit"], "positive" = TRUE, )) if(sm_gas.heat_modifier) numeric_data += list(list( "name" = "Waste Multiplier", - "amount" = sm_gas.heat_modifier, + "amount" = 100 * sm_gas.heat_modifier, + "unit" = "%", "positive" = FALSE, )) if(sm_gas.heat_resistance) numeric_data += list(list( "name" = "Heat Resistance", - "amount" = sm_gas.heat_resistance, + "amount" = 100 * sm_gas.heat_resistance, + "unit" = "%", "positive" = TRUE, )) if(sm_gas.heat_power_generation) + var/list/si_derived_data = siunit_isolated(sm_gas.heat_power_generation * GAS_HEAT_POWER_SCALING_COEFFICIENT * 1e7 / SSair.wait, "eV/K/s", 2) numeric_data += list(list( "name" = "Heat Power Gain", - "amount" = sm_gas.heat_power_generation, + "amount" = si_derived_data["coefficient"], + "unit" = si_derived_data["unit"], "positive" = TRUE, )) if(sm_gas.powerloss_inhibition) numeric_data += list(list( "name" = "Power Decay Negation", - "amount" = sm_gas.powerloss_inhibition, + "amount" = 100 * sm_gas.powerloss_inhibition, + "unit" = "%", "positive" = TRUE, )) singular_gas_data["numeric_data"] = numeric_data @@ -59,8 +66,7 @@ GLOBAL_LIST_INIT(sm_gas_behavior, init_sm_gas()) /datum/sm_gas /// Path of the [/datum/gas] involved with this interaction. var/gas_path - - /// Influences zap power without interfering with the crystal's own energy. + /// Influences zap power without interfering with the crystal's own energy. Gets scaled by [BASE_POWER_TRANSMISSION_RATE]. var/power_transmission = 0 /// How much more waste heat and gas the SM generates. var/heat_modifier = 0 @@ -216,7 +222,7 @@ GLOBAL_LIST_INIT(sm_gas_behavior, init_sm_gas()) sm.supermatter_zap( sm, range = 6, - zap_str = clamp(sm.internal_energy * 2, 4000, 20000), + zap_str = clamp(sm.internal_energy * 1600, 3.2e6, 1.6e7), zap_flags = ZAP_MOB_STUN, zap_cutoff = sm.zap_cutoff, power_level = sm.internal_energy, diff --git a/code/modules/power/supermatter/supermatter_hit_procs.dm b/code/modules/power/supermatter/supermatter_hit_procs.dm index 452b37e054100..6f01b5ff7e3f4 100644 --- a/code/modules/power/supermatter/supermatter_hit_procs.dm +++ b/code/modules/power/supermatter/supermatter_hit_procs.dm @@ -5,16 +5,20 @@ for(var/atom/thing_to_consume as anything in tram_contents) Bumped(thing_to_consume) -/obj/machinery/power/supermatter_crystal/bullet_act(obj/projectile/projectile) +/obj/machinery/power/supermatter_crystal/proc/eat_bullets(datum/source, obj/projectile/projectile) + SIGNAL_HANDLER + var/turf/local_turf = loc + if(!istype(local_turf)) + return NONE + var/kiss_power = 0 switch(projectile.type) if(/obj/projectile/kiss) kiss_power = 60 if(/obj/projectile/kiss/death) kiss_power = 20000 - if(!istype(local_turf)) - return FALSE + if(!istype(projectile.firer, /obj/machinery/power/emitter)) investigate_log("has been hit by [projectile] fired by [key_name(projectile.firer)]", INVESTIGATE_ENGINE) if(projectile.armor_flag != BULLET || kiss_power) @@ -29,7 +33,10 @@ var/damage_to_be = damage + external_damage_immediate * clamp((emergency_point - damage) / emergency_point, 0, 1) if(damage_to_be > danger_point) visible_message(span_notice("[src] compresses under stress, resisting further impacts!")) - return BULLET_ACT_HIT + playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE) + + qdel(projectile) + return COMPONENT_BULLET_BLOCKED /obj/machinery/power/supermatter_crystal/singularity_act() var/gain = 100 diff --git a/code/modules/power/supermatter/supermatter_variants.dm b/code/modules/power/supermatter/supermatter_variants.dm index 2390ab3d0b759..9d69066a5353b 100644 --- a/code/modules/power/supermatter/supermatter_variants.dm +++ b/code/modules/power/supermatter/supermatter_variants.dm @@ -19,7 +19,6 @@ absorption_ratio = 0.125 explosion_power = 12 layer = ABOVE_MOB_LAYER - plane = GAME_PLANE_UPPER moveable = TRUE /// Shard SM with it's processing disabled. diff --git a/code/modules/power/tesla/coil.dm b/code/modules/power/tesla/coil.dm index 098ff7ceaeea5..def7bf7aa4d28 100644 --- a/code/modules/power/tesla/coil.dm +++ b/code/modules/power/tesla/coil.dm @@ -1,7 +1,5 @@ // zap needs to be over this amount to get power -#define TESLA_COIL_THRESHOLD 80 -// each zap power unit produces 400 joules -#define ZAP_TO_ENERGY(p) (joules_to_energy((p) * 400)) +#define TESLA_COIL_THRESHOLD 32000 /obj/machinery/power/energy_accumulator/tesla_coil name = "tesla coil" @@ -75,7 +73,7 @@ /obj/machinery/power/energy_accumulator/tesla_coil/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/power/energy_accumulator/tesla_coil/attackby(obj/item/W, mob/user, params) if(default_deconstruction_screwdriver(user, "coil_open[anchored]", "coil[anchored]", W)) @@ -107,7 +105,7 @@ power /= 10 zap_buckle_check(power) var/power_removed = powernet ? power * input_power_multiplier : power - stored_energy += max(ZAP_TO_ENERGY(power_removed - TESLA_COIL_THRESHOLD), 0) + stored_energy += max(joules_to_energy(power_removed - TESLA_COIL_THRESHOLD), 0) return max(power - power_removed, 0) //You get back the amount we didn't use /obj/machinery/power/energy_accumulator/tesla_coil/proc/zap() @@ -118,7 +116,7 @@ power = min(surplus(), power) //Take the smaller of the two add_load(power) playsound(src.loc, 'sound/magic/lightningshock.ogg', zap_sound_volume, TRUE, zap_sound_range) - tesla_zap(src, 10, power, zap_flags) + tesla_zap(source = src, zap_range = 10, power = power, cutoff = 1e3, zap_flags = zap_flags) zap_buckle_check(power) /obj/machinery/power/energy_accumulator/grounding_rod @@ -155,7 +153,7 @@ /obj/machinery/power/energy_accumulator/grounding_rod/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/power/energy_accumulator/grounding_rod/attackby(obj/item/W, mob/user, params) if(default_deconstruction_screwdriver(user, "grounding_rod_open[anchored]", "grounding_rod[anchored]", W)) @@ -170,10 +168,9 @@ if(anchored && !panel_open) flick("grounding_rodhit", src) zap_buckle_check(power) - stored_energy += ZAP_TO_ENERGY(power) + stored_energy += joules_to_energy(power) return 0 else . = ..() #undef TESLA_COIL_THRESHOLD -#undef ZAP_TO_ENERGY diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm index f3d4c5e1c7ef7..2b625b36d5bf7 100644 --- a/code/modules/power/tesla/energy_ball.dm +++ b/code/modules/power/tesla/energy_ball.dm @@ -1,5 +1,5 @@ -#define TESLA_DEFAULT_POWER 1738260 -#define TESLA_MINI_POWER 869130 +#define TESLA_DEFAULT_POWER 6.95304e8 +#define TESLA_MINI_POWER 3.47652e8 //Zap constants, speeds up targeting #define BIKE (COIL + 1) #define COIL (ROD + 1) @@ -76,7 +76,7 @@ pixel_y = 0 shocked_things.Cut(1, shocked_things.len / 1.3) var/list/shocking_info = list() - tesla_zap(src, 3, TESLA_DEFAULT_POWER, shocked_targets = shocking_info) + tesla_zap(source = src, zap_range = 3, power = TESLA_DEFAULT_POWER, shocked_targets = shocking_info) pixel_x = -32 pixel_y = -32 @@ -84,7 +84,7 @@ var/range = rand(1, clamp(orbiting_balls.len, 2, 3)) var/list/temp_shock = list() //We zap off the main ball instead of ourselves to make things looks proper - tesla_zap(src, range, TESLA_MINI_POWER/7*range, shocked_targets = temp_shock) + tesla_zap(source = src, zap_range = range, power = TESLA_MINI_POWER / 7 * range, shocked_targets = temp_shock) shocking_info += temp_shock shocked_things += shocking_info @@ -199,13 +199,13 @@ C.investigate_log("has been dusted by an energy ball.", INVESTIGATE_DEATHS) C.dust() -/proc/tesla_zap(atom/source, zap_range = 3, power, zap_flags = ZAP_DEFAULT_FLAGS, list/shocked_targets = list()) +/proc/tesla_zap(atom/source, zap_range = 3, power, cutoff = 4e5, zap_flags = ZAP_DEFAULT_FLAGS, list/shocked_targets = list()) if(QDELETED(source)) return if(!(zap_flags & ZAP_ALLOW_DUPLICATES)) LAZYSET(shocked_targets, source, TRUE) //I don't want no null refs in my list yeah? . = source.dir - if(power < 1000) + if(power < cutoff) return /* @@ -334,7 +334,7 @@ var/mob/living/closest_mob = closest_atom ADD_TRAIT(closest_mob, TRAIT_BEING_SHOCKED, WAS_SHOCKED) addtimer(TRAIT_CALLBACK_REMOVE(closest_mob, TRAIT_BEING_SHOCKED, WAS_SHOCKED), 1 SECONDS) - var/shock_damage = (zap_flags & ZAP_MOB_DAMAGE) ? (min(round(power/600), 90) + rand(-5, 5)) : 0 + var/shock_damage = (zap_flags & ZAP_MOB_DAMAGE) ? (min(round(power / 600), 90) + rand(-5, 5)) : 0 closest_mob.electrocute_act(shock_damage, source, 1, SHOCK_TESLA | ((zap_flags & ZAP_MOB_STUN) ? NONE : SHOCK_NOSTUN)) if(issilicon(closest_mob)) var/mob/living/silicon/S = closest_mob @@ -350,11 +350,11 @@ if(prob(20))//I know I know var/list/shocked_copy = shocked_targets.Copy() - tesla_zap(closest_atom, next_range, power * 0.5, zap_flags, shocked_copy)//Normally I'd copy here so grounding rods work properly, but it fucks with movement - tesla_zap(closest_atom, next_range, power * 0.5, zap_flags, shocked_targets) + tesla_zap(source = closest_atom, zap_range = next_range, power = power * 0.5, cutoff = cutoff, zap_flags = zap_flags, shocked_targets = shocked_copy) + tesla_zap(source = closest_atom, zap_range = next_range, power = power * 0.5, cutoff = cutoff, zap_flags = zap_flags, shocked_targets = shocked_targets) shocked_targets += shocked_copy else - tesla_zap(closest_atom, next_range, power, zap_flags, shocked_targets) + tesla_zap(source = closest_atom, zap_range = next_range, power = power, cutoff = cutoff, zap_flags = zap_flags, shocked_targets = shocked_targets) #undef BIKE #undef COIL diff --git a/code/modules/power/thermoelectric_generator.dm b/code/modules/power/thermoelectric_generator.dm new file mode 100644 index 0000000000000..bded1482825f2 --- /dev/null +++ b/code/modules/power/thermoelectric_generator.dm @@ -0,0 +1,222 @@ +#define TEG_EFFICIENCY 0.65 + +/obj/machinery/power/thermoelectric_generator + name = "thermoelectric generator" + desc = "It's a high efficiency thermoelectric generator." + icon_state = "teg" + base_icon_state = "teg" + density = TRUE + use_power = NO_POWER_USE + circuit = /obj/item/circuitboard/machine/thermoelectric_generator + + ///The cold circulator machine, containing cold gas for the mix. + var/obj/machinery/atmospherics/components/binary/circulator/cold_circ + ///The hot circulator machine, containing very hot gas for the mix. + var/obj/machinery/atmospherics/components/binary/circulator/hot_circ + ///The amount of power the generator is currently producing. + var/lastgen = 0 + ///The amount of power the generator has last produced. + var/lastgenlev = -1 + /** + * Used in overlays for the TEG, basically; + * one number is for the cold mix, one is for the hot mix + * If the cold mix has pressure in it, then the first number is 1, else 0 + * If the hot mix has pressure in it, then the second number is 1, else 0 + * Neither has pressure: 00 + * Only cold has pressure: 10 + * Only hot has pressure: 01 + * Both has pressure: 11 + */ + var/last_pressure_overlay = "00" + +/obj/machinery/power/thermoelectric_generator/Initialize(mapload) + . = ..() + AddComponent(/datum/component/simple_rotation) + find_circulators() + connect_to_network() + SSair.start_processing_machine(src) + update_appearance() + +/obj/machinery/power/thermoelectric_generator/Destroy() + null_circulators() + SSair.stop_processing_machine(src) + return ..() + +/obj/machinery/power/thermoelectric_generator/on_deconstruction(disassembled) + null_circulators() + +/obj/machinery/power/thermoelectric_generator/update_overlays() + . = ..() + if(machine_stat & (NOPOWER|BROKEN)) + return + + var/level = min(round(lastgenlev / 100000), 11) + if(level) + . += mutable_appearance('icons/obj/machines/engine/other.dmi', "[base_icon_state]-op[level]") + if(hot_circ && cold_circ) + . += "[base_icon_state]-oc[last_pressure_overlay]" + +/obj/machinery/power/thermoelectric_generator/wrench_act(mob/living/user, obj/item/tool) + if(!panel_open) + balloon_alert(user, "open the panel!") + return + set_anchored(!anchored) + tool.play_tool_sound(src) + if(anchored) + connect_to_network() + else + null_circulators() + balloon_alert(user, "[anchored ? "secure" : "unsecure"]") + return TRUE + +/obj/machinery/power/thermoelectric_generator/multitool_act(mob/living/user, obj/item/tool) + . = ..() + if(!anchored) + return + find_circulators() + balloon_alert(user, "circulators updated") + return TRUE + +/obj/machinery/power/thermoelectric_generator/screwdriver_act(mob/user, obj/item/tool) + if(!anchored) + balloon_alert(user, "anchor it down!") + return + toggle_panel_open() + tool.play_tool_sound(src) + balloon_alert(user, "panel [panel_open ? "open" : "closed"]") + return TRUE + +/obj/machinery/power/thermoelectric_generator/crowbar_act(mob/living/user, obj/item/tool) + default_deconstruction_crowbar(tool) + return TRUE + +/obj/machinery/power/thermoelectric_generator/process() + //Setting this number higher just makes the change in power output slower, it doesnt actualy reduce power output cause **math** + var/power_output = round(lastgen / 10) + add_avail(power_output) + lastgenlev = power_output + lastgen -= power_output + +/obj/machinery/power/thermoelectric_generator/process_atmos() + if(!cold_circ || !hot_circ) + return + if(!powernet) + return + + var/datum/gas_mixture/cold_air = cold_circ.return_transfer_air() + var/datum/gas_mixture/hot_air = hot_circ.return_transfer_air() + if(cold_air && hot_air) + var/cold_air_heat_capacity = cold_air.heat_capacity() + var/hot_air_heat_capacity = hot_air.heat_capacity() + var/delta_temperature = hot_air.temperature - cold_air.temperature + if(delta_temperature > 0 && cold_air_heat_capacity > 0 && hot_air_heat_capacity > 0) + var/efficiency = TEG_EFFICIENCY + var/energy_transfer = delta_temperature*hot_air_heat_capacity*cold_air_heat_capacity/(hot_air_heat_capacity+cold_air_heat_capacity) + var/heat = energy_transfer*(1-efficiency) + lastgen += energy_transfer*efficiency + hot_air.temperature = hot_air.temperature - energy_transfer/hot_air_heat_capacity + cold_air.temperature = cold_air.temperature + heat/cold_air_heat_capacity + + if(hot_air) + var/datum/gas_mixture/hot_circ_air1 = hot_circ.airs[1] + hot_circ_air1.merge(hot_air) + + if(cold_air) + var/datum/gas_mixture/cold_circ_air1 = cold_circ.airs[1] + cold_circ_air1.merge(cold_air) + + var/current_pressure = "[cold_circ?.last_pressure_delta > 0 ? "1" : "0"][hot_circ?.last_pressure_delta > 0 ? "1" : "0"]" + if(current_pressure != last_pressure_overlay) + //this requires an update to overlays. + last_pressure_overlay = current_pressure + + update_appearance(UPDATE_ICON) + +/obj/machinery/power/thermoelectric_generator/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ThermoElectricGenerator", name) + ui.open() + +/obj/machinery/power/thermoelectric_generator/ui_data(mob/user) + var/list/data = list() + data["error_message"] = null + if(!powernet) + data["error_message"] = "Unable to connect to the power network!" + return data + if(!cold_circ && !hot_circ) + data["error_message"] = "Unable to locate any parts! Multitool the machine to sync to nearby parts." + return data + if(!cold_circ) + data["error_message"] = "Unable to locate cold circulator!" + return data + if(!hot_circ) + data["error_message"] = "Unable to locate hot circulator!" + return data + + var/datum/gas_mixture/cold_circ_air1 = cold_circ.airs[1] + var/datum/gas_mixture/cold_circ_air2 = cold_circ.airs[2] + + var/datum/gas_mixture/hot_circ_air1 = hot_circ.airs[1] + var/datum/gas_mixture/hot_circ_air2 = hot_circ.airs[2] + + data["last_power_output"] = display_power(lastgenlev) + + var/list/cold_data = list() + cold_data["temperature_inlet"] = round(cold_circ_air2.temperature, 0.1) + cold_data["temperature_outlet"] = round(cold_circ_air1.temperature, 0.1) + cold_data["pressure_inlet"] = round(cold_circ_air2.return_pressure(), 0.1) + cold_data["pressure_outlet"] = round(cold_circ_air1.return_pressure(), 0.1) + data["cold_data"] = list(cold_data) + + var/list/hot_data = list() + hot_data["temperature_inlet"] = round(hot_circ_air2.temperature, 0.1) + hot_data["temperature_outlet"] = round(hot_circ_air1.temperature, 0.1) + hot_data["pressure_inlet"] = round(hot_circ_air2.return_pressure(), 0.1) + hot_data["pressure_outlet"] = round(hot_circ_air1.return_pressure(), 0.1) + data["hot_data"] = list(hot_data) + + return data + +///Finds and connects nearby valid circulators to the machine, nulling out previous ones. +/obj/machinery/power/thermoelectric_generator/proc/find_circulators() + null_circulators() + var/list/valid_circulators = list() + + if(dir & (NORTH|SOUTH)) + var/obj/machinery/atmospherics/components/binary/circulator/east_circulator = locate() in get_step(src, EAST) + if(east_circulator && east_circulator.dir == WEST) + valid_circulators += east_circulator + var/obj/machinery/atmospherics/components/binary/circulator/west_circulator = locate() in get_step(src, WEST) + if(west_circulator && west_circulator.dir == EAST) + valid_circulators += west_circulator + else + var/obj/machinery/atmospherics/components/binary/circulator/north_circulator = locate() in get_step(src, NORTH) + if(north_circulator && north_circulator.dir == SOUTH) + valid_circulators += north_circulator + var/obj/machinery/atmospherics/components/binary/circulator/south_circulator = locate() in get_step(src, SOUTH) + if(south_circulator && south_circulator.dir == NORTH) + valid_circulators += south_circulator + + if(!valid_circulators.len) + return + + for(var/obj/machinery/atmospherics/components/binary/circulator/circulators as anything in valid_circulators) + if(circulators.mode == CIRCULATOR_COLD && !cold_circ) + cold_circ = circulators + circulators.generator = src + continue + if(circulators.mode == CIRCULATOR_HOT && !hot_circ) + hot_circ = circulators + circulators.generator = src + +///Removes hot and cold circulators from the generator, nulling them. +/obj/machinery/power/thermoelectric_generator/proc/null_circulators() + if(hot_circ) + hot_circ.generator = null + hot_circ = null + if(cold_circ) + cold_circ.generator = null + cold_circ = null + +#undef TEG_EFFICIENCY diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm index 922f0ede64619..a6118e9ac093f 100644 --- a/code/modules/power/tracker.dm +++ b/code/modules/power/tracker.dm @@ -137,18 +137,16 @@ playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE) unset_control() -/obj/machinery/power/tracker/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(disassembled) - var/obj/item/solar_assembly/S = locate() in src - if(S) - S.forceMove(loc) - S.give_glass(machine_stat & BROKEN) - else - playsound(src, SFX_SHATTER, 70, TRUE) - new /obj/item/shard(src.loc) - new /obj/item/shard(src.loc) - qdel(src) +/obj/machinery/power/tracker/on_deconstruction(disassembled) + if(disassembled) + var/obj/item/solar_assembly/S = locate() in src + if(S) + S.forceMove(loc) + S.give_glass(machine_stat & BROKEN) + else + playsound(src, SFX_SHATTER, 70, TRUE) + new /obj/item/shard(src.loc) + new /obj/item/shard(src.loc) // Tracker Electronic diff --git a/code/modules/power/turbine/turbine.dm b/code/modules/power/turbine/turbine.dm index bb4afcede94c5..3f4d3fd844650 100644 --- a/code/modules/power/turbine/turbine.dm +++ b/code/modules/power/turbine/turbine.dm @@ -7,25 +7,17 @@ can_atmos_pass = ATMOS_PASS_DENSITY processing_flags = NONE - ///Theoretical volume of gas that's moving through the turbine, it expands the further it goes - var/gas_theoretical_volume = 0 - ///Stores the turf thermal conductivity to restore it later - var/our_turf_thermal_conductivity ///Checks if the machine is processing or not var/active = FALSE ///The parts can be registered on the main one only when their panel is closed var/can_connect = TRUE - ///Reference to our turbine part var/obj/item/turbine_parts/installed_part ///Path of the turbine part we can install var/obj/item/turbine_parts/part_path - - var/has_gasmix = FALSE + ///The gas mixture this turbine part is storing var/datum/gas_mixture/machine_gasmix - var/mapped = TRUE - ///Our overlay when active var/active_overlay = "" ///Our overlay when off @@ -35,20 +27,22 @@ ///Should we use emissive appearance? var/emissive = FALSE -/obj/machinery/power/turbine/Initialize(mapload) +/obj/machinery/power/turbine/Initialize(mapload, gas_theoretical_volume) . = ..() - if(has_gasmix) - machine_gasmix = new - machine_gasmix.volume = gas_theoretical_volume + machine_gasmix = new + machine_gasmix.volume = gas_theoretical_volume - if(part_path && mapped) + if(mapload) installed_part = new part_path(src) air_update_turf(TRUE) update_appearance() + register_context() + + /obj/machinery/power/turbine/LateInitialize() . = ..() activate_parts() @@ -60,13 +54,22 @@ QDEL_NULL(installed_part) if(machine_gasmix) - machine_gasmix = null + QDEL_NULL(machine_gasmix) deactivate_parts() return ..() /** * Handles all the calculations needed for the gases, work done, temperature increase/decrease + * + * Arguments + * * datum/gas_mixture/input_mix - the gas from the environment or from another part of the turbine + * * datum/gas_mixture/output_mix - the gas that got pumped into this part from the input mix. + * ideally should be same as input mix but varying texmperatur & pressures can cause varying results + * * work_amount_to_remove - the amount of work to subtract from the actual work done to pump in the input mixture. + * For e.g. if gas was transfered from the inlet compressor to the rotor we want to subtract the work done + * by the inlet from the rotor to get the true work done + * * intake_size - the percentage of gas to be fed into an turbine part, controlled by turbine computer for inlet compressor only */ /obj/machinery/power/turbine/proc/transfer_gases(datum/gas_mixture/input_mix, datum/gas_mixture/output_mix, work_amount_to_remove, intake_size = 1) //pump gases. if no gases were transferred then no work was done @@ -91,15 +94,49 @@ /obj/machinery/power/turbine/block_superconductivity() return TRUE +/obj/machinery/power/turbine/add_context(atom/source, list/context, obj/item/held_item, mob/user) + if(isnull(held_item)) + return NONE + + if(panel_open && istype(held_item, part_path)) + context[SCREENTIP_CONTEXT_CTRL_LMB] = "[installed_part ? "Replace" : "Install"] part" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_CTRL_LMB] = "[panel_open ? "Close" : "Open"] panel" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_WRENCH && panel_open) + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Rotate" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_CROWBAR) + if(installed_part) + context[SCREENTIP_CONTEXT_CTRL_RMB] = "Remove part" + if(panel_open) + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_MULTITOOL) + if(panel_open) + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Change cable layer" + else + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Link parts" + return CONTEXTUAL_SCREENTIP_SET + /obj/machinery/power/turbine/examine(mob/user) . = ..() if(installed_part) - . += "Currently at tier [installed_part.current_tier]." + . += span_notice("Currently at tier [installed_part.current_tier].") if(installed_part.current_tier + 1 < installed_part.max_tier) - . += "Can be upgraded by using a tier [installed_part.current_tier + 1] part." - . += "The [installed_part.name] can be removed by right-click with a crowbar tool." + . += span_notice("Can be upgraded by using a tier [installed_part.current_tier + 1] part.") + . += span_notice("The [installed_part.name] can be [EXAMINE_HINT("pried")] out.") else - . += "Is missing a [initial(part_path.name)]." + . += span_warning("Is missing a [initial(part_path.name)].") + . += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].") + if(panel_open) + . += span_notice("It can rotated with a [EXAMINE_HINT("wrench")]") + . += span_notice("The full machine can be [EXAMINE_HINT("pried")] apart") /obj/machinery/power/turbine/update_overlays() . = ..() @@ -114,12 +151,13 @@ . += off_overlay /obj/machinery/power/turbine/screwdriver_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING if(active) balloon_alert(user, "turn it off!") - return TOOL_ACT_TOOLTYPE_SUCCESS + return if(!anchored) balloon_alert(user, "anchor first!") - return TOOL_ACT_TOOLTYPE_SUCCESS + return tool.play_tool_sound(src, 50) toggle_panel_open() @@ -127,44 +165,54 @@ deactivate_parts(user) else activate_parts(user) - balloon_alert(user, "you [panel_open ? "open" : "close"] the maintenance hatch of [src]") update_appearance() - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/power/turbine/wrench_act(mob/living/user, obj/item/tool) - return default_change_direction_wrench(user, tool) + . = ITEM_INTERACT_BLOCKING + if(default_change_direction_wrench(user, tool)) + return ITEM_INTERACT_SUCCESS /obj/machinery/power/turbine/crowbar_act(mob/living/user, obj/item/tool) - return default_deconstruction_crowbar(tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS -/obj/machinery/power/turbine/on_deconstruction() - if(installed_part) - installed_part.forceMove(loc) +/obj/machinery/power/turbine/on_deconstruction(disassembled) + installed_part?.forceMove(loc) return ..() /obj/machinery/power/turbine/crowbar_act_secondary(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING if(!panel_open) balloon_alert(user, "panel is closed!") - return TOOL_ACT_TOOLTYPE_SUCCESS + return if(!installed_part) balloon_alert(user, "no rotor installed!") - return TOOL_ACT_TOOLTYPE_SUCCESS + return if(active) balloon_alert(user, "[src] is on!") - return TOOL_ACT_TOOLTYPE_SUCCESS - user.put_in_hands(installed_part) + return - return TOOL_ACT_TOOLTYPE_SUCCESS + user.put_in_hands(installed_part) + return ITEM_INTERACT_SUCCESS /** * Allow easy enabling of each machine for connection to the main controller + * + * Arguments + * * mob/user - the player who activated the parts + * * check_only - if TRUE it will not activate the machine but will only check if it can be activated */ /obj/machinery/power/turbine/proc/activate_parts(mob/user, check_only = FALSE) can_connect = TRUE /** * Allow easy disabling of each machine from the main controller + * + * Arguments + * * mob/user - the player who deactivated the parts */ /obj/machinery/power/turbine/proc/deactivate_parts(mob/user) can_connect = FALSE @@ -196,7 +244,7 @@ //install the part if(!do_after(user, 2 SECONDS, src)) - return + return TRUE if(installed_part) user.put_in_hands(installed_part) balloon_alert(user, "replaced part with the one in hand") @@ -204,29 +252,19 @@ balloon_alert(user, "installed new part") user.transferItemToLoc(object, src) installed_part = object + return TRUE -/** - * Gets the efficiency of the installed part, returns 0 if no part is installed - */ +/// Gets the efficiency of the installed part, returns 0 if no part is installed /obj/machinery/power/turbine/proc/get_efficiency() - if(installed_part) - return installed_part.part_efficiency - return 0 + return installed_part?.part_efficiency || 0 /obj/machinery/power/turbine/inlet_compressor name = "inlet compressor" desc = "The input side of a turbine generator, contains the compressor." icon = 'icons/obj/machines/engine/turbine.dmi' icon_state = "inlet_compressor" - circuit = /obj/item/circuitboard/machine/turbine_compressor - - gas_theoretical_volume = 1000 - part_path = /obj/item/turbine_parts/compressor - - has_gasmix = TRUE - active_overlay = "inlet_animation" off_overlay = "inlet_off" open_overlay = "inlet_open" @@ -239,9 +277,13 @@ var/compressor_work /// Pressure of gases absorbed var/compressor_pressure - ///Ratio of the amount of gas going in the turbine + ///Ratio of gases going in the turbine var/intake_regulator = 0.5 +/obj/machinery/power/turbine/inlet_compressor/Initialize(mapload) + //Volume of gas mixture is 1000 + return ..(mapload, gas_theoretical_volume = 1000) + /obj/machinery/power/turbine/inlet_compressor/deactivate_parts(mob/user) . = ..() if(!QDELETED(rotor)) @@ -266,29 +308,19 @@ //the compressor compresses down the gases from 2500 L to 1000 L //the temperature and pressure rises up, you can regulate this to increase/decrease the amount of gas moved in. compressor_work = transfer_gases(input_turf_mixture, machine_gasmix, work_amount_to_remove = 0, intake_size = intake_regulator) - input_turf.update_visuals() input_turf.air_update_turf(TRUE) + input_turf.update_visuals() compressor_pressure = PRESSURE_MAX(machine_gasmix.return_pressure()) return input_turf_mixture.temperature -/obj/machinery/power/turbine/inlet_compressor/constructed - mapped = FALSE - /obj/machinery/power/turbine/turbine_outlet name = "turbine outlet" desc = "The output side of a turbine generator, contains the turbine and the stator." icon = 'icons/obj/machines/engine/turbine.dmi' icon_state = "turbine_outlet" - circuit = /obj/item/circuitboard/machine/turbine_stator - - gas_theoretical_volume = 6000 - part_path = /obj/item/turbine_parts/stator - - has_gasmix = TRUE - active_overlay = "outlet_animation" off_overlay = "outlet_off" open_overlay = "outlet_open" @@ -298,6 +330,10 @@ /// The turf to puch the gases out into var/turf/open/output_turf +/obj/machinery/power/turbine/turbine_outlet/Initialize(mapload) + //Volume of gas mixture is 6000 + return ..(mapload, gas_theoretical_volume = 6000) + /obj/machinery/power/turbine/turbine_outlet/deactivate_parts(mob/user) . = ..() if(!QDELETED(rotor)) @@ -316,77 +352,56 @@ //eject gases and update turf is any was ejected var/datum/gas_mixture/ejected_gases = machine_gasmix.pump_gas_to(output_turf.air, machine_gasmix.return_pressure()) if(ejected_gases) - output_turf.update_visuals() output_turf.air_update_turf(TRUE) + output_turf.update_visuals() //return ejected gases return ejected_gases -/obj/machinery/power/turbine/turbine_outlet/constructed - mapped = FALSE - /obj/machinery/power/turbine/core_rotor name = "core rotor" desc = "The middle part of a turbine generator, contains the rotor and the main computer." icon = 'icons/obj/machines/engine/turbine.dmi' icon_state = "core_rotor" - can_change_cable_layer = TRUE - - circuit = /obj/item/circuitboard/machine/turbine_rotor - - gas_theoretical_volume = 3000 - - part_path = /obj/item/turbine_parts/rotor - - has_gasmix = TRUE - active_overlay = "core_light" open_overlay = "core_open" - + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION emissive = TRUE + can_change_cable_layer = TRUE + circuit = /obj/item/circuitboard/machine/turbine_rotor + part_path = /obj/item/turbine_parts/rotor ///ID to easily connect the main part of the turbine to the computer var/mapping_id - ///Reference to the compressor var/obj/machinery/power/turbine/inlet_compressor/compressor ///Reference to the turbine var/obj/machinery/power/turbine/turbine_outlet/turbine - ///Rotation per minute the machine is doing var/rpm ///Amount of power the machine is producing var/produced_energy - ///Check to see if all parts are connected to the core var/all_parts_connected = FALSE - ///Max rmp that the installed parts can handle, limits the rpms var/max_allowed_rpm = 0 ///Max temperature that the installed parts can handle, unlimited and causes damage to the machine var/max_allowed_temperature = 0 - ///Amount of damage the machine has received var/damage = 0 ///Used to calculate the max damage received per tick and if the alarm should be called var/damage_archived = 0 - ///Our internal radio var/obj/item/radio/radio - ///The key our internal radio uses - var/radio_key = /obj/item/encryptionkey/headset_eng - ///The engineering channel - var/engineering_channel = "Engineering" COOLDOWN_DECLARE(turbine_damage_alert) -/obj/machinery/power/turbine/core_rotor/constructed - mapped = FALSE - /obj/machinery/power/turbine/core_rotor/Initialize(mapload) - . = ..() + //Volume of gas mixture is 3000 + . = ..(mapload, gas_theoretical_volume = 3000) + radio = new(src) - radio.keyslot = new radio_key + radio.keyslot = new /obj/item/encryptionkey/headset_eng radio.set_listening(FALSE) radio.recalculateChannels() @@ -396,6 +411,18 @@ QDEL_NULL(radio) return ..() +/obj/machinery/power/turbine/core_rotor/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(. == NONE) + return + + if(held_item.tool_behaviour == TOOL_MULTITOOL) + if(panel_open) + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Change cable layer" + else + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Link/Log parts" + return CONTEXTUAL_SCREENTIP_SET + /obj/machinery/power/turbine/core_rotor/examine(mob/user) . = ..() if(!panel_open) @@ -416,7 +443,7 @@ //failed checks if(!activate_parts(user)) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS //log rotor to link later to computer balloon_alert(user, "all parts linked") @@ -425,7 +452,7 @@ to_chat(user, span_notice("You store linkage information in [tool]'s buffer.")) //success - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/power/turbine/core_rotor/multitool_act_secondary(mob/living/user, obj/item/tool) //allow cable layer changing @@ -512,19 +539,19 @@ /obj/machinery/power/turbine/core_rotor/deactivate_parts() if(all_parts_connected) power_off() + compressor?.rotor = null compressor = null + turbine?.rotor = null turbine = null all_parts_connected = FALSE disconnect_from_network() SSair.stop_processing_machine(src) -/obj/machinery/power/turbine/core_rotor/on_deconstruction() +/obj/machinery/power/turbine/core_rotor/on_deconstruction(disassembled) deactivate_parts() return ..() -/** - * Toggle power on and off, not safe - */ +/// Toggle power on and off, not safe /obj/machinery/power/turbine/core_rotor/proc/toggle_power() if(active) power_off() @@ -544,9 +571,7 @@ call_parts_update_appearance() SSair.start_processing_machine(src) -/** - * Calls all parts update appearance proc. - */ +/// Calls all parts update appearance proc. /obj/machinery/power/turbine/core_rotor/proc/call_parts_update_appearance() update_appearance() if(!QDELETED(compressor)) @@ -569,9 +594,7 @@ call_parts_update_appearance() SSair.stop_processing_machine(src) -/** - * Returns true if all parts have their panel closed - */ +/// Returns true if all parts have their panel closed /obj/machinery/power/turbine/core_rotor/proc/all_parts_ready() if(QDELETED(compressor)) return FALSE @@ -579,19 +602,20 @@ return FALSE return !panel_open && !compressor.panel_open && !turbine.panel_open -/** - * Getter for turbine integrity, return the amount in % - */ +/// Getter for turbine integrity, return the amount in % /obj/machinery/power/turbine/core_rotor/proc/get_turbine_integrity() var/integrity = damage / 500 integrity = max(round(100 - integrity * 100, 0.01), 0) return integrity /obj/machinery/power/turbine/core_rotor/process_atmos() - if(!active || !activate_parts(check_only = TRUE)) + if(!active || !activate_parts(check_only = TRUE) || (machine_stat & BROKEN) || !powered(ignore_use_power = TRUE)) power_off() return PROCESS_KILL + //use power to operate internal electronics & stuff + update_mode_power_usage(ACTIVE_POWER_USE, active_power_usage) + //===============COMPRESSOR WORKING========// //Transfer gases from turf to compressor var/temperature = compressor.compress_gases() @@ -622,7 +646,8 @@ if(rpm < 550000) explosion(src, 2, 5, 7) return PROCESS_KILL - radio.talk_into(src, "Warning, turbine at [get_area_name(src)] taking damage, current integrity at [integrity]%!", engineering_channel) + + radio.talk_into(src, "Warning, turbine at [get_area_name(src)] taking damage, current integrity at [integrity]%!", RADIO_CHANNEL_ENGINEERING) playsound(src, 'sound/machines/engine_alert1.ogg', 100, FALSE, 30, 30, falloff_distance = 10) //================ROTOR WORKING============// @@ -644,7 +669,7 @@ //calculate final acheived rpm rpm = ((work_done * compressor.get_efficiency()) ** turbine.get_efficiency()) * get_efficiency() / TURBINE_RPM_CONVERSION rpm = FLOOR(min(rpm, max_allowed_rpm), 1) - //add energy into the grid + //add energy into the grid, also use part of it for turbine operation produced_energy = rpm * TURBINE_ENERGY_RECTIFICATION_MULTIPLIER * TURBINE_RPM_CONVERSION add_avail(produced_energy) diff --git a/code/modules/power/turbine/turbine_computer.dm b/code/modules/power/turbine/turbine_computer.dm index 8e8ba8deb4c76..9e0f5bdaa469e 100644 --- a/code/modules/power/turbine/turbine_computer.dm +++ b/code/modules/power/turbine/turbine_computer.dm @@ -54,20 +54,20 @@ var/list/data = list() var/obj/machinery/power/turbine/core_rotor/main_control = turbine_core?.resolve() - - data["connected"] = main_control ? TRUE : FALSE + data["connected"] = !!QDELETED(main_control) if(!main_control) return + data["active"] = main_control.active data["rpm"] = main_control.rpm ? main_control.rpm : 0 data["power"] = main_control.produced_energy ? main_control.produced_energy : 0 - data["temp"] = main_control.compressor.input_turf?.air.temperature data["integrity"] = main_control.get_turbine_integrity() data["parts_linked"] = main_control.all_parts_connected data["parts_ready"] = main_control.all_parts_ready() data["max_rpm"] = main_control.max_allowed_rpm data["max_temperature"] = main_control.max_allowed_temperature + data["temp"] = main_control.compressor?.input_turf?.air.temperature || 0 data["regulator"] = QDELETED(main_control.compressor) ? 0 : main_control.compressor.intake_regulator return data diff --git a/code/modules/power/turbine/turbine_parts.dm b/code/modules/power/turbine/turbine_parts.dm index d53d92190e1a5..4215fccf39fe0 100644 --- a/code/modules/power/turbine/turbine_parts.dm +++ b/code/modules/power/turbine/turbine_parts.dm @@ -64,7 +64,7 @@ if(!istype(attacking_item, second_tier_material)) return var/obj/item/stack/sheet/second_tier = attacking_item - if(second_tier.use(second_tier_material_amount) && do_after(user, 1 SECONDS, src)) + if(do_after(user, 1 SECONDS, src) && second_tier.use(second_tier_material_amount)) current_tier = 2 part_efficiency += part_efficiency_increase_amount max_rpm *= max_rpm_tier_multiplier @@ -74,7 +74,7 @@ if(!istype(attacking_item, third_tier_material)) return var/obj/item/stack/sheet/third_tier = attacking_item - if(third_tier.use(third_tier_material_amount) && do_after(user, 2 SECONDS, src)) + if(do_after(user, 2 SECONDS, src) && third_tier.use(third_tier_material_amount)) current_tier = 3 part_efficiency += part_efficiency_increase_amount max_rpm *= max_rpm_tier_multiplier @@ -84,7 +84,7 @@ if(!istype(attacking_item, fourth_tier_material)) return var/obj/item/stack/sheet/fourth_tier = attacking_item - if(fourth_tier.use(fourth_tier_material_amount) && do_after(user, 3 SECONDS, src)) + if(do_after(user, 3 SECONDS, src) && fourth_tier.use(fourth_tier_material_amount)) current_tier = 4 part_efficiency += part_efficiency_increase_amount max_rpm *= max_rpm_tier_multiplier diff --git a/code/modules/procedural_mapping/mapGenerator.dm b/code/modules/procedural_mapping/mapGenerator.dm index 4a79ad4c3059a..420ac9c8d7976 100644 --- a/code/modules/procedural_mapping/mapGenerator.dm +++ b/code/modules/procedural_mapping/mapGenerator.dm @@ -1,10 +1,12 @@ +///This type is responsible for any map generation behavior that is done in areas, override this to allow for +///area-specific map generation. This generation is ran by areas in initialize. /datum/map_generator - //Map information - var/list/map = list() + ///Map information, such as the start and end turfs of the map generation. + var/list/turf/map = list() - //mapGeneratorModule information - var/list/modules = list() + ///The map generator modules that we will generate and sync to. + var/list/datum/map_generator_module/modules = list() var/buildmode_name = "Undocumented" @@ -14,6 +16,18 @@ buildmode_name = copytext_char("[type]", 20) // / d a t u m / m a p g e n e r a t o r / = 20 characters. initialiseModules() +/datum/map_generator/Destroy(force) + . = ..() + QDEL_LIST(modules) + +///This proc will be ran by areas on Initialize, and provides the areas turfs as argument to allow for generation. +/datum/map_generator/proc/generate_terrain(list/turfs, area/generate_in) + return + +/// Populate terrain with flora, fauna, features and basically everything that isn't a turf. +/datum/map_generator/proc/populate_terrain(list/turfs, area/generate_in) + return + //Defines the region the map represents, sets map //Returns the map /datum/map_generator/proc/defineRegion(turf/Start, turf/End, replace = 0) @@ -22,7 +36,7 @@ if(replace) undefineRegion() - map |= block(Start,End) + map |= block(Start, End) return map @@ -56,7 +70,7 @@ theRadius = max(radius/max((2*abs(sphereMagic-i)),1),1) - map |= circle_range(locate(centerX,centerY,i),theRadius) + map |= circle_range(locate(centerX, centerY, i),theRadius) return map @@ -87,7 +101,7 @@ syncModules() if(!modules || !modules.len) return - for(var/datum/map_generator_module/mod in modules) + for(var/datum/map_generator_module/mod as anything in modules) INVOKE_ASYNC(mod, TYPE_PROC_REF(/datum/map_generator_module, generate)) @@ -98,7 +112,7 @@ syncModules() if(!modules || !modules.len) return - for(var/datum/map_generator_module/mod in modules) + for(var/datum/map_generator_module/mod as anything in modules) INVOKE_ASYNC(mod, TYPE_PROC_REF(/datum/map_generator_module, place), T) @@ -113,7 +127,7 @@ //Sync mapGeneratorModule(s) to mapGenerator /datum/map_generator/proc/syncModules() - for(var/datum/map_generator_module/mod in modules) + for(var/datum/map_generator_module/mod as anything in modules) mod.sync(src) @@ -127,12 +141,12 @@ set category = "Debug" var/datum/map_generator/nature/N = new() - var/startInput = input(usr,"Start turf of Map, (X;Y;Z)", "Map Gen Settings", "1;1;1") as text|null + var/startInput = input(usr, "Start turf of Map, (X;Y;Z)", "Map Gen Settings", "1;1;1") as text|null if (isnull(startInput)) return - var/endInput = input(usr,"End turf of Map (X;Y;Z)", "Map Gen Settings", "[world.maxx];[world.maxy];[mob ? mob.z : 1]") as text|null + var/endInput = input(usr, "End turf of Map (X;Y;Z)", "Map Gen Settings", "[world.maxx];[world.maxy];[mob ? mob.z : 1]") as text|null if (isnull(endInput)) return @@ -158,9 +172,18 @@ to_chat(src, "End Coords: [endCoords[1]] - [endCoords[2]] - [endCoords[3]]") return - var/list/clusters = list("None"=CLUSTER_CHECK_NONE,"All"=CLUSTER_CHECK_ALL,"Sames"=CLUSTER_CHECK_SAMES,"Differents"=CLUSTER_CHECK_DIFFERENTS, \ - "Same turfs"=CLUSTER_CHECK_SAME_TURFS, "Same atoms"=CLUSTER_CHECK_SAME_ATOMS, "Different turfs"=CLUSTER_CHECK_DIFFERENT_TURFS, \ - "Different atoms"=CLUSTER_CHECK_DIFFERENT_ATOMS, "All turfs"=CLUSTER_CHECK_ALL_TURFS,"All atoms"=CLUSTER_CHECK_ALL_ATOMS) + var/static/list/clusters = list( + "None" = CLUSTER_CHECK_NONE, + "All" = CLUSTER_CHECK_ALL, + "Sames" = CLUSTER_CHECK_SAMES, + "Differents" = CLUSTER_CHECK_DIFFERENTS, + "Same turfs" = CLUSTER_CHECK_SAME_TURFS, + "Same atoms" = CLUSTER_CHECK_SAME_ATOMS, + "Different turfs" = CLUSTER_CHECK_DIFFERENT_TURFS, + "Different atoms" = CLUSTER_CHECK_DIFFERENT_ATOMS, + "All turfs" = CLUSTER_CHECK_ALL_TURFS, + "All atoms" = CLUSTER_CHECK_ALL_ATOMS, + ) var/moduleClusters = input("Cluster Flags (Cancel to leave unchanged from defaults)","Map Gen Settings") as null|anything in clusters //null for default @@ -175,7 +198,7 @@ theCluster = CLUSTER_CHECK_NONE if(theCluster) - for(var/datum/map_generator_module/M in N.modules) + for(var/datum/map_generator_module/M as anything in N.modules) M.clusterCheckFlags = theCluster diff --git a/code/modules/procedural_mapping/mapGeneratorModule.dm b/code/modules/procedural_mapping/mapGeneratorModule.dm index d5742fdb85ab0..7bf32d15195f5 100644 --- a/code/modules/procedural_mapping/mapGeneratorModule.dm +++ b/code/modules/procedural_mapping/mapGeneratorModule.dm @@ -8,6 +8,9 @@ var/clusterCheckFlags = CLUSTER_CHECK_SAME_ATOMS var/allowAtomsOnSpace = FALSE +/datum/map_generator_module/Destroy(force) + mother = null + return ..() //Syncs the module up with its mother /datum/map_generator_module/proc/sync(datum/map_generator/mum) diff --git a/code/modules/procedural_mapping/mapGenerators/asteroid.dm b/code/modules/procedural_mapping/mapGenerators/asteroid.dm index ab2bc6f2ca426..bf6c84ebf883a 100644 --- a/code/modules/procedural_mapping/mapGenerators/asteroid.dm +++ b/code/modules/procedural_mapping/mapGenerators/asteroid.dm @@ -22,7 +22,7 @@ spawnableAtoms = list( /mob/living/basic/mining/basilisk = 10, /mob/living/basic/mining/goliath/ancient = 10, - /mob/living/simple_animal/hostile/asteroid/hivelord = 10, + /mob/living/basic/mining/hivelord = 10, ) diff --git a/code/modules/procedural_mapping/mapGenerators/lavaland.dm b/code/modules/procedural_mapping/mapGenerators/lavaland.dm index 2c8ae376a3c47..5251f5e8435a2 100644 --- a/code/modules/procedural_mapping/mapGenerators/lavaland.dm +++ b/code/modules/procedural_mapping/mapGenerators/lavaland.dm @@ -3,10 +3,10 @@ spawnableTurfs = list(/turf/open/misc/asteroid/basalt/lava_land_surface = 100) /datum/map_generator_module/bottom_layer/lavaland_mineral - spawnableTurfs = list(/turf/closed/mineral/random/volcanic = 100) + spawnableTurfs = list(/turf/closed/mineral/volcanic = 100) /datum/map_generator_module/bottom_layer/lavaland_mineral/dense - spawnableTurfs = list(/turf/closed/mineral/random/high_chance/volcanic = 100) + spawnableTurfs = list(/turf/closed/mineral/volcanic = 100) /datum/map_generator_module/splatter_layer/lavaland_monsters spawnableTurfs = list() diff --git a/code/modules/procedural_mapping/mapGenerators/syndicate.dm b/code/modules/procedural_mapping/mapGenerators/syndicate.dm index b9dc00e13642d..74d2d153d06a5 100644 --- a/code/modules/procedural_mapping/mapGenerators/syndicate.dm +++ b/code/modules/procedural_mapping/mapGenerators/syndicate.dm @@ -17,10 +17,12 @@ /obj/structure/closet/syndicate = 25, /obj/machinery/suit_storage_unit/syndicate = 15) /datum/map_generator_module/splatter_layer/syndie_mobs - spawnableAtoms = list(/mob/living/basic/syndicate = 30, \ - /mob/living/basic/syndicate/melee = 20, \ - /mob/living/basic/syndicate/ranged = 20, \ - /mob/living/basic/viscerator = 30) + spawnableAtoms = list( + /mob/living/basic/trooper/syndicate = 30, + /mob/living/basic/trooper/syndicate/melee = 20, + /mob/living/basic/trooper/syndicate/ranged = 20, + /mob/living/basic/viscerator = 30 + ) spawnableTurfs = list() // Generators diff --git a/code/modules/projectiles/ammunition/_ammunition.dm b/code/modules/projectiles/ammunition/_ammunition.dm index e492afb776b75..45e09db624caf 100644 --- a/code/modules/projectiles/ammunition/_ammunition.dm +++ b/code/modules/projectiles/ammunition/_ammunition.dm @@ -4,7 +4,7 @@ icon = 'icons/obj/weapons/guns/ammo.dmi' icon_state = "s-casing" worn_icon_state = "bullet" - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BELT throwforce = 0 w_class = WEIGHT_CLASS_TINY diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm index 06c2ba51d3b41..1b1decd3649ee 100644 --- a/code/modules/projectiles/ammunition/_firing.dm +++ b/code/modules/projectiles/ammunition/_firing.dm @@ -65,6 +65,7 @@ if(reagents && loaded_projectile.reagents) reagents.trans_to(loaded_projectile, reagents.total_volume, transferred_by = user) //For chemical darts/bullets qdel(reagents) + SEND_SIGNAL(src, COMSIG_CASING_READY_PROJECTILE, target, user, quiet, zone_override, fired_from) /obj/item/ammo_casing/proc/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread, atom/fired_from) var/turf/curloc = get_turf(fired_from) diff --git a/code/modules/projectiles/ammunition/ballistic/foam.dm b/code/modules/projectiles/ammunition/ballistic/foam.dm index 21ceeb6918bbc..2895d74555be5 100644 --- a/code/modules/projectiles/ammunition/ballistic/foam.dm +++ b/code/modules/projectiles/ammunition/ballistic/foam.dm @@ -9,6 +9,7 @@ custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 0.1125) harmful = FALSE var/modified = FALSE + var/static/list/insertable_items_hint = list(/obj/item/pen) /obj/item/ammo_casing/foam_dart/Initialize(mapload) . = ..() @@ -18,47 +19,37 @@ . = ..() if(modified) icon_state = "[base_icon_state]_empty" - loaded_projectile?.icon_state = "[base_icon_state]_empty" + loaded_projectile?.icon_state = "[loaded_projectile.base_icon_state]_empty_proj" return icon_state = "[base_icon_state]" - loaded_projectile?.icon_state = "[loaded_projectile.base_icon_state]" + loaded_projectile?.icon_state = "[loaded_projectile.base_icon_state]_proj" /obj/item/ammo_casing/foam_dart/update_desc() . = ..() desc = "It's Donk or Don't! [modified ? "... Although, this one doesn't look too safe." : "Ages 8 and up."]" -/obj/item/ammo_casing/foam_dart/attackby(obj/item/A, mob/user, params) - var/obj/projectile/bullet/foam_dart/FD = loaded_projectile - if (A.tool_behaviour == TOOL_SCREWDRIVER && !modified) +/obj/item/ammo_casing/foam_dart/examine_more(mob/user) + . = ..() + if(!HAS_TRAIT(src, TRAIT_DART_HAS_INSERT)) + var/list/type_initial_names = list() + for(var/type in insertable_items_hint) + var/obj/item/type_item = type + type_initial_names += "\a [initial(type_item.name)]" + . += span_notice("[modified ? "You can" : "If you removed the safety cap with a screwdriver, you could"] insert a small item\ + [length(type_initial_names) ? ", such as [english_list(type_initial_names, and_text = "or ", final_comma_text = ", ")]" : ""].") + + +/obj/item/ammo_casing/foam_dart/attackby(obj/item/attacking_item, mob/user, params) + var/obj/projectile/bullet/foam_dart/dart = loaded_projectile + if (attacking_item.tool_behaviour == TOOL_SCREWDRIVER && !modified) modified = TRUE - FD.modified = TRUE - FD.damage_type = BRUTE + dart.modified = TRUE + dart.damage_type = BRUTE to_chat(user, span_notice("You pop the safety cap off [src].")) update_appearance() - else if (istype(A, /obj/item/pen)) - if(modified) - if(!FD.pen) - harmful = TRUE - if(!user.transferItemToLoc(A, FD)) - return - FD.pen = A - FD.damage = 5 - to_chat(user, span_notice("You insert [A] into [src].")) - else - to_chat(user, span_warning("There's already something in [src].")) - else - to_chat(user, span_warning("The safety cap prevents you from inserting [A] into [src].")) else return ..() -/obj/item/ammo_casing/foam_dart/attack_self(mob/living/user) - var/obj/projectile/bullet/foam_dart/FD = loaded_projectile - if(FD.pen) - FD.damage = initial(FD.damage) - user.put_in_hands(FD.pen) - to_chat(user, span_notice("You remove [FD.pen] from [src].")) - FD.pen = null - /obj/item/ammo_casing/foam_dart/riot name = "riot foam dart" desc = "Whose smart idea was it to use toys as crowd control? Ages 18 and up." diff --git a/code/modules/projectiles/ammunition/ballistic/pistol.dm b/code/modules/projectiles/ammunition/ballistic/pistol.dm index c61888b95259f..a2f55f797bdb5 100644 --- a/code/modules/projectiles/ammunition/ballistic/pistol.dm +++ b/code/modules/projectiles/ammunition/ballistic/pistol.dm @@ -21,6 +21,11 @@ desc = "A 10mm incendiary bullet casing." projectile_type = /obj/projectile/bullet/incendiary/c10mm +/obj/item/ammo_casing/c10mm/reaper + name = "10mm reaper bullet casing" + desc = "A 10mm reaper bullet casing." + projectile_type = /obj/projectile/bullet/c10mm/reaper + // 9mm (Makarov, Stechkin APS, PP-95) /obj/item/ammo_casing/c9mm diff --git a/code/modules/projectiles/ammunition/ballistic/rifle.dm b/code/modules/projectiles/ammunition/ballistic/rifle.dm index 3e545dc106077..8e06a0e10b5af 100644 --- a/code/modules/projectiles/ammunition/ballistic/rifle.dm +++ b/code/modules/projectiles/ammunition/ballistic/rifle.dm @@ -49,3 +49,27 @@ name = "40mm rubber shell" desc = "A cased rubber slug. The big brother of the beanbag slug, this thing will knock someone out in one. Doesn't do so great against anyone in armor." projectile_type = /obj/projectile/bullet/shotgun_beanbag/a40mm + +/obj/item/ammo_casing/rebar + name = "sharpened iron rod" + desc = "A Sharpened Iron rod. It's Pointy!" + caliber = CALIBER_REBAR + icon_state = "rod_sharp" + base_icon_state = "rod_sharp" + projectile_type = /obj/projectile/bullet/rebar + +/obj/item/ammo_casing/rebar/Initialize(mapload) + . = ..() + AddElement(/datum/element/caseless, TRUE) + +/obj/item/ammo_casing/rebar/update_icon_state() + . = ..() + icon_state = "[base_icon_state]" + +/obj/item/ammo_casing/rebar/syndie + name = "Jagged iron rod" + desc = "An Iron rod, with notches cut into it. You really dont want this stuck in you." + caliber = CALIBER_REBAR_SYNDIE + icon_state = "rod_jagged" + base_icon_state = "rod_jagged" + projectile_type = /obj/projectile/bullet/rebarsyndie diff --git a/code/modules/projectiles/ammunition/ballistic/shotgun.dm b/code/modules/projectiles/ammunition/ballistic/shotgun.dm index ac3cc859b63b8..078f4bba1c4fd 100644 --- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm +++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm @@ -121,20 +121,39 @@ pellets = 4 variance = 35 -/obj/item/ammo_casing/shotgun/laserslug +/obj/item/ammo_casing/shotgun/scatterlaser name = "scatter laser shell" desc = "An advanced shotgun shell that uses a micro laser to replicate the effects of a scatter laser weapon in a ballistic package." icon_state = "lshell" - projectile_type = /obj/projectile/beam/weak + projectile_type = /obj/projectile/beam/scatter pellets = 6 variance = 35 +/obj/item/ammo_casing/shotgun/scatterlaser/emp_act(severity) + . = ..() + if(isnull(loaded_projectile) || !prob(40/severity)) + return + name = "malfunctioning laser shell" + desc = "An advanced shotgun shell that uses a micro laser to replicate the effects of a scatter laser weapon in a ballistic package. The capacitor powering this assembly appears to be smoking." + projectile_type = /obj/projectile/beam/scatter/pathetic + loaded_projectile = new projectile_type(src) + /obj/item/ammo_casing/shotgun/techshell name = "unloaded technological shell" desc = "A high-tech shotgun shell which can be loaded with materials to produce unique effects." icon_state = "cshell" projectile_type = null +/obj/item/ammo_casing/shotgun/techshell/Initialize(mapload) + . = ..() + + var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/meteorslug, /datum/crafting_recipe/pulseslug, /datum/crafting_recipe/dragonsbreath, /datum/crafting_recipe/ionslug) + + AddComponent( + /datum/component/slapcrafting,\ + slapcraft_recipes = slapcraft_recipe_list,\ + ) + /obj/item/ammo_casing/shotgun/dart name = "shotgun dart" desc = "A dart for use in shotguns. Can be injected with up to 15 units of any chemical." diff --git a/code/modules/projectiles/ammunition/energy/_energy.dm b/code/modules/projectiles/ammunition/energy/_energy.dm index 8ff8f6510caf3..877dc7784d02d 100644 --- a/code/modules/projectiles/ammunition/energy/_energy.dm +++ b/code/modules/projectiles/ammunition/energy/_energy.dm @@ -4,7 +4,7 @@ caliber = ENERGY projectile_type = /obj/projectile/energy slot_flags = null - var/e_cost = 100 //The amount of energy a cell needs to expend to create this shot. + var/e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE) //The amount of energy a cell needs to expend to create this shot. var/select_name = CALIBER_ENERGY fire_sound = 'sound/weapons/laser.ogg' - firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/red diff --git a/code/modules/projectiles/ammunition/energy/ebow.dm b/code/modules/projectiles/ammunition/energy/ebow.dm index 0eee10a58e532..a6a928c25095d 100644 --- a/code/modules/projectiles/ammunition/energy/ebow.dm +++ b/code/modules/projectiles/ammunition/energy/ebow.dm @@ -1,8 +1,9 @@ /obj/item/ammo_casing/energy/bolt projectile_type = /obj/projectile/energy/bolt select_name = "bolt" - e_cost = 500 + e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE * 0.5) fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' // Even for non-suppressed crossbows, this is the most appropriate sound + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect /obj/item/ammo_casing/energy/bolt/halloween projectile_type = /obj/projectile/energy/bolt/halloween diff --git a/code/modules/projectiles/ammunition/energy/gravity.dm b/code/modules/projectiles/ammunition/energy/gravity.dm index 5b781189c5282..6ad3a776475ce 100644 --- a/code/modules/projectiles/ammunition/energy/gravity.dm +++ b/code/modules/projectiles/ammunition/energy/gravity.dm @@ -1,9 +1,10 @@ /obj/item/ammo_casing/energy/gravity - e_cost = 0 + e_cost = 0 // Not possible to use the macro fire_sound = 'sound/weapons/wave.ogg' select_name = "gravity" delay = 50 var/obj/item/gun/energy/gravity_gun/gun + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect /obj/item/ammo_casing/energy/gravity/Initialize(mapload) if(istype(loc,/obj/item/gun/energy/gravity_gun)) diff --git a/code/modules/projectiles/ammunition/energy/laser.dm b/code/modules/projectiles/ammunition/energy/laser.dm index fbf9b289aa079..6eb2d238bb061 100644 --- a/code/modules/projectiles/ammunition/energy/laser.dm +++ b/code/modules/projectiles/ammunition/energy/laser.dm @@ -1,32 +1,40 @@ /obj/item/ammo_casing/energy/laser projectile_type = /obj/projectile/beam/laser - e_cost = 83 + e_cost = LASER_SHOTS(12, STANDARD_CELL_CHARGE) select_name = "kill" /obj/item/ammo_casing/energy/laser/hellfire projectile_type = /obj/projectile/beam/laser/hellfire - e_cost = 100 + e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE) select_name = "maim" -/obj/item/ammo_casing/energy/laser/hellfire/antique - e_cost = 100 - /obj/item/ammo_casing/energy/lasergun projectile_type = /obj/projectile/beam/laser - e_cost = 62.5 + e_cost = LASER_SHOTS(16, STANDARD_CELL_CHARGE) + select_name = "kill" + +/obj/item/ammo_casing/energy/lasergun/carbine + projectile_type = /obj/projectile/beam/laser/carbine + e_cost = LASER_SHOTS(40, STANDARD_CELL_CHARGE) select_name = "kill" + fire_sound = 'sound/weapons/laser2.ogg' + +/obj/item/ammo_casing/energy/lasergun/carbine/practice + projectile_type = /obj/projectile/beam/laser/carbine/practice + select_name = "practice" + harmful = FALSE /obj/item/ammo_casing/energy/lasergun/old projectile_type = /obj/projectile/beam/laser - e_cost = 200 + e_cost = LASER_SHOTS(5, STANDARD_CELL_CHARGE) select_name = "kill" /obj/item/ammo_casing/energy/laser/hos - e_cost = 120 + e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE * 1.2) /obj/item/ammo_casing/energy/laser/musket projectile_type = /obj/projectile/beam/laser/musket - e_cost = 1000 + e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/laser/musket/prime projectile_type = /obj/projectile/beam/laser/musket/prime @@ -38,7 +46,7 @@ /obj/item/ammo_casing/energy/chameleon projectile_type = /obj/projectile/energy/chameleon - e_cost = 0 + e_cost = 0 // Can't really use the macro here, unfortunately var/projectile_vars = list() /obj/item/ammo_casing/energy/chameleon/ready_proj() @@ -70,6 +78,7 @@ pellets = 3 variance = 15 harmful = FALSE + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/blue /obj/item/ammo_casing/energy/laser/heavy projectile_type = /obj/projectile/beam/laser/heavylaser @@ -78,9 +87,10 @@ /obj/item/ammo_casing/energy/laser/pulse projectile_type = /obj/projectile/beam/pulse - e_cost = 200 + e_cost = LASER_SHOTS(200, STANDARD_CELL_CHARGE * 40) select_name = "DESTROY" fire_sound = 'sound/weapons/pulse.ogg' + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/blue /obj/item/ammo_casing/energy/laser/bluetag projectile_type = /obj/projectile/beam/lasertag/bluetag @@ -100,7 +110,7 @@ /obj/item/ammo_casing/energy/xray projectile_type = /obj/projectile/beam/xray - e_cost = 50 + e_cost = LASER_SHOTS(20, STANDARD_CELL_CHARGE) fire_sound = 'sound/weapons/laser3.ogg' /obj/item/ammo_casing/energy/mindflayer @@ -116,7 +126,7 @@ /obj/item/ammo_casing/energy/nanite projectile_type = /obj/projectile/bullet/c10mm //henk select_name = "bullet" - e_cost = 120 + e_cost = LASER_SHOTS(8, STANDARD_CELL_CHARGE) fire_sound = 'sound/weapons/thermalpistol.ogg' /obj/item/ammo_casing/energy/nanite/inferno @@ -126,6 +136,7 @@ /obj/item/ammo_casing/energy/nanite/cryo projectile_type = /obj/projectile/energy/cryo select_name = "cryo" + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/blue ///not exactly an energy ammo casing, but it's used by the laser gatling. /obj/item/ammo_casing/laser @@ -137,7 +148,7 @@ slot_flags = null projectile_type = /obj/projectile/beam fire_sound = 'sound/weapons/laser.ogg' - firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/red /obj/item/ammo_casing/laser/Initialize(mapload) . = ..() diff --git a/code/modules/projectiles/ammunition/energy/lmg.dm b/code/modules/projectiles/ammunition/energy/lmg.dm index fbd5916613e39..632044f065203 100644 --- a/code/modules/projectiles/ammunition/energy/lmg.dm +++ b/code/modules/projectiles/ammunition/energy/lmg.dm @@ -2,5 +2,5 @@ projectile_type = /obj/projectile/bullet/c3d select_name = "spraydown" fire_sound = 'sound/weapons/gun/smg/shot.ogg' - e_cost = 20 + e_cost = LASER_SHOTS(30, STANDARD_CELL_CHARGE * 0.6) firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect diff --git a/code/modules/projectiles/ammunition/energy/plasma.dm b/code/modules/projectiles/ammunition/energy/plasma.dm index 00de4a90ffee7..e660903bdc95d 100644 --- a/code/modules/projectiles/ammunition/energy/plasma.dm +++ b/code/modules/projectiles/ammunition/energy/plasma.dm @@ -3,9 +3,9 @@ select_name = "plasma burst" fire_sound = 'sound/weapons/plasma_cutter.ogg' delay = 15 - e_cost = 25 + e_cost = LASER_SHOTS(40, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/plasma/adv projectile_type = /obj/projectile/plasma/adv delay = 10 - e_cost = 10 + e_cost = LASER_SHOTS(100, STANDARD_CELL_CHARGE) diff --git a/code/modules/projectiles/ammunition/energy/portal.dm b/code/modules/projectiles/ammunition/energy/portal.dm index 8bdd697f1bfc6..787f2e4eac76c 100644 --- a/code/modules/projectiles/ammunition/energy/portal.dm +++ b/code/modules/projectiles/ammunition/energy/portal.dm @@ -1,15 +1,17 @@ /obj/item/ammo_casing/energy/wormhole projectile_type = /obj/projectile/beam/wormhole - e_cost = 0 + e_cost = 0 // Can't use the macro harmful = FALSE fire_sound = 'sound/weapons/pulse3.ogg' select_name = "blue" //Weakref to the gun that shot us var/datum/weakref/gun + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/blue /obj/item/ammo_casing/energy/wormhole/orange projectile_type = /obj/projectile/beam/wormhole/orange select_name = "orange" + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/red /obj/item/ammo_casing/energy/wormhole/Initialize(mapload, obj/item/gun/energy/wormhole_projector/wh) . = ..() diff --git a/code/modules/projectiles/ammunition/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm index 24fba4b9ba492..e5de3df5d50d4 100644 --- a/code/modules/projectiles/ammunition/energy/special.dm +++ b/code/modules/projectiles/ammunition/energy/special.dm @@ -2,45 +2,48 @@ projectile_type = /obj/projectile/ion select_name = "ion" fire_sound = 'sound/weapons/ionrifle.ogg' + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/blue /obj/item/ammo_casing/energy/ion/hos projectile_type = /obj/projectile/ion/weak - e_cost = 300 + e_cost = LASER_SHOTS(4, STANDARD_CELL_CHARGE * 1.2) -/obj/item/ammo_casing/energy/declone - projectile_type = /obj/projectile/energy/declone +/obj/item/ammo_casing/energy/radiation + projectile_type = /obj/projectile/energy/radiation select_name = "declone" fire_sound = 'sound/weapons/pulse3.ogg' -/obj/item/ammo_casing/energy/declone/weak - projectile_type = /obj/projectile/energy/declone/weak +/obj/item/ammo_casing/energy/radiation/weak + projectile_type = /obj/projectile/energy/radiation/weak /obj/item/ammo_casing/energy/flora fire_sound = 'sound/effects/stealthoff.ogg' harmful = FALSE /obj/item/ammo_casing/energy/flora/yield - projectile_type = /obj/projectile/energy/florayield + projectile_type = /obj/projectile/energy/flora/yield select_name = "yield" /obj/item/ammo_casing/energy/flora/mut - projectile_type = /obj/projectile/energy/floramut + projectile_type = /obj/projectile/energy/flora/mut select_name = "mutation" /obj/item/ammo_casing/energy/flora/revolution - projectile_type = /obj/projectile/energy/florarevolution + projectile_type = /obj/projectile/energy/flora/evolution select_name = "revolution" - e_cost = 250 + e_cost = LASER_SHOTS(4, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/temp projectile_type = /obj/projectile/temp select_name = "freeze" - e_cost = 250 + e_cost = LASER_SHOTS(40, STANDARD_CELL_CHARGE * 10) fire_sound = 'sound/weapons/pulse3.ogg' + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/blue /obj/item/ammo_casing/energy/temp/hot projectile_type = /obj/projectile/temp/hot select_name = "bake" + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/red /obj/item/ammo_casing/energy/meteor projectile_type = /obj/projectile/meteor @@ -60,23 +63,25 @@ /obj/item/ammo_casing/energy/tesla_cannon fire_sound = 'sound/magic/lightningshock.ogg' - e_cost = 30 + e_cost = LASER_SHOTS(33, STANDARD_CELL_CHARGE) select_name = "shock" projectile_type = /obj/projectile/energy/tesla_cannon + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/blue /obj/item/ammo_casing/energy/shrink projectile_type = /obj/projectile/beam/shrink select_name = "shrink ray" - e_cost = 200 + e_cost = LASER_SHOTS(5, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/marksman projectile_type = /obj/projectile/bullet/marksman select_name = "marksman nanoshot" - e_cost = 0 + e_cost = 0 // Can't use the macro fire_sound = 'sound/weapons/gun/revolver/shot_alt.ogg' /obj/item/ammo_casing/energy/fisher projectile_type = /obj/projectile/energy/fisher - select_name = "light-buster" - e_cost = 250 + select_name = "light disruptor" + harmful = FALSE + e_cost = LASER_SHOTS(2, STANDARD_CELL_CHARGE * 0.5) fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' // fwip fwip fwip fwip diff --git a/code/modules/projectiles/ammunition/energy/stun.dm b/code/modules/projectiles/ammunition/energy/stun.dm index 0a34ab1782c6b..a7c3f61ee750a 100644 --- a/code/modules/projectiles/ammunition/energy/stun.dm +++ b/code/modules/projectiles/ammunition/energy/stun.dm @@ -2,33 +2,40 @@ projectile_type = /obj/projectile/energy/electrode select_name = "stun" fire_sound = 'sound/weapons/taser.ogg' - e_cost = 200 + e_cost = LASER_SHOTS(5, STANDARD_CELL_CHARGE) harmful = FALSE + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect /obj/item/ammo_casing/energy/electrode/spec - e_cost = 100 + e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/electrode/gun fire_sound = 'sound/weapons/gun/pistol/shot.ogg' - e_cost = 100 + e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/electrode/old - e_cost = 1000 + e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/disabler projectile_type = /obj/projectile/beam/disabler select_name = "disable" - e_cost = 50 + e_cost = LASER_SHOTS(20, STANDARD_CELL_CHARGE) fire_sound = 'sound/weapons/taser2.ogg' harmful = FALSE + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/blue + +/obj/item/ammo_casing/energy/disabler/smg + projectile_type = /obj/projectile/beam/disabler/weak + e_cost = LASER_SHOTS(40, STANDARD_CELL_CHARGE) + fire_sound = 'sound/weapons/taser3.ogg' /obj/item/ammo_casing/energy/disabler/hos - e_cost = 60 + e_cost = LASER_SHOTS(20, STANDARD_CELL_CHARGE * 1.2) /obj/item/ammo_casing/energy/disabler/smoothbore projectile_type = /obj/projectile/beam/disabler/smoothbore - e_cost = 1000 + e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE) /obj/item/ammo_casing/energy/disabler/smoothbore/prime projectile_type = /obj/projectile/beam/disabler/smoothbore/prime - e_cost = 500 + e_cost = LASER_SHOTS(2, STANDARD_CELL_CHARGE) diff --git a/code/modules/projectiles/boxes_magazines/_box_magazine.dm b/code/modules/projectiles/boxes_magazines/_box_magazine.dm index b018c06317e40..62f9185d2145c 100644 --- a/code/modules/projectiles/boxes_magazines/_box_magazine.dm +++ b/code/modules/projectiles/boxes_magazines/_box_magazine.dm @@ -3,7 +3,7 @@ name = "ammo box (null_reference_exception)" desc = "A box of ammo." icon = 'icons/obj/weapons/guns/ammo.dmi' - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BELT inhand_icon_state = "syringe_kit" worn_icon_state = "ammobox" @@ -19,6 +19,8 @@ var/list/stored_ammo = list() ///type that the magazine will be searching for, rejects if not a subtype of var/ammo_type = /obj/item/ammo_casing + /// wording used for individual units of ammo, e.g. cartridges (regular ammo), shells (shotgun shells) + var/casing_phrasing = "cartridge" ///maximum amount of ammo in the magazine var/max_ammo = 7 ///Controls how sprites are updated for the ammo box; see defines in combat.dm: AMMO_BOX_ONE_SPRITE; AMMO_BOX_PER_BULLET; AMMO_BOX_FULL_EMPTY @@ -66,7 +68,7 @@ var/list/readout = list() if(caliber && max_ammo) // Text references a 'magazine' as only magazines generally have the caliber variable initialized - readout += "Up to [span_warning("[max_ammo] [caliber] rounds")] can be found within this magazine. \ + readout += "Up to [span_warning("[max_ammo] [caliber] [casing_phrasing]s")] can be found within this magazine. \ \nAccidentally discharging any of these projectiles may void your insurance contract." var/obj/item/ammo_casing/mag_ammo = get_round(TRUE) @@ -158,7 +160,7 @@ if(num_loaded) if(!silent) - to_chat(user, span_notice("You load [num_loaded] shell\s into \the [src]!")) + to_chat(user, span_notice("You load [num_loaded > 1 ? "[num_loaded] [casing_phrasing]s" : "a [casing_phrasing]"] into \the [src]!")) playsound(src, 'sound/weapons/gun/general/mag_bullet_insert.ogg', 60, TRUE) update_appearance() @@ -173,13 +175,23 @@ if(!user.is_holding(src) || !user.put_in_hands(A)) //incase they're using TK A.bounce_away(FALSE, NONE) playsound(src, 'sound/weapons/gun/general/mag_bullet_insert.ogg', 60, TRUE) - to_chat(user, span_notice("You remove a round from [src]!")) + to_chat(user, span_notice("You remove a [casing_phrasing] from [src]!")) update_appearance() +/obj/item/ammo_box/examine(mob/user) + . = ..() + var/top_round = get_round() + if(!top_round) + return + // this is kind of awkward phrasing, but it's the top/ready ammo in the box + // intended for people who have like three mislabeled magazines + . += span_notice("The [top_round] is ready in [src].") + + /obj/item/ammo_box/update_desc(updates) . = ..() var/shells_left = LAZYLEN(stored_ammo) - desc = "[initial(desc)] There [(shells_left == 1) ? "is" : "are"] [shells_left] shell\s left!" + desc = "[initial(desc)] There [(shells_left == 1) ? "is" : "are"] [shells_left] [casing_phrasing]\s left!" /obj/item/ammo_box/update_icon_state() . = ..() diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm index e01a309bbc658..430ef11b7dd5d 100644 --- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm +++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm @@ -44,7 +44,7 @@ /obj/item/ammo_box/c38/trac name = "speed loader (.38 TRAC)" - desc = "Designed to quickly reload revolvers. TRAC bullets embed a tracking implant within the target's body. The implant's signal is incompatible with teleporters." + desc = "Designed to quickly reload revolvers. TRAC bullets embed a tracking implant within the target's body." ammo_type = /obj/item/ammo_casing/c38/trac ammo_band_color = "#7b6383" diff --git a/code/modules/projectiles/boxes_magazines/external/pistol.dm b/code/modules/projectiles/boxes_magazines/external/pistol.dm index 49ea0029f8f6d..8b0bc1da7e5b8 100644 --- a/code/modules/projectiles/boxes_magazines/external/pistol.dm +++ b/code/modules/projectiles/boxes_magazines/external/pistol.dm @@ -107,15 +107,11 @@ multiple_sprites = AMMO_BOX_PER_BULLET /obj/item/ammo_box/magazine/r10mm - name = "regal condor magazine (10mm)" + name = "regal condor magazine (10mm Reaper)" icon_state = "r10mm-8" base_icon_state = "r10mm" - ammo_type = /obj/item/ammo_casing/c10mm + ammo_type = /obj/item/ammo_casing/c10mm/reaper caliber = CALIBER_10MM max_ammo = 8 multiple_sprites = AMMO_BOX_PER_BULLET multiple_sprite_use_base = TRUE - -/obj/item/ammo_box/magazine/r10mm/empty - icon_state = "r10mm-0" - start_empty = TRUE diff --git a/code/modules/projectiles/boxes_magazines/external/shotgun.dm b/code/modules/projectiles/boxes_magazines/external/shotgun.dm index 398df79771a3e..dbf071f6aee6c 100644 --- a/code/modules/projectiles/boxes_magazines/external/shotgun.dm +++ b/code/modules/projectiles/boxes_magazines/external/shotgun.dm @@ -6,6 +6,7 @@ ammo_type = /obj/item/ammo_casing/shotgun/buckshot caliber = CALIBER_SHOTGUN max_ammo = 8 + casing_phrasing = "shell" /obj/item/ammo_box/magazine/m12g/update_icon_state() . = ..() diff --git a/code/modules/projectiles/boxes_magazines/internal/_internal.dm b/code/modules/projectiles/boxes_magazines/internal/_internal.dm index c14e66af82cf3..0579d19234b69 100644 --- a/code/modules/projectiles/boxes_magazines/internal/_internal.dm +++ b/code/modules/projectiles/boxes_magazines/internal/_internal.dm @@ -1,6 +1,6 @@ /obj/item/ammo_box/magazine/internal desc = "Oh god, this shouldn't be here" - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY item_flags = ABSTRACT //internals magazines are accessible, so replace spent ammo if full when trying to put a live one in diff --git a/code/modules/projectiles/boxes_magazines/internal/rifle.dm b/code/modules/projectiles/boxes_magazines/internal/rifle.dm index 83133186c9ddc..5fdc182ccff98 100644 --- a/code/modules/projectiles/boxes_magazines/internal/rifle.dm +++ b/code/modules/projectiles/boxes_magazines/internal/rifle.dm @@ -28,3 +28,24 @@ max_ammo = 1 caliber = CALIBER_HARPOON ammo_type = /obj/item/ammo_casing/harpoon + +/obj/item/ammo_box/magazine/internal/boltaction/rebarxbow/normal + name = "single round magazine" + max_ammo = 1 + caliber = CALIBER_REBAR + ammo_type = /obj/item/ammo_casing/rebar + +/obj/item/ammo_box/magazine/internal/boltaction/rebarxbow/force + name = "two round magazine" + max_ammo = 2 + caliber = CALIBER_REBAR_FORCED + ammo_type = /obj/item/ammo_casing/rebar + +/obj/item/ammo_box/magazine/internal/boltaction/rebarxbow/syndie + max_ammo = 3 + caliber = CALIBER_REBAR_SYNDIE + ammo_type = /obj/item/ammo_casing/rebar/syndie + +/obj/item/ammo_box/magazine/internal/boltaction/rebarxbow/syndie/normal + caliber = CALIBER_REBAR_SYNDIE_NORMAL + ammo_type = /obj/item/ammo_casing/rebar diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index ba45baac6d3af..322ee737c9b09 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -9,7 +9,7 @@ icon_state = "revolver" inhand_icon_state = "gun" worn_icon_state = "gun" - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY appearance_flags = TILE_BOUND|PIXEL_SCALE|LONG_GLIDE|KEEP_TOGETHER slot_flags = ITEM_SLOT_BELT custom_materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT) @@ -211,8 +211,8 @@ /obj/item/gun/emp_act(severity) . = ..() if(!(. & EMP_PROTECT_CONTENTS)) - for(var/obj/O in contents) - O.emp_act(severity) + for(var/obj/inside in contents) + inside.emp_act(severity) /obj/item/gun/attack_self_secondary(mob/user, modifiers) . = ..() @@ -270,7 +270,8 @@ /obj/item/gun/afterattack(atom/target, mob/living/user, flag, params) ..() - return fire_gun(target, user, flag, params) | AFTERATTACK_PROCESSED_ITEM + fire_gun(target, user, flag, params) + return AFTERATTACK_PROCESSED_ITEM /obj/item/gun/proc/fire_gun(atom/target, mob/living/user, flag, params) if(QDELETED(target)) @@ -402,6 +403,7 @@ update_appearance() return TRUE +///returns true if the gun successfully fires /obj/item/gun/proc/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) var/base_bonus_spread = 0 if(user) @@ -511,7 +513,7 @@ if(Adjacent(user) && !issilicon(user)) user.put_in_hands(bayonet) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS else if(pin?.pin_removable && user.is_holding(src)) user.visible_message(span_warning("[user] attempts to remove [pin] from [src] with [I]."), @@ -522,7 +524,7 @@ user.visible_message(span_notice("[pin] is pried out of [src] by [user], destroying the pin in the process."), span_warning("You pry [pin] out with [I], destroying the pin in the process."), null, 3) QDEL_NULL(pin) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/item/gun/welder_act(mob/living/user, obj/item/I) . = ..() @@ -571,6 +573,9 @@ knife_overlay.pixel_y = knife_y_offset . += knife_overlay +/obj/item/gun/animate_atom_living(mob/living/owner) + new /mob/living/simple_animal/hostile/mimic/copy/ranged(drop_location(), src, owner) + /obj/item/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params, bypass_timer) if(!ishuman(user) || !ishuman(target)) return diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm index 88d6a4e3161cf..69668cfaf40d0 100644 --- a/code/modules/projectiles/guns/ballistic.dm +++ b/code/modules/projectiles/guns/ballistic.dm @@ -427,10 +427,9 @@ return TRUE /obj/item/gun/ballistic/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) - if(magazine && chambered.loaded_projectile && can_misfire && misfire_probability > 0) - if(prob(misfire_probability)) - if(blow_up(user)) - to_chat(user, span_userdanger("[src] misfires!")) + if(target != user && chambered.loaded_projectile && can_misfire && prob(misfire_probability) && blow_up(user)) + to_chat(user, span_userdanger("[src] misfires!")) + return if (sawn_off) bonus_spread += SAWN_OFF_ACC_PENALTY @@ -484,11 +483,11 @@ if (empty_alarm && last_shot_succeeded) playsound(src, empty_alarm_sound, empty_alarm_volume, empty_alarm_vary) update_appearance() - if (last_shot_succeeded && bolt_type == BOLT_TYPE_LOCKING) + if (last_shot_succeeded && bolt_type == BOLT_TYPE_LOCKING && semi_auto) bolt_locked = TRUE update_appearance() -/obj/item/gun/ballistic/afterattack() +/obj/item/gun/ballistic/fire_gun(atom/target, mob/living/user, flag, params) prefire_empty_checks() . = ..() //The gun actually firing postfire_empty_checks(.) @@ -697,11 +696,7 @@ GLOBAL_LIST_INIT(gun_saw_types, typecacheof(list( ///used for sawing guns, causes the gun to fire without the input of the user /obj/item/gun/ballistic/proc/blow_up(mob/user) - . = FALSE - for(var/obj/item/ammo_casing/AC in magazine.stored_ammo) - if(AC.loaded_projectile) - process_fire(user, user, FALSE) - . = TRUE + return chambered && process_fire(user, user, FALSE) /obj/item/gun/ballistic/proc/instant_reload() SIGNAL_HANDLER diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index 3e286f6c58b18..70e2210a4e992 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -146,6 +146,17 @@ mag_display = TRUE rack_sound = 'sound/weapons/gun/pistol/slide_lock.ogg' +/** + * Weak uzi for syndicate chimps. It comes in a 4 TC kit. + * Roughly 9 damage per bullet every 0.2 seconds, equaling out to downing an opponent in a bit over a second, if they have no armor. + */ +/obj/item/gun/ballistic/automatic/mini_uzi/chimpgun + name = "\improper MONK-10" + desc = "Developed by Syndicate monkeys, for syndicate Monkeys. Despite the name, this weapon resembles an Uzi significantly more than a MAC-10. Uses 9mm rounds. There's a label on the other side of the gun that says \"Do what comes natural.\"" + projectile_damage_multiplier = 0.4 + projectile_wound_bonus = -25 + pin = /obj/item/firing_pin/monkey + /obj/item/gun/ballistic/automatic/m90 name = "\improper M-90gl Carbine" desc = "A three-round burst 5.56 toploading carbine, designated 'M-90gl'. Has an attached underbarrel grenade launcher." @@ -218,10 +229,25 @@ bolt_type = BOLT_TYPE_OPEN empty_indicator = TRUE show_bolt_icon = FALSE + /// Rate of fire, set on initialize only + var/rof = 0.1 SECONDS /obj/item/gun/ballistic/automatic/tommygun/Initialize(mapload) . = ..() - AddComponent(/datum/component/automatic_fire, 0.1 SECONDS) + AddComponent(/datum/component/automatic_fire, rof) + +/** + * Weak tommygun for syndicate chimps. It comes in a 4 TC kit. + * Roughly 9 damage per bullet every 0.2 seconds, equaling out to downing an opponent in a bit over a second, if they have no armor. + */ +/obj/item/gun/ballistic/automatic/tommygun/chimpgun + name = "\improper Typewriter" + desc = "It was the best of times, it was the BLURST of times!? You stupid monkeys!" + fire_delay = 2 + rof = 0.2 SECONDS + projectile_damage_multiplier = 0.4 + projectile_wound_bonus = -25 + pin = /obj/item/firing_pin/monkey /obj/item/gun/ballistic/automatic/ar name = "\improper NT-ARG 'Boarder'" diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm index 5d33b3fce5170..22e441cd56b17 100644 --- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm +++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm @@ -32,6 +32,7 @@ damage = 50 speed = 1 range = 25 + shrapnel_type = null embedding = list( embed_chance = 90, fall_chance = 2, @@ -42,7 +43,6 @@ jostle_pain_mult = 3, rip_time = 1 SECONDS ) - shrapnel_type = /obj/item/ammo_casing/arrow /// holy arrows /obj/item/ammo_casing/arrow/holy @@ -59,7 +59,6 @@ desc = "Here it comes, cultist scum!" icon_state = "holy_arrow_projectile" damage = 20 //still a lot but this is roundstart gear so far less - shrapnel_type =/obj/item/ammo_casing/arrow/holy embedding = list( embed_chance = 50, fall_chance = 2, @@ -73,7 +72,7 @@ /obj/projectile/bullet/arrow/holy/Initialize(mapload) . = ..() //50 damage to revenants - AddElement(/datum/element/bane, target_type = /mob/living/simple_animal/revenant, damage_multiplier = 0, added_damage = 30) + AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 30) /// special pyre sect arrow /// in the future, this needs a special sprite, but bows don't support non-hardcoded arrow sprites diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm b/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm index 7d8669c8618ae..07d7cc93ce16e 100644 --- a/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm +++ b/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm @@ -15,9 +15,7 @@ atom_storage.max_specific_storage = WEIGHT_CLASS_TINY atom_storage.max_slots = 40 atom_storage.max_total_storage = 100 - atom_storage.set_holdable(list( - /obj/item/ammo_casing/arrow, - )) + atom_storage.set_holdable(/obj/item/ammo_casing/arrow) /obj/item/storage/bag/quiver/PopulateContents() . = ..() diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_types.dm b/code/modules/projectiles/guns/ballistic/bows/bow_types.dm index 355ed3575a814..b9ac1af0cca12 100644 --- a/code/modules/projectiles/guns/ballistic/bows/bow_types.dm +++ b/code/modules/projectiles/guns/ballistic/bows/bow_types.dm @@ -30,7 +30,7 @@ on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \ effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune) \ ) - AddElement(/datum/element/bane, target_type = /mob/living/simple_animal/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) + AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE) /obj/item/gun/ballistic/bow/divine/proc/on_cult_rune_removed(obj/effect/target, mob/living/user) SIGNAL_HANDLER diff --git a/code/modules/projectiles/guns/ballistic/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm index 2963afbaa513c..0f5158317fb7f 100644 --- a/code/modules/projectiles/guns/ballistic/launchers.dm +++ b/code/modules/projectiles/guns/ballistic/launchers.dm @@ -91,14 +91,14 @@ if(can_shoot()) ADD_TRAIT(user, TRAIT_NO_TRANSFORM, REF(src)) playsound(src, 'sound/vehicles/rocketlaunch.ogg', 80, TRUE, 5) - animate(user, pixel_z = 300, time = 30, easing = LINEAR_EASING) + animate(user, pixel_z = 300, time = 30, flags = ANIMATION_RELATIVE, easing = LINEAR_EASING) sleep(7 SECONDS) - animate(user, pixel_z = 0, time = 5, easing = LINEAR_EASING) + animate(user, pixel_z = -300, time = 5, flags = ANIMATION_RELATIVE, easing = LINEAR_EASING) sleep(0.5 SECONDS) REMOVE_TRAIT(user, TRAIT_NO_TRANSFORM, REF(src)) process_fire(user, user, TRUE) if(!QDELETED(user)) //if they weren't gibbed by the explosion, take care of them for good. - user.gib() + user.gib(DROP_ALL_REMAINS) return MANUAL_SUICIDE else sleep(0.5 SECONDS) diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm index a0fe8c2e46132..270e7edd93078 100644 --- a/code/modules/projectiles/guns/ballistic/pistol.dm +++ b/code/modules/projectiles/guns/ballistic/pistol.dm @@ -55,6 +55,18 @@ lock_back_sound = 'sound/weapons/gun/pistol/slide_lock.ogg' bolt_drop_sound = 'sound/weapons/gun/pistol/slide_drop.ogg' +/** + * Weak 1911 for syndicate chimps. It comes in a 4 TC kit. + * 15 damage every.. second? 7 shots to kill. Not fast. + */ +/obj/item/gun/ballistic/automatic/pistol/m1911/chimpgun + name = "\improper CH1M911" + desc = "For the monkey mafioso on-the-go. Uses .45 rounds and has the distinct smell of bananas." + projectile_damage_multiplier = 0.5 + projectile_wound_bonus = -12 + pin = /obj/item/firing_pin/monkey + + /obj/item/gun/ballistic/automatic/pistol/m1911/no_mag spawnwithmagazine = FALSE @@ -95,9 +107,6 @@ actions_types = list(/datum/action/item_action/toggle_firemode) obj_flags = UNIQUE_RENAME // if you did the sidequest, you get the customization -/obj/item/gun/ballistic/automatic/pistol/deagle/regal/no_mag - spawnwithmagazine = FALSE - /obj/item/gun/ballistic/automatic/pistol/aps name = "\improper Stechkin APS machine pistol" desc = "An old Soviet machine pistol. It fires quickly, but kicks like a mule. Uses 9mm ammo. Has a threaded barrel for suppressors." diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index 6439b78a9bd98..373607e53dd63 100644 --- a/code/modules/projectiles/guns/ballistic/revolver.dm +++ b/code/modules/projectiles/guns/ballistic/revolver.dm @@ -60,9 +60,9 @@ set category = "Object" set desc = "Click to spin your revolver's chamber." - var/mob/M = usr + var/mob/user = usr - if(M.stat || !in_range(M,src)) + if(user.stat || !in_range(user, src)) return if (recent_spin > world.time) @@ -71,7 +71,8 @@ if(do_spin()) playsound(usr, SFX_REVOLVER_SPIN, 30, FALSE) - usr.visible_message(span_notice("[usr] spins [src]'s chamber."), span_notice("You spin [src]'s chamber.")) + visible_message(span_notice("[user] spins [src]'s chamber."), span_notice("You spin [src]'s chamber.")) + balloon_alert(user, "chamber spun") else verbs -= /obj/item/gun/ballistic/revolver/verb/spin @@ -139,6 +140,17 @@ desc = "A modernized 7 round revolver manufactured by Waffle Co. Uses .357 ammo." icon_state = "revolversyndie" +/obj/item/gun/ballistic/revolver/syndicate/nuclear + pin = /obj/item/firing_pin/implant/pindicate + +/obj/item/gun/ballistic/revolver/syndicate/cowboy + desc = "A classic revolver, refurbished for modern use. Uses .357 ammo." + //There's already a cowboy sprite in there! + icon_state = "lucky" + +/obj/item/gun/ballistic/revolver/syndicate/cowboy/nuclear + pin = /obj/item/firing_pin/implant/pindicate + /obj/item/gun/ballistic/revolver/mateba name = "\improper Unica 6 auto-revolver" desc = "A retro high-powered autorevolver typically used by officers of the New Russia military. Uses .357 ammo." @@ -201,10 +213,13 @@ /obj/item/gun/ballistic/revolver/russian/fire_gun(atom/target, mob/living/user, flag, params) . = ..(null, user, flag, params) + var/tk_controlled = FALSE if(flag) if(!(target in user.contents) && ismob(target)) if(user.combat_mode) // Flogging action return + else if (HAS_TRAIT_FROM_ONLY(src, TRAIT_TELEKINESIS_CONTROLLED, REF(user))) // if we're far away, you can still fire it at yourself if you have TK. + tk_controlled = TRUE if(isliving(user)) if(!can_trigger_gun(user)) @@ -212,8 +227,9 @@ if(target != user) playsound(src, dry_fire_sound, 30, TRUE) user.visible_message( - span_danger("[user.name] tries to fire \the [src] at the same time, but only succeeds at looking like an idiot."), \ - span_danger("\The [src]'s anti-combat mechanism prevents you from firing it at anyone but yourself!")) + span_danger("[user.name] tries to fire \the [src] at the same time, but only succeeds at looking like an idiot."), + span_danger("\The [src]'s anti-combat mechanism prevents you from firing it at anyone but yourself!"), + ) return if(ishuman(user)) @@ -229,23 +245,28 @@ var/is_target_face = zone == BODY_ZONE_HEAD || zone == BODY_ZONE_PRECISE_EYES || zone == BODY_ZONE_PRECISE_MOUTH var/loaded_rounds = get_ammo(FALSE, FALSE) // check before it is fired + if(HAS_TRAIT(user, TRAIT_CURSED)) // I cannot live, I cannot die, trapped in myself, body my holding cell. + to_chat(user, span_warning("What a horrible night... To have a curse!")) + return + if(loaded_rounds && is_target_face) add_memory_in_range(user, 7, /datum/memory/witnessed_russian_roulette, \ protagonist = user, \ antagonist = src, \ rounds_loaded = loaded_rounds, \ aimed_at = affecting.name, \ - result = (chambered ? "lost" : "won")) + result = (chambered ? "lost" : "won"), \ + ) if(chambered) - if(HAS_TRAIT(user, TRAIT_CURSED)) // I cannot live, I cannot die, trapped in myself, body my holding cell. - to_chat(user, span_warning("What a horrible night... To have a curse!")) - return var/obj/item/ammo_casing/AC = chambered if(AC.fire_casing(user, user, params, distro = 0, quiet = 0, zone_override = null, spread = 0, fired_from = src)) playsound(user, fire_sound, fire_sound_volume, vary_fire_sound) if(is_target_face) shoot_self(user, affecting) + else if(tk_controlled) // the consequence of you doing the telekinesis stuff + to_chat(user, span_userdanger("As your mind concentrates on the revolver, you realize that it's pointing towards your head a little too late!")) + shoot_self(user, BODY_ZONE_HEAD) else user.visible_message(span_danger("[user.name] cowardly fires [src] at [user.p_their()] [affecting.name]!"), span_userdanger("You cowardly fire [src] at your [affecting.name]!"), span_hear("You hear a gunshot!")) chambered = null @@ -275,7 +296,8 @@ user.visible_message(span_danger("[user.name]'s soul is captured by \the [src]!"), span_userdanger("You've lost the gamble! Your soul is forfeit!")) /obj/item/gun/ballistic/revolver/reverse //Fires directly at its user... unless the user is a clown, of course. - name = "\improper Syndicate Revolver" + name = /obj/item/gun/ballistic/revolver/syndicate::name + desc = /obj/item/gun/ballistic/revolver/syndicate::desc clumsy_check = FALSE icon_state = "revolversyndie" diff --git a/code/modules/projectiles/guns/ballistic/rifle.dm b/code/modules/projectiles/guns/ballistic/rifle.dm index 67c70352ff306..966dd2caf32a3 100644 --- a/code/modules/projectiles/guns/ballistic/rifle.dm +++ b/code/modules/projectiles/guns/ballistic/rifle.dm @@ -25,6 +25,7 @@ return drop_bolt(user) + /obj/item/gun/ballistic/rifle/can_shoot() if (bolt_locked) return FALSE @@ -169,6 +170,91 @@ if(.) name = "\improper Obrez Moderna" // wear it loud and proud +/obj/item/gun/ballistic/rifle/rebarxbow + name = "Heated Rebar Crossbow" + desc = "Made from an inducer, iron rods, and some wire, this crossbow fires sharpened iron rods, made from the plentiful iron rods found stationwide. \ + Only holds one rod in the magazine - you can craft the crossbow with a crowbar to try and force a second rod in, but risks a misfire, or worse..." + icon = 'icons/obj/weapons/guns/ballistic.dmi' + icon_state = "rebarxbow" + inhand_icon_state = "rebarxbow" + worn_icon_state = "rebarxbow" + rack_sound = 'sound/weapons/gun/sniper/rack.ogg' + must_hold_to_load = TRUE + mag_display = FALSE + empty_indicator = TRUE + bolt_type = BOLT_TYPE_LOCKING + semi_auto = FALSE + internal_magazine = TRUE + can_modify_ammo = FALSE + slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_SUITSTORE + bolt_wording = "bowstring" + magazine_wording = "rod" + cartridge_wording = "rod" + misfire_probability = 25 + weapon_weight = WEAPON_HEAVY + initial_caliber = CALIBER_REBAR + accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction/rebarxbow/normal + fire_sound = 'sound/items/syringeproj.ogg' + can_be_sawn_off = FALSE + tac_reloads = FALSE + var/draw_time = 3 SECONDS + SET_BASE_PIXEL(0, 0) + +/obj/item/gun/ballistic/rifle/rebarxbow/rack(mob/user = null) + if (bolt_locked) + drop_bolt(user) + return + balloon_alert(user, "bowstring loosened") + playsound(src, rack_sound, rack_sound_volume, rack_sound_vary) + handle_chamber(empty_chamber = FALSE, from_firing = FALSE, chamber_next_round = FALSE) + bolt_locked = TRUE + update_appearance() + +/obj/item/gun/ballistic/rifle/rebarxbow/drop_bolt(mob/user = null) + if(!do_after(user, draw_time, target = src)) + return + playsound(src, bolt_drop_sound, bolt_drop_sound_volume, FALSE) + balloon_alert(user, "bowstring drawn") + chamber_round() + bolt_locked = FALSE + update_appearance() + +/obj/item/gun/ballistic/rifle/rebarxbow/can_shoot() + if (bolt_locked) + return FALSE + return ..() + +/obj/item/gun/ballistic/rifle/rebarxbow/examine(mob/user) + . = ..() + . += "The crossbow is [bolt_locked ? "not ready" : "ready"] to fire." + +/obj/item/gun/ballistic/rifle/rebarxbow/forced + name = "Stressed Rebar Crossbow" + desc = "Some idiot decided that they would risk shooting themselves in the face if it meant they could have a bit more ammo in this crossbow. Hopefully, it was worth it." + // Feel free to add a recipe to allow you to change it back if you would like, I just wasn't sure if you could have two recipes for the same thing. + can_misfire = TRUE + misfire_probability = 25 + accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction/rebarxbow/force + +/obj/item/gun/ballistic/rifle/rebarxbow/syndie + name = "Syndicate Rebar Crossbow" + desc = "The syndicate liked the bootleg rebar crossbow NT engineers made, so they showed what it could be if properly developed. \ + Holds three shots without a chance of exploding, and features a built in scope. Normally uses special syndicate jagged iron bars, but can be wrenched to shoot inferior normal ones." + icon_state = "rebarxbowsyndie" + inhand_icon_state = "rebarxbowsyndie" + worn_icon_state = "rebarxbowsyndie" + w_class = WEIGHT_CLASS_NORMAL + can_modify_ammo = TRUE + initial_caliber = CALIBER_REBAR_SYNDIE + alternative_caliber = CALIBER_REBAR_SYNDIE_NORMAL + alternative_ammo_misfires = FALSE + draw_time = 1 + accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction/rebarxbow/syndie + +/obj/item/gun/ballistic/rifle/rebarxbow/syndie/Initialize(mapload) + . = ..() + AddComponent(/datum/component/scope, range_modifier = 2) //enough range to at least be useful for stealth + /obj/item/gun/ballistic/rifle/boltaction/pipegun name = "pipegun" desc = "An excellent weapon for flushing out tunnel rats and enemy assistants, but its rifling leaves much to be desired." @@ -188,6 +274,7 @@ alternative_fire_sound = 'sound/weapons/gun/shotgun/shot.ogg' can_modify_ammo = TRUE can_bayonet = TRUE + knife_x_offset = 25 knife_y_offset = 11 can_be_sawn_off = FALSE projectile_damage_multiplier = 0.75 diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm index ff04499c41be5..8a6f15e9a981d 100644 --- a/code/modules/projectiles/guns/ballistic/shotgun.dm +++ b/code/modules/projectiles/guns/ballistic/shotgun.dm @@ -14,7 +14,7 @@ load_sound = 'sound/weapons/gun/shotgun/insert_shell.ogg' w_class = WEIGHT_CLASS_BULKY force = 10 - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BACK accepted_magazine_type = /obj/item/ammo_box/magazine/internal/shot semi_auto = FALSE @@ -265,7 +265,7 @@ w_class = WEIGHT_CLASS_BULKY weapon_weight = WEAPON_MEDIUM force = 10 - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BACK accepted_magazine_type = /obj/item/ammo_box/magazine/internal/shot/dual sawn_desc = "Omar's coming!" @@ -317,7 +317,7 @@ accepted_magazine_type = /obj/item/ammo_box/magazine/internal/shot/bounty weapon_weight = WEAPON_MEDIUM semi_auto = TRUE - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY force = 18 //it has a hook on it sharpness = SHARP_POINTY //it does in fact, have a hook on it attack_verb_continuous = list("slashes", "hooks", "stabs") diff --git a/code/modules/projectiles/guns/energy/beam_rifle.dm b/code/modules/projectiles/guns/energy/beam_rifle.dm index 8869da14a59e3..1c5b025baea80 100644 --- a/code/modules/projectiles/guns/energy/beam_rifle.dm +++ b/code/modules/projectiles/guns/energy/beam_rifle.dm @@ -433,7 +433,7 @@ /obj/item/ammo_casing/energy/beam_rifle/hitscan projectile_type = /obj/projectile/beam/beam_rifle/hitscan select_name = "beam" - e_cost = 10000 + e_cost = LASER_SHOTS(5, 50000) // Beam rifle has a custom cell fire_sound = 'sound/weapons/beam_sniper.ogg' /obj/projectile/beam/beam_rifle @@ -520,8 +520,8 @@ if(!QDELETED(target)) handle_impact(target) -/obj/projectile/beam/beam_rifle/on_hit(atom/target, blocked = FALSE, piercing_hit = FALSE) - handle_hit(target, piercing_hit) +/obj/projectile/beam/beam_rifle/on_hit(atom/target, blocked = 0, pierce_hit) + handle_hit(target, pierce_hit) return ..() /obj/projectile/beam/beam_rifle/is_hostile_projectile() @@ -567,7 +567,8 @@ /obj/projectile/beam/beam_rifle/hitscan/aiming_beam/prehit_pierce(atom/target) return PROJECTILE_DELETE_WITHOUT_HITTING -/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/on_hit() +/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/on_hit(atom/target, blocked = 0, pierce_hit) + SHOULD_CALL_PARENT(FALSE) // This is some snowflake stuff so whatever qdel(src) return BULLET_ACT_BLOCK diff --git a/code/modules/projectiles/guns/energy/dueling.dm b/code/modules/projectiles/guns/energy/dueling.dm index a766ba5667fdf..f35769e663c71 100644 --- a/code/modules/projectiles/guns/energy/dueling.dm +++ b/code/modules/projectiles/guns/energy/dueling.dm @@ -292,7 +292,7 @@ //Casing /obj/item/ammo_casing/energy/duel - e_cost = 0 + e_cost = 0 // Can't use the macro projectile_type = /obj/projectile/energy/duel var/setting @@ -327,7 +327,7 @@ if(DUEL_SETTING_C) color = "blue" -/obj/projectile/energy/duel/on_hit(atom/target, blocked) +/obj/projectile/energy/duel/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/turf/T = get_turf(target) var/obj/effect/temp_visual/dueling_chaff/C = locate() in T @@ -370,7 +370,7 @@ . = ..() atom_storage.max_specific_storage = WEIGHT_CLASS_SMALL atom_storage.max_slots = 2 - atom_storage.set_holdable(list(/obj/item/gun/energy/dueling)) + atom_storage.set_holdable(/obj/item/gun/energy/dueling) /obj/item/storage/lockbox/dueling/update_icon_state() if(atom_storage?.locked) diff --git a/code/modules/projectiles/guns/energy/energy_gun.dm b/code/modules/projectiles/guns/energy/energy_gun.dm index 34c447cfb3cee..fec816b1e765f 100644 --- a/code/modules/projectiles/guns/energy/energy_gun.dm +++ b/code/modules/projectiles/guns/energy/energy_gun.dm @@ -9,6 +9,18 @@ ammo_x_offset = 3 dual_wield_spread = 60 +/obj/item/gun/energy/e_gun/Initialize(mapload) + . = ..() + // Only actual eguns can be converted + if(type != /obj/item/gun/energy/e_gun) + return + var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/advancedegun, /datum/crafting_recipe/tempgun, /datum/crafting_recipe/beam_rifle) + + AddComponent( + /datum/component/slapcrafting,\ + slapcraft_recipes = slapcraft_recipe_list,\ + ) + /obj/item/gun/energy/e_gun/add_seclight_point() AddComponent(/datum/component/seclite_attachable, \ light_overlay_icon = 'icons/obj/weapons/guns/flashlights.dmi', \ diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index 2b908664e30fb..7ff033d772028 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -16,6 +16,19 @@ var/list/modkits = list() gun_flags = NOT_A_REAL_GUN + +/obj/item/gun/energy/recharge/kinetic_accelerator/Initialize(mapload) + . = ..() + // Only actual KAs can be converted + if(type != /obj/item/gun/energy/recharge/kinetic_accelerator) + return + var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/ebow) + + AddComponent( + /datum/component/slapcrafting,\ + slapcraft_recipes = slapcraft_recipe_list,\ + ) + /obj/item/gun/energy/recharge/kinetic_accelerator/apply_fantasy_bonuses(bonus) . = ..() max_mod_capacity = modify_fantasy_variable("max_mod_capacity", max_mod_capacity, bonus * 10) @@ -140,7 +153,7 @@ icon_state = "kineticgun_b" holds_charge = TRUE unique_frequency = TRUE - max_mod_capacity = 80 + max_mod_capacity = 90 /obj/item/gun/energy/recharge/kinetic_accelerator/minebot trigger_guard = TRIGGER_GUARD_ALLOW_ALL @@ -152,8 +165,8 @@ /obj/item/ammo_casing/energy/kinetic projectile_type = /obj/projectile/kinetic select_name = "kinetic" - e_cost = 500 - fire_sound = 'sound/weapons/kenetic_accel.ogg' // fine spelling there chap + e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE * 0.5) + fire_sound = 'sound/weapons/kinetic_accel.ogg' /obj/item/ammo_casing/energy/kinetic/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") ..() @@ -196,7 +209,7 @@ strike_thing() ..() -/obj/projectile/kinetic/on_hit(atom/target) +/obj/projectile/kinetic/on_hit(atom/target, blocked = 0, pierce_hit) strike_thing(target) . = ..() @@ -350,7 +363,7 @@ name = "minebot cooldown decrease" desc = "Decreases the cooldown of a kinetic accelerator. Only rated for minebot use." icon_state = "door_electronics" - icon = 'icons/obj/assemblies/module.dmi' + icon = 'icons/obj/devices/circuitry_n_data.dmi' denied_type = /obj/item/borg/upgrade/modkit/cooldown/minebot modifier = 10 cost = 0 @@ -603,4 +616,3 @@ var/new_color = input(user,"","Choose Color",bolt_color) as color|null bolt_color = new_color || bolt_color - diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index ad5a5ce927954..561525fe6fe9d 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -9,6 +9,18 @@ ammo_x_offset = 1 shaded_charge = 1 +/obj/item/gun/energy/laser/Initialize(mapload) + . = ..() + // Only actual lasguns can be converted + if(type != /obj/item/gun/energy/laser) + return + var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/xraylaser, /datum/crafting_recipe/hellgun, /datum/crafting_recipe/ioncarbine) + + AddComponent( + /datum/component/slapcrafting,\ + slapcraft_recipes = slapcraft_recipe_list,\ + ) + /obj/item/gun/energy/laser/practice name = "practice laser gun" desc = "A modified version of the basic laser gun, this one fires less concentrated energy bolts designed for target practice." @@ -23,6 +35,24 @@ desc = "An older model of the basic lasergun, no longer used by Nanotrasen's private security or military forces. Nevertheless, it is still quite deadly and easy to maintain, making it a favorite amongst pirates and other outlaws." ammo_x_offset = 3 +/obj/item/gun/energy/laser/carbine + name = "laser carbine" + desc = "A modified laser gun which can shoot far faster, but each shot is far less damaging." + icon_state = "laser_carbine" + ammo_type = list(/obj/item/ammo_casing/energy/lasergun/carbine) + +/obj/item/gun/energy/laser/carbine/Initialize(mapload) + . = ..() + AddComponent(/datum/component/automatic_fire, 0.15 SECONDS, allow_akimbo = FALSE) + +/obj/item/gun/energy/laser/carbine/practice + name = "practice laser carbine" + desc = "A modified version of the laser carbine, this one fires even less concentrated energy bolts designed for target practice." + ammo_type = list(/obj/item/ammo_casing/energy/lasergun/carbine/practice) + clumsy_check = FALSE + item_flags = NONE + gun_flags = NOT_A_REAL_GUN + /obj/item/gun/energy/laser/retro/old name ="laser gun" icon_state = "retro" @@ -47,7 +77,7 @@ selfcharge = 1 resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF flags_1 = PREVENT_CONTENTS_EXPLOSION_1 - ammo_type = list(/obj/item/ammo_casing/energy/laser/hellfire/antique) + ammo_type = list(/obj/item/ammo_casing/energy/laser/hellfire) /obj/item/gun/energy/laser/captain/scattershot name = "scatter shot laser rifle" @@ -92,7 +122,7 @@ worn_icon_state = null w_class = WEIGHT_CLASS_BULKY force = 10 - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BACK ammo_type = list(/obj/item/ammo_casing/energy/laser/accelerator) pin = null diff --git a/code/modules/projectiles/guns/energy/pulse.dm b/code/modules/projectiles/guns/energy/pulse.dm index 4db8e626bda2e..f441937e60a44 100644 --- a/code/modules/projectiles/guns/energy/pulse.dm +++ b/code/modules/projectiles/guns/energy/pulse.dm @@ -7,7 +7,7 @@ w_class = WEIGHT_CLASS_BULKY force = 10 modifystate = TRUE - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BACK ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse, /obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser) cell_type = /obj/item/stock_parts/cell/pulse @@ -26,7 +26,11 @@ message_admins("A pulse rifle prize has been created at [ADMIN_VERBOSEJMP(T)]") log_game("A pulse rifle prize has been created at [AREACOORD(T)]") - notify_ghosts("Someone won a pulse rifle as a prize!", source = src, action = NOTIFY_ORBIT, header = "Pulse rifle prize") + notify_ghosts( + "Someone won a pulse rifle as a prize!", + source = src, + header = "Pulse rifle prize", + ) /obj/item/gun/energy/pulse/loyalpin pin = /obj/item/firing_pin/implant/mindshield diff --git a/code/modules/projectiles/guns/energy/recharge.dm b/code/modules/projectiles/guns/energy/recharge.dm index eed2747875548..26fbdef6e139a 100644 --- a/code/modules/projectiles/guns/energy/recharge.dm +++ b/code/modules/projectiles/guns/energy/recharge.dm @@ -12,7 +12,7 @@ /// How much time we need to recharge var/recharge_time = 1.6 SECONDS /// Sound we use when recharged - var/recharge_sound = 'sound/weapons/kenetic_reload.ogg' + var/recharge_sound = 'sound/weapons/kinetic_reload.ogg' /// An ID for our recharging timer. var/recharge_timerid /// Do we recharge slower with more of our type? diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index c7d99e732bbdb..fc44a536fef60 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -6,7 +6,7 @@ worn_icon_state = null shaded_charge = TRUE w_class = WEIGHT_CLASS_HUGE - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BACK ammo_type = list(/obj/item/ammo_casing/energy/ion) @@ -33,23 +33,6 @@ // We use the same overlay as the parent, so we can just let the component inherit the correct offsets here AddComponent(/datum/component/seclite_attachable, overlay_x = 18, overlay_y = 11) -/obj/item/gun/energy/decloner - name = "biological demolecularisor" - desc = "A gun that discharges high amounts of controlled radiation to slowly break a target into component elements." - icon_state = "decloner" - ammo_type = list(/obj/item/ammo_casing/energy/declone) - ammo_x_offset = 1 - -/obj/item/gun/energy/decloner/update_overlays() - . = ..() - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - if(!QDELETED(cell) && (cell.charge > shot.e_cost)) - . += "decloner_spin" - -/obj/item/gun/energy/decloner/unrestricted - pin = /obj/item/firing_pin - ammo_type = list(/obj/item/ammo_casing/energy/declone/weak) - /obj/item/gun/energy/floragun name = "floral somatoray" desc = "A tool that discharges controlled radiation which induces mutation in plant cells." @@ -100,7 +83,7 @@ icon_state = "plasmacutter" inhand_icon_state = "plasmacutter" ammo_type = list(/obj/item/ammo_casing/energy/plasma) - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY attack_verb_continuous = list("attacks", "slashes", "cuts", "slices") attack_verb_simple = list("attack", "slash", "cut", "slice") force = 12 diff --git a/code/modules/projectiles/guns/energy/stun.dm b/code/modules/projectiles/guns/energy/stun.dm index 635570fbb15ce..e099176ddd018 100644 --- a/code/modules/projectiles/guns/energy/stun.dm +++ b/code/modules/projectiles/guns/energy/stun.dm @@ -40,6 +40,26 @@ overlay_x = 15, \ overlay_y = 10) +/obj/item/gun/energy/disabler/smg + name = "disabler smg" + desc = "An automatic disabler variant, as opposed to the conventional model, boasts a higher ammunition capacity at the cost of slightly reduced beam effectiveness." + icon_state = "disabler_smg" + ammo_type = list(/obj/item/ammo_casing/energy/disabler/smg) + shaded_charge = 1 + +/obj/item/gun/energy/disabler/smg/Initialize(mapload) + . = ..() + AddComponent(/datum/component/automatic_fire, 0.15 SECONDS, allow_akimbo = FALSE) + +/obj/item/gun/energy/disabler/add_seclight_point() + AddComponent(\ + /datum/component/seclite_attachable, \ + light_overlay_icon = 'icons/obj/weapons/guns/flashlights.dmi', \ + light_overlay = "flight", \ + overlay_x = 15, \ + overlay_y = 13, \ + ) + /obj/item/gun/energy/disabler/cyborg name = "cyborg disabler" desc = "An integrated disabler that draws from a cyborg's power cell. This weapon contains a limiter to prevent the cyborg's power cell from overheating." diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm index 94c1e7502a8e2..0c1c27c9c0759 100644 --- a/code/modules/projectiles/guns/magic.dm +++ b/code/modules/projectiles/guns/magic.dm @@ -7,7 +7,7 @@ lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' //not really a gun and some toys use these inhands righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' fire_sound = 'sound/weapons/emitter.ogg' - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY w_class = WEIGHT_CLASS_HUGE ///what kind of magic is this var/school = SCHOOL_EVOCATION diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm index 568c7a0d657c3..c4e719e781724 100644 --- a/code/modules/projectiles/guns/magic/staff.dm +++ b/code/modules/projectiles/guns/magic/staff.dm @@ -199,7 +199,6 @@ /obj/projectile/bullet/honker, /obj/projectile/bullet/mime, /obj/projectile/curse_hand, - /obj/projectile/energy/declone, /obj/projectile/energy/electrode, /obj/projectile/energy/net, /obj/projectile/energy/nuclear_particle, @@ -263,8 +262,8 @@ ) /obj/item/gun/magic/staff/spellblade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) - if(attack_type == PROJECTILE_ATTACK) - final_block_chance = 0 + if(attack_type == PROJECTILE_ATTACK || attack_type == LEAP_ATTACK) + final_block_chance = 0 //Don't bring a sword to a gunfight, and also you aren't going to really block someone full body tackling you with a sword return ..() /obj/item/gun/magic/staff/locker diff --git a/code/modules/projectiles/guns/special/hand_of_midas.dm b/code/modules/projectiles/guns/special/hand_of_midas.dm new file mode 100644 index 0000000000000..5c9cb1fd9781b --- /dev/null +++ b/code/modules/projectiles/guns/special/hand_of_midas.dm @@ -0,0 +1,140 @@ +// Hand of Midas + +/obj/item/gun/magic/midas_hand + name = "The Hand of Midas" + desc = "An ancient Egyptian matchlock pistol imbued with the powers of the Greek King Midas. Don't question the cultural or religious implications of this." + ammo_type = /obj/item/ammo_casing/magic/midas_round + icon_state = "midas_hand" + inhand_icon_state = "gun" + worn_icon_state = "gun" + lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' + fire_sound = 'sound/weapons/gun/rifle/shot.ogg' + pinless = TRUE + max_charges = 1 + can_charge = FALSE + item_flags = NEEDS_PERMIT + w_class = WEIGHT_CLASS_BULKY // Should fit on a belt. + force = 3 + trigger_guard = TRIGGER_GUARD_NORMAL + antimagic_flags = NONE + can_hold_up = FALSE + + /// The length of the Midas Blight debuff, dependant on the amount of gold reagent we've sucked up. + var/gold_timer = 3 SECONDS + /// The range that we can suck gold out of people's bodies + var/gold_suck_range = 2 + +/obj/item/gun/magic/midas_hand/examine(mob/user) + . = ..() + var/gold_time_converted = gold_time_convert() + . += span_notice("Your next shot will inflict [gold_time_converted] second[gold_time_converted == 1 ? "" : "s"] of Midas Blight.") + . += span_notice("Right-Click on enemies to drain gold from their bloodstreams to reload [src].") + . += span_notice("[src] can be reloaded using gold coins in a pinch.") + +/obj/item/gun/magic/midas_hand/shoot_with_empty_chamber(mob/living/user) + . = ..() + balloon_alert(user, "not enough gold") + +// Siphon gold from a victim, recharging our gun & removing their Midas Blight debuff in the process. +/obj/item/gun/magic/midas_hand/afterattack_secondary(mob/living/victim, mob/living/user, proximity_flag, click_parameters) + if(!isliving(victim) || !IN_GIVEN_RANGE(user, victim, gold_suck_range)) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + if(victim == user) + balloon_alert(user, "can't siphon from self") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + if(!victim.reagents) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + var/gold_amount = victim.reagents.get_reagent_amount(/datum/reagent/gold, type_check = REAGENT_SUB_TYPE) + if(!gold_amount) + balloon_alert(user, "no gold in bloodstream") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + var/gold_beam = user.Beam(victim, icon_state="drain_gold") + if(!do_after(user = user, delay = 1 SECONDS, target = victim, timed_action_flags = (IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE), extra_checks = CALLBACK(src, PROC_REF(check_gold_range), user, victim))) + qdel(gold_beam) + balloon_alert(user, "link broken") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + handle_gold_charges(user, gold_amount) + victim.reagents.remove_reagent(/datum/reagent/gold, gold_amount, include_subtypes = TRUE) + victim.remove_status_effect(/datum/status_effect/midas_blight) + qdel(gold_beam) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +// If we botch a shot, we have to start over again by inserting gold coins into the gun. Can only be done if it has no charges or gold. +/obj/item/gun/magic/midas_hand/attackby(obj/item/I, mob/living/user, params) + . = ..() + if(charges || gold_timer) + balloon_alert(user, "already loaded") + return + if(istype(I, /obj/item/coin/gold)) + handle_gold_charges(user, 1.5 SECONDS) + qdel(I) + +/// Handles recharging & inserting gold amount +/obj/item/gun/magic/midas_hand/proc/handle_gold_charges(user, gold_amount) + gold_timer += gold_amount + var/gold_time_converted = gold_time_convert() + balloon_alert(user, "[gold_time_converted] second[gold_time_converted == 1 ? "" : "s"]") + if(!charges) + instant_recharge() + +/// Converts our gold_timer to time in seconds, for various ballons/examines +/obj/item/gun/magic/midas_hand/proc/gold_time_convert() + return min(30 SECONDS, round(gold_timer, 0.2)) / 10 + +/// Checks our range to the person we're sucking gold out of. Double the initial range, so you need to get in close to start. +/obj/item/gun/magic/midas_hand/proc/check_gold_range(mob/living/user, mob/living/victim) + return IN_GIVEN_RANGE(user, victim, gold_suck_range*2) + +/obj/item/ammo_casing/magic/midas_round + projectile_type = /obj/projectile/magic/midas_round + + +/obj/projectile/magic/midas_round + name = "gold pellet" + desc = "A typical flintlock ball, save for the fact it's made of cursed Egyptian gold." + damage_type = BRUTE + damage = 10 + stamina = 20 + armour_penetration = 50 + hitsound = 'sound/effects/coin2.ogg' + icon_state = "pellet" + color = "#FFD700" + /// The gold charge in this pellet + var/gold_charge = 0 + + +/obj/projectile/magic/midas_round/fire(setAngle) + /// Transfer the gold energy to our bullet + var/obj/item/gun/magic/midas_hand/my_gun = fired_from + gold_charge = my_gun.gold_timer + my_gun.gold_timer = 0 + ..() + +// Gives human targets Midas Blight. +/obj/projectile/magic/midas_round/on_hit(atom/target, blocked = 0, pierce_hit) + . = ..() + if(ishuman(target)) + var/mob/living/carbon/human/my_guy = target + if(isskeleton(my_guy)) // No cheap farming + return + my_guy.apply_status_effect(/datum/status_effect/midas_blight, min(30 SECONDS, round(gold_charge, 0.2))) // 100u gives 10 seconds + return + +/obj/item/gun/magic/midas_hand/suicide_act(mob/living/user) + if(!ishuman(user)) + return + + var/mob/living/carbon/human/victim = user + victim.visible_message(span_suicide("[victim] holds the barrel of [src] to [victim.p_their()] head, lighting the fuse. It looks like [user.p_theyre()] trying to commit suicide!")) + if(!do_after(victim, 1.5 SECONDS)) + return + playsound(src, 'sound/weapons/gun/rifle/shot.ogg', 75, TRUE) + to_chat(victim, span_danger("You don't even have the time to register the gunshot by the time your body has completely converted into a golden statue.")) + var/newcolors = list(rgb(206, 164, 50), rgb(146, 146, 139), rgb(28,28,28), rgb(0,0,0)) + victim.petrify(statue_timer = INFINITY, save_brain = FALSE, colorlist = newcolors) + playsound(victim, 'sound/effects/coin2.ogg', 75, TRUE) + charges = 0 + gold_timer = 0 + return OXYLOSS diff --git a/code/modules/projectiles/guns/special/meat_hook.dm b/code/modules/projectiles/guns/special/meat_hook.dm index 4add1bb77d4cb..0fcf6b2c8e6b9 100644 --- a/code/modules/projectiles/guns/special/meat_hook.dm +++ b/code/modules/projectiles/guns/special/meat_hook.dm @@ -1,5 +1,7 @@ -//Meat Hook +#define TRAIT_HOOKED "hooked" +#define IMMOBILIZATION_TIMER (0.25 SECONDS) //! How long we immobilize the firer after firing - we do cancel the immobilization early if nothing is hit. +/// Meat Hook /obj/item/gun/magic/hook name = "meat hook" desc = "Mid or feed." @@ -22,14 +24,28 @@ /obj/item/gun/magic/hook/can_trigger_gun(mob/living/user, akimbo_usage) // This isn't really a gun, so it shouldn't be checking for TRAIT_NOGUNS, a firing pin (pinless), or a trigger guard (guardless) if(akimbo_usage) return FALSE //this would be kinda weird while shooting someone down. + if(HAS_TRAIT(user, TRAIT_IMMOBILIZED)) + return FALSE return TRUE +/obj/item/gun/magic/hook/suicide_act(mob/living/user) + var/obj/item/bodypart/head/removable = user.get_bodypart(BODY_ZONE_HEAD) + if(isnull(removable)) + user.visible_message(span_suicide("[user] stuffs the chain of the [src] down the hole where their head should be! It looks like [user.p_theyre()] trying to commit suicide!")) + return OXYLOSS + + playsound(get_turf(src), fire_sound, 50, TRUE, -1) + user.visible_message(span_suicide("[user] is using the [src] on their [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!")) + playsound(get_turf(src), 'sound/weapons/bladeslice.ogg', 70) + removable.dismember(silent = FALSE) + return BRUTELOSS + /obj/item/ammo_casing/magic/hook name = "hook" desc = "A hook." projectile_type = /obj/projectile/hook caliber = CALIBER_HOOK - firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect /obj/projectile/hook name = "hook" @@ -41,36 +57,153 @@ armour_penetration = 60 damage_type = BRUTE hitsound = 'sound/effects/splat.ogg' - var/chain - var/knockdown_time = (0.5 SECONDS) + /// The chain we send out while we are in motion, referred to as "initial" to not get confused with the chain we use to reel the victim in. + var/datum/beam/initial_chain /obj/projectile/hook/fire(setAngle) if(firer) - chain = firer.Beam(src, icon_state = "chain", emissive = FALSE) - ..() - //TODO: root the firer until the chain returns + initial_chain = firer.Beam(src, icon_state = "chain", emissive = FALSE) + ADD_TRAIT(firer, TRAIT_IMMOBILIZED, REF(src)) + addtimer(TRAIT_CALLBACK_REMOVE(firer, TRAIT_IMMOBILIZED, REF(src)), IMMOBILIZATION_TIMER) // safety if we miss, if we get a hit we stay immobilized + return ..() -/obj/projectile/hook/on_hit(atom/target) +/obj/projectile/hook/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() - if(ismovable(target)) - var/atom/movable/A = target - if(A.anchored) - return - A.visible_message(span_danger("[A] is snagged by [firer]'s hook!")) - //Should really be a movement loop, but I don't want to support moving 5 tiles a tick - //It just looks bad - new /datum/forced_movement(A, get_turf(firer), 5, TRUE) - if (isliving(target)) - var/mob/living/fresh_meat = target - fresh_meat.Knockdown(knockdown_time) - return - //TODO: keep the chain beamed to A - //TODO: needs a callback to delete the chain - -/obj/projectile/hook/Destroy() - qdel(chain) + if(!ismovable(target)) + return + + var/atom/movable/victim = target + if(victim.anchored || HAS_TRAIT_FROM(victim, TRAIT_HOOKED, REF(firer))) + return + + victim.visible_message(span_danger("[victim] is snagged by [firer]'s hook!")) + + var/datum/hook_and_move/puller = new + puller.begin_pulling(firer, victim, get_turf(firer)) + REMOVE_TRAIT(firer, TRAIT_IMMOBILIZED, REF(src)) + +/obj/projectile/hook/Destroy(force) + QDEL_NULL(initial_chain) + return ..() + +/// Lightweight datum that just handles moving a target for the hook. +/// For the love of God, do not use this outside this file. +/datum/hook_and_move + /// Weakref to the victim we are dragging + var/datum/weakref/victim_ref = null + /// Weakref of the destination that the victim is heading towards. + var/datum/weakref/destination_ref = null + /// Weakref to the firer of the hook + var/datum/weakref/firer_ref = null + /// String to the REF() of the dude that fired us so we can ensure we always cleanup our traits + var/firer_ref_string = null + + /// The last time our movement fired. + var/last_movement = 0 + /// The chain beam we currently own. + var/datum/beam/return_chain = null + + /// How many steps we force the victim to take per tick + var/steps_per_tick = 5 + /// How long we knockdown the victim for. + var/knockdown_time = (0.5 SECONDS) + + /// List of traits that prevent the user from moving. More restrictive than attempting to fire the hook by design. + var/static/list/prevent_movement_traits = list( + TRAIT_IMMOBILIZED, + TRAIT_UI_BLOCKED, + ) + +/datum/hook_and_move/Destroy(force) + STOP_PROCESSING(SSfastprocess, src) + QDEL_NULL(return_chain) return ..() +/// Uses fastprocessing to move our victim to the destination at a rather fast speed. +/datum/hook_and_move/proc/begin_pulling(atom/movable/firer, atom/movable/victim, atom/destination) + return_chain = firer.Beam(victim, icon_state = "chain", emissive = FALSE) + + firer_ref_string = REF(firer) + ADD_TRAIT(victim, TRAIT_HOOKED, firer_ref_string) + firer.add_traits(prevent_movement_traits, REF(src)) + if(isliving(victim)) + var/mob/living/fresh_meat = victim + fresh_meat.Knockdown(knockdown_time) + + destination_ref = WEAKREF(destination) + victim_ref = WEAKREF(victim) + firer_ref = WEAKREF(firer) + + START_PROCESSING(SSfastprocess, src) + +/// Cancels processing and removes the trait from the victim. +/datum/hook_and_move/proc/end_movement() + var/atom/movable/firer = firer_ref?.resolve() + if(!QDELETED(firer)) + firer.remove_traits(prevent_movement_traits, REF(src)) + + var/atom/movable/victim = victim_ref?.resolve() + if(!QDELETED(victim)) + REMOVE_TRAIT(victim, TRAIT_HOOKED, firer_ref_string) + + qdel(src) + +/datum/hook_and_move/process(seconds_per_tick) + var/atom/movable/victim = victim_ref?.resolve() + var/atom/destination = destination_ref?.resolve() + if(QDELETED(victim) || QDELETED(destination)) + end_movement() + return + + var/steps_to_take = round(steps_per_tick * (world.time - last_movement)) + if(steps_to_take <= 0) + return + + var/movement_result = attempt_movement(victim, destination) + if(!movement_result || (victim.loc == destination.loc)) // either we failed our movement or our mission is complete + end_movement() + +/// Attempts to move the victim towards the destination. Returns TRUE if we do a successful movement, FALSE otherwise. +/// second_attempt is a boolean to prevent infinite recursion. +/// If this whole series of events wasn't reliant on SSfastprocess firing as fast as it does, it would have been more useful to make this a move loop datum. But, we need the speed. +/datum/hook_and_move/proc/attempt_movement(atom/movable/subject, atom/target, second_attempt = FALSE) + var/actually_moved = FALSE + if(!second_attempt) + actually_moved = step_towards(subject, target) + + if(actually_moved) + return TRUE + + // alright now the code fucking sucks + var/subject_x = subject.x + var/subject_y = subject.y + var/target_x = target.x + var/target_y = target.y + + //If we're going x, step x + if((target_x > subject_x) && step(subject, EAST)) + actually_moved = TRUE + else if((target_x < subject_x) && step(subject, WEST)) + actually_moved = TRUE + + if(actually_moved) + return TRUE + + //If the x step failed, go y + if((target_y > subject_y) && step(subject, NORTH)) + actually_moved = TRUE + else if((target_y < subject_y) && step(subject, SOUTH)) + actually_moved = TRUE + + if(actually_moved) + return TRUE + + // if we fail twice, abort. otherwise queue up the second attempt. + if(second_attempt) + return FALSE + + return attempt_movement(subject, target, second_attempt = TRUE) + //just a nerfed version of the real thing for the bounty hunters. /obj/item/gun/magic/hook/bounty name = "hook" @@ -82,3 +215,12 @@ /obj/projectile/hook/bounty damage = 0 stamina = 40 + +/// Debug hook for fun (AKA admin abuse). doesn't do any more damage or anything just lets you wildfire it. +/obj/item/gun/magic/hook/debug + name = "super meat hook" + max_charges = 100 + recharge_rate = 1 + +#undef TRAIT_HOOKED +#undef IMMOBILIZATION_TIMER diff --git a/code/modules/projectiles/guns/special/medbeam.dm b/code/modules/projectiles/guns/special/medbeam.dm index d3ee77ef3b3a3..267470f17013c 100644 --- a/code/modules/projectiles/guns/special/medbeam.dm +++ b/code/modules/projectiles/guns/special/medbeam.dm @@ -144,10 +144,13 @@ /obj/item/gun/medbeam/proc/on_beam_tick(mob/living/target) if(target.health != target.maxHealth) new /obj/effect/temp_visual/heal(get_turf(target), COLOR_HEALING_CYAN) - target.adjustBruteLoss(-4) - target.adjustFireLoss(-4) - target.adjustToxLoss(-1, forced = TRUE) - target.adjustOxyLoss(-1, forced = TRUE) + var/need_mob_update + need_mob_update = target.adjustBruteLoss(-4, updating_health = FALSE, forced = TRUE) + need_mob_update += target.adjustFireLoss(-4, updating_health = FALSE, forced = TRUE) + need_mob_update += target.adjustToxLoss(-1, updating_health = FALSE, forced = TRUE) + need_mob_update += target.adjustOxyLoss(-1, updating_health = FALSE, forced = TRUE) + if(need_mob_update) + target.updatehealth() return /obj/item/gun/medbeam/proc/on_beam_release(mob/living/target) diff --git a/code/modules/projectiles/guns/special/syringe_gun.dm b/code/modules/projectiles/guns/special/syringe_gun.dm index dbfa5c73538ec..71b1a82a38029 100644 --- a/code/modules/projectiles/guns/special/syringe_gun.dm +++ b/code/modules/projectiles/guns/special/syringe_gun.dm @@ -200,6 +200,6 @@ /obj/item/gun/syringe/blowgun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) visible_message(span_danger("[user] shoots the blowgun!")) - user.adjustStaminaLoss(20) + user.adjustStaminaLoss(20, updating_stamina = FALSE) user.adjustOxyLoss(20) return ..() diff --git a/code/modules/projectiles/pins.dm b/code/modules/projectiles/pins.dm index 6c2914b495dbb..c4b6f6fb4ce7e 100644 --- a/code/modules/projectiles/pins.dm +++ b/code/modules/projectiles/pins.dm @@ -1,11 +1,11 @@ /obj/item/firing_pin name = "electronic firing pin" desc = "A small authentication device, to be inserted into a firearm receiver to allow operation. NT safety regulations require all new designs to incorporate one." - icon = 'icons/obj/device.dmi' + icon = 'icons/obj/devices/gunmod.dmi' icon_state = "firing_pin" inhand_icon_state = "pen" worn_icon_state = "pen" - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY w_class = WEIGHT_CLASS_TINY attack_verb_continuous = list("pokes") attack_verb_simple = list("poke") @@ -30,19 +30,19 @@ if(proximity_flag) if(isgun(target)) . |= AFTERATTACK_PROCESSED_ITEM - var/obj/item/gun/targetted_gun = target - var/obj/item/firing_pin/old_pin = targetted_gun.pin + var/obj/item/gun/targeted_gun = target + var/obj/item/firing_pin/old_pin = targeted_gun.pin if(old_pin?.pin_removable && (force_replace || old_pin.pin_hot_swappable)) if(Adjacent(user)) user.put_in_hands(old_pin) else - old_pin.forceMove(targetted_gun.drop_location()) + old_pin.forceMove(targeted_gun.drop_location()) old_pin.gun_remove(user) - if(!targetted_gun.pin) + if(!targeted_gun.pin) if(!user.temporarilyRemoveItemFromInventory(src)) return . - if(gun_insert(user, targetted_gun)) + if(gun_insert(user, targeted_gun)) if(old_pin) balloon_alert(user, "swapped firing pin") else @@ -373,6 +373,17 @@ suit_requirement = /obj/item/clothing/suit/bluetag tagcolor = "blue" +/obj/item/firing_pin/monkey + name = "monkeylock firing pin" + desc = "This firing pin prevents non-monkeys from firing a gun." + fail_message = "not a monkey!" + +/obj/item/firing_pin/monkey/pin_auth(mob/living/user) + if(!is_simian(user)) + playsound(get_turf(src), "sound/creatures/monkey/monkey_screech_[rand(1,7)].ogg", 75, TRUE) + return FALSE + return TRUE + /obj/item/firing_pin/Destroy() if(gun) gun.pin = null diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 6bce420a02bcb..682baac7927e2 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -14,7 +14,6 @@ generic_canpass = FALSE blocks_emissive = EMISSIVE_BLOCK_GENERIC layer = MOB_LAYER - plane = GAME_PLANE_FOV_HIDDEN //The sound this plays on impact. var/hitsound = 'sound/weapons/pierce.ogg' var/hitsound_wall = "" @@ -146,7 +145,7 @@ var/homing_offset_y = 0 var/damage = 10 - var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here + var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY are the only things that should be in here ///Defines what armor to use when it hits things. Must be set to bullet, laser, energy, or bomb var/armor_flag = BULLET @@ -159,13 +158,15 @@ var/decayedRange //stores original range var/reflect_range_decrease = 5 //amount of original range that falls off when reflecting, so it doesn't go forever var/reflectable = NONE // Can it be reflected or not? + // Status effects applied on hit - var/stun = 0 - var/knockdown = 0 - var/paralyze = 0 - var/immobilize = 0 - var/unconscious = 0 - var/eyeblur = 0 + var/stun = 0 SECONDS + var/knockdown = 0 SECONDS + var/paralyze = 0 SECONDS + var/immobilize = 0 SECONDS + var/unconscious = 0 SECONDS + /// Seconds of blurry eyes applied on projectile hit + var/eyeblur = 0 SECONDS /// Drowsiness applied on projectile hit var/drowsy = 0 SECONDS /// Jittering applied on projectile hit @@ -192,6 +193,10 @@ var/hit_prone_targets = FALSE ///For what kind of brute wounds we're rolling for, if we're doing such a thing. Lasers obviously don't care since they do burn instead. var/sharpness = NONE + ///How much we want to drop damage per tile as it travels through the air + var/damage_falloff_tile + ///How much we want to drop stamina damage (defined by the stamina variable) per tile as it travels through the air + var/stamina_falloff_tile ///How much we want to drop both wound_bonus and bare_wound_bonus (to a minimum of 0 for the latter) per tile, for falloff purposes var/wound_falloff_tile ///How much we want to drop the embed_chance value, if we can embed, per tile, for falloff purposes @@ -219,10 +224,18 @@ bare_wound_bonus = max(0, bare_wound_bonus + wound_falloff_tile) if(embedding) embedding["embed_chance"] += embed_falloff_tile + if(damage_falloff_tile && damage >= 0) + damage += damage_falloff_tile + if(stamina_falloff_tile && stamina >= 0) + stamina += stamina_falloff_tile + SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE) if(range <= 0 && loc) on_range() + if(damage_falloff_tile && damage <= 0 || stamina_falloff_tile && stamina <= 0) + on_range() + /obj/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE_OUT) qdel(src) @@ -241,12 +254,22 @@ /** * Called when the projectile hits something * - * @params - * target - thing hit - * blocked - percentage of hit blocked - * pierce_hit - are we piercing through or regular hitting + * By default parent call will always return [BULLET_ACT_HIT] (unless qdeleted) + * so it is save to assume a successful hit in children (though not necessarily successfully damaged - it could've been blocked) + * + * Arguments + * * target - thing hit + * * blocked - percentage of hit blocked (0 to 100) + * * pierce_hit - boolean, are we piercing through or regular hitting + * + * Returns + * * Returns [BULLET_ACT_HIT] if we hit something. Default return value. + * * Returns [BULLET_ACT_BLOCK] if we were hit but sustained no effects (blocked it). Note, Being "blocked" =/= "blocked is 100". + * * Returns [BULLET_ACT_FORCE_PIERCE] to have the projectile keep going instead of "hitting", as if we were not hit at all. */ -/obj/projectile/proc/on_hit(atom/target, blocked = FALSE, pierce_hit) +/obj/projectile/proc/on_hit(atom/target, blocked = 0, pierce_hit) + SHOULD_CALL_PARENT(TRUE) + // i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself. // maybe we care what the projectile thinks! See about combining these via args some time when it's not 5AM var/hit_limb_zone @@ -254,11 +277,11 @@ var/mob/living/L = target hit_limb_zone = L.check_hit_limb_zone_name(def_zone) if(fired_from) - SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle, hit_limb_zone) - SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb_zone) + SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle, hit_limb_zone, blocked) + SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb_zone, blocked) if(QDELETED(src)) // in case one of the above signals deleted the projectile for whatever reason - return + return BULLET_ACT_BLOCK var/turf/target_turf = get_turf(target) var/hitx @@ -293,8 +316,8 @@ if(blocked != 100) // not completely blocked var/obj/item/bodypart/hit_bodypart = living_target.get_bodypart(hit_limb_zone) - if (damage) - if (living_target.blood_volume && damage_type == BRUTE && (isnull(hit_bodypart) || hit_bodypart.can_bleed())) + if (damage && damage_type == BRUTE) + if (living_target.blood_volume && (isnull(hit_bodypart) || hit_bodypart.can_bleed())) var/splatter_dir = dir if(starting) splatter_dir = get_dir(starting, target_turf) @@ -304,7 +327,7 @@ new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_turf, splatter_dir) if(prob(33)) living_target.add_splatter_floor(target_turf) - else if (!isnull(hit_bodypart) && (hit_bodypart.biological_state & (BIO_METAL|BIO_WIRED))) + else if (hit_bodypart?.biological_state & (BIO_METAL|BIO_WIRED)) var/random_damage_mult = RANDOM_DECIMAL(0.85, 1.15) // SOMETIMES you can get more or less sparks var/damage_dealt = ((damage / (1 - (blocked / 100))) * random_damage_mult) @@ -331,7 +354,6 @@ span_userdanger("You're hit by \a [src][organ_hit_text]!"), null, COMBAT_MESSAGE_RANGE) if(living_target.is_blind()) to_chat(living_target, span_userdanger("You feel something hit you[organ_hit_text]!")) - living_target.on_hit(src) var/reagent_note if(reagents?.reagent_list) @@ -770,9 +792,7 @@ set_angle(get_angle(src, target)) original_angle = Angle if(!nondirectional_sprite) - var/matrix/matrix = new - matrix.Turn(Angle) - transform = matrix + transform = transform.Turn(Angle) trajectory_ignore_forcemove = TRUE forceMove(starting) trajectory_ignore_forcemove = FALSE @@ -789,11 +809,9 @@ pixel_move(pixel_speed_multiplier, FALSE) //move it now! /obj/projectile/proc/set_angle(new_angle) //wrapper for overrides. - Angle = new_angle if(!nondirectional_sprite) - var/matrix/matrix = new - matrix.Turn(Angle) - transform = matrix + transform = transform.TurnTo(Angle, new_angle) + Angle = new_angle if(trajectory) trajectory.set_angle(new_angle) if(fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store)) @@ -805,11 +823,9 @@ /// Same as set_angle, but the reflection continues from the center of the object that reflects it instead of the side /obj/projectile/proc/set_angle_centered(new_angle) - Angle = new_angle if(!nondirectional_sprite) - var/matrix/matrix = new - matrix.Turn(Angle) - transform = matrix + transform = transform.TurnTo(Angle, new_angle) + Angle = new_angle if(trajectory) trajectory.set_angle(new_angle) @@ -887,10 +903,6 @@ if(!loc || !trajectory) return last_projectile_move = world.time - if(!nondirectional_sprite && !hitscanning) - var/matrix/matrix = new - matrix.Turn(Angle) - transform = matrix if(homing) process_homing() var/forcemoved = FALSE @@ -1139,17 +1151,40 @@ /obj/projectile/proc/can_embed_into(atom/hit) return embedding && shrapnel_type && iscarbon(hit) && !HAS_TRAIT(hit, TRAIT_PIERCEIMMUNE) +/// Reflects the projectile off of something +/obj/projectile/proc/reflect(atom/hit_atom) + if(!starting) + return + var/new_x = starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/new_y = starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/turf/current_tile = get_turf(hit_atom) + + // redirect the projectile + original = locate(new_x, new_y, z) + starting = current_tile + firer = hit_atom + yo = new_y - current_tile.y + xo = new_x - current_tile.x + var/new_angle_s = Angle + rand(120,240) + while(new_angle_s > 180) // Translate to regular projectile degrees + new_angle_s -= 360 + set_angle(new_angle_s) + #undef MOVES_HITSCAN #undef MUZZLE_EFFECT_PIXEL_INCREMENT /// Fire a projectile from this atom at another atom -/atom/proc/fire_projectile(projectile_type, atom/target, sound, firer) +/atom/proc/fire_projectile(projectile_type, atom/target, sound, firer, list/ignore_targets = list()) if (!isnull(sound)) playsound(src, sound, vol = 100, vary = TRUE) var/turf/startloc = get_turf(src) var/obj/projectile/bullet = new projectile_type(startloc) bullet.starting = startloc + var/list/ignore = list() + for (var/atom/thing as anything in ignore_targets) + ignore[thing] = TRUE + bullet.impacted += ignore bullet.firer = firer || src bullet.fired_from = src bullet.yo = target.y - startloc.y @@ -1157,3 +1192,4 @@ bullet.original = target bullet.preparePixelProjectile(target, src) bullet.fire() + return bullet diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index 454684e1bee32..e902a0e142984 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -7,9 +7,9 @@ hitsound = 'sound/weapons/sear.ogg' hitsound_wall = 'sound/weapons/effects/searwall.ogg' armor_flag = LASER - eyeblur = 2 + eyeblur = 4 SECONDS impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser - light_system = MOVABLE_LIGHT + light_system = OVERLAY_LIGHT light_range = 1 light_power = 1 light_color = COLOR_SOFT_RED @@ -28,6 +28,16 @@ damage = 25 bare_wound_bonus = 40 +/obj/projectile/beam/laser/carbine + icon_state = "carbine_laser" + impact_effect_type = /obj/effect/temp_visual/impact_effect/yellow_laser + damage = 10 + +/obj/projectile/beam/laser/carbine/practice + name = "practice laser" + impact_effect_type = /obj/effect/temp_visual/impact_effect/yellow_laser + damage = 0 + //overclocked laser, does a bit more damage but has much higher wound power (-0 vs -20) /obj/projectile/beam/laser/hellfire name = "hellfire laser" @@ -47,7 +57,7 @@ muzzle_type = /obj/effect/projectile/muzzle/heavy_laser impact_type = /obj/effect/projectile/impact/heavy_laser -/obj/projectile/beam/laser/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/laser/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/M = target @@ -83,7 +93,20 @@ /obj/projectile/beam/scatter name = "laser pellet" icon_state = "scatterlaser" - damage = 5 + damage = 7.5 + wound_bonus = 5 + bare_wound_bonus = 5 + damage_falloff_tile = -0.45 + wound_falloff_tile = -2.5 + +/obj/projectile/beam/scatter/pathetic + name = "extremely weak laser pellet" + damage = 1 + wound_bonus = 0 + damage_falloff_tile = -0.1 + color = "#dbc11d" + hitsound = 'sound/items/bikehorn.ogg' //honk + hitsound_wall = 'sound/items/bikehorn.ogg' /obj/projectile/beam/xray name = "\improper X-ray beam" @@ -106,7 +129,6 @@ damage_type = STAMINA armor_flag = ENERGY hitsound = 'sound/weapons/sear_disabler.ogg' - eyeblur = 0 impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser light_color = LIGHT_COLOR_BLUE tracer_type = /obj/effect/projectile/tracer/disabler @@ -136,7 +158,7 @@ impact_type = /obj/effect/projectile/impact/pulse wound_bonus = 10 -/obj/projectile/beam/pulse/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/pulse/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if (!QDELETED(target) && (isturf(target) || isstructure(target))) if(isobj(target)) @@ -153,11 +175,11 @@ projectile_piercing = ALL var/pierce_hits = 2 -/obj/projectile/beam/pulse/heavy/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/pulse/heavy/on_hit(atom/target, blocked = 0, pierce_hit) if(pierce_hits <= 0) projectile_piercing = NONE pierce_hits -= 1 - ..() + return ..() /obj/projectile/beam/emitter name = "emitter beam" @@ -197,7 +219,7 @@ impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser light_color = LIGHT_COLOR_BLUE -/obj/projectile/beam/lasertag/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/lasertag/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(ishuman(target)) var/mob/living/carbon/human/M = target @@ -239,7 +261,7 @@ light_color = LIGHT_COLOR_BLUE var/shrink_time = 90 -/obj/projectile/beam/shrink/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/shrink/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isopenturf(target) || isindestructiblewall(target))//shrunk floors wouldnt do anything except look weird, i-walls shouldn't be bypassable return diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm index 6808f7c48fd5e..85c2dce80c51a 100644 --- a/code/modules/projectiles/projectile/bullets/_incendiary.dm +++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm @@ -5,7 +5,7 @@ /// If TRUE, leaves a trail of hotspots as it flies, very very chaotic var/leaves_fire_trail = TRUE -/obj/projectile/bullet/incendiary/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/incendiary/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/M = target @@ -41,7 +41,7 @@ wound_falloff_tile = -4 fire_stacks = 3 -/obj/projectile/bullet/incendiary/fire/on_hit(atom/target, blocked) +/obj/projectile/bullet/incendiary/fire/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/turf/location = get_turf(target) if(isopenturf(location)) diff --git a/code/modules/projectiles/projectile/bullets/cannonball.dm b/code/modules/projectiles/projectile/bullets/cannonball.dm index 11ffa603cbf74..2f57a3dcc99bd 100644 --- a/code/modules/projectiles/projectile/bullets/cannonball.dm +++ b/code/modules/projectiles/projectile/bullets/cannonball.dm @@ -22,7 +22,7 @@ /// How much our object damage decreases on hit, similar to normal damage. var/object_damage_decrease_on_hit = 0 -/obj/projectile/bullet/cannonball/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/cannonball/on_hit(atom/target, blocked = 0, pierce_hit) damage -= damage_decrease_on_hit if(object_damage_decreases) object_damage -= min(damage, object_damage_decrease_on_hit) @@ -46,7 +46,7 @@ projectile_piercing = NONE damage = 40 //set to 30 before first mob impact, but they're gonna be gibbed by the explosion -/obj/projectile/bullet/cannonball/explosive/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/cannonball/explosive/on_hit(atom/target, blocked = 0, pierce_hit) explosion(target, devastation_range = 2, heavy_impact_range = 3, light_impact_range = 4, explosion_cause = src) . = ..() @@ -56,7 +56,7 @@ projectile_piercing = NONE damage = 15 //very low -/obj/projectile/bullet/cannonball/emp/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/cannonball/emp/on_hit(atom/target, blocked = 0, pierce_hit) empulse(src, 4, 10) . = ..() @@ -65,7 +65,7 @@ icon_state = "biggest_one" damage = 70 //low pierce -/obj/projectile/bullet/cannonball/biggest_one/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/cannonball/biggest_one/on_hit(atom/target, blocked = 0, pierce_hit) if(projectile_piercing == NONE) explosion(target, devastation_range = GLOB.MAX_EX_DEVESTATION_RANGE, heavy_impact_range = GLOB.MAX_EX_HEAVY_RANGE, light_impact_range = GLOB.MAX_EX_LIGHT_RANGE, flash_range = GLOB.MAX_EX_FLASH_RANGE, explosion_cause = src) . = ..() diff --git a/code/modules/projectiles/projectile/bullets/dart_syringe.dm b/code/modules/projectiles/projectile/bullets/dart_syringe.dm index 1f853127858a4..405552a8909c2 100644 --- a/code/modules/projectiles/projectile/bullets/dart_syringe.dm +++ b/code/modules/projectiles/projectile/bullets/dart_syringe.dm @@ -10,7 +10,7 @@ . = ..() create_reagents(50, NO_REACT) -/obj/projectile/bullet/dart/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/dart/on_hit(atom/target, blocked = 0, pierce_hit) if(iscarbon(target)) var/mob/living/carbon/M = target if(blocked != 100) // not completely blocked diff --git a/code/modules/projectiles/projectile/bullets/dnainjector.dm b/code/modules/projectiles/projectile/bullets/dnainjector.dm index 139f20c339ca2..fdb051e7f8006 100644 --- a/code/modules/projectiles/projectile/bullets/dnainjector.dm +++ b/code/modules/projectiles/projectile/bullets/dnainjector.dm @@ -7,7 +7,7 @@ embedding = null shrapnel_type = null -/obj/projectile/bullet/dnainjector/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/dnainjector/on_hit(atom/target, blocked = 0, pierce_hit) if(iscarbon(target)) var/mob/living/carbon/M = target if(blocked != 100) diff --git a/code/modules/projectiles/projectile/bullets/foam_dart.dm b/code/modules/projectiles/projectile/bullets/foam_dart.dm index 6d4cffd4524d6..3f086166e6a88 100644 --- a/code/modules/projectiles/projectile/bullets/foam_dart.dm +++ b/code/modules/projectiles/projectile/bullets/foam_dart.dm @@ -5,27 +5,24 @@ damage_type = OXY icon = 'icons/obj/weapons/guns/toy.dmi' icon_state = "foamdart_proj" - base_icon_state = "foamdart_proj" + base_icon_state = "foamdart" range = 10 + shrapnel_type = null embedding = null var/modified = FALSE var/obj/item/pen/pen = null /obj/projectile/bullet/foam_dart/Initialize(mapload) . = ..() - RegisterSignal(src, COMSIG_PROJECTILE_ON_SPAWN_DROP, PROC_REF(handle_drop)) + RegisterSignals(src, list(COMSIG_PROJECTILE_ON_SPAWN_DROP, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED), PROC_REF(handle_drop)) /obj/projectile/bullet/foam_dart/proc/handle_drop(datum/source, obj/item/ammo_casing/foam_dart/newcasing) SIGNAL_HANDLER newcasing.modified = modified + newcasing.update_appearance() var/obj/projectile/bullet/foam_dart/newdart = newcasing.loaded_projectile newdart.modified = modified newdart.damage_type = damage_type - if(pen) - newdart.pen = pen - pen.forceMove(newdart) - pen = null - newdart.damage = 5 newdart.update_appearance() /obj/projectile/bullet/foam_dart/Destroy() @@ -35,5 +32,5 @@ /obj/projectile/bullet/foam_dart/riot name = "riot foam dart" icon_state = "foamdart_riot_proj" - base_icon_state = "foamdart_riot_proj" + base_icon_state = "foamdart_riot" stamina = 25 diff --git a/code/modules/projectiles/projectile/bullets/grenade.dm b/code/modules/projectiles/projectile/bullets/grenade.dm index b1d7278228f7a..a99a7b57ff3ec 100644 --- a/code/modules/projectiles/projectile/bullets/grenade.dm +++ b/code/modules/projectiles/projectile/bullets/grenade.dm @@ -8,7 +8,7 @@ embedding = null shrapnel_type = null -/obj/projectile/bullet/a40mm/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/a40mm/on_hit(atom/target, blocked = 0, pierce_hit) ..() explosion(target, devastation_range = -1, light_impact_range = 2, flame_range = 3, flash_range = 1, adminlog = FALSE, explosion_cause = src) return BULLET_ACT_HIT diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm index ece260f92fbde..8fccc510ff8fd 100644 --- a/code/modules/projectiles/projectile/bullets/pistol.dm +++ b/code/modules/projectiles/projectile/bullets/pistol.dm @@ -42,3 +42,22 @@ name = "10mm incendiary bullet" damage = 20 fire_stacks = 3 + +/obj/projectile/bullet/c10mm/reaper + name = "10mm reaper pellet" + damage = 50 + armour_penetration = 40 + tracer_type = /obj/effect/projectile/tracer/sniper + impact_type = /obj/effect/projectile/impact/sniper + muzzle_type = /obj/effect/projectile/muzzle/sniper + hitscan = TRUE + impact_effect_type = null + hitscan_light_intensity = 3 + hitscan_light_range = 0.75 + hitscan_light_color_override = LIGHT_COLOR_DIM_YELLOW + muzzle_flash_intensity = 5 + muzzle_flash_range = 1 + muzzle_flash_color_override = LIGHT_COLOR_DIM_YELLOW + impact_light_intensity = 5 + impact_light_range = 1 + impact_light_color_override = LIGHT_COLOR_DIM_YELLOW diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm index b5411c937be8f..417f61534bcb7 100644 --- a/code/modules/projectiles/projectile/bullets/revolver.dm +++ b/code/modules/projectiles/projectile/bullets/revolver.dm @@ -65,7 +65,7 @@ damage = 10 ricochets_max = 0 -/obj/projectile/bullet/c38/trac/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/c38/trac/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/mob/living/carbon/M = target if(!istype(M)) @@ -83,7 +83,7 @@ damage = 20 ricochets_max = 0 -/obj/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/M = target @@ -96,7 +96,7 @@ var/temperature = 100 ricochets_max = 0 -/obj/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/mob/living/M = target diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm index 4cb7bd543b481..d76b2de9d6ace 100644 --- a/code/modules/projectiles/projectile/bullets/rifle.dm +++ b/code/modules/projectiles/projectile/bullets/rifle.dm @@ -46,4 +46,32 @@ bare_wound_bonus = 80 embedding = list(embed_chance=100, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10) wound_falloff_tile = -5 - shrapnel_type = /obj/item/ammo_casing/harpoon + shrapnel_type = null + +// Rebar (Rebar Crossbow) +/obj/projectile/bullet/rebar + name = "rebar" + icon_state = "rebar" + damage = 30 + speed = 0.4 + dismemberment = 1 //because a 1 in 100 chance to just blow someones arm off is enough to be cool but also not enough to be reliable + armour_penetration = 10 + wound_bonus = -20 + bare_wound_bonus = 20 + embedding = list(embed_chance=60, fall_chance=2, jostle_chance=2, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=3, jostle_pain_mult=2, rip_time=10) + embed_falloff_tile = -5 + wound_falloff_tile = -2 + shrapnel_type = /obj/item/stack/rods + +/obj/projectile/bullet/rebarsyndie + name = "rebar" + icon_state = "rebar" + damage = 35 + speed = 0.4 + dismemberment = 2 //It's a budget sniper rifle. + armour_penetration = 20 //A bit better versus armor. Gets past anti laser armor or a vest, but doesnt wound proc on sec armor. + wound_bonus = 10 + bare_wound_bonus = 10 + embedding = list(embed_chance=80, fall_chance=1, jostle_chance=3, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=3, jostle_pain_mult=2, rip_time=14) + embed_falloff_tile = -3 + shrapnel_type = /obj/item/stack/rods diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm index 639939e150fb7..9bdd5a145ead2 100644 --- a/code/modules/projectiles/projectile/bullets/shotgun.dm +++ b/code/modules/projectiles/projectile/bullets/shotgun.dm @@ -63,24 +63,14 @@ damage = 15 paralyze = 10 -/obj/projectile/bullet/shotgun_frag12/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/shotgun_frag12/on_hit(atom/target, blocked = 0, pierce_hit) ..() explosion(target, devastation_range = -1, light_impact_range = 1, explosion_cause = src) return BULLET_ACT_HIT /obj/projectile/bullet/pellet icon_state = "pellet" - var/tile_dropoff = 0.45 - var/tile_dropoff_s = 0.25 - -/obj/projectile/bullet/pellet/Range() - ..() - if(damage > 0) - damage -= tile_dropoff - if(stamina > 0) - stamina -= tile_dropoff_s - if(damage < 0 && stamina < 0) - qdel(src) + damage_falloff_tile = -0.45 /obj/projectile/bullet/pellet/shotgun_buckshot name = "buckshot pellet" @@ -96,6 +86,7 @@ sharpness = NONE embedding = null speed = 1.2 + stamina_falloff_tile = -0.25 ricochets_max = 4 ricochet_chance = 120 ricochet_decay_chance = 0.9 diff --git a/code/modules/projectiles/projectile/bullets/sniper.dm b/code/modules/projectiles/projectile/bullets/sniper.dm index bc4f69eb946a9..4425b20eeedc4 100644 --- a/code/modules/projectiles/projectile/bullets/sniper.dm +++ b/code/modules/projectiles/projectile/bullets/sniper.dm @@ -14,7 +14,7 @@ ///Determines how much additional damage the round does to mechs. var/mecha_damage = 10 -/obj/projectile/bullet/p50/on_hit(atom/target, blocked = 0) +/obj/projectile/bullet/p50/on_hit(atom/target, blocked = 0, pierce_hit) if(isobj(target) && (blocked != 100)) var/obj/thing_to_break = target var/damage_to_deal = object_damage @@ -41,7 +41,7 @@ mecha_damage = 100 var/emp_radius = 2 -/obj/projectile/bullet/p50/disruptor/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/p50/disruptor/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if((blocked != 100) && isliving(target)) var/mob/living/living_guy = target @@ -60,7 +60,7 @@ object_damage = 30 mecha_damage = 0 -/obj/projectile/bullet/p50/incendiary/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/p50/incendiary/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/poor_burning_dork = target diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm index c424f2cd6bed4..f595c3e116510 100644 --- a/code/modules/projectiles/projectile/bullets/special.dm +++ b/code/modules/projectiles/projectile/bullets/special.dm @@ -16,7 +16,7 @@ . = ..() SpinAnimation() -/obj/projectile/bullet/honker/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/honker/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/mob/M = target if(istype(M)) @@ -30,7 +30,7 @@ /obj/projectile/bullet/mime damage = 40 -/obj/projectile/bullet/mime/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/mime/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(!isliving(target)) return diff --git a/code/modules/projectiles/projectile/energy/decloner.dm b/code/modules/projectiles/projectile/energy/decloner.dm deleted file mode 100644 index 6e5f6a5f1e776..0000000000000 --- a/code/modules/projectiles/projectile/energy/decloner.dm +++ /dev/null @@ -1,19 +0,0 @@ -/obj/projectile/energy/declone - name = "radiation beam" - icon_state = "declone" - damage = 20 - damage_type = CLONE - impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser - - /// The chance to be irradiated on hit - var/radiation_chance = 30 - -/obj/projectile/energy/declone/on_hit(atom/target, blocked, pierce_hit) - if (ishuman(target) && prob(radiation_chance)) - radiation_pulse(target, max_range = 0, threshold = RAD_FULL_INSULATION) - - ..() - -/obj/projectile/energy/declone/weak - damage = 9 - radiation_chance = 10 diff --git a/code/modules/projectiles/projectile/energy/ebow.dm b/code/modules/projectiles/projectile/energy/ebow.dm index 73faaffc22f53..e1da23495f4a0 100644 --- a/code/modules/projectiles/projectile/energy/ebow.dm +++ b/code/modules/projectiles/projectile/energy/ebow.dm @@ -4,7 +4,7 @@ damage = 15 damage_type = TOX stamina = 60 - eyeblur = 10 + eyeblur = 20 SECONDS knockdown = 10 slur = 10 SECONDS diff --git a/code/modules/projectiles/projectile/energy/net_snare.dm b/code/modules/projectiles/projectile/energy/net_snare.dm index 440ab9438e2dd..925096f63514d 100644 --- a/code/modules/projectiles/projectile/energy/net_snare.dm +++ b/code/modules/projectiles/projectile/energy/net_snare.dm @@ -10,7 +10,7 @@ . = ..() SpinAnimation() -/obj/projectile/energy/net/on_hit(atom/target, blocked = FALSE) +/obj/projectile/energy/net/on_hit(atom/target, blocked = 0, pierce_hit) if(isliving(target)) var/turf/Tloc = get_turf(target) if(!locate(/obj/effect/nettingportal) in Tloc) @@ -64,12 +64,12 @@ hitsound = 'sound/weapons/taserhit.ogg' range = 4 -/obj/projectile/energy/trap/on_hit(atom/target, blocked = FALSE) +/obj/projectile/energy/trap/on_hit(atom/target, blocked = 0, pierce_hit) if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - drop a trap new/obj/item/restraints/legcuffs/beartrap/energy(get_turf(loc)) else if(iscarbon(target)) var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy(get_turf(target)) - B.spring_trap(null, target) + B.spring_trap(target) . = ..() /obj/projectile/energy/trap/on_range() @@ -82,13 +82,13 @@ hitsound = 'sound/weapons/taserhit.ogg' range = 10 -/obj/projectile/energy/trap/cyborg/on_hit(atom/target, blocked = FALSE) +/obj/projectile/energy/trap/cyborg/on_hit(atom/target, blocked = 0, pierce_hit) if(!ismob(target) || blocked >= 100) do_sparks(1, TRUE, src) qdel(src) if(iscarbon(target)) var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(target)) - B.spring_trap(null, target) + B.spring_trap(target) QDEL_IN(src, 10) . = ..() diff --git a/code/modules/projectiles/projectile/energy/radiation.dm b/code/modules/projectiles/projectile/energy/radiation.dm new file mode 100644 index 0000000000000..c9c649228105f --- /dev/null +++ b/code/modules/projectiles/projectile/energy/radiation.dm @@ -0,0 +1,19 @@ +/obj/projectile/energy/radiation + name = "radiation beam" + icon_state = "declone" + damage = 20 + damage_type = TOX + impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser + + /// The chance to be irradiated on hit + var/radiation_chance = 30 + +/obj/projectile/energy/radiation/on_hit(atom/target, blocked, pierce_hit) + if (ishuman(target) && prob(radiation_chance)) + radiation_pulse(target, max_range = 0, threshold = RAD_FULL_INSULATION) + + ..() + +/obj/projectile/energy/radiation/weak + damage = 9 + radiation_chance = 10 diff --git a/code/modules/projectiles/projectile/energy/stun.dm b/code/modules/projectiles/projectile/energy/stun.dm index 03cf5f85d84df..7f36bf437ed65 100644 --- a/code/modules/projectiles/projectile/energy/stun.dm +++ b/code/modules/projectiles/projectile/energy/stun.dm @@ -11,7 +11,7 @@ muzzle_type = /obj/effect/projectile/muzzle/stun impact_type = /obj/effect/projectile/impact/stun -/obj/projectile/energy/electrode/on_hit(atom/target, blocked = FALSE) +/obj/projectile/energy/electrode/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks! do_sparks(1, TRUE, src) diff --git a/code/modules/projectiles/projectile/energy/tesla.dm b/code/modules/projectiles/projectile/energy/tesla.dm index 9afb816088ff3..9dfe043a01565 100644 --- a/code/modules/projectiles/projectile/energy/tesla.dm +++ b/code/modules/projectiles/projectile/energy/tesla.dm @@ -5,24 +5,24 @@ damage = 10 //A worse lasergun var/zap_flags = ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_LOW_POWER_GEN var/zap_range = 3 - var/power = 10000 + var/power = 1e4 -/obj/projectile/energy/tesla/on_hit(atom/target) +/obj/projectile/energy/tesla/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() - tesla_zap(src, zap_range, power, zap_flags) + tesla_zap(source = src, zap_range = zap_range, power = power, cutoff = 1e3, zap_flags = zap_flags) qdel(src) /obj/projectile/energy/tesla/process() . = ..() //Many coders have given their blood for this speed - tesla_zap(src, zap_range, power, zap_flags) + tesla_zap(source = src, zap_range = zap_range, power = power, cutoff = 1e3, zap_flags = zap_flags) /obj/projectile/energy/tesla/revolver name = "energy orb" /obj/projectile/energy/tesla/cannon name = "tesla orb" - power = 20000 + power = 2e4 damage = 15 //Mech man big /obj/projectile/energy/tesla_cannon @@ -32,7 +32,7 @@ speed = 1.5 var/shock_damage = 5 -/obj/projectile/energy/tesla_cannon/on_hit(atom/target) +/obj/projectile/energy/tesla_cannon/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/mob/living/victim = target diff --git a/code/modules/projectiles/projectile/energy/thermal.dm b/code/modules/projectiles/projectile/energy/thermal.dm index 41efd21475c6d..0efb983eb3b69 100644 --- a/code/modules/projectiles/projectile/energy/thermal.dm +++ b/code/modules/projectiles/projectile/energy/thermal.dm @@ -10,8 +10,8 @@ bare_wound_bonus = 10 impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser -/obj/projectile/energy/inferno/on_hit(atom/target, blocked, pierce_hit) - ..() +/obj/projectile/energy/inferno/on_hit(atom/target, blocked = 0, pierce_hit) + . = ..() if(!ishuman(target)) return @@ -35,8 +35,8 @@ wound_bonus = 0 bare_wound_bonus = 10 -/obj/projectile/energy/cryo/on_hit(atom/target, blocked, pierce_hit) - ..() +/obj/projectile/energy/cryo/on_hit(atom/target, blocked = 0, pierce_hit) + . = ..() if(!ishuman(target)) return diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index afe592354785f..ae91fb6c60318 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -31,7 +31,7 @@ name = "bolt of death" icon_state = "pulse1_bl" -/obj/projectile/magic/death/on_hit(atom/target) +/obj/projectile/magic/death/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) @@ -57,7 +57,7 @@ name = "bolt of resurrection" icon_state = "ion" -/obj/projectile/magic/resurrection/on_hit(atom/target) +/obj/projectile/magic/resurrection/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) @@ -85,7 +85,7 @@ var/inner_tele_radius = 0 var/outer_tele_radius = 6 -/obj/projectile/magic/teleport/on_hit(mob/target) +/obj/projectile/magic/teleport/on_hit(mob/target, blocked = 0, pierce_hit) . = ..() var/teleammount = 0 var/teleloc = target @@ -104,7 +104,7 @@ name = "bolt of safety" icon_state = "bluespace" -/obj/projectile/magic/safety/on_hit(atom/target) +/obj/projectile/magic/safety/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isturf(target)) return BULLET_ACT_HIT @@ -123,7 +123,7 @@ icon_state = "energy" var/list/door_types = list(/obj/structure/mineral_door/wood, /obj/structure/mineral_door/iron, /obj/structure/mineral_door/silver, /obj/structure/mineral_door/gold, /obj/structure/mineral_door/uranium, /obj/structure/mineral_door/sandstone, /obj/structure/mineral_door/transparent/plasma, /obj/structure/mineral_door/transparent/diamond) -/obj/projectile/magic/door/on_hit(atom/target) +/obj/projectile/magic/door/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(istype(target, /obj/machinery/door)) OpenDoor(target) @@ -153,7 +153,7 @@ /// If set, this projectile will only pass certain changeflags to wabbajack var/set_wabbajack_changeflags -/obj/projectile/magic/change/on_hit(atom/target) +/obj/projectile/magic/change/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) @@ -171,43 +171,14 @@ icon_state = "red_1" damage_type = BURN -/obj/projectile/magic/animate/on_hit(atom/target, blocked = FALSE) +/obj/projectile/magic/animate/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() - target.animate_atom_living(firer) - -/atom/proc/animate_atom_living(mob/living/owner = null) - if((isitem(src) || isstructure(src)) && !is_type_in_list(src, GLOB.animatable_blacklist)) - if(istype(src, /obj/structure/statue/petrified)) - var/obj/structure/statue/petrified/P = src - if(P.petrified_mob) - var/mob/living/L = P.petrified_mob - var/mob/living/basic/statue/S = new(P.loc, owner) - S.name = "statue of [L.name]" - if(owner) - S.faction = list("[REF(owner)]") - S.icon = P.icon - S.icon_state = P.icon_state - S.copy_overlays(P, TRUE) - S.color = P.color - S.atom_colours = P.atom_colours.Copy() - if(L.mind) - L.mind.transfer_to(S) - if(owner) - to_chat(S, span_userdanger("You are an animate statue. You cannot move when monitored, but are nearly invincible and deadly when unobserved! Do not harm [owner], your creator.")) - P.forceMove(S) - return - else - var/obj/O = src - if(isgun(O)) - new /mob/living/simple_animal/hostile/mimic/copy/ranged(drop_location(), src, owner) - else - new /mob/living/simple_animal/hostile/mimic/copy(drop_location(), src, owner) - - else if(istype(src, /mob/living/simple_animal/hostile/mimic/copy)) - // Change our allegiance! - var/mob/living/simple_animal/hostile/mimic/copy/C = src - if(owner) - C.ChangeOwner(owner) + if(!is_type_in_typecache(target, GLOB.animatable_blacklist)) + target.animate_atom_living(firer) + +///proc to animate the target into a living creature +/atom/proc/animate_atom_living(mob/living/owner) + return /obj/projectile/magic/spellblade name = "blade energy" @@ -251,7 +222,7 @@ target.forceMove(src) return PROJECTILE_PIERCE_PHASE -/obj/projectile/magic/locker/on_hit(target) +/obj/projectile/magic/locker/on_hit(atom/target, blocked = 0, pierce_hit) if(created) return ..() if(LAZYLEN(contents)) @@ -313,7 +284,7 @@ name = "bolt of flying" icon_state = "flight" -/obj/projectile/magic/flying/on_hit(mob/living/target) +/obj/projectile/magic/flying/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/atom/throw_target = get_edge_target_turf(target, angle2dir(Angle)) @@ -323,7 +294,7 @@ name = "bolt of bounty" icon_state = "bounty" -/obj/projectile/magic/bounty/on_hit(mob/living/target) +/obj/projectile/magic/bounty/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) target.apply_status_effect(/datum/status_effect/bounty, firer) @@ -332,16 +303,16 @@ name = "bolt of antimagic" icon_state = "antimagic" -/obj/projectile/magic/antimagic/on_hit(mob/living/target) +/obj/projectile/magic/antimagic/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() - if(isliving(target)) + if(istype(target)) target.apply_status_effect(/datum/status_effect/song/antimagic) /obj/projectile/magic/fetch name = "bolt of fetching" icon_state = "fetch" -/obj/projectile/magic/fetch/on_hit(mob/living/target) +/obj/projectile/magic/fetch/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/atom/throw_target = get_edge_target_turf(target, get_dir(target, firer)) @@ -351,7 +322,7 @@ name = "bolt of babel" icon_state = "babel" -/obj/projectile/magic/babel/on_hit(mob/living/carbon/target) +/obj/projectile/magic/babel/on_hit(mob/living/carbon/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) if(curse_of_babel(target)) @@ -361,7 +332,7 @@ name = "bolt of necropotence" icon_state = "necropotence" -/obj/projectile/magic/necropotence/on_hit(mob/living/target) +/obj/projectile/magic/necropotence/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if(!isliving(target)) return @@ -378,7 +349,7 @@ name = "bolt of possession" icon_state = "wipe" -/obj/projectile/magic/wipe/on_hit(mob/living/carbon/target) +/obj/projectile/magic/wipe/on_hit(mob/living/carbon/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) for(var/x in target.get_traumas())//checks to see if the victim is already going through possession @@ -400,7 +371,7 @@ var/datum/antagonist/A = target.mind.has_antag_datum(/datum/antagonist/) if(A) poll_message = "[poll_message] Status:[A.name]." - var/list/mob/dead/observer/candidates = poll_candidates_for_mob(poll_message, ROLE_PAI, FALSE, 10 SECONDS, target) + var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob(poll_message, check_jobban = ROLE_PAI, poll_time = 10 SECONDS, target_mob = target, pic_source = target, role_name_text = "bolt of possession") if(target.stat == DEAD)//boo. return if(LAZYLEN(candidates)) @@ -481,7 +452,7 @@ speed = 0.3 /// The power of the zap itself when it electrocutes someone - var/zap_power = 20000 + var/zap_power = 2e4 /// The range of the zap itself when it electrocutes someone var/zap_range = 15 /// The flags of the zap itself when it electrocutes someone @@ -494,16 +465,16 @@ chain = firer.Beam(src, icon_state = "lightning[rand(1, 12)]") return ..() -/obj/projectile/magic/aoe/lightning/on_hit(target) +/obj/projectile/magic/aoe/lightning/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() - tesla_zap(src, zap_range, zap_power, zap_flags) + tesla_zap(source = src, zap_range = zap_range, power = zap_power, cutoff = 1e3, zap_flags = zap_flags) /obj/projectile/magic/aoe/lightning/Destroy() QDEL_NULL(chain) return ..() /obj/projectile/magic/aoe/lightning/no_zap - zap_power = 10000 + zap_power = 1e4 zap_range = 4 zap_flags = ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_LOW_POWER_GEN @@ -522,7 +493,7 @@ /// Flash radius of the fireball var/exp_flash = 3 -/obj/projectile/magic/fireball/on_hit(atom/target, blocked = FALSE, pierce_hit) +/obj/projectile/magic/fireball/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) var/mob/living/mob_target = target @@ -577,7 +548,7 @@ speed = 1 pixel_speed_multiplier = 1/7 -/obj/projectile/magic/aoe/juggernaut/on_hit(atom/target, blocked) +/obj/projectile/magic/aoe/juggernaut/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() var/turf/target_turf = get_turf(src) playsound(target_turf, 'sound/weapons/resonator_blast.ogg', 100, FALSE) diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm index 335064ec0af18..23df9c9c50a6f 100644 --- a/code/modules/projectiles/projectile/special/curse.dm +++ b/code/modules/projectiles/projectile/special/curse.dm @@ -1,7 +1,5 @@ /obj/effect/ebeam/curse_arm name = "curse arm" - layer = LARGE_MOB_LAYER - plane = GAME_PLANE_UPPER_FOV_HIDDEN /obj/projectile/curse_hand name = "curse hand" @@ -9,7 +7,6 @@ base_icon_state = "cursehand" hitsound = 'sound/effects/curse4.ogg' layer = LARGE_MOB_LAYER - plane = GAME_PLANE_UPPER_FOV_HIDDEN damage_type = BURN damage = 10 paralyze = 20 @@ -20,6 +17,7 @@ /obj/projectile/curse_hand/Initialize(mapload) . = ..() + ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT) handedness = prob(50) icon_state = "[base_icon_state][handedness]" diff --git a/code/modules/projectiles/projectile/special/floral.dm b/code/modules/projectiles/projectile/special/floral.dm index 0fef1ef7443d4..608679bf6da24 100644 --- a/code/modules/projectiles/projectile/special/floral.dm +++ b/code/modules/projectiles/projectile/special/floral.dm @@ -1,59 +1,67 @@ -/obj/projectile/energy/floramut - name = "alpha somatoray" - icon_state = "energy" +/obj/projectile/energy/flora damage = 0 damage_type = TOX armor_flag = ENERGY -/obj/projectile/energy/floramut/on_hit(atom/target, blocked = FALSE) +/obj/projectile/energy/flora/on_hit(atom/target, blocked, pierce_hit) + if(!isliving(target)) + return ..() + + var/mob/living/hit_plant = target + if(!(hit_plant.mob_biotypes & MOB_PLANT)) + hit_plant.show_message(span_notice("The radiation beam dissipates harmlessly through your body.")) + return BULLET_ACT_BLOCK + . = ..() - if(isliving(target)) - var/mob/living/L = target - if(L.mob_biotypes & MOB_PLANT) - if(prob(15)) - L.adjustToxLoss(rand(3, 6)) - L.Paralyze(100) - L.visible_message(span_warning("[L] writhes in pain as [L.p_their()] vacuoles boil."), span_userdanger("You writhe in pain as your vacuoles boil!"), span_hear("You hear the crunching of leaves.")) - if(iscarbon(L) && L.has_dna()) - var/mob/living/carbon/C = L - if(prob(80)) - C.easy_random_mutate(NEGATIVE + MINOR_NEGATIVE) - else - C.easy_random_mutate(POSITIVE) - C.random_mutate_unique_identity() - C.random_mutate_unique_features() - C.domutcheck() - else - L.adjustFireLoss(rand(5, 15)) - L.show_message(span_userdanger("The radiation beam singes you!")) - -/obj/projectile/energy/florayield + if(. == BULLET_ACT_HIT && blocked < 100) + on_hit_plant_effect(target) + + return . + +/// Called when we hit a mob with plant biotype +/obj/projectile/energy/flora/proc/on_hit_plant_effect(mob/living/hit_plant) + return + +/obj/projectile/energy/flora/mut + name = "alpha somatoray" + icon_state = "energy" + +/obj/projectile/energy/flora/mut/on_hit_plant_effect(mob/living/hit_plant) + if(prob(85)) + hit_plant.adjustFireLoss(rand(5, 15)) + hit_plant.show_message(span_userdanger("The radiation beam singes you!")) + return + + hit_plant.adjustToxLoss(rand(3, 6)) + hit_plant.Paralyze(10 SECONDS) + hit_plant.visible_message( + span_warning("[hit_plant] writhes in pain as [hit_plant.p_their()] vacuoles boil."), + span_userdanger("You writhe in pain as your vacuoles boil!"), + span_hear("You hear the crunching of leaves."), + ) + if(iscarbon(hit_plant) && hit_plant.has_dna()) + var/mob/living/carbon/carbon_plant = hit_plant + if(prob(80)) + carbon_plant.easy_random_mutate(NEGATIVE + MINOR_NEGATIVE) + else + carbon_plant.easy_random_mutate(POSITIVE) + carbon_plant.random_mutate_unique_identity() + carbon_plant.random_mutate_unique_features() + carbon_plant.domutcheck() + +/obj/projectile/energy/flora/yield name = "beta somatoray" icon_state = "energy2" - damage = 0 - damage_type = TOX - armor_flag = ENERGY -/obj/projectile/energy/florayield/on_hit(atom/target, blocked = FALSE) - . = ..() - if(isliving(target)) - var/mob/living/L = target - if(L.mob_biotypes & MOB_PLANT) - L.set_nutrition(min(L.nutrition + 30, NUTRITION_LEVEL_FULL)) +/obj/projectile/energy/flora/yield/on_hit_plant_effect(mob/living/hit_plant) + hit_plant.set_nutrition(min(hit_plant.nutrition + 30, NUTRITION_LEVEL_FULL)) -/obj/projectile/energy/florarevolution +/obj/projectile/energy/flora/evolution name = "gamma somatoray" icon_state = "energy3" - damage = 0 - damage_type = TOX - armor_flag = ENERGY -/obj/projectile/energy/florarevolution/on_hit(atom/target, blocked = FALSE) - . = ..() - if(isliving(target)) - var/mob/living/L = target - if(L.mob_biotypes & MOB_PLANT) - L.show_message(span_notice("The radiation beam leaves you feeling disoriented!")) - L.set_dizzy_if_lower(30 SECONDS) - L.emote("flip") - L.emote("spin") +/obj/projectile/energy/flora/evolution/on_hit_plant_effect(mob/living/hit_plant) + hit_plant.show_message(span_notice("The radiation beam leaves you feeling disoriented!")) + hit_plant.set_dizzy_if_lower(30 SECONDS) + hit_plant.emote("flip") + hit_plant.emote("spin") diff --git a/code/modules/projectiles/projectile/special/gravity.dm b/code/modules/projectiles/projectile/special/gravity.dm index 2a0df1b510b54..1a23b653a0519 100644 --- a/code/modules/projectiles/projectile/special/gravity.dm +++ b/code/modules/projectiles/projectile/special/gravity.dm @@ -16,7 +16,7 @@ if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items power = min(C.gun?.power, 15) -/obj/projectile/gravityrepulse/on_hit() +/obj/projectile/gravityrepulse/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() T = get_turf(src) for(var/atom/movable/A in range(T, power)) @@ -50,7 +50,7 @@ if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items power = min(C.gun?.power, 15) -/obj/projectile/gravityattract/on_hit() +/obj/projectile/gravityattract/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() T = get_turf(src) for(var/atom/movable/A in range(T, power)) @@ -83,7 +83,7 @@ if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items power = min(C.gun?.power, 15) -/obj/projectile/gravitychaos/on_hit() +/obj/projectile/gravitychaos/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() T = get_turf(src) for(var/atom/movable/A in range(T, power)) diff --git a/code/modules/projectiles/projectile/special/ion.dm b/code/modules/projectiles/projectile/special/ion.dm index 6dc0246d35bb0..9d25f1504cde5 100644 --- a/code/modules/projectiles/projectile/special/ion.dm +++ b/code/modules/projectiles/projectile/special/ion.dm @@ -7,7 +7,7 @@ impact_effect_type = /obj/effect/temp_visual/impact_effect/ion var/emp_radius = 1 -/obj/projectile/ion/on_hit(atom/target, blocked = FALSE) +/obj/projectile/ion/on_hit(atom/target, blocked = 0, pierce_hit) ..() empulse(target, emp_radius, emp_radius) return BULLET_ACT_HIT diff --git a/code/modules/projectiles/projectile/special/lightbreaker.dm b/code/modules/projectiles/projectile/special/lightbreaker.dm index fd7d3d89e7a97..2be6d9e4470da 100644 --- a/code/modules/projectiles/projectile/special/lightbreaker.dm +++ b/code/modules/projectiles/projectile/special/lightbreaker.dm @@ -4,7 +4,7 @@ damage = 0 damage_type = BRUTE armor_flag = BOMB - range = 14 + range = 21 projectile_phasing = PASSTABLE | PASSMOB | PASSMACHINE | PASSSTRUCTURE hitscan = TRUE var/disrupt_duration = 10 SECONDS diff --git a/code/modules/projectiles/projectile/special/meteor.dm b/code/modules/projectiles/projectile/special/meteor.dm index a0020a573d371..7cecbecc6aa31 100644 --- a/code/modules/projectiles/projectile/special/meteor.dm +++ b/code/modules/projectiles/projectile/special/meteor.dm @@ -9,7 +9,7 @@ damage_type = BRUTE armor_flag = BULLET -/obj/projectile/meteor/on_hit(atom/target, blocked = FALSE) +/obj/projectile/meteor/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(. == BULLET_ACT_HIT && isliving(target)) explosion(target, devastation_range = -1, light_impact_range = 2, flame_range = 0, flash_range = 1, adminlog = FALSE) diff --git a/code/modules/projectiles/projectile/special/mindflayer.dm b/code/modules/projectiles/projectile/special/mindflayer.dm index 54889bbced1c6..9f15e9389d591 100644 --- a/code/modules/projectiles/projectile/special/mindflayer.dm +++ b/code/modules/projectiles/projectile/special/mindflayer.dm @@ -1,7 +1,7 @@ /obj/projectile/beam/mindflayer name = "flayer ray" -/obj/projectile/beam/mindflayer/on_hit(atom/target, blocked = FALSE) +/obj/projectile/beam/mindflayer/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(ishuman(target)) var/mob/living/carbon/human/human_hit = target diff --git a/code/modules/projectiles/projectile/special/neurotoxin.dm b/code/modules/projectiles/projectile/special/neurotoxin.dm index 24d24f68d3087..077b3a275e99e 100644 --- a/code/modules/projectiles/projectile/special/neurotoxin.dm +++ b/code/modules/projectiles/projectile/special/neurotoxin.dm @@ -7,7 +7,7 @@ impact_effect_type = /obj/effect/temp_visual/impact_effect/neurotoxin armour_penetration = 50 -/obj/projectile/neurotoxin/on_hit(atom/target, blocked = FALSE) +/obj/projectile/neurotoxin/on_hit(atom/target, blocked = 0, pierce_hit) if(isalien(target)) damage = 0 return ..() diff --git a/code/modules/projectiles/projectile/special/plasma.dm b/code/modules/projectiles/projectile/special/plasma.dm index cf8778fe4deb6..5564ba14dab0e 100644 --- a/code/modules/projectiles/projectile/special/plasma.dm +++ b/code/modules/projectiles/projectile/special/plasma.dm @@ -11,7 +11,7 @@ muzzle_type = /obj/effect/projectile/muzzle/plasma_cutter impact_type = /obj/effect/projectile/impact/plasma_cutter -/obj/projectile/plasma/on_hit(atom/target) +/obj/projectile/plasma/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(ismineralturf(target)) var/turf/closed/mineral/M = target diff --git a/code/modules/projectiles/projectile/special/rocket.dm b/code/modules/projectiles/projectile/special/rocket.dm index 899f737d8cc83..22082d3809fcb 100644 --- a/code/modules/projectiles/projectile/special/rocket.dm +++ b/code/modules/projectiles/projectile/special/rocket.dm @@ -5,7 +5,7 @@ embedding = null shrapnel_type = null -/obj/projectile/bullet/gyro/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/gyro/on_hit(atom/target, blocked = 0, pierce_hit) ..() explosion(target, devastation_range = -1, light_impact_range = 2, explosion_cause = src) return BULLET_ACT_HIT @@ -25,7 +25,7 @@ /// Whether the rocket is capable of instantly killing a living target var/random_crits_enabled = TRUE // Worst thing Valve ever added -/obj/projectile/bullet/rocket/on_hit(atom/target, blocked = FALSE) +/obj/projectile/bullet/rocket/on_hit(atom/target, blocked = 0, pierce_hit) var/random_crit_gib = FALSE if(isliving(target) && prob(1) && random_crits_enabled) var/mob/living/gibbed_dude = target @@ -53,7 +53,7 @@ among other potential differences. This granularity is helpful for things like t if(random_crit_gib) var/mob/living/gibbed_dude = target new /obj/effect/temp_visual/crit(get_turf(gibbed_dude)) - gibbed_dude.gib() + gibbed_dude.gib(DROP_ALL_REMAINS) /// PM9 HEAP rocket - the anti-anything missile you always craved. /obj/projectile/bullet/rocket/heap diff --git a/code/modules/projectiles/projectile/special/temperature.dm b/code/modules/projectiles/projectile/special/temperature.dm index 7eae3edfa2036..3d88c40fdfb9c 100644 --- a/code/modules/projectiles/projectile/special/temperature.dm +++ b/code/modules/projectiles/projectile/special/temperature.dm @@ -9,7 +9,7 @@ /obj/projectile/temp/is_hostile_projectile() return temperature != 0 // our damage is done by cooling or heating (casting to boolean here) -/obj/projectile/temp/on_hit(atom/target, blocked = 0) +/obj/projectile/temp/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() if(iscarbon(target)) var/mob/living/carbon/hit_mob = target @@ -26,6 +26,7 @@ /obj/projectile/temp/hot name = "heat beam" + icon_state = "lava" temperature = 100 // Raise the body temp by 100 points /obj/projectile/temp/cryo diff --git a/code/modules/projectiles/projectile/special/wormhole.dm b/code/modules/projectiles/projectile/special/wormhole.dm index 26873daac871b..90eadd0bb097b 100644 --- a/code/modules/projectiles/projectile/special/wormhole.dm +++ b/code/modules/projectiles/projectile/special/wormhole.dm @@ -22,9 +22,11 @@ gun = casing.gun -/obj/projectile/beam/wormhole/on_hit(atom/target) +/obj/projectile/beam/wormhole/on_hit(atom/target, blocked = 0, pierce_hit) var/obj/item/gun/energy/wormhole_projector/projector = gun.resolve() if(!projector) qdel(src) - return + return BULLET_ACT_BLOCK + + . = ..() projector.create_portal(src, get_turf(src)) diff --git a/code/modules/reagents/chem_splash.dm b/code/modules/reagents/chem_splash.dm index 50d5ed37b474a..ebc02c308964d 100644 --- a/code/modules/reagents/chem_splash.dm +++ b/code/modules/reagents/chem_splash.dm @@ -50,7 +50,7 @@ holder.multiply_reagents(threatscale) for(var/datum/reagents/reactant as anything in reactants) - reactant.trans_to(holder, reactant.total_volume, threatscale, preserve_data = TRUE, no_react = TRUE) + reactant.trans_to(holder, reactant.total_volume, threatscale, no_react = TRUE) holder.chem_temp += extra_heat // Average temperature of reagents + extra heat. holder.handle_reactions() // React them now. diff --git a/code/modules/reagents/chemistry/equilibrium.dm b/code/modules/reagents/chemistry/equilibrium.dm index 7d7aff20fae3e..c3ccc00020705 100644 --- a/code/modules/reagents/chemistry/equilibrium.dm +++ b/code/modules/reagents/chemistry/equilibrium.dm @@ -57,10 +57,6 @@ /datum/equilibrium/New(datum/chemical_reaction/input_reaction, datum/reagents/input_holder) reaction = input_reaction holder = input_holder - if(!holder || !reaction) //sanity check - stack_trace("A new [type] was set up, with incorrect/null input vars!") - to_delete = TRUE - return if(!check_inital_conditions()) //If we're outside of the scope of the reaction vars to_delete = TRUE return @@ -78,6 +74,7 @@ LAZYREMOVE(holder.reaction_list, src) holder = null reaction = null + to_delete = TRUE return ..() /* @@ -87,39 +84,55 @@ * Don't call this unless you know what you're doing, this is an internal proc */ /datum/equilibrium/proc/check_inital_conditions() + PRIVATE_PROC(TRUE) + + if(QDELETED(holder)) + stack_trace("an equilibrium is missing it's holder.") + return FALSE + if(QDELETED(reaction)) + stack_trace("an equilibrium is missing it's reaction.") + return FALSE + if(!length(reaction.required_reagents)) + stack_trace("an equilibrium is missing required reagents.") + return FALSE + //Make sure we have the right multipler for on_reaction() - for(var/single_reagent in reaction.required_reagents) - multiplier = min(multiplier, round((holder.get_reagent_amount(single_reagent) / reaction.required_reagents[single_reagent]), CHEMICAL_QUANTISATION_LEVEL)) - if(multiplier == INFINITY) + for(var/datum/reagent/single_reagent as anything in reaction.required_reagents) + multiplier = min(multiplier, holder.get_reagent_amount(single_reagent) / reaction.required_reagents[single_reagent]) + multiplier = round(multiplier, CHEMICAL_QUANTISATION_LEVEL) + if(!multiplier) //we have no more or very little reagents left return FALSE - //Consider purity gating too? - probably not, purity is hard to determine + //To prevent reactions outside of the pH window from starting. - if(!((holder.ph >= (reaction.optimal_ph_min - reaction.determin_ph_range)) && (holder.ph <= (reaction.optimal_ph_max + reaction.determin_ph_range)))) + if(holder.ph < (reaction.optimal_ph_min - reaction.determin_ph_range) || holder.ph > (reaction.optimal_ph_max + reaction.determin_ph_range)) return FALSE + + //All checks pass. cache the product ratio + if(length(reaction.results)) + product_ratio = 0 + for(var/datum/reagent/product as anything in reaction.results) + product_ratio += reaction.results[product] + else + product_ratio = 1 return TRUE -/* -* Check to make sure our input vars are sensible - is the holder overheated? does it have the required reagents? Does it have the required calalysts? -* -* If you're adding more checks for reactions, this is the proc to edit -* otherwise, generally, don't call this directed except internally -*/ +/** + * Check to make sure our input vars are sensible + * 1) Is our atom in which this reaction is occuring still intact? + * 2) Do we still have reagents to react with + * 3) Do we have the required catalysts? + * If you're adding more checks for reactions, this is the proc to edit + * otherwise, generally, don't call this directed except internally + */ /datum/equilibrium/proc/check_reagent_properties() - //Have we exploded from on_reaction? - if(!holder.my_atom || holder.reagent_list.len == 0) - return FALSE - if(!holder) - stack_trace("an equilibrium is missing it's holder.") - return FALSE - if(!reaction) - stack_trace("an equilibrium is missing it's reaction.") + PRIVATE_PROC(TRUE) + + //Have we exploded from on_reaction or did we run out of reagents? + if(QDELETED(holder.my_atom) || !holder.reagent_list.len) return FALSE - //set up catalyst checks + //Check for catalysts var/total_matching_catalysts = 0 - //Reagents check should be handled in the calculate_yield() from multiplier - - //If the product/reactants are too impure for(var/datum/reagent/reagent as anything in holder.reagent_list) //this is done this way to reduce processing compared to holder.has_reagent(P) for(var/datum/reagent/catalyst as anything in reaction.required_catalysts) @@ -130,11 +143,8 @@ if(reagent.volume >= catalyst_agent.min_volume) catalyst_agent.consider_catalyst(src) - if(!(total_matching_catalysts == reaction.required_catalysts.len)) - return FALSE - - //All good! - return TRUE + //Our present catalysts should match with our required catalyts + return total_matching_catalysts == reaction.required_catalysts.len /* * Calculates how much we're aiming to create @@ -144,61 +154,31 @@ * Generally an internal proc */ /datum/equilibrium/proc/calculate_yield() - if(!reaction) - stack_trace("Tried to calculate an equlibrium for reaction [reaction.type], but there was no reaction set for the datum") - return FALSE + PRIVATE_PROC(TRUE) multiplier = INFINITY - for(var/reagent in reaction.required_reagents) - multiplier = min(multiplier, round((holder.get_reagent_amount(reagent) / reaction.required_reagents[reagent]), CHEMICAL_QUANTISATION_LEVEL)) + for(var/datum/reagent/reagent as anything in reaction.required_reagents) + multiplier = min(multiplier, holder.get_reagent_amount(reagent) / reaction.required_reagents[reagent]) + multiplier = round(multiplier, CHEMICAL_QUANTISATION_LEVEL) + if(!multiplier) //we have no more or very little reagents left + return FALSE - if(!length(reaction.results)) //Incase of no reagent product - product_ratio = 1 + //Incase of no reagent product + if(!length(reaction.results)) step_target_vol = INFINITY - for(var/reagent in reaction.required_reagents) + for(var/datum/reagent/reagent as anything in reaction.required_reagents) step_target_vol = min(step_target_vol, multiplier * reaction.required_reagents[reagent]) - if(step_target_vol == 0 || multiplier == 0) - return FALSE - //Sanity Check - if(step_target_vol == INFINITY || multiplier == INFINITY) //I don't see how this can happen, but I'm not bold enough to let infinities roll around for free - to_delete = TRUE - CRASH("Tried to calculate target vol for [reaction.type] with no products, but could not find required reagents for the reaction. If it got here, something is really broken with the recipe.") return TRUE - product_ratio = 0 + //If we have reagent products step_target_vol = 0 - var/true_reacted_vol //Because volumes can be lost mid reactions - for(var/product in reaction.results) - step_target_vol += (reaction.results[product]*multiplier) - product_ratio += reaction.results[product] - true_reacted_vol += holder.get_reagent_amount(product) - if(step_target_vol == 0 || multiplier == INFINITY) - return FALSE - target_vol = step_target_vol + true_reacted_vol - reacted_vol = true_reacted_vol + reacted_vol = 0 //Because volumes can be lost mid reactions + for(var/datum/reagent/product as anything in reaction.results) + step_target_vol += multiplier * reaction.results[product] + reacted_vol += holder.get_reagent_amount(product) + target_vol = reacted_vol + step_target_vol return TRUE -/* -* Deals with lag - allows a reaction to speed up to 3x from seconds_per_tick -* "Charged" time (time_deficit) discharges by incrementing reactions by doubling them -* If seconds_per_tick is greater than 1.5, then we save the extra time for the next ticks -* -* Arguments: -* * seconds_per_tick - the time between the last proc in world.time -*/ -/datum/equilibrium/proc/deal_with_time(seconds_per_tick) - if(seconds_per_tick > 1) - time_deficit += seconds_per_tick - 1 - seconds_per_tick = 1 //Lets make sure reactions aren't super speedy and blow people up from a big lag spike - else if (time_deficit) - if(time_deficit < 0.25) - seconds_per_tick += time_deficit - time_deficit = 0 - else - seconds_per_tick += 0.25 - time_deficit -= 0.25 - return seconds_per_tick - /* * Main method of checking for explosive - or failed states * Checks overheated() and overly_impure() of a reaction @@ -207,6 +187,8 @@ * step_volume_added is how much product (across all products) was added for this single step */ /datum/equilibrium/proc/check_fail_states(step_volume_added) + PRIVATE_PROC(TRUE) + //Are we overheated? if(reaction.is_cold_recipe) if(holder.chem_temp < reaction.overheat_temp && reaction.overheat_temp != NO_OVERHEAT) //This is before the process - this is here so that overly_impure and overheated() share the same code location (and therefore vars) for calls. @@ -218,7 +200,7 @@ reaction.overheated(holder, src, step_volume_added) //is our product too impure? - for(var/product in reaction.results) + for(var/datum/reagent/product as anything in reaction.results) var/datum/reagent/reagent = holder.has_reagent(product) if(!reagent) //might be missing from overheat exploding continue @@ -226,10 +208,31 @@ SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[reaction.type] overly impure reaction steps") reaction.overly_impure(holder, src, step_volume_added) - //did we explode? - if(!holder.my_atom || holder.reagent_list.len == 0) - return FALSE - return TRUE + //did we explode or run out of reagents? + return !QDELETED(holder.my_atom) && holder.reagent_list.len + +/* +* Deals with lag - allows a reaction to speed up to 3x from seconds_per_tick +* "Charged" time (time_deficit) discharges by incrementing reactions by doubling them +* If seconds_per_tick is greater than 1.5, then we save the extra time for the next ticks +* +* Arguments: +* * seconds_per_tick - the time between the last proc in world.time +*/ +/datum/equilibrium/proc/deal_with_time(seconds_per_tick) + PRIVATE_PROC(TRUE) + + if(seconds_per_tick > 1) + time_deficit += seconds_per_tick - 1 + seconds_per_tick = 1 //Lets make sure reactions aren't super speedy and blow people up from a big lag spike + else if (time_deficit) + if(time_deficit < 0.25) + seconds_per_tick += time_deficit + time_deficit = 0 + else + seconds_per_tick += 0.25 + time_deficit -= 0.25 + return seconds_per_tick /* * Main reaction processor - Increments the reaction by a timestep @@ -243,8 +246,7 @@ * * purity_modifier - how much to modify the step's purity by (0 - 1) */ /datum/equilibrium/proc/react_timestep(seconds_per_tick, purity_modifier = 1) - if(to_delete) - //This occurs when it explodes + if(to_delete) //Sanity incase we try to complete a failed reaction return FALSE if(!check_reagent_properties()) //this is first because it'll call explosions first to_delete = TRUE @@ -263,29 +265,28 @@ //Begin checks //Calculate DeltapH (Deviation of pH from optimal) //Within mid range + var/acceptable_ph if (cached_ph >= reaction.optimal_ph_min && cached_ph <= reaction.optimal_ph_max) delta_ph = 1 //100% purity for this step //Lower range else if (cached_ph < reaction.optimal_ph_min) //If we're outside of the optimal lower bound - if (cached_ph < (reaction.optimal_ph_min - reaction.determin_ph_range)) //If we're outside of the deterministic bound + acceptable_ph = reaction.optimal_ph_min - reaction.determin_ph_range + if (cached_ph < acceptable_ph) //If we're outside of the deterministic bound delta_ph = 0 //0% purity else //We're in the deterministic phase - delta_ph = (((cached_ph - (reaction.optimal_ph_min - reaction.determin_ph_range))**reaction.ph_exponent_factor)/((reaction.determin_ph_range**reaction.ph_exponent_factor))) //main pH calculation + delta_ph = ((cached_ph - acceptable_ph) / reaction.determin_ph_range) ** reaction.ph_exponent_factor //Upper range else if (cached_ph > reaction.optimal_ph_max) //If we're above of the optimal lower bound - if (cached_ph > (reaction.optimal_ph_max + reaction.determin_ph_range)) //If we're outside of the deterministic bound + acceptable_ph = reaction.optimal_ph_max + reaction.determin_ph_range + if (cached_ph > acceptable_ph) //If we're outside of the deterministic bound delta_ph = 0 //0% purity else //We're in the deterministic phase - delta_ph = (((- cached_ph + (reaction.optimal_ph_max + reaction.determin_ph_range))**reaction.ph_exponent_factor)/(reaction.determin_ph_range**reaction.ph_exponent_factor))//Reverse - to + to prevent math operation failures. - - //This should never proc, but it's a catch incase someone puts in incorrect values - else - stack_trace("[holder.my_atom] attempted to determine FermiChem pH for '[reaction.type]' which had an invalid pH of [cached_ph] for set recipie pH vars. It's likely the recipe vars are wrong.") + delta_ph = ((acceptable_ph - cached_ph) / reaction.determin_ph_range) ** reaction.ph_exponent_factor //Calculate DeltaT (Deviation of T from optimal) if(!reaction.is_cold_recipe) if (cached_temp < reaction.optimal_temp && cached_temp >= reaction.required_temp) - delta_t = (((cached_temp - reaction.required_temp)**reaction.temp_exponent_factor)/((reaction.optimal_temp - reaction.required_temp)**reaction.temp_exponent_factor)) + delta_t = ((cached_temp - reaction.required_temp) / (reaction.optimal_temp - reaction.required_temp)) ** reaction.temp_exponent_factor else if (cached_temp >= reaction.optimal_temp) delta_t = 1 else //too hot @@ -294,7 +295,7 @@ return else if (cached_temp > reaction.optimal_temp && cached_temp <= reaction.required_temp) - delta_t = (((reaction.required_temp - cached_temp)**reaction.temp_exponent_factor)/((reaction.required_temp - reaction.optimal_temp)**reaction.temp_exponent_factor)) + delta_t = ((reaction.required_temp - cached_temp) / (reaction.required_temp - reaction.optimal_temp)) ** reaction.temp_exponent_factor else if (cached_temp <= reaction.optimal_temp) delta_t = 1 else //Too cold @@ -310,51 +311,58 @@ //Catalyst modifier delta_t *= speed_mod - purity = delta_ph//set purity equal to pH offset + //set purity equal to pH offset + purity = delta_ph //Then adjust purity of result with beaker reagent purity. - purity *= reactant_purity(reaction) + purity *= holder.get_average_purity() //Then adjust it from the input modifier purity *= purity_modifier //Now we calculate how much to add - this is normalised to the rate up limiter - var/delta_chem_factor = (reaction.rate_up_lim*delta_t)*seconds_per_tick//add/remove factor - var/total_step_added = 0 + var/delta_chem_factor = reaction.rate_up_lim * delta_t * seconds_per_tick//add/remove factor //keep limited if(delta_chem_factor > step_target_vol) delta_chem_factor = step_target_vol - else if (delta_chem_factor < CHEMICAL_VOLUME_MINIMUM) - delta_chem_factor = CHEMICAL_VOLUME_MINIMUM //Normalise to multiproducts - delta_chem_factor /= product_ratio - //delta_chem_factor = round(delta_chem_factor, CHEMICAL_QUANTISATION_LEVEL) // Might not be needed - left here incase testmerge shows that it does. Remove before full commit. + delta_chem_factor = round(delta_chem_factor / product_ratio, CHEMICAL_VOLUME_ROUNDING) + if(delta_chem_factor <= 0) + to_delete = TRUE + return //Calculate how much product to make and how much reactant to remove factors.. - for(var/reagent in reaction.required_reagents) - holder.remove_reagent(reagent, (delta_chem_factor * reaction.required_reagents[reagent]), safety = TRUE) + var/required_amount + var/pH_adjust + for(var/datum/reagent/requirement as anything in reaction.required_reagents) + required_amount = reaction.required_reagents[requirement] + if(!holder.remove_reagent(requirement, delta_chem_factor * required_amount)) + to_delete = TRUE + return //Apply pH changes - var/pH_adjust if(reaction.reaction_flags & REACTION_PH_VOL_CONSTANT) - pH_adjust = ((delta_chem_factor * reaction.required_reagents[reagent])/target_vol)*(reaction.H_ion_release*h_ion_mod) + pH_adjust = ((delta_chem_factor * required_amount) / target_vol) * (reaction.H_ion_release * h_ion_mod) else //Default adds pH independant of volume - pH_adjust = (delta_chem_factor * reaction.required_reagents[reagent])*(reaction.H_ion_release*h_ion_mod) - holder.adjust_specific_reagent_ph(reagent, pH_adjust) + pH_adjust = (delta_chem_factor * required_amount) * (reaction.H_ion_release * h_ion_mod) + holder.adjust_specific_reagent_ph(requirement, pH_adjust) var/step_add - for(var/product in reaction.results) + var/total_step_added = 0 + for(var/datum/reagent/product as anything in reaction.results) //create the products - step_add = delta_chem_factor * reaction.results[product] - //Default handiling - holder.add_reagent(product, step_add, null, cached_temp, purity, override_base_ph = TRUE) + step_add = holder.add_reagent(product, delta_chem_factor * reaction.results[product], null, cached_temp, purity, override_base_ph = TRUE) + if(!step_add) + to_delete = TRUE + return //Apply pH changes - var/pH_adjust if(reaction.reaction_flags & REACTION_PH_VOL_CONSTANT) - pH_adjust = (step_add/target_vol)*(reaction.H_ion_release*h_ion_mod) + pH_adjust = (step_add / target_vol) * (reaction.H_ion_release * h_ion_mod) else - pH_adjust = step_add*(reaction.H_ion_release*h_ion_mod) + pH_adjust = step_add * (reaction.H_ion_release * h_ion_mod) holder.adjust_specific_reagent_ph(product, pH_adjust) + + //record amounts created reacted_vol += step_add total_step_added += step_add @@ -366,11 +374,11 @@ #endif //Apply thermal output of reaction to beaker - if(reaction.reaction_flags & REACTION_HEAT_ARBITARY) - holder.chem_temp += clamp((reaction.thermic_constant* total_step_added*thermic_mod), 0, CHEMICAL_MAXIMUM_TEMPERATURE) //old method - for every bit added, the whole temperature is adjusted - else //Standard mechanics - var/heat_energy = reaction.thermic_constant * total_step_added * thermic_mod * SPECIFIC_HEAT_DEFAULT - holder.adjust_thermal_energy(heat_energy, 0, CHEMICAL_MAXIMUM_TEMPERATURE) //heat is relative to the beaker conditions + var/heat_energy = reaction.thermic_constant * total_step_added * thermic_mod + if(reaction.reaction_flags & REACTION_HEAT_ARBITARY) //old method - for every bit added, the whole temperature is adjusted + holder.set_temperature(clamp(holder.chem_temp + heat_energy, 0, CHEMICAL_MAXIMUM_TEMPERATURE)) + else //Standard mechanics - heat is relative to the beaker conditions + holder.adjust_thermal_energy(heat_energy * SPECIFIC_HEAT_DEFAULT, 0, CHEMICAL_MAXIMUM_TEMPERATURE) //Give a chance of sounds if(prob(5)) @@ -384,34 +392,9 @@ //post reaction checks if(!(check_fail_states(total_step_added))) to_delete = TRUE + return - //end reactions faster so plumbing is faster - if((step_add >= step_target_vol) && (length(holder.reaction_list == 1)))//length is so that plumbing is faster - but it doesn't disable competitive reactions. Basically, competitive reactions will likely reach their step target at the start, so this will disable that. We want to avoid that. But equally, we do want to full stop a holder from reacting asap so plumbing isn't waiting an tick to resolve. + //If the volume of reagents created(total_step_added) >= volume of reagents still to be created(step_target_vol) then end + //i.e. we have created all the reagents needed for this reaction + if(total_step_added >= step_target_vol) to_delete = TRUE - - holder.update_total()//do NOT recalculate reactions - - -/* -* Calculates the total sum normalised purity of ALL reagents in a holder -* -* Currently calculates it irrespective of required reagents at the start, but this should be changed if this is powergamed to required reagents -* It's not currently because overly_impure affects all reagents -*/ -/datum/equilibrium/proc/reactant_purity(datum/chemical_reaction/C) - var/list/cached_reagents = holder.reagent_list - var/i = 0 - var/cached_purity - for(var/datum/reagent/reagent as anything in holder.reagent_list) - if (reagent in cached_reagents) - cached_purity += reagent.purity - i++ - if(!i)//I've never seen it get here with 0, but in case - it gets here when it blows up from overheat - stack_trace("No reactants found mid reaction for [C.type]. Beaker: [holder.my_atom]") - return 0 //we exploded and cleared reagents - but lets not kill the process - return cached_purity/i - -///Panic stop a reaction - cleanup should be handled by the next timestep -/datum/equilibrium/proc/force_clear_reactive_agents() - for(var/reagent in reaction.required_reagents) - holder.remove_reagent(reagent, (multiplier * reaction.required_reagents[reagent]), safety = 1) diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm deleted file mode 100644 index 6b4ccc77101c1..0000000000000 --- a/code/modules/reagents/chemistry/holder.dm +++ /dev/null @@ -1,2108 +0,0 @@ -#define REAGENTS_UI_MODE_LOOKUP 0 -#define REAGENTS_UI_MODE_REAGENT 1 -#define REAGENTS_UI_MODE_RECIPE 2 - -#define REAGENT_TRANSFER_AMOUNT "amount" -#define REAGENT_PURITY "purity" - -/// Initialises all /datum/reagent into a list indexed by reagent id -/proc/init_chemical_reagent_list() - var/list/reagent_list = list() - - var/paths = subtypesof(/datum/reagent) - - for(var/path in paths) - if(path in GLOB.fake_reagent_blacklist) - continue - var/datum/reagent/D = new path() - D.mass = rand(10, 800) //This is terrible and should be removed ASAP! - reagent_list[path] = D - - return reagent_list - -/// Creates an list which is indexed by reagent name . used by plumbing reaction chamber and chemical filter UI -/proc/init_chemical_name_list() - var/list/name_list = list() - for(var/X in GLOB.chemical_reagents_list) - var/datum/reagent/Reagent = GLOB.chemical_reagents_list[X] - name_list += Reagent.name - return sort_list(name_list) - - -/proc/build_chemical_reactions_lists() - //Chemical Reactions - Initialises all /datum/chemical_reaction into a list - // It is filtered into multiple lists within a list. - // For example: - // chemical_reactions_list_reactant_index[/datum/reagent/toxin/plasma] is a list of all reactions relating to plasma - //For chemical reaction list product index - indexes reactions based off the product reagent type - see get_recipe_from_reagent_product() in helpers - //For chemical reactions list lookup list - creates a bit list of info passed to the UI. This is saved to reduce lag from new windows opening, since it's a lot of data. - - //Prevent these reactions from appearing in lookup tables (UI code) - var/list/blacklist = typecacheof(/datum/chemical_reaction/randomized) - - if(GLOB.chemical_reactions_list_reactant_index) - return - - //Randomized need to go last since they need to check against conflicts with normal recipes - var/paths = subtypesof(/datum/chemical_reaction) - typesof(/datum/chemical_reaction/randomized) + subtypesof(/datum/chemical_reaction/randomized) - GLOB.chemical_reactions_list = list() //typepath to reaction list - GLOB.chemical_reactions_list_reactant_index = list() //reagents to reaction list - GLOB.chemical_reactions_results_lookup_list = list() //UI glob - GLOB.chemical_reactions_list_product_index = list() //product to reaction list - - var/list/datum/chemical_reaction/reactions = list() - for(var/path in paths) - var/datum/chemical_reaction/reaction = new path() - reactions += reaction - - // Ok so we're gonna do a thingTM here - // I want to distribute all our reactions such that each reagent id links to as few as possible - // I get the feeling there's a canonical way of doing this, but I don't know it - // So instead, we're gonna wing it - var/list/reagent_to_react_count = list() - for(var/datum/chemical_reaction/reaction as anything in reactions) - for(var/reagent_id as anything in reaction.required_reagents) - reagent_to_react_count[reagent_id] += 1 - - var/list/reaction_lookup = GLOB.chemical_reactions_list_reactant_index - // Create filters based on a random reagent id in the required reagents list - this is used to speed up handle_reactions() - // Basically, we only really need to care about ONE reagent, at least when initially filtering, since any others are ignorable - // Doing this separately because it relies on the loop above, and this is easier to parse - for(var/datum/chemical_reaction/reaction as anything in reactions) - var/preferred_id = null - for(var/reagent_id as anything in reaction.required_reagents) - if(!preferred_id) - preferred_id = reagent_id - continue - // If we would have less then they would, take it - if(length(reaction_lookup[reagent_id]) < length(reaction_lookup[preferred_id])) - preferred_id = reagent_id - continue - // If they potentially have more then us, we take it - if(reagent_to_react_count[reagent_id] < reagent_to_react_count[preferred_id]) - preferred_id = reagent_id - continue - if (preferred_id != null) - if(!reaction_lookup[preferred_id]) - reaction_lookup[preferred_id] = list() - reaction_lookup[preferred_id] += reaction - - for(var/datum/chemical_reaction/reaction as anything in reactions) - var/list/product_ids = list() - var/list/reagents = list() - var/list/product_names = list() - var/bitflags = reaction.reaction_tags - - if(!reaction.required_reagents || !reaction.required_reagents.len) //Skip impossible reactions - continue - - GLOB.chemical_reactions_list[reaction.type] = reaction - - for(var/reagent_path in reaction.required_reagents) - var/datum/reagent/reagent = find_reagent_object_from_type(reagent_path) - if(!istype(reagent)) - stack_trace("Invalid reagent found in [reaction] required_reagents: [reagent_path]") - continue - reagents += list(list("name" = reagent.name, "id" = reagent.type)) - - for(var/product in reaction.results) - var/datum/reagent/reagent = find_reagent_object_from_type(product) - if(!istype(reagent)) - stack_trace("Invalid reagent found in [reaction] results: [product]") - continue - product_names += reagent.name - product_ids += product - - var/product_name - if(!length(product_names)) - var/list/names = splittext("[reaction.type]", "/") - product_name = names[names.len] - else - product_name = product_names[1] - - if(!is_type_in_typecache(reaction.type, blacklist)) - //Master list of ALL reactions that is used in the UI lookup table. This is expensive to make, and we don't want to lag the server by creating it on UI request, so it's cached to send to UIs instantly. - GLOB.chemical_reactions_results_lookup_list += list(list("name" = product_name, "id" = reaction.type, "bitflags" = bitflags, "reactants" = reagents)) - - // Create filters based on each reagent id in the required reagents list - this is specifically for finding reactions from product(reagent) ids/typepaths. - for(var/id in product_ids) - if(!GLOB.chemical_reactions_list_product_index[id]) - GLOB.chemical_reactions_list_product_index[id] = list() - GLOB.chemical_reactions_list_product_index[id] += reaction - - -///////////////////////////////Main reagents code///////////////////////////////////////////// - -/// Holder for a bunch of [/datum/reagent] -/datum/reagents - /// The reagents being held - var/list/datum/reagent/reagent_list = new/list() - /// Current volume of all the reagents - var/total_volume = 0 - /// Max volume of this holder - var/maximum_volume = 100 - /// The atom this holder is attached to - var/atom/my_atom = null - /// Current temp of the holder volume - var/chem_temp = 150 - ///pH of the whole system - var/ph = CHEMICAL_NORMAL_PH - /// unused - var/last_tick = 1 - /// various flags, see code\__DEFINES\reagents.dm - var/flags - ///list of reactions currently on going, this is a lazylist for optimisation - var/list/datum/equilibrium/reaction_list - ///cached list of reagents typepaths (not object references), this is a lazylist for optimisation - var/list/datum/reagent/previous_reagent_list - ///If a reaction fails due to temperature or pH, this tracks the required temperature or pH for it to be enabled. - var/list/failed_but_capable_reactions - ///Hard check to see if the reagents is presently reacting - var/is_reacting = FALSE - ///UI lookup stuff - ///Keeps the id of the reaction displayed in the ui - var/ui_reaction_id = null - ///Keeps the id of the reagent displayed in the ui - var/ui_reagent_id = null - ///The bitflag of the currently selected tags in the ui - var/ui_tags_selected = NONE - ///What index we're at if we have multiple reactions for a reagent product - var/ui_reaction_index = 1 - ///If we're syncing with the beaker - so return reactions that are actively happening - var/ui_beaker_sync = FALSE - -/datum/reagents/New(maximum=100, new_flags=0) - maximum_volume = maximum - flags = new_flags - -/datum/reagents/Destroy() - //We're about to delete all reagents, so lets cleanup - for(var/datum/reagent/reagent as anything in reagent_list) - qdel(reagent) - reagent_list = null - if(is_reacting) //If false, reaction list should be cleaned up - force_stop_reacting() - QDEL_LAZYLIST(reaction_list) - previous_reagent_list = null - if(my_atom && my_atom.reagents == src) - my_atom.reagents = null - my_atom = null - return ..() - -/** - * Adds a reagent to this holder - * - * Arguments: - * * reagent - The reagent id to add - * * amount - Amount to add - * * list/data - Any reagent data for this reagent, used for transferring data with reagents - * * reagtemp - Temperature of this reagent, will be equalized - * * no_react - prevents reactions being triggered by this addition - * * added_purity - override to force a purity when added - * * added_ph - override to force a pH when added - * * override_base_ph - ingore the present pH of the reagent, and instead use the default (i.e. if buffers/reactions alter it) - * * ignore splitting - Don't call the process that handles reagent spliting in a mob (impure/inverse) - generally leave this false unless you care about REAGENTS_DONOTSPLIT flags (see reagent defines) - */ -/datum/reagents/proc/add_reagent(reagent, amount, list/data=null, reagtemp = DEFAULT_REAGENT_TEMPERATURE, added_purity = null, added_ph, no_react = FALSE, override_base_ph = FALSE, ignore_splitting = FALSE) - // Prevents small amount problems, as well as zero and below zero amounts. - if(amount <= CHEMICAL_QUANTISATION_LEVEL) - return FALSE - - if(!IS_FINITE(amount)) - stack_trace("non finite amount passed to add reagent [amount] [reagent]") - return FALSE - - if(SEND_SIGNAL(src, COMSIG_REAGENTS_PRE_ADD_REAGENT, reagent, amount, reagtemp, data, no_react) & COMPONENT_CANCEL_REAGENT_ADD) - return FALSE - - var/datum/reagent/glob_reagent = GLOB.chemical_reagents_list[reagent] - if(!glob_reagent) - stack_trace("[my_atom] attempted to add a reagent called '[reagent]' which doesn't exist. ([usr])") - return FALSE - if(isnull(added_purity)) //Because purity additions can be 0 - added_purity = glob_reagent.creation_purity //Usually 1 - if(!added_ph) - added_ph = glob_reagent.ph - - //Split up the reagent if it's in a mob - var/has_split = FALSE - if(!ignore_splitting && (flags & REAGENT_HOLDER_ALIVE)) //Stomachs are a pain - they will constantly call on_mob_add unless we split on addition to stomachs, but we also want to make sure we don't double split - var/adjusted_vol = process_mob_reagent_purity(glob_reagent, amount, added_purity) - if(!adjusted_vol) //If we're inverse or FALSE cancel addition - return TRUE - /* We return true here because of #63301 - The only cases where this will be false or 0 if its an inverse chem, an impure chem of 0 purity (highly unlikely if even possible), or if glob_reagent is null (which shouldn't happen at all as there's a check for that a few lines up), - In the first two cases, we would want to return TRUE so trans_to and other similar methods actually delete the corresponding chemical from the original reagent holder. - */ - amount = adjusted_vol - has_split = TRUE - - update_total() - var/cached_total = total_volume - if(cached_total + amount > maximum_volume) - amount = (maximum_volume - cached_total) //Doesnt fit in. Make it disappear. shouldn't happen. Will happen. - if(amount <= 0) - return FALSE - - var/cached_temp = chem_temp - var/list/cached_reagents = reagent_list - - //Equalize temperature - Not using specific_heat() because the new chemical isn't in yet. - var/old_heat_capacity = 0 - if(reagtemp != cached_temp) - for(var/datum/reagent/iter_reagent as anything in cached_reagents) - old_heat_capacity += iter_reagent.specific_heat * iter_reagent.volume - - //add the reagent to the existing if it exists - for(var/datum/reagent/iter_reagent as anything in cached_reagents) - if(iter_reagent.type == reagent) - if(override_base_ph) - added_ph = iter_reagent.ph - iter_reagent.purity = ((iter_reagent.creation_purity * iter_reagent.volume) + (added_purity * amount)) /(iter_reagent.volume + amount) //This should add the purity to the product - iter_reagent.creation_purity = iter_reagent.purity - iter_reagent.ph = ((iter_reagent.ph*(iter_reagent.volume))+(added_ph*amount))/(iter_reagent.volume+amount) - iter_reagent.volume += round(amount, CHEMICAL_QUANTISATION_LEVEL) - update_total() - - iter_reagent.on_merge(data, amount) - if(reagtemp != cached_temp) - var/new_heat_capacity = heat_capacity() - if(new_heat_capacity) - set_temperature(((old_heat_capacity * cached_temp) + (iter_reagent.specific_heat * amount * reagtemp)) / new_heat_capacity) - else - set_temperature(reagtemp) - - SEND_SIGNAL(src, COMSIG_REAGENTS_ADD_REAGENT, iter_reagent, amount, reagtemp, data, no_react) - if(!no_react && !is_reacting) //To reduce the amount of calculations for a reaction the reaction list is only updated on a reagents addition. - handle_reactions() - return TRUE - - //otherwise make a new one - var/datum/reagent/new_reagent = new reagent(data) - cached_reagents += new_reagent - new_reagent.holder = src - new_reagent.volume = amount - new_reagent.purity = added_purity - new_reagent.creation_purity = added_purity - new_reagent.ph = added_ph - new_reagent.on_new(data) - - if(isliving(my_atom)) - new_reagent.on_mob_add(my_atom, amount) //Must occur before it could posibly run on_mob_delete - - if(has_split) //prevent it from splitting again - new_reagent.chemical_flags |= REAGENT_DONOTSPLIT - - update_total() - if(reagtemp != cached_temp) - var/new_heat_capacity = heat_capacity() - if(new_heat_capacity) - set_temperature(((old_heat_capacity * cached_temp) + (new_reagent.specific_heat * amount * reagtemp)) / new_heat_capacity) - else - set_temperature(reagtemp) - - SEND_SIGNAL(src, COMSIG_REAGENTS_NEW_REAGENT, new_reagent, amount, reagtemp, data, no_react) - if(!no_react) - handle_reactions() - return TRUE - -/// Like add_reagent but you can enter a list. Format it like this: list(/datum/reagent/toxin = 10, "beer" = 15) -/datum/reagents/proc/add_reagent_list(list/list_reagents, list/data=null) - for(var/r_id in list_reagents) - var/amt = list_reagents[r_id] - add_reagent(r_id, amt, data) - - -/// Remove a specific reagent -/datum/reagents/proc/remove_reagent(reagent, amount, safety = TRUE)//Added a safety check for the trans_id_to - if(isnull(amount)) - stack_trace("null amount passed to reagent code") - return FALSE - - if(amount < 0 || !IS_FINITE(amount)) - stack_trace("invalid number passed to remove_reagent [amount]") - return FALSE - - var/list/cached_reagents = reagent_list - for(var/datum/reagent/cached_reagent as anything in cached_reagents) - if(cached_reagent.type == reagent) - //clamp the removal amount to be between current reagent amount - //and zero, to prevent removing more than the holder has stored - amount = clamp(amount, 0, cached_reagent.volume) - cached_reagent.volume -= amount - update_total() - if(!safety)//So it does not handle reactions when it need not to - handle_reactions() - SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, QDELING(cached_reagent) ? reagent : cached_reagent, amount) - - return TRUE - return FALSE - -/// Remove an amount of reagents without caring about what they are -/datum/reagents/proc/remove_any(amount = 1) - var/list/cached_reagents = reagent_list - var/total_removed = 0 - var/current_list_element = 1 - var/initial_list_length = cached_reagents.len //stored here because removing can cause some reagents to be deleted, ergo length change. - - current_list_element = rand(1, cached_reagents.len) - - while(total_removed != amount) - if(total_removed >= amount) - break - if(total_volume <= 0 || !cached_reagents.len) - break - - if(current_list_element > cached_reagents.len) - current_list_element = 1 - - var/datum/reagent/R = cached_reagents[current_list_element] - var/remove_amt = min(amount-total_removed,round(amount/rand(2,initial_list_length),round(amount/10,0.01))) //double round to keep it at a somewhat even spread relative to amount without getting funky numbers. - //min ensures we don't go over amount. - remove_reagent(R.type, remove_amt) - - current_list_element++ - total_removed += remove_amt - update_total() - - handle_reactions() - return total_removed //this should be amount unless the loop is prematurely broken, in which case it'll be lower. It shouldn't ever go OVER amount. - -/// Removes all reagents from this holder -/datum/reagents/proc/remove_all(amount = 1) - var/list/cached_reagents = reagent_list - if(total_volume > 0) - var/part = amount / total_volume - for(var/datum/reagent/reagent as anything in cached_reagents) - remove_reagent(reagent.type, reagent.volume * part) - - //finish_reacting() //A just in case - update total is in here - should be unneeded, make sure to test this - handle_reactions() - return amount - -/// Removes all reagent of X type. @strict set to 1 determines whether the childs of the type are included. -/datum/reagents/proc/remove_all_type(reagent_type, amount, strict = 0, safety = 1) - if(!isnum(amount)) - return 1 - var/list/cached_reagents = reagent_list - var/has_removed_reagent = 0 - - for(var/datum/reagent/reagent as anything in cached_reagents) - var/matches = 0 - // Switch between how we check the reagent type - if(strict) - if(reagent.type == reagent_type) - matches = 1 - else - if(istype(reagent, reagent_type)) - matches = 1 - // We found a match, proceed to remove the reagent. Keep looping, we might find other reagents of the same type. - if(matches) - // Have our other proc handle removement - has_removed_reagent = remove_reagent(reagent.type, amount, safety) - - return has_removed_reagent - -/// Fuck this one reagent -/datum/reagents/proc/del_reagent(target_reagent_typepath) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - if(reagent.type == target_reagent_typepath) - if(isliving(my_atom)) - if(reagent.metabolizing) - reagent.metabolizing = FALSE - reagent.on_mob_end_metabolize(my_atom) - reagent.on_mob_delete(my_atom) - - reagent_list -= reagent - LAZYREMOVE(previous_reagent_list, reagent.type) - qdel(reagent) - update_total() - SEND_SIGNAL(src, COMSIG_REAGENTS_DEL_REAGENT, reagent) - return TRUE - -/// Turn one reagent into another, preserving volume, temp, purity, ph -/datum/reagents/proc/convert_reagent(source_reagent_typepath, target_reagent_typepath, multiplier = 1, include_source_subtypes = FALSE) - var/reagent_amount - var/reagent_purity - var/reagent_ph - if(include_source_subtypes) - reagent_ph = ph - var/weighted_purity - for(var/datum/reagent/reagent as anything in reagent_list) - if(reagent.type in typecacheof(source_reagent_typepath)) - weighted_purity += reagent.volume * reagent.purity - reagent_amount += reagent.volume - remove_reagent(reagent.type, reagent.volume) - reagent_purity = weighted_purity / reagent_amount - else - var/datum/reagent/source_reagent = get_reagent(source_reagent_typepath) - reagent_amount = source_reagent.volume - reagent_purity = source_reagent.purity - reagent_ph = source_reagent.ph - remove_reagent(source_reagent_typepath, reagent_amount) - add_reagent(target_reagent_typepath, reagent_amount * multiplier, reagtemp = chem_temp, added_purity = reagent_purity, added_ph = reagent_ph) - -//Converts the creation_purity to purity -/datum/reagents/proc/uncache_creation_purity(id) - var/datum/reagent/R = has_reagent(id) - if(!R) - return - R.purity = R.creation_purity - -/// Remove every reagent except this one -/datum/reagents/proc/isolate_reagent(reagent) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/cached_reagent as anything in cached_reagents) - if(cached_reagent.type != reagent) - del_reagent(cached_reagent.type) - update_total() - -/// Removes all reagents -/datum/reagents/proc/clear_reagents() - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - del_reagent(reagent.type) - SEND_SIGNAL(src, COMSIG_REAGENTS_CLEAR_REAGENTS) - - -/** - * Check if this holder contains this reagent. - * Reagent takes a PATH to a reagent. - * Amount checks for having a specific amount of that chemical. - * Needs matabolizing takes into consideration if the chemical is matabolizing when it's checked. - * Check subtypes controls whether it should it should also include subtypes: ispath(type, reagent) versus type == reagent. - */ -/datum/reagents/proc/has_reagent(reagent, amount = -1, needs_metabolizing = FALSE, check_subtypes = FALSE) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/holder_reagent as anything in cached_reagents) - if (check_subtypes ? ispath(holder_reagent.type, reagent) : holder_reagent.type == reagent) - if(!amount) - if(needs_metabolizing && !holder_reagent.metabolizing) - if(check_subtypes) - continue - return FALSE - return holder_reagent - else - if(round(holder_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) >= amount) - if(needs_metabolizing && !holder_reagent.metabolizing) - if(check_subtypes) - continue - return FALSE - return holder_reagent - else if(!check_subtypes) - return FALSE - return FALSE - -/** - * Check if this holder contains a reagent with a chemical_flags containing this flag - * Reagent takes the bitflag to search for - * Amount checks for having a specific amount of reagents matching that chemical - */ -/datum/reagents/proc/has_chemical_flag(chemical_flag, amount = 0) - var/found_amount = 0 - var/list/cached_reagents = reagent_list - for(var/datum/reagent/holder_reagent as anything in cached_reagents) - if (holder_reagent.chemical_flags & chemical_flag) - found_amount += holder_reagent.volume - if(found_amount >= amount) - return TRUE - return FALSE - - -/** - * Transfer some stuff from this holder to a target object - * - * Arguments: - * * obj/target - Target to attempt transfer to - * * amount - amount of reagent volume to transfer - * * multiplier - multiplies amount of each reagent by this number - * * preserve_data - if preserve_data=0, the reagents data will be lost. Usefull if you use data for some strange stuff and don't want it to be transferred. - * * no_react - passed through to [/datum/reagents/proc/add_reagent] - * * mob/transferred_by - used for logging - * * remove_blacklisted - skips transferring of reagents without REAGENT_CAN_BE_SYNTHESIZED in chemical_flags - * * methods - passed through to [/datum/reagents/proc/expose_single] and [/datum/reagent/proc/on_transfer] - * * show_message - passed through to [/datum/reagents/proc/expose_single] - * * round_robin - if round_robin=TRUE, so transfer 5 from 15 water, 15 sugar and 15 plasma becomes 10, 15, 15 instead of 13.3333, 13.3333 13.3333. Good if you hate floating point errors - * * ignore_stomach - when using methods INGEST will not use the stomach as the target - */ -/datum/reagents/proc/trans_to(obj/target, amount = 1, multiplier = 1, preserve_data = TRUE, no_react = FALSE, mob/transferred_by, remove_blacklisted = FALSE, methods = NONE, show_message = TRUE, round_robin = FALSE, ignore_stomach = FALSE) - var/list/cached_reagents = reagent_list - if(!target || !total_volume) - return - if(amount < 0) - return - - var/cached_amount = amount - var/atom/target_atom - var/datum/reagents/R - if(istype(target, /datum/reagents)) - R = target - target_atom = R.my_atom - else - if(!ignore_stomach && (methods & INGEST) && iscarbon(target)) - var/mob/living/carbon/eater = target - var/obj/item/organ/internal/stomach/belly = eater.get_organ_slot(ORGAN_SLOT_STOMACH) - if(!belly) - eater.expel_ingested(my_atom, amount) - return - R = belly.reagents - target_atom = belly - else if(!target.reagents) - return - else - R = target.reagents - target_atom = target - - //Set up new reagents to inherit the old ongoing reactions - if(!no_react) - transfer_reactions(R) - - amount = min(min(amount, src.total_volume), R.maximum_volume-R.total_volume) - var/trans_data = null - var/transfer_log = list() - var/r_to_send = list() // Validated list of reagents to be exposed - var/reagents_to_remove = list() - if(!round_robin) - var/part = amount / src.total_volume - for(var/datum/reagent/reagent as anything in cached_reagents) - if(remove_blacklisted && !(reagent.chemical_flags & REAGENT_CAN_BE_SYNTHESIZED)) - continue - var/transfer_amount = reagent.volume * part - if(preserve_data) - trans_data = copy_data(reagent) - if(reagent.intercept_reagents_transfer(R, cached_amount))//Use input amount instead. - continue - if(!R.add_reagent(reagent.type, transfer_amount * multiplier, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT)) //we only handle reaction after every reagent has been transferred. - continue - if(methods) - r_to_send += reagent - - reagents_to_remove += reagent - - if(isorgan(target_atom)) - R.expose_multiple(r_to_send, target, methods, part, show_message) - else - R.expose_multiple(r_to_send, target_atom, methods, part, show_message) - - for(var/datum/reagent/reagent as anything in reagents_to_remove) - var/transfer_amount = reagent.volume * part - if(methods) - reagent.on_transfer(target_atom, methods, transfer_amount * multiplier) - remove_reagent(reagent.type, transfer_amount) - var/list/reagent_qualities = list(REAGENT_TRANSFER_AMOUNT = transfer_amount, REAGENT_PURITY = reagent.purity) - transfer_log[reagent.type] = reagent_qualities - - else - var/to_transfer = amount - for(var/datum/reagent/reagent as anything in cached_reagents) - if(!to_transfer) - break - if(remove_blacklisted && !(reagent.chemical_flags & REAGENT_CAN_BE_SYNTHESIZED)) - continue - if(preserve_data) - trans_data = copy_data(reagent) - var/transfer_amount = amount - if(amount > reagent.volume) - transfer_amount = reagent.volume - if(reagent.intercept_reagents_transfer(R, cached_amount))//Use input amount instead. - continue - if(!R.add_reagent(reagent.type, transfer_amount * multiplier, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT)) //we only handle reaction after every reagent has been transferred. - continue - to_transfer = max(to_transfer - transfer_amount , 0) - if(methods) - if(isorgan(target_atom)) - R.expose_single(reagent, target, methods, transfer_amount, show_message) - else - R.expose_single(reagent, target_atom, methods, transfer_amount, show_message) - reagent.on_transfer(target_atom, methods, transfer_amount * multiplier) - remove_reagent(reagent.type, transfer_amount) - var/list/reagent_qualities = list(REAGENT_TRANSFER_AMOUNT = transfer_amount, REAGENT_PURITY = reagent.purity) - transfer_log[reagent.type] = reagent_qualities - - if(transferred_by && target_atom) - target_atom.add_hiddenprint(transferred_by) //log prints so admins can figure out who touched it last. - log_combat(transferred_by, target_atom, "transferred reagents ([get_external_reagent_log_string(transfer_log)]) from [my_atom] to") - - update_total() - R.update_total() - if(!no_react) - R.handle_reactions() - src.handle_reactions() - return amount - -/// Transfer a specific reagent id to the target object -/datum/reagents/proc/trans_id_to(obj/target, reagent, amount=1, preserve_data=1)//Not sure why this proc didn't exist before. It does now! /N - var/list/cached_reagents = reagent_list - if (!target) - return - - var/datum/reagents/holder - if(istype(target, /datum/reagents)) - holder = target - else if(target.reagents && total_volume > 0 && get_reagent_amount(reagent)) - holder = target.reagents - else - return - if(amount < 0) - return - var/cached_amount = amount - if(get_reagent_amount(reagent) < amount) - amount = get_reagent_amount(reagent) - - amount = min(round(amount, CHEMICAL_VOLUME_ROUNDING), holder.maximum_volume - holder.total_volume) - var/trans_data = null - for (var/looping_through_reagents in cached_reagents) - var/datum/reagent/current_reagent = looping_through_reagents - if(current_reagent.type == reagent) - if(preserve_data) - trans_data = current_reagent.data - if(current_reagent.intercept_reagents_transfer(holder, cached_amount))//Use input amount instead. - break - force_stop_reagent_reacting(current_reagent) - holder.add_reagent(current_reagent.type, amount, trans_data, chem_temp, current_reagent.purity, current_reagent.ph, no_react = TRUE, ignore_splitting = current_reagent.chemical_flags & REAGENT_DONOTSPLIT) - remove_reagent(current_reagent.type, amount, 1) - break - - update_total() - holder.update_total() - holder.handle_reactions() - return amount - -/// Copies the reagents to the target object -/datum/reagents/proc/copy_to(obj/target, amount = 1, multiplier = 1, preserve_data = TRUE, no_react = FALSE) - var/list/cached_reagents = reagent_list - if(!target || !total_volume) - return - - var/datum/reagents/target_holder - if(istype(target, /datum/reagents)) - target_holder = target - else - if(!target.reagents) - return - target_holder = target.reagents - - if(amount < 0) - return - - amount = min(min(amount, total_volume), target_holder.maximum_volume - target_holder.total_volume) - var/part = amount / total_volume - var/trans_data = null - for(var/datum/reagent/reagent as anything in cached_reagents) - var/copy_amount = reagent.volume * part - if(preserve_data) - trans_data = reagent.data - target_holder.add_reagent(reagent.type, copy_amount * multiplier, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) - - if(!no_react) - // pass over previous ongoing reactions before handle_reactions is called - transfer_reactions(target_holder) - - target_holder.update_total() - target_holder.handle_reactions() - - return amount - -///Multiplies the reagents inside this holder by a specific amount -/datum/reagents/proc/multiply_reagents(multiplier=1) - var/list/cached_reagents = reagent_list - if(!total_volume) - return - var/change = (multiplier - 1) //Get the % change - for(var/datum/reagent/reagent as anything in cached_reagents) - if(change > 0) - add_reagent(reagent.type, reagent.volume * change, added_purity = reagent.purity, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) - else - remove_reagent(reagent.type, abs(reagent.volume * change)) //absolute value to prevent a double negative situation (removing -50% would be adding 50%) - - update_total() - handle_reactions() - - -/// Get the name of the reagent there is the most of in this holder -/datum/reagents/proc/get_master_reagent_name() - var/list/cached_reagents = reagent_list - var/name - var/max_volume = 0 - for(var/datum/reagent/reagent as anything in cached_reagents) - if(reagent.volume > max_volume) - max_volume = reagent.volume - name = reagent.name - - return name - -/// Get the id of the reagent there is the most of in this holder -/datum/reagents/proc/get_master_reagent_id() - var/list/cached_reagents = reagent_list - var/max_type - var/max_volume = 0 - for(var/datum/reagent/reagent as anything in cached_reagents) - if(reagent.volume > max_volume) - max_volume = reagent.volume - max_type = reagent.type - - return max_type - -/// Get a reference to the reagent there is the most of in this holder -/datum/reagents/proc/get_master_reagent() - var/list/cached_reagents = reagent_list - var/datum/reagent/master - var/max_volume = 0 - for(var/datum/reagent/reagent as anything in cached_reagents) - if(reagent.volume > max_volume) - max_volume = reagent.volume - master = reagent - - return master -/* MOB/CARBON RELATED PROCS */ - -/** - * Triggers metabolizing for all the reagents in this holder - * - * Arguments: - * * mob/living/carbon/carbon - The mob to metabolize in, if null it uses [/datum/reagents/var/my_atom] - * * seconds_per_tick - the time in server seconds between proc calls (when performing normally it will be 2) - * * times_fired - the number of times the owner's life() tick has been called aka The number of times SSmobs has fired - * * can_overdose - Allows overdosing - * * liverless - Stops reagents that aren't set as [/datum/reagent/var/self_consuming] from metabolizing - */ -/datum/reagents/proc/metabolize(mob/living/carbon/owner, seconds_per_tick, times_fired, can_overdose = FALSE, liverless = FALSE, dead = FALSE) - var/list/cached_reagents = reagent_list - if(owner) - expose_temperature(owner.bodytemperature, 0.25) - - var/need_mob_update = FALSE - var/obj/item/organ/internal/stomach/belly = owner.get_organ_slot(ORGAN_SLOT_STOMACH) - var/obj/item/organ/internal/liver/liver = owner.get_organ_slot(ORGAN_SLOT_LIVER) - var/liver_tolerance - if(liver) - var/liver_health_percent = (liver.maxHealth - liver.damage) / liver.maxHealth - liver_tolerance = liver.toxTolerance * liver_health_percent - - for(var/datum/reagent/reagent as anything in cached_reagents) - // skip metabolizing effects for small units of toxins - if(istype(reagent, /datum/reagent/toxin) && liver && !dead) - var/datum/reagent/toxin/toxin = reagent - var/amount = round(toxin.volume, CHEMICAL_QUANTISATION_LEVEL) - if(belly) - amount += belly.reagents.get_reagent_amount(toxin.type) - - if(amount <= liver_tolerance) - owner.reagents.remove_reagent(toxin.type, toxin.metabolization_rate * owner.metabolism_efficiency * seconds_per_tick) - continue - - need_mob_update += metabolize_reagent(owner, reagent, seconds_per_tick, times_fired, can_overdose, liverless, dead) - - if(owner && need_mob_update) //some of the metabolized reagents had effects on the mob that requires some updates. - owner.updatehealth() - update_total() - -/* - * Metabolises a single reagent for a target owner carbon mob. See above. - * - * Arguments: - * * mob/living/carbon/owner - The mob to metabolize in, if null it uses [/datum/reagents/var/my_atom] - * * seconds_per_tick - the time in server seconds between proc calls (when performing normally it will be 2) - * * times_fired - the number of times the owner's life() tick has been called aka The number of times SSmobs has fired - * * can_overdose - Allows overdosing - * * liverless - Stops reagents that aren't set as [/datum/reagent/var/self_consuming] from metabolizing - */ -/datum/reagents/proc/metabolize_reagent(mob/living/carbon/owner, datum/reagent/reagent, seconds_per_tick, times_fired, can_overdose = FALSE, liverless = FALSE, dead = FALSE) - var/need_mob_update = FALSE - if(QDELETED(reagent.holder)) - return FALSE - - if(!owner) - owner = reagent.holder.my_atom - - if(owner && reagent && (!dead || (reagent.chemical_flags & REAGENT_DEAD_PROCESS))) - if(owner.reagent_check(reagent, seconds_per_tick, times_fired)) - return - if(liverless && !reagent.self_consuming) //need to be metabolized - return - if(!reagent.metabolizing) - reagent.metabolizing = TRUE - reagent.on_mob_metabolize(owner) - if(can_overdose) - if(reagent.overdose_threshold) - if(reagent.volume >= reagent.overdose_threshold && !reagent.overdosed) - reagent.overdosed = TRUE - need_mob_update += reagent.overdose_start(owner) - owner.log_message("has started overdosing on [reagent.name] at [reagent.volume] units.", LOG_GAME) - for(var/addiction in reagent.addiction_types) - owner.mind?.add_addiction_points(addiction, reagent.addiction_types[addiction] * REAGENTS_METABOLISM) - - if(reagent.overdosed) - need_mob_update += reagent.overdose_process(owner, seconds_per_tick, times_fired) - if(!dead) - need_mob_update += reagent.on_mob_life(owner, seconds_per_tick, times_fired) - if(dead) - need_mob_update += reagent.on_mob_dead(owner, seconds_per_tick) - return need_mob_update - -/// Signals that metabolization has stopped, triggering the end of trait-based effects -/datum/reagents/proc/end_metabolization(mob/living/carbon/C, keep_liverless = TRUE) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - if(QDELETED(reagent.holder)) - continue - if(keep_liverless && reagent.self_consuming) //Will keep working without a liver - continue - if(!C) - C = reagent.holder.my_atom - if(reagent.metabolizing) - reagent.metabolizing = FALSE - reagent.on_mob_end_metabolize(C) - -/*Processes the reagents in the holder and converts them, only called in a mob/living/carbon on addition -* -* Arguments: -* * reagent - the added reagent datum/object -* * added_volume - the volume of the reagent that was added (since it can already exist in a mob) -* * added_purity - the purity of the added volume -* returns the volume of the original, pure, reagent to add / keep -*/ -/datum/reagents/proc/process_mob_reagent_purity(datum/reagent/reagent, added_volume, added_purity) - if(!reagent) - stack_trace("Attempted to process a mob's reagent purity for a null reagent!") - return FALSE - if(added_purity == 1) - return added_volume - if(reagent.chemical_flags & REAGENT_DONOTSPLIT) - return added_volume - if(added_purity < 0) - stack_trace("Purity below 0 for chem on mob splitting: [reagent.type]!") - added_purity = 0 - - if((reagent.inverse_chem_val > added_purity) && (reagent.inverse_chem))//Turns all of a added reagent into the inverse chem - add_reagent(reagent.inverse_chem, added_volume, FALSE, added_purity = reagent.get_inverse_purity(reagent.creation_purity)) - var/datum/reagent/inverse_reagent = has_reagent(reagent.inverse_chem) - if(inverse_reagent.chemical_flags & REAGENT_SNEAKYNAME) - inverse_reagent.name = reagent.name//Negative effects are hidden - return FALSE //prevent addition - return added_volume - -///Processes any chems that have the REAGENT_IGNORE_STASIS bitflag ONLY -/datum/reagents/proc/handle_stasis_chems(mob/living/carbon/owner, seconds_per_tick, times_fired) - var/need_mob_update = FALSE - for(var/datum/reagent/reagent as anything in reagent_list) - if(!(reagent.chemical_flags & REAGENT_IGNORE_STASIS)) - continue - need_mob_update += metabolize_reagent(owner, reagent, seconds_per_tick, times_fired, can_overdose = TRUE) - if(owner && need_mob_update) //some of the metabolized reagents had effects on the mob that requires some updates. - owner.updatehealth() - update_total() - -/** - * Calls [/datum/reagent/proc/on_move] on every reagent in this holder - * - * Arguments: - * * atom/A - passed to on_move - * * Running - passed to on_move - */ -/datum/reagents/proc/conditional_update_move(atom/A, Running = 0) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - reagent.on_move(A, Running) - update_total() - -/** - * Calls [/datum/reagent/proc/on_update] on every reagent in this holder - * - * Arguments: - * * atom/A - passed to on_update - */ -/datum/reagents/proc/conditional_update(atom/A) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - reagent.on_update(A) - update_total() - -/// Handle any reactions possible in this holder -/// Also UPDATES the reaction list -/// High potential for infinite loopsa if you're editing this. -/datum/reagents/proc/handle_reactions() - if(QDELING(src)) - CRASH("[my_atom] is trying to handle reactions while being flagged for deletion. It presently has [length(reagent_list)] number of reactants in it. If that is over 0 then something terrible happened.") - - if(!length(reagent_list))//The liver is calling this method a lot, and is often empty of reagents so it's pointless busywork. It should be an easy fix, but I'm nervous about touching things beyond scope. Also since everything is so handle_reactions() trigger happy it might be a good idea having this check anyways. - return FALSE - - if(flags & NO_REACT) - if(is_reacting) - force_stop_reacting() //Force anything that is trying to to stop - return FALSE //Yup, no reactions here. No siree. - - if(is_reacting)//Prevent wasteful calculations - if(!(datum_flags & DF_ISPROCESSING))//If we're reacting - but not processing (i.e. we've transferred) - START_PROCESSING(SSreagents, src) - if(!(has_changed_state())) - return FALSE - -#ifndef UNIT_TESTS - // We assert that reagents will not need to react before the map is fully loaded - // This is the best I can do, sorry :( - if(!MC_RUNNING()) - return FALSE -#endif - - var/list/cached_reagents = reagent_list - var/list/cached_reactions = GLOB.chemical_reactions_list_reactant_index - var/datum/cached_my_atom = my_atom - LAZYNULL(failed_but_capable_reactions) - LAZYNULL(previous_reagent_list) - - . = 0 - var/list/possible_reactions = list() - for(var/datum/reagent/reagent as anything in cached_reagents) - LAZYADD(previous_reagent_list, reagent.type) - // I am SO sorry - reaction_loop: - for(var/datum/chemical_reaction/reaction as anything in cached_reactions[reagent.type]) // Was a big list but now it should be smaller since we filtered it with our reagent id - if(!reaction) - continue - - if(!reaction.required_reagents)//Don't bring in empty ones - continue - - var/granularity = 1 - if(!(reaction.reaction_flags & REACTION_INSTANT)) - granularity = CHEMICAL_VOLUME_MINIMUM - - var/list/cached_required_reagents = reaction.required_reagents - for(var/req_reagent in cached_required_reagents) - if(!has_reagent(req_reagent, (cached_required_reagents[req_reagent]*granularity))) - continue reaction_loop - - var/list/cached_required_catalysts = reaction.required_catalysts - for(var/_catalyst in cached_required_catalysts) - if(!has_reagent(_catalyst, (cached_required_catalysts[_catalyst]*granularity))) - continue reaction_loop - - if(cached_my_atom) - if(reaction.required_container) - if(reaction.required_container_accepts_subtypes && !istype(cached_my_atom, reaction.required_container)) - continue - else if(cached_my_atom.type != reaction.required_container) - continue - - if(isliving(cached_my_atom) && !reaction.mob_react) //Makes it so certain chemical reactions don't occur in mobs - continue - - else if(reaction.required_container) - continue - - if(reaction.required_other && !reaction.pre_reaction_other_checks(src)) - continue - - // At this point, we've passed all the hard restrictions and entered into just the soft ones - // So we're gonna start tracking reactions that COULD be completed on continue, instead of just exiting - var/required_temp = reaction.required_temp - var/is_cold_recipe = reaction.is_cold_recipe - if(required_temp != 0 && (is_cold_recipe && chem_temp > required_temp) || (!is_cold_recipe && chem_temp < required_temp)) - LAZYADD(failed_but_capable_reactions, reaction) - continue - - if(ph < reaction.optimal_ph_min - reaction.determin_ph_range && ph > reaction.optimal_ph_max + reaction.determin_ph_range) - LAZYADD(failed_but_capable_reactions, reaction) - continue - - possible_reactions += reaction - - //This is the point where we have all the possible reactions from a reagent/catalyst point of view, so we set up the reaction list - for(var/datum/chemical_reaction/selected_reaction as anything in possible_reactions) - if((selected_reaction.reaction_flags & REACTION_INSTANT) || (flags & REAGENT_HOLDER_INSTANT_REACT)) //If we have instant reactions, we process them here - instant_react(selected_reaction) - .++ - update_total() - continue - else - var/exists = FALSE - for(var/datum/equilibrium/E_exist as anything in reaction_list) - if(ispath(E_exist.reaction.type, selected_reaction.type)) //Don't add duplicates - exists = TRUE - - //Add it if it doesn't exist in the list - if(!exists) - is_reacting = TRUE//Prevent any on_reaction() procs from infinite looping - var/datum/equilibrium/equilibrium = new (selected_reaction, src) //Otherwise we add them to the processing list. - if(equilibrium.to_delete)//failed startup checks - qdel(equilibrium) - else - //Adding is done in new(), deletion is in qdel - equilibrium.reaction.on_reaction(src, equilibrium, equilibrium.multiplier) - equilibrium.react_timestep(1)//Get an initial step going so there's not a delay between setup and start - DO NOT ADD THIS TO equilibrium.NEW() - - if(LAZYLEN(reaction_list)) - is_reacting = TRUE //We've entered the reaction phase - this is set here so any reagent handling called in on_reaction() doesn't cause infinite loops - START_PROCESSING(SSreagents, src) //see process() to see how reactions are handled - else - is_reacting = FALSE - - if(.) - SEND_SIGNAL(src, COMSIG_REAGENTS_REACTED, .) - - TEST_ONLY_ASSERT(!. || MC_RUNNING(), "We reacted during subsystem init, that shouldn't be happening!") - -/* -* Main Reaction loop handler, Do not call this directly -* -* Checks to see if there's a reaction, then processes over the reaction list, removing them if flagged -* If any are ended, it displays the reaction message and removes it from the reaction list -* If the list is empty at the end it finishes reacting. -* Arguments: -* * seconds_per_tick - the time between each time step -*/ -/datum/reagents/process(seconds_per_tick) - if(!is_reacting) - force_stop_reacting() - stack_trace("[src] | [my_atom] was forced to stop reacting. This might be unintentional.") - //sum of output messages. - var/list/mix_message = list() - //Process over our reaction list - //See equilibrium.dm for mechanics - var/num_reactions = 0 - for(var/datum/equilibrium/equilibrium as anything in reaction_list) - //Continue reacting - equilibrium.react_timestep(seconds_per_tick) - num_reactions++ - //if it's been flagged to delete - if(equilibrium.to_delete) - var/temp_mix_message = end_reaction(equilibrium) - if(!text_in_list(temp_mix_message, mix_message)) - mix_message += temp_mix_message - continue - SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[equilibrium.reaction.type] total reaction steps") - if(num_reactions) - SEND_SIGNAL(src, COMSIG_REAGENTS_REACTION_STEP, num_reactions, seconds_per_tick) - - if(length(mix_message)) //This is only at the end - my_atom.audible_message(span_notice("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [mix_message.Join()]")) - - if(!LAZYLEN(reaction_list)) - finish_reacting() - else - update_total() - handle_reactions() - -/* -* This ends a single instance of an ongoing reaction -* -* Arguments: -* * E - the equilibrium that will be ended -* Returns: -* * mix_message - the associated mix message of a reaction -*/ -/datum/reagents/proc/end_reaction(datum/equilibrium/equilibrium) - equilibrium.reaction.reaction_finish(src, equilibrium, equilibrium.reacted_vol) - if(!equilibrium.holder || !equilibrium.reaction) //Somehow I'm getting empty equilibrium. This is here to handle them - LAZYREMOVE(reaction_list, equilibrium) - qdel(equilibrium) - stack_trace("The equilibrium datum currently processing in this reagents datum had a nulled holder or nulled reaction. src holder:[my_atom] || src type:[my_atom.type] ") //Shouldn't happen. Does happen - return - if(equilibrium.holder != src) //When called from Destroy() eqs are nulled in smoke. This is very strange. This is probably causing it to spam smoke because of the runtime interupting the removal. - stack_trace("The equilibrium datum currently processing in this reagents datum had a desynced holder to the ending reaction. src holder:[my_atom] | equilibrium holder:[equilibrium.holder.my_atom] || src type:[my_atom.type] | equilibrium holder:[equilibrium.holder.my_atom.type]") - LAZYREMOVE(reaction_list, equilibrium) - - var/reaction_message = equilibrium.reaction.mix_message - if(equilibrium.reaction.mix_sound) - playsound(get_turf(my_atom), equilibrium.reaction.mix_sound, 80, TRUE) - qdel(equilibrium) - update_total() - SEND_SIGNAL(src, COMSIG_REAGENTS_REACTED, .) - return reaction_message - -/* -* This stops the holder from processing at the end of a series of reactions (i.e. when all the equilibriums are completed) -* -* Also resets reaction variables to be null/empty/FALSE so that it can restart correctly in the future -*/ -/datum/reagents/proc/finish_reacting() - STOP_PROCESSING(SSreagents, src) - is_reacting = FALSE - //Cap off values - for(var/datum/reagent/reagent as anything in reagent_list) - reagent.volume = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING)//To prevent runaways. - LAZYNULL(previous_reagent_list) //reset it to 0 - because any change will be different now. - update_total() - if(!QDELING(src)) - handle_reactions() //Should be okay without. Each step checks. - -/* -* Force stops the current holder/reagents datum from reacting -* -* Calls end_reaction() for each equlilbrium datum in reaction_list and finish_reacting() -* Usually only called when a datum is transferred into a NO_REACT container -*/ -/datum/reagents/proc/force_stop_reacting() - var/list/mix_message = list() - for(var/datum/equilibrium/equilibrium as anything in reaction_list) - mix_message += end_reaction(equilibrium) - if(my_atom && length(mix_message)) - my_atom.audible_message(span_notice("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [mix_message.Join()]")) - finish_reacting() - -/* -* Force stops a specific reagent's associated reaction if it exists -* -* Mostly used if a reagent is being taken out by trans_id_to -* Might have some other applciations -* Returns TRUE if it stopped something, FALSE if it didn't -* Arguments: -* * reagent - the reagent PRODUCT that we're seeking reactions for, any and all found will be shut down -*/ -/datum/reagents/proc/force_stop_reagent_reacting(datum/reagent/reagent) - var/any_stopped = FALSE - var/list/mix_message = list() - for(var/datum/equilibrium/equilibrium as anything in reaction_list) - for(var/result in equilibrium.reaction.results) - if(result == reagent.type) - mix_message += end_reaction(equilibrium) - any_stopped = TRUE - if(length(mix_message)) - my_atom.audible_message(span_notice("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))][mix_message.Join()]")) - return any_stopped - -/* -* Transfers the reaction_list to a new reagents datum -* -* Arguments: -* * target - the datum/reagents that this src is being transferred into -*/ -/datum/reagents/proc/transfer_reactions(datum/reagents/target) - if(QDELETED(target)) - CRASH("transfer_reactions() had a [target] ([target.type]) passed to it when it was set to qdel, or it isn't a reagents datum.") - if(!reaction_list) - return - for(var/datum/equilibrium/reaction_source as anything in reaction_list) - var/exists = FALSE - for(var/datum/equilibrium/reaction_target as anything in target.reaction_list) //Don't add duplicates - if(reaction_source.reaction.type == reaction_target.reaction.type) - exists = TRUE - if(exists) - continue - if(!reaction_source.holder) - CRASH("reaction_source is missing a holder in transfer_reactions()!") - - var/datum/equilibrium/new_E = new (reaction_source.reaction, target)//addition to reaction_list is done in new() - if(new_E.to_delete)//failed startup checks - qdel(new_E) - - target.previous_reagent_list = LAZYLISTDUPLICATE(previous_reagent_list) - target.is_reacting = is_reacting - -///Checks to see if the reagents has a difference in reagents_list and previous_reagent_list (I.e. if there's a difference between the previous call and the last) -///Also checks to see if the saved reactions in failed_but_capable_reactions can start as a result of temp/pH change -/datum/reagents/proc/has_changed_state() - //Check if reagents are different - var/total_matching_reagents = 0 - for(var/reagent in previous_reagent_list) - if(has_reagent(reagent)) - total_matching_reagents++ - if(total_matching_reagents != reagent_list.len) - return TRUE - - //Check our last reactions - for(var/datum/chemical_reaction/reaction as anything in failed_but_capable_reactions) - if(reaction.is_cold_recipe) - if(reaction.required_temp < chem_temp) - return TRUE - else - if(reaction.required_temp < chem_temp) - return TRUE - if(((ph >= (reaction.optimal_ph_min - reaction.determin_ph_range)) && (ph <= (reaction.optimal_ph_max + reaction.determin_ph_range)))) - return TRUE - return FALSE - -///Old reaction mechanics, edited to work on one only -///This is changed from the old - purity of the reagents will affect yield -/datum/reagents/proc/instant_react(datum/chemical_reaction/selected_reaction) - var/list/cached_required_reagents = selected_reaction.required_reagents - var/list/cached_results = selected_reaction.results - var/datum/cached_my_atom = my_atom - var/multiplier = INFINITY - for(var/reagent in cached_required_reagents) - multiplier = min(multiplier, round(get_reagent_amount(reagent) / cached_required_reagents[reagent])) - - if(multiplier == 0)//Incase we're missing reagents - usually from on_reaction being called in an equlibrium when the results.len == 0 handlier catches a misflagged reaction - return FALSE - var/sum_purity = 0 - for(var/_reagent in cached_required_reagents)//this is not an object - var/datum/reagent/reagent = has_reagent(_reagent) - if (!reagent) - continue - sum_purity += reagent.purity - remove_reagent(_reagent, (multiplier * cached_required_reagents[_reagent]), safety = 1) - sum_purity /= cached_required_reagents.len - - for(var/product in selected_reaction.results) - multiplier = max(multiplier, 1) //this shouldn't happen ... - var/yield = (cached_results[product]*multiplier)*sum_purity - SSblackbox.record_feedback("tally", "chemical_reaction", yield, product) - add_reagent(product, yield, null, chem_temp, sum_purity) - - var/list/seen = viewers(4, get_turf(my_atom)) - var/iconhtml = icon2html(cached_my_atom, seen) - if(cached_my_atom) - if(!ismob(cached_my_atom)) // No bubbling mobs - if(selected_reaction.mix_sound) - playsound(get_turf(cached_my_atom), selected_reaction.mix_sound, 80, TRUE) - - my_atom.audible_message(span_notice("[iconhtml] [selected_reaction.mix_message]")) - - if(istype(cached_my_atom, /obj/item/slime_extract)) - var/obj/item/slime_extract/extract = my_atom - extract.Uses-- - if(extract.Uses <= 0) // give the notification that the slime core is dead - my_atom.visible_message(span_notice("[iconhtml] \The [my_atom]'s power is consumed in the reaction.")) - extract.name = "used slime extract" - extract.desc = "This extract has been used up." - - selected_reaction.on_reaction(src, null, multiplier) - -///Possibly remove - see if multiple instant reactions is okay (Though, this "sorts" reactions by temp decending) -///Presently unused -/datum/reagents/proc/get_priority_instant_reaction(list/possible_reactions) - if(!length(possible_reactions)) - return FALSE - var/datum/chemical_reaction/selected_reaction = possible_reactions[1] - //select the reaction with the most extreme temperature requirements - for(var/datum/chemical_reaction/competitor as anything in possible_reactions) - if(selected_reaction.is_cold_recipe) - if(competitor.required_temp <= selected_reaction.required_temp) - selected_reaction = competitor - else - if(competitor.required_temp >= selected_reaction.required_temp) - selected_reaction = competitor - return selected_reaction - -/// Updates [/datum/reagents/var/total_volume] -/datum/reagents/proc/update_total() - var/list/cached_reagents = reagent_list - . = 0 // This is a relatively hot proc. - var/total_ph = 0 // I know I know, I'm sorry - for(var/datum/reagent/reagent as anything in cached_reagents) - if((reagent.volume < 0.05) && !is_reacting) - del_reagent(reagent.type) - else if(reagent.volume <= CHEMICAL_VOLUME_MINIMUM)//For clarity - del_reagent(reagent.type) - else - . += reagent.volume - total_ph += (reagent.ph * reagent.volume) - total_volume = . - - if(!.) // No volume, default to the base - ph = CHEMICAL_NORMAL_PH - return . - //Keep limited // should really be defines - ph = clamp(total_ph/total_volume, 0, 14) - - -/** - * Applies the relevant expose_ proc for every reagent in this holder - * * [/datum/reagent/proc/expose_mob] - * * [/datum/reagent/proc/expose_turf] - * * [/datum/reagent/proc/expose_obj] - * - * Arguments - * - Atom/A: What mob/turf/object is being exposed to reagents? This is your reaction target. - * - Methods: What reaction type is the reagent itself going to call on the reaction target? Types are TOUCH, INGEST, VAPOR, PATCH, and INJECT. - * - Volume_modifier: What is the reagent volume multiplied by when exposed? Note that this is called on the volume of EVERY reagent in the base body, so factor in your Maximum_Volume if necessary! - * - Show_message: Whether to display anything to mobs when they are exposed. - */ -/datum/reagents/proc/expose(atom/A, methods = TOUCH, volume_modifier = 1, show_message = 1) - if(isnull(A)) - return null - - if(!reagent_list.len) - return null - - var/list/reagents = list() - for(var/datum/reagent/reagent as anything in reagent_list) - reagents[reagent] = reagent.volume * volume_modifier - - return A.expose_reagents(reagents, src, methods, volume_modifier, show_message) - -// Same as [/datum/reagents/proc/expose] but only for multiple reagents (through a list) -/datum/reagents/proc/expose_multiple(list/r_to_expose, atom/A, methods = TOUCH, volume_modifier = 1, show_message = 1) - if(isnull(A)) - return null - - var/list/cached_reagents = r_to_expose - if(!cached_reagents.len) - return null - - var/list/reagents = list() - for(var/datum/reagent/reagent as anything in cached_reagents) - reagents[reagent] = reagent.volume * volume_modifier - - return A.expose_reagents(reagents, src, methods, volume_modifier, show_message) - -/// Same as [/datum/reagents/proc/expose] but only for one reagent -/datum/reagents/proc/expose_single(datum/reagent/R, atom/A, methods = TOUCH, volume_modifier = 1, show_message = TRUE) - if(isnull(A)) - return null - - if(ispath(R)) - R = get_reagent(R) - if(isnull(R)) - return null - - // Yes, we need the parentheses. - return A.expose_reagents(list((R) = R.volume * volume_modifier), src, methods, volume_modifier, show_message) - -/// Is this holder full or not -/datum/reagents/proc/holder_full() - return total_volume >= maximum_volume - -/// Get the amount of this reagent -/datum/reagents/proc/get_reagent_amount(reagent, include_subtypes = FALSE) - var/list/cached_reagents = reagent_list - var/total_amount = 0 - for(var/datum/reagent/cached_reagent as anything in cached_reagents) - if((!include_subtypes && cached_reagent.type == reagent) || (include_subtypes && ispath(cached_reagent.type, reagent))) - total_amount += round(cached_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) - return total_amount - -/datum/reagents/proc/get_multiple_reagent_amounts(list/reagents) - var/list/cached_reagents = reagent_list - var/total_amount = 0 - for(var/datum/reagent/cached_reagent as anything in cached_reagents) - if(cached_reagent.type in reagents) - total_amount += round(cached_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) - return total_amount - -/// Get the purity of this reagent -/datum/reagents/proc/get_reagent_purity(reagent) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/cached_reagent as anything in cached_reagents) - if(cached_reagent.type == reagent) - return round(cached_reagent.purity, 0.01) - return 0 - -/// Directly set the purity of all contained reagents to a new value -/datum/reagents/proc/set_all_reagents_purity(new_purity = 0) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/cached_reagent as anything in cached_reagents) - cached_reagent.purity = max(0, new_purity) - -/// Get the average purity of all reagents (or all subtypes of provided typepath) -/datum/reagents/proc/get_average_purity(parent_type = null) - var/total_amount - var/weighted_purity - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - if(!isnull(parent_type) && !istype(reagent, parent_type)) - continue - total_amount += reagent.volume - weighted_purity += reagent.volume * reagent.purity - return weighted_purity / total_amount - -/// Get the average nutriment_factor of all consumable reagents -/datum/reagents/proc/get_average_nutriment_factor() - var/consumable_volume - var/weighted_nutriment_factor - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - if(istype(reagent, /datum/reagent/consumable)) - var/datum/reagent/consumable/consumable_reagent = reagent - consumable_volume += consumable_reagent.volume - weighted_nutriment_factor += consumable_reagent.volume * consumable_reagent.nutriment_factor - return weighted_nutriment_factor / consumable_volume - -/// Get a comma separated string of every reagent name in this holder. UNUSED -/datum/reagents/proc/get_reagent_names() - var/list/names = list() - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - names += reagent.name - - return jointext(names, ",") - -/// helper function to preserve data across reactions (needed for xenoarch) -/datum/reagents/proc/get_data(reagent_id) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - if(reagent.type == reagent_id) - return reagent.data - -/// helper function to preserve data across reactions (needed for xenoarch) -/datum/reagents/proc/set_data(reagent_id, new_data) - var/list/cached_reagents = reagent_list - for(var/datum/reagent/reagent as anything in cached_reagents) - if(reagent.type == reagent_id) - reagent.data = new_data - -/// Shallow copies (deep copy of viruses) data from the provided reagent into our copy of that reagent -/datum/reagents/proc/copy_data(datum/reagent/current_reagent) - if(!current_reagent || !current_reagent.data) - return null - if(!istype(current_reagent.data, /list)) - return current_reagent.data - - var/list/trans_data = current_reagent.data.Copy() - - // We do this so that introducing a virus to a blood sample - // doesn't automagically infect all other blood samples from - // the same donor. - // - // Technically we should probably copy all data lists, but - // that could possibly eat up a lot of memory needlessly - // if most data lists are read-only. - if(trans_data["viruses"]) - var/list/v = trans_data["viruses"] - trans_data["viruses"] = v.Copy() - - return trans_data - -/// Get a reference to the reagent if it exists -/datum/reagents/proc/get_reagent(type) - var/list/cached_reagents = reagent_list - . = locate(type) in cached_reagents - -/** - * Returns what this holder's reagents taste like - * - * Arguments: - * * mob/living/taster - who is doing the tasting. Some mobs can pick up specific flavours. - * * minimum_percent - the lower the minimum percent, the more sensitive the message is. - */ -/datum/reagents/proc/generate_taste_message(mob/living/taster, minimum_percent) - var/list/out = list() - var/list/tastes = list() //descriptor = strength - if(minimum_percent <= 100) - for(var/datum/reagent/reagent as anything in reagent_list) - if(!reagent.taste_mult) - continue - - var/list/taste_data = reagent.get_taste_description(taster) - for(var/taste in taste_data) - if(taste in tastes) - tastes[taste] += taste_data[taste] * reagent.volume * reagent.taste_mult - else - tastes[taste] = taste_data[taste] * reagent.volume * reagent.taste_mult - //deal with percentages - // TODO it would be great if we could sort these from strong to weak - var/total_taste = counterlist_sum(tastes) - if(total_taste > 0) - for(var/taste_desc in tastes) - var/percent = tastes[taste_desc]/total_taste * 100 - if(percent < minimum_percent) - continue - var/intensity_desc = "a hint of" - if(percent > minimum_percent * 2 || percent == 100) - intensity_desc = "" - else if(percent > minimum_percent * 3) - intensity_desc = "the strong flavor of" - if(intensity_desc != "") - out += "[intensity_desc] [taste_desc]" - else - out += "[taste_desc]" - - return english_list(out, "something indescribable") - - -/// Returns the total heat capacity for all of the reagents currently in this holder. -/datum/reagents/proc/heat_capacity() - . = 0 - var/list/cached_reagents = reagent_list //cache reagents - for(var/datum/reagent/reagent in cached_reagents) - . += reagent.specific_heat * reagent.volume - -/** Adjusts the thermal energy of the reagents in this holder by an amount. - * - * Arguments: - * - delta_energy: The amount to change the thermal energy by. - * - min_temp: The minimum temperature that can be reached. - * - max_temp: The maximum temperature that can be reached. - */ -/datum/reagents/proc/adjust_thermal_energy(delta_energy, min_temp = 2.7, max_temp = 1000) - var/heat_capacity = heat_capacity() - if(!heat_capacity) - return // no div/0 please - set_temperature(clamp(chem_temp + (delta_energy / heat_capacity), min_temp, max_temp)) - -/// Applies heat to this holder -/datum/reagents/proc/expose_temperature(temperature, coeff=0.02) - if(istype(my_atom,/obj/item/reagent_containers)) - var/obj/item/reagent_containers/RCs = my_atom - if(RCs.reagent_flags & NO_REACT) //stasis holders IE cryobeaker - return - var/temp_delta = (temperature - chem_temp) * coeff - if(temp_delta > 0) - chem_temp = min(chem_temp + max(temp_delta, 1), temperature) - else - chem_temp = max(chem_temp + min(temp_delta, -1), temperature) - set_temperature(round(chem_temp)) - handle_reactions() - -/** Sets the temperature of this reagent container to a new value. - * - * Handles setter signals. - * - * Arguments: - * - _temperature: The new temperature value. - */ -/datum/reagents/proc/set_temperature(_temperature) - if(_temperature == chem_temp) - return - - . = chem_temp - chem_temp = clamp(_temperature, 0, CHEMICAL_MAXIMUM_TEMPERATURE) - SEND_SIGNAL(src, COMSIG_REAGENTS_TEMP_CHANGE, _temperature, .) - -/* -* Adjusts the base pH of all of the reagents in a beaker -* -* - moves it towards acidic -* + moves it towards basic -* Arguments: -* * value - How much to adjust the base pH by -*/ -/datum/reagents/proc/adjust_all_reagents_ph(value, lower_limit = 0, upper_limit = 14) - for(var/datum/reagent/reagent as anything in reagent_list) - reagent.ph = clamp(reagent.ph + value, lower_limit, upper_limit) - -/* -* Adjusts the base pH of all of the listed types -* -* - moves it towards acidic -* + moves it towards basic -* Arguments: -* * input_reagents_list - list of reagent objects to adjust -* * value - How much to adjust the base pH by -*/ -/datum/reagents/proc/adjust_specific_reagent_list_ph(list/input_reagents_list, value, lower_limit = 0, upper_limit = 14) - for(var/datum/reagent/reagent as anything in input_reagents_list) - if(!reagent) //We can call this with missing reagents. - continue - reagent.ph = clamp(reagent.ph + value, lower_limit, upper_limit) - -/* -* Adjusts the base pH of a specific type -* -* - moves it towards acidic -* + moves it towards basic -* Arguments: -* * input_reagent - type path of the reagent -* * value - How much to adjust the base pH by -* * lower_limit - how low the pH can go -* * upper_limit - how high the pH can go -*/ -/datum/reagents/proc/adjust_specific_reagent_ph(input_reagent, value, lower_limit = 0, upper_limit = 14) - var/datum/reagent/reagent = get_reagent(input_reagent) - if(!reagent) //We can call this with missing reagents. - return FALSE - reagent.ph = clamp(reagent.ph + value, lower_limit, upper_limit) - -/** - * Outputs a log-friendly list of reagents based on an external reagent list. - * - * Arguments: - * * external_list - Assoc list of (reagent_type) = list(REAGENT_TRANSFER_AMOUNT = amounts, REAGENT_PURITY = purity) - */ -/datum/reagents/proc/get_external_reagent_log_string(external_list) - if(!length(external_list)) - return "no reagents" - - var/list/data = list() - - for(var/reagent_type in external_list) - var/list/qualities = external_list[reagent_type] - data += "[reagent_type] ([round(qualities[REAGENT_TRANSFER_AMOUNT], 0.1)]u, [qualities[REAGENT_PURITY]] purity)" - - return english_list(data) - -/** - * Outputs a log-friendly list of reagents based on the internal reagent_list. - * - * Arguments: - * * external_list - Assoc list of (reagent_type) = list(REAGENT_TRANSFER_AMOUNT = amounts, REAGENT_PURITY = purity) - */ -/datum/reagents/proc/get_reagent_log_string() - if(!length(reagent_list)) - return "no reagents" - - var/list/data = list() - - for(var/datum/reagent/reagent as anything in reagent_list) - data += "[reagent.type] ([round(reagent.volume, 0.1)]u, [reagent.purity] purity)" - - return english_list(data) - -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////UI / REAGENTS LOOKUP CODE///////////////////////////// -///////////////////////////////////////////////////////////////////////////////// - - -/datum/reagents/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "Reagents", "Reaction search") - ui.status = UI_INTERACTIVE //How do I prevent a UI from autoclosing if not in LoS - ui_tags_selected = NONE //Resync with gui on open (gui expects no flags) - ui_reagent_id = null - ui_reaction_id = null - ui.open() - - -/datum/reagents/ui_status(mob/user) - return UI_INTERACTIVE //please advise - -/datum/reagents/ui_state(mob/user) - return GLOB.physical_state - -/datum/reagents/proc/generate_possible_reactions() - var/list/cached_reagents = reagent_list - if(!cached_reagents) - return null - var/list/cached_reactions = list() - var/list/possible_reactions = list() - if(!length(cached_reagents)) - return null - cached_reactions = GLOB.chemical_reactions_list_reactant_index - for(var/_reagent in cached_reagents) - var/datum/reagent/reagent = _reagent - for(var/_reaction in cached_reactions[reagent.type]) // Was a big list but now it should be smaller since we filtered it with our reagent id - var/datum/chemical_reaction/reaction = _reaction - if(!_reaction) - continue - if(!reaction.required_reagents)//Don't bring in empty ones - continue - var/list/cached_required_reagents = reaction.required_reagents - var/total_matching_reagents = 0 - for(var/req_reagent in cached_required_reagents) - if(!has_reagent(req_reagent, (cached_required_reagents[req_reagent]*0.01))) - continue - total_matching_reagents++ - if(total_matching_reagents >= reagent_list.len) - possible_reactions += reaction - return possible_reactions - -///Generates a (rough) rate vs temperature graph profile -/datum/reagents/proc/generate_thermodynamic_profile(datum/chemical_reaction/reaction) - var/list/coords = list() - var/x_temp - var/increment - if(reaction.is_cold_recipe) - coords += list(list(0, 0)) - coords += list(list(reaction.required_temp, 0)) - x_temp = reaction.required_temp - increment = (reaction.optimal_temp - reaction.required_temp)/10 - while(x_temp < reaction.optimal_temp) - var/y = (((x_temp - reaction.required_temp)**reaction.temp_exponent_factor)/((reaction.optimal_temp - reaction.required_temp)**reaction.temp_exponent_factor)) - coords += list(list(x_temp, y)) - x_temp += increment - else - coords += list(list(reaction.required_temp, 0)) - x_temp = reaction.required_temp - increment = (reaction.required_temp - reaction.optimal_temp)/10 - while(x_temp > reaction.optimal_temp) - var/y = (((x_temp - reaction.required_temp)**reaction.temp_exponent_factor)/((reaction.optimal_temp - reaction.required_temp)**reaction.temp_exponent_factor)) - coords += list(list(x_temp, y)) - x_temp -= increment - - coords += list(list(reaction.optimal_temp, 1)) - if(reaction.overheat_temp == NO_OVERHEAT) - if(reaction.is_cold_recipe) - coords += list(list(reaction.optimal_temp+10, 1)) - else - coords += list(list(reaction.optimal_temp-10, 1)) - return coords - coords += list(list(reaction.overheat_temp, 1)) - coords += list(list(reaction.overheat_temp, 0)) - return coords - -/datum/reagents/proc/generate_explosive_profile(datum/chemical_reaction/reaction) - if(reaction.overheat_temp == NO_OVERHEAT) - return null - var/list/coords = list() - coords += list(list(reaction.overheat_temp, 0)) - coords += list(list(reaction.overheat_temp, 1)) - if(reaction.is_cold_recipe) - coords += list(list(reaction.overheat_temp-50, 1)) - coords += list(list(reaction.overheat_temp-50, 0)) - else - coords += list(list(reaction.overheat_temp+50, 1)) - coords += list(list(reaction.overheat_temp+50, 0)) - return coords - - -///Returns a string descriptor of a reactions themic_constant -/datum/reagents/proc/determine_reaction_thermics(datum/chemical_reaction/reaction) - var/thermic = reaction.thermic_constant - if(reaction.reaction_flags & REACTION_HEAT_ARBITARY) - thermic *= 100 //Because arbitary is a lower scale - switch(thermic) - if(-INFINITY to -1500) - return "Overwhelmingly endothermic" - if(-1500 to -1000) - return "Extremely endothermic" - if(-1000 to -500) - return "Strongly endothermic" - if(-500 to -200) - return "Moderately endothermic" - if(-200 to -50) - return "Endothermic" - if(-50 to 0) - return "Weakly endothermic" - if(0) - return "" - if(0 to 50) - return "Weakly Exothermic" - if(50 to 200) - return "Exothermic" - if(200 to 500) - return "Moderately exothermic" - if(500 to 1000) - return "Strongly exothermic" - if(1000 to 1500) - return "Extremely exothermic" - if(1500 to INFINITY) - return "Overwhelmingly exothermic" - -/datum/reagents/proc/parse_addictions(datum/reagent/reagent) - var/addict_text = list() - for(var/entry in reagent.addiction_types) - var/datum/addiction/ref = SSaddiction.all_addictions[entry] - switch(reagent.addiction_types[entry]) - if(-INFINITY to 0) - continue - if(0 to 5) - addict_text += "Weak [ref.name]" - if(5 to 10) - addict_text += "[ref.name]" - if(10 to 20) - addict_text += "Strong [ref.name]" - if(20 to INFINITY) - addict_text += "Potent [ref.name]" - return addict_text - -/datum/reagents/ui_data(mob/user) - var/data = list() - data["selectedBitflags"] = ui_tags_selected - data["currentReagents"] = previous_reagent_list //This keeps the string of reagents that's updated when handle_reactions() is called - data["beakerSync"] = ui_beaker_sync - data["linkedBeaker"] = my_atom.name //To solidify the fact that the UI is linked to a beaker - not a machine. - - //First we check to see if reactions are synced with the beaker - if(ui_beaker_sync) - if(reaction_list)//But we don't want to null the previously displayed if there are none - //makes sure we're within bounds - if(ui_reaction_index > reaction_list.len) - ui_reaction_index = reaction_list.len - ui_reaction_id = reaction_list[ui_reaction_index].reaction.type - - //reagent lookup data - if(ui_reagent_id) - var/datum/reagent/reagent = find_reagent_object_from_type(ui_reagent_id) - if(!reagent) - to_chat(user, "Could not find reagent!") - ui_reagent_id = null - else - data["reagent_mode_reagent"] = list("name" = reagent.name, "id" = reagent.type, "desc" = reagent.description, "reagentCol" = reagent.color, "pH" = reagent.ph, "pHCol" = convert_ph_to_readable_color(reagent.ph), "metaRate" = (reagent.metabolization_rate/2), "OD" = reagent.overdose_threshold) - data["reagent_mode_reagent"]["addictions"] = list() - data["reagent_mode_reagent"]["addictions"] = parse_addictions(reagent) - - var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem] - if(inverse_reagent) - data["reagent_mode_reagent"] += list("inverseReagent" = inverse_reagent.name, "inverseId" = inverse_reagent.type) - - if(reagent.chemical_flags & REAGENT_DEAD_PROCESS) - data["reagent_mode_reagent"] += list("deadProcess" = TRUE) - else - data["reagent_mode_reagent"] = null - - //reaction lookup data - if (ui_reaction_id) - - var/datum/chemical_reaction/reaction = get_chemical_reaction(ui_reaction_id) - if(!reaction) - to_chat(user, "Could not find reaction!") - ui_reaction_id = null - return data - //Required holder - var/container_name - if(reaction.required_container) - var/list/names = splittext("[reaction.required_container]", "/") - container_name = "[names[names.len-1]] [names[names.len]]" - container_name = replacetext(container_name, "_", " ") - - //Next, find the product - var/has_product = TRUE - //If we have no product, use the typepath to create a name for it - if(!length(reaction.results)) - has_product = FALSE - var/list/names = splittext("[reaction.type]", "/") - var/product_name = names[names.len] - data["reagent_mode_recipe"] = list("name" = product_name, "id" = reaction.type, "hasProduct" = has_product, "reagentCol" = "#FFFFFF", "thermodynamics" = generate_thermodynamic_profile(reaction), "explosive" = generate_explosive_profile(reaction), "lowerpH" = reaction.optimal_ph_min, "upperpH" = reaction.optimal_ph_max, "thermics" = determine_reaction_thermics(reaction), "thermoUpper" = reaction.rate_up_lim, "minPurity" = reaction.purity_min, "inversePurity" = "N/A", "tempMin" = reaction.required_temp, "explodeTemp" = reaction.overheat_temp, "reqContainer" = container_name, "subReactLen" = 1, "subReactIndex" = 1) - - //If we do have a product then we find it - else - //Find out if we have multiple reactions for the same product - var/datum/reagent/primary_reagent = find_reagent_object_from_type(reaction.results[1])//We use the first product - though it might be worth changing this - //If we're syncing from the beaker - var/list/sub_reactions = list() - if(ui_beaker_sync && reaction_list) - for(var/_ongoing_eq in reaction_list) - var/datum/equilibrium/ongoing_eq = _ongoing_eq - var/ongoing_r = ongoing_eq.reaction - sub_reactions += ongoing_r - else - sub_reactions = get_recipe_from_reagent_product(primary_reagent.type) - var/sub_reaction_length = length(sub_reactions) - var/i = 1 - for(var/datum/chemical_reaction/sub_reaction in sub_reactions) - if(sub_reaction.type == reaction.type) - ui_reaction_index = i //update our index - break - i += 1 - data["reagent_mode_recipe"] = list("name" = primary_reagent.name, "id" = reaction.type, "hasProduct" = has_product, "reagentCol" = primary_reagent.color, "thermodynamics" = generate_thermodynamic_profile(reaction), "explosive" = generate_explosive_profile(reaction), "lowerpH" = reaction.optimal_ph_min, "upperpH" = reaction.optimal_ph_max, "thermics" = determine_reaction_thermics(reaction), "thermoUpper" = reaction.rate_up_lim, "minPurity" = reaction.purity_min, "inversePurity" = primary_reagent.inverse_chem_val, "tempMin" = reaction.required_temp, "explodeTemp" = reaction.overheat_temp, "reqContainer" = container_name, "subReactLen" = sub_reaction_length, "subReactIndex" = ui_reaction_index) - - //Results sweep - var/has_reagent = "default" - for(var/_reagent in reaction.results) - var/datum/reagent/reagent = find_reagent_object_from_type(_reagent) - if(has_reagent(_reagent)) - has_reagent = "green" - data["reagent_mode_recipe"]["products"] += list(list("name" = reagent.name, "id" = reagent.type, "ratio" = reaction.results[reagent.type], "hasReagentCol" = has_reagent)) - - //Reactant sweep - for(var/_reagent in reaction.required_reagents) - var/datum/reagent/reagent = find_reagent_object_from_type(_reagent) - var/color_r = "default" //If the holder is missing the reagent, it's displayed in orange - if(has_reagent(reagent.type)) - color_r = "green" //It's green if it's present - var/tooltip - var/tooltip_bool = FALSE - var/list/sub_reactions = get_recipe_from_reagent_product(reagent.type) - //Get sub reaction possibilities, but ignore ones that need a specific holder atom - var/sub_index = 0 - for(var/datum/chemical_reaction/sub_reaction as anything in sub_reactions) - if(sub_reaction.required_container)//So we don't have slime reactions confusing things - sub_index++ - continue - sub_index++ - break - if(sub_index) - var/datum/chemical_reaction/sub_reaction = sub_reactions[sub_index] - //Subreactions sweep (if any) - for(var/_sub_reagent in sub_reaction.required_reagents) - var/datum/reagent/sub_reagent = find_reagent_object_from_type(_sub_reagent) - tooltip += "[sub_reaction.required_reagents[_sub_reagent]]u [sub_reagent.name]\n" //I forgot the better way of doing this - fix this after this works - tooltip_bool = TRUE - data["reagent_mode_recipe"]["reactants"] += list(list("name" = reagent.name, "id" = reagent.type, "ratio" = reaction.required_reagents[reagent.type], "color" = color_r, "tooltipBool" = tooltip_bool, "tooltip" = tooltip)) - - //Catalyst sweep - for(var/_reagent in reaction.required_catalysts) - var/datum/reagent/reagent = find_reagent_object_from_type(_reagent) - var/color_r = "default" - if(has_reagent(reagent.type)) - color_r = "green" - var/tooltip - var/tooltip_bool = FALSE - var/list/sub_reactions = get_recipe_from_reagent_product(reagent.type) - if(length(sub_reactions)) - var/datum/chemical_reaction/sub_reaction = sub_reactions[1] - //Subreactions sweep (if any) - for(var/_sub_reagent in sub_reaction.required_reagents) - var/datum/reagent/sub_reagent = find_reagent_object_from_type(_sub_reagent) - tooltip += "[sub_reaction.required_reagents[_sub_reagent]]u [sub_reagent.name]\n" //I forgot the better way of doing this - fix this after this works - tooltip_bool = TRUE - data["reagent_mode_recipe"]["catalysts"] += list(list("name" = reagent.name, "id" = reagent.type, "ratio" = reaction.required_catalysts[reagent.type], "color" = color_r, "tooltipBool" = tooltip_bool, "tooltip" = tooltip)) - data["reagent_mode_recipe"]["isColdRecipe"] = reaction.is_cold_recipe - else - data["reagent_mode_recipe"] = null - - return data - -/datum/reagents/ui_static_data(mob/user) - var/data = list() - //Use GLOB list - saves processing - data["master_reaction_list"] = GLOB.chemical_reactions_results_lookup_list - data["bitflags"] = list() - data["bitflags"]["BRUTE"] = REACTION_TAG_BRUTE - data["bitflags"]["BURN"] = REACTION_TAG_BURN - data["bitflags"]["TOXIN"] = REACTION_TAG_TOXIN - data["bitflags"]["OXY"] = REACTION_TAG_OXY - data["bitflags"]["CLONE"] = REACTION_TAG_CLONE - data["bitflags"]["HEALING"] = REACTION_TAG_HEALING - data["bitflags"]["DAMAGING"] = REACTION_TAG_DAMAGING - data["bitflags"]["EXPLOSIVE"] = REACTION_TAG_EXPLOSIVE - data["bitflags"]["OTHER"] = REACTION_TAG_OTHER - data["bitflags"]["DANGEROUS"] = REACTION_TAG_DANGEROUS - data["bitflags"]["EASY"] = REACTION_TAG_EASY - data["bitflags"]["MODERATE"] = REACTION_TAG_MODERATE - data["bitflags"]["HARD"] = REACTION_TAG_HARD - data["bitflags"]["ORGAN"] = REACTION_TAG_ORGAN - data["bitflags"]["DRINK"] = REACTION_TAG_DRINK - data["bitflags"]["FOOD"] = REACTION_TAG_FOOD - data["bitflags"]["SLIME"] = REACTION_TAG_SLIME - data["bitflags"]["DRUG"] = REACTION_TAG_DRUG - data["bitflags"]["UNIQUE"] = REACTION_TAG_UNIQUE - data["bitflags"]["CHEMICAL"] = REACTION_TAG_CHEMICAL - data["bitflags"]["PLANT"] = REACTION_TAG_PLANT - data["bitflags"]["COMPETITIVE"] = REACTION_TAG_COMPETITIVE - - return data - -/* Returns a reaction type by index from an input reagent type -* i.e. the input reagent's associated reactions are found, and the index determines which one to return -* If the index is out of range, it is set to 1 -*/ -/datum/reagents/proc/get_reaction_from_indexed_possibilities(path, index = null) - if(index) - ui_reaction_index = index - var/list/sub_reactions = get_recipe_from_reagent_product(path) - if(!length(sub_reactions)) - to_chat(usr, "There is no recipe associated with this product.") - return FALSE - if(ui_reaction_index > length(sub_reactions)) - ui_reaction_index = 1 - var/datum/chemical_reaction/reaction = sub_reactions[ui_reaction_index] - return reaction.type - -/datum/reagents/ui_act(action, params) - . = ..() - if(.) - return - switch(action) - if("find_reagent_reaction") - ui_reaction_id = get_reaction_from_indexed_possibilities(text2path(params["id"])) - return TRUE - if("reagent_click") - ui_reagent_id = text2path(params["id"]) - return TRUE - if("recipe_click") - ui_reaction_id = text2path(params["id"]) - return TRUE - if("search_reagents") - var/input_reagent = tgui_input_list(usr, "Select reagent", "Reagent", GLOB.chemical_name_list) - input_reagent = get_reagent_type_from_product_string(input_reagent) //from string to type - var/datum/reagent/reagent = find_reagent_object_from_type(input_reagent) - if(!reagent) - to_chat(usr, "Could not find reagent!") - return FALSE - ui_reagent_id = reagent.type - return TRUE - if("search_recipe") - var/input_reagent = (input("Enter the name of product reagent", "Input") as text|null) - input_reagent = get_reagent_type_from_product_string(input_reagent) //from string to type - var/datum/reagent/reagent = find_reagent_object_from_type(input_reagent) - if(!reagent) - to_chat(usr, "Could not find product reagent!") - return - ui_reaction_id = get_reaction_from_indexed_possibilities(reagent.type) - return TRUE - if("increment_index") - ui_reaction_index += 1 - if(!ui_beaker_sync || !reaction_list) - ui_reaction_id = get_reaction_from_indexed_possibilities(get_reagent_type_from_product_string(params["id"])) - return TRUE - if("reduce_index") - if(ui_reaction_index == 1) - return - ui_reaction_index -= 1 - if(!ui_beaker_sync || !reaction_list) - ui_reaction_id = get_reaction_from_indexed_possibilities(get_reagent_type_from_product_string(params["id"])) - return TRUE - if("beaker_sync") - ui_beaker_sync = !ui_beaker_sync - return TRUE - if("toggle_tag_brute") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_BRUTE - return TRUE - if("toggle_tag_burn") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_BURN - return TRUE - if("toggle_tag_toxin") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_TOXIN - return TRUE - if("toggle_tag_oxy") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_OXY - return TRUE - if("toggle_tag_clone") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_CLONE - return TRUE - if("toggle_tag_healing") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_HEALING - return TRUE - if("toggle_tag_damaging") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_DAMAGING - return TRUE - if("toggle_tag_explosive") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_EXPLOSIVE - return TRUE - if("toggle_tag_other") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_OTHER - return TRUE - if("toggle_tag_easy") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_EASY - return TRUE - if("toggle_tag_moderate") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_MODERATE - return TRUE - if("toggle_tag_hard") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_HARD - return TRUE - if("toggle_tag_organ") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_ORGAN - return TRUE - if("toggle_tag_drink") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_DRINK - return TRUE - if("toggle_tag_food") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_FOOD - return TRUE - if("toggle_tag_dangerous") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_DANGEROUS - return TRUE - if("toggle_tag_slime") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_SLIME - return TRUE - if("toggle_tag_drug") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_DRUG - return TRUE - if("toggle_tag_unique") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_UNIQUE - return TRUE - if("toggle_tag_chemical") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_CHEMICAL - return TRUE - if("toggle_tag_plant") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_PLANT - return TRUE - if("toggle_tag_competitive") - ui_tags_selected = ui_tags_selected ^ REACTION_TAG_COMPETITIVE - return TRUE - if("update_ui") - return TRUE - - -/////////////////////////////////////////////////////////////////////////////////// - - -/** - * Convenience proc to create a reagents holder for an atom - * - * Arguments: - * * max_vol - maximum volume of holder - * * flags - flags to pass to the holder - */ -/atom/proc/create_reagents(max_vol, flags) - if(reagents) - qdel(reagents) - reagents = new /datum/reagents(max_vol, flags) - reagents.my_atom = src - -/atom/movable/chem_holder - name = "This atom exists to hold chems. If you can see this, make an issue report" - desc = "God this is stupid" - -#undef REAGENT_TRANSFER_AMOUNT -#undef REAGENT_PURITY - -#undef REAGENTS_UI_MODE_LOOKUP -#undef REAGENTS_UI_MODE_REAGENT -#undef REAGENTS_UI_MODE_RECIPE diff --git a/code/modules/reagents/chemistry/holder/holder.dm b/code/modules/reagents/chemistry/holder/holder.dm new file mode 100644 index 0000000000000..a4e149a2bc862 --- /dev/null +++ b/code/modules/reagents/chemistry/holder/holder.dm @@ -0,0 +1,858 @@ +#define REAGENT_TRANSFER_AMOUNT "amount" +#define REAGENT_PURITY "purity" + +///////////////////////////////Main reagents code///////////////////////////////////////////// + +/// Holder for a bunch of [/datum/reagent] +/datum/reagents + /// The reagents being held + var/list/datum/reagent/reagent_list = new/list() + /// Current volume of all the reagents + var/total_volume = 0 + /// Max volume of this holder + var/maximum_volume = 100 + /// The atom this holder is attached to + var/atom/my_atom = null + /// Current temp of the holder volume + var/chem_temp = 150 + ///pH of the whole system + var/ph = CHEMICAL_NORMAL_PH + /// various flags, see code\__DEFINES\reagents.dm + var/flags + ///list of reactions currently on going, this is a lazylist for optimisation + var/list/datum/equilibrium/reaction_list + ///cached list of reagents typepaths (not object references), this is a lazylist for optimisation + var/list/datum/reagent/previous_reagent_list + ///If a reaction fails due to temperature or pH, this tracks the required temperature or pH for it to be enabled. + var/list/failed_but_capable_reactions + ///Hard check to see if the reagents is presently reacting + var/is_reacting = FALSE + ///UI lookup stuff + ///Keeps the id of the reaction displayed in the ui + var/ui_reaction_id = null + ///Keeps the id of the reagent displayed in the ui + var/ui_reagent_id = null + ///The bitflag of the currently selected tags in the ui + var/ui_tags_selected = NONE + ///What index we're at if we have multiple reactions for a reagent product + var/ui_reaction_index = 1 + ///If we're syncing with the beaker - so return reactions that are actively happening + var/ui_beaker_sync = FALSE + +/datum/reagents/New(maximum = 100, new_flags = 0) + maximum_volume = maximum + flags = new_flags + +/datum/reagents/Destroy() + //We're about to delete all reagents, so lets cleanup + for(var/datum/reagent/reagent as anything in reagent_list) + qdel(reagent) + reagent_list = null + if(is_reacting) //If false, reaction list should be cleaned up + force_stop_reacting() + QDEL_LAZYLIST(reaction_list) + previous_reagent_list = null + if(my_atom && my_atom.reagents == src) + my_atom.reagents = null + my_atom = null + return ..() + + +/** + * Convenience proc to create a reagents holder for an atom + * + * Arguments: + * * max_vol - maximum volume of holder + * * flags - flags to pass to the holder + */ +/atom/proc/create_reagents(max_vol, flags) + if(reagents) + qdel(reagents) + reagents = new /datum/reagents(max_vol, flags) + reagents.my_atom = src + +/** + * Adds a reagent to this holder + * + * Arguments: + * * reagent - The reagent id to add + * * amount - Amount to add + * * list/data - Any reagent data for this reagent, used for transferring data with reagents + * * reagtemp - Temperature of this reagent, will be equalized + * * no_react - prevents reactions being triggered by this addition + * * added_purity - override to force a purity when added + * * added_ph - override to force a pH when added + * * override_base_ph - ingore the present pH of the reagent, and instead use the default (i.e. if buffers/reactions alter it) + * * ignore splitting - Don't call the process that handles reagent spliting in a mob (impure/inverse) - generally leave this false unless you care about REAGENTS_DONOTSPLIT flags (see reagent defines) + */ +/datum/reagents/proc/add_reagent( + datum/reagent/reagent_type, + amount, + list/data = null, + reagtemp = DEFAULT_REAGENT_TEMPERATURE, + added_purity = null, + added_ph, + no_react = FALSE, + override_base_ph = FALSE, + ignore_splitting = FALSE +) + if(!ispath(reagent_type)) + stack_trace("invalid reagent passed to add reagent [reagent_type]") + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to add reagent [amount] [reagent_type]") + return FALSE + + if(SEND_SIGNAL(src, COMSIG_REAGENTS_PRE_ADD_REAGENT, reagent_type, amount, reagtemp, data, no_react) & COMPONENT_CANCEL_REAGENT_ADD) + return FALSE + + var/datum/reagent/glob_reagent = GLOB.chemical_reagents_list[reagent_type] + if(!glob_reagent) + stack_trace("[my_atom] attempted to add a reagent called '[reagent_type]' which doesn't exist. ([usr])") + return FALSE + if(isnull(added_purity)) //Because purity additions can be 0 + added_purity = glob_reagent.creation_purity //Usually 1 + if(!added_ph) + added_ph = glob_reagent.ph + + //Split up the reagent if it's in a mob + var/has_split = FALSE + if(!ignore_splitting && (flags & REAGENT_HOLDER_ALIVE)) //Stomachs are a pain - they will constantly call on_mob_add unless we split on addition to stomachs, but we also want to make sure we don't double split + var/adjusted_vol = process_mob_reagent_purity(glob_reagent, amount, added_purity) + if(!adjusted_vol) //If we're inverse or FALSE cancel addition + return amount + /* We return true here because of #63301 + The only cases where this will be false or 0 if its an inverse chem, an impure chem of 0 purity (highly unlikely if even possible), or if glob_reagent is null (which shouldn't happen at all as there's a check for that a few lines up), + In the first two cases, we would want to return TRUE so trans_to and other similar methods actually delete the corresponding chemical from the original reagent holder. + */ + amount = adjusted_vol + has_split = TRUE + + var/cached_total = total_volume + if(cached_total + amount > maximum_volume) + amount = maximum_volume - cached_total //Doesnt fit in. Make it disappear. shouldn't happen. Will happen. + amount = round(amount, CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + + var/cached_temp = chem_temp + var/list/cached_reagents = reagent_list + + //Equalize temperature - Not using specific_heat() because the new chemical isn't in yet. + var/old_heat_capacity = 0 + if(reagtemp != cached_temp) + for(var/datum/reagent/iter_reagent as anything in cached_reagents) + old_heat_capacity += iter_reagent.specific_heat * iter_reagent.volume + + //add the reagent to the existing if it exists + for(var/datum/reagent/iter_reagent as anything in cached_reagents) + if(iter_reagent.type == reagent_type) + if(override_base_ph) + added_ph = iter_reagent.ph + iter_reagent.purity = ((iter_reagent.creation_purity * iter_reagent.volume) + (added_purity * amount)) /(iter_reagent.volume + amount) //This should add the purity to the product + iter_reagent.creation_purity = iter_reagent.purity + iter_reagent.ph = ((iter_reagent.ph * (iter_reagent.volume)) + (added_ph * amount)) / (iter_reagent.volume + amount) + iter_reagent.volume += amount + update_total() + + iter_reagent.on_merge(data, amount) + if(reagtemp != cached_temp) + var/new_heat_capacity = heat_capacity() + if(new_heat_capacity) + set_temperature(((old_heat_capacity * cached_temp) + (iter_reagent.specific_heat * amount * reagtemp)) / new_heat_capacity) + else + set_temperature(reagtemp) + + SEND_SIGNAL(src, COMSIG_REAGENTS_ADD_REAGENT, iter_reagent, amount, reagtemp, data, no_react) + if(!no_react && !is_reacting) //To reduce the amount of calculations for a reaction the reaction list is only updated on a reagents addition. + handle_reactions() + return amount + + //otherwise make a new one + var/datum/reagent/new_reagent = new reagent_type(data) + cached_reagents += new_reagent + new_reagent.holder = src + new_reagent.volume = amount + new_reagent.purity = added_purity + new_reagent.creation_purity = added_purity + new_reagent.ph = added_ph + new_reagent.on_new(data) + + if(isliving(my_atom)) + new_reagent.on_mob_add(my_atom, amount) //Must occur before it could posibly run on_mob_delete + + if(has_split) //prevent it from splitting again + new_reagent.chemical_flags |= REAGENT_DONOTSPLIT + + update_total() + if(reagtemp != cached_temp) + var/new_heat_capacity = heat_capacity() + if(new_heat_capacity) + set_temperature(((old_heat_capacity * cached_temp) + (new_reagent.specific_heat * amount * reagtemp)) / new_heat_capacity) + else + set_temperature(reagtemp) + + SEND_SIGNAL(src, COMSIG_REAGENTS_NEW_REAGENT, new_reagent, amount, reagtemp, data, no_react) + if(!no_react) + handle_reactions() + return amount + +/** + * Like add_reagent but you can enter a list. + * Arguments + * + * * [list_reagents][list] - list to add. Format it like this: list(/datum/reagent/toxin = 10, "beer" = 15) + * * [data][list] - additional data to add + */ +/datum/reagents/proc/add_reagent_list(list/list_reagents, list/data = null) + for(var/r_id in list_reagents) + var/amt = list_reagents[r_id] + add_reagent(r_id, amt, data) + +/** + * Removes a specific reagent. can supress reactions if needed + * Arguments + * + * * [reagent_type][datum/reagent] - the type of reagent + * * amount - the volume to remove + * * safety - if FALSE will initiate reactions upon removing. used for trans_id_to + * * include_subtypes - if TRUE will remove the specified amount from all subtypes of reagent_type as well + */ +/datum/reagents/proc/remove_reagent(datum/reagent/reagent_type, amount, safety = TRUE, include_subtypes = FALSE) + if(!ispath(reagent_type)) + stack_trace("invalid reagent passed to remove reagent [reagent_type]") + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to remove reagent [amount] [reagent_type]") + return FALSE + + amount = round(amount, CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + + var/total_removed_amount = 0 + var/remove_amount = 0 + var/list/cached_reagents = reagent_list + for(var/datum/reagent/cached_reagent as anything in cached_reagents) + //check for specific type or subtypes + if(!include_subtypes) + if(cached_reagent.type != reagent_type) + continue + else if(!istype(cached_reagent, reagent_type)) + continue + + remove_amount = min(cached_reagent.volume, amount) + cached_reagent.volume -= remove_amount + + update_total() + if(!safety)//So it does not handle reactions when it need not to + handle_reactions() + SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, QDELING(cached_reagent) ? reagent_type : cached_reagent, amount) + + total_removed_amount += remove_amount + + //if we reached here means we have found our specific reagent type so break + if(!include_subtypes) + break + + return total_removed_amount + +/** + * Removes a reagent at random and by a random quantity till the specified amount has been removed. + * Used to create a shower/spray effect for e.g. when you spill a bottle or turn a shower on + * and you want an chaotic effect of whatever coming out + * Arguments + * + * * amount- the volume to remove + */ +/datum/reagents/proc/remove_any(amount = 1) + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to remove any reagent [amount]") + return FALSE + + amount = round(amount, CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + + var/list/cached_reagents = reagent_list + var/total_removed = 0 + var/current_list_element = 1 + var/initial_list_length = cached_reagents.len //stored here because removing can cause some reagents to be deleted, ergo length change. + + current_list_element = rand(1, cached_reagents.len) + + while(total_removed != amount) + if(total_removed >= amount) + break + if(total_volume <= 0 || !cached_reagents.len) + break + + if(current_list_element > cached_reagents.len) + current_list_element = 1 + + var/datum/reagent/target_holder = cached_reagents[current_list_element] + var/remove_amt = min(amount - total_removed, round(amount / rand(2, initial_list_length), round(amount / 10, 0.01))) //double round to keep it at a somewhat even spread relative to amount without getting funky numbers. + //min ensures we don't go over amount. + remove_reagent(target_holder.type, remove_amt) + + current_list_element++ + total_removed += remove_amt + + handle_reactions() + return total_removed //this should be amount unless the loop is prematurely broken, in which case it'll be lower. It shouldn't ever go OVER amount. + +/** + * Removes all reagents by an amount equal to + * [amount specified] / total volume present in this holder + * Arguments + * + * * amount - the volume of each reagent + */ + +/datum/reagents/proc/remove_all(amount = 1) + if(!total_volume) + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to remove all reagents [amount]") + return FALSE + + amount = round(amount, CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + + var/list/cached_reagents = reagent_list + var/part = amount / total_volume + var/total_removed_amount = 0 + + for(var/datum/reagent/reagent as anything in cached_reagents) + total_removed_amount += remove_reagent(reagent.type, reagent.volume * part) + + handle_reactions() + return round(total_removed_amount, CHEMICAL_VOLUME_ROUNDING) + +/** + * Removes an specific reagent from this holder + * Arguments + * + * * [target_reagent_typepath][datum/reagent] - type typepath of the reagent to remove + */ +/datum/reagents/proc/del_reagent(datum/reagent/target_reagent_typepath) + if(!ispath(target_reagent_typepath)) + stack_trace("invalid reagent path passed to del reagent [target_reagent_typepath]") + return FALSE + + //setting the volume to 0 will allow update_total() to clear it up for us + var/list/cached_reagents = reagent_list + for(var/datum/reagent/reagent as anything in cached_reagents) + if(reagent.type == target_reagent_typepath) + reagent.volume = 0 + update_total() + return TRUE + + return FALSE + +/** + * Turn one reagent into another, preserving volume, temp, purity, ph + * Arguments + * + * * [source_reagent_typepath][/datum/reagent] - the typepath of the reagent you are trying to convert + * * [target_reagent_typepath][/datum/reagent] - the final typepath the source_reagent_typepath will be converted into + * * multiplier - the multiplier applied on the source_reagent_typepath volume before converting + * * include_source_subtypes- if TRUE will convert all subtypes of source_reagent_typepath into target_reagent_typepath as well + */ +/datum/reagents/proc/convert_reagent( + datum/reagent/source_reagent_typepath, + datum/reagent/target_reagent_typepath, + multiplier = 1, + include_source_subtypes = FALSE +) + if(!ispath(source_reagent_typepath)) + stack_trace("invalid reagent path passed to convert reagent [source_reagent_typepath]") + return FALSE + + var/reagent_amount + var/reagent_purity + var/reagent_ph + if(include_source_subtypes) + reagent_ph = ph + var/weighted_purity + var/list/reagent_type_list = typecacheof(source_reagent_typepath) + for(var/datum/reagent/reagent as anything in reagent_list) + if(reagent.type in reagent_type_list) + weighted_purity += reagent.volume * reagent.purity + reagent_amount += reagent.volume + remove_reagent(reagent.type, reagent.volume * multiplier) + reagent_purity = weighted_purity / reagent_amount + else + var/datum/reagent/source_reagent = has_reagent(source_reagent_typepath) + reagent_amount = source_reagent.volume + reagent_purity = source_reagent.purity + reagent_ph = source_reagent.ph + remove_reagent(source_reagent_typepath, reagent_amount) + add_reagent(target_reagent_typepath, reagent_amount * multiplier, reagtemp = chem_temp, added_purity = reagent_purity, added_ph = reagent_ph) + +/// Removes all reagents +/datum/reagents/proc/clear_reagents() + var/list/cached_reagents = reagent_list + + //setting volume to 0 will allow update_total() to clean it up + for(var/datum/reagent/reagent as anything in cached_reagents) + reagent.volume = 0 + update_total() + + SEND_SIGNAL(src, COMSIG_REAGENTS_CLEAR_REAGENTS) + +/** + * Transfer some stuff from this holder to a target object + * + * Arguments: + * * obj/target - Target to attempt transfer to + * * amount - amount of reagent volume to transfer + * * multiplier - multiplies each reagent amount by this number well byond their available volume before transfering. used to create reagents from thin air if you ever need to + * * datum/reagent/target_id - transfer only this reagent in this holder leaving others untouched + * * preserve_data - if preserve_data=0, the reagents data will be lost. Usefull if you use data for some strange stuff and don't want it to be transferred. + * * no_react - passed through to [/datum/reagents/proc/add_reagent] + * * mob/transferred_by - used for logging + * * remove_blacklisted - skips transferring of reagents without REAGENT_CAN_BE_SYNTHESIZED in chemical_flags + * * methods - passed through to [/datum/reagents/proc/expose] and [/datum/reagent/proc/on_transfer] + * * show_message - passed through to [/datum/reagents/proc/expose] + * * ignore_stomach - when using methods INGEST will not use the stomach as the target + */ +/datum/reagents/proc/trans_to( + atom/target, + amount = 1, + multiplier = 1, + datum/reagent/target_id, + preserve_data = TRUE, + no_react = FALSE, + mob/transferred_by, + remove_blacklisted = FALSE, + methods = NONE, + show_message = TRUE, + ignore_stomach = FALSE +) + if(QDELETED(target) || !total_volume) + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to trans_to [amount] amount of reagents") + return FALSE + + if(!isnull(target_id) && !ispath(target_id)) + stack_trace("invalid target reagent id [target_id] passed to trans_to") + return FALSE + + var/list/cached_reagents = reagent_list + + var/atom/target_atom + var/datum/reagents/target_holder + if(istype(target, /datum/reagents)) + target_holder = target + target_atom = target_holder.my_atom + else + if(!ignore_stomach && (methods & INGEST) && iscarbon(target)) + var/mob/living/carbon/eater = target + var/obj/item/organ/internal/stomach/belly = eater.get_organ_slot(ORGAN_SLOT_STOMACH) + if(!belly) + var/expel_amount = round(amount, CHEMICAL_QUANTISATION_LEVEL) + if(expel_amount > 0 ) + eater.expel_ingested(my_atom, expel_amount) + return + target_holder = belly.reagents + target_atom = belly + else if(!target.reagents) + return + else + target_holder = target.reagents + target_atom = target + + var/cached_amount = amount + + // Prevents small amount problems, as well as zero and below zero amounts. + amount = round(min(amount, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + + //Set up new reagents to inherit the old ongoing reactions + if(!no_react) + transfer_reactions(target_holder) + + var/trans_data = null + var/list/transfer_log = list() + var/list/r_to_send = list() // Validated list of reagents to be exposed + var/list/reagents_to_remove = list() + + var/part = isnull(target_id) ? (amount / total_volume) : 1 + var/transfer_amount + var/transfered_amount + var/total_transfered_amount = 0 + + //first add reagents to target + for(var/datum/reagent/reagent as anything in cached_reagents) + if(remove_blacklisted && !(reagent.chemical_flags & REAGENT_CAN_BE_SYNTHESIZED)) + continue + + if(!isnull(target_id)) + if(reagent.type == target_id) + force_stop_reagent_reacting(reagent) + transfer_amount = min(amount, reagent.volume) + else + continue + else + transfer_amount = reagent.volume * part + + if(preserve_data) + trans_data = copy_data(reagent) + if(reagent.intercept_reagents_transfer(target_holder, cached_amount)) + continue + transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount * multiplier, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred. + if(!transfered_amount) + continue + if(methods) + r_to_send += reagent + reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount)) + total_transfered_amount += transfered_amount + + if(!isnull(target_id)) + break + + //expose target to reagent changes + if(methods) + target_holder.expose(isorgan(target_atom) ? target : target_atom, methods, part, show_message, r_to_send) + + //remove chemicals that were added above + for(var/list/data as anything in reagents_to_remove) + var/datum/reagent/reagent = data["R"] + transfer_amount = data["T"] + if(methods) + reagent.on_transfer(target_atom, methods, transfer_amount) + remove_reagent(reagent.type, transfer_amount) + transfer_log[reagent.type] = list(REAGENT_TRANSFER_AMOUNT = transfer_amount, REAGENT_PURITY = reagent.purity) + + //combat log + if(transferred_by && target_atom) + var/atom/log_target = target_atom + if(isorgan(target_atom)) + var/obj/item/organ/organ_item = target_atom + log_target = organ_item.owner ? organ_item.owner : organ_item + log_target.add_hiddenprint(transferred_by) //log prints so admins can figure out who touched it last. + log_combat(transferred_by, log_target, "transferred reagents to", my_atom, "which had [get_external_reagent_log_string(transfer_log)]") + + update_total() + target_holder.update_total() + if(!no_react) + target_holder.handle_reactions() + src.handle_reactions() + + return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING) + +/** + * Copies the reagents to the target object + * Arguments + * + * * [target][obj] - the target to transfer reagents to + * * multiplier - multiplies each reagent amount by this number well byond their available volume before transfering. used to create reagents from thin air if you ever need to + * * preserve_data - preserve user data of all reagents after transfering + * * no_react - if TRUE will not handle reactions + */ +/datum/reagents/proc/copy_to( + atom/target, + amount = 1, + multiplier = 1, + preserve_data = TRUE, + no_react = FALSE +) + if(QDELETED(target) || !total_volume) + return + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to copy_to [amount] amount of reagents") + return FALSE + + var/datum/reagents/target_holder + if(istype(target, /datum/reagents)) + target_holder = target + else + if(!target.reagents) + return + target_holder = target.reagents + + // Prevents small amount problems, as well as zero and below zero amounts. + amount = round(min(amount, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return + + var/list/cached_reagents = reagent_list + var/part = amount / total_volume + var/transfer_amount + var/transfered_amount = 0 + var/total_transfered_amount = 0 + var/trans_data = null + + for(var/datum/reagent/reagent as anything in cached_reagents) + transfer_amount = reagent.volume * part * multiplier + if(preserve_data) + trans_data = copy_data(reagent) + transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, trans_data, chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) + if(!transfered_amount) + continue + total_transfered_amount += transfered_amount + + if(!no_react) + // pass over previous ongoing reactions before handle_reactions is called + transfer_reactions(target_holder) + + target_holder.update_total() + target_holder.handle_reactions() + + return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING) + +/** + * Multiplies the reagents inside this holder by a specific amount + * Arguments + * * multiplier - the amount to multiply each reagent by + */ +/datum/reagents/proc/multiply_reagents(multiplier = 1) + var/list/cached_reagents = reagent_list + if(!total_volume) + return + var/change = (multiplier - 1) //Get the % change + for(var/datum/reagent/reagent as anything in cached_reagents) + if(change > 0) + add_reagent(reagent.type, reagent.volume * change, added_purity = reagent.purity, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) + else + remove_reagent(reagent.type, abs(reagent.volume * change)) //absolute value to prevent a double negative situation (removing -50% would be adding 50%) + + update_total() + handle_reactions() + +/// Updates [/datum/reagents/var/total_volume] +/datum/reagents/proc/update_total() + var/list/cached_reagents = reagent_list + var/list/deleted_reagents = list() + var/chem_index = 1 + var/num_reagents = length(cached_reagents) + var/total_ph = 0 + var/reagent_volume = 0 + . = 0 + + //responsible for removing reagents and computing total ph & volume + //all it's code was taken out of del_reagent() initially for efficiency purposes + while(chem_index <= num_reagents) + var/datum/reagent/reagent = cached_reagents[chem_index] + chem_index += 1 + reagent_volume = round(reagent.volume, CHEMICAL_QUANTISATION_LEVEL) //round to this many decimal places + + //remove very small amounts of reagents + if(reagent_volume <= 0 || (!is_reacting && reagent_volume < CHEMICAL_VOLUME_ROUNDING)) + //end metabolization + if(isliving(my_atom)) + if(reagent.metabolizing) + reagent.metabolizing = FALSE + reagent.on_mob_end_metabolize(my_atom) + reagent.on_mob_delete(my_atom) + + //removing it and store in a seperate list for processing later + cached_reagents -= reagent + LAZYREMOVE(previous_reagent_list, reagent.type) + deleted_reagents += reagent + + //move pointer back so we don't overflow & decrease length + chem_index -= 1 + num_reagents -= 1 + continue + + //compute volume & ph like we would normally + . += reagent_volume + total_ph += reagent.ph * reagent_volume + + //reasign rounded value + reagent.volume = reagent_volume + + //assign the final values, rounding up can sometimes cause overflow so bring it down + total_volume = min(round(., CHEMICAL_VOLUME_ROUNDING), maximum_volume) + if(!total_volume) + ph = CHEMICAL_NORMAL_PH + else + ph = clamp(total_ph / total_volume, CHEMICAL_MIN_PH, CHEMICAL_MAX_PH) + + //now send the signals after the volume & ph has been computed + for(var/datum/reagent/deleted_reagent as anything in deleted_reagents) + SEND_SIGNAL(src, COMSIG_REAGENTS_DEL_REAGENT, deleted_reagent) + qdel(deleted_reagent) + +/** + * Shallow copies (deep copy of viruses) data from the provided reagent into our copy of that reagent + * Arguments + * [current_reagent][datum/reagent] - the reagent(not typepath) to copy data from + */ +/datum/reagents/proc/copy_data(datum/reagent/current_reagent) + if(!current_reagent || !current_reagent.data) + return null + if(!istype(current_reagent.data, /list)) + return current_reagent.data + + var/list/trans_data = current_reagent.data.Copy() + + // We do this so that introducing a virus to a blood sample + // doesn't automagically infect all other blood samples from + // the same donor. + // + // Technically we should probably copy all data lists, but + // that could possibly eat up a lot of memory needlessly + // if most data lists are read-only. + if(trans_data["viruses"]) + var/list/v = trans_data["viruses"] + trans_data["viruses"] = v.Copy() + + return trans_data + +//===============================Generic getters======================================= +/** + * Returns a reagent from this holder if it matches all the specified arguments + * Arguments + * + * * [target_reagent][datum/reagent] - the reagent typepath to check for. can be null to return any reagent + * * amount - checks for having a specific amount of that chemical + * * needs_metabolizing - takes into consideration if the chemical is matabolizing when it's checked. + * * check_subtypes - controls whether it should it should also include subtypes: ispath(type, reagent) versus type == reagent. + * * chemical_flags - checks for reagent flags. + */ +/datum/reagents/proc/has_reagent( + datum/reagent/target_reagent, + amount = -1, + needs_metabolizing = FALSE, + check_subtypes = FALSE, + chemical_flags = NONE +) + if(!isnull(target_reagent) && !ispath(target_reagent)) + stack_trace("invalid reagent path passed to has reagent [target_reagent]") + return FALSE + + var/list/cached_reagents = reagent_list + for(var/datum/reagent/holder_reagent as anything in cached_reagents) + //finding for a specific reagent + if(!isnull(target_reagent)) + //first find for specific type or subtype + if(!check_subtypes) + if(holder_reagent.type != target_reagent) + continue + else if(!istype(holder_reagent, target_reagent)) + continue + + //next check if we have the requested amount + if(amount > 0 && holder_reagent.volume < amount) + continue + + //next check for metabolization + if(needs_metabolizing && !holder_reagent.metabolizing) + continue + + //next check if it has the specified flag + if(chemical_flags && !(holder_reagent.chemical_flags & chemical_flags)) + continue + + //after all that if we get here then we have found our reagent + return holder_reagent + + return FALSE + + +/// Get a reference to the reagent there is the most of in this holder +/datum/reagents/proc/get_master_reagent() + var/list/cached_reagents = reagent_list + var/datum/reagent/master + var/max_volume = 0 + for(var/datum/reagent/reagent as anything in cached_reagents) + if(reagent.volume > max_volume) + max_volume = reagent.volume + master = reagent + + return master + + +//================================Exposure(to apply reagent effects)====================== +/** + * Applies the relevant expose_ proc for every reagent in this holder + * * [/datum/reagent/proc/expose_mob] + * * [/datum/reagent/proc/expose_turf] + * * [/datum/reagent/proc/expose_obj] + * + * Arguments + * - Atom/target: What mob/turf/object is being exposed to reagents? This is your reaction target. + * - Methods: What reaction type is the reagent itself going to call on the reaction target? Types are TOUCH, INGEST, VAPOR, PATCH, and INJECT. + * - Volume_modifier: What is the reagent volume multiplied by when exposed? Note that this is called on the volume of EVERY reagent in the base body, so factor in your Maximum_Volume if necessary! + * - Show_message: Whether to display anything to mobs when they are exposed. + * - list/datum/reagent/r_to_expose: list of reagents to expose. if null will expose the reagents present in this holder instead + */ +/datum/reagents/proc/expose(atom/target, methods = TOUCH, volume_modifier = 1, show_message = 1, list/datum/reagent/r_to_expose = null) + if(isnull(target)) + return null + + var/list/target_reagents = isnull(r_to_expose) ? reagent_list : r_to_expose + if(!target_reagents.len) + return null + + var/list/datum/reagent/reagents = list() + for(var/datum/reagent/reagent as anything in target_reagents) + reagents[reagent] = reagent.volume * volume_modifier + + return target.expose_reagents(reagents, src, methods, volume_modifier, show_message) + +/** + * Applies heat to this holder + * Arguments + * + * * temperature - the temperature we to heat/cool by + * * coeff - multiplier to be applied on temp diff between param temp and current temp + */ +/datum/reagents/proc/expose_temperature(temperature, coeff = 0.02) + if(istype(my_atom,/obj/item/reagent_containers)) + var/obj/item/reagent_containers/RCs = my_atom + if(RCs.reagent_flags & NO_REACT) //stasis holders IE cryobeaker + return + var/temp_delta = (temperature - chem_temp) * coeff + if(temp_delta > 0) + chem_temp = min(chem_temp + max(temp_delta, 1), temperature) + else + chem_temp = max(chem_temp + min(temp_delta, -1), temperature) + set_temperature(round(chem_temp)) + handle_reactions() + + +//===============================Logging========================================== +/** + * Outputs a log-friendly list of reagents based on an external reagent list. + * + * Arguments: + * * external_list - Assoc list of (reagent_type) = list(REAGENT_TRANSFER_AMOUNT = amounts, REAGENT_PURITY = purity) + */ +/datum/reagents/proc/get_external_reagent_log_string(external_list) + if(!length(external_list)) + return "no reagents" + + var/list/data = list() + + for(var/reagent_type in external_list) + var/list/qualities = external_list[reagent_type] + data += "[reagent_type] ([round(qualities[REAGENT_TRANSFER_AMOUNT], CHEMICAL_QUANTISATION_LEVEL)]u, [qualities[REAGENT_PURITY]] purity)" + + return english_list(data) + +/// Outputs a log-friendly list of reagents based on the internal reagent_list. +/datum/reagents/proc/get_reagent_log_string() + if(!length(reagent_list)) + return "no reagents" + + var/list/data = list() + + for(var/datum/reagent/reagent as anything in reagent_list) + data += "[reagent.type] ([round(reagent.volume, CHEMICAL_QUANTISATION_LEVEL)]u, [reagent.purity] purity)" + + return english_list(data) + +#undef REAGENT_TRANSFER_AMOUNT +#undef REAGENT_PURITY diff --git a/code/modules/reagents/chemistry/holder/mob_life.dm b/code/modules/reagents/chemistry/holder/mob_life.dm new file mode 100644 index 0000000000000..03421f1577098 --- /dev/null +++ b/code/modules/reagents/chemistry/holder/mob_life.dm @@ -0,0 +1,153 @@ +/** + * Triggers metabolizing for all the reagents in this holder + * + * Arguments: + * * mob/living/carbon/carbon - The mob to metabolize in, if null it uses [/datum/reagents/var/my_atom] + * * seconds_per_tick - the time in server seconds between proc calls (when performing normally it will be 2) + * * times_fired - the number of times the owner's life() tick has been called aka The number of times SSmobs has fired + * * can_overdose - Allows overdosing + * * liverless - Stops reagents that aren't set as [/datum/reagent/var/self_consuming] from metabolizing + */ +/datum/reagents/proc/metabolize(mob/living/carbon/owner, seconds_per_tick, times_fired, can_overdose = FALSE, liverless = FALSE, dead = FALSE) + var/list/cached_reagents = reagent_list + if(owner) + expose_temperature(owner.bodytemperature, 0.25) + + var/need_mob_update = FALSE + var/obj/item/organ/internal/stomach/belly = owner.get_organ_slot(ORGAN_SLOT_STOMACH) + var/obj/item/organ/internal/liver/liver = owner.get_organ_slot(ORGAN_SLOT_LIVER) + var/liver_tolerance + if(liver) + var/liver_health_percent = (liver.maxHealth - liver.damage) / liver.maxHealth + liver_tolerance = liver.toxTolerance * liver_health_percent + + for(var/datum/reagent/reagent as anything in cached_reagents) + // skip metabolizing effects for small units of toxins + if(istype(reagent, /datum/reagent/toxin) && liver && !dead) + var/datum/reagent/toxin/toxin = reagent + var/amount = toxin.volume + if(belly) + amount += belly.reagents.get_reagent_amount(toxin.type) + + if(amount <= liver_tolerance) + owner.reagents.remove_reagent(toxin.type, toxin.metabolization_rate * owner.metabolism_efficiency * seconds_per_tick) + continue + + need_mob_update += metabolize_reagent(owner, reagent, seconds_per_tick, times_fired, can_overdose, liverless, dead) + + if(owner && need_mob_update) //some of the metabolized reagents had effects on the mob that requires some updates. + owner.updatehealth() + update_total() + +/* + * Metabolises a single reagent for a target owner carbon mob. See above. + * + * Arguments: + * * mob/living/carbon/owner - The mob to metabolize in, if null it uses [/datum/reagents/var/my_atom] + * * seconds_per_tick - the time in server seconds between proc calls (when performing normally it will be 2) + * * times_fired - the number of times the owner's life() tick has been called aka The number of times SSmobs has fired + * * can_overdose - Allows overdosing + * * liverless - Stops reagents that aren't set as [/datum/reagent/var/self_consuming] from metabolizing + */ +/datum/reagents/proc/metabolize_reagent(mob/living/carbon/owner, datum/reagent/reagent, seconds_per_tick, times_fired, can_overdose = FALSE, liverless = FALSE, dead = FALSE) + var/need_mob_update = FALSE + if(QDELETED(reagent.holder)) + return FALSE + + if(!owner) + owner = reagent.holder.my_atom + + if(owner && reagent && (!dead || (reagent.chemical_flags & REAGENT_DEAD_PROCESS))) + if(owner.reagent_check(reagent, seconds_per_tick, times_fired)) + return + if(liverless && !reagent.self_consuming) //need to be metabolized + return + if(!reagent.metabolizing) + reagent.metabolizing = TRUE + reagent.on_mob_metabolize(owner) + if(can_overdose && !HAS_TRAIT(owner, TRAIT_OVERDOSEIMMUNE)) + if(reagent.overdose_threshold) + if(reagent.volume >= reagent.overdose_threshold && !reagent.overdosed) + reagent.overdosed = TRUE + need_mob_update += reagent.overdose_start(owner) + owner.log_message("has started overdosing on [reagent.name] at [reagent.volume] units.", LOG_GAME) + for(var/addiction in reagent.addiction_types) + owner.mind?.add_addiction_points(addiction, reagent.addiction_types[addiction] * REAGENTS_METABOLISM) + + if(reagent.overdosed) + need_mob_update += reagent.overdose_process(owner, seconds_per_tick, times_fired) + reagent.current_cycle++ + need_mob_update += reagent.on_mob_life(owner, seconds_per_tick, times_fired) + if(dead && !QDELETED(owner) && !QDELETED(reagent)) + need_mob_update += reagent.on_mob_dead(owner, seconds_per_tick) + if(!QDELETED(owner) && !QDELETED(reagent)) + reagent.metabolize_reagent(owner, seconds_per_tick, times_fired) + + return need_mob_update + +/** + * Signals that metabolization has stopped, triggering the end of trait-based effects + * Arguments + * + * * [C][mob/living/carbon] - the mob to end metabolization on + * * keep_liverless - if true will work without a liver + */ +/datum/reagents/proc/end_metabolization(mob/living/carbon/C, keep_liverless = TRUE) + var/list/cached_reagents = reagent_list + for(var/datum/reagent/reagent as anything in cached_reagents) + if(QDELETED(reagent.holder)) + continue + if(keep_liverless && reagent.self_consuming) //Will keep working without a liver + continue + if(!C) + C = reagent.holder.my_atom + if(reagent.metabolizing) + reagent.metabolizing = FALSE + reagent.on_mob_end_metabolize(C) + +/** + * Processes the reagents in the holder and converts them, only called in a mob/living/carbon on addition + * + * Arguments: + * * reagent - the added reagent datum/object + * * added_volume - the volume of the reagent that was added (since it can already exist in a mob) + * * added_purity - the purity of the added volume + * returns the volume of the original, pure, reagent to add / keep + */ +/datum/reagents/proc/process_mob_reagent_purity(datum/reagent/reagent, added_volume, added_purity) + if(!reagent) + stack_trace("Attempted to process a mob's reagent purity for a null reagent!") + return FALSE + if(added_purity == 1) + return added_volume + if(reagent.chemical_flags & REAGENT_DONOTSPLIT) + return added_volume + if(added_purity < 0) + stack_trace("Purity below 0 for chem on mob splitting: [reagent.type]!") + added_purity = 0 + + if((reagent.inverse_chem_val > added_purity) && (reagent.inverse_chem))//Turns all of a added reagent into the inverse chem + add_reagent(reagent.inverse_chem, added_volume, FALSE, added_purity = reagent.get_inverse_purity(reagent.creation_purity)) + var/datum/reagent/inverse_reagent = has_reagent(reagent.inverse_chem) + if(inverse_reagent.chemical_flags & REAGENT_SNEAKYNAME) + inverse_reagent.name = reagent.name//Negative effects are hidden + return FALSE //prevent addition + return added_volume + +/** + * Processes any chems that have the REAGENT_IGNORE_STASIS bitflag ONLY + * Arguments + * + * * [owner][mob/living/carbon] - the mob we are doing stasis handlng on + * * seconds_per_tick - passed from process + * * times_fired - number of times to metabolize this reagent + */ +/datum/reagents/proc/handle_stasis_chems(mob/living/carbon/owner, seconds_per_tick, times_fired) + var/need_mob_update = FALSE + for(var/datum/reagent/reagent as anything in reagent_list) + if(!(reagent.chemical_flags & REAGENT_IGNORE_STASIS)) + continue + need_mob_update += metabolize_reagent(owner, reagent, seconds_per_tick, times_fired, can_overdose = TRUE) + if(owner && need_mob_update) //some of the metabolized reagents had effects on the mob that requires some updates. + owner.updatehealth() + update_total() diff --git a/code/modules/reagents/chemistry/holder/properties.dm b/code/modules/reagents/chemistry/holder/properties.dm new file mode 100644 index 0000000000000..b949866a6d447 --- /dev/null +++ b/code/modules/reagents/chemistry/holder/properties.dm @@ -0,0 +1,195 @@ +//============================VOLUME====================================== +/// Is this holder full or not +/datum/reagents/proc/holder_full() + return total_volume >= maximum_volume + +/** + * Get the amount of this reagent or the sum of all its subtypes if specified + * Arguments + * * [reagent][datum/reagent] - the typepath of the reagent to look for + * * type_check - see defines under reagents.dm file + */ +/datum/reagents/proc/get_reagent_amount(datum/reagent/reagent, type_check = REAGENT_STRICT_TYPE) + if(!ispath(reagent)) + stack_trace("invalid path passed to get_reagent_amount [reagent]") + return 0 + var/list/cached_reagents = reagent_list + + var/total_amount = 0 + for(var/datum/reagent/cached_reagent as anything in cached_reagents) + switch(type_check) + if(REAGENT_STRICT_TYPE) + if(cached_reagent.type != reagent) + continue + if(REAGENT_PARENT_TYPE) //to simulate typesof() which returns the type and then child types + if(cached_reagent.type != reagent && type2parent(cached_reagent.type) != reagent) + continue + else + if(!istype(cached_reagent, reagent)) + continue + + total_amount += cached_reagent.volume + + //short cut to break when we have found our one exact type + if(type_check == REAGENT_STRICT_TYPE) + return total_amount + + return round(total_amount, CHEMICAL_VOLUME_ROUNDING) + + +//======================PH(clamped between 0->14)======================================== +/* +* Adjusts the base pH of all of the reagents in a beaker +* +* - moves it towards acidic +* + moves it towards basic +* Arguments: +* * value - How much to adjust the base pH by +*/ +/datum/reagents/proc/adjust_all_reagents_ph(value) + for(var/datum/reagent/reagent as anything in reagent_list) + reagent.ph = clamp(reagent.ph + value, CHEMICAL_MIN_PH, CHEMICAL_MAX_PH) + +/* +* Adjusts the base pH of a specific type +* +* - moves it towards acidic +* + moves it towards basic +* Arguments: +* * input_reagent - type path of the reagent +* * value - How much to adjust the base pH by +*/ +/datum/reagents/proc/adjust_specific_reagent_ph(input_reagent, value) + var/datum/reagent/reagent = has_reagent(input_reagent) + if(!reagent) //We can call this with missing reagents. + return FALSE + reagent.ph = clamp(reagent.ph + value, CHEMICAL_MIN_PH, CHEMICAL_MAX_PH) + + +//==========================TEMPERATURE====================================== +/// Returns the total heat capacity for all of the reagents currently in this holder. +/datum/reagents/proc/heat_capacity() + . = 0 + var/list/cached_reagents = reagent_list //cache reagents + for(var/datum/reagent/reagent in cached_reagents) + . += reagent.specific_heat * reagent.volume + +/** Adjusts the thermal energy of the reagents in this holder by an amount. + * + * Arguments: + * - delta_energy: The amount to change the thermal energy by. + * - min_temp: The minimum temperature that can be reached. + * - max_temp: The maximum temperature that can be reached. + */ +/datum/reagents/proc/adjust_thermal_energy(delta_energy, min_temp = 2.7, max_temp = 1000) + var/heat_capacity = heat_capacity() + if(!heat_capacity) + return // no div/0 please + set_temperature(clamp(chem_temp + (delta_energy / heat_capacity), min_temp, max_temp)) + +/** Sets the temperature of this reagent container to a new value. + * + * Handles setter signals. + * + * Arguments: + * - _temperature: The new temperature value. + */ +/datum/reagents/proc/set_temperature(_temperature) + if(_temperature == chem_temp) + return + + . = chem_temp + chem_temp = clamp(_temperature, 0, CHEMICAL_MAXIMUM_TEMPERATURE) + SEND_SIGNAL(src, COMSIG_REAGENTS_TEMP_CHANGE, _temperature, .) + +//==============================PURITY========================================== +/** + * Get the purity of this reagent + * Arguments + * * [reagent][datum/reagent] - the typepath of the specific reagent to get purity of + */ +/datum/reagents/proc/get_reagent_purity(datum/reagent/reagent) + if(!ispath(reagent)) + stack_trace("invalid reagent typepath passed to get_reagent_purity [reagent]") + return 0 + + var/list/cached_reagents = reagent_list + for(var/datum/reagent/cached_reagent as anything in cached_reagents) + if(cached_reagent.type == reagent) + return round(cached_reagent.purity, 0.01) + + return 0 + +/** + * Get the average purity of all reagents (or all subtypes of provided typepath) + * Arguments + * * [parent_type][datum/reagent] - the typepath of specific reagents to look for + */ +/datum/reagents/proc/get_average_purity(datum/reagent/parent_type = null) + if(!isnull(parent_type) && !ispath(parent_type)) + stack_trace("illegal path passed to get_average_purity [parent_type]") + return FALSE + + var/total_amount + var/weighted_purity + var/list/cached_reagents = reagent_list + for(var/datum/reagent/reagent as anything in cached_reagents) + if(!isnull(parent_type) && !istype(reagent, parent_type)) + continue + total_amount += reagent.volume + weighted_purity += reagent.volume * reagent.purity + + return weighted_purity / total_amount + +/** + * Directly set the purity of all contained reagents to a new value + * Arguments + * * new_purity - the new purity value + */ +/datum/reagents/proc/set_all_reagents_purity(new_purity = 0) + var/list/cached_reagents = reagent_list + for(var/datum/reagent/cached_reagent as anything in cached_reagents) + cached_reagent.purity = max(0, new_purity) + + +//================================TASTE=================================================== +/** + * Returns what this holder's reagents taste like + * + * Arguments: + * * mob/living/taster - who is doing the tasting. Some mobs can pick up specific flavours. + * * minimum_percent - the lower the minimum percent, the more sensitive the message is. + */ +/datum/reagents/proc/generate_taste_message(mob/living/taster, minimum_percent) + var/list/out = list() + var/list/tastes = list() //descriptor = strength + if(minimum_percent <= 100) + for(var/datum/reagent/reagent as anything in reagent_list) + if(!reagent.taste_mult) + continue + + var/list/taste_data = reagent.get_taste_description(taster) + for(var/taste in taste_data) + if(taste in tastes) + tastes[taste] += taste_data[taste] * reagent.volume * reagent.taste_mult + else + tastes[taste] = taste_data[taste] * reagent.volume * reagent.taste_mult + //deal with percentages + // TODO it would be great if we could sort these from strong to weak + var/total_taste = counterlist_sum(tastes) + if(total_taste > 0) + for(var/taste_desc in tastes) + var/percent = tastes[taste_desc]/total_taste * 100 + if(percent < minimum_percent) + continue + var/intensity_desc = "a hint of" + if(percent > minimum_percent * 2 || percent == 100) + intensity_desc = "" + else if(percent > minimum_percent * 3) + intensity_desc = "the strong flavor of" + if(intensity_desc != "") + out += "[intensity_desc] [taste_desc]" + else + out += "[taste_desc]" + + return english_list(out, "something indescribable") diff --git a/code/modules/reagents/chemistry/holder/reactions.dm b/code/modules/reagents/chemistry/holder/reactions.dm new file mode 100644 index 0000000000000..3fa4c7ac95cdf --- /dev/null +++ b/code/modules/reagents/chemistry/holder/reactions.dm @@ -0,0 +1,346 @@ +/** + * Handle any reactions possible in this holder + * Also UPDATES the reaction list + * High potential for infinite loopsa if you're editing this. +*/ +/datum/reagents/proc/handle_reactions() + if(QDELING(src)) + CRASH("[my_atom] is trying to handle reactions while being flagged for deletion. It presently has [length(reagent_list)] number of reactants in it. If that is over 0 then something terrible happened.") + + if(!length(reagent_list))//The liver is calling this method a lot, and is often empty of reagents so it's pointless busywork. It should be an easy fix, but I'm nervous about touching things beyond scope. Also since everything is so handle_reactions() trigger happy it might be a good idea having this check anyways. + return FALSE + + if(flags & NO_REACT) + if(is_reacting) + force_stop_reacting() //Force anything that is trying to to stop + return FALSE //Yup, no reactions here. No siree. + + if(is_reacting)//Prevent wasteful calculations + if(!(datum_flags & DF_ISPROCESSING))//If we're reacting - but not processing (i.e. we've transferred) + START_PROCESSING(SSreagents, src) + if(!(has_changed_state())) + return FALSE + +#ifndef UNIT_TESTS + // We assert that reagents will not need to react before the map is fully loaded + // This is the best I can do, sorry :( + if(!MC_RUNNING()) + return FALSE +#endif + + var/list/cached_reagents = reagent_list + var/list/cached_reactions = GLOB.chemical_reactions_list_reactant_index + var/datum/cached_my_atom = my_atom + LAZYNULL(failed_but_capable_reactions) + LAZYNULL(previous_reagent_list) + + . = 0 + var/list/possible_reactions = list() + for(var/datum/reagent/reagent as anything in cached_reagents) + LAZYADD(previous_reagent_list, reagent.type) + // I am SO sorry + reaction_loop: + for(var/datum/chemical_reaction/reaction as anything in cached_reactions[reagent.type]) // Was a big list but now it should be smaller since we filtered it with our reagent id + if(!reaction) + continue + + if(!reaction.required_reagents)//Don't bring in empty ones + continue + + var/granularity = 1 + if(!(reaction.reaction_flags & REACTION_INSTANT)) + granularity = CHEMICAL_QUANTISATION_LEVEL + + var/list/cached_required_reagents = reaction.required_reagents + for(var/req_reagent in cached_required_reagents) + if(!has_reagent(req_reagent, (cached_required_reagents[req_reagent] * granularity))) + continue reaction_loop + + var/list/cached_required_catalysts = reaction.required_catalysts + for(var/_catalyst in cached_required_catalysts) + if(!has_reagent(_catalyst, (cached_required_catalysts[_catalyst] * granularity))) + continue reaction_loop + + if(cached_my_atom) + if(reaction.required_container) + if(reaction.required_container_accepts_subtypes) + if(!istype(cached_my_atom, reaction.required_container)) + continue + else if(cached_my_atom.type != reaction.required_container) + continue + + if(isliving(cached_my_atom) && !reaction.mob_react) //Makes it so certain chemical reactions don't occur in mobs + continue + + else if(reaction.required_container) + continue + + if(reaction.required_other && !reaction.pre_reaction_other_checks(src)) + continue + + // At this point, we've passed all the hard restrictions and entered into just the soft ones + // So we're gonna start tracking reactions that COULD be completed on continue, instead of just exiting + var/required_temp = reaction.required_temp + var/is_cold_recipe = reaction.is_cold_recipe + if(required_temp != 0 && (is_cold_recipe && chem_temp > required_temp) || (!is_cold_recipe && chem_temp < required_temp)) + LAZYADD(failed_but_capable_reactions, reaction) + continue + + if(ph < reaction.optimal_ph_min - reaction.determin_ph_range && ph > reaction.optimal_ph_max + reaction.determin_ph_range) + LAZYADD(failed_but_capable_reactions, reaction) + continue + + possible_reactions += reaction + + //This is the point where we have all the possible reactions from a reagent/catalyst point of view, so we set up the reaction list + for(var/datum/chemical_reaction/selected_reaction as anything in possible_reactions) + if((selected_reaction.reaction_flags & REACTION_INSTANT) || (flags & REAGENT_HOLDER_INSTANT_REACT)) //If we have instant reactions, we process them here + instant_react(selected_reaction) + .++ + update_total() + continue + else + var/exists = FALSE + for(var/datum/equilibrium/E_exist as anything in reaction_list) + if(ispath(E_exist.reaction.type, selected_reaction.type)) //Don't add duplicates + exists = TRUE + + //Add it if it doesn't exist in the list + if(!exists) + is_reacting = TRUE//Prevent any on_reaction() procs from infinite looping + var/datum/equilibrium/equilibrium = new (selected_reaction, src) //Otherwise we add them to the processing list. + if(equilibrium.to_delete)//failed startup checks + qdel(equilibrium) + else + //Adding is done in new(), deletion is in qdel + equilibrium.reaction.on_reaction(src, equilibrium, equilibrium.multiplier) + equilibrium.react_timestep(1)//Get an initial step going so there's not a delay between setup and start - DO NOT ADD THIS TO equilibrium.NEW() + + if(LAZYLEN(reaction_list)) + is_reacting = TRUE //We've entered the reaction phase - this is set here so any reagent handling called in on_reaction() doesn't cause infinite loops + START_PROCESSING(SSreagents, src) //see process() to see how reactions are handled + else + is_reacting = FALSE + + if(.) + SEND_SIGNAL(src, COMSIG_REAGENTS_REACTED, .) + + TEST_ONLY_ASSERT(!. || MC_RUNNING(), "We reacted during subsystem init, that shouldn't be happening!") + +/** + * Checks to see if the reagents has a difference in reagents_list and previous_reagent_list (I.e. if there's a difference between the previous call and the last) + * Also checks to see if the saved reactions in failed_but_capable_reactions can start as a result of temp/pH change +*/ +/datum/reagents/proc/has_changed_state() + //Check if reagents are different + var/total_matching_reagents = 0 + for(var/reagent in previous_reagent_list) + if(has_reagent(reagent)) + total_matching_reagents++ + if(total_matching_reagents != reagent_list.len) + return TRUE + + //Check our last reactions + for(var/datum/chemical_reaction/reaction as anything in failed_but_capable_reactions) + if(reaction.is_cold_recipe) + if(reaction.required_temp < chem_temp) + return TRUE + else + if(reaction.required_temp < chem_temp) + return TRUE + if(((ph >= (reaction.optimal_ph_min - reaction.determin_ph_range)) && (ph <= (reaction.optimal_ph_max + reaction.determin_ph_range)))) + return TRUE + return FALSE + + +/* +* Main Reaction loop handler, Do not call this directly +* +* Checks to see if there's a reaction, then processes over the reaction list, removing them if flagged +* If any are ended, it displays the reaction message and removes it from the reaction list +* If the list is empty at the end it finishes reacting. +* Arguments: +* * seconds_per_tick - the time between each time step +*/ +/datum/reagents/process(seconds_per_tick) + if(!is_reacting) + force_stop_reacting() + stack_trace("[src] | [my_atom] was forced to stop reacting. This might be unintentional.") + //sum of output messages. + var/list/mix_message = list() + //Process over our reaction list + //See equilibrium.dm for mechanics + var/num_reactions = 0 + for(var/datum/equilibrium/equilibrium as anything in reaction_list) + //Continue reacting + equilibrium.react_timestep(seconds_per_tick) + num_reactions++ + //if it's been flagged to delete + if(equilibrium.to_delete) + var/temp_mix_message = end_reaction(equilibrium) + if(!text_in_list(temp_mix_message, mix_message)) + mix_message += temp_mix_message + continue + SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[equilibrium.reaction.type] total reaction steps") + if(num_reactions) + SEND_SIGNAL(src, COMSIG_REAGENTS_REACTION_STEP, num_reactions, seconds_per_tick) + + if(length(mix_message)) //This is only at the end + my_atom.audible_message(span_notice("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [mix_message.Join()]")) + + if(!LAZYLEN(reaction_list)) + finish_reacting() + else + handle_reactions() + +/* +* This ends a single instance of an ongoing reaction +* +* Arguments: +* * [equilibrium][datum/equilibrium] - the equilibrium that will be ended +* Returns: +* * mix_message - the associated mix message of a reaction +*/ +/datum/reagents/proc/end_reaction(datum/equilibrium/equilibrium) + equilibrium.reaction.reaction_finish(src, equilibrium, equilibrium.reacted_vol) + if(!equilibrium.holder || !equilibrium.reaction) //Somehow I'm getting empty equilibrium. This is here to handle them + LAZYREMOVE(reaction_list, equilibrium) + qdel(equilibrium) + stack_trace("The equilibrium datum currently processing in this reagents datum had a nulled holder or nulled reaction. src holder:[my_atom] || src type:[my_atom.type] ") //Shouldn't happen. Does happen + return + if(equilibrium.holder != src) //When called from Destroy() eqs are nulled in smoke. This is very strange. This is probably causing it to spam smoke because of the runtime interupting the removal. + stack_trace("The equilibrium datum currently processing in this reagents datum had a desynced holder to the ending reaction. src holder:[my_atom] | equilibrium holder:[equilibrium.holder.my_atom] || src type:[my_atom.type] | equilibrium holder:[equilibrium.holder.my_atom.type]") + LAZYREMOVE(reaction_list, equilibrium) + + var/reaction_message = equilibrium.reaction.mix_message + if(equilibrium.reaction.mix_sound) + playsound(get_turf(my_atom), equilibrium.reaction.mix_sound, 80, TRUE) + qdel(equilibrium) + update_total() + SEND_SIGNAL(src, COMSIG_REAGENTS_REACTED, .) + return reaction_message + +/* +* This stops the holder from processing at the end of a series of reactions (i.e. when all the equilibriums are completed) +* Also resets reaction variables to be null/empty/FALSE so that it can restart correctly in the future +*/ +/datum/reagents/proc/finish_reacting() + STOP_PROCESSING(SSreagents, src) + is_reacting = FALSE + LAZYNULL(previous_reagent_list) //reset it to 0 - because any change will be different now. + update_total() + +/* +* Force stops the current holder/reagents datum from reacting +* Calls end_reaction() for each equlilbrium datum in reaction_list and finish_reacting() +* Usually only called when a datum is transferred into a NO_REACT container +*/ +/datum/reagents/proc/force_stop_reacting() + var/list/mix_message = list() + for(var/datum/equilibrium/equilibrium as anything in reaction_list) + mix_message += end_reaction(equilibrium) + if(my_atom && length(mix_message)) + my_atom.audible_message(span_notice("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))] [mix_message.Join()]")) + finish_reacting() + +/* +* Force stops a specific reagent's associated reaction if it exists +* +* Returns TRUE if it stopped something, FALSE if it didn't +* Arguments: +* * reagent - the reagent PRODUCT that we're seeking reactions for, any and all found will be shut down +*/ +/datum/reagents/proc/force_stop_reagent_reacting(datum/reagent/reagent) + var/any_stopped = FALSE + var/list/mix_message = list() + for(var/datum/equilibrium/equilibrium as anything in reaction_list) + for(var/result in equilibrium.reaction.results) + if(result == reagent.type) + mix_message += end_reaction(equilibrium) + any_stopped = TRUE + if(length(mix_message)) + my_atom.audible_message(span_notice("[icon2html(my_atom, viewers(DEFAULT_MESSAGE_RANGE, src))][mix_message.Join()]")) + return any_stopped + +/* +* Transfers the reaction_list to a new reagents datum +* +* Arguments: +* * target - the datum/reagents that this src is being transferred into +*/ +/datum/reagents/proc/transfer_reactions(datum/reagents/target) + if(QDELETED(target)) + CRASH("transfer_reactions() had a [target] ([target.type]) passed to it when it was set to qdel, or it isn't a reagents datum.") + if(!reaction_list) + return + for(var/datum/equilibrium/reaction_source as anything in reaction_list) + var/exists = FALSE + for(var/datum/equilibrium/reaction_target as anything in target.reaction_list) //Don't add duplicates + if(reaction_source.reaction.type == reaction_target.reaction.type) + exists = TRUE + if(exists) + continue + if(!reaction_source.holder) + CRASH("reaction_source is missing a holder in transfer_reactions()!") + + var/datum/equilibrium/new_E = new (reaction_source.reaction, target)//addition to reaction_list is done in new() + if(new_E.to_delete)//failed startup checks + qdel(new_E) + + target.previous_reagent_list = LAZYLISTDUPLICATE(previous_reagent_list) + target.is_reacting = is_reacting + +/** + * Old reaction mechanics, edited to work on one only + * This is changed from the old - purity of the reagents will affect yield + * + * Arguments + * * [selected_reaction][datum/chemical_reaction] - the chemical reaction to finish instantly + */ +/datum/reagents/proc/instant_react(datum/chemical_reaction/selected_reaction) + var/list/cached_required_reagents = selected_reaction.required_reagents + var/list/cached_results = selected_reaction.results + var/datum/cached_my_atom = my_atom + + //find how much ration of products to create + var/multiplier = INFINITY + for(var/datum/reagent/requirement as anything in cached_required_reagents) + multiplier = min(multiplier, get_reagent_amount(requirement) / cached_required_reagents[requirement]) + multiplier = round(multiplier, CHEMICAL_QUANTISATION_LEVEL) + if(!multiplier)//Incase we're missing reagents - usually from on_reaction being called in an equlibrium when the results.len == 0 handler catches a misflagged reaction + return FALSE + + //average purity to be used in scaling the yield of products formed + var/average_purity = get_average_purity() + + //remove the required reagents + for(var/datum/reagent/requirement as anything in cached_required_reagents)//this is not an object + remove_reagent(requirement, cached_required_reagents[requirement] * multiplier) + + //add the result reagents whose yield depend on the average purity + var/yield + for(var/datum/reagent/product as anything in cached_results) + yield = cached_results[product] * multiplier * average_purity + SSblackbox.record_feedback("tally", "chemical_reaction", yield, product) + add_reagent(product, yield, null, chem_temp, average_purity) + + //play sounds on the target atom + var/list/seen = viewers(4, get_turf(my_atom)) + var/iconhtml = icon2html(cached_my_atom, seen) + if(cached_my_atom) + if(!ismob(cached_my_atom)) // No bubbling mobs + if(selected_reaction.mix_sound) + playsound(get_turf(cached_my_atom), selected_reaction.mix_sound, 80, TRUE) + my_atom.audible_message(span_notice("[iconhtml] [selected_reaction.mix_message]")) + + //use slime extract + if(istype(cached_my_atom, /obj/item/slime_extract)) + var/obj/item/slime_extract/extract = my_atom + extract.extract_uses-- + if(extract.extract_uses <= 0) // give the notification that the slime core is dead + my_atom.visible_message(span_notice("[iconhtml] \The [my_atom]'s power is consumed in the reaction.")) + extract.name = "used slime extract" + extract.desc = "This extract has been used up." + + //finish the reaction + selected_reaction.on_reaction(src, null, multiplier) diff --git a/code/modules/reagents/chemistry/holder/ui_data.dm b/code/modules/reagents/chemistry/holder/ui_data.dm new file mode 100644 index 0000000000000..bc8b3d6c713f6 --- /dev/null +++ b/code/modules/reagents/chemistry/holder/ui_data.dm @@ -0,0 +1,411 @@ +/datum/reagents/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Reagents", "Reaction search") + ui.status = UI_INTERACTIVE //How do I prevent a UI from autoclosing if not in LoS + ui_tags_selected = NONE //Resync with gui on open (gui expects no flags) + ui_reagent_id = null + ui_reaction_id = null + ui.open() + + +/datum/reagents/ui_status(mob/user) + return UI_INTERACTIVE //please advise + +/datum/reagents/ui_state(mob/user) + return GLOB.physical_state + +///Generates a (rough) rate vs temperature graph profile +/datum/reagents/proc/generate_thermodynamic_profile(datum/chemical_reaction/reaction) + var/list/coords = list() + var/x_temp + var/increment + if(reaction.is_cold_recipe) + coords += list(list(0, 0)) + coords += list(list(reaction.required_temp, 0)) + x_temp = reaction.required_temp + increment = (reaction.optimal_temp - reaction.required_temp)/10 + while(x_temp < reaction.optimal_temp) + var/y = (((x_temp - reaction.required_temp)**reaction.temp_exponent_factor)/((reaction.optimal_temp - reaction.required_temp)**reaction.temp_exponent_factor)) + coords += list(list(x_temp, y)) + x_temp += increment + else + coords += list(list(reaction.required_temp, 0)) + x_temp = reaction.required_temp + increment = (reaction.required_temp - reaction.optimal_temp)/10 + while(x_temp > reaction.optimal_temp) + var/y = (((x_temp - reaction.required_temp)**reaction.temp_exponent_factor)/((reaction.optimal_temp - reaction.required_temp)**reaction.temp_exponent_factor)) + coords += list(list(x_temp, y)) + x_temp -= increment + + coords += list(list(reaction.optimal_temp, 1)) + if(reaction.overheat_temp == NO_OVERHEAT) + if(reaction.is_cold_recipe) + coords += list(list(reaction.optimal_temp+10, 1)) + else + coords += list(list(reaction.optimal_temp-10, 1)) + return coords + coords += list(list(reaction.overheat_temp, 1)) + coords += list(list(reaction.overheat_temp, 0)) + return coords + +/datum/reagents/proc/generate_explosive_profile(datum/chemical_reaction/reaction) + if(reaction.overheat_temp == NO_OVERHEAT) + return null + var/list/coords = list() + coords += list(list(reaction.overheat_temp, 0)) + coords += list(list(reaction.overheat_temp, 1)) + if(reaction.is_cold_recipe) + coords += list(list(reaction.overheat_temp-50, 1)) + coords += list(list(reaction.overheat_temp-50, 0)) + else + coords += list(list(reaction.overheat_temp+50, 1)) + coords += list(list(reaction.overheat_temp+50, 0)) + return coords + + +///Returns a string descriptor of a reactions themic_constant +/datum/reagents/proc/determine_reaction_thermics(datum/chemical_reaction/reaction) + var/thermic = reaction.thermic_constant + if(reaction.reaction_flags & REACTION_HEAT_ARBITARY) + thermic *= 100 //Because arbitary is a lower scale + switch(thermic) + if(-INFINITY to -1500) + return "Overwhelmingly endothermic" + if(-1500 to -1000) + return "Extremely endothermic" + if(-1000 to -500) + return "Strongly endothermic" + if(-500 to -200) + return "Moderately endothermic" + if(-200 to -50) + return "Endothermic" + if(-50 to 0) + return "Weakly endothermic" + if(0) + return "" + if(0 to 50) + return "Weakly Exothermic" + if(50 to 200) + return "Exothermic" + if(200 to 500) + return "Moderately exothermic" + if(500 to 1000) + return "Strongly exothermic" + if(1000 to 1500) + return "Extremely exothermic" + if(1500 to INFINITY) + return "Overwhelmingly exothermic" + +/datum/reagents/proc/parse_addictions(datum/reagent/reagent) + var/addict_text = list() + for(var/entry in reagent.addiction_types) + var/datum/addiction/ref = SSaddiction.all_addictions[entry] + switch(reagent.addiction_types[entry]) + if(-INFINITY to 0) + continue + if(0 to 5) + addict_text += "Weak [ref.name]" + if(5 to 10) + addict_text += "[ref.name]" + if(10 to 20) + addict_text += "Strong [ref.name]" + if(20 to INFINITY) + addict_text += "Potent [ref.name]" + return addict_text + +/datum/reagents/ui_data(mob/user) + var/data = list() + data["selectedBitflags"] = ui_tags_selected + data["currentReagents"] = previous_reagent_list //This keeps the string of reagents that's updated when handle_reactions() is called + data["beakerSync"] = ui_beaker_sync + data["linkedBeaker"] = my_atom.name //To solidify the fact that the UI is linked to a beaker - not a machine. + + //First we check to see if reactions are synced with the beaker + if(ui_beaker_sync) + if(reaction_list)//But we don't want to null the previously displayed if there are none + //makes sure we're within bounds + if(ui_reaction_index > reaction_list.len) + ui_reaction_index = reaction_list.len + ui_reaction_id = reaction_list[ui_reaction_index].reaction.type + + //reagent lookup data + if(ui_reagent_id) + var/datum/reagent/reagent = find_reagent_object_from_type(ui_reagent_id) + if(!reagent) + to_chat(user, "Could not find reagent!") + ui_reagent_id = null + else + data["reagent_mode_reagent"] = list("name" = reagent.name, "id" = reagent.type, "desc" = reagent.description, "reagentCol" = reagent.color, "pH" = reagent.ph, "pHCol" = convert_ph_to_readable_color(reagent.ph), "metaRate" = reagent.metabolization_rate, "OD" = reagent.overdose_threshold) + data["reagent_mode_reagent"]["addictions"] = list() + data["reagent_mode_reagent"]["addictions"] = parse_addictions(reagent) + + var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem] + if(inverse_reagent) + data["reagent_mode_reagent"] += list("inverseReagent" = inverse_reagent.name, "inverseId" = inverse_reagent.type) + + if(reagent.chemical_flags & REAGENT_DEAD_PROCESS) + data["reagent_mode_reagent"] += list("deadProcess" = TRUE) + else + data["reagent_mode_reagent"] = null + + //reaction lookup data + if (ui_reaction_id) + + var/datum/chemical_reaction/reaction = get_chemical_reaction(ui_reaction_id) + if(!reaction) + to_chat(user, "Could not find reaction!") + ui_reaction_id = null + return data + //Required holder + var/container_name + if(reaction.required_container) + var/list/names = splittext("[reaction.required_container]", "/") + container_name = "[names[names.len-1]] [names[names.len]]" + container_name = replacetext(container_name, "_", " ") + + //Next, find the product + var/has_product = TRUE + //If we have no product, use the typepath to create a name for it + if(!length(reaction.results)) + has_product = FALSE + var/list/names = splittext("[reaction.type]", "/") + var/product_name = names[names.len] + data["reagent_mode_recipe"] = list("name" = product_name, "id" = reaction.type, "hasProduct" = has_product, "reagentCol" = "#FFFFFF", "thermodynamics" = generate_thermodynamic_profile(reaction), "explosive" = generate_explosive_profile(reaction), "lowerpH" = reaction.optimal_ph_min, "upperpH" = reaction.optimal_ph_max, "thermics" = determine_reaction_thermics(reaction), "thermoUpper" = reaction.rate_up_lim, "minPurity" = reaction.purity_min, "inversePurity" = "N/A", "tempMin" = reaction.required_temp, "explodeTemp" = reaction.overheat_temp, "reqContainer" = container_name, "subReactLen" = 1, "subReactIndex" = 1) + + //If we do have a product then we find it + else + //Find out if we have multiple reactions for the same product + var/datum/reagent/primary_reagent = find_reagent_object_from_type(reaction.results[1])//We use the first product - though it might be worth changing this + //If we're syncing from the beaker + var/list/sub_reactions = list() + if(ui_beaker_sync && reaction_list) + for(var/_ongoing_eq in reaction_list) + var/datum/equilibrium/ongoing_eq = _ongoing_eq + var/ongoing_r = ongoing_eq.reaction + sub_reactions += ongoing_r + else + sub_reactions = get_recipe_from_reagent_product(primary_reagent.type) + var/sub_reaction_length = length(sub_reactions) + var/i = 1 + for(var/datum/chemical_reaction/sub_reaction in sub_reactions) + if(sub_reaction.type == reaction.type) + ui_reaction_index = i //update our index + break + i += 1 + data["reagent_mode_recipe"] = list("name" = primary_reagent.name, "id" = reaction.type, "hasProduct" = has_product, "reagentCol" = primary_reagent.color, "thermodynamics" = generate_thermodynamic_profile(reaction), "explosive" = generate_explosive_profile(reaction), "lowerpH" = reaction.optimal_ph_min, "upperpH" = reaction.optimal_ph_max, "thermics" = determine_reaction_thermics(reaction), "thermoUpper" = reaction.rate_up_lim, "minPurity" = reaction.purity_min, "inversePurity" = primary_reagent.inverse_chem_val, "tempMin" = reaction.required_temp, "explodeTemp" = reaction.overheat_temp, "reqContainer" = container_name, "subReactLen" = sub_reaction_length, "subReactIndex" = ui_reaction_index) + + //Results sweep + var/has_reagent = "default" + for(var/_reagent in reaction.results) + var/datum/reagent/reagent = find_reagent_object_from_type(_reagent) + if(has_reagent(_reagent)) + has_reagent = "green" + data["reagent_mode_recipe"]["products"] += list(list("name" = reagent.name, "id" = reagent.type, "ratio" = reaction.results[reagent.type], "hasReagentCol" = has_reagent)) + + //Reactant sweep + for(var/_reagent in reaction.required_reagents) + var/datum/reagent/reagent = find_reagent_object_from_type(_reagent) + var/color_r = "default" //If the holder is missing the reagent, it's displayed in orange + if(has_reagent(reagent.type)) + color_r = "green" //It's green if it's present + var/tooltip + var/tooltip_bool = FALSE + var/list/sub_reactions = get_recipe_from_reagent_product(reagent.type) + //Get sub reaction possibilities, but ignore ones that need a specific holder atom + var/sub_index = 0 + for(var/datum/chemical_reaction/sub_reaction as anything in sub_reactions) + if(sub_reaction.required_container)//So we don't have slime reactions confusing things + sub_index++ + continue + sub_index++ + break + if(sub_index) + var/datum/chemical_reaction/sub_reaction = sub_reactions[sub_index] + //Subreactions sweep (if any) + for(var/_sub_reagent in sub_reaction.required_reagents) + var/datum/reagent/sub_reagent = find_reagent_object_from_type(_sub_reagent) + tooltip += "[sub_reaction.required_reagents[_sub_reagent]]u [sub_reagent.name]\n" //I forgot the better way of doing this - fix this after this works + tooltip_bool = TRUE + data["reagent_mode_recipe"]["reactants"] += list(list("name" = reagent.name, "id" = reagent.type, "ratio" = reaction.required_reagents[reagent.type], "color" = color_r, "tooltipBool" = tooltip_bool, "tooltip" = tooltip)) + + //Catalyst sweep + for(var/_reagent in reaction.required_catalysts) + var/datum/reagent/reagent = find_reagent_object_from_type(_reagent) + var/color_r = "default" + if(has_reagent(reagent.type)) + color_r = "green" + var/tooltip + var/tooltip_bool = FALSE + var/list/sub_reactions = get_recipe_from_reagent_product(reagent.type) + if(length(sub_reactions)) + var/datum/chemical_reaction/sub_reaction = sub_reactions[1] + //Subreactions sweep (if any) + for(var/_sub_reagent in sub_reaction.required_reagents) + var/datum/reagent/sub_reagent = find_reagent_object_from_type(_sub_reagent) + tooltip += "[sub_reaction.required_reagents[_sub_reagent]]u [sub_reagent.name]\n" //I forgot the better way of doing this - fix this after this works + tooltip_bool = TRUE + data["reagent_mode_recipe"]["catalysts"] += list(list("name" = reagent.name, "id" = reagent.type, "ratio" = reaction.required_catalysts[reagent.type], "color" = color_r, "tooltipBool" = tooltip_bool, "tooltip" = tooltip)) + data["reagent_mode_recipe"]["isColdRecipe"] = reaction.is_cold_recipe + else + data["reagent_mode_recipe"] = null + + return data + +/datum/reagents/ui_static_data(mob/user) + var/data = list() + //Use GLOB list - saves processing + data["master_reaction_list"] = GLOB.chemical_reactions_results_lookup_list + data["bitflags"] = list() + data["bitflags"]["BRUTE"] = REACTION_TAG_BRUTE + data["bitflags"]["BURN"] = REACTION_TAG_BURN + data["bitflags"]["TOXIN"] = REACTION_TAG_TOXIN + data["bitflags"]["OXY"] = REACTION_TAG_OXY + data["bitflags"]["HEALING"] = REACTION_TAG_HEALING + data["bitflags"]["DAMAGING"] = REACTION_TAG_DAMAGING + data["bitflags"]["EXPLOSIVE"] = REACTION_TAG_EXPLOSIVE + data["bitflags"]["OTHER"] = REACTION_TAG_OTHER + data["bitflags"]["DANGEROUS"] = REACTION_TAG_DANGEROUS + data["bitflags"]["EASY"] = REACTION_TAG_EASY + data["bitflags"]["MODERATE"] = REACTION_TAG_MODERATE + data["bitflags"]["HARD"] = REACTION_TAG_HARD + data["bitflags"]["ORGAN"] = REACTION_TAG_ORGAN + data["bitflags"]["DRINK"] = REACTION_TAG_DRINK + data["bitflags"]["FOOD"] = REACTION_TAG_FOOD + data["bitflags"]["SLIME"] = REACTION_TAG_SLIME + data["bitflags"]["DRUG"] = REACTION_TAG_DRUG + data["bitflags"]["UNIQUE"] = REACTION_TAG_UNIQUE + data["bitflags"]["CHEMICAL"] = REACTION_TAG_CHEMICAL + data["bitflags"]["PLANT"] = REACTION_TAG_PLANT + data["bitflags"]["COMPETITIVE"] = REACTION_TAG_COMPETITIVE + + return data + +/* Returns a reaction type by index from an input reagent type +* i.e. the input reagent's associated reactions are found, and the index determines which one to return +* If the index is out of range, it is set to 1 +*/ +/datum/reagents/proc/get_reaction_from_indexed_possibilities(path, index = null) + if(index) + ui_reaction_index = index + var/list/sub_reactions = get_recipe_from_reagent_product(path) + if(!length(sub_reactions)) + to_chat(usr, "There is no recipe associated with this product.") + return FALSE + if(ui_reaction_index > length(sub_reactions)) + ui_reaction_index = 1 + var/datum/chemical_reaction/reaction = sub_reactions[ui_reaction_index] + return reaction.type + +/datum/reagents/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("find_reagent_reaction") + ui_reaction_id = get_reaction_from_indexed_possibilities(text2path(params["id"])) + return TRUE + if("reagent_click") + ui_reagent_id = text2path(params["id"]) + return TRUE + if("recipe_click") + ui_reaction_id = text2path(params["id"]) + return TRUE + if("search_reagents") + var/input_reagent = tgui_input_list(usr, "Select reagent", "Reagent", GLOB.name2reagent) + input_reagent = get_reagent_type_from_product_string(input_reagent) //from string to type + var/datum/reagent/reagent = find_reagent_object_from_type(input_reagent) + if(!reagent) + to_chat(usr, "Could not find reagent!") + return FALSE + ui_reagent_id = reagent.type + return TRUE + if("search_recipe") + var/input_reagent = (input("Enter the name of product reagent", "Input") as text|null) + input_reagent = get_reagent_type_from_product_string(input_reagent) //from string to type + var/datum/reagent/reagent = find_reagent_object_from_type(input_reagent) + if(!reagent) + to_chat(usr, "Could not find product reagent!") + return + ui_reaction_id = get_reaction_from_indexed_possibilities(reagent.type) + return TRUE + if("increment_index") + ui_reaction_index += 1 + if(!ui_beaker_sync || !reaction_list) + ui_reaction_id = get_reaction_from_indexed_possibilities(get_reagent_type_from_product_string(params["id"])) + return TRUE + if("reduce_index") + if(ui_reaction_index == 1) + return + ui_reaction_index -= 1 + if(!ui_beaker_sync || !reaction_list) + ui_reaction_id = get_reaction_from_indexed_possibilities(get_reagent_type_from_product_string(params["id"])) + return TRUE + if("beaker_sync") + ui_beaker_sync = !ui_beaker_sync + return TRUE + if("toggle_tag_brute") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_BRUTE + return TRUE + if("toggle_tag_burn") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_BURN + return TRUE + if("toggle_tag_toxin") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_TOXIN + return TRUE + if("toggle_tag_oxy") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_OXY + return TRUE + if("toggle_tag_healing") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_HEALING + return TRUE + if("toggle_tag_damaging") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_DAMAGING + return TRUE + if("toggle_tag_explosive") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_EXPLOSIVE + return TRUE + if("toggle_tag_other") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_OTHER + return TRUE + if("toggle_tag_easy") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_EASY + return TRUE + if("toggle_tag_moderate") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_MODERATE + return TRUE + if("toggle_tag_hard") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_HARD + return TRUE + if("toggle_tag_organ") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_ORGAN + return TRUE + if("toggle_tag_drink") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_DRINK + return TRUE + if("toggle_tag_food") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_FOOD + return TRUE + if("toggle_tag_dangerous") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_DANGEROUS + return TRUE + if("toggle_tag_slime") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_SLIME + return TRUE + if("toggle_tag_drug") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_DRUG + return TRUE + if("toggle_tag_unique") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_UNIQUE + return TRUE + if("toggle_tag_chemical") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_CHEMICAL + return TRUE + if("toggle_tag_plant") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_PLANT + return TRUE + if("toggle_tag_competitive") + ui_tags_selected = ui_tags_selected ^ REACTION_TAG_COMPETITIVE + return TRUE + if("update_ui") + return TRUE diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index 44c2b72121c9b..c3910529fc86b 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -1,16 +1,3 @@ -/proc/translate_legacy_chem_id(id) - switch (id) - if ("sacid") - return "sulfuricacid" - if ("facid") - return "fluorosulfuricacid" - if ("co2") - return "carbondioxide" - if ("mine_salve") - return "minerssalve" - else - return ckey(id) - /obj/machinery/chem_dispenser name = "chem dispenser" desc = "Creates and dispenses chemicals." @@ -23,21 +10,45 @@ circuit = /obj/item/circuitboard/machine/chem_dispenser processing_flags = NONE + /// The cell used to dispense reagents var/obj/item/stock_parts/cell/cell + /// Efficiency used when converting cell power to reagents var/powerefficiency = 0.1 + /// The current amount this machine is dispensing var/amount = 30 + /// The rate at which this machine recharges the power cell var/recharge_amount = 10 + /// Keep track of the intervals made during recharges var/recharge_counter = 0 + /// The temperature reagents are dispensed into the beaker var/dispensed_temperature = DEFAULT_REAGENT_TEMPERATURE - ///If the UI has the pH meter shown + /// If the UI has the pH meter shown var/show_ph = TRUE + /// The overlay used to display the beaker on the machine var/mutable_appearance/beaker_overlay + /// Icon to display when the machine is powered var/working_state = "dispenser_working" + /// Icon to display when the machine is not powered var/nopower_state = "dispenser_nopower" + /// Should we display the open panel overlay when the panel is opened with a screwdriver var/has_panel_overlay = TRUE + /// The actual beaker inserted into this machine var/obj/item/reagent_containers/beaker = null - //dispensable_reagents is copypasted in plumbing synthesizers. Please update accordingly. (I didn't make it global because that would limit custom chem dispensers) - var/list/dispensable_reagents = list( + /// Dispensable_reagents is copypasted in plumbing synthesizers. Please update accordingly. (I didn't make it global because that would limit custom chem dispensers) + var/list/dispensable_reagents = list() + /// These become available once the manipulator has been upgraded to tier 4 (femto) + var/list/upgrade_reagents = list() + /// These become available once the machine has been emaged + var/list/emagged_reagents = list() + /// Starting purity of the created reagents + var/base_reagent_purity = 1 + /// Records the reagents dispensed by the user if this list is not null + var/list/recording_recipe + /// Saves all the recipes recorded by the machine + var/list/saved_recipes = list() + + /// The default list of dispensable_reagents + var/static/list/default_dispensable_reagents = list( /datum/reagent/aluminium, /datum/reagent/bromine, /datum/reagent/carbon, @@ -64,8 +75,8 @@ /datum/reagent/water, /datum/reagent/fuel ) - //these become available once the manipulator has been upgraded to tier 4 (femto) - var/list/upgrade_reagents = list( + /// The default list of reagents upgrade_reagents + var/static/list/default_upgrade_reagents = list( /datum/reagent/acetone, /datum/reagent/ammonia, /datum/reagent/ash, @@ -73,34 +84,39 @@ /datum/reagent/fuel/oil, /datum/reagent/saltpetre ) - var/list/emagged_reagents = list( + /// The default list of reagents emagged_reagents + var/static/list/default_emagged_reagents = list( /datum/reagent/toxin/carpotoxin, /datum/reagent/medicine/mine_salve, /datum/reagent/medicine/morphine, /datum/reagent/drug/space_drugs, /datum/reagent/toxin ) - /// Starting purity of the created reagents - var/base_reagent_purity = 1 - - var/list/recording_recipe +/obj/machinery/chem_dispenser/Initialize(mapload) + if(dispensable_reagents != null && !dispensable_reagents.len) + dispensable_reagents = default_dispensable_reagents + if(dispensable_reagents) + dispensable_reagents = sort_list(dispensable_reagents, GLOBAL_PROC_REF(cmp_reagents_asc)) - var/list/saved_recipes = list() + if(upgrade_reagents != null && !upgrade_reagents.len) + upgrade_reagents = default_upgrade_reagents + if(upgrade_reagents) + upgrade_reagents = sort_list(upgrade_reagents, GLOBAL_PROC_REF(cmp_reagents_asc)) -/obj/machinery/chem_dispenser/Initialize(mapload) - . = ..() - dispensable_reagents = sort_list(dispensable_reagents, GLOBAL_PROC_REF(cmp_reagents_asc)) + if(emagged_reagents != null && !emagged_reagents.len) + emagged_reagents = default_emagged_reagents if(emagged_reagents) emagged_reagents = sort_list(emagged_reagents, GLOBAL_PROC_REF(cmp_reagents_asc)) - if(upgrade_reagents) - upgrade_reagents = sort_list(upgrade_reagents, GLOBAL_PROC_REF(cmp_reagents_asc)) + + . = ..() // So that we call RefreshParts() after adjusting the lists + if(is_operational) begin_processing() update_appearance() /obj/machinery/chem_dispenser/Destroy() + cell = null QDEL_NULL(beaker) - QDEL_NULL(cell) return ..() /obj/machinery/chem_dispenser/examine(mob/user) @@ -110,17 +126,15 @@ if(in_range(user, src) || isobserver(user)) . += "The status display reads:\n\ Recharging [recharge_amount] power units per interval.\n\ - Power efficiency increased by [round((powerefficiency*1000)-100, 1)]%." + Power efficiency increased by [round((powerefficiency * 1000) -100, 1)]%." . += span_notice("Use RMB to eject a stored beaker.") - /obj/machinery/chem_dispenser/on_set_is_operational(old_value) if(old_value) //Turned off end_processing() else //Turned on begin_processing() - /obj/machinery/chem_dispenser/process(seconds_per_tick) if (recharge_counter >= 8) var/usedpower = cell.give(recharge_amount) @@ -153,7 +167,6 @@ beaker_overlay = display_beaker() . += beaker_overlay - /obj/machinery/chem_dispenser/emag_act(mob/user, obj/item/card/emag/emag_card) if(obj_flags & EMAGGED) balloon_alert(user, "already emagged!") @@ -164,12 +177,10 @@ return TRUE /obj/machinery/chem_dispenser/ex_act(severity, target) - if(severity <= EXPLODE_LIGHT) - return FALSE - return ..() + return severity <= EXPLODE_LIGHT ? FALSE : ..() /obj/machinery/chem_dispenser/contents_explosion(severity, target) - ..() + . = ..() if(!beaker) return @@ -191,45 +202,25 @@ ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "ChemDispenser", name) + ui.open() - var/is_hallucinating = FALSE - if(isliving(user)) - var/mob/living/living_user = user - is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) - - if(is_hallucinating) - ui.set_autoupdate(FALSE) //to not ruin the immersion by constantly changing the fake chemicals + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + ui.set_autoupdate(!is_hallucinating) //to not ruin the immersion by constantly changing the fake chemicals - ui.open() +/obj/machinery/chem_dispenser/ui_static_data(mob/user) + . = ..() + .["showpH"] = show_ph /obj/machinery/chem_dispenser/ui_data(mob/user) - var/data = list() - data["amount"] = amount - data["energy"] = cell.charge ? cell.charge * powerefficiency : "0" //To prevent NaN in the UI. - data["maxEnergy"] = cell.maxcharge * powerefficiency - data["isBeakerLoaded"] = beaker ? 1 : 0 - data["showpH"] = show_ph - - var/beakerContents[0] - var/beakerCurrentVolume = 0 - if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) - for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "volume" = round(R.volume, 0.01), "pH" = R.ph, "purity" = R.purity))) // list in a list because Byond merges the first list... - beakerCurrentVolume += R.volume - data["beakerContents"] = beakerContents - - if (beaker) - data["beakerCurrentVolume"] = round(beakerCurrentVolume, 0.01) - data["beakerMaxVolume"] = beaker.volume - data["beakerTransferAmounts"] = beaker.possible_transfer_amounts - data["beakerCurrentpH"] = round(beaker.reagents.ph, 0.01) - else - data["beakerCurrentVolume"] = null - data["beakerMaxVolume"] = null - data["beakerTransferAmounts"] = null - data["beakerCurrentpH"] = null - - var/chemicals[0] + . = list() + .["amount"] = amount + .["energy"] = cell.charge ? cell.charge * powerefficiency : 0 //To prevent NaN in the UI. + .["maxEnergy"] = cell.maxcharge * powerefficiency + + var/list/chemicals = list() var/is_hallucinating = FALSE if(isliving(user)) var/mob/living/living_user = user @@ -241,23 +232,37 @@ var/chemname = temp.name if(is_hallucinating && prob(5)) chemname = "[pick_list_replacements("hallucination.json", "chemicals")]" - chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name), "pH" = temp.ph, "pHCol" = convert_ph_to_readable_color(temp.ph)))) - data["chemicals"] = chemicals - data["recipes"] = saved_recipes + chemicals += list(list("title" = chemname, "id" = temp.name, "pH" = temp.ph, "pHCol" = convert_ph_to_readable_color(temp.ph))) + .["chemicals"] = chemicals + .["recipes"] = saved_recipes - data["recordingRecipe"] = recording_recipe - data["recipeReagents"] = list() + .["recordingRecipe"] = recording_recipe + .["recipeReagents"] = list() if(beaker?.reagents.ui_reaction_id) var/datum/chemical_reaction/reaction = get_chemical_reaction(beaker.reagents.ui_reaction_id) for(var/_reagent in reaction.required_reagents) var/datum/reagent/reagent = find_reagent_object_from_type(_reagent) - data["recipeReagents"] += ckey(reagent.name) - return data - -/obj/machinery/chem_dispenser/ui_act(action, params) + .["recipeReagents"] += reagent.name + + var/list/beaker_data = null + if(!QDELETED(beaker)) + beaker_data = list() + beaker_data["maxVolume"] = beaker.volume + beaker_data["transferAmounts"] = beaker.possible_transfer_amounts + beaker_data["pH"] = round(beaker.reagents.ph, 0.01) + beaker_data["currentVolume"] = round(beaker.reagents.total_volume, CHEMICAL_VOLUME_ROUNDING) + var/list/beakerContents = list() + if(length(beaker.reagents.reagent_list)) + for(var/datum/reagent/reagent in beaker.reagents.reagent_list) + beakerContents += list(list("name" = reagent.name, "volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING))) // list in a list because Byond merges the first list... + beaker_data["contents"] = beakerContents + .["beaker"] = beaker_data + +/obj/machinery/chem_dispenser/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return + switch(action) if("amount") if(!is_operational || QDELETED(beaker)) @@ -266,7 +271,8 @@ if(target in beaker.possible_transfer_amounts) amount = target work_animation() - . = TRUE + return TRUE + if("dispense") if(!is_operational || QDELETED(cell)) return @@ -277,7 +283,7 @@ var/datum/reagents/holder = beaker.reagents var/to_dispense = max(0, min(amount, holder.maximum_volume - holder.total_volume)) - if(!cell?.use(to_dispense / powerefficiency)) + if(!cell.use(to_dispense / powerefficiency)) say("Not enough energy to complete operation!") return holder.add_reagent(reagent, to_dispense, reagtemp = dispensed_temperature, added_purity = base_reagent_purity) @@ -285,7 +291,8 @@ work_animation() else recording_recipe[reagent_name] += amount - . = TRUE + return TRUE + if("remove") if(!is_operational || recording_recipe) return @@ -293,10 +300,12 @@ if(beaker && (amount in beaker.possible_transfer_amounts)) beaker.reagents.remove_all(amount) work_animation() - . = TRUE + return TRUE + if("eject") - replace_beaker(usr) - . = TRUE + replace_beaker(ui.user) + return TRUE + if("dispense_recipe") if(!is_operational || QDELETED(cell)) return @@ -305,7 +314,7 @@ if(!LAZYLEN(chemicals_to_dispense)) return for(var/key in chemicals_to_dispense) - var/reagent = GLOB.name2reagent[translate_legacy_chem_id(key)] + var/reagent = GLOB.name2reagent[key] var/dispense_amount = chemicals_to_dispense[key] if(!dispensable_reagents.Find(reagent)) return @@ -317,78 +326,88 @@ var/to_dispense = max(0, min(dispense_amount, holder.maximum_volume - holder.total_volume)) if(!to_dispense) continue - if(!cell?.use(to_dispense / powerefficiency)) + if(!cell.use(to_dispense / powerefficiency)) say("Not enough energy to complete operation!") return holder.add_reagent(reagent, to_dispense, reagtemp = dispensed_temperature, added_purity = base_reagent_purity) work_animation() else recording_recipe[key] += dispense_amount - . = TRUE + return TRUE + if("clear_recipes") - if(!is_operational) - return - var/yesno = tgui_alert(usr, "Clear all recipes?",, list("Yes","No")) - if(yesno == "Yes") + if(is_operational && tgui_alert(ui.user, "Clear all recipes?", "Clear?", list("Yes", "No")) == "Yes") saved_recipes = list() - . = TRUE + return TRUE + if("record_recipe") - if(!is_operational) - return - recording_recipe = list() - . = TRUE + if(is_operational) + recording_recipe = list() + return TRUE + if("save_recording") if(!is_operational) return - var/name = tgui_input_text(usr, "What do you want to name this recipe?", "Recipe Name", MAX_NAME_LEN) - if(!usr.can_perform_action(src, ALLOW_SILICON_REACH)) + var/name = tgui_input_text(ui.user, "What do you want to name this recipe?", "Recipe Name", MAX_NAME_LEN) + if(!ui.user.can_perform_action(src, ALLOW_SILICON_REACH)) return - if(saved_recipes[name] && tgui_alert(usr, "\"[name]\" already exists, do you want to overwrite it?",, list("Yes", "No")) == "No") + if(saved_recipes[name] && tgui_alert(ui.user, "\"[name]\" already exists, do you want to overwrite it?",, list("Yes", "No")) == "No") return if(name && recording_recipe) for(var/reagent in recording_recipe) - var/reagent_id = GLOB.name2reagent[translate_legacy_chem_id(reagent)] + var/reagent_id = GLOB.name2reagent[reagent] if(!dispensable_reagents.Find(reagent_id)) visible_message(span_warning("[src] buzzes."), span_hear("You hear a faint buzz.")) - to_chat(usr, span_warning("[src] cannot find [reagent]!")) + to_chat(ui.user, span_warning("[src] cannot find [reagent]!")) playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE) return saved_recipes[name] = recording_recipe recording_recipe = null - . = TRUE + return TRUE + if("cancel_recording") - if(!is_operational) - return - recording_recipe = null - . = TRUE + if(is_operational) + recording_recipe = null + return TRUE + if("reaction_lookup") if(beaker) - beaker.reagents.ui_interact(usr) + beaker.reagents.ui_interact(ui.user) + + var/result = handle_ui_act(action, params, ui, state) + if(isnull(result)) + result = FALSE + return result + +/// Same as ui_act() but to be used by subtypes exclusively +/obj/machinery/chem_dispenser/proc/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + return null /obj/machinery/chem_dispenser/wrench_act(mob/living/user, obj/item/tool) - . = ..() - default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING -/obj/machinery/chem_dispenser/attackby(obj/item/I, mob/living/user, params) - if(default_deconstruction_screwdriver(user, icon_state, icon_state, I)) +/obj/machinery/chem_dispenser/screwdriver_act(mob/living/user, obj/item/tool) + if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) update_appearance() - return - if(default_deconstruction_crowbar(I)) - return - if(is_reagent_container(I) && !(I.item_flags & ABSTRACT) && I.is_open_container()) - var/obj/item/reagent_containers/B = I - . = TRUE //no afterattack - if(!user.transferItemToLoc(B, src)) - return - replace_beaker(user, B) - to_chat(user, span_notice("You add [B] to [src].")) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + +/obj/machinery/chem_dispenser/crowbar_act(mob/living/user, obj/item/tool) + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + +/obj/machinery/chem_dispenser/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) + if(is_reagent_container(tool) && !(tool.item_flags & ABSTRACT) && tool.is_open_container()) + if(!user.transferItemToLoc(tool, src)) + return ..() + replace_beaker(user, tool) ui_interact(user) - else if(!user.combat_mode && !istype(I, /obj/item/card/emag)) - to_chat(user, span_warning("You can't load [I] into [src]!")) - return ..() - else - return ..() + return ITEM_INTERACT_SUCCESS + + return ..() /obj/machinery/chem_dispenser/get_cell() return cell @@ -445,7 +464,7 @@ update_appearance() return TRUE -/obj/machinery/chem_dispenser/on_deconstruction() +/obj/machinery/chem_dispenser/on_deconstruction(disassembled) cell = null if(beaker) beaker.forceMove(drop_location()) @@ -485,7 +504,8 @@ nopower_state = null pass_flags = PASSTABLE show_ph = FALSE - dispensable_reagents = list( + /// The default list of reagents dispensable by the soda dispenser + var/static/list/drinks_dispensable_reagents = list( /datum/reagent/consumable/coffee, /datum/reagent/consumable/space_cola, /datum/reagent/consumable/cream, @@ -513,7 +533,8 @@ /datum/reagent/water, ) upgrade_reagents = null - emagged_reagents = list( + /// The default list of emagged reagents dispensable by the soda dispenser + var/static/list/drink_emagged_reagents = list( /datum/reagent/consumable/ethanol/thirteenloko, /datum/reagent/consumable/ethanol/whiskey_cola, /datum/reagent/toxin/mindbreaker, @@ -522,6 +543,10 @@ base_reagent_purity = 0.5 /obj/machinery/chem_dispenser/drinks/Initialize(mapload) + if(dispensable_reagents != null && !dispensable_reagents.len) + dispensable_reagents = drinks_dispensable_reagents + if(emagged_reagents != null && !emagged_reagents.len) + emagged_reagents = drink_emagged_reagents . = ..() AddComponent(/datum/component/simple_rotation) @@ -550,8 +575,7 @@ /obj/machinery/chem_dispenser/drinks/fullupgrade //fully ugpraded stock parts, emagged desc = "Contains a large reservoir of soft drinks. This model has had its safeties shorted out." - obj_flags = CAN_BE_HIT | EMAGGED - flags_1 = NODECONSTRUCT_1 + obj_flags = CAN_BE_HIT | EMAGGED | NO_DECONSTRUCTION circuit = /obj/item/circuitboard/machine/chem_dispenser/drinks/fullupgrade /obj/machinery/chem_dispenser/drinks/fullupgrade/Initialize(mapload) @@ -566,7 +590,8 @@ base_icon_state = "booze_dispenser" dispensed_temperature = WATER_MATTERSTATE_CHANGE_TEMP circuit = /obj/item/circuitboard/machine/chem_dispenser/drinks/beer - dispensable_reagents = list( + /// The default list of reagents dispensable by the beer dispenser + var/static/list/beer_dispensable_reagents = list( /datum/reagent/consumable/ethanol/absinthe, /datum/reagent/consumable/ethanol/ale, /datum/reagent/consumable/ethanol/applejack, @@ -594,7 +619,8 @@ /datum/reagent/consumable/ethanol/yuyake, ) upgrade_reagents = null - emagged_reagents = list( + /// The default list of emagged reagents dispensable by the beer dispenser + var/static/list/beer_emagged_reagents = list( /datum/reagent/consumable/ethanol, /datum/reagent/iron, /datum/reagent/consumable/mintextract, @@ -602,10 +628,14 @@ /datum/reagent/consumable/ethanol/fernet ) +/obj/machinery/chem_dispenser/drinks/beer/Initialize(mapload) + dispensable_reagents = beer_dispensable_reagents + emagged_reagents = beer_emagged_reagents + . = ..() + /obj/machinery/chem_dispenser/drinks/beer/fullupgrade //fully ugpraded stock parts, emagged desc = "Contains a large reservoir of the good stuff. This model has had its safeties shorted out." - obj_flags = CAN_BE_HIT | EMAGGED - flags_1 = NODECONSTRUCT_1 + obj_flags = CAN_BE_HIT | EMAGGED | NO_DECONSTRUCTION circuit = /obj/item/circuitboard/machine/chem_dispenser/drinks/beer/fullupgrade /obj/machinery/chem_dispenser/drinks/beer/fullupgrade/Initialize(mapload) @@ -615,19 +645,25 @@ /obj/machinery/chem_dispenser/mutagen name = "mutagen dispenser" desc = "Creates and dispenses mutagen." - dispensable_reagents = list(/datum/reagent/toxin/mutagen) + /// The default list of reagents dispensable by mutagen chem dispenser + var/static/list/mutagen_dispensable_reagents = list(/datum/reagent/toxin/mutagen) upgrade_reagents = null - emagged_reagents = list(/datum/reagent/toxin/plasma) + /// The default list of emagged reagents dispensable by mutagen chem dispenser + var/static/list/mutagen_emagged_reagents = list(/datum/reagent/toxin/plasma) +/obj/machinery/chem_dispenser/mutagen/Initialize(mapload) + dispensable_reagents = mutagen_dispensable_reagents + emagged_reagents = mutagen_emagged_reagents + . = ..() /obj/machinery/chem_dispenser/mutagensaltpeter name = "botanical chemical dispenser" desc = "Creates and dispenses chemicals useful for botany." - flags_1 = NODECONSTRUCT_1 - + obj_flags = parent_type::obj_flags | NO_DECONSTRUCTION circuit = /obj/item/circuitboard/machine/chem_dispenser/mutagensaltpeter - dispensable_reagents = list( + /// The default list of dispensable reagents available in the mutagensaltpeter chem dispenser + var/static/list/mutagensaltpeter_dispensable_reagents = list( /datum/reagent/toxin/mutagen, /datum/reagent/saltpetre, /datum/reagent/plantnutriment/eznutriment, @@ -643,10 +679,13 @@ /datum/reagent/diethylamine) upgrade_reagents = null +/obj/machinery/chem_dispenser/mutagensaltpeter/Initialize(mapload) + dispensable_reagents = mutagensaltpeter_dispensable_reagents + . = ..() + /obj/machinery/chem_dispenser/fullupgrade //fully ugpraded stock parts, emagged desc = "Creates and dispenses chemicals. This model has had its safeties shorted out." - obj_flags = CAN_BE_HIT | EMAGGED - flags_1 = NODECONSTRUCT_1 + obj_flags = CAN_BE_HIT | EMAGGED | NO_DECONSTRUCTION circuit = /obj/item/circuitboard/machine/chem_dispenser/fullupgrade /obj/machinery/chem_dispenser/fullupgrade/Initialize(mapload) @@ -664,7 +703,9 @@ working_state = null nopower_state = null use_power = NO_POWER_USE - dispensable_reagents = list( + + /// The default list of dispensable reagents available in the abductor chem dispenser + var/static/list/abductor_dispensable_reagents = list( /datum/reagent/aluminium, /datum/reagent/bromine, /datum/reagent/carbon, @@ -706,3 +747,7 @@ /datum/reagent/consumable/liquidelectricity/enriched, /datum/reagent/medicine/c2/synthflesh ) + +/obj/machinery/chem_dispenser/abductor/Initialize(mapload) + dispensable_reagents = abductor_dispensable_reagents + . = ..() diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index 3716715a2d6cf..a07fd289f0784 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -1,13 +1,3 @@ -///Tutorial states -#define TUT_NO_BUFFER 50 -#define TUT_START 1 -#define TUT_HAS_REAGENTS 2 -#define TUT_IS_ACTIVE 3 -#define TUT_IS_REACTING 4 -#define TUT_FAIL 4.5 -#define TUT_COMPLETE 5 -#define TUT_MISSING 10 - /obj/machinery/chem_heater name = "reaction chamber" //Maybe this name is more accurate? density = TRUE @@ -19,29 +9,24 @@ resistance_flags = FIRE_PROOF | ACID_PROOF circuit = /obj/item/circuitboard/machine/chem_heater + /// The beaker inside this machine var/obj/item/reagent_containers/beaker = null + /// The temperature this heater is trying to acheive var/target_temperature = 300 + /// The energy used by the heater to achieve the target temperature var/heater_coefficient = 0.05 + /// Is the heater on or off var/on = FALSE + /// How much buffer are we transferig per click var/dispense_volume = 1 - //The list of active clients using this heater, so that we can update the UI on a reaction_step. I assume there are multiple clients possible. - var/list/ui_client_list - ///If the user has the tutorial enabled - var/tutorial_active = FALSE - ///What state we're at in the tutorial - var/tutorial_state = 0 /obj/machinery/chem_heater/Initialize(mapload) . = ..() - create_reagents(200, NO_REACT)//Lets save some calculations here - //TODO: comsig reaction_start and reaction_end to enable/disable the UI autoupdater - this doesn't work presently as there's a hard divide between instant and processed reactions + create_reagents(200, NO_REACT) + register_context() -/obj/machinery/chem_heater/deconstruct(disassembled) - . = ..() - if(beaker && disassembled) - UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP) - beaker.forceMove(drop_location()) - beaker = null +/obj/machinery/chem_heater/on_deconstruction(disassembled) + beaker?.forceMove(drop_location()) /obj/machinery/chem_heater/Destroy() if(beaker) @@ -49,11 +34,31 @@ QDEL_NULL(beaker) return ..() -/obj/machinery/chem_heater/Exited(atom/movable/gone, direction) - . = ..() - if(gone == beaker) - beaker = null - update_appearance() + +/obj/machinery/chem_heater/add_context(atom/source, list/context, obj/item/held_item, mob/user) + if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1)) + return NONE + + if(!QDELETED(beaker)) + if(istype(held_item, /obj/item/reagent_containers/dropper) || istype(held_item, /obj/item/reagent_containers/syringe)) + context[SCREENTIP_CONTEXT_LMB] = "Inject" + return CONTEXTUAL_SCREENTIP_SET + if(is_reagent_container(held_item) && held_item.is_open_container()) + context[SCREENTIP_CONTEXT_LMB] = "Replace beaker" + return CONTEXTUAL_SCREENTIP_SET + else if(is_reagent_container(held_item) && held_item.is_open_container()) + context[SCREENTIP_CONTEXT_LMB] = "Insert beaker" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Open panel" + return CONTEXTUAL_SCREENTIP_SET + else if(panel_open && held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + + return NONE + /obj/machinery/chem_heater/update_icon_state() icon_state = "[base_icon_state][beaker ? 1 : 0]b" @@ -63,28 +68,45 @@ . = ..() if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) return - if(!can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH|FORBID_TELEKINESIS_REACH)) + if(!user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH)) return replace_beaker(user) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN +/obj/machinery/chem_heater/Exited(atom/movable/gone, direction) + . = ..() + if(gone == beaker) + UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP) + beaker = null + update_appearance() + /obj/machinery/chem_heater/attack_robot_secondary(mob/user, list/modifiers) return attack_hand_secondary(user, modifiers) /obj/machinery/chem_heater/attack_ai_secondary(mob/user, list/modifiers) return attack_hand_secondary(user, modifiers) +/** + * Replace or eject the beaker inside this machine + * Arguments + * * mob/living/user - the player operating this machine + * * obj/item/reagent_containers/new_beaker - the new beaker to replace the current one if not null else it will just eject + */ /obj/machinery/chem_heater/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker) - if(!user) - return FALSE - if(beaker) - UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP) + PRIVATE_PROC(TRUE) + + if(!QDELETED(beaker)) try_put_in_hand(beaker, user) - beaker = null - if(new_beaker) + + if(!QDELETED(new_beaker)) + if(!user.transferItemToLoc(new_beaker, src)) + update_appearance() + return FALSE beaker = new_beaker - RegisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP, PROC_REF(on_reaction_step)) + RegisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP, TYPE_PROC_REF(/obj/machinery/chem_heater, on_reaction_step)) + update_appearance() + return TRUE /obj/machinery/chem_heater/RefreshParts() @@ -93,206 +115,168 @@ for(var/datum/stock_part/micro_laser/micro_laser in component_parts) heater_coefficient *= micro_laser.tier +/** + * Heats the reagents of the currently inserted beaker only if machine is on & beaker has some reagents inside + * Arguments + * * seconds_per_tick - passed from process() or from reaction_step() + */ +/obj/machinery/chem_heater/proc/heat_reagents(seconds_per_tick) + PRIVATE_PROC(TRUE) + + //must be on and beaker must have something inside to heat + if(!on || (machine_stat & NOPOWER) || QDELETED(beaker) || !beaker.reagents.total_volume) + return FALSE + + //heat the beaker and use some power. we want to use only a small amount of power since this proc gets called frequently + beaker.reagents.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume) + use_power(active_power_usage * seconds_per_tick * 0.3) + return TRUE + +/obj/machinery/chem_heater/proc/on_reaction_step(datum/reagents/holder, num_reactions, seconds_per_tick) + SIGNAL_HANDLER + + //adjust temp + heat_reagents(seconds_per_tick) + + //send updates to ui. faster than SStgui.update_uis + for(var/datum/tgui/ui in src.open_uis) + ui.send_update() + /obj/machinery/chem_heater/examine(mob/user) . = ..() if(in_range(user, src) || isobserver(user)) - . += span_notice("The status display reads: Heating reagents at [heater_coefficient*1000]% speed.") + . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.") + if(!QDELETED(beaker)) + . += span_notice("It has a beaker of [beaker.reagents.total_volume] units capacity.") + if(beaker.reagents.is_reacting) + . += span_notice("Its contents are currently reacting.") + else + . += span_warning("There is no beaker inserted.") + . += span_notice("Its heating is turned [on ? "On" : "Off"].") + . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.") + if(panel_open) + . += span_notice("Its panel is open and can now be [EXAMINE_HINT("pried")] apart.") + else + . += span_notice("Its panel can be [EXAMINE_HINT("pried")] open") /obj/machinery/chem_heater/process(seconds_per_tick) - ..() - //Tutorial logics - if(tutorial_active) - switch(tutorial_state) - if(TUT_NO_BUFFER) - if(reagents.has_reagent(/datum/reagent/reaction_agent/basic_buffer, 5) && reagents.has_reagent(/datum/reagent/reaction_agent/acidic_buffer, 5)) - tutorial_state = TUT_START - - if(TUT_START) - if(!reagents.has_reagent(/datum/reagent/reaction_agent/basic_buffer, 5) || !reagents.has_reagent(/datum/reagent/reaction_agent/acidic_buffer, 5)) - tutorial_state = TUT_NO_BUFFER - return - if(beaker?.reagents.has_reagent(/datum/reagent/mercury, 10) || beaker?.reagents.has_reagent(/datum/reagent/chlorine, 10)) - tutorial_state = TUT_HAS_REAGENTS - if(TUT_HAS_REAGENTS) - if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury, 9)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine, 9))) - tutorial_state = TUT_MISSING - return - if(beaker?.reagents.chem_temp > 374)//If they heated it up as asked - tutorial_state = TUT_IS_ACTIVE - target_temperature = 375 - beaker.reagents.chem_temp = 375 - - if(TUT_IS_ACTIVE) - if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine))) //Slightly concerned that people might take ages to read and it'll react anyways - tutorial_state = TUT_MISSING - return - if(length(beaker?.reagents.reaction_list) == 1)//Only fudge numbers for our intentful reaction - beaker.reagents.chem_temp = 375 - - if(target_temperature >= 390) - tutorial_state = TUT_IS_REACTING - - if(TUT_IS_REACTING) - if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine))) - tutorial_state = TUT_COMPLETE - - if(TUT_COMPLETE) - if(beaker?.reagents.has_reagent(/datum/reagent/consumable/failed_reaction)) - tutorial_state = TUT_FAIL - return - if(!beaker?.reagents.has_reagent(/datum/reagent/medicine/calomel)) - tutorial_state = TUT_MISSING - - if(machine_stat & NOPOWER) + //is_reacting is handled in reaction_step() + if(QDELETED(beaker) || beaker.reagents.is_reacting) return - if(on) - if(beaker?.reagents.total_volume) - if(beaker.reagents.is_reacting)//on_reaction_step() handles this - return - //keep constant with the chemical acclimator please - beaker.reagents.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume) - beaker.reagents.handle_reactions() - use_power(active_power_usage * seconds_per_tick) + if(heat_reagents(seconds_per_tick)) + //create new reactions after temperature adjust + beaker.reagents.handle_reactions() + + //send updates to ui. faster than SStgui.update_uis + for(var/datum/tgui/ui in src.open_uis) + ui.send_update() /obj/machinery/chem_heater/wrench_act(mob/living/user, obj/item/tool) - . = ..() - default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + . = ITEM_INTERACT_BLOCKING + if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) + return ITEM_INTERACT_SUCCESS -/obj/machinery/chem_heater/attackby(obj/item/I, mob/user, params) - if(default_deconstruction_screwdriver(user, "mixer0b", "mixer0b", I)) - return +/obj/machinery/chem_heater/screwdriver_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_screwdriver(user, "mixer0b", "[base_icon_state][beaker ? 1 : 0]b", tool)) + return ITEM_INTERACT_SUCCESS - if(default_deconstruction_crowbar(I)) - return +/obj/machinery/chem_heater/crowbar_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS - if(is_reagent_container(I) && !(I.item_flags & ABSTRACT) && I.is_open_container()) - . = TRUE //no afterattack - var/obj/item/reagent_containers/B = I - if(!user.transferItemToLoc(B, src)) - return - replace_beaker(user, B) - to_chat(user, span_notice("You add [B] to [src].")) - ui_interact(user) - update_appearance() - return +/obj/machinery/chem_heater/attackby(obj/item/held_item, mob/user, params) + if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1)) + return ..() if(beaker) - if(istype(I, /obj/item/reagent_containers/dropper)) - var/obj/item/reagent_containers/dropper/D = I - D.afterattack(beaker, user, 1) - return - if(istype(I, /obj/item/reagent_containers/syringe)) - var/obj/item/reagent_containers/syringe/S = I - S.afterattack(beaker, user, 1) - return - return ..() + if(istype(held_item, /obj/item/reagent_containers/dropper) || istype(held_item, /obj/item/reagent_containers/syringe)) + var/obj/item/reagent_containers/injector = held_item + injector.afterattack(beaker, user, proximity_flag = TRUE) + return TRUE -/obj/machinery/chem_heater/on_deconstruction() - replace_beaker() - return ..() + if(is_reagent_container(held_item) && held_item.is_open_container()) + if(replace_beaker(user, held_item)) + ui_interact(user) + balloon_alert(user, "beaker added!") + return TRUE -///Forces a UI update every time a reaction step happens inside of the beaker it contains. This is so the UI is in sync with the reaction since it's important that the output matches the current conditions for pH adjustment and temperature. -/obj/machinery/chem_heater/proc/on_reaction_step(datum/reagents/holder, num_reactions, seconds_per_tick) - SIGNAL_HANDLER - if(on) - holder.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume * (rand(8,11) * 0.1))//Give it a little wiggle room since we're actively reacting - for(var/ui_client in ui_client_list) - var/datum/tgui/ui = ui_client - if(!ui) - stack_trace("Warning: UI in UI client list is missing in [src] (chem_heater)") - remove_ui_client_list(ui) - continue - ui.send_update() + return ..() /obj/machinery/chem_heater/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "ChemHeater", name) ui.open() - add_ui_client_list(ui) - -/obj/machinery/chem_heater/ui_close(mob/user) - for(var/ui_client in ui_client_list) - var/datum/tgui/ui = ui_client - if(ui.user == user) - remove_ui_client_list(ui) - return ..() - -/* -*This adds an open ui client to the list - so that it can be force updated from reaction mechanisms. -* After adding it to the list, it enables a signal incase the ui is deleted - which will call a method to remove it from the list -* This is mostly to ensure we don't have defunct ui instances stored from any condition. -*/ -/obj/machinery/chem_heater/proc/add_ui_client_list(new_ui) - LAZYADD(ui_client_list, new_ui) - RegisterSignal(new_ui, COMSIG_QDELETING, PROC_REF(on_ui_deletion)) - -///This removes an open ui instance from the ui list and deregsiters the signal -/obj/machinery/chem_heater/proc/remove_ui_client_list(old_ui) - UnregisterSignal(old_ui, COMSIG_QDELETING) - LAZYREMOVE(ui_client_list, old_ui) - -///This catches a signal and uses it to delete the ui instance from the list -/obj/machinery/chem_heater/proc/on_ui_deletion(datum/tgui/source, force) - SIGNAL_HANDLER - remove_ui_client_list(source) - -/obj/machinery/chem_heater/ui_assets() - . = ..() || list() - . += get_asset_datum(/datum/asset/simple/tutorial_advisors) /obj/machinery/chem_heater/ui_data(mob/user) - var/data = list() - data["targetTemp"] = target_temperature - data["isActive"] = on - data["isBeakerLoaded"] = beaker ? 1 : 0 - - data["currentTemp"] = beaker ? beaker.reagents.chem_temp : null - data["beakerCurrentVolume"] = beaker ? round(beaker.reagents.total_volume, 0.01) : null - data["beakerMaxVolume"] = beaker ? beaker.volume : null - data["currentpH"] = beaker ? round(beaker.reagents.ph, 0.01) : null - var/upgrade_level = heater_coefficient*10 - data["upgradeLevel"] = upgrade_level - - var/list/beaker_contents = list() - for(var/r in beaker?.reagents.reagent_list) - var/datum/reagent/reagent = r - beaker_contents.len++ - beaker_contents[length(beaker_contents)] = list("name" = reagent.name, "volume" = round(reagent.volume, 0.01)) - data["beakerContents"] = beaker_contents + . = list() + .["targetTemp"] = target_temperature + .["isActive"] = on + .["upgradeLevel"] = heater_coefficient * 10 + + var/list/beaker_data = null + var/chem_temp = 0 + if(!QDELETED(beaker)) + beaker_data = list() + beaker_data["maxVolume"] = beaker.volume + beaker_data["pH"] = round(beaker.reagents.ph, 0.01) + beaker_data["currentVolume"] = round(beaker.reagents.total_volume, CHEMICAL_VOLUME_ROUNDING) + var/list/beakerContents = list() + if(length(beaker.reagents.reagent_list)) + for(var/datum/reagent/reagent in beaker.reagents.reagent_list) + beakerContents += list(list("name" = reagent.name, "volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING))) // list in a list because Byond merges the first list... + beaker_data["contents"] = beakerContents + chem_temp = beaker.reagents.chem_temp + .["beaker"] = beaker_data + .["currentTemp"] = chem_temp var/list/active_reactions = list() var/flashing = DISABLE_FLASHING //for use with alertAfter - since there is no alertBefore, I set the after to 0 if true, or to the max value if false - for(var/_reaction in beaker?.reagents.reaction_list) - var/datum/equilibrium/equilibrium = _reaction - if(!length(beaker.reagents.reaction_list))//I'm not sure why when it explodes it causes the gui to fail (it's missing danger (?) ) - stack_trace("how is this happening??") - continue + for(var/datum/equilibrium/equilibrium as anything in beaker?.reagents.reaction_list) if(!equilibrium.reaction.results)//Incase of no result reactions continue - var/_reagent = equilibrium.reaction.results[1] - var/datum/reagent/reagent = beaker?.reagents.get_reagent(_reagent) //Reactions are named after their primary products + var/datum/reagents/beaker_reagents = beaker.reagents + var/datum/reagent/reagent = beaker_reagents.has_reagent(equilibrium.reaction.results[1]) //Reactions are named after their primary products if(!reagent) continue + + //check for danger levels primirarly overheating var/overheat = FALSE var/danger = FALSE var/purity_alert = 2 //same as flashing if(reagent.purity < equilibrium.reaction.purity_min) purity_alert = ENABLE_FLASHING//Because 0 is seen as null danger = TRUE - if(!(flashing == ENABLE_FLASHING))//So that the pH meter flashes for ANY reactions out of optimal - if(equilibrium.reaction.optimal_ph_min > beaker?.reagents.ph || equilibrium.reaction.optimal_ph_max < beaker?.reagents.ph) + if(flashing != ENABLE_FLASHING)//So that the pH meter flashes for ANY reactions out of optimal + if(equilibrium.reaction.optimal_ph_min > beaker_reagents.ph || equilibrium.reaction.optimal_ph_max < beaker_reagents.ph) flashing = ENABLE_FLASHING if(equilibrium.reaction.is_cold_recipe) - if(equilibrium.reaction.overheat_temp > beaker?.reagents.chem_temp && equilibrium.reaction.overheat_temp != NO_OVERHEAT) + if(equilibrium.reaction.overheat_temp > beaker_reagents.chem_temp && equilibrium.reaction.overheat_temp != NO_OVERHEAT) danger = TRUE overheat = TRUE else - if(equilibrium.reaction.overheat_temp < beaker?.reagents.chem_temp) + if(equilibrium.reaction.overheat_temp < beaker_reagents.chem_temp) danger = TRUE overheat = TRUE + + //create ui data + active_reactions += list(list( + "name" = reagent.name, + "danger" = danger, + "overheat" = overheat, + "purityAlert" = purity_alert, + "quality" = equilibrium.reaction_quality, + "inverse" = reagent.inverse_chem_val, + "minPure" = equilibrium.reaction.purity_min, + "reactedVol" = equilibrium.reacted_vol, + "targetVol" = round(equilibrium.target_vol, 1) + ) + ) + + //additional data for competitive reactions if(equilibrium.reaction.reaction_flags & REACTION_COMPETITIVE) //We have a compeitive reaction - concatenate the results for the different reactions for(var/entry in active_reactions) if(entry["name"] == reagent.name) //If we have multiple reaction methods for the same result - combine them @@ -300,190 +284,97 @@ entry["targetVol"] = round(equilibrium.target_vol, 1)//Use the first result reagent to name the reaction detected entry["quality"] = (entry["quality"] + equilibrium.reaction_quality) /2 continue - active_reactions.len++ - active_reactions[length(active_reactions)] = list("name" = reagent.name, "danger" = danger, "purityAlert" = purity_alert, "quality" = equilibrium.reaction_quality, "overheat" = overheat, "inverse" = reagent.inverse_chem_val, "minPure" = equilibrium.reaction.purity_min, "reactedVol" = equilibrium.reacted_vol, "targetVol" = round(equilibrium.target_vol, 1))//Use the first result reagent to name the reaction detected - data["activeReactions"] = active_reactions - data["isFlashing"] = flashing - - data["acidicBufferVol"] = reagents.get_reagent_amount(/datum/reagent/reaction_agent/acidic_buffer) - data["basicBufferVol"] = reagents.get_reagent_amount(/datum/reagent/reaction_agent/basic_buffer) - data["dispenseVolume"] = dispense_volume - - data["tutorialMessage"] = null - //Tutorial output - if(tutorial_active) - switch(tutorial_state) - if(TUT_NO_BUFFER)//missing buffer - data["tutorialMessage"] = {"It looks like you’re a little low on buffers, here’s how to make more: - -Acidic buffer: 2 parts Sodium - 2 parts Hydrogen - 2 parts Ethanol - 2 parts Water - -Basic buffer: 3 parts Ammonia - 2 parts Chlorine - 2 parts Hydrogen - 2 parts Oxygen - -Heat either up to speed up the reaction. - -When the reactions are done, refill your chamber by pressing the Draw all buttons, to the right of the respective volume indicators. - -To continue with the tutorial, fill both of your acidic and alkaline volumes to at least 5u."} - if(TUT_START)//Default start - data["tutorialMessage"] = {"Hello and welcome to the exciting world of chemistry! This help option will teach you the basic of reactions by guiding you through a calomel reaction. - -For the majority of reactions, the overheat temperature is 900K, and the pH range is 5-9, though it's always worth looking up the ranges as these are changing. Calomel is no different. - -To continue the tutorial, insert a beaker with at least 10u mercury and 10u chlorine added."} - if(TUT_HAS_REAGENTS) //10u Hg and Cl - data["tutorialMessage"] = {"Good job! You'll see that at present this isn't reacting. That's because this reaction needs a minimum temperature of 375K. - -For the most part the hotter your reaction is, the faster it will react when it’s past it’s minimum temperature. But be careful to not heat it too much! "If your reaction is slow, your temperature is too low"! - -When you’re ready, set your temperature to 375K and heat up the beaker to that amount."} - if(TUT_IS_ACTIVE) //heat 375K - data["tutorialMessage"] = {"Great! You should see your reaction slowly progressing. - -Notice the pH dial on the right; the sum pH should be slowly drifting towards the left on the dial. How pure your solution is at the end depends on how well you keep your reaction within the optimal pH range. The dial will flash if any of the present reactions are outside their optimal. "If you're getting sludge, give your pH a nudge"! - -In a moment, we’ll increase the temperature so that our rate is faster. It’s up to you to keep your pH within the limits, so keep an eye on that dial, and get ready to add basic buffer using the injection button to the left of the volume indicator. - -To continue set your target temperature to 390K."} - if(TUT_IS_REACTING) //Heat 390K - data["tutorialMessage"] = "Stay focused on the reaction! You can do it!" - if(TUT_FAIL) //Sludge - data["tutorialMessage"] = "Ah, unfortunately your purity was too low and the reaction fell apart into errant sludge. Don't worry, you can always try again! Be careful though, for some reactions, failing isn't nearly as forgiving." - if(TUT_COMPLETE) //Complete - var/datum/reagent/calo = beaker?.reagents.has_reagent(/datum/reagent/medicine/calomel) - if(!calo) - tutorial_state = TUT_COMPLETE - return - switch(calo.purity) - if(-INFINITY to 0.25) - data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. That's pretty close to the fail purity of 0.15 - which can often make some reactions explode. This chem will invert into Toxic sludge when ingested by another person, and will not cause of calomel's normal effects. Sneaky, huh?" - if(0.25 to 0.6) - data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. Normally, this reaction will resolve above 0.7 without intervention. Are you praticing impure reactions? The lower you go, the higher change you have of getting dangerous effects during a reaction. In some more dangerous reactions, you're riding a fine line between death and an inverse chem, don't forget you can always chill your reaction to give yourself more time to manage it!" - if(0.6 to 0.75) - data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. Normally, this reaction will resolve above 0.7 without intervention. Did you maybe add too much basic buffer and go past 9? If you like - you're welcome to try again. Just double press the help button!" - if(0.75 to 0.85) - data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. You got pretty close to optimal! Feel free to try again if you like by double pressing the help button." - if(0.75 to 0.99) - data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. You got pretty close to optimal! Feel free to try again if you like by double pressing the help button, but this is a respectable purity." - if(0.99 to 1) - data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. Your calomel is as pure as they come! You've mastered the basics of chemistry, but there's plenty more challenges on the horizon. Good luck!" - user.client?.give_award(/datum/award/achievement/jobs/chemistry_tut, user) - data["tutorialMessage"] += "\n\nDid you notice that your temperature increased past 390K while reacting too? That's because this reaction is exothermic (heat producing), so for some reactions you might have to adjust your target to compensate. Oh, and you can check your purity by researching and printing off a chemical analyzer at the medlathe (for now)!" - if(TUT_MISSING) //Missing - data["tutorialMessage"] = "Uh oh, something went wrong. Did you take the beaker out, heat it up too fast, or have other things in the beaker? Try restarting the tutorial by double pressing the help button." - - return data - -/obj/machinery/chem_heater/ui_act(action, params) + .["activeReactions"] = active_reactions + + .["isFlashing"] = flashing + .["acidicBufferVol"] = reagents.get_reagent_amount(/datum/reagent/reaction_agent/acidic_buffer) + .["basicBufferVol"] = reagents.get_reagent_amount(/datum/reagent/reaction_agent/basic_buffer) + .["dispenseVolume"] = dispense_volume + +/obj/machinery/chem_heater/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return + switch(action) if("power") on = !on - . = TRUE + return TRUE + if("temperature") var/target = params["target"] - if(text2num(target) != null) - target = text2num(target) - . = TRUE - if(.) - target_temperature = clamp(target, 0, 1000) + if(isnull(target)) + return FALSE + + target = text2num(target) + if(isnull(target)) + return FALSE + + target_temperature = clamp(target, 0, 1000) + return TRUE + if("eject") //Eject doesn't turn it off, so you can preheat for beaker swapping - replace_beaker(usr) - . = TRUE + return replace_beaker(ui.user) + if("acidBuffer") var/target = params["target"] - if(text2num(target) != null) - target = text2num(target) - . = TRUE - if(.) - move_buffer("acid", target) + if(!target) + return FALSE + + target = text2num(target) + if(isnull(target)) + return FALSE + + return move_buffer(/datum/reagent/reaction_agent/acidic_buffer, target) if("basicBuffer") var/target = params["target"] - if(text2num(target) != null) - target = text2num(target) //Because the input is flipped - . = TRUE - if(.) - move_buffer("basic", target) + if(!target) + return FALSE + + target = text2num(target) + if(isnull(target)) + return FALSE + + return move_buffer(/datum/reagent/reaction_agent/basic_buffer, target) if("disp_vol") var/target = params["target"] - if(text2num(target) != null) - target = text2num(target) //Because the input is flipped - . = TRUE - if(.) - dispense_volume = target - if("help") - tutorial_active = !tutorial_active - if(tutorial_active) - tutorial_state = 1 - return - tutorial_state = 0 - //Refresh window size - ui_close(usr) - ui_interact(usr, null) - - -///Moves a type of buffer from the heater to the beaker, or vice versa -/obj/machinery/chem_heater/proc/move_buffer(buffer_type, volume) - if(!beaker) + if(!target) + return FALSE + + target = text2num(target) + if(isnull(target)) + return FALSE + + dispense_volume = target + return TRUE + +/** + * Injects either acid/base buffer into the beaker + * Arguments + * * datum/reagent/buffer_type - the type of buffer[acid, base] to inject/withdraw + * * volume - how much to volume to inject -ve values means withdraw + */ +/obj/machinery/chem_heater/proc/move_buffer(datum/reagent/buffer_type, volume) + PRIVATE_PROC(TRUE) + + //no beaker + if(QDELETED(beaker)) say("No beaker found!") - return - if(buffer_type == "acid") - if(volume < 0) - var/datum/reagent/acid_reagent = beaker.reagents.get_reagent(/datum/reagent/reaction_agent/acidic_buffer) - if(!acid_reagent) - say("Unable to find acidic buffer in beaker to draw from! Please insert a beaker containing acidic buffer.") - return - var/datum/reagent/acid_reagent_heater = reagents.get_reagent(/datum/reagent/reaction_agent/acidic_buffer) - var/cur_vol = 0 - if(acid_reagent_heater) - cur_vol = acid_reagent_heater.volume - volume = 100 - cur_vol - beaker.reagents.trans_id_to(src, acid_reagent.type, volume)//negative because we're going backwards - return - //We must be positive here - reagents.trans_id_to(beaker, /datum/reagent/reaction_agent/acidic_buffer, dispense_volume) - return - - if(buffer_type == "basic") - if(volume < 0) - var/datum/reagent/basic_reagent = beaker.reagents.get_reagent(/datum/reagent/reaction_agent/basic_buffer) - if(!basic_reagent) - say("Unable to find basic buffer in beaker to draw from! Please insert a beaker containing basic buffer.") - return - var/datum/reagent/basic_reagent_heater = reagents.get_reagent(/datum/reagent/reaction_agent/basic_buffer) - var/cur_vol = 0 - if(basic_reagent_heater) - cur_vol = basic_reagent_heater.volume - volume = 100 - cur_vol - beaker.reagents.trans_id_to(src, basic_reagent.type, volume)//negative because we're going backwards - return - reagents.trans_id_to(beaker, /datum/reagent/reaction_agent/basic_buffer, dispense_volume) - return - + return FALSE -/obj/machinery/chem_heater/proc/get_purity_color(datum/equilibrium/equilibrium) - var/_reagent = equilibrium.reaction.results[1] - var/datum/reagent/reagent = equilibrium.holder.get_reagent(_reagent) - // Can't be a switch due to http://www.byond.com/forum/post/2750423 - if(reagent.purity in 1 to INFINITY) - return "blue" - else if(reagent.purity in 0.8 to 1) - return "green" - else if(reagent.purity in reagent.inverse_chem_val to 0.8) - return "olive" - else if(reagent.purity in equilibrium.reaction.purity_min to reagent.inverse_chem_val) - return "orange" - else if(reagent.purity in -INFINITY to equilibrium.reaction.purity_min) - return "red" + //trying to absorb buffer from currently inserted beaker + if(volume < 0) + if(!beaker.reagents.has_reagent(buffer_type)) + var/name = initial(buffer_type.name) + say("Unable to find [name] in beaker to draw from! Please insert a beaker containing [name].") + return FALSE + beaker.reagents.trans_to(src, (reagents.maximum_volume / 2) - reagents.get_reagent_amount(buffer_type), target_id = buffer_type) + return TRUE + + //trying to inject buffer into currently inserted beaker + reagents.trans_to(beaker, dispense_volume, target_id = buffer_type) + return TRUE //Has a lot of buffer and is upgraded /obj/machinery/chem_heater/debug @@ -505,12 +396,3 @@ To continue set your target temperature to 390K."} . = ..() reagents.add_reagent(/datum/reagent/reaction_agent/basic_buffer, 20) reagents.add_reagent(/datum/reagent/reaction_agent/acidic_buffer, 20) - -#undef TUT_NO_BUFFER -#undef TUT_START -#undef TUT_HAS_REAGENTS -#undef TUT_IS_ACTIVE -#undef TUT_IS_REACTING -#undef TUT_FAIL -#undef TUT_COMPLETE -#undef TUT_MISSING diff --git a/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm b/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm index af11a30533dd2..c23a86ca9dc86 100644 --- a/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm +++ b/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm @@ -58,14 +58,13 @@ This will not clean any inverted reagents. Inverted reagents will still be corre for(var/datum/stock_part/micro_laser/laser in component_parts) cms_coefficient /= laser.tier -/obj/machinery/chem_mass_spec/deconstruct(disassembled) +/obj/machinery/chem_mass_spec/on_deconstruction(disassembled) if(beaker1) beaker1.forceMove(drop_location()) beaker1 = null if(beaker2) beaker2.forceMove(drop_location()) beaker2 = null - . = ..() /obj/machinery/chem_mass_spec/update_overlays() . = ..() @@ -75,7 +74,7 @@ This will not clean any inverted reagents. Inverted reagents will still be corre /obj/machinery/chem_mass_spec/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /* beaker swapping/attack code */ /obj/machinery/chem_mass_spec/attackby(obj/item/item, mob/user, params) diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index 464df7bf36397..6c961571b613f 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -2,48 +2,6 @@ #define TRANSFER_MODE_MOVE 1 #define TARGET_BEAKER "beaker" #define TARGET_BUFFER "buffer" -#define CAT_CONDIMENTS "condiments" -#define CAT_TUBES "tubes" -#define CAT_PILLS "pills" -#define CAT_PATCHES "patches" - -/// List of containers the Chem Master machine can print -GLOBAL_LIST_INIT(chem_master_containers, list( - CAT_CONDIMENTS = list( - /obj/item/reagent_containers/cup/bottle, - /obj/item/reagent_containers/condiment/flour, - /obj/item/reagent_containers/condiment/sugar, - /obj/item/reagent_containers/condiment/rice, - /obj/item/reagent_containers/condiment/cornmeal, - /obj/item/reagent_containers/condiment/milk, - /obj/item/reagent_containers/condiment/soymilk, - /obj/item/reagent_containers/condiment/yoghurt, - /obj/item/reagent_containers/condiment/saltshaker, - /obj/item/reagent_containers/condiment/peppermill, - /obj/item/reagent_containers/condiment/soysauce, - /obj/item/reagent_containers/condiment/bbqsauce, - /obj/item/reagent_containers/condiment/enzyme, - /obj/item/reagent_containers/condiment/hotsauce, - /obj/item/reagent_containers/condiment/coldsauce, - /obj/item/reagent_containers/condiment/mayonnaise, - /obj/item/reagent_containers/condiment/ketchup, - /obj/item/reagent_containers/condiment/olive_oil, - /obj/item/reagent_containers/condiment/vegetable_oil, - /obj/item/reagent_containers/condiment/peanut_butter, - /obj/item/reagent_containers/condiment/cherryjelly, - /obj/item/reagent_containers/condiment/honey, - /obj/item/reagent_containers/condiment/pack, - ), - CAT_TUBES = list( - /obj/item/reagent_containers/cup/tube - ), - CAT_PILLS = typecacheof(list( - /obj/item/reagent_containers/pill/style - )), - CAT_PATCHES = typecacheof(list( - /obj/item/reagent_containers/pill/patch/style - )) -)) /obj/machinery/chem_master name = "ChemMaster 3000" @@ -59,7 +17,7 @@ GLOBAL_LIST_INIT(chem_master_containers, list( /// Icons for different percentages of buffer reagents var/fill_icon = 'icons/obj/medical/reagent_fillings.dmi' var/fill_icon_state = "chemmaster" - var/list/fill_icon_thresholds = list(10,20,30,40,50,60,70,80,90,100) + var/static/list/fill_icon_thresholds = list(10, 20, 30, 40, 50, 60, 70, 80, 90, 100) /// Inserted reagent container var/obj/item/reagent_containers/beaker /// Whether separated reagents should be moved back to container or destroyed. @@ -101,7 +59,7 @@ GLOBAL_LIST_INIT(chem_master_containers, list( QDEL_NULL(beaker) return ..() -/obj/machinery/chem_master/on_deconstruction() +/obj/machinery/chem_master/on_deconstruction(disassembled) replace_beaker() return ..() @@ -166,30 +124,36 @@ GLOBAL_LIST_INIT(chem_master_containers, list( . += filling /obj/machinery/chem_master/wrench_act(mob/living/user, obj/item/tool) - . = ..() - default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING -/obj/machinery/chem_master/attackby(obj/item/item, mob/user, params) - if(default_deconstruction_screwdriver(user, icon_state, icon_state, item)) +/obj/machinery/chem_master/screwdriver_act(mob/living/user, obj/item/tool) + if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) update_appearance(UPDATE_ICON) - return - if(default_deconstruction_crowbar(item)) - return - if(is_reagent_container(item) && !(item.item_flags & ABSTRACT) && item.is_open_container()) - . = TRUE // No afterattack - var/obj/item/reagent_containers/beaker = item - replace_beaker(user, beaker) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + +/obj/machinery/chem_master/crowbar_act(mob/living/user, obj/item/tool) + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + +/obj/machinery/chem_master/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) + if(is_reagent_container(tool) && !(tool.item_flags & ABSTRACT) && tool.is_open_container()) + replace_beaker(user, tool) if(!panel_open) ui_interact(user) + return ITEM_INTERACT_SUCCESS + return ..() /obj/machinery/chem_master/attack_hand_secondary(mob/user, list/modifiers) . = ..() if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) - return + return . if(!can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH|FORBID_TELEKINESIS_REACH)) - return + return . replace_beaker(user) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN @@ -213,9 +177,9 @@ GLOBAL_LIST_INIT(chem_master_containers, list( /obj/machinery/chem_master/proc/load_printable_containers() printable_containers = list( - CAT_TUBES = GLOB.chem_master_containers[CAT_TUBES], - CAT_PILLS = GLOB.chem_master_containers[CAT_PILLS], - CAT_PATCHES = GLOB.chem_master_containers[CAT_PATCHES], + CAT_TUBES = GLOB.reagent_containers[CAT_TUBES], + CAT_PILLS = GLOB.reagent_containers[CAT_PILLS], + CAT_PATCHES = GLOB.reagent_containers[CAT_PATCHES], ) /obj/machinery/chem_master/ui_assets(mob/user) @@ -379,10 +343,11 @@ GLOBAL_LIST_INIT(chem_master_containers, list( // Generate item name var/item_name_default = initial(container_style.name) + var/datum/reagent/master_reagent = reagents.get_master_reagent() if(selected_container == default_container) // Tubes and bottles gain reagent name - item_name_default = "[reagents.get_master_reagent_name()] [item_name_default]" + item_name_default = "[master_reagent.name] [item_name_default]" if(!(initial(container_style.reagent_flags) & OPENCONTAINER)) // Closed containers get both reagent name and units in the name - item_name_default = "[reagents.get_master_reagent_name()] [item_name_default] ([volume_in_each]u)" + item_name_default = "[master_reagent.name] [item_name_default] ([volume_in_each]u)" var/item_name = tgui_input_text(usr, "Container name", "Name", @@ -434,7 +399,7 @@ GLOBAL_LIST_INIT(chem_master_containers, list( if (target == TARGET_BUFFER) if(!check_reactions(reagent, beaker.reagents)) return FALSE - beaker.reagents.trans_id_to(src, reagent.type, amount) + beaker.reagents.trans_to(src, amount, target_id = reagent.type) update_appearance(UPDATE_ICON) return TRUE @@ -445,7 +410,7 @@ GLOBAL_LIST_INIT(chem_master_containers, list( if (target == TARGET_BEAKER && transfer_mode == TRANSFER_MODE_MOVE) if(!check_reactions(reagent, reagents)) return FALSE - reagents.trans_id_to(beaker, reagent.type, amount) + reagents.trans_to(beaker, amount, target_id = reagent.type) update_appearance(UPDATE_ICON) return TRUE @@ -489,14 +454,10 @@ GLOBAL_LIST_INIT(chem_master_containers, list( /obj/machinery/chem_master/condimaster/load_printable_containers() printable_containers = list( - CAT_CONDIMENTS = GLOB.chem_master_containers[CAT_CONDIMENTS], + CAT_CONDIMENTS = GLOB.reagent_containers[CAT_CONDIMENTS], ) #undef TRANSFER_MODE_DESTROY #undef TRANSFER_MODE_MOVE #undef TARGET_BEAKER #undef TARGET_BUFFER -#undef CAT_CONDIMENTS -#undef CAT_TUBES -#undef CAT_PILLS -#undef CAT_PATCHES diff --git a/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm b/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm index ce409dd29a81d..9d81188fb405e 100644 --- a/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm +++ b/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm @@ -2,238 +2,277 @@ * A debug chem tester that will process through all recipies automatically and try to react them. * Highlights low purity reactions and and reactions that don't happen */ + +///don't alter the temperatrue of the reaction +#define USE_REACTION_TEMPERATURE 0 +///force a user specified value for temperature on the reaction +#define USE_USER_TEMPERATURE 1 +///force the minimum required temperature for the reaction to start on the reaction +#define USE_MINIMUM_TEMPERATURE 2 +///force the optimal temperature for the reaction +#define USE_OPTIMAL_TEMPERATURE 3 +///force the overheat temperature for the reaction. At this point reagents start to decrease +#define USE_OVERHEAT_TEMPERATURE 4 + +///Play the next reaction i.e. increment current_reaction_index +#define PLAY_NEXT_REACTION 0 +///Play the previous reaction i.e. decrement current_reaction_index +#define PLAY_PREVIOUS_REACTION 1 +///Pick a reaction at random i.e. user decides via input list what the value of current_reaction_index should be +#define PLAY_USER_REACTION 2 + +///Maximum volume of reagents this machine & its required container can hold +#define MAXIMUM_HOLDER_VOLUME 9000 + /obj/machinery/chem_recipe_debug name = "chemical reaction tester" - density = TRUE icon = 'icons/obj/medical/chemical.dmi' icon_state = "HPLC_debug" + density = TRUE idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.4 resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE - ///List of every reaction in the game kept locally for easy access - var/list/cached_reactions = list() - ///What index in the cached_reactions we're in - var/index = 1 - ///If the machine is currently processing through the list - var/processing = FALSE - ///Final output that highlights all of the reactions with inoptimal purity/voolume at base - var/problem_string - ///Final output that highlights all of the reactions with inoptimal purity/voolume at base - var/impure_string - ///The count of reactions that resolve between 1 - 0.9 purity - var/minorImpurity - ///The count of reactions that resolve below 0.9 purity - var/majorImpurity - ///If we failed to react this current chem so use a lower temp - all reactions only - var/failed = 0 - ///If we're forcing optimal conditions - var/should_force_temp = FALSE - var/should_force_ph = FALSE - ///Forced values - var/force_temp = 300 - var/force_ph = 7 - ///Multiplier of product - var/vol_multiplier = 20 - ///If we're reacting - var/react = FALSE - ///Number of delta times taken to react - var/react_time = 0 - ///IF we're doing EVERY reaction - var/process_all = FALSE - ///The name - var/list/reaction_names = list() - ///If it's started - var/reaction_stated = FALSE - ///If we spawn a beaker at the end of a reaction or not - var/beaker_spawn = FALSE - ///If we force min temp on reaction setup - var/min_temp = FALSE - ///The recipe we're editing - var/datum/chemical_reaction/edit_recipe - -///Create reagents datum + + ///Temperature to be imposed on the reaction + var/forced_temp = DEFAULT_REAGENT_TEMPERATURE + ///The mode for setting reaction temps. see temp defines + var/temp_mode = USE_REACTION_TEMPERATURE + ///The ph to be imposed on the reaction + var/forced_ph = CHEMICAL_NORMAL_PH + ///if TRUE will use forced_ph else don't alter the ph of the reaction + var/use_forced_ph = FALSE + ///The purity of all reagents to be imposed on the reaction + var/forced_purity = 1.0 + ///If TRUE will use forced_purity else don't alter the purity of the reaction + var/use_forced_purity = FALSE + ///The multiplier to be applied on the selected reaction required reagents to start the reaction + var/volume_multiplier = 1 + + ///Cached copy all reactions mapped with their name + var/static/list/all_reaction_list + ///The list of reactions to test + var/list/datum/chemical_reaction/reactions_to_test = list() + ///The index in reactions_to_test list which points to the current reaction under test + var/current_reaction_index = 0 + ///Decides which reaction to play in the reactions_to_test list see Play defines + var/current_reaction_mode = PLAY_NEXT_REACTION + ///The current reaction we are editing + var/datum/chemical_reaction/edit_reaction + ///The current var of the reaction we are editing + var/edit_var = "Required Temp" + + ///The target reagents to we are working with. can vary if an reaction requires a specific container + var/datum/reagents/target_reagents + ///The beaker inside this machine, if null will create a new one + var/obj/item/reagent_containers/cup/beaker/bluespace/beaker + ///The default reagent container required for the selected test reaction if any + var/obj/item/reagent_containers/required_container + /obj/machinery/chem_recipe_debug/Initialize(mapload) . = ..() - create_reagents(9000)//I want to make sure everything fits - end_processing() -///Enable the machine -/obj/machinery/chem_recipe_debug/attackby(obj/item/I, mob/user, params) + create_reagents(MAXIMUM_HOLDER_VOLUME) + target_reagents = reagents + RegisterSignal(reagents, COMSIG_REAGENTS_REACTION_STEP, TYPE_PROC_REF(/obj/machinery/chem_recipe_debug, on_reaction_step)) + register_context() + + if(isnull(all_reaction_list)) + all_reaction_list = list() + for(var/datum/reagent/reagent as anything in GLOB.chemical_reactions_list_reactant_index) + for(var/datum/chemical_reaction/reaction as anything in GLOB.chemical_reactions_list_reactant_index[reagent]) + all_reaction_list[extract_reaction_name(reaction)] = reaction + +/obj/machinery/chem_recipe_debug/Destroy() + reactions_to_test.Cut() + target_reagents = null + edit_reaction = null + QDEL_NULL(beaker) + QDEL_NULL(required_container) + UnregisterSignal(reagents, COMSIG_REAGENTS_REACTION_STEP) . = ..() - ui_interact(usr) -///Enable the machine -/obj/machinery/chem_recipe_debug/AltClick(mob/living/user) +/obj/machinery/chem_recipe_debug/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = ..() - if(processing) - say("currently processing reaction [index]: [cached_reactions[index]] of [cached_reactions.len]") - return - process_all = TRUE - say("Starting processing") - setup_reactions() - begin_processing() - -///Resets the index, and creates the cached_reaction list from all possible reactions -/obj/machinery/chem_recipe_debug/proc/setup_reactions() - cached_reactions = list() - if(process_all) - for(var/reaction in GLOB.chemical_reactions_list_reactant_index) - if(is_type_in_list(GLOB.chemical_reactions_list_reactant_index[reaction], cached_reactions)) - continue - cached_reactions += GLOB.chemical_reactions_list_reactant_index[reaction] + if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1)) + return NONE + + if(!QDELETED(beaker)) + if(is_reagent_container(held_item) && held_item.is_open_container()) + context[SCREENTIP_CONTEXT_LMB] = "Replace beaker" + return CONTEXTUAL_SCREENTIP_SET + else if(is_reagent_container(held_item) && held_item.is_open_container()) + context[SCREENTIP_CONTEXT_LMB] = "Insert beaker" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/chem_recipe_debug/examine(mob/user) + . = ..() + if(!QDELETED(beaker)) + . += span_notice("A beaker of [beaker.reagents.maximum_volume]u capacity is inside.") else - cached_reactions = reaction_names - reagents.clear_reagents() - index = 1 - processing = TRUE + . += span_notice("No beaker is present. A new will be created when ejecting.") + +/obj/machinery/chem_recipe_debug/Exited(atom/movable/gone, direction) + . = ..() + if(gone == beaker) + beaker = null + +/obj/machinery/chem_recipe_debug/attackby(obj/item/held_item, mob/user, params) + if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1)) + return ..() + + if(is_reagent_container(held_item) && held_item.is_open_container()) + . = TRUE + if(!QDELETED(beaker)) + try_put_in_hand(beaker, user) + if(!user.transferItemToLoc(held_item, src)) + return + beaker = held_item + +/** + * Extracts a human readable name for this chemical reaction + * Arguments + * + * * datum/chemical_reaction/reaction - the reaction who's name we have to decode + */ +/obj/machinery/chem_recipe_debug/proc/extract_reaction_name(datum/chemical_reaction/reaction) + PRIVATE_PROC(TRUE) + SHOULD_BE_PURE(TRUE) + + var/reaction_name = "[reaction]" + reaction_name = copytext(reaction_name, findlasttext(reaction_name, "/") + 1) + reaction_name = replacetext(reaction_name, "_", " ") + return full_capitalize(reaction_name) + +///Retrives the target temperature to be imposed on the test reaction based on temp_mode +/obj/machinery/chem_recipe_debug/proc/decode_target_temperature() + PRIVATE_PROC(TRUE) + SHOULD_BE_PURE(TRUE) + + if(temp_mode == USE_REACTION_TEMPERATURE) + return null //simply means don't alter the reaction temperature + else if(temp_mode == USE_USER_TEMPERATURE) + return forced_temp + else + var/datum/chemical_reaction/test_reaction = reactions_to_test[current_reaction_index || 1] + switch(temp_mode) + if(USE_MINIMUM_TEMPERATURE) + return test_reaction.required_temp + (test_reaction.is_cold_recipe ? - 20 : 20) //20k is good enough offset to account for reaction rate rounding + if(USE_OPTIMAL_TEMPERATURE) + return test_reaction.optimal_temp + if(USE_OVERHEAT_TEMPERATURE) + return test_reaction.overheat_temp + + +/** + * Adjusts the temperature, ph & purity of the holder + * Arguments + * + * * seconds_per_tick - passed from on_reaction_step or process + */ +/obj/machinery/chem_recipe_debug/proc/adjust_environment(seconds_per_tick) + PRIVATE_PROC(TRUE) + + var/target_temperature = decode_target_temperature() + if(!isnull(target_temperature)) + target_reagents.adjust_thermal_energy((target_temperature - target_reagents.chem_temp) * 0.4 * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * target_reagents.total_volume) + + if(use_forced_purity) + target_reagents.set_all_reagents_purity(forced_purity) + + if(use_forced_ph) + for(var/datum/reagent/reagent as anything in target_reagents.reagent_list) + reagent.ph = clamp(forced_ph, CHEMICAL_MIN_PH, CHEMICAL_MAX_PH) + + target_reagents.update_total() -/* -* The main loop that sets up, creates and displays results from a reaction -* warning: this code is a hot mess -*/ /obj/machinery/chem_recipe_debug/process(seconds_per_tick) - if(processing == FALSE) - setup_reactions() - if(should_force_ph) - reagents.ph = force_ph - if(should_force_temp) - reagents.chem_temp = force_temp - if(reagents.is_reacting == TRUE) - react_time += seconds_per_tick - return - if(reaction_stated == TRUE) - reaction_stated = FALSE - relay_ended_reaction() - if(index > cached_reactions.len) - relay_all_reactions() + if(!target_reagents.is_reacting) + adjust_environment(seconds_per_tick) + target_reagents.handle_reactions() + + //send updates to ui. faster than SStgui.update_uis + for(var/datum/tgui/ui in src.open_uis) + ui.send_update() + +/obj/machinery/chem_recipe_debug/proc/on_reaction_step(datum/reagents/holder, num_reactions, seconds_per_tick) + SIGNAL_HANDLER + + adjust_environment(seconds_per_tick) + + //send updates to ui. faster than SStgui.update_uis + for(var/datum/tgui/ui in src.open_uis) + ui.send_update() + +/** + * Decodes the ui reaction var into it's original name + * Arguments + * + * * variable - the name of the variable as seen in the UI + */ +/obj/machinery/chem_recipe_debug/proc/decode_var(variable) + PRIVATE_PROC(TRUE) + + . = null + + if(isnull(edit_reaction)) return - setup_reaction() - reaction_stated = TRUE - -/obj/machinery/chem_recipe_debug/proc/relay_all_reactions() - say("Completed testing, missing reactions products (may have exploded) are:") - say("[problem_string]", sanitize=FALSE) - say("Problem with results are:") - say("[impure_string]", sanitize=FALSE) - say("Reactions with minor impurity: [minorImpurity], reactions with major impurity: [majorImpurity]") - processing = FALSE - problem_string = null - impure_string = null - minorImpurity = null - majorImpurity = null - end_processing() - -/obj/machinery/chem_recipe_debug/proc/relay_ended_reaction() - if(reagents.reagent_list) - var/cached_purity - say("Reaction completed for [cached_reactions[index]] final temperature = [reagents.chem_temp], ph = [reagents.ph], time taken = [react_time]s.") - var/datum/chemical_reaction/reaction = cached_reactions[index] - for(var/reagent_type in reaction.results) - var/datum/reagent/reagent = reagents.get_reagent(reagent_type) - if(!reagent) - say(span_warning("Unable to find product [reagent_type] in holder after reaction! reagents found are:")) - for(var/other_reagent in reagents.reagent_list) - say("[other_reagent]") - var/obj/item/reagent_containers/cup/beaker/bluespace/beaker = new /obj/item/reagent_containers/cup/beaker/bluespace(loc) - reagents.trans_to(beaker) - beaker.name = "[cached_reactions[index]] failed" - if(!failed) - problem_string += "[cached_reactions[index]] [span_warning("Unable to find product [reagent_type] in holder after reaction! Trying alternative setup. index:[index]")]\n" - failed++ - return - say("Reaction has a product [reagent_type] [reagent.volume]u purity of [reagent.purity]") - if(reagent.purity < 0.9) - impure_string += "Reaction [cached_reactions[index]] has a product [reagent_type] [reagent.volume]u [span_boldwarning("purity of [reagent.purity]")] index:[index]\n" - majorImpurity++ - else if (reagent.purity < 1) - impure_string += "Reaction [cached_reactions[index]] has a product [reagent_type] [reagent.volume]u [span_warning("purity of [reagent.purity]")] index:[index]\n" - minorImpurity++ - if(reagent.volume < reaction.results[reagent_type]) - impure_string += "Reaction [cached_reactions[index]] has a product [reagent_type] [span_warning("[reagent.volume]u")] purity of [reagent.purity] index:[index]\n" - cached_purity = reagent.purity - if(beaker_spawn && reagents.total_volume) - var/obj/item/reagent_containers/cup/beaker/bluespace/beaker = new /obj/item/reagent_containers/cup/beaker/bluespace(loc) - reagents.trans_to(beaker) - beaker.name = "[cached_reactions[index]] purity: [cached_purity]" - reagents.clear_reagents() - reagents.chem_temp = 300 - index++ - failed = 0 - else - say("No reagents left in beaker!") - index++ - -/obj/machinery/chem_recipe_debug/proc/setup_reaction() - react_time = 0 - if(!length(cached_reactions)) - return FALSE - var/datum/chemical_reaction/reaction = cached_reactions[index] - if(!reaction) - say("Unable to find reaction on index: [index]") - say("Using forced temperatures.") - if(reaction.reaction_flags & REACTION_INSTANT) - say("This reaction is instant") - for(var/reagent_type in reaction.required_reagents) - reagents.add_reagent(reagent_type, reaction.required_reagents[reagent_type]*vol_multiplier) - for(var/catalyst_type in reaction.required_catalysts) - reagents.add_reagent(catalyst_type, reaction.required_catalysts[catalyst_type]) - if(should_force_temp && !min_temp) - say("Using forced temperatures.") - reagents.chem_temp = force_temp ? force_temp : reaction.optimal_temp - if(should_force_ph) - say("Using forced pH.") - reagents.ph = force_ph ? force_ph : (reaction.optimal_ph_max + reaction.optimal_ph_min)/2 - if(failed == 0 && !should_force_temp) - reagents.chem_temp = reaction.optimal_temp - if(failed == 1 && !should_force_temp) - reagents.chem_temp = reaction.required_temp+25 - failed++ - if(min_temp) - say("Overriding temperature to required temp.") - reagents.chem_temp = reaction.is_cold_recipe ? reaction.required_temp - 1 : reaction.required_temp + 1 - say("Reacting [span_nicegreen("[cached_reactions[index]]")] starting pH: [reagents.ph] index [index] of [cached_reactions.len]") + + var/static/list/ui_to_var + if(isnull(ui_to_var)) + ui_to_var = list( + "Required Temp" = "required_temp", + "Optimal Temp" = "optimal_temp", + "Overheat Temp" = "overheat_temp", + "Optimal Min Ph" = "optimal_ph_min", + "Optimal Max Ph" = "optimal_ph_max", + "Ph Range" = "determin_ph_range", + "Temp Exp Factor" = "temp_exponent_factor", + "Ph Exp Factor" = "ph_exponent_factor", + "Thermic Constant" = "thermic_constant", + "H Ion Release" = "H_ion_release", + "Rate Up Limit" = "rate_up_lim", + "Purity Min" = "purity_min", + ) + + var/value = ui_to_var[variable] + if(!isnull(value)) + . = value + +/obj/machinery/chem_recipe_debug/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ChemRecipeDebug", name) + ui.open() /obj/machinery/chem_recipe_debug/ui_data(mob/user) - var/data = list() - data["targetTemp"] = force_temp - data["targatpH"] = force_ph - data["isActive"] = reagents.is_reacting - data["forcepH"] = should_force_ph - data["forceTemp"] = should_force_temp - data["targetVol"] = vol_multiplier - data["processAll"] = process_all - data["currentTemp"] = reagents.chem_temp - data["currentpH"] = round(reagents.ph, 0.01) - data["processing"] = processing - data["index"] = index - data["endIndex"] = cached_reactions.len - data["beakerSpawn"] = beaker_spawn - data["minTemp"] = min_temp - data["editRecipe"] = null - - var/list/beaker_contents = list() - for(var/datum/reagent/reagent as anything in reagents.reagent_list) - beaker_contents.len++ - beaker_contents[length(beaker_contents)] = list("name" = reagent.name, "volume" = round(reagent.volume, 0.01), "purity" = round(reagent.purity)) - data["chamberContents"] = beaker_contents - - var/list/queued_reactions = list() - for(var/datum/chemical_reaction/reaction as anything in reaction_names) - var/datum/reagent/reagent = find_reagent_object_from_type(reaction.results[1]) - queued_reactions.len++ - queued_reactions[length(queued_reactions)] = list("name" = reagent.name) - data["queuedReactions"] = queued_reactions + . = list() + + .["forced_temp"] = forced_temp + .["temp_mode"] = temp_mode + .["forced_ph"] = forced_ph + .["use_forced_ph"] = use_forced_ph + .["forced_purity"] = forced_purity + .["use_forced_purity"] = use_forced_purity + .["volume_multiplier"] = volume_multiplier + + var/datum/chemical_reaction/current_reaction = null + if(reactions_to_test.len) + current_reaction = reactions_to_test[current_reaction_index || 1] + if(isnull(current_reaction)) + .["current_reaction_name"] = "N/A" + else + .["current_reaction_name"] = extract_reaction_name(current_reaction) + .["current_reaction_mode"] = current_reaction_mode var/list/active_reactions = list() var/flashing = DISABLE_FLASHING //for use with alertAfter - since there is no alertBefore, I set the after to 0 if true, or to the max value if false - for(var/datum/equilibrium/equilibrium as anything in reagents.reaction_list) - if(!length(reagents.reaction_list))//I'm not sure why when it explodes it causes the gui to fail (it's missing danger (?) ) - stack_trace("Chem debug managed to find an equilibrium in a location where there should be none (skipping this entry and continuing). This is usually because of an ill timed explosion.") - continue + for(var/datum/equilibrium/equilibrium as anything in target_reagents.reaction_list) if(!equilibrium.reaction.results)//Incase of no result reactions continue - var/datum/reagent/reagent = reagents.get_reagent(equilibrium.reaction.results[1]) //Reactions are named after their primary products + var/datum/reagent/reagent = target_reagents.has_reagent(equilibrium.reaction.results[1]) //Reactions are named after their primary products if(!reagent) continue + + //check for danger levels primirarly overheating var/overheat = FALSE var/danger = FALSE var/purity_alert = 2 //same as flashing @@ -241,16 +280,32 @@ purity_alert = ENABLE_FLASHING//Because 0 is seen as null danger = TRUE if(flashing != ENABLE_FLASHING)//So that the pH meter flashes for ANY reactions out of optimal - if(equilibrium.reaction.optimal_ph_min > reagents.ph || equilibrium.reaction.optimal_ph_max < reagents.ph) + if(equilibrium.reaction.optimal_ph_min > target_reagents.ph || equilibrium.reaction.optimal_ph_max < target_reagents.ph) flashing = ENABLE_FLASHING if(equilibrium.reaction.is_cold_recipe) - if(equilibrium.reaction.overheat_temp > reagents.chem_temp && equilibrium.reaction.overheat_temp != NO_OVERHEAT) + if(equilibrium.reaction.overheat_temp > target_reagents.chem_temp && equilibrium.reaction.overheat_temp != NO_OVERHEAT) danger = TRUE overheat = TRUE else - if(equilibrium.reaction.overheat_temp < reagents.chem_temp) + if(equilibrium.reaction.overheat_temp < target_reagents.chem_temp) danger = TRUE overheat = TRUE + + //create ui data + active_reactions += list(list( + "name" = reagent.name, + "danger" = danger, + "overheat" = overheat, + "purityAlert" = purity_alert, + "quality" = equilibrium.reaction_quality, + "inverse" = reagent.inverse_chem_val, + "minPure" = equilibrium.reaction.purity_min, + "reactedVol" = equilibrium.reacted_vol, + "targetVol" = round(equilibrium.target_vol, 1) + ) + ) + + //additional data for competitive reactions if(equilibrium.reaction.reaction_flags & REACTION_COMPETITIVE) //We have a compeitive reaction - concatenate the results for the different reactions for(var/entry in active_reactions) if(entry["name"] == reagent.name) //If we have multiple reaction methods for the same result - combine them @@ -258,149 +313,373 @@ entry["targetVol"] = round(equilibrium.target_vol, 1)//Use the first result reagent to name the reaction detected entry["quality"] = (entry["quality"] + equilibrium.reaction_quality) /2 continue - active_reactions.len++ - active_reactions[length(active_reactions)] = list("name" = reagent.name, "danger" = danger, "purityAlert" = purity_alert, "quality" = equilibrium.reaction_quality, "overheat" = overheat, "inverse" = reagent.inverse_chem_val, "minPure" = equilibrium.reaction.purity_min, "reactedVol" = equilibrium.reacted_vol, "targetVol" = round(equilibrium.target_vol, 1))//Use the first result reagent to name the reaction detected - data["activeReactions"] = active_reactions - data["isFlashing"] = flashing - - if(edit_recipe) - data["editRecipeName"] = edit_recipe.type - data["editRecipeCold"] = edit_recipe.is_cold_recipe - data["editRecipe"] = list( - list("name" = "required_temp" , "var" = edit_recipe.required_temp), - list("name" = "optimal_temp" , "var" = edit_recipe.optimal_temp), - list("name" = "overheat_temp" , "var" = edit_recipe.overheat_temp), - list("name" = "optimal_ph_min" , "var" = edit_recipe.optimal_ph_min), - list("name" = "optimal_ph_max" , "var" = edit_recipe.optimal_ph_max), - list("name" = "determin_ph_range" , "var" = edit_recipe.determin_ph_range), - list("name" = "temp_exponent_factor" , "var" = edit_recipe.temp_exponent_factor), - list("name" = "ph_exponent_factor" , "var" = edit_recipe.ph_exponent_factor), - list("name" = "thermic_constant" , "var" = edit_recipe.thermic_constant), - list("name" = "H_ion_release" , "var" = edit_recipe.H_ion_release), - list("name" = "rate_up_lim" , "var" = edit_recipe.rate_up_lim), - list("name" = "purity_min" , "var" = edit_recipe.purity_min), + .["activeReactions"] = active_reactions + + .["isFlashing"] = flashing + .["isReacting"] = target_reagents.is_reacting + + var/list/reaction_data = null + if(!isnull(edit_reaction)) + var/reaction_name + if(length(edit_reaction.results)) //soups can have no results + var/datum/reagent/reagent = edit_reaction.results[1] + reaction_name = initial(reagent.name) + else + reaction_name = "[edit_reaction]" + reaction_data = list( + "name" = reaction_name, + "editVar" = edit_var, + "editValue" = edit_reaction.vars[decode_var(edit_var)] ) + .["editReaction"] = reaction_data - return data + var/list/beaker_data = null + if(target_reagents.reagent_list.len) + beaker_data = list() + beaker_data["maxVolume"] = target_reagents.maximum_volume + beaker_data["pH"] = round(target_reagents.ph, 0.01) + beaker_data["purity"] = round(target_reagents.get_average_purity(), 0.01) + beaker_data["currentVolume"] = round(target_reagents.total_volume, CHEMICAL_VOLUME_ROUNDING) + beaker_data["currentTemp"] = round(target_reagents.chem_temp, 1) + beaker_data["purity"] = round(target_reagents.get_average_purity(), 0.001) + var/list/beakerContents = list() + if(length(target_reagents.reagent_list)) + for(var/datum/reagent/reagent in target_reagents.reagent_list) + beakerContents += list(list("name" = reagent.name, "volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING))) -/obj/machinery/chem_recipe_debug/ui_act(action, params) + if(!QDELETED(required_container)) + //as of now we only decode soup pots. If more exotic containers are made make sure to add them here + if(istype(required_container, /obj/item/reagent_containers/cup/soup_pot)) + var/obj/item/reagent_containers/cup/soup_pot/pot = required_container + for(var/obj/item as anything in pot.added_ingredients) + //increment count if item already exists + var/entry_found = FALSE + for(var/list/entry as anything in beakerContents) + if(entry["name"] == item.name) + entry["volume"] += 1 + entry_found = TRUE + break + //new entry if non existent + if(!entry_found) + beakerContents += list(list("name" = item.name, "volume" = 1)) + + beaker_data["contents"] = beakerContents + .["beaker"] = beaker_data + +/obj/machinery/chem_recipe_debug/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return + switch(action) - if("power") - return - if("temperature") + if("forced_temp") + var/target = params["target"] + if(isnull(target)) + return + + target = text2num(target) + if(isnull(target)) + return + + forced_temp = target + return TRUE + + if("temp_mode") var/target = params["target"] - if(text2num(target) != null) - target = text2num(target) - . = TRUE - if(.) - force_temp = clamp(target, 0, 1000) - if("pH") + if(isnull(target)) + return + + switch(target) + if("Reaction Temp") + temp_mode = USE_REACTION_TEMPERATURE + return TRUE + if("Forced Temp") + temp_mode = USE_USER_TEMPERATURE + return TRUE + if("Minimum Temp") + temp_mode = USE_MINIMUM_TEMPERATURE + return TRUE + if("Optimal Temp") + temp_mode = USE_OPTIMAL_TEMPERATURE + return TRUE + if("Overheat Temp") + temp_mode = USE_OVERHEAT_TEMPERATURE + return TRUE + + if("forced_ph") var/target = params["target"] - if(text2num(target) != null) - target = text2num(target) - . = TRUE - if(.) - force_ph = target - if("forceTemp") - should_force_temp = ! should_force_temp - . = TRUE - if("forcepH") - should_force_ph = ! should_force_ph - . = TRUE - if("react") - react = TRUE + if(isnull(target)) + return + + target = text2num(target) + if(isnull(target)) + return + + forced_ph = target return TRUE - if("all") - process_all = !process_all + + if("toggle_forced_ph") + use_forced_ph = !use_forced_ph return TRUE - if("beakerSpawn") - beaker_spawn = !beaker_spawn + + if("forced_purity") + var/target = params["target"] + if(isnull(target)) + return + + target = text2num(target) + if(isnull(target)) + return + + forced_purity = target return TRUE - if("setTargetList") - var/text = tgui_input_text(usr, "Enter a list of Recipe product names separated by commas", "Recipe List", multiline = TRUE) - reaction_names = list() - if(!text) - say("Could not find reaction") - var/list/names = splittext("[text]", ",") - for(var/name in names) - var/datum/reagent/reagent = find_reagent_object_from_type(get_chem_id(name)) - if(!reagent) - say("Could not find [name]") - continue - var/datum/chemical_reaction/reaction = GLOB.chemical_reactions_list_product_index[reagent.type] - if(!reaction) - say("Could not find [name] reaction!") - continue - reaction_names += reaction - if("vol") + + if("toggle_forced_purity") + use_forced_purity = !use_forced_purity + return TRUE + + if("volume_multiplier") var/target = params["target"] - if(text2num(target) != null) - target = text2num(target) - . = TRUE - if(.) - vol_multiplier = clamp(target, 1, 200) - if("start") - if(processing) - say("currently processing reaction [index]: [cached_reactions[index]] of [cached_reactions.len]") + if(isnull(target)) return - say("Starting processing") - index = 1 - setup_reactions() - begin_processing() + + target = text2num(target) + if(isnull(target)) + return + + volume_multiplier = target return TRUE - if("stop") - relay_all_reactions() - if("minTemp") - min_temp = !min_temp - if("setEdit") - var/name = (input("Enter the name of any reagent", "Input") as text|null) - reaction_names = list() - if(!text) - say("Could not find reaction") - var/datum/reagent/reagent = find_reagent_object_from_type(get_chem_id(name)) - if(!reagent) - say("Could not find [name]") + + if("pick_reaction") + var/mode = tgui_alert(usr, "Play all or an specific reaction?","Select Reaction", list("All", "Specific")) + if(mode == "All") + reactions_to_test.Cut() + for(var/reaction as anything in all_reaction_list) + reactions_to_test += all_reaction_list[reaction] + current_reaction_index = 0 + return TRUE + + var/selected_reaction = tgui_input_list(ui.user, "Select Reaction", "Reaction", all_reaction_list) + if(!selected_reaction) return - var/datum/chemical_reaction/reaction = GLOB.chemical_reactions_list_product_index[reagent.type] + + var/datum/chemical_reaction/reaction = all_reaction_list[selected_reaction] if(!reaction) - say("Could not find [name] reaction!") return - edit_recipe = reaction[1] - if("updateVar") + + reactions_to_test.Cut() + reactions_to_test += reaction + current_reaction_index = 0 + return TRUE + + if("reaction_mode") var/target = params["target"] - edit_recipe.vars[params["type"]] = target - if("export") - var/export = {"[edit_recipe.type] -[edit_recipe.is_cold_recipe ? "is_cold_recipe = TRUE" : ""] -required_temp = [edit_recipe.required_temp] -optimal_temp = [edit_recipe.optimal_temp] -overheat_temp = [edit_recipe.overheat_temp] -optimal_ph_min = [edit_recipe.optimal_ph_min] -optimal_ph_max = [edit_recipe.optimal_ph_max] -determin_ph_range = [edit_recipe.determin_ph_range] -temp_exponent_factor = [edit_recipe.temp_exponent_factor] -ph_exponent_factor = [edit_recipe.ph_exponent_factor] -thermic_constant = [edit_recipe.thermic_constant] -H_ion_release = [edit_recipe.H_ion_release] -rate_up_lim = [edit_recipe.rate_up_lim] -purity_min = [edit_recipe.purity_min]"} - say(export) - text2file(export, "[GLOB.log_directory]/chem_parse.txt") + if(isnull(target)) + return + switch(target) + if("Next Reaction") + current_reaction_mode = PLAY_NEXT_REACTION + return TRUE + if("Previous Reaction") + current_reaction_mode = PLAY_PREVIOUS_REACTION + return TRUE + if("Pick Reaction") + current_reaction_mode = PLAY_USER_REACTION + return TRUE -/obj/machinery/chem_recipe_debug/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "ChemRecipeDebug", name) - ui.open() + if("start_reaction") + var/datum/chemical_reaction/test_reaction + + //pick the reaction based on the reaction mode + var/len = reactions_to_test.len + if(len > 1) + switch(current_reaction_mode) + if(PLAY_NEXT_REACTION) + current_reaction_index = (current_reaction_index + 1) % len + if(PLAY_PREVIOUS_REACTION) + current_reaction_index = max(current_reaction_index - 1, 1) + if(PLAY_USER_REACTION) + var/list/reaction_names = list() + for(var/datum/chemical_reaction/reaction as anything in reactions_to_test) + reaction_names += extract_reaction_name(reaction) + if(!reaction_names.len) + return + + var/selected_reaction = tgui_input_list(ui.user, "Select Reaction", "Reaction", reaction_names) + if(!selected_reaction) + return + for(var/i = 1; i <= reaction_names.len; i++) + if(selected_reaction == reaction_names[i]) + current_reaction_index = i + break + test_reaction = reactions_to_test[current_reaction_index] + else if(len == 1) + current_reaction_index = 1 + test_reaction = reactions_to_test[1] + + //clear the previous reaction stuff + target_reagents.force_stop_reacting() + target_reagents.clear_reagents() + + //If the reaction requires a specific container initialize & do other stuff accordingly + target_reagents = reagents + if(!QDELETED(required_container)) + UnregisterSignal(required_container.reagents, COMSIG_REAGENTS_REACTION_STEP) + QDEL_NULL(required_container) + if(!isnull(test_reaction.required_container)) + required_container = new test_reaction.required_container(src) + required_container.create_reagents(MAXIMUM_HOLDER_VOLUME) + target_reagents = required_container.reagents + RegisterSignal(target_reagents, COMSIG_REAGENTS_REACTION_STEP, TYPE_PROC_REF(/obj/machinery/chem_recipe_debug, on_reaction_step)) + + //append everything required + var/list/reagent_list = list() + if(length(test_reaction.required_catalysts)) + reagent_list += test_reaction.required_catalysts + if(length(test_reaction.required_reagents)) + reagent_list += test_reaction.required_reagents + //now add the required reagents + var/target_temperature + switch(temp_mode) + if(USE_REACTION_TEMPERATURE) + target_temperature = DEFAULT_REAGENT_TEMPERATURE + else + target_temperature = decode_target_temperature() + for(var/datum/reagent/_reagent as anything in reagent_list) + var/vol_mul = volume_multiplier + if(length(test_reaction.required_catalysts) && test_reaction.required_catalysts[_reagent.type]) + vol_mul = 1 //catalysts don't need to be present in large amounts + + //add the required reagents with the precise conditions + target_reagents.add_reagent( + _reagent, + reagent_list[_reagent] * vol_mul, + reagtemp = target_temperature, + added_purity = use_forced_purity ? forced_purity : null, + added_ph = use_forced_ph ? forced_ph : null, + no_react = TRUE + ) + + //add solid ingredients for soups + if(istype(test_reaction, /datum/chemical_reaction/food/soup)) + var/datum/chemical_reaction/food/soup/soup_reaction = test_reaction + var/obj/item/reagent_containers/cup/soup_pot/pot = required_container + for(var/obj/item as anything in soup_reaction.required_ingredients) + for(var/_ in 1 to soup_reaction.required_ingredients[item]) + LAZYADD(pot.added_ingredients, new item(pot)) -///Moves a type of buffer from the heater to the beaker, + target_reagents.handle_reactions() + return TRUE + + if("edit_reaction") + var/selected_reaction = tgui_input_list(ui.user, "Select Reaction", "Reaction", all_reaction_list) + if(!selected_reaction) + return -/obj/machinery/chem_recipe_debug/ui_status(mob/user) - return UI_INTERACTIVE + var/datum/chemical_reaction/reaction = all_reaction_list[selected_reaction] + if(!reaction) + return + + edit_reaction = reaction + edit_var = initial(edit_var) + return TRUE + + if("edit_var") + var/target = params["target"] + if(isnull(target)) + return + if(isnull(decode_var(target))) + return + edit_var = target + return TRUE + + if("edit_value") + var/target = params["target"] + if(isnull(target)) + return + + target = text2num(target) + if(isnull(target)) + return + + edit_reaction.vars[decode_var(edit_var)] = target + return TRUE + + if("reset_value") + switch(edit_var) + if("Required Temp") + edit_reaction.required_temp = initial(edit_reaction.required_temp) + return TRUE + if("Optimal Temp") + edit_reaction.optimal_temp = initial(edit_reaction.optimal_temp) + return TRUE + if("Overheat Temp") + edit_reaction.overheat_temp = initial(edit_reaction.overheat_temp) + return TRUE + if("Optimal Min Ph") + edit_reaction.optimal_ph_min = initial(edit_reaction.optimal_ph_min) + return TRUE + if("Optimal Max Ph") + edit_reaction.optimal_ph_max = initial(edit_reaction.optimal_ph_max) + return TRUE + if("Ph Range") + edit_reaction.determin_ph_range = initial(edit_reaction.determin_ph_range) + return TRUE + if("Temp Exp Factor") + edit_reaction.temp_exponent_factor = initial(edit_reaction.temp_exponent_factor) + return TRUE + if("Ph Exp Factor") + edit_reaction.ph_exponent_factor = initial(edit_reaction.ph_exponent_factor) + return TRUE + if("Thermic Constant") + edit_reaction.thermic_constant = initial(edit_reaction.thermic_constant) + return TRUE + if("H Ion Release") + edit_reaction.H_ion_release = initial(edit_reaction.H_ion_release) + return TRUE + if("Rate Up Limit") + edit_reaction.rate_up_lim = initial(edit_reaction.rate_up_lim) + return TRUE + if("Purity Min") + edit_reaction.purity_min = initial(edit_reaction.purity_min) + return TRUE + + if("export") + var/export = "[edit_reaction]\n" + export += "\tis_cold_recipe = [edit_reaction.is_cold_recipe]\n" + export += "\trequired_temp = [edit_reaction.required_temp]\n" + export += "\toptimal_temp = [edit_reaction.optimal_temp]\n" + export += "\toverheat_temp = [edit_reaction.overheat_temp]\n" + export += "\toptimal_ph_min = [edit_reaction.optimal_ph_min]\n" + export += "\toptimal_ph_max = [edit_reaction.optimal_ph_max]\n" + export += "\tdetermin_ph_range = [edit_reaction.determin_ph_range]\n" + export += "\ttemp_exponent_factor = [edit_reaction.temp_exponent_factor]\n" + export += "\tph_exponent_factor = [edit_reaction.ph_exponent_factor]\n" + export += "\tthermic_constant = [edit_reaction.thermic_constant]\n" + export += "\tH_ion_release = [edit_reaction.H_ion_release]\n" + export += "\trate_up_lim = [edit_reaction.rate_up_lim]\n" + export += "\tpurity_min = [edit_reaction.purity_min]\n" + + var/dest = "[GLOB.log_directory]/chem_parse.txt" + text2file(export, dest) + tgui_alert(ui.user, "Saved to [dest]") + + if("eject") + if(!target_reagents.total_volume) + return + if(QDELETED(beaker)) + beaker = new /obj/item/reagent_containers/cup/beaker/bluespace(src) + target_reagents.trans_to(beaker, target_reagents.total_volume) + try_put_in_hand(beaker, ui.user) + return TRUE -/obj/machinery/chem_recipe_debug/ui_state(mob/user) - return GLOB.physical_state +#undef USE_REACTION_TEMPERATURE +#undef USE_MINIMUM_TEMPERATURE +#undef USE_USER_TEMPERATURE +#undef USE_OPTIMAL_TEMPERATURE +#undef USE_OVERHEAT_TEMPERATURE +#undef PLAY_NEXT_REACTION +#undef PLAY_PREVIOUS_REACTION +#undef PLAY_USER_REACTION +#undef MAXIMUM_HOLDER_VOLUME diff --git a/code/modules/reagents/chemistry/machinery/chem_separator.dm b/code/modules/reagents/chemistry/machinery/chem_separator.dm index 2e5571fd4312b..13be8d6554f3a 100644 --- a/code/modules/reagents/chemistry/machinery/chem_separator.dm +++ b/code/modules/reagents/chemistry/machinery/chem_separator.dm @@ -231,7 +231,7 @@ soundloop.start() var/vapor_amount = distillation_rate * seconds_per_tick // Vapor to condenser - reagents.trans_id_to(condenser, separating_reagent.type, vapor_amount) + reagents.trans_to(condenser, vapor_amount, target_id = separating_reagent.type) // Cool the vapor down condenser.set_temperature(air.temperature) // Condense into container diff --git a/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm b/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm index 6de441c6fc7d3..c0cb45dda2aa2 100644 --- a/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm +++ b/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm @@ -6,7 +6,7 @@ base_icon_state = "dispenser" amount = 10 resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF - flags_1 = NODECONSTRUCT_1 + obj_flags = parent_type::obj_flags | NO_DECONSTRUCTION use_power = NO_POWER_USE var/static/list/shortcuts = list( "meth" = /datum/reagent/drug/methamphetamine @@ -20,41 +20,54 @@ ui = new(user, src, "ChemDebugSynthesizer", name) ui.open() -/obj/machinery/chem_dispenser/chem_synthesizer/ui_act(action, params) - . = ..() - if(.) - return +/obj/machinery/chem_dispenser/chem_synthesizer/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state) switch(action) - if("ejectBeaker") - if(beaker) - try_put_in_hand(beaker, usr) - beaker = null - . = TRUE if("input") - var/input_reagent = (input("Enter the name of any reagent", "Input") as text|null) - input_reagent = get_reagent_type_from_product_string(input_reagent) //from string to type + if(QDELETED(beaker)) + return FALSE + + var/selected_reagent = tgui_input_list(ui.user, "Select reagent", "Reagent", GLOB.name2reagent) + if(!selected_reagent) + return FALSE + + var/datum/reagent/input_reagent = GLOB.name2reagent[selected_reagent] if(!input_reagent) - say("REAGENT NOT FOUND") - return - else - if(!beaker) - return - else if(!beaker.reagents && !QDELETED(beaker)) - beaker.create_reagents(beaker.volume) - beaker.reagents.add_reagent(input_reagent, amount, added_purity = (purity/100)) + return FALSE + + beaker.reagents.add_reagent(input_reagent, amount, added_purity = (purity / 100)) + return TRUE + if("makecup") if(beaker) return beaker = new /obj/item/reagent_containers/cup/beaker/bluespace(src) visible_message(span_notice("[src] dispenses a bluespace beaker.")) + return TRUE + if("amount") - var/input = text2num(params["amount"]) - if(input) - amount = input + var/input = params["amount"] + if(isnull(input)) + return FALSE + + input = text2num(input) + if(isnull(input)) + return FALSE + + amount = input + return TRUE + if("purity") - var/input = text2num(params["amount"]) - if(input) - purity = input + var/input = params["amount"] + if(isnull(input)) + return FALSE + + input = text2num(input) + if(isnull(input)) + return FALSE + + purity = input + return TRUE + update_appearance() /obj/machinery/chem_dispenser/chem_synthesizer/Destroy() @@ -64,11 +77,3 @@ /obj/machinery/chem_dispenser/chem_synthesizer/ui_data(mob/user) . = ..() .["purity"] = purity - return . - -/obj/machinery/chem_dispenser/chem_synthesizer/proc/find_reagent(input) - . = FALSE - if(GLOB.chemical_reagents_list[input]) //prefer IDs! - return input - else - return get_chem_id(input) diff --git a/code/modules/reagents/chemistry/machinery/pandemic.dm b/code/modules/reagents/chemistry/machinery/pandemic.dm index 586cb0ce8fa1f..bbde13c78a818 100644 --- a/code/modules/reagents/chemistry/machinery/pandemic.dm +++ b/code/modules/reagents/chemistry/machinery/pandemic.dm @@ -108,7 +108,7 @@ update_appearance() SStgui.update_uis(src) -/obj/machinery/computer/pandemic/on_deconstruction() +/obj/machinery/computer/pandemic/on_deconstruction(disassembled) eject_beaker() . = ..() diff --git a/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm b/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm new file mode 100644 index 0000000000000..791feb800390e --- /dev/null +++ b/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm @@ -0,0 +1,272 @@ +/obj/item/storage/portable_chem_mixer + name = "Portable Chemical Mixer" + desc = "A portable device that dispenses and mixes chemicals using the beakers inserted inside." + icon = 'icons/obj/medical/chemical.dmi' + icon_state = "portablechemicalmixer_open" + worn_icon_state = "portable_chem_mixer" + equip_sound = 'sound/items/equip/toolbelt_equip.ogg' + w_class = WEIGHT_CLASS_HUGE + slot_flags = ITEM_SLOT_BELT + custom_price = PAYCHECK_CREW * 10 + custom_premium_price = PAYCHECK_CREW * 14 + + ///Creating an empty slot for a beaker that can be added to dispense into + var/obj/item/reagent_containers/beaker + ///The amount of reagent that is to be dispensed currently + var/amount = 30 + ///List in which all currently dispensable reagents go + var/list/dispensable_reagents = list() + +/obj/item/storage/portable_chem_mixer/Initialize(mapload) + . = ..() + atom_storage.max_total_storage = 200 + atom_storage.max_slots = 50 + atom_storage.set_holdable(list( + /obj/item/reagent_containers/cup/beaker, + /obj/item/reagent_containers/cup/bottle, + /obj/item/reagent_containers/cup/tube, + /obj/item/reagent_containers/cup/glass/waterbottle, + /obj/item/reagent_containers/condiment, + )) + register_context() + +/obj/item/storage/portable_chem_mixer/Destroy() + dispensable_reagents.Cut() + QDEL_NULL(beaker) + return ..() + +/obj/item/storage/portable_chem_mixer/add_context(atom/source, list/context, obj/item/held_item, mob/user) + context[SCREENTIP_CONTEXT_CTRL_LMB] = "[atom_storage.locked ? "Un" : ""]Lock storage" + if(atom_storage.locked && !QDELETED(beaker)) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Eject beaker" + + if(!isnull(held_item)) + if (!atom_storage.locked || \ + (held_item.item_flags & ABSTRACT) || \ + (held_item.flags_1 & HOLOGRAM_1) || \ + !is_reagent_container(held_item) || \ + !held_item.is_open_container() \ + ) + return CONTEXTUAL_SCREENTIP_SET + context[SCREENTIP_CONTEXT_LMB] = "Insert beaker" + + return CONTEXTUAL_SCREENTIP_SET + +/obj/item/storage/portable_chem_mixer/examine(mob/user) + . = ..() + if(!atom_storage.locked) + . += span_notice("Use [EXAMINE_HINT("Ctrl Click")] to lock in order to use its interface.") + else + . += span_notice("Its storage is locked, use [EXAMINE_HINT("Ctrl Click")] to unlock it.") + if(QDELETED(beaker)) + . += span_notice("A beaker can be inserted to dispense reagents after it is locked.") + else + . += span_notice("A beaker of [beaker.reagents.maximum_volume]u capacity is inserted.") + . += span_notice("It can be ejected with [EXAMINE_HINT("Alt Click")].") + +/obj/item/storage/portable_chem_mixer/update_icon_state() + if(!atom_storage.locked) + icon_state = "portablechemicalmixer_open" + return ..() + if(!QDELETED(beaker)) + icon_state = "portablechemicalmixer_full" + return ..() + icon_state = "portablechemicalmixer_empty" + return ..() + +/obj/item/storage/portable_chem_mixer/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if(!atom_storage.locked) + update_contents() + +/// Reload dispensable reagents from new contents +/obj/item/storage/portable_chem_mixer/proc/update_contents() + PRIVATE_PROC(TRUE) + + dispensable_reagents.Cut() + for (var/obj/item/reagent_containers/container in contents) + var/datum/reagent/key = container.reagents.get_master_reagent() + if(isnull(key)) //no reagent inside container + continue + + var/key_type = key.type + if (!(key_type in dispensable_reagents)) + dispensable_reagents[key_type] = list() + dispensable_reagents[key_type]["reagents"] = list() + dispensable_reagents[key_type]["reagents"] += container.reagents + +/obj/item/storage/portable_chem_mixer/Exited(atom/movable/gone, direction) + . = ..() + if(gone == beaker) + beaker = null + else + update_contents() + +/obj/item/storage/portable_chem_mixer/ex_act(severity, target) + return severity > EXPLODE_LIGHT ? ..() : FALSE + +/obj/item/storage/portable_chem_mixer/attackby(obj/item/weapon, mob/user, params) + if (!atom_storage.locked || \ + (weapon.item_flags & ABSTRACT) || \ + (weapon.flags_1 & HOLOGRAM_1) || \ + !is_reagent_container(weapon) || \ + !weapon.is_open_container() \ + ) + return ..() + + replace_beaker(user, weapon) + update_appearance() + return TRUE + +/** + * Replaces the beaker of the portable chemical mixer with another beaker, or simply adds the new beaker if none is in currently + * + * Checks if a valid user and a valid new beaker exist and attempts to replace the current beaker in the portable chemical mixer with the one in hand. Simply places the new beaker in if no beaker is currently loaded + * Arguments: + * * mob/living/user - The user who is trying to exchange beakers + * * obj/item/reagent_containers/new_beaker - The new beaker that the user wants to put into the device + */ +/obj/item/storage/portable_chem_mixer/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker) + PRIVATE_PROC(TRUE) + + if(!QDELETED(beaker)) + user.put_in_hands(beaker) + + if(!QDELETED(new_beaker)) + if(!user.transferItemToLoc(new_beaker, src)) + return + beaker = new_beaker + +/obj/item/storage/portable_chem_mixer/ui_interact(mob/user, datum/tgui/ui) + if(loc != user) + balloon_alert(user, "hold it in your hand!") + return + if(!atom_storage.locked) + balloon_alert(user, "lock it first!") + return + + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PortableChemMixer", name) + ui.open() + + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + ui.set_autoupdate(!is_hallucinating) // to not ruin the immersion by constantly changing the fake chemicals + +/obj/item/storage/portable_chem_mixer/ui_data(mob/user) + . = list() + .["amount"] = amount + + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + + .["chemicals"] = list() + for(var/datum/reagent/reagent_type as anything in dispensable_reagents) + var/datum/reagent/temp = GLOB.chemical_reagents_list[reagent_type] + if(temp) + var/chemname = temp.name + var/total_volume = 0 + var/total_ph = 0 + for (var/datum/reagents/rs as anything in dispensable_reagents[reagent_type]["reagents"]) + total_volume += rs.total_volume + total_ph = rs.ph + if(is_hallucinating && prob(5)) + chemname = "[pick_list_replacements("hallucination.json", "chemicals")]" + .["chemicals"] += list(list("title" = chemname, "id" = temp.name, "volume" = total_volume, "pH" = total_ph)) + + var/list/beaker_data = null + if(!QDELETED(beaker)) + beaker_data = list() + beaker_data["maxVolume"] = beaker.volume + beaker_data["transferAmounts"] = beaker.possible_transfer_amounts + beaker_data["pH"] = round(beaker.reagents.ph, 0.01) + beaker_data["currentVolume"] = round(beaker.reagents.total_volume, 0.01) + var/list/beakerContents = list() + if(length(beaker.reagents.reagent_list)) + for(var/datum/reagent/reagent in beaker.reagents.reagent_list) + beakerContents += list(list("name" = reagent.name, "volume" = round(reagent.volume, 0.01))) // list in a list because Byond merges the first list... + beaker_data["contents"] = beakerContents + .["beaker"] = beaker_data + +/obj/item/storage/portable_chem_mixer/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + switch(action) + if("amount") + var/target = params["target"] + if(isnull(target)) + return + + target = text2num(target) + if(isnull(target)) + return + + amount = target + return TRUE + + if("dispense") + var/datum/reagent/reagent = GLOB.name2reagent[params["reagent"]] + if(isnull(reagent)) + return + + if(!QDELETED(beaker)) + var/datum/reagents/container = beaker.reagents + var/actual = min(amount, container.maximum_volume - container.total_volume) + for(var/datum/reagents/source as anything in dispensable_reagents[reagent]["reagents"]) + actual -= source.trans_to(beaker, min(source.total_volume, actual), transferred_by = ui.user) + if(actual <= 0) + break + return TRUE + + if("remove") + var/target = params["amount"] + if(isnull(target)) + return + + target = text2num(target) + if(isnull(target)) + return + + beaker.reagents.remove_all(target) + return TRUE + + if("eject") + replace_beaker(ui.user) + update_appearance() + return TRUE + +/obj/item/storage/portable_chem_mixer/MouseDrop(obj/over_object) + . = ..() + if(ismob(loc)) + var/mob/M = loc + if(!M.incapacitated() && istype(over_object, /atom/movable/screen/inventory/hand)) + var/atom/movable/screen/inventory/hand/H = over_object + M.putItemFromInventoryInHandIfPossible(src, H.held_index) + +/obj/item/storage/portable_chem_mixer/AltClick(mob/living/user) + if(!atom_storage.locked) + balloon_alert(user, "lock first to use alt eject!") + return ..() + if(!can_interact(user) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) + return + + replace_beaker(user) + update_appearance() + +/obj/item/storage/portable_chem_mixer/CtrlClick(mob/living/user) + if(atom_storage.locked == STORAGE_FULLY_LOCKED) + atom_storage.locked = STORAGE_NOT_LOCKED + replace_beaker(user) + SStgui.close_uis(src) + else + atom_storage.locked = STORAGE_FULLY_LOCKED + atom_storage.hide_contents(usr) + + update_appearance() diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm index 26ebfa1d18d9f..e7a6c9839eb7e 100644 --- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm +++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm @@ -42,11 +42,10 @@ QDEL_NULL(beaker) update_appearance() -/obj/machinery/reagentgrinder/deconstruct() +/obj/machinery/reagentgrinder/on_deconstruction(disassmbled) drop_all_items() beaker?.forceMove(drop_location()) beaker = null - return ..() /obj/machinery/reagentgrinder/Destroy() QDEL_NULL(beaker) @@ -142,10 +141,10 @@ /obj/machinery/reagentgrinder/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/reagentgrinder/screwdriver_act(mob/living/user, obj/item/tool) - . = TOOL_ACT_TOOLTYPE_SUCCESS + . = ITEM_INTERACT_SUCCESS if(!beaker && !length(holdingitems)) return default_deconstruction_screwdriver(user, icon_state, icon_state, tool) @@ -308,18 +307,23 @@ if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.holder_full()) return operate_for(50, juicing = TRUE) - for(var/obj/item/i in holdingitems) + for(var/obj/item/ingredient in holdingitems) if(beaker.reagents.holder_full()) break - var/obj/item/I = i - if(I.juice_typepath) - juice_item(I, user) -/obj/machinery/reagentgrinder/proc/juice_item(obj/item/I, mob/user) //Juicing results can be found in respective object definitions - if(!I.juice(beaker.reagents, user)) - to_chat(usr, span_danger("[src] shorts out as it tries to juice up [I], and transfers it back to storage.")) + if(ingredient.flags_1 & HOLOGRAM_1) + to_chat(user, span_notice("You try to juice [ingredient], but it fades away!")) + qdel(ingredient) + continue + + if(ingredient.juice_typepath) + juice_item(ingredient, user) + +/obj/machinery/reagentgrinder/proc/juice_item(obj/item/ingredient, mob/user) //Juicing results can be found in respective object definitions + if(!ingredient.juice(beaker.reagents, user)) + to_chat(user, span_danger("[src] shorts out as it tries to juice up [ingredient], and transfers it back to storage.")) return - remove_object(I) + remove_object(ingredient) /obj/machinery/reagentgrinder/proc/grind(mob/user) power_change() @@ -327,21 +331,26 @@ return operate_for(60) warn_of_dust() // don't breathe this. - for(var/i in holdingitems) + for(var/obj/item/ingredient in holdingitems) if(beaker.reagents.holder_full()) break - var/obj/item/I = i - if(I.grind_results) - grind_item(i, user) - -/obj/machinery/reagentgrinder/proc/grind_item(obj/item/I, mob/user) //Grind results can be found in respective object definitions - if(!I.grind(beaker.reagents, user)) - if(isstack(I)) - to_chat(usr, span_notice("[src] attempts to grind as many pieces of [I] as possible.")) + + if(ingredient.flags_1 & HOLOGRAM_1) + to_chat(user, span_notice("You try to grind [ingredient], but it fades away!")) + qdel(ingredient) + continue + + if(ingredient.grind_results) + grind_item(ingredient, user) + +/obj/machinery/reagentgrinder/proc/grind_item(obj/item/ingredient, mob/user) //Grind results can be found in respective object definitions + if(!ingredient.grind(beaker.reagents, user)) + if(isstack(ingredient)) + to_chat(user, span_notice("[src] attempts to grind as many pieces of [ingredient] as possible.")) else - to_chat(usr, span_danger("[src] shorts out as it tries to grind up [I], and transfers it back to storage.")) + to_chat(user, span_danger("[src] shorts out as it tries to grind up [ingredient], and transfers it back to storage.")) return - remove_object(I) + remove_object(ingredient) /obj/machinery/reagentgrinder/proc/mix(mob/user) //For butter and other things that would change upon shaking or mixing diff --git a/code/modules/reagents/chemistry/machinery/smoke_machine.dm b/code/modules/reagents/chemistry/machinery/smoke_machine.dm index 3a8754bfe4f89..e91aea78d5995 100644 --- a/code/modules/reagents/chemistry/machinery/smoke_machine.dm +++ b/code/modules/reagents/chemistry/machinery/smoke_machine.dm @@ -13,7 +13,6 @@ var/efficiency = 20 var/on = FALSE var/cooldown = 0 - var/screen = "home" var/useramount = 30 // Last used amount var/setting = 1 // displayed range is 3 * setting var/max_range = 3 // displayed max range is 3 * max range @@ -78,7 +77,6 @@ /obj/machinery/smoke_machine/process() - ..() if(reagents.total_volume == 0) on = FALSE update_appearance() @@ -96,7 +94,7 @@ . = ..() if(default_unfasten_wrench(user, tool, time = 4 SECONDS)) on = FALSE - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS return FALSE /obj/machinery/smoke_machine/attackby(obj/item/I, mob/user, params) @@ -113,10 +111,9 @@ return return ..() -/obj/machinery/smoke_machine/deconstruct() +/obj/machinery/smoke_machine/on_deconstruction(disassembled) reagents.expose(loc, TOUCH) reagents.clear_reagents() - return ..() /obj/machinery/smoke_machine/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) @@ -126,18 +123,16 @@ /obj/machinery/smoke_machine/ui_data(mob/user) var/data = list() - var/TankContents[0] - var/TankCurrentVolume = 0 + var/tank_contents = list() + var/tank_current_volume = 0 for(var/datum/reagent/R in reagents.reagent_list) - TankContents.Add(list(list("name" = R.name, "volume" = R.volume))) // list in a list because Byond merges the first list... - TankCurrentVolume += R.volume - data["TankContents"] = TankContents - data["isTankLoaded"] = reagents.total_volume ? TRUE : FALSE - data["TankCurrentVolume"] = reagents.total_volume ? reagents.total_volume : null - data["TankMaxVolume"] = reagents.maximum_volume + tank_contents += list(list("name" = R.name, "volume" = R.volume)) // list in a list because Byond merges the first list... + tank_current_volume += R.volume + data["tankContents"] = tank_contents + data["tankCurrentVolume"] = reagents.total_volume ? reagents.total_volume : null + data["tankMaxVolume"] = reagents.maximum_volume data["active"] = on data["setting"] = setting - data["screen"] = screen data["maxSetting"] = max_range return data @@ -163,8 +158,5 @@ message_admins("[ADMIN_LOOKUPFLW(usr)] activated a smoke machine that contains [english_list(reagents.reagent_list)] at [ADMIN_VERBOSEJMP(src)].") usr.log_message("activated a smoke machine that contains [english_list(reagents.reagent_list)]", LOG_GAME) log_combat(usr, src, "has activated [src] which contains [english_list(reagents.reagent_list)] at [AREACOORD(src)].") - if("goScreen") - screen = params["screen"] - . = TRUE #undef REAGENTS_BASE_VOLUME diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm index 8de1d98cc2e4e..f51532b28b19d 100644 --- a/code/modules/reagents/chemistry/reagents.dm +++ b/code/modules/reagents/chemistry/reagents.dm @@ -1,17 +1,3 @@ -GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) - -/proc/build_name2reagent() - . = list() - for (var/t in subtypesof(/datum/reagent)) - var/datum/reagent/R = t - if (length(initial(R.name))) - .[ckey(initial(R.name))] = t - - -//Various reagents -//Toxin & acid reagents -//Hydroponics stuff - /// A single reagent /datum/reagent /// datums don't have names by default @@ -46,8 +32,6 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) var/color = "#000000" // rgb: 0, 0, 0 ///how fast the reagent is metabolized by the mob var/metabolization_rate = REAGENTS_METABOLISM - /// appears unused - var/overrides_metab = 0 /// above this overdoses happen var/overdose_threshold = 0 /// You fucked up and this is now triggering its overdose effects, purge that shit quick. @@ -58,8 +42,6 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) var/reagent_weight = 1 ///is it currently metabolizing var/metabolizing = FALSE - /// is it bad for you? Currently only used for borghypo. C2s and Toxins have it TRUE by default. - var/harmful = FALSE /// Are we from a material? We might wanna know that for special stuff. Like metalgen. Is replaced with a ref of the material on New() var/datum/material/material ///A list of causes why this chem should skip being removed, if the length is 0 it will be removed from holder naturally, if this is >0 it will not be removed from the holder. @@ -92,6 +74,10 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) /// The affected respiration type, if the reagent damages/heals oxygen damage of an affected mob. /// See "Mob bio-types flags" in /code/_DEFINES/mobs.dm var/affected_respiration_type = ALL + /// A list of traits to apply while the reagent is being metabolized. + var/list/metabolized_traits + /// A list of traits to apply while the reagent is in a mob. + var/list/added_traits ///The default reagent container for the reagent, used for icon generation var/obj/item/reagent_containers/default_container = /obj/item/reagent_containers/cup/bottle @@ -117,7 +103,8 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) if(!mass) mass = rand(10, 800) -/datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references +/// This should only be called by the holder, so it's already handled clearing its references +/datum/reagent/Destroy() . = ..() holder = null @@ -155,12 +142,40 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) /datum/reagent/proc/burn(datum/reagents/holder) return -/// Called from [/datum/reagents/proc/metabolize] -/datum/reagent/proc/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) - current_cycle++ +/** + * Ticks on mob Life() for as long as the reagent remains in the mob's reagents. + * + * Usage: Parent should be called first using . = ..() + * + * Exceptions: If the holder var needs to be accessed, call the parent afterward that as it can become null if the reagent is fully removed. + * + * Returns: UPDATE_MOB_HEALTH only if you need to update the health of a mob (this is only needed when damage is dealt to the mob) + * + * Arguments + * * mob/living/carbon/affected_mob - the mob which the reagent currently is inside of + * * seconds_per_tick - the time in server seconds between proc calls (when performing normally it will be 2) + * * times_fired - the number of times the owner's Life() tick has been called aka The number of times SSmobs has fired + * + */ +/datum/reagent/proc/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + SHOULD_CALL_PARENT(TRUE) + +///Metabolizes a portion of the reagent after on_mob_life() is called +/datum/reagent/proc/metabolize_reagent(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) if(length(reagent_removal_skip_list)) return - holder.remove_reagent(type, metabolization_rate * M.metabolism_efficiency * seconds_per_tick) //By default it slowly disappears. + if(isnull(holder)) + return + + var/metabolizing_out = metabolization_rate * seconds_per_tick + if(!(chemical_flags & REAGENT_UNAFFECTED_BY_METABOLISM)) + if(chemical_flags & REAGENT_REVERSE_METABOLISM) + metabolizing_out /= affected_mob.metabolism_efficiency + else + metabolizing_out *= affected_mob.metabolism_efficiency + + holder.remove_reagent(type, metabolizing_out) + /// Called in burns.dm *if* the reagent has the REAGENT_AFFECTS_WOUNDS process flag /datum/reagent/proc/on_burn_wound_processing(datum/wound/burn/flesh/burn_wound) @@ -170,7 +185,7 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent()) Used to run functions before a reagent is transferred. Returning TRUE will block the transfer attempt. Primarily used in reagents/reaction_agents */ -/datum/reagent/proc/intercept_reagents_transfer(datum/reagents/target) +/datum/reagent/proc/intercept_reagents_transfer(datum/reagents/target, amount) return FALSE ///Called after a reagent is transferred @@ -178,35 +193,33 @@ Primarily used in reagents/reaction_agents return /// Called when this reagent is first added to a mob -/datum/reagent/proc/on_mob_add(mob/living/L, amount) +/datum/reagent/proc/on_mob_add(mob/living/affected_mob, amount) overdose_threshold /= max(normalise_creation_purity(), 1) //Maybe??? Seems like it would help pure chems be even better but, if I normalised this to 1, then everything would take a 25% reduction - return + if(added_traits) + affected_mob.add_traits(added_traits, "base:[type]") /// Called when this reagent is removed while inside a mob -/datum/reagent/proc/on_mob_delete(mob/living/L) - L.clear_mood_event("[type]_overdose") - return +/datum/reagent/proc/on_mob_delete(mob/living/affected_mob) + affected_mob.clear_mood_event("[type]_overdose") + REMOVE_TRAITS_IN(affected_mob, "base:[type]") /// Called when this reagent first starts being metabolized by a liver -/datum/reagent/proc/on_mob_metabolize(mob/living/L) - return +/datum/reagent/proc/on_mob_metabolize(mob/living/affected_mob) + SHOULD_CALL_PARENT(TRUE) + if(metabolized_traits) + affected_mob.add_traits(metabolized_traits, "metabolize:[type]") /// Called when this reagent stops being metabolized by a liver -/datum/reagent/proc/on_mob_end_metabolize(mob/living/L) - return - -/// Called when a reagent is inside of a mob when they are dead -/datum/reagent/proc/on_mob_dead(mob/living/carbon/C, seconds_per_tick) - if(!(chemical_flags & REAGENT_DEAD_PROCESS)) - return - current_cycle++ - if(length(reagent_removal_skip_list)) - return - holder.remove_reagent(type, metabolization_rate * C.metabolism_efficiency * seconds_per_tick) +/datum/reagent/proc/on_mob_end_metabolize(mob/living/affected_mob) + SHOULD_CALL_PARENT(TRUE) + REMOVE_TRAITS_IN(affected_mob, "metabolize:[type]") -/// Called by [/datum/reagents/proc/conditional_update_move] -/datum/reagent/proc/on_move(mob/M) - return +/** + * Called when a reagent is inside of a mob when they are dead if the reagent has the REAGENT_DEAD_PROCESS flag + * Returning UPDATE_MOB_HEALTH will cause updatehealth() to be called on the holder mob by /datum/reagents/proc/metabolize. + */ +/datum/reagent/proc/on_mob_dead(mob/living/carbon/affected_mob, seconds_per_tick) + SHOULD_CALL_PARENT(TRUE) /// Called after add_reagents creates a new reagent. /datum/reagent/proc/on_new(data) @@ -217,18 +230,14 @@ Primarily used in reagents/reaction_agents /datum/reagent/proc/on_merge(data, amount) return -/// Called by [/datum/reagents/proc/conditional_update] -/datum/reagent/proc/on_update(atom/A) - return - -/// Called if the reagent has passed the overdose threshold and is set to be triggering overdose effects -/datum/reagent/proc/overdose_process(mob/living/M, seconds_per_tick, times_fired) +/// Called if the reagent has passed the overdose threshold and is set to be triggering overdose effects. Returning UPDATE_MOB_HEALTH will cause updatehealth() to be called on the holder mob by /datum/reagents/proc/metabolize. +/datum/reagent/proc/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) return -/// Called when an overdose starts -/datum/reagent/proc/overdose_start(mob/living/M) - to_chat(M, span_userdanger("You feel like you took too much of [name]!")) - M.add_mood_event("[type]_overdose", /datum/mood_event/overdose, name) +/// Called when an overdose starts. Returning UPDATE_MOB_HEALTH will cause updatehealth() to be called on the holder mob by /datum/reagents/proc/metabolize. +/datum/reagent/proc/overdose_start(mob/living/affected_mob) + to_chat(affected_mob, span_userdanger("You feel like you took too much of [name]!")) + affected_mob.add_mood_event("[type]_overdose", /datum/mood_event/overdose, name) return /** diff --git a/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm b/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm index 817e5ed98bfe5..1406e37369359 100644 --- a/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm @@ -12,8 +12,8 @@ breather.add_movespeed_modifier(/datum/movespeed_modifier/reagent/freon) /datum/reagent/freon/on_mob_end_metabolize(mob/living/breather) + . = ..() breather.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/freon) - return ..() /datum/reagent/halon name = "Halon" @@ -23,16 +23,15 @@ color = "90560B" taste_description = "minty" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE + metabolized_traits = list(TRAIT_RESISTHEAT) /datum/reagent/halon/on_mob_metabolize(mob/living/breather) . = ..() breather.add_movespeed_modifier(/datum/movespeed_modifier/reagent/halon) - ADD_TRAIT(breather, TRAIT_RESISTHEAT, type) /datum/reagent/halon/on_mob_end_metabolize(mob/living/breather) + . = ..() breather.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/halon) - REMOVE_TRAIT(breather, TRAIT_RESISTHEAT, type) - return ..() /datum/reagent/healium name = "Healium" @@ -44,16 +43,18 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/healium/on_mob_end_metabolize(mob/living/breather) + . = ..() breather.SetSleeping(1 SECONDS) - return ..() /datum/reagent/healium/on_mob_life(mob/living/breather, seconds_per_tick, times_fired) - breather.SetSleeping(30 SECONDS) - breather.adjustFireLoss(-2 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - breather.adjustToxLoss(-5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - breather.adjustBruteLoss(-2 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) . = ..() - return TRUE + breather.SetSleeping(30 SECONDS) + var/need_mob_update + need_mob_update = breather.adjustFireLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += breather.adjustToxLoss(-5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += breather.adjustBruteLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/hypernoblium name = "Hyper-Noblium" @@ -65,9 +66,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/hypernoblium/on_mob_life(mob/living/carbon/breather, seconds_per_tick, times_fired) + . = ..() if(isplasmaman(breather)) breather.set_timed_status_effect(10 SECONDS * REM * seconds_per_tick, /datum/status_effect/hypernob_protection) - ..() /datum/reagent/nitrium_high_metabolization name = "Nitrosyl plasmide" @@ -79,20 +80,15 @@ ph = 1.8 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE addiction_types = list(/datum/addiction/stimulants = 14) - -/datum/reagent/nitrium_high_metabolization/on_mob_metabolize(mob/living/breather) - . = ..() - ADD_TRAIT(breather, TRAIT_SLEEPIMMUNE, type) - -/datum/reagent/nitrium_high_metabolization/on_mob_end_metabolize(mob/living/breather) - REMOVE_TRAIT(breather, TRAIT_SLEEPIMMUNE, type) - return ..() + metabolized_traits = list(TRAIT_SLEEPIMMUNE) /datum/reagent/nitrium_high_metabolization/on_mob_life(mob/living/carbon/breather, seconds_per_tick, times_fired) - breather.adjustStaminaLoss(-2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - breather.adjustToxLoss(0.1 * current_cycle * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) // 1 toxin damage per cycle at cycle 10 . = ..() - return TRUE + var/need_mob_update + need_mob_update = breather.adjustStaminaLoss(-2 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + need_mob_update += breather.adjustToxLoss(0.1 * (current_cycle-1) * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) // 1 toxin damage per cycle at cycle 10 + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/nitrium_low_metabolization name = "Nitrium" @@ -109,8 +105,8 @@ breather.add_movespeed_modifier(/datum/movespeed_modifier/reagent/nitrium) /datum/reagent/nitrium_low_metabolization/on_mob_end_metabolize(mob/living/breather) + . = ..() breather.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/nitrium) - return ..() /datum/reagent/pluoxium name = "Pluoxium" @@ -122,18 +118,16 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/pluoxium/on_mob_life(mob/living/carbon/breather, seconds_per_tick, times_fired) + . = ..() if(!HAS_TRAIT(breather, TRAIT_KNOCKEDOUT)) - return ..() + return - . = ..() for(var/obj/item/organ/organ_being_healed as anything in breather.organs) if(!organ_being_healed.damage) continue - organ_being_healed.apply_organ_damage(-0.5 * REM * seconds_per_tick, required_organ_flag = ORGAN_ORGANIC) - . = TRUE - - return . + if(organ_being_healed.apply_organ_damage(-0.5 * REM * seconds_per_tick, required_organ_flag = ORGAN_ORGANIC)) + . = UPDATE_MOB_HEALTH /datum/reagent/zauker name = "Zauker" @@ -147,9 +141,11 @@ affected_respiration_type = ALL /datum/reagent/zauker/on_mob_life(mob/living/breather, seconds_per_tick, times_fired) - breather.adjustBruteLoss(6 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - breather.adjustOxyLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - breather.adjustFireLoss(2 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - breather.adjustToxLoss(2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - return TRUE + . = ..() + var/need_mob_update + need_mob_update = breather.adjustBruteLoss(6 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += breather.adjustOxyLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += breather.adjustFireLoss(2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += breather.adjustToxLoss(2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm index 5ac2380aacfdb..039bce7eaa8eb 100644 --- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm @@ -1,6 +1,5 @@ // Category 2 medicines are medicines that have an ill effect regardless of volume/OD to dissuade doping. Mostly used as emergency chemicals OR to convert damage (and heal a bit in the process). The type is used to prompt borgs that the medicine is harmful. /datum/reagent/medicine/c2 - harmful = TRUE metabolization_rate = 0.5 * REAGENTS_METABOLISM inverse_chem = null //Some of these use inverse chems - we're just defining them all to null here to avoid repetition, eventually this will be moved up to parent creation_purity = REAGENT_STANDARD_PURITY//All sources by default are 0.75 - reactions are primed to resolve to roughly the same with no intervention for these. @@ -27,24 +26,31 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/c2/helbital/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - . = TRUE + . = ..() var/death_is_coming = (affected_mob.getToxLoss() + affected_mob.getOxyLoss() + affected_mob.getFireLoss() + affected_mob.getBruteLoss())*normalise_creation_purity() var/thou_shall_heal = 0 var/good_kind_of_healing = FALSE + var/need_mob_update = FALSE switch(affected_mob.stat) if(CONSCIOUS) //bad thou_shall_heal = death_is_coming/50 - affected_mob.adjustOxyLoss(2 * REM * seconds_per_tick, TRUE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustOxyLoss(2 * REM * seconds_per_tick, TRUE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) if(SOFT_CRIT) //meh convert thou_shall_heal = round(death_is_coming/47,0.1) - affected_mob.adjustOxyLoss(1 * REM * seconds_per_tick, TRUE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustOxyLoss(1 * REM * seconds_per_tick, TRUE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) else //no convert thou_shall_heal = round(death_is_coming/45, 0.1) good_kind_of_healing = TRUE - affected_mob.adjustBruteLoss(-thou_shall_heal * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustBruteLoss(-thou_shall_heal * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + . = UPDATE_MOB_HEALTH if(good_kind_of_healing && !reaping && SPT_PROB(0.00005, seconds_per_tick)) //janken with the grim reaper! - notify_ghosts("[affected_mob] has entered a game of rock-paper-scissors with death!", source = affected_mob, action = NOTIFY_ORBIT, header = "Who Will Win?") + notify_ghosts( + "[affected_mob] has entered a game of rock-paper-scissors with death!", + source = affected_mob, + header = "Who Will Win?", + ) reaping = TRUE if(affected_mob.apply_status_effect(/datum/status_effect/necropolis_curse, CURSE_BLINDING)) helbent = TRUE @@ -74,20 +80,16 @@ holder.del_reagent(type) return - ..() - return - /datum/reagent/medicine/c2/helbital/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(!helbent) affected_mob.apply_necropolis_curse(CURSE_WASTING | CURSE_BLINDING) helbent = TRUE - ..() - return TRUE -/datum/reagent/medicine/c2/helbital/on_mob_delete(mob/living/L) +/datum/reagent/medicine/c2/helbital/on_mob_delete(mob/living/affected_mob) + . = ..() if(helbent) - L.remove_status_effect(/datum/status_effect/necropolis_curse) - ..() + affected_mob.remove_status_effect(/datum/status_effect/necropolis_curse) /datum/reagent/medicine/c2/libital //messes with your liber name = "Libital" @@ -96,13 +98,17 @@ ph = 8.2 taste_description = "bitter with a hint of alcohol" reagent_state = SOLID + inverse_chem_val = 0.3 + inverse_chem = /datum/reagent/inverse/libitoil chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/c2/libital/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - affected_mob.adjustBruteLoss(-3 * REM * normalise_creation_purity() * seconds_per_tick, required_bodytype = affected_bodytype) - ..() - return TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + need_mob_update += affected_mob.adjustBruteLoss(-3 * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/c2/probital name = "Probital" @@ -116,7 +122,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/c2/probital/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustBruteLoss(-2.25 * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(-2.25 * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) var/ooo_youaregettingsleepy = 3.5 switch(round(affected_mob.getStaminaLoss())) if(10 to 40) @@ -125,20 +133,22 @@ ooo_youaregettingsleepy = 2.5 if(61 to 200) //you really can only go to 120 ooo_youaregettingsleepy = 2 - affected_mob.adjustStaminaLoss(ooo_youaregettingsleepy * REM * seconds_per_tick) - ..() - . = TRUE + need_mob_update += affected_mob.adjustStaminaLoss(ooo_youaregettingsleepy * REM * seconds_per_tick, updating_stamina = FALSE) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/c2/probital/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustStaminaLoss(3 * REM * seconds_per_tick, updating_stamina = FALSE) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustStaminaLoss(3 * REM * seconds_per_tick, updating_stamina = FALSE) if(affected_mob.getStaminaLoss() >= 80) affected_mob.adjust_drowsiness(2 SECONDS * REM * seconds_per_tick) if(affected_mob.getStaminaLoss() >= 100) to_chat(affected_mob,span_warning("You feel more tired than you usually do, perhaps if you rest your eyes for a bit...")) - affected_mob.adjustStaminaLoss(-100, updating_stamina = TRUE) // Don't add the biotype parameter here as it results in infinite sleep and chat spam. + need_mob_update += affected_mob.adjustStaminaLoss(-100, updating_stamina = FALSE) // Don't add the biotype parameter here as it results in infinite sleep and chat spam. affected_mob.Sleeping(10 SECONDS) - ..() - . = TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/c2/probital/on_transfer(atom/A, methods=INGEST, trans_volume) if(!(methods & INGEST) || (!iscarbon(A) && !istype(A, /obj/item/organ/internal/stomach)) ) @@ -153,19 +163,24 @@ /*Suffix: -uri*/ /datum/reagent/medicine/c2/lenturi name = "Lenturi" - description = "Used to treat burns. Makes you move slower while it is in your system. Applies stomach damage when it leaves your system." + description = "Used to treat burns. Applies stomach damage when it leaves your system." reagent_state = LIQUID color = "#6171FF" ph = 4.7 var/resetting_probability = 0 //What are these for?? Can I remove them? var/spammer = 0 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + inverse_chem_val = 0.4 + inverse_chem = /datum/reagent/inverse/lentslurri + /datum/reagent/medicine/c2/lenturi/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustFireLoss(-3 * REM * normalise_creation_purity() * seconds_per_tick, required_bodytype = affected_bodytype) - affected_mob.adjustOrganLoss(ORGAN_SLOT_STOMACH, 0.4 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - ..() - return TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustFireLoss(-3 * REM * normalise_creation_purity() * seconds_per_tick, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_STOMACH, 0.4 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/c2/aiuri name = "Aiuri" @@ -176,12 +191,16 @@ var/resetting_probability = 0 //same with this? Old legacy vars that should be removed? var/message_cd = 0 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + inverse_chem_val = 0.35 + inverse_chem = /datum/reagent/inverse/aiuri /datum/reagent/medicine/c2/aiuri/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustFireLoss(-2 * REM * normalise_creation_purity() * seconds_per_tick, required_bodytype = affected_bodytype) - affected_mob.adjustOrganLoss(ORGAN_SLOT_EYES, 0.25 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - ..() - return TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustFireLoss(-2 * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_EYES, 0.25 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/c2/hercuri name = "Hercuri" @@ -196,18 +215,20 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/c2/hercuri/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update if(affected_mob.getFireLoss() > 50) - affected_mob.adjustFireLoss(-2 * REM * seconds_per_tick * normalise_creation_purity(), FALSE, required_bodytype = affected_bodytype) + need_mob_update = affected_mob.adjustFireLoss(-2 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_bodytype = affected_bodytype) else - affected_mob.adjustFireLoss(-1.25 * REM * seconds_per_tick * normalise_creation_purity(), FALSE, required_bodytype = affected_bodytype) + need_mob_update = affected_mob.adjustFireLoss(-1.25 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_bodytype = affected_bodytype) affected_mob.adjust_bodytemperature(rand(-25,-5) * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 50) if(ishuman(affected_mob)) var/mob/living/carbon/human/humi = affected_mob humi.adjust_coretemperature(rand(-25,-5) * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 50) affected_mob.reagents?.chem_temp += (-10 * REM * seconds_per_tick) affected_mob.adjust_fire_stacks(-1 * REM * seconds_per_tick) - ..() - . = TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/c2/hercuri/expose_mob(mob/living/carbon/exposed_mob, methods=VAPOR, reac_volume) . = ..() @@ -220,11 +241,11 @@ exposed_mob.extinguish_mob() /datum/reagent/medicine/c2/hercuri/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_bodytemperature(-10 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 50) //chilly chilly if(ishuman(affected_mob)) var/mob/living/carbon/human/humi = affected_mob humi.adjust_coretemperature(-10 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 50) - ..() /******OXY******/ @@ -243,20 +264,22 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/c2/convermol/on_mob_life(mob/living/carbon/human/affected_mob, seconds_per_tick, times_fired) - var/oxycalc = 2.5 * REM * current_cycle + . = ..() + var/oxycalc = 2.5 * REM * (current_cycle-1) if(!overdosed) oxycalc = min(oxycalc, affected_mob.getOxyLoss() + 0.5) //if NOT overdosing, we lower our toxdamage to only the damage we actually healed with a minimum of 0.1*current_cycle. IE if we only heal 10 oxygen damage but we COULD have healed 20, we will only take toxdamage for the 10. We would take the toxdamage for the extra 10 if we were overdosing. - affected_mob.adjustOxyLoss(-oxycalc * seconds_per_tick * normalise_creation_purity(), FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustToxLoss(oxycalc * seconds_per_tick / CONVERMOL_RATIO, FALSE, required_biotype = affected_biotype) - if(SPT_PROB(current_cycle / 2, seconds_per_tick) && affected_mob.losebreath) + var/need_mob_update + need_mob_update = affected_mob.adjustOxyLoss(-oxycalc * seconds_per_tick * normalise_creation_purity(), FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustToxLoss(oxycalc * seconds_per_tick / CONVERMOL_RATIO, updating_health = FALSE, required_biotype = affected_biotype) + if(SPT_PROB((current_cycle-1) / 2, seconds_per_tick) && affected_mob.losebreath) affected_mob.losebreath-- - ..() - return TRUE + need_mob_update = TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/c2/convermol/overdose_process(mob/living/carbon/human/affected_mob, seconds_per_tick, times_fired) + . = ..() metabolization_rate += 2.5 * REAGENTS_METABOLISM - ..() - return TRUE #undef CONVERMOL_RATIO @@ -273,20 +296,22 @@ COOLDOWN_DECLARE(drowsycd) /datum/reagent/medicine/c2/tirimol/on_mob_life(mob/living/carbon/human/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOxyLoss(-3 * REM * seconds_per_tick * normalise_creation_purity(), required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustStaminaLoss(2 * REM * seconds_per_tick, required_biotype = affected_biotype) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOxyLoss(-3 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustStaminaLoss(2 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) if(drowsycd && COOLDOWN_FINISHED(src, drowsycd)) affected_mob.adjust_drowsiness(20 SECONDS) COOLDOWN_START(src, drowsycd, 45 SECONDS) else if(!drowsycd) COOLDOWN_START(src, drowsycd, 15 SECONDS) - ..() - return TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH -/datum/reagent/medicine/c2/tirimol/on_mob_end_metabolize(mob/living/L) - if(current_cycle > 20) - L.Sleeping(10 SECONDS) - ..() +/datum/reagent/medicine/c2/tirimol/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() + if(current_cycle > 21) + affected_mob.Sleeping(10 SECONDS) /******TOXIN******/ /*Suffix: -iver*/ @@ -307,14 +332,16 @@ rads_heal_threshold = rand(rads_heal_threshold - 50, rads_heal_threshold + 50) // Basically this means 50K and below will always give the radiation heal, and upto 150K could. Calculated once. /datum/reagent/medicine/c2/seiver/on_mob_life(mob/living/carbon/human/affected_mob, seconds_per_tick, times_fired) + . = ..() var/chemtemp = min(holder.chem_temp, 1000) chemtemp = chemtemp ? chemtemp : T0C //why do you have null sweaty var/healypoints = 0 //5 healypoints = 1 heart damage; 5 rads = 1 tox damage healed for the purpose of healypoints //you're hot var/toxcalc = min(round(5 + ((chemtemp-1000)/175), 0.1), 5) * REM * seconds_per_tick * normalise_creation_purity() //max 2.5 tox healing per second + var/need_mob_update if(toxcalc > 0) - affected_mob.adjustToxLoss(-toxcalc, required_biotype = affected_biotype) + need_mob_update = affected_mob.adjustToxLoss(-toxcalc, updating_health = FALSE, required_biotype = affected_biotype) healypoints += toxcalc //and you're cold @@ -323,16 +350,16 @@ radcalc *= normalise_creation_purity() // extra rad healing if you are SUPER cold if(chemtemp < rads_heal_threshold*0.1) - affected_mob.adjustToxLoss(-radcalc * 0.9, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustToxLoss(-radcalc * 0.9, updating_health = FALSE, required_biotype = affected_biotype) else if(chemtemp < rads_heal_threshold) - affected_mob.adjustToxLoss(-radcalc * 0.75, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustToxLoss(-radcalc * 0.75, updating_health = FALSE, required_biotype = affected_biotype) healypoints += (radcalc / 5) //you're yes and... oh no! healypoints = round(healypoints, 0.1) affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, healypoints / 5, required_organ_flag = affected_organ_flags) - ..() - return TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/c2/multiver //enhanced with MULTIple medicines name = "Multiver" @@ -343,6 +370,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/c2/multiver/on_mob_life(mob/living/carbon/human/affected_mob, seconds_per_tick, times_fired) + . = ..() var/medibonus = 0 //it will always have itself which makes it REALLY start @ 1 for(var/r in affected_mob.reagents.reagent_list) var/datum/reagent/the_reagent = r @@ -350,8 +378,9 @@ medibonus += 1 if(creation_purity >= 1) //Perfectly pure multivers gives a bonus of 2! medibonus += 1 - affected_mob.adjustToxLoss(-0.5 * min(medibonus, 3 * normalise_creation_purity()) * REM * seconds_per_tick, required_biotype = affected_biotype) //not great at healing but if you have nothing else it will work - affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) //kills at 40u + var/need_mob_update + need_mob_update = affected_mob.adjustToxLoss(-0.5 * min(medibonus, 3 * normalise_creation_purity()) * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) //not great at healing but if you have nothing else it will work + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) //kills at 40u for(var/r2 in affected_mob.reagents.reagent_list) var/datum/reagent/the_reagent2 = r2 if(the_reagent2 == src) @@ -360,8 +389,8 @@ if(medibonus >= 3 && istype(the_reagent2, /datum/reagent/medicine)) //3 unique meds (2+multiver) | (1 + pure multiver) will make it not purge medicines continue affected_mob.reagents.remove_reagent(the_reagent2.type, amount2purge * REM * seconds_per_tick) - ..() - return TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH // Antitoxin binds plants pretty well. So the tox goes significantly down /datum/reagent/medicine/c2/multiver/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) @@ -385,7 +414,7 @@ return var/mob/living/carbon/C = A if(trans_volume >= 0.6) //prevents cheesing with ultralow doses. - C.adjustToxLoss((-1.5 * min(2, trans_volume) * REM) * normalise_creation_purity(), FALSE, required_biotype = affected_biotype) //This is to promote iv pole use for that chemotherapy feel. + C.adjustToxLoss((-1.5 * min(2, trans_volume) * REM) * normalise_creation_purity(), required_biotype = affected_biotype) //This is to promote iv pole use for that chemotherapy feel. var/obj/item/organ/internal/liver/L = C.organs_slot[ORGAN_SLOT_LIVER] if(!L || L.organ_flags & ORGAN_FAILING) return @@ -395,22 +424,24 @@ ..() /datum/reagent/medicine/c2/syriniver/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.8 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.8 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + need_mob_update += affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) for(var/datum/reagent/R in affected_mob.reagents.reagent_list) if(issyrinormusc(R)) continue affected_mob.reagents.remove_reagent(R.type, 0.4 * REM * seconds_per_tick) - ..() - . = TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/c2/syriniver/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 1.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + . = ..() + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 1.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + . = UPDATE_MOB_HEALTH affected_mob.adjust_disgust(3 * REM * seconds_per_tick) affected_mob.reagents.add_reagent(/datum/reagent/medicine/c2/musiver, 0.225 * REM * seconds_per_tick) - ..() - . = TRUE /datum/reagent/medicine/c2/musiver //MUScles name = "Musiver" @@ -424,30 +455,32 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/c2/musiver/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.1 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick * normalise_creation_purity(), FALSE, required_biotype = affected_biotype) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.1 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + need_mob_update += affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_biotype = affected_biotype) for(var/datum/reagent/R in affected_mob.reagents.reagent_list) if(issyrinormusc(R)) continue affected_mob.reagents.remove_reagent(R.type, 0.2 * REM * seconds_per_tick) - ..() - . = TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/c2/musiver/overdose_start(mob/living/carbon/affected_mob) + . = ..() trauma = new() affected_mob.gain_trauma(trauma, TRAUMA_RESILIENCE_ABSOLUTE) - ..() -/datum/reagent/medicine/c2/musiver/on_mob_delete(mob/living/carbon/affected_mob) +/datum/reagent/medicine/c2/musiver/on_mob_delete(mob/living/affected_mob) + . = ..() if(trauma) QDEL_NULL(trauma) - return ..() /datum/reagent/medicine/c2/musiver/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 1.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + . = ..() + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 1.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + . = UPDATE_MOB_HEALTH affected_mob.adjust_disgust(3 * REM * seconds_per_tick) - ..() - . = TRUE #undef issyrinormusc /******COMBOS******/ @@ -469,14 +502,21 @@ show_message = 0 if(!(methods & (PATCH|TOUCH|VAPOR))) return - var/harmies = min(carbies.getBruteLoss(), carbies.adjustBruteLoss(-1.25 * reac_volume, required_bodytype = affected_bodytype)*-1) - var/burnies = min(carbies.getFireLoss(), carbies.adjustFireLoss(-1.25 * reac_volume, required_bodytype = affected_bodytype)*-1) + var/current_bruteloss = carbies.getBruteLoss() // because this will be changed after calling adjustBruteLoss() + var/current_fireloss = carbies.getFireLoss() // because this will be changed after calling adjustFireLoss() + var/harmies = clamp(carbies.adjustBruteLoss(-1.25 * reac_volume, updating_health = FALSE, required_bodytype = affected_bodytype), 0, current_bruteloss) + var/burnies = clamp(carbies.adjustFireLoss(-1.25 * reac_volume, updating_health = FALSE, required_bodytype = affected_bodytype), 0, current_fireloss) for(var/i in carbies.all_wounds) var/datum/wound/iter_wound = i iter_wound.on_synthflesh(reac_volume) - carbies.adjustToxLoss((harmies+burnies)*(0.5 + (0.25*(1-creation_purity))), required_biotype = affected_biotype) //0.5 - 0.75 + var/need_mob_update = harmies + burnies + need_mob_update = carbies.adjustToxLoss((harmies + burnies)*(0.5 + (0.25*(1-creation_purity))), updating_health = FALSE, required_biotype = affected_biotype) || need_mob_update //0.5 - 0.75 + + if(need_mob_update) + carbies.updatehealth() if(show_message) to_chat(carbies, span_danger("You feel your burns and bruises healing! It stings like hell!")) + carbies.add_mood_event("painful_medicine", /datum/mood_event/painful_medicine) if(HAS_TRAIT_FROM(exposed_mob, TRAIT_HUSK, BURN) && carbies.getFireLoss() < UNHUSK_DAMAGE_THRESHOLD && (carbies.reagents.get_reagent_amount(/datum/reagent/medicine/c2/synthflesh) + reac_volume >= SYNTHFLESH_UNHUSK_AMOUNT)) carbies.cure_husk(BURN) @@ -521,44 +561,52 @@ user.throw_alert("penthrite", /atom/movable/screen/alert/penthrite) user.add_traits(subject_traits, type) -/datum/reagent/medicine/c2/penthrite/on_mob_life(mob/living/carbon/human/H, seconds_per_tick, times_fired) - H.adjustOrganLoss(ORGAN_SLOT_STOMACH, 0.25 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - if(H.health <= HEALTH_THRESHOLD_CRIT && H.health > (H.crit_threshold + HEALTH_THRESHOLD_FULLCRIT * (2 * normalise_creation_purity()))) //we cannot save someone below our lowered crit threshold. +/datum/reagent/medicine/c2/penthrite/on_mob_life(mob/living/carbon/human/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_STOMACH, 0.25 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(affected_mob.health <= HEALTH_THRESHOLD_CRIT && affected_mob.health > (affected_mob.crit_threshold + HEALTH_THRESHOLD_FULLCRIT * (2 * normalise_creation_purity()))) //we cannot save someone below our lowered crit threshold. - H.adjustToxLoss(-2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - H.adjustBruteLoss(-2 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - H.adjustFireLoss(-2 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - H.adjustOxyLoss(-6 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustToxLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustBruteLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustOxyLoss(-6 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - H.losebreath = 0 + affected_mob.losebreath = 0 - H.adjustOrganLoss(ORGAN_SLOT_HEART, max(volume/10, 1) * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) // your heart is barely keeping up! + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, max(volume/10, 1) * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) // your heart is barely keeping up! - H.set_jitter_if_lower(rand(0 SECONDS, 4 SECONDS) * REM * seconds_per_tick) - H.set_dizzy_if_lower(rand(0 SECONDS, 4 SECONDS) * REM * seconds_per_tick) + affected_mob.set_jitter_if_lower(rand(0 SECONDS, 4 SECONDS) * REM * seconds_per_tick) + affected_mob.set_dizzy_if_lower(rand(0 SECONDS, 4 SECONDS) * REM * seconds_per_tick) if(SPT_PROB(18, seconds_per_tick)) - to_chat(H,span_danger("Your body is trying to give up, but your heart is still beating!")) - - if(H.health <= (H.crit_threshold + HEALTH_THRESHOLD_FULLCRIT*(2*normalise_creation_purity()))) //certain death below this threshold - REMOVE_TRAIT(H, TRAIT_STABLEHEART, type) //we have to remove the stable heart trait before we give them a heart attack - to_chat(H,span_danger("You feel something rupturing inside your chest!")) - H.emote("scream") - H.set_heartattack(TRUE) + to_chat(affected_mob,span_danger("Your body is trying to give up, but your heart is still beating!")) + + if(affected_mob.health <= (affected_mob.crit_threshold + HEALTH_THRESHOLD_FULLCRIT*(2*normalise_creation_purity()))) //certain death below this threshold + REMOVE_TRAIT(affected_mob, TRAIT_STABLEHEART, type) //we have to remove the stable heart trait before we give them a heart attack + affected_mob.remove_traits(subject_traits, type) + to_chat(affected_mob, span_danger("You feel something rupturing inside your chest!")) + if(!HAS_TRAIT(affected_mob, TRAIT_ANALGESIA)) + affected_mob.emote("scream") + affected_mob.set_heartattack(TRUE) volume = 0 - . = ..() - return TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH -/datum/reagent/medicine/c2/penthrite/on_mob_end_metabolize(mob/living/user) - user.clear_alert("penthrite") - user.remove_traits(subject_traits, type) +/datum/reagent/medicine/c2/penthrite/on_mob_end_metabolize(mob/living/affected_mob) . = ..() + affected_mob.clear_alert("penthrite") + affected_mob.remove_traits(subject_traits, type) -/datum/reagent/medicine/c2/penthrite/overdose_process(mob/living/carbon/human/H, seconds_per_tick, times_fired) - REMOVE_TRAIT(H, TRAIT_STABLEHEART, type) - H.adjustStaminaLoss(10 * REM * seconds_per_tick, required_biotype = affected_biotype) - H.adjustOrganLoss(ORGAN_SLOT_HEART, 10 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - H.set_heartattack(TRUE) +/datum/reagent/medicine/c2/penthrite/overdose_process(mob/living/carbon/human/affected_mob, seconds_per_tick, times_fired) + . = ..() + REMOVE_TRAIT(affected_mob, TRAIT_STABLEHEART, type) + var/need_mob_update + need_mob_update = affected_mob.adjustStaminaLoss(10 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, 10 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + need_mob_update += affected_mob.set_heartattack(TRUE) + if(need_mob_update) + return UPDATE_MOB_HEALTH /******NICHE******/ diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm index 2683103a111a9..87033589812cb 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm @@ -51,20 +51,35 @@ return ..() /datum/reagent/consumable/ethanol/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.get_drunk_amount() < volume * boozepwr * ALCOHOL_THRESHOLD_MODIFIER || boozepwr < 0) var/booze_power = boozepwr - if(HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) //we're an accomplished drinker + if(HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) // we're an accomplished drinker booze_power *= 0.7 if(HAS_TRAIT(drinker, TRAIT_LIGHT_DRINKER)) booze_power *= 2 + + // water will dilute alcohol effects + var/total_water_volume = 0 + var/total_alcohol_volume = 0 + for(var/datum/reagent/water/sobriety in drinker.reagents.reagent_list) + total_water_volume += sobriety.volume + + for(var/datum/reagent/consumable/ethanol/alcohol in drinker.reagents.reagent_list) + total_alcohol_volume += alcohol.volume + + var/combined_dilute_volume = total_alcohol_volume + total_water_volume + if(combined_dilute_volume) // safety check to prevent division by zero + booze_power *= (total_alcohol_volume / combined_dilute_volume) + // Volume, power, and server alcohol rate effect how quickly one gets drunk drinker.adjust_drunk_effect(sqrt(volume) * booze_power * ALCOHOL_RATE * REM * seconds_per_tick) if(boozepwr > 0) var/obj/item/organ/internal/liver/liver = drinker.get_organ_slot(ORGAN_SLOT_LIVER) var/heavy_drinker_multiplier = (HAS_TRAIT(drinker, TRAIT_HEAVY_DRINKER) ? 0.5 : 1) if (istype(liver)) - liver.apply_organ_damage(((max(sqrt(volume) * (boozepwr ** ALCOHOL_EXPONENT) * liver.alcohol_tolerance * heavy_drinker_multiplier * seconds_per_tick, 0))/150)) - return ..() + if(liver.apply_organ_damage(((max(sqrt(volume) * (boozepwr ** ALCOHOL_EXPONENT) * liver.alcohol_tolerance * heavy_drinker_multiplier * seconds_per_tick, 0))/150))) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/expose_obj(obj/exposed_obj, reac_volume) if(istype(exposed_obj, /obj/item/paper)) @@ -132,18 +147,34 @@ name = "Green Beer" description = "An alcoholic beverage brewed since ancient times on Old Earth. This variety is dyed a festive green." color = COLOR_CRAYON_GREEN + overdose_threshold = 55 //More than a glass taste_description = "green piss water" ph = 6 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/beer/green/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.color != color) drinker.add_atom_colour(color, TEMPORARY_COLOUR_PRIORITY) - return ..() /datum/reagent/consumable/ethanol/beer/green/on_mob_end_metabolize(mob/living/drinker) + . = ..() drinker.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, color) +/datum/reagent/consumable/ethanol/beer/green/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() + metabolization_rate = 1 * REAGENTS_METABOLISM + + if(!ishuman(affected_mob)) + return + + var/mob/living/carbon/human/affected_human = affected_mob + if(HAS_TRAIT(affected_human, TRAIT_USES_SKINTONES)) + affected_human.skin_tone = "green" + else if(HAS_TRAIT(affected_human, TRAIT_MUTANT_COLORS) && !HAS_TRAIT(affected_human, TRAIT_FIXED_MUTANT_COLORS)) //Code stolen from spraytan overdose + affected_human.dna.features["mcolor"] = "#a8e61d" + affected_human.update_body(is_creating = TRUE) + /datum/reagent/consumable/ethanol/kahlua name = "Kahlua" description = "A widely known, Mexican coffee-flavoured liqueur. In production since 1936!" @@ -153,13 +184,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/kahlua/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.set_dizzy_if_lower(10 SECONDS * REM * seconds_per_tick) drinker.adjust_drowsiness(-6 SECONDS * REM * seconds_per_tick) drinker.AdjustSleeping(-40 * REM * seconds_per_tick) if(!HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) drinker.set_jitter_if_lower(10 SECONDS) - ..() - . = TRUE /datum/reagent/consumable/ethanol/whiskey name = "Whiskey" @@ -186,9 +216,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/whiskey/candycorn/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(5, seconds_per_tick)) drinker.adjust_hallucinations(4 SECONDS * REM * seconds_per_tick) - ..() /datum/reagent/consumable/ethanol/thirteenloko name = "Thirteen Loko" @@ -202,20 +232,21 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/thirteenloko/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_drowsiness(-14 SECONDS * REM * seconds_per_tick) drinker.AdjustSleeping(-40 * REM * seconds_per_tick) drinker.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, drinker.get_body_temp_normal()) if(!HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) drinker.set_jitter_if_lower(10 SECONDS) - ..() - return TRUE /datum/reagent/consumable/ethanol/thirteenloko/overdose_start(mob/living/drinker) + . = ..() to_chat(drinker, span_userdanger("Your entire body violently jitters as you start to feel queasy. You really shouldn't have drank all of that [name]!")) drinker.set_jitter_if_lower(40 SECONDS) drinker.Stun(1.5 SECONDS) /datum/reagent/consumable/ethanol/thirteenloko/overdose_process(mob/living/drinker, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(3.5, seconds_per_tick) && iscarbon(drinker)) var/obj/item/held_item = drinker.get_active_held_item() if(held_item) @@ -234,15 +265,18 @@ eyes.forceMove(get_turf(drinker)) to_chat(drinker, span_userdanger("You double over in pain as you feel your eyeballs liquify in your head!")) drinker.emote("scream") - drinker.adjustBruteLoss(15, required_bodytype = affected_bodytype) + if(drinker.adjustBruteLoss(15 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)) + . = UPDATE_MOB_HEALTH else to_chat(drinker, span_userdanger("You scream in terror as you go blind!")) - eyes.apply_organ_damage(eyes.maxHealth) + if(eyes.apply_organ_damage(eyes.maxHealth)) + . = UPDATE_MOB_HEALTH drinker.emote("scream") if(SPT_PROB(1.5, seconds_per_tick) && iscarbon(drinker)) drinker.visible_message(span_danger("[drinker] starts having a seizure!"), span_userdanger("You have a seizure!")) - drinker.Unconscious(10 SECONDS) + if(drinker.Unconscious(10 SECONDS)) + . = UPDATE_MOB_HEALTH drinker.set_jitter_if_lower(700 SECONDS) if(SPT_PROB(0.5, seconds_per_tick) && iscarbon(drinker)) @@ -271,10 +305,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/bilk/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.getBruteLoss() && SPT_PROB(5, seconds_per_tick)) - drinker.heal_bodypart_damage(brute = 1) - . = TRUE - return ..() || . + if(drinker.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/threemileisland name = "Three Mile Island Iced Tea" @@ -287,8 +321,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/threemileisland/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.set_drugginess(100 SECONDS * REM * seconds_per_tick) - return ..() /datum/reagent/consumable/ethanol/gin name = "Gin" @@ -387,7 +421,7 @@ description = "A sweet and strongly alcoholic drink, made after numerous distillations and years of maturing. Classy as fornication." color = "#AB3C05" // rgb: 171, 60, 5 boozepwr = 75 - taste_description = "angry and irish" + taste_description = "smooth and french" ph = 3.5 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED glass_price = DRINK_PRICE_STOCK @@ -401,9 +435,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/absinthe/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(5, seconds_per_tick) && !HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) - drinker.adjust_hallucinations(8 SECONDS) - ..() + drinker.adjust_hallucinations(8 SECONDS * REM * seconds_per_tick) /datum/reagent/consumable/ethanol/hooch name = "Hooch" @@ -499,13 +533,15 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/cuba_libre/on_mob_life(mob/living/carbon/cubano, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update if(cubano.mind && cubano.mind.has_antag_datum(/datum/antagonist/rev)) //Cuba Libre, the traditional drink of revolutions! Heals revolutionaries. - cubano.adjustBruteLoss(-1 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - cubano.adjustFireLoss(-1 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - cubano.adjustToxLoss(-1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - cubano.adjustOxyLoss(-5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - . = TRUE - return ..() || . + need_mob_update = cubano.adjustBruteLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += cubano.adjustFireLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += cubano.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += cubano.adjustOxyLoss(-5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/whiskey_cola name = "Whiskey Cola" @@ -554,51 +590,69 @@ taste_description = "oranges" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/ethanol/screwdrivercocktail/on_transfer(atom/atom, methods = TOUCH, trans_volume) - if(!(methods & INGEST)) - return ..() - - if(src == atom.reagents.get_master_reagent() && istype(atom, /obj/item/reagent_containers/cup/glass/drinkingglass)) - var/obj/item/reagent_containers/cup/glass/drinkingglass/drink = atom - drink.tool_behaviour = TOOL_SCREWDRIVER +/datum/reagent/consumable/ethanol/screwdrivercocktail/on_new(data) + . = ..() + // We want to turn only base drinking glasses with screwdriver(cocktail) into screwdrivers(tool), + // but we can't check style so we have to check type, and we don't want it match subtypes like istype does + if(holder?.my_atom && holder.my_atom.type == /obj/item/reagent_containers/cup/glass/drinkingglass/) var/list/reagent_change_signals = list( COMSIG_REAGENTS_ADD_REAGENT, COMSIG_REAGENTS_NEW_REAGENT, COMSIG_REAGENTS_REM_REAGENT, - COMSIG_REAGENTS_DEL_REAGENT, - COMSIG_REAGENTS_CLEAR_REAGENTS, - COMSIG_REAGENTS_REACTED, ) - RegisterSignals(drink.reagents, reagent_change_signals, PROC_REF(on_reagent_change)) - - return ..() + RegisterSignals(holder, reagent_change_signals, PROC_REF(on_reagent_change)) + RegisterSignal(holder, COMSIG_REAGENTS_CLEAR_REAGENTS, PROC_REF(on_reagents_clear)) + RegisterSignal(holder, COMSIG_REAGENTS_DEL_REAGENT, PROC_REF(on_reagent_delete)) + if(src == holder.get_master_reagent()) + var/obj/item/reagent_containers/cup/glass/drinkingglass/drink = holder.my_atom + drink.tool_behaviour = TOOL_SCREWDRIVER + drink.usesound = list('sound/items/screwdriver.ogg', 'sound/items/screwdriver2.ogg') /datum/reagent/consumable/ethanol/screwdrivercocktail/proc/on_reagent_change(datum/reagents/reagents) SIGNAL_HANDLER - if(src != reagents.get_master_reagent()) - var/obj/item/reagent_containers/cup/glass/drinkingglass/drink = reagents.my_atom + var/obj/item/reagent_containers/cup/glass/drinkingglass/drink = reagents.my_atom + if(reagents.get_master_reagent() == src) + drink.tool_behaviour = TOOL_SCREWDRIVER + drink.usesound = list('sound/items/screwdriver.ogg', 'sound/items/screwdriver2.ogg') + else + drink.tool_behaviour = initial(drink.tool_behaviour) + drink.usesound = initial(drink.usesound) + +/datum/reagent/consumable/ethanol/screwdrivercocktail/proc/on_reagents_clear(datum/reagents/reagents) + SIGNAL_HANDLER + unregister_screwdriver(reagents) + +/datum/reagent/consumable/ethanol/screwdrivercocktail/proc/on_reagent_delete(datum/reagents/reagents, datum/reagent/deleted_reagent) + SIGNAL_HANDLER + if(deleted_reagent != src) + return + unregister_screwdriver(reagents) + +/datum/reagent/consumable/ethanol/screwdrivercocktail/proc/unregister_screwdriver(datum/reagents/reagents) + var/obj/item/reagent_containers/cup/glass/drinkingglass/drink = reagents.my_atom + if(drink.tool_behaviour == TOOL_SCREWDRIVER) drink.tool_behaviour = initial(drink.tool_behaviour) - UnregisterSignal(reagents, list( + drink.usesound = initial(drink.usesound) + UnregisterSignal(reagents, list( COMSIG_REAGENTS_ADD_REAGENT, COMSIG_REAGENTS_NEW_REAGENT, COMSIG_REAGENTS_REM_REAGENT, COMSIG_REAGENTS_DEL_REAGENT, COMSIG_REAGENTS_CLEAR_REAGENTS, - COMSIG_REAGENTS_REACTED, )) /datum/reagent/consumable/ethanol/screwdrivercocktail/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() var/obj/item/organ/internal/liver/liver = drinker.get_organ_slot(ORGAN_SLOT_LIVER) if(HAS_TRAIT(liver, TRAIT_ENGINEER_METABOLISM)) ADD_TRAIT(drinker, TRAIT_HALT_RADIATION_EFFECTS, "[type]") if (HAS_TRAIT(drinker, TRAIT_IRRADIATED)) - drinker.adjustToxLoss(-2 * REM * seconds_per_tick, required_biotype = affected_biotype) - - return ..() + if(drinker.adjustToxLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/screwdrivercocktail/on_mob_end_metabolize(mob/living/drinker) + . = ..() REMOVE_TRAIT(drinker, TRAIT_HALT_RADIATION_EFFECTS, "[type]") - return ..() /datum/reagent/consumable/ethanol/booger name = "Booger" @@ -618,9 +672,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/bloody_mary/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.blood_volume < BLOOD_VOLUME_NORMAL) drinker.blood_volume = min(drinker.blood_volume + (3 * REM * seconds_per_tick), BLOOD_VOLUME_NORMAL) //Bloody Mary quickly restores blood loss. - ..() /datum/reagent/consumable/ethanol/brave_bull name = "Brave Bull" @@ -631,20 +685,21 @@ taste_description = "alcoholic bravery" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED glass_price = DRINK_PRICE_EASY + metabolized_traits = list(TRAIT_FEARLESS, TRAIT_ANALGESIA) var/tough_text /datum/reagent/consumable/ethanol/brave_bull/on_mob_metabolize(mob/living/drinker) + . = ..() tough_text = pick("brawny", "tenacious", "tough", "hardy", "sturdy") //Tuff stuff to_chat(drinker, span_notice("You feel [tough_text]!")) drinker.maxHealth += 10 //Brave Bull makes you sturdier, and thus capable of withstanding a tiny bit more punishment. drinker.health += 10 - ADD_TRAIT(drinker, TRAIT_FEARLESS, type) /datum/reagent/consumable/ethanol/brave_bull/on_mob_end_metabolize(mob/living/drinker) + . = ..() to_chat(drinker, span_notice("You no longer feel [tough_text].")) drinker.maxHealth -= 10 drinker.health = min(drinker.health - 10, drinker.maxHealth) //This can indeed crit you if you're alive solely based on alchol ingestion - REMOVE_TRAIT(drinker, TRAIT_FEARLESS, type) /datum/reagent/consumable/ethanol/tequila_sunrise name = "Tequila Sunrise" @@ -658,6 +713,7 @@ var/obj/effect/light_holder /datum/reagent/consumable/ethanol/tequila_sunrise/on_mob_metabolize(mob/living/drinker) + . = ..() to_chat(drinker, span_notice("You feel gentle warmth spread through your body!")) light_holder = new(drinker) light_holder.set_light(3, 0.7, "#FFCC00") //Tequila Sunrise makes you radiate dim light, like a sunrise! @@ -670,6 +726,7 @@ return ..() /datum/reagent/consumable/ethanol/tequila_sunrise/on_mob_end_metabolize(mob/living/drinker) + . = ..() to_chat(drinker, span_notice("The warmth in your body fades.")) QDEL_NULL(light_holder) @@ -683,8 +740,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/toxins_special/on_mob_life(mob/living/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_bodytemperature(15 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, drinker.get_body_temp_normal() + 20) //310.15 is the normal bodytemp. - return ..() /datum/reagent/consumable/ethanol/beepsky_smash name = "Beepsky Smash" @@ -700,6 +757,7 @@ var/datum/brain_trauma/special/beepsky/beepsky_hallucination /datum/reagent/consumable/ethanol/beepsky_smash/on_mob_metabolize(mob/living/carbon/drinker) + . = ..() if(HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) metabolization_rate = 0.8 // if you don't have a liver, or your liver isn't an officer's liver @@ -707,28 +765,27 @@ if(!liver || !HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM)) beepsky_hallucination = new() drinker.gain_trauma(beepsky_hallucination, TRAUMA_RESILIENCE_ABSOLUTE) - ..() /datum/reagent/consumable/ethanol/beepsky_smash/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.set_jitter_if_lower(4 SECONDS) var/obj/item/organ/internal/liver/liver = drinker.get_organ_slot(ORGAN_SLOT_LIVER) // if you have a liver and that liver is an officer's liver if(liver && HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM)) - . = TRUE - drinker.adjustStaminaLoss(-10 * REM * seconds_per_tick, required_biotype = affected_biotype) + if(drinker.adjustStaminaLoss(-10 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH if(SPT_PROB(10, seconds_per_tick)) drinker.cause_hallucination(get_random_valid_hallucination_subtype(/datum/hallucination/nearby_fake_item), name) if(SPT_PROB(5, seconds_per_tick)) drinker.cause_hallucination(/datum/hallucination/stray_bullet, name) - ..() - /datum/reagent/consumable/ethanol/beepsky_smash/on_mob_end_metabolize(mob/living/carbon/drinker) + . = ..() if(beepsky_hallucination) QDEL_NULL(beepsky_hallucination) - return ..() /datum/reagent/consumable/ethanol/beepsky_smash/overdose_start(mob/living/carbon/drinker) + . = ..() var/obj/item/organ/internal/liver/liver = drinker.get_organ_slot(ORGAN_SLOT_LIVER) // if you don't have a liver, or your liver isn't an officer's liver if(!liver || !HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM)) @@ -754,6 +811,7 @@ var/dorf_mode = FALSE /datum/reagent/consumable/ethanol/manly_dorf/on_mob_metabolize(mob/living/drinker) + . = ..() if(ishuman(drinker)) var/mob/living/carbon/human/potential_dwarf = drinker if(HAS_TRAIT(potential_dwarf, TRAIT_DWARF)) @@ -762,10 +820,13 @@ dorf_mode = TRUE /datum/reagent/consumable/ethanol/manly_dorf/on_mob_life(mob/living/carbon/dwarf, seconds_per_tick, times_fired) + . = ..() if(dorf_mode) - dwarf.adjustBruteLoss(-2 * REM * seconds_per_tick, required_bodytype = affected_bodytype) - dwarf.adjustFireLoss(-2 * REM * seconds_per_tick, required_bodytype = affected_bodytype) - return ..() + var/need_mob_update + need_mob_update = dwarf.adjustBruteLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += dwarf.adjustFireLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/longislandicedtea name = "Long Island Iced Tea" @@ -795,6 +856,7 @@ glass_price = DRINK_PRICE_EASY /datum/reagent/consumable/ethanol/b52/on_mob_metabolize(mob/living/drinker) + . = ..() playsound(drinker, 'sound/effects/explosion_distant.ogg', 100, FALSE) /datum/reagent/consumable/ethanol/irishcoffee @@ -845,8 +907,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/manhattan_proj/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.set_drugginess(1 MINUTES * REM * seconds_per_tick) - return ..() /datum/reagent/consumable/ethanol/whiskeysoda name = "Whiskey Soda" @@ -867,8 +929,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/antifreeze/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_bodytemperature(20 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, drinker.get_body_temp_normal() + 20) //310.15 is the normal bodytemp. - return ..() /datum/reagent/consumable/ethanol/barefoot name = "Barefoot" @@ -880,12 +942,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/barefoot/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(ishuman(drinker)) //Barefoot causes the imbiber to quickly regenerate brute trauma if they're not wearing shoes. var/mob/living/carbon/human/unshoed = drinker if(!unshoed.shoes) - unshoed.adjustBruteLoss(-3 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - . = TRUE - return ..() || . + if(unshoed.adjustBruteLoss(-3 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/snowwhite name = "Snow White" @@ -1011,16 +1073,15 @@ quality = DRINK_VERYGOOD taste_description = "concentrated matter" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + metabolized_traits = list(TRAIT_MADNESS_IMMUNE) var/static/list/ray_filter = list(type = "rays", size = 40, density = 15, color = SUPERMATTER_SINGULARITY_RAYS_COLOUR, factor = 15) -/datum/reagent/consumable/ethanol/singulo/on_mob_metabolize(mob/living/drinker) - ADD_TRAIT(drinker, TRAIT_MADNESS_IMMUNE, type) - /datum/reagent/consumable/ethanol/singulo/on_mob_end_metabolize(mob/living/drinker) - REMOVE_TRAIT(drinker, TRAIT_MADNESS_IMMUNE, type) + . = ..() drinker.remove_filter("singulo_rays") /datum/reagent/consumable/ethanol/singulo/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(2.5, seconds_per_tick)) // 20u = 1x1, 45u = 2x2, 80u = 3x3 var/volume_to_radius = FLOOR(sqrt(volume/5), 1) - 1 @@ -1036,7 +1097,6 @@ animate(drinker.get_filter("singulo_rays"), offset = 10, time = 1.5 SECONDS, loop = -1) addtimer(CALLBACK(drinker, TYPE_PROC_REF(/datum, remove_filter), "singulo_rays"), 1.5 SECONDS) drinker.emote("burp") - return ..() /datum/reagent/consumable/ethanol/sbiten name = "Sbiten" @@ -1048,8 +1108,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/sbiten/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_bodytemperature(50 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, BODYTEMP_HEAT_DAMAGE_LIMIT) //310.15 is the normal bodytemp. - return ..() /datum/reagent/consumable/ethanol/red_mead name = "Red Mead" @@ -1079,8 +1139,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/iced_beer/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_bodytemperature(-20 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, T0C) //310.15 is the normal bodytemp. - return ..() /datum/reagent/consumable/ethanol/grog name = "Grog" @@ -1148,9 +1208,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/changelingsting/on_mob_life(mob/living/carbon/target, seconds_per_tick, times_fired) - var/datum/antagonist/changeling/changeling = target.mind?.has_antag_datum(/datum/antagonist/changeling) + . = ..() + var/datum/antagonist/changeling/changeling = IS_CHANGELING(target) changeling?.adjust_chemicals(metabolization_rate * REM * seconds_per_tick) - return ..() /datum/reagent/consumable/ethanol/irishcarbomb name = "Irish Car Bomb" @@ -1171,9 +1231,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/syndicatebomb/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(2.5, seconds_per_tick)) playsound(get_turf(drinker), 'sound/effects/explosionfar.ogg', 100, TRUE) - return ..() /datum/reagent/consumable/ethanol/hiveminderaser name = "Hivemind Eraser" @@ -1214,11 +1274,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/bananahonk/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() var/obj/item/organ/internal/liver/liver = drinker.get_organ_slot(ORGAN_SLOT_LIVER) - if((liver && HAS_TRAIT(liver, TRAIT_COMEDY_METABOLISM)) || ismonkey(drinker)) - drinker.heal_bodypart_damage(1 * REM * seconds_per_tick, 1 * REM * seconds_per_tick) - . = TRUE - return ..() || . + if((liver && HAS_TRAIT(liver, TRAIT_COMEDY_METABOLISM)) || is_simian(drinker)) + if(drinker.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/silencer name = "Silencer" @@ -1231,11 +1291,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/silencer/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(ishuman(drinker) && HAS_MIND_TRAIT(drinker, TRAIT_MIMING)) drinker.set_silence_if_lower(MIMEDRINK_SILENCE_DURATION) - drinker.heal_bodypart_damage(1 * REM * seconds_per_tick, 1 * REM * seconds_per_tick) - . = TRUE - return ..() || . + if(drinker.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/drunkenblumpkin name = "Drunken Blumpkin" @@ -1275,9 +1335,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/fetching_fizz/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() for(var/obj/item/stack/ore/O in orange(3, drinker)) step_towards(O, get_turf(drinker)) - return ..() //Another reference. Heals those in critical condition extremely quickly. /datum/reagent/consumable/ethanol/hearty_punch @@ -1291,14 +1351,15 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/hearty_punch/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.health <= 0) - drinker.adjustBruteLoss(-3 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - drinker.adjustFireLoss(-3 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - drinker.adjustCloneLoss(-5 * REM * seconds_per_tick, 0) - drinker.adjustOxyLoss(-4 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - drinker.adjustToxLoss(-3 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - . = TRUE - return ..() || . + var/need_mob_update + need_mob_update = drinker.adjustBruteLoss(-3 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += drinker.adjustFireLoss(-3 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += drinker.adjustOxyLoss(-4 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += drinker.adjustToxLoss(-3 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/bacchus_blessing //An EXTREMELY powerful drink. Smashed in seconds, dead in minutes. name = "Bacchus' Blessing" @@ -1319,20 +1380,19 @@ glass_price = DRINK_PRICE_HIGH /datum/reagent/consumable/ethanol/atomicbomb/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.set_drugginess(100 SECONDS * REM * seconds_per_tick) if(!HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) drinker.adjust_confusion(2 SECONDS * REM * seconds_per_tick) drinker.set_dizzy_if_lower(20 SECONDS * REM * seconds_per_tick) drinker.adjust_slurring(6 SECONDS * REM * seconds_per_tick) switch(current_cycle) - if(51 to 200) + if(52 to 201) drinker.Sleeping(100 * REM * seconds_per_tick) - . = TRUE - if(201 to INFINITY) + if(202 to INFINITY) drinker.AdjustSleeping(40 * REM * seconds_per_tick) - drinker.adjustToxLoss(2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + if(drinker.adjustToxLoss(2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/gargle_blaster name = "Pan-Galactic Gargle Blaster" @@ -1344,20 +1404,19 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/gargle_blaster/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_dizzy(3 SECONDS * REM * seconds_per_tick) switch(current_cycle) - if(15 to 45) + if(16 to 46) drinker.adjust_slurring(3 SECONDS * REM * seconds_per_tick) - - if(45 to 55) + if(46 to 56) if(SPT_PROB(30, seconds_per_tick)) drinker.adjust_confusion(3 SECONDS * REM * seconds_per_tick) - if(55 to 200) + if(56 to 201) drinker.set_drugginess(110 SECONDS * REM * seconds_per_tick) - if(200 to INFINITY) - drinker.adjustToxLoss(2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + if(201 to INFINITY) + if(drinker.adjustToxLoss(2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/neurotoxin name = "Neurotoxin" @@ -1373,35 +1432,37 @@ return (pick(TRAIT_PARALYSIS_L_ARM,TRAIT_PARALYSIS_R_ARM,TRAIT_PARALYSIS_R_LEG,TRAIT_PARALYSIS_L_LEG)) /datum/reagent/consumable/ethanol/neurotoxin/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.set_drugginess(100 SECONDS * REM * seconds_per_tick) drinker.adjust_dizzy(4 SECONDS * REM * seconds_per_tick) - drinker.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1 * REM * seconds_per_tick, 150, required_organ_flag = affected_organ_flags) + var/need_mob_update + need_mob_update = drinker.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1 * REM * seconds_per_tick, 150, required_organ_flag = affected_organ_flags) if(SPT_PROB(10, seconds_per_tick)) - drinker.adjustStaminaLoss(10, required_biotype = affected_biotype) + need_mob_update += drinker.adjustStaminaLoss(10 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) drinker.drop_all_held_items() to_chat(drinker, span_notice("You cant feel your hands!")) - if(current_cycle > 5) + if(current_cycle > 6) if(SPT_PROB(10, seconds_per_tick)) var/paralyzed_limb = pick_paralyzed_limb() ADD_TRAIT(drinker, paralyzed_limb, type) - drinker.adjustStaminaLoss(10, required_biotype = affected_biotype) - if(current_cycle > 30) - drinker.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - if(current_cycle > 50 && SPT_PROB(7.5, seconds_per_tick)) + need_mob_update += drinker.adjustStaminaLoss(10 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + if(current_cycle > 31) + need_mob_update += drinker.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(current_cycle > 51 && SPT_PROB(7.5, seconds_per_tick)) if(!drinker.undergoing_cardiac_arrest() && drinker.can_heartattack()) drinker.set_heartattack(TRUE) if(drinker.stat == CONSCIOUS) drinker.visible_message(span_userdanger("[drinker] clutches at [drinker.p_their()] chest as if [drinker.p_their()] heart stopped!")) - . = TRUE - ..() + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/neurotoxin/on_mob_end_metabolize(mob/living/carbon/drinker) + . = ..() REMOVE_TRAIT(drinker, TRAIT_PARALYSIS_L_ARM, type) REMOVE_TRAIT(drinker, TRAIT_PARALYSIS_R_ARM, type) REMOVE_TRAIT(drinker, TRAIT_PARALYSIS_R_LEG, type) REMOVE_TRAIT(drinker, TRAIT_PARALYSIS_L_LEG, type) drinker.adjustStaminaLoss(10, required_biotype = affected_biotype) - ..() /datum/reagent/consumable/ethanol/hippies_delight name = "Hippie's Delight" @@ -1415,36 +1476,36 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/hippies_delight/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.set_slurring_if_lower(1 SECONDS * REM * seconds_per_tick) switch(current_cycle) - if(1 to 5) + if(2 to 6) drinker.set_dizzy_if_lower(20 SECONDS * REM * seconds_per_tick) drinker.set_drugginess(1 MINUTES * REM * seconds_per_tick) if(SPT_PROB(5, seconds_per_tick)) drinker.emote(pick("twitch","giggle")) - if(5 to 10) + if(6 to 11) drinker.set_jitter_if_lower(40 SECONDS * REM * seconds_per_tick) drinker.set_dizzy_if_lower(40 SECONDS * REM * seconds_per_tick) drinker.set_drugginess(1.5 MINUTES * REM * seconds_per_tick) if(SPT_PROB(10, seconds_per_tick)) drinker.emote(pick("twitch","giggle")) - if (10 to 200) + if (11 to 201) drinker.set_jitter_if_lower(80 SECONDS * REM * seconds_per_tick) drinker.set_dizzy_if_lower(80 SECONDS * REM * seconds_per_tick) drinker.set_drugginess(2 MINUTES * REM * seconds_per_tick) if(SPT_PROB(16, seconds_per_tick)) drinker.emote(pick("twitch","giggle")) - if(200 to INFINITY) + if(201 to INFINITY) drinker.set_jitter_if_lower(120 SECONDS * REM * seconds_per_tick) drinker.set_dizzy_if_lower(120 SECONDS * REM * seconds_per_tick) drinker.set_drugginess(2.5 MINUTES * REM * seconds_per_tick) if(SPT_PROB(23, seconds_per_tick)) drinker.emote(pick("twitch","giggle")) if(SPT_PROB(16, seconds_per_tick)) - drinker.adjustToxLoss(2, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + if(drinker.adjustToxLoss(2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/eggnog name = "Eggnog" @@ -1476,9 +1537,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/narsour/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_timed_status_effect(6 SECONDS * REM * seconds_per_tick, /datum/status_effect/speech/slurring/cult, max_duration = 6 SECONDS) drinker.adjust_stutter_up_to(6 SECONDS * REM * seconds_per_tick, 6 SECONDS) - return ..() /datum/reagent/consumable/ethanol/triple_sec name = "Triple Sec" @@ -1522,12 +1583,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/quadruple_sec/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() //Securidrink in line with the Screwdriver for engineers or Nothing for mimes var/obj/item/organ/internal/liver/liver = drinker.get_organ_slot(ORGAN_SLOT_LIVER) if(liver && HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM)) - drinker.heal_bodypart_damage(1 * REM * seconds_per_tick, 1 * REM * seconds_per_tick) - . = TRUE - return ..() + if(drinker.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/quintuple_sec name = "Quintuple Sec" @@ -1539,13 +1600,15 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/quintuple_sec/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() //Securidrink in line with the Screwdriver for engineers or Nothing for mimes but STRONG.. var/obj/item/organ/internal/liver/liver = drinker.get_organ_slot(ORGAN_SLOT_LIVER) if(liver && HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM)) - drinker.heal_bodypart_damage(2 * REM * seconds_per_tick, 2 * REM * seconds_per_tick) - drinker.adjustStaminaLoss(-2 * REM * seconds_per_tick, required_biotype = affected_biotype) - . = TRUE - return ..() + var/need_mob_update + need_mob_update = drinker.heal_bodypart_damage(2 * REM * seconds_per_tick, 2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += drinker.adjustStaminaLoss(-2 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/grasshopper name = "Grasshopper" @@ -1578,28 +1641,34 @@ glass_price = DRINK_PRICE_HIGH /datum/reagent/consumable/ethanol/bastion_bourbon/on_mob_metabolize(mob/living/drinker) + . = ..() var/heal_points = 10 if(drinker.health <= 0) heal_points = 20 //heal more if we're in softcrit - for(var/counter in 1 to min(volume, heal_points)) //only heals 1 point of damage per unit on add, for balance reasons - drinker.adjustBruteLoss(-1, required_bodytype = affected_bodytype) - drinker.adjustFireLoss(-1, required_bodytype = affected_bodytype) - drinker.adjustToxLoss(-1, required_biotype = affected_biotype) - drinker.adjustOxyLoss(-1, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - drinker.adjustStaminaLoss(-1, required_biotype = affected_biotype) + var/need_mob_update + var/heal_amt = min(volume, heal_points) //only heals 1 point of damage per unit on add, for balance reasons + need_mob_update = drinker.adjustBruteLoss(-heal_amt, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += drinker.adjustFireLoss(-heal_amt, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += drinker.adjustToxLoss(-heal_amt, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += drinker.adjustOxyLoss(-heal_amt, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += drinker.adjustStaminaLoss(-heal_amt, updating_stamina = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + drinker.updatehealth() drinker.visible_message(span_warning("[drinker] shivers with renewed vigor!"), span_notice("One taste of [lowertext(name)] fills you with energy!")) if(!drinker.stat && heal_points == 20) //brought us out of softcrit drinker.visible_message(span_danger("[drinker] lurches to [drinker.p_their()] feet!"), span_boldnotice("Up and at 'em, kid.")) /datum/reagent/consumable/ethanol/bastion_bourbon/on_mob_life(mob/living/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.health > 0) - drinker.adjustBruteLoss(-1 * REM * seconds_per_tick, required_bodytype = affected_bodytype) - drinker.adjustFireLoss(-1 * REM * seconds_per_tick, required_bodytype = affected_bodytype) - drinker.adjustToxLoss(-0.5 * REM * seconds_per_tick, required_biotype = affected_biotype) - drinker.adjustOxyLoss(-3 * REM * seconds_per_tick, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - drinker.adjustStaminaLoss(-5 * REM * seconds_per_tick, required_biotype = affected_biotype) - . = TRUE - ..() + var/need_mob_update + need_mob_update = drinker.adjustBruteLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += drinker.adjustFireLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += drinker.adjustToxLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += drinker.adjustOxyLoss(-3 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += drinker.adjustStaminaLoss(-5 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/squirt_cider name = "Squirt Cider" @@ -1611,9 +1680,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/squirt_cider/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.satiety += 5 * REM * seconds_per_tick //for context, vitamins give 15 satiety per second - ..() - . = TRUE /datum/reagent/consumable/ethanol/fringe_weaver name = "Fringe Weaver" @@ -1635,9 +1703,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/sugar_rush/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.satiety -= 10 * REM * seconds_per_tick //junky as hell! a whole glass will keep you from being able to eat junk food - ..() - . = TRUE /datum/reagent/consumable/ethanol/crevice_spike name = "Crevice Spike" @@ -1649,6 +1716,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/crevice_spike/on_mob_metabolize(mob/living/drinker) //damage only applies when drink first enters system and won't again until drink metabolizes out + . = ..() drinker.adjustBruteLoss(3 * min(5,volume), required_bodytype = affected_bodytype) //minimum 3 brute damage on ingestion to limit non-drink means of injury - a full 5 unit gulp of the drink trucks you for the full 15 /datum/reagent/consumable/ethanol/sake @@ -1670,9 +1738,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/peppermint_patty/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.apply_status_effect(/datum/status_effect/throat_soothed) drinker.adjust_bodytemperature(5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, drinker.get_body_temp_normal()) - ..() /datum/reagent/consumable/ethanol/alexander name = "Alexander" @@ -1685,24 +1753,25 @@ var/obj/item/shield/mighty_shield /datum/reagent/consumable/ethanol/alexander/on_mob_metabolize(mob/living/drinker) + . = ..() if(ishuman(drinker)) var/mob/living/carbon/human/the_human = drinker for(var/obj/item/shield/the_shield in the_human.contents) mighty_shield = the_shield mighty_shield.block_chance += 10 to_chat(the_human, span_notice("[the_shield] appears polished, although you don't recall polishing it.")) - return TRUE /datum/reagent/consumable/ethanol/alexander/on_mob_life(mob/living/drinker, seconds_per_tick, times_fired) - ..() if(mighty_shield && !(mighty_shield in drinker.contents)) //If you had a shield and lose it, you lose the reagent as well. Otherwise this is just a normal drink. holder.remove_reagent(type, volume) + return + return ..() /datum/reagent/consumable/ethanol/alexander/on_mob_end_metabolize(mob/living/drinker) + . = ..() if(mighty_shield) mighty_shield.block_chance -= 10 to_chat(drinker,span_notice("You notice [mighty_shield] looks worn again. Weird.")) - ..() /datum/reagent/consumable/ethanol/amaretto_alexander name = "Amaretto Alexander" @@ -1734,7 +1803,7 @@ glass_price = DRINK_PRICE_MEDIUM /datum/reagent/consumable/ethanol/between_the_sheets/on_mob_life(mob/living/drinker, seconds_per_tick, times_fired) - ..() + . = ..() var/is_between_the_sheets = FALSE for(var/obj/item/bedsheet/bedsheet in range(drinker.loc, 0)) if(bedsheet.loc != drinker.loc) // bedsheets in your backpack/neck don't count @@ -1745,15 +1814,18 @@ if(!drinker.IsSleeping() || !is_between_the_sheets) return + var/need_mob_update if(drinker.getBruteLoss() && drinker.getFireLoss()) //If you are damaged by both types, slightly increased healing but it only heals one. The more the merrier wink wink. if(prob(50)) - drinker.adjustBruteLoss(-0.25 * REM * seconds_per_tick, required_bodytype = affected_bodytype) + need_mob_update = drinker.adjustBruteLoss(-0.25 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) else - drinker.adjustFireLoss(-0.25 * REM * seconds_per_tick, required_bodytype = affected_bodytype) + need_mob_update = drinker.adjustFireLoss(-0.25 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) else if(drinker.getBruteLoss()) //If you have only one, it still heals but not as well. - drinker.adjustBruteLoss(-0.2 * REM * seconds_per_tick, required_bodytype = affected_bodytype) + need_mob_update = drinker.adjustBruteLoss(-0.2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) else if(drinker.getFireLoss()) - drinker.adjustFireLoss(-0.2 * REM * seconds_per_tick, required_bodytype = affected_bodytype) + need_mob_update = drinker.adjustFireLoss(-0.2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/kamikaze name = "Kamikaze" @@ -1792,11 +1864,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/fernet/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.nutrition <= NUTRITION_LEVEL_STARVING) - drinker.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + if(drinker.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH drinker.adjust_nutrition(-5 * REM * seconds_per_tick) drinker.overeatduration = 0 - return ..() /datum/reagent/consumable/ethanol/fernet_cola name = "Fernet Cola" @@ -1808,11 +1881,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/fernet_cola/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.nutrition <= NUTRITION_LEVEL_STARVING) - drinker.adjustToxLoss(0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + if(drinker.adjustToxLoss(0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH drinker.adjust_nutrition(-3 * REM * seconds_per_tick) drinker.overeatduration = 0 - return ..() /datum/reagent/consumable/ethanol/fanciulli name = "Fanciulli" @@ -1825,15 +1899,14 @@ glass_price = DRINK_PRICE_HIGH /datum/reagent/consumable/ethanol/fanciulli/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_nutrition(-5 * REM * seconds_per_tick) drinker.overeatduration = 0 - return ..() /datum/reagent/consumable/ethanol/fanciulli/on_mob_metabolize(mob/living/drinker) + . = ..() if(drinker.health > 0) drinker.adjustStaminaLoss(20, required_biotype = affected_biotype) - . = TRUE - ..() /datum/reagent/consumable/ethanol/branca_menta name = "Branca Menta" @@ -1846,14 +1919,13 @@ glass_price = DRINK_PRICE_MEDIUM /datum/reagent/consumable/ethanol/branca_menta/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_bodytemperature(-20 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, T0C) - return ..() /datum/reagent/consumable/ethanol/branca_menta/on_mob_metabolize(mob/living/drinker) + . = ..() if(drinker.health > 0) drinker.adjustStaminaLoss(35, required_biotype = affected_biotype) - . = TRUE - ..() /datum/reagent/consumable/ethanol/blank_paper name = "Blank Paper" @@ -1866,11 +1938,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/blank_paper/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(ishuman(drinker) && HAS_MIND_TRAIT(drinker, TRAIT_MIMING)) drinker.set_silence_if_lower(MIMEDRINK_SILENCE_DURATION) - drinker.heal_bodypart_damage(1 * REM * seconds_per_tick, 1 * REM * seconds_per_tick) - . = TRUE - return ..() + if(drinker.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/fruit_wine name = "Fruit Wine" @@ -1997,13 +2069,16 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/wizz_fizz/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() //A healing drink similar to Quadruple Sec, Ling Stings, and Screwdrivers for the Wizznerds; the check is consistent with the changeling sting if(drinker?.mind?.has_antag_datum(/datum/antagonist/wizard)) - drinker.heal_bodypart_damage(1 * REM * seconds_per_tick, 1 * REM * seconds_per_tick) - drinker.adjustOxyLoss(-1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - drinker.adjustToxLoss(-1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - drinker.adjustStaminaLoss(-1 * REM * seconds_per_tick, required_biotype = affected_biotype) - return ..() + var/need_mob_update + need_mob_update = drinker.heal_bodypart_damage(1 * REM * seconds_per_tick, 1 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += drinker.adjustOxyLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += drinker.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += drinker.adjustStaminaLoss(-1 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/bug_spray name = "Bug Spray" @@ -2013,19 +2088,20 @@ quality = DRINK_GOOD taste_description = "the pain of ten thousand slain mosquitos" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + affected_biotype = MOB_BUG /datum/reagent/consumable/ethanol/bug_spray/on_new(data) . = ..() AddElement(/datum/element/bugkiller_reagent) /datum/reagent/consumable/ethanol/bug_spray/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() // Does some damage to bug biotypes - var/did_damage = drinker.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = MOB_BUG) - // Random chance of causing a screm if we did some damage - if(did_damage && SPT_PROB(2, seconds_per_tick)) - drinker.emote("scream") - - return ..() || did_damage + if(drinker.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH + // Random chance of causing a screm if we did some damage + if(SPT_PROB(2, seconds_per_tick)) + drinker.emote("scream") /datum/reagent/consumable/ethanol/applejack name = "Applejack" @@ -2054,10 +2130,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/turbo/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(2, seconds_per_tick)) to_chat(drinker, span_notice("[pick("You feel disregard for the rule of law.", "You feel pumped!", "Your head is pounding.", "Your thoughts are racing..")]")) - drinker.adjustStaminaLoss(-0.25 * drinker.get_drunk_amount() * REM * seconds_per_tick, required_biotype = affected_biotype) - return ..() + if(drinker.adjustStaminaLoss(-0.25 * drinker.get_drunk_amount() * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/old_timer name = "Old Timer" @@ -2069,6 +2146,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/old_timer/on_mob_life(mob/living/carbon/human/metabolizer, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(10, seconds_per_tick) && istype(metabolizer)) metabolizer.age += 1 if(metabolizer.age > 70) @@ -2083,8 +2161,6 @@ metabolizer.visible_message(span_notice("[metabolizer] becomes older than any man should be.. and crumbles into dust!")) metabolizer.dust(just_ash = FALSE, drop_items = TRUE, force = FALSE) - return ..() - /datum/reagent/consumable/ethanol/rubberneck name = "Rubberneck" description = "A quality rubberneck should not contain any gross natural ingredients." @@ -2093,14 +2169,7 @@ quality = DRINK_GOOD taste_description = "artifical fruityness" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/consumable/ethanol/rubberneck/on_mob_metabolize(mob/living/drinker) - . = ..() - ADD_TRAIT(drinker, TRAIT_SHOCKIMMUNE, type) - -/datum/reagent/consumable/ethanol/rubberneck/on_mob_end_metabolize(mob/living/drinker) - REMOVE_TRAIT(drinker, TRAIT_SHOCKIMMUNE, type) - return ..() + metabolized_traits = list(TRAIT_SHOCKIMMUNE) /datum/reagent/consumable/ethanol/duplex name = "Duplex" @@ -2121,11 +2190,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/trappist/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.mind?.holy_role) - drinker.adjustFireLoss(-2.5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) + if(drinker.adjustFireLoss(-2.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)) + . = UPDATE_MOB_HEALTH drinker.adjust_jitter(-2 SECONDS * REM * seconds_per_tick) drinker.adjust_stutter(-2 SECONDS * REM * seconds_per_tick) - return ..() /datum/reagent/consumable/ethanol/blazaam name = "Blazaam" @@ -2136,6 +2206,7 @@ var/stored_teleports = 0 /datum/reagent/consumable/ethanol/blazaam/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.get_drunk_amount() > 40) if(stored_teleports) do_teleport(drinker, get_turf(drinker), rand(1,3), channel = TELEPORT_CHANNEL_WORMHOLE) @@ -2145,7 +2216,6 @@ stored_teleports += rand(2, 6) if(prob(70)) drinker.vomit(vomit_flags = VOMIT_CATEGORY_DEFAULT, vomit_type = /obj/effect/decal/cleanable/vomit/purple) - return ..() /datum/reagent/consumable/ethanol/planet_cracker name = "Planet Cracker" @@ -2164,12 +2234,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/mauna_loa/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() // Heats the user up while the reagent is in the body. Occasionally makes you burst into flames. drinker.adjust_bodytemperature(25 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick) if (SPT_PROB(2.5, seconds_per_tick)) - drinker.adjust_fire_stacks(1) + drinker.adjust_fire_stacks(1 * REM * seconds_per_tick) drinker.ignite_mob() - ..() /datum/reagent/consumable/ethanol/painkiller name = "Painkiller" @@ -2179,6 +2249,7 @@ quality = DRINK_NICE taste_description = "sugary tartness" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + metabolized_traits = list(TRAIT_ANALGESIA) /datum/reagent/consumable/ethanol/pina_colada name = "Pina Colada" @@ -2197,6 +2268,7 @@ taste_description = "a horrible emulsion of pineapple and olive oil" /datum/reagent/consumable/ethanol/pina_olivada/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(8, seconds_per_tick)) drinker.manual_emote(pick("coughs up some oil", "swallows the lump in [drinker.p_their()] throat", "gags", "chokes up a bit")) if(SPT_PROB(3, seconds_per_tick)) @@ -2208,7 +2280,6 @@ "Your throat feels horrible.", ) to_chat(drinker, span_notice(pick(messages))) - return ..() /datum/reagent/consumable/ethanol/pruno // pruno mix is in drink_reagents name = "Pruno" @@ -2219,8 +2290,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/pruno/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_disgust(5 * REM * seconds_per_tick) - ..() /datum/reagent/consumable/ethanol/ginger_amaretto name = "Ginger Amaretto" @@ -2260,9 +2331,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/kortara/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(drinker.getBruteLoss() && SPT_PROB(10, seconds_per_tick)) - drinker.heal_bodypart_damage(1,0) - . = TRUE + if(drinker.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 0, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/ethanol/sea_breeze name = "Sea Breeze" @@ -2274,8 +2346,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/sea_breeze/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.apply_status_effect(/datum/status_effect/throat_soothed) - ..() /datum/reagent/consumable/ethanol/white_tiziran name = "White Tiziran" @@ -2296,8 +2368,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/drunken_espatier/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.add_mood_event("numb", /datum/mood_event/narcotic_medium, name) //comfortably numb - ..() /datum/reagent/consumable/ethanol/drunken_espatier/on_mob_metabolize(mob/living/drinker) . = ..() @@ -2318,12 +2390,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/protein_blend/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() drinker.adjust_nutrition(2 * REM * seconds_per_tick) if(!islizard(drinker)) drinker.adjust_disgust(5 * REM * seconds_per_tick) else drinker.adjust_disgust(2 * REM * seconds_per_tick) - ..() /datum/reagent/consumable/ethanol/mushi_kombucha name = "Mushi Kombucha" @@ -2344,9 +2416,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/triumphal_arch/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(islizard(drinker)) drinker.add_mood_event("triumph", /datum/mood_event/memories_of_home, name) - ..() /datum/reagent/consumable/ethanol/the_juice name = "The Juice" @@ -2364,9 +2436,9 @@ drinker.gain_trauma(prophet_trauma, TRAUMA_RESILIENCE_ABSOLUTE) /datum/reagent/consumable/ethanol/the_juice/on_mob_end_metabolize(mob/living/carbon/drinker) + . = ..() if(prophet_trauma) QDEL_NULL(prophet_trauma) - return ..() //a jacked up absinthe that causes hallucinations to the game master controller basically, used in smuggling objectives /datum/reagent/consumable/ethanol/ritual_wine @@ -2503,11 +2575,10 @@ var/hal_cap = 24 /datum/reagent/consumable/ethanol/helianthus/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(5, seconds_per_tick)) drinker.adjust_hallucinations_up_to(4 SECONDS * REM * seconds_per_tick, 48 SECONDS) - ..() - /datum/reagent/consumable/ethanol/plumwine name = "Plum wine" description = "Plums turned into wine." @@ -2538,8 +2609,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/gin_garden/on_mob_life(mob/living/carbon/doll, seconds_per_tick, times_fired) + . = ..() doll.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, doll.get_body_temp_normal()) - ..() /datum/reagent/consumable/ethanol/wine_voltaic name = "Voltaic Yellow Wine" @@ -2561,20 +2632,13 @@ /datum/reagent/consumable/ethanol/telepole name = "Telepole" - description = "A grounding rod in the form of a drink. Recharges ethereals, and gives temporary shock resistance." + description = "A grounding rod in the form of a drink. Recharges ethereals, and gives temporary shock resistance." boozepwr = 50 color = "#b300ff" quality = DRINK_NICE taste_description = "the howling storm" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/consumable/ethanol/telepole/on_mob_metabolize(mob/living/affected_mob) - . = ..() - ADD_TRAIT(affected_mob, TRAIT_SHOCKIMMUNE, type) - -/datum/reagent/consumable/ethanol/telepole/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_SHOCKIMMUNE, type) - return ..() + metabolized_traits = list(TRAIT_SHOCKIMMUNE) /datum/reagent/consumable/ethanol/telepole/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) //can't be on life because of the way blood works. . = ..() @@ -2596,11 +2660,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/pod_tesla/on_mob_metabolize(mob/living/affected_mob) - ..() + . = ..() affected_mob.add_traits(list(TRAIT_SHOCKIMMUNE,TRAIT_TESLA_SHOCKIMMUNE,TRAIT_FEARLESS), type) - /datum/reagent/consumable/ethanol/pod_tesla/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.remove_traits(list(TRAIT_SHOCKIMMUNE,TRAIT_TESLA_SHOCKIMMUNE,TRAIT_FEARLESS), type) /datum/reagent/consumable/ethanol/pod_tesla/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) //can't be on life because of the way blood works. @@ -2665,7 +2729,7 @@ description = "A drink glorifying Cybersun's enduring business." boozepwr = 20 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_FANTASTIC taste_description = "betrayal" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2674,7 +2738,7 @@ description = "A variation on the Long Island Iced Tea, made with yuyake for an alternative flavour that's hard to place." boozepwr = 40 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_VERYGOOD taste_description = "an asian twist on the liquor cabinet" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2683,7 +2747,7 @@ description = "It's a melon cream soda, except with alcohol- what's not to love? Well... possibly the hangovers." boozepwr = 6 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_GOOD taste_description = "creamy melon soda" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2692,7 +2756,7 @@ description = "A new take on a classic cocktail, the Kumicho takes the Godfather formula and adds shochu for an Asian twist." boozepwr = 62 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_VERYGOOD taste_description = "rice and rye" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2701,7 +2765,7 @@ description = "Made in celebration of the Martian Concession, the Red Planet is based on the classic El Presidente, and is as patriotic as it is bright crimson." boozepwr = 45 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_VERYGOOD taste_description = "the spirit of freedom" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2710,7 +2774,7 @@ description = "Named for Amaterasu, the Shinto Goddess of the Sun, this cocktail embodies radiance- or something like that, anyway." boozepwr = 54 //1 part bitters is a lot color = "#F54040" - quality = DRINK_NICE + quality = DRINK_VERYGOOD taste_description = "sweet nectar of the gods" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2719,7 +2783,7 @@ description = "An overly sweet cocktail, made with melon liqueur, melon juice, and champagne (which contains no melon, unfortunately)." boozepwr = 17 color = "#FF0C8D" - quality = DRINK_NICE + quality = DRINK_GOOD taste_description = "MELON" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2728,7 +2792,7 @@ description = "Based on the galaxy-famous \"Kyūkyoku no Ninja Pawā Sentai\", the Sentai Quencha is a favourite at anime conventions and weeb bars." boozepwr = 28 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_GOOD taste_description = "ultimate ninja power" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2737,7 +2801,7 @@ description = "A simple summer drink from Mars, made from a 1:1 mix of rice beer and lemonade." boozepwr = 6 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_GOOD taste_description = "bittersweet lemon" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2746,7 +2810,7 @@ description = "Sweet, bitter, spicy- that's a great combination." boozepwr = 6 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_VERYGOOD taste_description = "spicy pineapple beer" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2764,7 +2828,7 @@ description = "A stiff, bitter drink with an odd name and odder recipe." boozepwr = 26 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_VERYGOOD taste_description = "bitter raspberry" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2773,7 +2837,7 @@ description = "A drink to power your typing hands." boozepwr = 26 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_GOOD taste_description = "cyberspace" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2782,7 +2846,7 @@ description = "A take on the classic White Russian, subbing out the classics for some tropical flavours." boozepwr = 16 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_GOOD taste_description = "COCONUT" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2791,7 +2855,7 @@ description = "Behind this drink's red facade lurks a sharp, complex flavour." boozepwr = 15 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_VERYGOOD taste_description = "sunrise over the pacific" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2800,7 +2864,7 @@ description = "For when orgeat is in short supply, do as the spacers do- make do and mend." boozepwr = 52 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_VERYGOOD taste_description = "spicy nutty rum" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2809,7 +2873,7 @@ description = "Coconut rum, coffee liqueur, and espresso- an odd combination, to be sure, but a welcomed one." boozepwr = 20 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_VERYGOOD taste_description = "coconut coffee" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -2818,7 +2882,7 @@ description = "Sweet, sharp and coconutty." boozepwr = 30 color = "#F54040" - quality = DRINK_NICE + quality = DRINK_VERYGOOD taste_description = "the aloha state" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED diff --git a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm index 36444d6229b81..43430d0946916 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm @@ -8,10 +8,10 @@ default_container = /obj/item/reagent_containers/cup/glass/bottle/juice/orangejuice /datum/reagent/consumable/orangejuice/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.getOxyLoss() && SPT_PROB(16, seconds_per_tick)) - affected_mob.adjustOxyLoss(-1, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - . = TRUE - ..() + if(affected_mob.adjustOxyLoss(-1 * REM * seconds_per_tick, FALSE, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/tomatojuice name = "Tomato Juice" @@ -22,10 +22,10 @@ default_container = /obj/item/reagent_containers/cup/glass/bottle/juice/tomatojuice /datum/reagent/consumable/tomatojuice/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.getFireLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.heal_bodypart_damage(0, 1) - . = TRUE - ..() + if(affected_mob.heal_bodypart_damage(brute = 0, burn = 1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/limejuice name = "Lime Juice" @@ -37,10 +37,10 @@ default_container = /obj/item/reagent_containers/cup/glass/bottle/juice/limejuice /datum/reagent/consumable/limejuice/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.getToxLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.adjustToxLoss(-1, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + if(affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/carrotjuice name = "Carrot Juice" @@ -50,17 +50,20 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/carrotjuice/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_eye_blur(-2 SECONDS * REM * seconds_per_tick) affected_mob.adjust_temp_blindness(-2 SECONDS * REM * seconds_per_tick) + var/need_mob_update switch(current_cycle) if(1 to 20) //nothing if(21 to 110) if(SPT_PROB(100 * (1 - (sqrt(110 - current_cycle) / 10)), seconds_per_tick)) - affected_mob.adjustOrganLoss(ORGAN_SLOT_EYES, -2) + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_EYES, -2 * REM * seconds_per_tick) if(110 to INFINITY) - affected_mob.adjustOrganLoss(ORGAN_SLOT_EYES, -2) - return ..() + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_EYES, -2 * REM * seconds_per_tick) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/berryjuice name = "Berry Juice" @@ -84,9 +87,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/poisonberryjuice/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + . = ..() + if(affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/watermelonjuice name = "Watermelon Juice" @@ -111,11 +114,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/banana/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) - if((liver && HAS_TRAIT(liver, TRAIT_COMEDY_METABOLISM)) || ismonkey(affected_mob)) - affected_mob.heal_bodypart_damage(1 * REM * seconds_per_tick, 1 * REM * seconds_per_tick) - . = TRUE - ..() + if((liver && HAS_TRAIT(liver, TRAIT_COMEDY_METABOLISM)) || is_simian(affected_mob)) + if(affected_mob.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/nothing name = "Nothing" @@ -128,11 +131,11 @@ icon_state = "shotglass" /datum/reagent/consumable/nothing/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() if(ishuman(drinker) && HAS_MIND_TRAIT(drinker, TRAIT_MIMING)) drinker.set_silence_if_lower(MIMEDRINK_SILENCE_DURATION) - drinker.heal_bodypart_damage(1 * REM * seconds_per_tick, 1 * REM * seconds_per_tick) - . = TRUE - ..() + if(drinker.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/laughter name = "Laughter" @@ -143,9 +146,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/laughter/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.emote("laugh") affected_mob.add_mood_event("chemical_laughter", /datum/mood_event/chemical_laughter) - ..() /datum/reagent/consumable/superlaughter name = "Super Laughter" @@ -156,11 +159,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/superlaughter/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(16, seconds_per_tick)) affected_mob.visible_message(span_danger("[affected_mob] bursts out into a fit of uncontrollable laughter!"), span_userdanger("You burst out in a fit of uncontrollable laughter!")) affected_mob.Stun(5) affected_mob.add_mood_event("chemical_laughter", /datum/mood_event/chemical_superlaughter) - ..() /datum/reagent/consumable/potato_juice name = "Potato Juice" @@ -179,11 +182,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/pickle/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) if((liver && HAS_TRAIT(liver, TRAIT_CORONER_METABOLISM))) - affected_mob.adjustToxLoss(-1, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + if(affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/grapejuice name = "Grape Juice" @@ -219,11 +222,11 @@ /datum/reagent/consumable/milk/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) if(affected_mob.getBruteLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.heal_bodypart_damage(1,0) - . = TRUE + if(affected_mob.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 0, updating_health = FALSE)) + . = UPDATE_MOB_HEALTH if(holder.has_reagent(/datum/reagent/consumable/capsaicin)) - holder.remove_reagent(/datum/reagent/consumable/capsaicin, 1 * seconds_per_tick) - ..() + holder.remove_reagent(/datum/reagent/consumable/capsaicin, seconds_per_tick) + return ..() || . /datum/reagent/consumable/soymilk name = "Soy Milk" @@ -234,10 +237,10 @@ default_container = /obj/item/reagent_containers/condiment/soymilk /datum/reagent/consumable/soymilk/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.getBruteLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.heal_bodypart_damage(1, 0) - . = TRUE - ..() + if(affected_mob.heal_bodypart_damage(1, 0)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/cream name = "Cream" @@ -248,10 +251,9 @@ default_container = /obj/item/reagent_containers/cup/glass/bottle/juice/cream /datum/reagent/consumable/cream/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(affected_mob.getBruteLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.heal_bodypart_damage(1, 0) - . = TRUE - ..() + . = ..() + if(SPT_PROB(10, seconds_per_tick) && affected_mob.heal_bodypart_damage(1, 0)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/coffee name = "Coffee" @@ -265,10 +267,11 @@ /datum/reagent/consumable/coffee/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick) - ..() /datum/reagent/consumable/coffee/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_dizzy(-10 SECONDS * REM * seconds_per_tick) affected_mob.adjust_drowsiness(-6 SECONDS * REM * seconds_per_tick) affected_mob.AdjustSleeping(-40 * REM * seconds_per_tick) @@ -276,8 +279,6 @@ affected_mob.adjust_bodytemperature(25 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, affected_mob.get_body_temp_normal()) if(holder.has_reagent(/datum/reagent/consumable/frostoil)) holder.remove_reagent(/datum/reagent/consumable/frostoil, 5 * REM * seconds_per_tick) - ..() - . = TRUE /datum/reagent/consumable/tea name = "Tea" @@ -290,12 +291,16 @@ default_container = /obj/item/reagent_containers/cup/glass/mug/tea /datum/reagent/consumable/tea/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_dizzy(-4 SECONDS * REM * seconds_per_tick) affected_mob.adjust_drowsiness(-2 SECONDS * REM * seconds_per_tick) affected_mob.adjust_jitter(-6 SECONDS * REM * seconds_per_tick) affected_mob.AdjustSleeping(-20 * REM * seconds_per_tick) if(affected_mob.getToxLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.adjustToxLoss(-1, FALSE, required_biotype = affected_biotype) + if(affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH + affected_mob.adjust_bodytemperature(20 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, affected_mob.get_body_temp_normal()) + var/to_chatted = FALSE for(var/datum/wound/iter_wound as anything in affected_mob.all_wounds) if(SPT_PROB(10, seconds_per_tick)) @@ -303,9 +308,6 @@ if(!to_chatted && helped) to_chat(affected_mob, span_notice("A calm, relaxed feeling suffuses you. Your wounds feel a little healthier.")) to_chatted = TRUE - affected_mob.adjust_bodytemperature(20 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, affected_mob.get_body_temp_normal()) - ..() - . = TRUE // Different handling, different name. // Returns FALSE by default so broken bones and 'loss' wounds don't give a false message @@ -348,10 +350,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/tea/arnold_palmer/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(2.5, seconds_per_tick)) to_chat(affected_mob, span_notice("[pick("You remember to square your shoulders.","You remember to keep your head down.","You can't decide between squaring your shoulders and keeping your head down.","You remember to relax.","You think about how someday you'll get two strokes off your golf game.")]")) - ..() - . = TRUE /datum/reagent/consumable/icecoffee name = "Iced Coffee" @@ -362,13 +363,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/icecoffee/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_dizzy(-10 SECONDS * REM * seconds_per_tick) affected_mob.adjust_drowsiness(-6 SECONDS * REM * seconds_per_tick) affected_mob.AdjustSleeping(-40 * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick) - ..() - . = TRUE /datum/reagent/consumable/hot_ice_coffee name = "Hot Ice Coffee" @@ -379,14 +379,14 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/hot_ice_coffee/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_dizzy(-10 SECONDS * REM * seconds_per_tick) affected_mob.adjust_drowsiness(-6 SECONDS * REM * seconds_per_tick) affected_mob.AdjustSleeping(-60 * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(-7 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick) - affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - . = TRUE + if(affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/icetea name = "Iced Tea" @@ -397,14 +397,14 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/icetea/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_dizzy(-4 SECONDS * REM * seconds_per_tick) affected_mob.adjust_drowsiness(-2 SECONDS * REM * seconds_per_tick) affected_mob.AdjustSleeping(-40 * REM * seconds_per_tick) if(affected_mob.getToxLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.adjustToxLoss(-1, FALSE, required_biotype = affected_biotype) + if(affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() - . = TRUE /datum/reagent/consumable/space_cola name = "Cola" @@ -414,9 +414,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/space_cola/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_drowsiness(-10 SECONDS * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() /datum/reagent/consumable/roy_rogers name = "Roy Rogers" @@ -441,22 +441,23 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/nuka_cola/on_mob_metabolize(mob/living/affected_mob) - ..() + . = ..() affected_mob.add_movespeed_modifier(/datum/movespeed_modifier/reagent/nuka_cola) /datum/reagent/consumable/nuka_cola/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/nuka_cola) - ..() /datum/reagent/consumable/nuka_cola/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.set_jitter_if_lower(40 SECONDS * REM * seconds_per_tick) affected_mob.set_drugginess(1 MINUTES * REM * seconds_per_tick) affected_mob.adjust_dizzy(3 SECONDS * REM * seconds_per_tick) affected_mob.remove_status_effect(/datum/status_effect/drowsiness) - affected_mob.AdjustSleeping(-40 * REM * seconds_per_tick) + affected_mob.AdjustSleeping(-4 SECONDS * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() - . = TRUE + if (SSradiation.can_irradiate_basic(affected_mob)) + affected_mob.AddComponent(/datum/component/irradiated) /datum/reagent/consumable/rootbeer name = "root beer" @@ -471,15 +472,16 @@ var/effect_enabled = FALSE /datum/reagent/consumable/rootbeer/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() REMOVE_TRAIT(affected_mob, TRAIT_DOUBLE_TAP, type) if(current_cycle > 10) to_chat(affected_mob, span_warning("You feel kinda tired as your sugar rush wears off...")) affected_mob.adjustStaminaLoss(min(80, current_cycle * 3), required_biotype = affected_biotype) - affected_mob.adjust_drowsiness(current_cycle * 2 SECONDS) - ..() + affected_mob.adjust_drowsiness((current_cycle-1) * 2 SECONDS) /datum/reagent/consumable/rootbeer/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(current_cycle >= 3 && !effect_enabled) // takes a few seconds for the bonus to kick in to prevent microdosing + . = ..() + if(current_cycle > 3 && !effect_enabled) // takes a few seconds for the bonus to kick in to prevent microdosing to_chat(affected_mob, span_notice("You feel your trigger finger getting itchy...")) ADD_TRAIT(affected_mob, TRAIT_DOUBLE_TAP, type) effect_enabled = TRUE @@ -490,9 +492,6 @@ if(current_cycle > 10) affected_mob.adjust_dizzy(3 SECONDS * REM * seconds_per_tick) - ..() - . = TRUE - /datum/reagent/consumable/grey_bull name = "Grey Bull" description = "Grey Bull, it gives you gloves!" @@ -500,26 +499,22 @@ quality = DRINK_VERYGOOD taste_description = "carbonated oil" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + metabolized_traits = list(TRAIT_SHOCKIMMUNE) /datum/reagent/consumable/grey_bull/on_mob_metabolize(mob/living/carbon/affected_atom) - ..() - ADD_TRAIT(affected_atom, TRAIT_SHOCKIMMUNE, type) + . = ..() var/obj/item/organ/internal/liver/liver = affected_atom.get_organ_slot(ORGAN_SLOT_LIVER) if(HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM)) affected_atom.add_mood_event("maintenance_fun", /datum/mood_event/maintenance_high) metabolization_rate *= 0.8 -/datum/reagent/consumable/grey_bull/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_SHOCKIMMUNE, type) - ..() - /datum/reagent/consumable/grey_bull/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.set_jitter_if_lower(40 SECONDS * REM * seconds_per_tick) affected_mob.adjust_dizzy(2 SECONDS * REM * seconds_per_tick) affected_mob.remove_status_effect(/datum/status_effect/drowsiness) affected_mob.AdjustSleeping(-40 * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() /datum/reagent/consumable/spacemountainwind name = "SM Wind" @@ -529,12 +524,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/spacemountainwind/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_drowsiness(-14 SECONDS * REM * seconds_per_tick) affected_mob.AdjustSleeping(-20 * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick) - ..() - . = TRUE /datum/reagent/consumable/dr_gibb name = "Dr. Gibb" @@ -544,9 +538,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/dr_gibb/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_drowsiness(-12 SECONDS * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() /datum/reagent/consumable/space_up name = "Space-Up" @@ -556,8 +550,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/space_up/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_bodytemperature(-8 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() /datum/reagent/consumable/lemon_lime name = "Lemon Lime" @@ -567,8 +561,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/lemon_lime/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_bodytemperature(-8 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() /datum/reagent/consumable/pwr_game name = "Pwr Game" @@ -585,10 +579,10 @@ You feel as though a great secret of the universe has been made known to you...") /datum/reagent/consumable/pwr_game/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_bodytemperature(-8 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) if(SPT_PROB(5, seconds_per_tick)) affected_mob.mind?.adjust_experience(/datum/skill/gaming, 5) - ..() /datum/reagent/consumable/shamblers name = "Shambler's Juice" @@ -598,8 +592,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/shamblers/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_bodytemperature(-8 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() /datum/reagent/consumable/sodawater name = "Soda Water" @@ -615,10 +609,10 @@ mytray.adjust_plant_health(round(volume * 0.1)) /datum/reagent/consumable/sodawater/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_dizzy(-10 SECONDS * REM * seconds_per_tick) affected_mob.adjust_drowsiness(-6 SECONDS * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() /datum/reagent/consumable/tonic name = "Tonic Water" @@ -628,12 +622,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/tonic/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_dizzy(-10 SECONDS * REM * seconds_per_tick) affected_mob.adjust_drowsiness(-6 SECONDS * REM * seconds_per_tick) affected_mob.AdjustSleeping(-40 * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() - . = TRUE /datum/reagent/consumable/wellcheers name = "Wellcheers" @@ -643,15 +636,18 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/wellcheers/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_drowsiness(3 SECONDS * REM * seconds_per_tick) + var/need_mob_update switch(affected_mob.mob_mood.sanity_level) if (SANITY_INSANE to SANITY_CRAZY) - affected_mob.adjustStaminaLoss(3 * REM * seconds_per_tick, 0) + need_mob_update = affected_mob.adjustStaminaLoss(3 * REM * seconds_per_tick, updating_stamina = FALSE) if (SANITY_UNSTABLE to SANITY_DISTURBED) affected_mob.add_mood_event("wellcheers", /datum/mood_event/wellcheers) if (SANITY_NEUTRAL to SANITY_GREAT) - affected_mob.adjustBruteLoss(-1.5 * REM * seconds_per_tick, 0) - return ..() + need_mob_update = affected_mob.adjustBruteLoss(-1.5 * REM * seconds_per_tick, updating_health = FALSE) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/monkey_energy name = "Monkey Energy" @@ -662,26 +658,26 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/monkey_energy/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.set_jitter_if_lower(80 SECONDS * REM * seconds_per_tick) affected_mob.adjust_dizzy(2 SECONDS * REM * seconds_per_tick) affected_mob.remove_status_effect(/datum/status_effect/drowsiness) affected_mob.AdjustSleeping(-40 * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() /datum/reagent/consumable/monkey_energy/on_mob_metabolize(mob/living/affected_mob) - ..() - if(ismonkey(affected_mob)) + . = ..() + if(is_simian(affected_mob)) affected_mob.add_movespeed_modifier(/datum/movespeed_modifier/reagent/monkey_energy) /datum/reagent/consumable/monkey_energy/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/monkey_energy) - ..() /datum/reagent/consumable/monkey_energy/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(7.5, seconds_per_tick)) affected_mob.say(pick_list_replacements(BOOMER_FILE, "boomer"), forced = /datum/reagent/consumable/monkey_energy) - ..() /datum/reagent/consumable/ice name = "Ice" @@ -693,8 +689,9 @@ default_container = /obj/item/reagent_containers/cup/glass/ice /datum/reagent/consumable/ice/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() + . = ..() + if(affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, FALSE, affected_mob.get_body_temp_normal())) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/soy_latte name = "Soy Latte" @@ -706,15 +703,17 @@ glass_price = DRINK_PRICE_EASY /datum/reagent/consumable/soy_latte/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_dizzy(-10 SECONDS * REM * seconds_per_tick) affected_mob.adjust_drowsiness(-6 SECONDS * REM * seconds_per_tick) - affected_mob.SetSleeping(0) + var/need_mob_update + need_mob_update = affected_mob.SetSleeping(0) affected_mob.adjust_bodytemperature(5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, affected_mob.get_body_temp_normal()) affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick) if(affected_mob.getBruteLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.heal_bodypart_damage(1,0) - ..() - . = TRUE + need_mob_update += affected_mob.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 0, updating_health = FALSE) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/cafe_latte name = "Cafe Latte" @@ -726,15 +725,17 @@ glass_price = DRINK_PRICE_EASY /datum/reagent/consumable/cafe_latte/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_dizzy(-10 SECONDS * REM * seconds_per_tick) affected_mob.adjust_drowsiness(-12 SECONDS * REM * seconds_per_tick) - affected_mob.SetSleeping(0) + var/need_mob_update + need_mob_update = affected_mob.SetSleeping(0) affected_mob.adjust_bodytemperature(5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, affected_mob.get_body_temp_normal()) affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick) if(affected_mob.getBruteLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.heal_bodypart_damage(1, 0) - ..() - . = TRUE + need_mob_update += affected_mob.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 0, updating_health = FALSE) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/doctor_delight name = "The Doctor's Delight" @@ -745,17 +746,19 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/doctor_delight/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustBruteLoss(-0.5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-0.5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustToxLoss(-0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustOxyLoss(-0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustToxLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustOxyLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) if(affected_mob.nutrition && (affected_mob.nutrition - 2 > 0)) var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) if(!(HAS_TRAIT(liver, TRAIT_MEDICAL_METABOLISM))) // Drains the nutrition of the holder. Not medical doctors though, since it's the Doctor's Delight! affected_mob.adjust_nutrition(-2 * REM * seconds_per_tick) - ..() - . = TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/cinderella name = "Cinderella" @@ -766,8 +769,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/cinderella/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_disgust(-5 * REM * seconds_per_tick) - return ..() /datum/reagent/consumable/cherryshake name = "Cherry Shake" @@ -886,8 +889,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/grape_soda/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() /datum/reagent/consumable/milk/chocolate_milk name = "Chocolate Milk" @@ -908,11 +911,11 @@ /datum/reagent/consumable/hot_coco/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) affected_mob.adjust_bodytemperature(5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, affected_mob.get_body_temp_normal()) if(affected_mob.getBruteLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.heal_bodypart_damage(1, 0) - . = TRUE + if(affected_mob.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 0, updating_health = FALSE)) + . = UPDATE_MOB_HEALTH if(holder.has_reagent(/datum/reagent/consumable/capsaicin)) holder.remove_reagent(/datum/reagent/consumable/capsaicin, 2 * REM * seconds_per_tick) - ..() + return ..() || . /datum/reagent/consumable/italian_coco name = "Italian Hot Chocolate" @@ -924,8 +927,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/italian_coco/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_bodytemperature(5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, affected_mob.get_body_temp_normal()) - return ..() /datum/reagent/consumable/menthol name = "Menthol" @@ -936,8 +939,8 @@ default_container = /obj/item/reagent_containers/cup/glass/bottle/juice/menthol /datum/reagent/consumable/menthol/on_mob_life(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.apply_status_effect(/datum/status_effect/throat_soothed) - ..() /datum/reagent/consumable/grenadine name = "Grenadine" @@ -977,8 +980,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/cream_soda/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - ..() /datum/reagent/consumable/sol_dry name = "Sol Dry" @@ -989,8 +992,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/sol_dry/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_disgust(-5 * REM * seconds_per_tick) - ..() /datum/reagent/consumable/shirley_temple name = "Shirley Temple" @@ -1014,8 +1017,9 @@ var/current_size = RESIZE_DEFAULT_SIZE /datum/reagent/consumable/red_queen/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(50, seconds_per_tick)) - return ..() + return var/newsize = pick(0.5, 0.75, 1, 1.50, 2) newsize *= RESIZE_DEFAULT_SIZE @@ -1023,12 +1027,11 @@ current_size = newsize if(SPT_PROB(23, seconds_per_tick)) affected_mob.emote("sneeze") - ..() /datum/reagent/consumable/red_queen/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.update_transform(RESIZE_DEFAULT_SIZE/current_size) current_size = RESIZE_DEFAULT_SIZE - ..() /datum/reagent/consumable/bungojuice name = "Bungo Juice" @@ -1052,10 +1055,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/aloejuice/on_mob_life(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.getToxLoss() && SPT_PROB(16, seconds_per_tick)) - affected_mob.adjustToxLoss(-1, FALSE, required_biotype = affected_biotype) - ..() - . = TRUE + if(affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/agua_fresca name = "Agua Fresca" @@ -1066,10 +1069,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/agua_fresca/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_bodytemperature(-8 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) if(affected_mob.getToxLoss() && SPT_PROB(10, seconds_per_tick)) - affected_mob.adjustToxLoss(-0.5, FALSE, required_biotype = affected_biotype) - return ..() + if(affected_mob.adjustToxLoss(-0.5, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/mushroom_tea name = "Mushroom Tea" @@ -1080,10 +1084,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/mushroom_tea/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(islizard(affected_mob)) - affected_mob.adjustOxyLoss(-0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - ..() - . = TRUE + if(affected_mob.adjustOxyLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type)) + return UPDATE_MOB_HEALTH //Moth Stuff /datum/reagent/consumable/toechtauese_juice @@ -1164,12 +1168,14 @@ quality = DRINK_GOOD taste_description = "citrus soda with cucumber" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + glass_price = DRINK_PRICE_HIGH /datum/reagent/consumable/cucumberlemonade/on_mob_life(mob/living/carbon/doll, seconds_per_tick, times_fired) + . = ..() doll.adjust_bodytemperature(-8 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, doll.get_body_temp_normal()) if(doll.getToxLoss() && SPT_PROB(10, seconds_per_tick)) - doll.adjustToxLoss(-0.5, FALSE, required_biotype = affected_biotype) - return ..() + if(doll.adjustToxLoss(-0.5, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/mississippi_queen name = "Mississippi Queen" @@ -1179,17 +1185,16 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/mississippi_queen/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() switch(current_cycle) - if(10 to 20) + if(11 to 21) drinker.adjust_dizzy(4 SECONDS * REM * seconds_per_tick) - if(20 to 30) + if(21 to 31) if(SPT_PROB(15, seconds_per_tick)) drinker.adjust_confusion(4 SECONDS * REM * seconds_per_tick) - if(30 to 200) + if(31 to 201) drinker.adjust_hallucinations(60 SECONDS * REM * seconds_per_tick) - return ..() - /datum/reagent/consumable/t_letter name = "T" description = "You expected to find this in a soup, but this is fine too." @@ -1198,14 +1203,15 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/t_letter/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(!HAS_MIND_TRAIT(affected_mob, TRAIT_MIMING)) - return ..() + return affected_mob.set_silence_if_lower(MIMEDRINK_SILENCE_DURATION) affected_mob.adjust_drowsiness(-6 SECONDS * REM * seconds_per_tick) affected_mob.AdjustSleeping(-40 * REM * seconds_per_tick) if(affected_mob.getToxLoss() && SPT_PROB(25, seconds_per_tick)) - affected_mob.adjustToxLoss(-2, FALSE, required_biotype = affected_biotype) - return ..() + if(affected_mob.adjustToxLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/hakka_mate name = "Hakka-Mate" diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index d10f9dda2bdec..6363a9766a35a 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -5,6 +5,7 @@ var/trippy = TRUE //Does this drug make you trip? /datum/reagent/drug/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() if(trippy) affected_mob.clear_mood_event("[type]_high") @@ -18,22 +19,23 @@ addiction_types = list(/datum/addiction/hallucinogens = 10) //4 per 2 seconds /datum/reagent/drug/space_drugs/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.set_drugginess(30 SECONDS * REM * seconds_per_tick) if(isturf(affected_mob.loc) && !isspaceturf(affected_mob.loc) && !HAS_TRAIT(affected_mob, TRAIT_IMMOBILIZED) && SPT_PROB(5, seconds_per_tick)) step(affected_mob, pick(GLOB.cardinals)) if(SPT_PROB(3.5, seconds_per_tick)) affected_mob.emote(pick("twitch","drool","moan","giggle")) - ..() /datum/reagent/drug/space_drugs/overdose_start(mob/living/affected_mob) + . = ..() to_chat(affected_mob, span_userdanger("You start tripping hard!")) affected_mob.add_mood_event("[type]_overdose", /datum/mood_event/overdose, name) /datum/reagent/drug/space_drugs/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() var/hallucination_duration_in_seconds = (affected_mob.get_timed_status_effect_duration(/datum/status_effect/hallucination) / 10) if(hallucination_duration_in_seconds < volume && SPT_PROB(10, seconds_per_tick)) affected_mob.adjust_hallucinations(10 SECONDS) - ..() /datum/reagent/drug/cannabis name = "Cannabis" @@ -45,6 +47,7 @@ metabolization_rate = 0.125 * REAGENTS_METABOLISM /datum/reagent/drug/cannabis/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.apply_status_effect(/datum/status_effect/stoned) if(SPT_PROB(1, seconds_per_tick)) var/smoke_message = pick("You feel relaxed.","You feel calmed.","Your mouth feels dry.","You could use some water.","Your heart beats quickly.","You feel clumsy.","You crave junk food.","You notice you've been moving more slowly.") @@ -58,7 +61,6 @@ if(SPT_PROB(4, seconds_per_tick) && affected_mob.buckled && affected_mob.body_position != LYING_DOWN && !affected_mob.IsParalyzed()) //chance to be couchlocked if sitting to_chat(affected_mob, "It's too comfy to move...") affected_mob.Paralyze(10 SECONDS) - return ..() /datum/reagent/drug/nicotine name = "Nicotine" @@ -79,24 +81,26 @@ mytray.adjust_pestlevel(-rand(1, 2)) /datum/reagent/drug/nicotine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(0.5, seconds_per_tick)) var/smoke_message = pick("You feel relaxed.", "You feel calmed.","You feel alert.","You feel rugged.") to_chat(affected_mob, span_notice("[smoke_message]")) - affected_mob.add_mood_event("smoked", /datum/mood_event/smoked, name) + affected_mob.add_mood_event("smoked", /datum/mood_event/smoked) affected_mob.remove_status_effect(/datum/status_effect/jitter) affected_mob.AdjustStun(-50 * REM * seconds_per_tick) affected_mob.AdjustKnockdown(-50 * REM * seconds_per_tick) affected_mob.AdjustUnconscious(-50 * REM * seconds_per_tick) affected_mob.AdjustParalyzed(-50 * REM * seconds_per_tick) affected_mob.AdjustImmobilized(-50 * REM * seconds_per_tick) - ..() - . = TRUE + return UPDATE_MOB_HEALTH /datum/reagent/drug/nicotine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustToxLoss(0.1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustOxyLoss(1.1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - ..() - . = TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustToxLoss(0.1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustOxyLoss(1.1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/drug/krokodil name = "Krokodil" @@ -110,28 +114,28 @@ /datum/reagent/drug/krokodil/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/high_message = pick("You feel calm.", "You feel collected.", "You feel like you need to relax.") if(SPT_PROB(2.5, seconds_per_tick)) to_chat(affected_mob, span_notice("[high_message]")) - affected_mob.add_mood_event("smacked out", /datum/mood_event/narcotic_heavy, name) - if(current_cycle == 35 && creation_purity <= 0.6) + affected_mob.add_mood_event("smacked out", /datum/mood_event/narcotic_heavy) + if(current_cycle == 36 && creation_purity <= 0.6) if(!istype(affected_mob.dna.species, /datum/species/human/krokodil_addict)) to_chat(affected_mob, span_userdanger("Your skin falls off easily!")) var/mob/living/carbon/human/affected_human = affected_mob affected_human.set_facial_hairstyle("Shaved", update = FALSE) affected_human.set_hairstyle("Bald", update = FALSE) affected_mob.set_species(/datum/species/human/krokodil_addict) - affected_mob.adjustBruteLoss(50 * REM, FALSE, required_bodytype = affected_bodytype) // holy shit your skin just FELL THE FUCK OFF - . = TRUE - ..() + if(affected_mob.adjustBruteLoss(50 * REM, updating_health = FALSE, required_bodytype = affected_bodytype)) // holy shit your skin just FELL THE FUCK OFF + return UPDATE_MOB_HEALTH /datum/reagent/drug/krokodil/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.25 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - affected_mob.adjustToxLoss(0.25 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - . = TRUE - - + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.25 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + need_mob_update = affected_mob.adjustToxLoss(0.25 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/drug/methamphetamine name = "Methamphetamine" @@ -159,33 +163,36 @@ var/effective_impurity = min(1, (1 - creation_purity)/0.5) color = BlendRGB(initial(color), "#FAFAFA", effective_impurity) -/datum/reagent/drug/methamphetamine/on_mob_metabolize(mob/living/L) - ..() - L.add_movespeed_modifier(/datum/movespeed_modifier/reagent/methamphetamine) +/datum/reagent/drug/methamphetamine/on_mob_metabolize(mob/living/affected_mob) + . = ..() + affected_mob.add_movespeed_modifier(/datum/movespeed_modifier/reagent/methamphetamine) -/datum/reagent/drug/methamphetamine/on_mob_end_metabolize(mob/living/L) - L.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/methamphetamine) - ..() +/datum/reagent/drug/methamphetamine/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() + affected_mob.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/methamphetamine) /datum/reagent/drug/methamphetamine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/high_message = pick("You feel hyper.", "You feel like you need to go faster.", "You feel like you can run the world.") if(SPT_PROB(2.5, seconds_per_tick)) to_chat(affected_mob, span_notice("[high_message]")) - affected_mob.add_mood_event("tweaking", /datum/mood_event/stimulant_medium, name) + affected_mob.add_mood_event("tweaking", /datum/mood_event/stimulant_medium) affected_mob.AdjustStun(-40 * REM * seconds_per_tick) affected_mob.AdjustKnockdown(-40 * REM * seconds_per_tick) affected_mob.AdjustUnconscious(-40 * REM * seconds_per_tick) affected_mob.AdjustParalyzed(-40 * REM * seconds_per_tick) affected_mob.AdjustImmobilized(-40 * REM * seconds_per_tick) - affected_mob.adjustStaminaLoss(-2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + var/need_mob_update + need_mob_update = affected_mob.adjustStaminaLoss(-2 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) affected_mob.set_jitter_if_lower(4 SECONDS * REM * seconds_per_tick) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, rand(1, 4) * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, rand(1, 4) * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(need_mob_update) + . = UPDATE_MOB_HEALTH if(SPT_PROB(2.5, seconds_per_tick)) affected_mob.emote(pick("twitch", "shiver")) - ..() - . = TRUE /datum/reagent/drug/methamphetamine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(!HAS_TRAIT(affected_mob, TRAIT_IMMOBILIZED) && !ismovable(affected_mob.loc)) for(var/i in 1 to round(4 * REM * seconds_per_tick, 1)) step(affected_mob, pick(GLOB.cardinals)) @@ -194,10 +201,11 @@ if(SPT_PROB(18, seconds_per_tick)) affected_mob.visible_message(span_danger("[affected_mob]'s hands flip out and flail everywhere!")) affected_mob.drop_all_held_items() - ..() - affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, (rand(5, 10) / 10) * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - . = TRUE + var/need_mob_update + need_mob_update = affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, (rand(5, 10) / 10) * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/drug/bath_salts name = "Bath Salts" @@ -207,39 +215,41 @@ overdose_threshold = 20 taste_description = "salt" // because they're bathsalts? addiction_types = list(/datum/addiction/stimulants = 25) //8 per 2 seconds - var/datum/brain_trauma/special/psychotic_brawling/bath_salts/rage ph = 8.2 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + metabolized_traits = list(TRAIT_STUNIMMUNE, TRAIT_SLEEPIMMUNE, TRAIT_ANALGESIA) + var/datum/brain_trauma/special/psychotic_brawling/bath_salts/rage -/datum/reagent/drug/bath_salts/on_mob_metabolize(mob/living/L) - ..() - L.add_traits(list(TRAIT_STUNIMMUNE, TRAIT_SLEEPIMMUNE), type) - if(iscarbon(L)) - var/mob/living/carbon/C = L +/datum/reagent/drug/bath_salts/on_mob_metabolize(mob/living/affected_mob) + . = ..() + if(iscarbon(affected_mob)) + var/mob/living/carbon/carbon_mob = affected_mob rage = new() - C.gain_trauma(rage, TRAUMA_RESILIENCE_ABSOLUTE) + carbon_mob.gain_trauma(rage, TRAUMA_RESILIENCE_ABSOLUTE) -/datum/reagent/drug/bath_salts/on_mob_end_metabolize(mob/living/L) - L.remove_traits(list(TRAIT_STUNIMMUNE, TRAIT_SLEEPIMMUNE), type) +/datum/reagent/drug/bath_salts/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() if(rage) QDEL_NULL(rage) - ..() /datum/reagent/drug/bath_salts/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/high_message = pick("You feel amped up.", "You feel ready.", "You feel like you can push it to the limit.") if(SPT_PROB(2.5, seconds_per_tick)) to_chat(affected_mob, span_notice("[high_message]")) - affected_mob.add_mood_event("salted", /datum/mood_event/stimulant_heavy, name) - affected_mob.adjustStaminaLoss(-5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 4 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + affected_mob.add_mood_event("salted", /datum/mood_event/stimulant_heavy) + var/need_mob_update + need_mob_update = affected_mob.adjustStaminaLoss(-5 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 4 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) affected_mob.adjust_hallucinations(10 SECONDS * REM * seconds_per_tick) + if(need_mob_update) + . = UPDATE_MOB_HEALTH if(!HAS_TRAIT(affected_mob, TRAIT_IMMOBILIZED) && !ismovable(affected_mob.loc)) step(affected_mob, pick(GLOB.cardinals)) step(affected_mob, pick(GLOB.cardinals)) - ..() - . = TRUE /datum/reagent/drug/bath_salts/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_hallucinations(10 SECONDS * REM * seconds_per_tick) if(!HAS_TRAIT(affected_mob, TRAIT_IMMOBILIZED) && !ismovable(affected_mob.loc)) for(var/i in 1 to round(8 * REM * seconds_per_tick, 1)) @@ -248,7 +258,6 @@ affected_mob.emote(pick("twitch","drool","moan")) if(SPT_PROB(28, seconds_per_tick)) affected_mob.drop_all_held_items() - ..() /datum/reagent/drug/aranesp name = "Aranesp" @@ -259,16 +268,18 @@ addiction_types = list(/datum/addiction/stimulants = 8) /datum/reagent/drug/aranesp/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/high_message = pick("You feel amped up.", "You feel ready.", "You feel like you can push it to the limit.") if(SPT_PROB(2.5, seconds_per_tick)) to_chat(affected_mob, span_notice("[high_message]")) - affected_mob.adjustStaminaLoss(-18 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustToxLoss(0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + var/need_mob_update + need_mob_update = affected_mob.adjustStaminaLoss(-18 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustToxLoss(0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) if(SPT_PROB(30, seconds_per_tick)) affected_mob.losebreath++ - affected_mob.adjustOxyLoss(1, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - ..() - . = TRUE + need_mob_update += affected_mob.adjustOxyLoss(1, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/drug/happiness name = "Happiness" @@ -279,26 +290,26 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED taste_description = "paint thinner" addiction_types = list(/datum/addiction/hallucinogens = 18) + metabolized_traits = list(TRAIT_FEARLESS, TRAIT_ANALGESIA) -/datum/reagent/drug/happiness/on_mob_metabolize(mob/living/L) - ..() - ADD_TRAIT(L, TRAIT_FEARLESS, type) - L.add_mood_event("happiness_drug", /datum/mood_event/happiness_drug) +/datum/reagent/drug/happiness/on_mob_metabolize(mob/living/affected_mob) + . = ..() + affected_mob.add_mood_event("happiness_drug", /datum/mood_event/happiness_drug) -/datum/reagent/drug/happiness/on_mob_delete(mob/living/L) - REMOVE_TRAIT(L, TRAIT_FEARLESS, type) - L.clear_mood_event("happiness_drug") - ..() +/datum/reagent/drug/happiness/on_mob_delete(mob/living/affected_mob) + . = ..() + affected_mob.clear_mood_event("happiness_drug") /datum/reagent/drug/happiness/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.remove_status_effect(/datum/status_effect/jitter) affected_mob.remove_status_effect(/datum/status_effect/confusion) affected_mob.disgust = 0 - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - ..() - . = TRUE + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH /datum/reagent/drug/happiness/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(16, seconds_per_tick)) var/reaction = rand(1,3) switch(reaction) @@ -311,9 +322,8 @@ if(3) affected_mob.emote("frown") affected_mob.add_mood_event("happiness_drug", /datum/mood_event/happiness_drug_bad_od) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - ..() - . = TRUE + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH /datum/reagent/drug/pumpup name = "Pump-Up" @@ -324,55 +334,60 @@ overdose_threshold = 30 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED addiction_types = list(/datum/addiction/stimulants = 6) //2.6 per 2 seconds + metabolized_traits = list(TRAIT_BATON_RESISTANCE, TRAIT_ANALGESIA) /datum/reagent/drug/pumpup/on_mob_metabolize(mob/living/carbon/affected_mob) . = ..() - ADD_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type) var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) if(liver && HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM)) affected_mob.add_mood_event("maintenance_fun", /datum/mood_event/maintenance_high) metabolization_rate *= 0.8 -/datum/reagent/drug/pumpup/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type) - return ..() - /datum/reagent/drug/pumpup/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick) if(SPT_PROB(2.5, seconds_per_tick)) to_chat(affected_mob, span_notice("[pick("Go! Go! GO!", "You feel ready...", "You feel invincible...")]")) if(SPT_PROB(7.5, seconds_per_tick)) affected_mob.losebreath++ - affected_mob.adjustToxLoss(2, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() - + affected_mob.adjustToxLoss(2, updating_health = FALSE, required_biotype = affected_biotype) + return UPDATE_MOB_HEALTH /datum/reagent/drug/pumpup/overdose_start(mob/living/affected_mob) + . = ..() to_chat(affected_mob, span_userdanger("You can't stop shaking, your heart beats faster and faster...")) /datum/reagent/drug/pumpup/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick) + var/need_mob_update if(SPT_PROB(2.5, seconds_per_tick)) affected_mob.drop_all_held_items() if(SPT_PROB(7.5, seconds_per_tick)) affected_mob.emote(pick("twitch","drool")) if(SPT_PROB(10, seconds_per_tick)) affected_mob.losebreath++ - affected_mob.adjustStaminaLoss(4, FALSE, required_biotype = affected_biotype) + affected_mob.adjustStaminaLoss(4, updating_stamina = FALSE, required_biotype = affected_biotype) + need_mob_update = TRUE if(SPT_PROB(7.5, seconds_per_tick)) - affected_mob.adjustToxLoss(2, FALSE, required_biotype = affected_biotype) - ..() + need_mob_update += affected_mob.adjustToxLoss(2, updating_health = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/drug/maint name = "Maintenance Drugs" chemical_flags = NONE -/datum/reagent/drug/maint/on_mob_metabolize(mob/living/carbon/L) - var/obj/item/organ/internal/liver/liver = L.get_organ_slot(ORGAN_SLOT_LIVER) +/datum/reagent/drug/maint/on_mob_metabolize(mob/living/affected_mob) + . = ..() + if(!iscarbon(affected_mob)) + return + + var/mob/living/carbon/carbon_mob = affected_mob + var/obj/item/organ/internal/liver/liver = carbon_mob.get_organ_slot(ORGAN_SLOT_LIVER) if(HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM)) - L.add_mood_event("maintenance_fun", /datum/mood_event/maintenance_high) + carbon_mob.add_mood_event("maintenance_fun", /datum/mood_event/maintenance_high) metabolization_rate *= 0.8 /datum/reagent/drug/maint/powder @@ -400,7 +415,8 @@ /datum/reagent/drug/maint/powder/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) . = ..() - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 6 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 6 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH /datum/reagent/drug/maint/sludge name = "Maintenance Sludge" @@ -411,20 +427,12 @@ overdose_threshold = 25 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED addiction_types = list(/datum/addiction/maintenance_drugs = 8) - -/datum/reagent/drug/maint/sludge/on_mob_metabolize(mob/living/L) - - . = ..() - ADD_TRAIT(L,TRAIT_HARDLY_WOUNDED,type) + metabolized_traits = list(TRAIT_HARDLY_WOUNDED, TRAIT_ANALGESIA) /datum/reagent/drug/maint/sludge/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - affected_mob.adjustToxLoss(0.5 * REM * seconds_per_tick, required_biotype = affected_biotype) - return TRUE - -/datum/reagent/drug/maint/sludge/on_mob_end_metabolize(mob/living/affected_mob) - . = ..() - REMOVE_TRAIT(affected_mob, TRAIT_HARDLY_WOUNDED,type) + if(affected_mob.adjustToxLoss(0.5 * REM * seconds_per_tick, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/drug/maint/sludge/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) . = ..() @@ -432,10 +440,13 @@ return var/mob/living/carbon/carbie = affected_mob //You will be vomiting so the damage is really for a few ticks before you flush it out of your system - carbie.adjustToxLoss(1 * REM * seconds_per_tick, required_biotype = affected_biotype) + var/need_mob_update + need_mob_update = carbie.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) if(SPT_PROB(5, seconds_per_tick)) - carbie.adjustToxLoss(5, required_biotype = affected_biotype) + need_mob_update += carbie.adjustToxLoss(5, required_biotype = affected_biotype, updating_health = FALSE) carbie.vomit(VOMIT_CATEGORY_DEFAULT) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/drug/maint/tar name = "Maintenance Tar" @@ -454,13 +465,15 @@ affected_mob.AdjustParalyzed(-10 * REM * seconds_per_tick) affected_mob.AdjustImmobilized(-10 * REM * seconds_per_tick) affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 1.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - return TRUE + return UPDATE_MOB_HEALTH /datum/reagent/drug/maint/tar/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) . = ..() - - affected_mob.adjustToxLoss(5 * REM * seconds_per_tick, required_biotype = affected_biotype) - affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + var/need_update + need_update = affected_mob.adjustToxLoss(5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(need_update) + return UPDATE_MOB_HEALTH /datum/reagent/drug/mushroomhallucinogen name = "Mushroom Hallucinogen" @@ -474,26 +487,26 @@ addiction_types = list(/datum/addiction/hallucinogens = 12) /datum/reagent/drug/mushroomhallucinogen/on_mob_life(mob/living/carbon/psychonaut, seconds_per_tick, times_fired) + . = ..() psychonaut.set_slurring_if_lower(1 SECONDS * REM * seconds_per_tick) switch(current_cycle) - if(1 to 5) + if(2 to 6) if(SPT_PROB(5, seconds_per_tick)) psychonaut.emote(pick("twitch","giggle")) - if(5 to 10) + if(6 to 11) psychonaut.set_jitter_if_lower(20 SECONDS * REM * seconds_per_tick) if(SPT_PROB(10, seconds_per_tick)) psychonaut.emote(pick("twitch","giggle")) - if (10 to INFINITY) + if (11 to INFINITY) psychonaut.set_jitter_if_lower(40 SECONDS * REM * seconds_per_tick) if(SPT_PROB(16, seconds_per_tick)) psychonaut.emote(pick("twitch","giggle")) - ..() /datum/reagent/drug/mushroomhallucinogen/on_mob_metabolize(mob/living/psychonaut) . = ..() - psychonaut.add_mood_event("tripping", /datum/mood_event/high, name) + psychonaut.add_mood_event("tripping", /datum/mood_event/high) if(!psychonaut.hud_used) return @@ -554,7 +567,7 @@ /datum/reagent/drug/blastoff/on_mob_metabolize(mob/living/dancer) . = ..() - dancer.add_mood_event("vibing", /datum/mood_event/high, name) + dancer.add_mood_event("vibing", /datum/mood_event/high) RegisterSignal(dancer, COMSIG_MOB_EMOTED("flip"), PROC_REF(on_flip)) RegisterSignal(dancer, COMSIG_MOB_EMOTED("spin"), PROC_REF(on_spin)) @@ -600,17 +613,17 @@ /datum/reagent/drug/blastoff/on_mob_life(mob/living/carbon/dancer, seconds_per_tick, times_fired) . = ..() - - dancer.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(dancer.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + . = UPDATE_MOB_HEALTH dancer.AdjustKnockdown(-20) if(SPT_PROB(BLASTOFF_DANCE_MOVE_CHANCE_PER_UNIT * volume, seconds_per_tick)) dancer.emote("flip") - return TRUE /datum/reagent/drug/blastoff/overdose_process(mob/living/dancer, seconds_per_tick, times_fired) . = ..() - dancer.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(dancer.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + . = UPDATE_MOB_HEALTH if(SPT_PROB(BLASTOFF_DANCE_MOVE_CHANCE_PER_UNIT * volume, seconds_per_tick)) dancer.emote("spin") @@ -673,8 +686,8 @@ /datum/reagent/drug/saturnx/on_mob_life(mob/living/carbon/invisible_man, seconds_per_tick, times_fired) . = ..() - invisible_man.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - return TRUE + if(invisible_man.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH /datum/reagent/drug/saturnx/on_mob_metabolize(mob/living/invisible_man) . = ..() @@ -750,7 +763,8 @@ invisible_man.emote("giggle") if(SPT_PROB(5, seconds_per_tick)) invisible_man.emote("laugh") - invisible_man.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.4 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(invisible_man.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.4 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH /datum/reagent/drug/saturnx/stable name = "Stabilized Saturn-X" @@ -772,27 +786,29 @@ addiction_types = list(/datum/addiction/stimulants = 20) /datum/reagent/drug/kronkaine/on_mob_metabolize(mob/living/kronkaine_fiend) - ..() + . = ..() kronkaine_fiend.add_actionspeed_modifier(/datum/actionspeed_modifier/kronkaine) kronkaine_fiend.sound_environment_override = SOUND_ENVIRONMENT_HANGAR /datum/reagent/drug/kronkaine/on_mob_end_metabolize(mob/living/kronkaine_fiend) + . = ..() kronkaine_fiend.remove_actionspeed_modifier(/datum/actionspeed_modifier/kronkaine) kronkaine_fiend.sound_environment_override = NONE - . = ..() /datum/reagent/drug/kronkaine/on_transfer(atom/kronkaine_receptacle, methods, trans_volume) . = ..() if(!iscarbon(kronkaine_receptacle)) return var/mob/living/carbon/druggo = kronkaine_receptacle - druggo.adjustStaminaLoss(-4 * trans_volume, 0) + if(druggo.adjustStaminaLoss(-4 * trans_volume, updating_stamina = FALSE)) + return UPDATE_MOB_HEALTH //I wish i could give it some kind of bonus when smoked, but we don't have an INHALE method. /datum/reagent/drug/kronkaine/on_mob_life(mob/living/carbon/kronkaine_fiend, seconds_per_tick, times_fired) - . = ..() || TRUE - kronkaine_fiend.add_mood_event("tweaking", /datum/mood_event/stimulant_medium, name) - kronkaine_fiend.adjustOrganLoss(ORGAN_SLOT_HEART, 0.4 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + . = ..() + kronkaine_fiend.add_mood_event("tweaking", /datum/mood_event/stimulant_medium) + if(kronkaine_fiend.adjustOrganLoss(ORGAN_SLOT_HEART, 0.4 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + . = UPDATE_MOB_HEALTH kronkaine_fiend.set_jitter_if_lower(20 SECONDS * REM * seconds_per_tick) kronkaine_fiend.AdjustSleeping(-20 * REM * seconds_per_tick) kronkaine_fiend.adjust_drowsiness(-10 SECONDS * REM * seconds_per_tick) @@ -805,7 +821,8 @@ /datum/reagent/drug/kronkaine/overdose_process(mob/living/kronkaine_fiend, seconds_per_tick, times_fired) . = ..() - kronkaine_fiend.adjustOrganLoss(ORGAN_SLOT_HEART, 1 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + if(kronkaine_fiend.adjustOrganLoss(ORGAN_SLOT_HEART, 1 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + . = UPDATE_MOB_HEALTH kronkaine_fiend.set_jitter_if_lower(20 SECONDS * REM * seconds_per_tick) if(SPT_PROB(10, seconds_per_tick)) to_chat(kronkaine_fiend, span_danger(pick("You feel like your heart is going to explode!", "Your ears are ringing!", "You sweat like a pig!", "You clench your jaw and grind your teeth.", "You feel prickles of pain in your chest."))) @@ -819,9 +836,10 @@ chemical_flags = NONE /datum/reagent/drug/kronkaine/gore/overdose_start(mob/living/gored) + . = ..() gored.visible_message( span_danger("[gored] explodes in a shower of gore!"), span_userdanger("GORE! GORE! GORE! YOU'RE GORE! TOO MUCH GORE! YOU'RE GORE! GORE! IT'S OVER! GORE! GORE! YOU'RE GORE! TOO MUCH G-"), ) new /obj/structure/bouncy_castle(gored.loc, gored) - gored.gib(TRUE, TRUE, TRUE) //no brain, no organs, no bodyparts + gored.gib() diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 92754537a0361..a9fad04ff7f1f 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -19,15 +19,18 @@ /// affects mood, typically higher for mixed drinks with more complex recipes' var/quality = 0 -/datum/reagent/consumable/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) - current_cycle++ - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(!HAS_TRAIT(H, TRAIT_NOHUNGER)) - H.adjust_nutrition(get_nutriment_factor() * REM * seconds_per_tick) - if(length(reagent_removal_skip_list)) +/datum/reagent/consumable/New() + . = ..() + // All food reagents function at a fixed rate + chemical_flags |= REAGENT_UNAFFECTED_BY_METABOLISM + +/datum/reagent/consumable/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(!ishuman(affected_mob) || HAS_TRAIT(affected_mob, TRAIT_NOHUNGER)) return - holder.remove_reagent(type, metabolization_rate * seconds_per_tick) + + var/mob/living/carbon/human/affected_human = affected_mob + affected_human.adjust_nutrition(get_nutriment_factor(affected_mob) * REM * seconds_per_tick) /datum/reagent/consumable/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -52,8 +55,9 @@ if(isitem(the_real_food) && !is_reagent_container(the_real_food)) exposed_mob.add_mob_memory(/datum/memory/good_food, food = the_real_food) -/datum/reagent/consumable/proc/get_nutriment_factor() - return nutriment_factor * REAGENTS_METABOLISM * (purity * 2) +/// Gets just how much nutrition this reagent is worth for the passed mob +/datum/reagent/consumable/proc/get_nutriment_factor(mob/living/carbon/eater) + return nutriment_factor * REAGENTS_METABOLISM * purity * 2 /datum/reagent/consumable/nutriment name = "Nutriment" @@ -61,7 +65,7 @@ reagent_state = SOLID nutriment_factor = 15 color = "#664330" // rgb: 102, 67, 48 - chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_DEAD_PROCESS + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED var/brute_heal = 1 var/burn_heal = 0 @@ -69,11 +73,11 @@ /datum/reagent/consumable/nutriment/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) mytray.adjust_plant_health(round(volume * 0.2)) -/datum/reagent/consumable/nutriment/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) +/datum/reagent/consumable/nutriment/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(30, seconds_per_tick)) - M.heal_bodypart_damage(brute = brute_heal, burn = burn_heal, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC) - . = TRUE - ..() + if(affected_mob.heal_bodypart_damage(brute = brute_heal * REM * seconds_per_tick, burn = burn_heal * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/nutriment/on_new(list/supplied_data) . = ..() @@ -124,10 +128,10 @@ brute_heal = 1 burn_heal = 1 -/datum/reagent/consumable/nutriment/vitamin/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) - if(M.satiety < MAX_SATIETY) - M.satiety += 30 * REM * seconds_per_tick +/datum/reagent/consumable/nutriment/vitamin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() + if(affected_mob.satiety < MAX_SATIETY) + affected_mob.satiety += 30 * REM * seconds_per_tick /// The basic resource of vat growing. /datum/reagent/consumable/nutriment/protein @@ -218,6 +222,13 @@ nutriment_factor = 10 default_container = /obj/item/reagent_containers/condiment/olive_oil +/datum/reagent/consumable/nutriment/fat/oil/corn + name = "Corn Oil" + description = "An oil derived from various types of corn." + color = "#302000" // rgb: 48, 32, 0 + taste_description = "slime" + nutriment_factor = 5 //it's a very cheap oil + /datum/reagent/consumable/nutriment/organ_tissue name = "Organ Tissue" description = "Natural tissues that make up the bulk of organs, providing many vitamins and minerals." @@ -234,15 +245,19 @@ ///Amount of satiety that will be drained when the cloth_fibers is fully metabolized var/delayed_satiety_drain = 2 * CLOTHING_NUTRITION_GAIN -/datum/reagent/consumable/nutriment/cloth_fibers/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) - if(M.satiety < MAX_SATIETY) - M.adjust_nutrition(CLOTHING_NUTRITION_GAIN) +/datum/reagent/consumable/nutriment/cloth_fibers/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(affected_mob.satiety < MAX_SATIETY) + affected_mob.adjust_nutrition(CLOTHING_NUTRITION_GAIN) delayed_satiety_drain += CLOTHING_NUTRITION_GAIN - return ..() -/datum/reagent/consumable/nutriment/cloth_fibers/on_mob_delete(mob/living/carbon/M) - M.adjust_nutrition(-delayed_satiety_drain) - return ..() +/datum/reagent/consumable/nutriment/cloth_fibers/on_mob_delete(mob/living/carbon/affected_mob) + . = ..() + if(!iscarbon(affected_mob)) + return + + var/mob/living/carbon/carbon_mob = affected_mob + carbon_mob.adjust_nutrition(-delayed_satiety_drain) /datum/reagent/consumable/nutriment/mineral name = "Mineral Slurry" @@ -252,14 +267,12 @@ brute_heal = 0 burn_heal = 0 -/datum/reagent/consumable/nutriment/mineral/on_mob_life(mob/living/carbon/eater, delta_time, times_fired) - current_cycle++ - if (HAS_TRAIT(eater, TRAIT_ROCK_EATER) && !HAS_TRAIT(eater, TRAIT_NOHUNGER) && ishuman(eater)) - var/mob/living/carbon/human/golem_eater = eater - golem_eater.adjust_nutrition(get_nutriment_factor() * REM * delta_time) - if(length(reagent_removal_skip_list)) - return - holder.remove_reagent(type, metabolization_rate * delta_time) +/datum/reagent/consumable/nutriment/mineral/get_nutriment_factor(mob/living/carbon/eater) + if(HAS_TRAIT(eater, TRAIT_ROCK_EATER)) + return ..() + + // You cannot eat rocks, it gives no nutrition + return 0 /datum/reagent/consumable/sugar name = "Sugar" @@ -268,9 +281,9 @@ color = "#FFFFFF" // rgb: 255, 255, 255 taste_mult = 1.5 // stop sugar drowning out other flavours nutriment_factor = 2 - metabolization_rate = 2 * REAGENTS_METABOLISM + metabolization_rate = 5 * REAGENTS_METABOLISM creation_purity = 1 // impure base reagents are a big no-no - overdose_threshold = 100 // Hyperglycaemic shock + overdose_threshold = 120 // Hyperglycaemic shock taste_description = "sweetness" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED default_container = /obj/item/reagent_containers/condiment/sugar @@ -280,15 +293,14 @@ mytray.adjust_weedlevel(rand(1, 2)) mytray.adjust_pestlevel(rand(1, 2)) -/datum/reagent/consumable/sugar/overdose_start(mob/living/M) - to_chat(M, span_userdanger("You go into hyperglycaemic shock! Lay off the twinkies!")) - M.AdjustSleeping(600) - . = TRUE +/datum/reagent/consumable/sugar/overdose_start(mob/living/affected_mob) + . = ..() + to_chat(affected_mob, span_userdanger("You go into hyperglycemic shock! Lay off the twinkies!")) + affected_mob.AdjustSleeping(20 SECONDS) -/datum/reagent/consumable/sugar/overdose_process(mob/living/M, seconds_per_tick, times_fired) - M.AdjustSleeping(40 * REM * seconds_per_tick) - ..() - . = TRUE +/datum/reagent/consumable/sugar/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.adjust_drowsiness_up_to((5 SECONDS * REM * seconds_per_tick), 60 SECONDS) /datum/reagent/consumable/virus_food name = "Virus Food" @@ -328,29 +340,21 @@ taste_mult = 1.5 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/capsaicin/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) +/datum/reagent/consumable/capsaicin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/heating = 0 switch(current_cycle) if(1 to 15) heating = 5 if(holder.has_reagent(/datum/reagent/cryostylane)) holder.remove_reagent(/datum/reagent/cryostylane, 5 * REM * seconds_per_tick) - if(isslime(M)) - heating = rand(5, 20) if(15 to 25) heating = 10 - if(isslime(M)) - heating = rand(10, 20) if(25 to 35) heating = 15 - if(isslime(M)) - heating = rand(15, 20) if(35 to INFINITY) heating = 20 - if(isslime(M)) - heating = rand(20, 25) - M.adjust_bodytemperature(heating * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick) - ..() + affected_mob.adjust_bodytemperature(heating * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick) /datum/reagent/consumable/frostoil name = "Frost Oil" @@ -363,33 +367,25 @@ specific_heat = 40 default_container = /obj/item/reagent_containers/cup/bottle/frostoil -/datum/reagent/consumable/frostoil/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) +/datum/reagent/consumable/frostoil/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/cooling = 0 switch(current_cycle) if(1 to 15) cooling = -10 if(holder.has_reagent(/datum/reagent/consumable/capsaicin)) holder.remove_reagent(/datum/reagent/consumable/capsaicin, 5 * REM * seconds_per_tick) - if(isslime(M)) - cooling = -rand(5, 20) if(15 to 25) cooling = -20 - if(isslime(M)) - cooling = -rand(10, 20) if(25 to 35) cooling = -30 if(prob(1)) - M.emote("shiver") - if(isslime(M)) - cooling = -rand(15, 20) + affected_mob.emote("shiver") if(35 to INFINITY) cooling = -40 if(prob(5)) - M.emote("shiver") - if(isslime(M)) - cooling = -rand(20, 25) - M.adjust_bodytemperature(cooling * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 50) - ..() + affected_mob.emote("shiver") + affected_mob.adjust_bodytemperature(cooling * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 50) /datum/reagent/consumable/frostoil/expose_turf(turf/exposed_turf, reac_volume) . = ..() @@ -417,7 +413,6 @@ default_container = /obj/item/reagent_containers/cup/bottle/capsaicin /datum/reagent/consumable/condensedcapsaicin/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) - . = ..() if(!ishuman(exposed_mob)) return @@ -446,12 +441,13 @@ victim.set_dizzy_if_lower(2 SECONDS) if(prob(5)) victim.vomit(VOMIT_CATEGORY_DEFAULT) + return ..() -/datum/reagent/consumable/condensedcapsaicin/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) +/datum/reagent/consumable/condensedcapsaicin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(!holder.has_reagent(/datum/reagent/consumable/milk)) if(SPT_PROB(5, seconds_per_tick)) - M.visible_message(span_warning("[M] [pick("dry heaves!","coughs!","splutters!")]")) - ..() + affected_mob.visible_message(span_warning("[affected_mob] [pick("dry heaves!","coughs!","splutters!")]")) /datum/reagent/consumable/salt name = "Table Salt" @@ -471,6 +467,8 @@ /datum/reagent/consumable/salt/expose_mob(mob/living/exposed_mob, methods, reac_volume) . = ..() + if(!iscarbon(exposed_mob)) + return var/mob/living/carbon/carbies = exposed_mob if(!(methods & (PATCH|TOUCH|VAPOR))) return @@ -502,7 +500,6 @@ flesh_healing -= max(VALUE_PER(5, 30) * reac_volume, 0) to_chat(victim, span_notice("The salt bits seep in and stick to [lowertext(src)], painfully irritating the skin! After a few moments, it feels marginally better.")) - /datum/reagent/consumable/blackpepper name = "Black Pepper" description = "A powder ground from peppercorns. *AAAACHOOO*" @@ -528,28 +525,21 @@ taste_description = "garlic" metabolization_rate = 0.15 * REAGENTS_METABOLISM chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + added_traits = list(TRAIT_GARLIC_BREATH) -/datum/reagent/consumable/garlic/on_mob_add(mob/living/L, amount) +/datum/reagent/consumable/garlic/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - ADD_TRAIT(L, TRAIT_GARLIC_BREATH, type) - -/datum/reagent/consumable/garlic/on_mob_delete(mob/living/L) - . = ..() - REMOVE_TRAIT(L, TRAIT_GARLIC_BREATH, type) - -/datum/reagent/consumable/garlic/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) - if(isvampire(M)) //incapacitating but not lethal. Unfortunately, vampires cannot vomit. - if(SPT_PROB(min(current_cycle/2, 12.5), seconds_per_tick)) - to_chat(M, span_danger("You can't get the scent of garlic out of your nose! You can barely think...")) - M.Paralyze(10) - M.set_jitter_if_lower(20 SECONDS) + if(isvampire(affected_mob)) //incapacitating but not lethal. Unfortunately, vampires cannot vomit. + if(SPT_PROB(min((current_cycle-1)/2, 12.5), seconds_per_tick)) + to_chat(affected_mob, span_danger("You can't get the scent of garlic out of your nose! You can barely think...")) + affected_mob.Paralyze(10) + affected_mob.set_jitter_if_lower(20 SECONDS) else - var/obj/item/organ/internal/liver/liver = M.get_organ_slot(ORGAN_SLOT_LIVER) + var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) if(liver && HAS_TRAIT(liver, TRAIT_CULINARY_METABOLISM)) if(SPT_PROB(10, seconds_per_tick)) //stays in the system much longer than sprinkles/banana juice, so heals slower to partially compensate - M.heal_bodypart_damage(brute = 1, burn = 1) - . = TRUE - ..() + if(affected_mob.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/tearjuice name = "Tear Juice" @@ -578,12 +568,12 @@ taste_description = "childhood whimsy" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/sprinkles/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) - var/obj/item/organ/internal/liver/liver = M.get_organ_slot(ORGAN_SLOT_LIVER) +/datum/reagent/consumable/sprinkles/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) if(liver && HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM)) - M.heal_bodypart_damage(1 * REM * seconds_per_tick, 1 * REM * seconds_per_tick, 0) - . = TRUE - ..() + if(affected_mob.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/enzyme name = "Universal Enzyme" @@ -619,9 +609,9 @@ taste_description = "your imprisonment" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/hot_ramen/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) - M.adjust_bodytemperature(10 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 0, M.get_body_temp_normal()) - ..() +/datum/reagent/consumable/hot_ramen/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.adjust_bodytemperature(10 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 0, affected_mob.get_body_temp_normal()) /datum/reagent/consumable/hell_ramen name = "Hell Ramen" @@ -631,9 +621,9 @@ taste_description = "wet and cheap noodles on fire" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/hell_ramen/on_mob_life(mob/living/carbon/target_mob, seconds_per_tick, times_fired) - target_mob.adjust_bodytemperature(10 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick) - ..() +/datum/reagent/consumable/hell_ramen/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.adjust_bodytemperature(10 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick) /datum/reagent/consumable/flour name = "Flour" @@ -646,6 +636,8 @@ /datum/reagent/consumable/flour/expose_mob(mob/living/exposed_mob, methods, reac_volume) . = ..() + if(!iscarbon(exposed_mob)) + return var/mob/living/carbon/carbies = exposed_mob if(!(methods & (PATCH|TOUCH|VAPOR))) return @@ -714,7 +706,7 @@ color = "#FFFFFF" // rgb: 0, 0, 0 taste_description = "chalky wheat with rice" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - + /datum/reagent/consumable/vanilla name = "Vanilla Powder" description = "A fatty, bitter paste made from vanilla pods." @@ -750,6 +742,8 @@ // Starch has similar absorbing properties to flour (Stronger here because it's rarer) /datum/reagent/consumable/corn_starch/expose_mob(mob/living/exposed_mob, methods, reac_volume) . = ..() + if(!iscarbon(exposed_mob)) + return var/mob/living/carbon/carbies = exposed_mob if(!(methods & (PATCH|TOUCH|VAPOR))) return @@ -785,9 +779,9 @@ taste_description = "sweet slime" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/corn_syrup/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) +/datum/reagent/consumable/corn_syrup/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() holder.add_reagent(/datum/reagent/consumable/sugar, 3 * REM * seconds_per_tick) - ..() /datum/reagent/consumable/honey name = "Honey" @@ -808,15 +802,17 @@ mytray.adjust_weedlevel(rand(1, 2)) mytray.adjust_pestlevel(rand(1, 2)) -/datum/reagent/consumable/honey/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) +/datum/reagent/consumable/honey/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() holder.add_reagent(/datum/reagent/consumable/sugar, 3 * REM * seconds_per_tick) + var/need_mob_update if(SPT_PROB(33, seconds_per_tick)) - M.adjustBruteLoss(-1, FALSE, required_bodytype = affected_bodytype) - M.adjustFireLoss(-1, FALSE, required_bodytype = affected_bodytype) - M.adjustOxyLoss(-1, FALSE, required_biotype = affected_biotype) - M.adjustToxLoss(-1, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + need_mob_update = affected_mob.adjustBruteLoss(-1, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-1, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustOxyLoss(-1, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustToxLoss(-1, updating_health = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/honey/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -857,10 +853,10 @@ color = "#664330" // rgb: 102, 67, 48 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/nutriment/stabilized/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) - if(M.nutrition > NUTRITION_LEVEL_FULL - 25) - M.adjust_nutrition(-3 * REM * get_nutriment_factor() * seconds_per_tick) - ..() +/datum/reagent/consumable/nutriment/stabilized/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(affected_mob.nutrition > NUTRITION_LEVEL_FULL - 25) + affected_mob.adjust_nutrition(-3 * REM * get_nutriment_factor(affected_mob) * seconds_per_tick) ////Lavaland Flora Reagents//// @@ -873,19 +869,20 @@ ph = 12 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/entpoly/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) - if(current_cycle >= 10) - M.Unconscious(40 * REM * seconds_per_tick, FALSE) - . = TRUE +/datum/reagent/consumable/entpoly/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update + if(current_cycle > 10) + affected_mob.Unconscious(40 * REM * seconds_per_tick, FALSE) if(SPT_PROB(10, seconds_per_tick)) - M.losebreath += 4 - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2*REM, 150, affected_biotype) - M.adjustToxLoss(3*REM, FALSE, required_biotype = affected_biotype) - M.adjustStaminaLoss(10*REM, FALSE, required_biotype = affected_biotype) - M.set_eye_blur_if_lower(10 SECONDS) - . = TRUE - ..() - + affected_mob.losebreath += 4 + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2*REM, 150, affected_biotype) + affected_mob.adjustToxLoss(3*REM, updating_health = FALSE, required_biotype = affected_biotype) + affected_mob.adjustStaminaLoss(10*REM, updating_stamina = FALSE, required_biotype = affected_biotype) + affected_mob.set_eye_blur_if_lower(10 SECONDS) + need_mob_update = TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/tinlux name = "Tinea Luxor" @@ -921,12 +918,14 @@ ph = 10.4 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/vitfro/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) +/datum/reagent/consumable/vitfro/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update if(SPT_PROB(55, seconds_per_tick)) - M.adjustBruteLoss(-1, FALSE, required_bodytype = affected_bodytype) - M.adjustFireLoss(-1, FALSE, required_bodytype = affected_bodytype) - . = TRUE - ..() + need_mob_update = affected_mob.adjustBruteLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/liquidelectricity name = "Liquid Electricity" @@ -949,13 +948,13 @@ if(istype(stomach)) stomach.adjust_charge(reac_volume * 30) -/datum/reagent/consumable/liquidelectricity/enriched/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) - if(isethereal(M)) - M.blood_volume += 1 * seconds_per_tick +/datum/reagent/consumable/liquidelectricity/enriched/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(isethereal(affected_mob)) + affected_mob.blood_volume += 1 * seconds_per_tick else if(SPT_PROB(10, seconds_per_tick)) //lmao at the newbs who eat energy bars - M.electrocute_act(rand(5,10), "Liquid Electricity in their body", 1, SHOCK_NOGLOVES) //the shock is coming from inside the house - playsound(M, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) - return ..() + affected_mob.electrocute_act(rand(5,10), "Liquid Electricity in their body", 1, SHOCK_NOGLOVES) //the shock is coming from inside the house + playsound(affected_mob, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) /datum/reagent/consumable/astrotame name = "Astrotame" @@ -969,11 +968,10 @@ overdose_threshold = 17 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/astrotame/overdose_process(mob/living/carbon/M, seconds_per_tick, times_fired) - if(M.disgust < 80) - M.adjust_disgust(10 * REM * seconds_per_tick) - ..() - . = TRUE +/datum/reagent/consumable/astrotame/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(affected_mob.disgust < 80) + affected_mob.adjust_disgust(10 * REM * seconds_per_tick) /datum/reagent/consumable/secretsauce name = "Secret Sauce" @@ -1018,11 +1016,10 @@ overdose_threshold = 15 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/consumable/char/overdose_process(mob/living/M, seconds_per_tick, times_fired) +/datum/reagent/consumable/char/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(13, seconds_per_tick)) - M.say(pick_list_replacements(BOOMER_FILE, "boomer"), forced = /datum/reagent/consumable/char) - ..() - return + affected_mob.say(pick_list_replacements(BOOMER_FILE, "boomer"), forced = /datum/reagent/consumable/char) /datum/reagent/consumable/bbqsauce name = "BBQ Sauce" @@ -1132,11 +1129,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED default_container = /obj/item/reagent_containers/condiment/peanut_butter -/datum/reagent/consumable/peanut_butter/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) //ET loves peanut butter - if(isabductor(M)) - M.add_mood_event("ET_pieces", /datum/mood_event/et_pieces, name) - M.set_drugginess(30 SECONDS * REM * seconds_per_tick) - ..() +/datum/reagent/consumable/peanut_butter/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) //ET loves peanut butter + . = ..() + if(isabductor(affected_mob)) + affected_mob.add_mood_event("ET_pieces", /datum/mood_event/et_pieces, name) + affected_mob.set_drugginess(30 SECONDS * REM * seconds_per_tick) /datum/reagent/consumable/vinegar name = "Vinegar" @@ -1194,10 +1191,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/mintextract/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(HAS_TRAIT(affected_mob, TRAIT_FAT)) affected_mob.investigate_log("has been gibbed by consuming [src] while fat.", INVESTIGATE_DEATHS) affected_mob.inflate_gib() - return ..() /datum/reagent/consumable/worcestershire name = "Worcestershire Sauce" diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents.dm index f7eaba3c211ba..59baceab5579f 100644 --- a/code/modules/reagents/chemistry/reagents/impure_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/impure_reagents.dm @@ -17,10 +17,15 @@ /datum/reagent/impurity/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) - if(isnull(liver)) //Though, lets be safe - return affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) //Incase of no liver! - affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, liver_damage * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - return TRUE + var/need_mob_update + + if(liver)//Though, lets be safe + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, liver_damage * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + else + need_mob_update = affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)//Incase of no liver! + + if(need_mob_update) + return UPDATE_MOB_HEALTH //Basically just so people don't forget to adjust metabolization_rate /datum/reagent/inverse @@ -36,7 +41,8 @@ /datum/reagent/inverse/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - return affected_mob.adjustToxLoss(tox_damage * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + if(affected_mob.adjustToxLoss(tox_damage * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH //Failed chems - generally use inverse if you want to use a impure subtype for it //technically not a impure chem, but it's here because it can only be made with a failed impure reaction @@ -54,13 +60,14 @@ // Unique -/datum/reagent/impurity/eigenswap +/datum/reagent/inverse/eigenswap name = "Eigenswap" description = "This reagent is known to swap the handedness of a patient." ph = 3.3 chemical_flags = REAGENT_DONOTSPLIT + tox_damage = 0 -/datum/reagent/impurity/eigenswap/on_mob_life(mob/living/carbon/affected_mob) +/datum/reagent/inverse/eigenswap/on_mob_life(mob/living/carbon/affected_mob) . = ..() if(!prob(creation_purity * 100)) return @@ -96,6 +103,7 @@ var/atom/movable/screen/alert/status_effect/freon/cryostylane_alert /datum/reagent/inverse/cryostylane/on_mob_add(mob/living/carbon/affected_mob, amount) + . = ..() cube = new /obj/structure/ice_stasis(get_turf(affected_mob)) cube.color = COLOR_CYAN cube.set_anchored(TRUE) @@ -103,17 +111,24 @@ affected_mob.apply_status_effect(/datum/status_effect/grouped/stasis, STASIS_CHEMICAL_EFFECT) cryostylane_alert = affected_mob.throw_alert("cryostylane_alert", /atom/movable/screen/alert/status_effect/freon/cryostylane) cryostylane_alert.attached_effect = src //so the alert can reference us, if it needs to - ..() /datum/reagent/inverse/cryostylane/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(!cube || affected_mob.loc != cube) - affected_mob.reagents.remove_reagent(type, volume) //remove it all if we're past 60s - if(current_cycle > 60) metabolization_rate += 0.01 - ..() + +/datum/reagent/inverse/cryostylane/metabolize_reagent(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + if(current_cycle >= 60) + holder.remove_reagent(type, volume) // remove it all if we're past 60 cycles + return + return ..() /datum/reagent/inverse/cryostylane/on_mob_delete(mob/living/carbon/affected_mob, amount) + . = ..() QDEL_NULL(cube) - affected_mob.remove_status_effect(/datum/status_effect/grouped/stasis, STASIS_CHEMICAL_EFFECT) - affected_mob.clear_alert("cryostylane_alert") - ..() + if(!iscarbon(affected_mob)) + return + + var/mob/living/carbon/carbon_mob = affected_mob + carbon_mob.remove_status_effect(/datum/status_effect/grouped/stasis, STASIS_CHEMICAL_EFFECT) + carbon_mob.clear_alert("cryostylane_alert") diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm index 5d1adbedcb052..3c677e43d1c07 100644 --- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm @@ -35,19 +35,21 @@ affected_respiration_type = ALL //Random healing of the 4 main groups -/datum/reagent/impurity/healing/medicine_failure/on_mob_life(mob/living/carbon/owner, seconds_per_tick, times_fired) +/datum/reagent/impurity/healing/medicine_failure/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update var/pick = pick("brute", "burn", "tox", "oxy") switch(pick) if("brute") - owner.adjustBruteLoss(-0.5, required_bodytype = affected_bodytype) + need_mob_update = affected_mob.adjustBruteLoss(-0.5, updating_health = FALSE, required_bodytype = affected_bodytype) if("burn") - owner.adjustFireLoss(-0.5, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-0.5, updating_health = FALSE, required_bodytype = affected_bodytype) if("tox") - owner.adjustToxLoss(-0.5, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustToxLoss(-0.5, updating_health = FALSE, required_biotype = affected_biotype) if("oxy") - owner.adjustOxyLoss(-0.5, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - ..() - return TRUE + need_mob_update += affected_mob.adjustOxyLoss(-0.5, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + if(need_mob_update) + return UPDATE_MOB_HEALTH // C2 medications // Helbital @@ -64,10 +66,10 @@ var/list/timer_ids //Warns you about the impenting hands -/datum/reagent/inverse/helgrasp/on_mob_add(mob/living/L, amount) - to_chat(L, span_hierophant("You hear laughter as malevolent hands apparate before you, eager to drag you down to hell...! Look out!")) - playsound(L.loc, 'sound/chemistry/ahaha.ogg', 80, TRUE, -1) //Very obvious tell so people can be ready +/datum/reagent/inverse/helgrasp/on_mob_add(mob/living/affected_mob, amount) . = ..() + to_chat(affected_mob, span_hierophant("You hear laughter as malevolent hands apparate before you, eager to drag you down to hell...! Look out!")) + playsound(affected_mob.loc, 'sound/chemistry/ahaha.ogg', 80, TRUE, -1) //Very obvious tell so people can be ready //Sends hands after you for your hubris /* @@ -81,8 +83,9 @@ Then I attempt to calculate the how many hands to created based off the current I take the 2s interval period and divide it by the number of hands I want to make (i.e. the current seconds_per_tick) and I keep track of how many hands I'm creating (since I always create one on a tick, then I start at 1 hand). For each hand I then use this time value multiplied by the number of hands. Since we're spawning one now, and it checks to see if hands is less than, but not less than or equal to, seconds_per_tick, no hands will be created on the next expected tick. Basically, we fill the time between now and 2s from now with hands based off the current lag. */ -/datum/reagent/inverse/helgrasp/on_mob_life(mob/living/carbon/owner, seconds_per_tick, times_fired) - spawn_hands(owner) +/datum/reagent/inverse/helgrasp/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + spawn_hands(affected_mob) lag_remainder += seconds_per_tick - FLOOR(seconds_per_tick, 1) seconds_per_tick = FLOOR(seconds_per_tick, 1) if(lag_remainder >= 1) @@ -91,36 +94,35 @@ Basically, we fill the time between now and 2s from now with hands based off the var/hands = 1 var/time = 2 / seconds_per_tick while(hands < seconds_per_tick) //we already made a hand now so start from 1 - LAZYADD(timer_ids, addtimer(CALLBACK(src, PROC_REF(spawn_hands), owner), (time*hands) SECONDS, TIMER_STOPPABLE)) //keep track of all the timers we set up + LAZYADD(timer_ids, addtimer(CALLBACK(src, PROC_REF(spawn_hands), affected_mob), (time*hands) SECONDS, TIMER_STOPPABLE)) //keep track of all the timers we set up hands += time - return ..() -/datum/reagent/inverse/helgrasp/proc/spawn_hands(mob/living/carbon/owner) - if(!owner && iscarbon(holder.my_atom))//Catch timer - owner = holder.my_atom +/datum/reagent/inverse/helgrasp/proc/spawn_hands(mob/living/carbon/affected_mob) + if(!affected_mob && iscarbon(holder.my_atom))//Catch timer + affected_mob = holder.my_atom //Adapted from the end of the curse - but lasts a short time - var/grab_dir = turn(owner.dir, pick(-90, 90, 180, 180)) //grab them from a random direction other than the one faced, favoring grabbing from behind - var/turf/spawn_turf = get_ranged_target_turf(owner, grab_dir, 8)//Larger range so you have more time to dodge + var/grab_dir = turn(affected_mob.dir, pick(-90, 90, 180, 180)) //grab them from a random direction other than the one faced, favoring grabbing from behind + var/turf/spawn_turf = get_ranged_target_turf(affected_mob, grab_dir, 8)//Larger range so you have more time to dodge if(!spawn_turf) return - new/obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, owner.dir) + new/obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, affected_mob.dir) playsound(spawn_turf, 'sound/effects/curse2.ogg', 80, TRUE, -1) var/obj/projectile/curse_hand/hel/hand = new (spawn_turf) - hand.preparePixelProjectile(owner, spawn_turf) + hand.preparePixelProjectile(affected_mob, spawn_turf) if(QDELETED(hand)) //safety check if above fails - above has a stack trace if it does fail return hand.fire() //At the end, we clear up any loose hanging timers just in case and spawn any remaining lag_remaining hands all at once. -/datum/reagent/inverse/helgrasp/on_mob_delete(mob/living/owner) +/datum/reagent/inverse/helgrasp/on_mob_delete(mob/living/affected_mob) + . = ..() var/hands = 0 while(lag_remainder > hands) - spawn_hands(owner) + spawn_hands(affected_mob) hands++ for(var/id in timer_ids) // So that we can be certain that all timers are deleted at the end. deltimer(id) timer_ids.Cut() - return ..() /datum/reagent/inverse/helgrasp/heretic name = "Grasp of the Mansus" @@ -129,19 +131,23 @@ Basically, we fill the time between now and 2s from now with hands based off the tox_damage = 0 //libital -//Impure +//Inverse: //Simply reduces your alcohol tolerance, kinda simular to prohol -/datum/reagent/impurity/libitoil +/datum/reagent/inverse/libitoil name = "Libitoil" description = "Temporarilly interferes a patient's ability to process alcohol." chemical_flags = REAGENT_DONOTSPLIT ph = 13.5 - liver_damage = 0.1 addiction_types = list(/datum/addiction/medicine = 4) + tox_damage = 0 + +/datum/reagent/inverse/libitoil/on_mob_life(mob/living/carbon/affected_mob, delta_time, times_fired) + . = ..() + affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.1 * REM * delta_time) -/datum/reagent/impurity/libitoil/on_mob_add(mob/living/L, amount) +/datum/reagent/inverse/libitoil/on_mob_add(mob/living/affected_mob, amount) . = ..() - var/mob/living/carbon/consumer = L + var/mob/living/carbon/consumer = affected_mob if(!consumer) return RegisterSignal(consumer, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_gained_organ)) @@ -149,23 +155,23 @@ Basically, we fill the time between now and 2s from now with hands based off the var/obj/item/organ/internal/liver/this_liver = consumer.get_organ_slot(ORGAN_SLOT_LIVER) this_liver.alcohol_tolerance *= 2 -/datum/reagent/impurity/libitoil/proc/on_gained_organ(mob/prev_owner, obj/item/organ/organ) +/datum/reagent/inverse/libitoil/proc/on_gained_organ(mob/prev_owner, obj/item/organ/organ) SIGNAL_HANDLER if(!istype(organ, /obj/item/organ/internal/liver)) return var/obj/item/organ/internal/liver/this_liver = organ this_liver.alcohol_tolerance *= 2 -/datum/reagent/impurity/libitoil/proc/on_removed_organ(mob/prev_owner, obj/item/organ/organ) +/datum/reagent/inverse/libitoil/proc/on_removed_organ(mob/prev_owner, obj/item/organ/organ) SIGNAL_HANDLER if(!istype(organ, /obj/item/organ/internal/liver)) return var/obj/item/organ/internal/liver/this_liver = organ this_liver.alcohol_tolerance /= 2 -/datum/reagent/impurity/libitoil/on_mob_delete(mob/living/L) +/datum/reagent/inverse/libitoil/on_mob_delete(mob/living/affected_mob) . = ..() - var/mob/living/carbon/consumer = L + var/mob/living/carbon/consumer = affected_mob UnregisterSignal(consumer, COMSIG_CARBON_LOSE_ORGAN) UnregisterSignal(consumer, COMSIG_CARBON_GAIN_ORGAN) var/obj/item/organ/internal/liver/this_liver = consumer.get_organ_slot(ORGAN_SLOT_LIVER) @@ -187,8 +193,8 @@ Basically, we fill the time between now and 2s from now with hands based off the liver_damage = 0 /datum/reagent/impurity/probital_failed/overdose_start(mob/living/carbon/M) + . = ..() metabolization_rate = 4 * REAGENTS_METABOLISM - ..() /datum/reagent/peptides_failed name = "Prion Peptides" @@ -196,27 +202,27 @@ Basically, we fill the time between now and 2s from now with hands based off the description = "These inhibitory peptides drains nutrition and causes brain damage in the patient!" ph = 2.1 -/datum/reagent/peptides_failed/on_mob_life(mob/living/carbon/owner, seconds_per_tick, times_fired) - owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.25 * seconds_per_tick, 170) - owner.adjust_nutrition(-5 * REAGENTS_METABOLISM * seconds_per_tick) - ..() - return TRUE +/datum/reagent/peptides_failed/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.25 * seconds_per_tick, 170)) + . = UPDATE_MOB_HEALTH + affected_mob.adjust_nutrition(-5 * REAGENTS_METABOLISM * seconds_per_tick) //Lenturi -//impure -/datum/reagent/impurity/lentslurri //Okay maybe I should outsource names for these +//inverse +/datum/reagent/inverse/lentslurri //Okay maybe I should outsource names for these name = "Lentslurri"//This is a really bad name please replace - description = "A highly addicitive muscle relaxant that is made when Lenturi reactions go wrong." + description = "A highly addicitive muscle relaxant that is made when Lenturi reactions go wrong, this will cause the patient to move slowly." addiction_types = list(/datum/addiction/medicine = 8) - liver_damage = 0 + tox_damage = 0 -/datum/reagent/impurity/lentslurri/on_mob_metabolize(mob/living/carbon/owner) - owner.add_movespeed_modifier(/datum/movespeed_modifier/reagent/lenturi) - return ..() +/datum/reagent/inverse/lentslurri/on_mob_metabolize(mob/living/carbon/affected_mob) + . = ..() + affected_mob.add_movespeed_modifier(/datum/movespeed_modifier/reagent/lenturi) -/datum/reagent/impurity/lentslurri/on_mob_end_metabolize(mob/living/carbon/owner) - owner.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/lenturi) - return ..() +/datum/reagent/inverse/lentslurri/on_mob_end_metabolize(mob/living/carbon/affected_mob) + . = ..() + affected_mob.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/lenturi) //failed /datum/reagent/inverse/ichiyuri @@ -233,39 +239,36 @@ Basically, we fill the time between now and 2s from now with hands based off the var/spammer = 0 //Just the removed itching mechanism - omage to it's origins. -/datum/reagent/inverse/ichiyuri/on_mob_life(mob/living/carbon/owner, seconds_per_tick, times_fired) - if(prob(resetting_probability) && !(HAS_TRAIT(owner, TRAIT_RESTRAINED) || owner.incapacitated())) +/datum/reagent/inverse/ichiyuri/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(prob(resetting_probability) && !(HAS_TRAIT(affected_mob, TRAIT_RESTRAINED) || affected_mob.incapacitated())) . = TRUE if(spammer < world.time) - to_chat(owner,span_warning("You can't help but itch yourself.")) + to_chat(affected_mob,span_warning("You can't help but itch yourself.")) spammer = world.time + (10 SECONDS) var/scab = rand(1,7) - owner.adjustBruteLoss(scab*REM) - owner.bleed(scab) + if(affected_mob.adjustBruteLoss(scab*REM, updating_health = FALSE)) + . = UPDATE_MOB_HEALTH + affected_mob.bleed(scab) resetting_probability = 0 - resetting_probability += (5*(current_cycle/10) * seconds_per_tick) // 10 iterations = >51% to itch - ..() - return . + resetting_probability += (5*((current_cycle-1)/10) * seconds_per_tick) // 10 iterations = >51% to itch //Aiuri -//impure -/datum/reagent/impurity/aiuri +//inverse +/datum/reagent/inverse/aiuri name = "Aivime" description = "This reagent is known to interfere with the eyesight of a patient." ph = 3.1 addiction_types = list(/datum/addiction/medicine = 1.5) - liver_damage = 0.1 - /// blurriness at the start of taking the med - var/amount_of_blur_applied = 0 SECONDS - -/datum/reagent/impurity/aiuri/on_mob_add(mob/living/owner, amount) - . = ..() - amount_of_blur_applied = creation_purity * (volume / metabolization_rate) * 2 SECONDS - owner.adjust_eye_blur(amount_of_blur_applied) + ///The amount of blur applied per second. Given the average on_life interval is 2 seconds, that'd be 2.5s. + var/amount_of_blur_applied = 1.25 SECONDS + tox_damage = 0 -/datum/reagent/impurity/aiuri/on_mob_delete(mob/living/owner, amount) +/datum/reagent/inverse/aiuri/on_mob_life(mob/living/carbon/owner, delta_time, times_fired) + owner.adjustOrganLoss(ORGAN_SLOT_EYES, 0.1 * REM * delta_time) + owner.adjust_eye_blur(amount_of_blur_applied * delta_time) . = ..() - owner.adjust_eye_blur(-amount_of_blur_applied) + return TRUE //Hercuri //inverse @@ -280,14 +283,14 @@ Basically, we fill the time between now and 2s from now with hands based off the taste_description = "heat! Ouch!" addiction_types = list(/datum/addiction/medicine = 2.5) -/datum/reagent/inverse/hercuri/on_mob_life(mob/living/carbon/owner, seconds_per_tick, times_fired) +/datum/reagent/inverse/hercuri/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() var/heating = rand(5, 25) * creation_purity * REM * seconds_per_tick - owner.reagents?.chem_temp += heating - owner.adjust_bodytemperature(heating * TEMPERATURE_DAMAGE_COEFFICIENT) - if(!ishuman(owner)) + affected_mob.reagents?.chem_temp += heating + affected_mob.adjust_bodytemperature(heating * TEMPERATURE_DAMAGE_COEFFICIENT) + if(!ishuman(affected_mob)) return - var/mob/living/carbon/human/human = owner + var/mob/living/carbon/human/human = affected_mob human.adjust_coretemperature(heating * TEMPERATURE_DAMAGE_COEFFICIENT) /datum/reagent/inverse/hercuri/expose_mob(mob/living/carbon/exposed_mob, methods=VAPOR, reac_volume) @@ -298,13 +301,14 @@ Basically, we fill the time between now and 2s from now with hands based off the exposed_mob.adjust_bodytemperature(reac_volume * TEMPERATURE_DAMAGE_COEFFICIENT) exposed_mob.adjust_fire_stacks(reac_volume / 2) -/datum/reagent/inverse/hercuri/overdose_process(mob/living/carbon/owner, seconds_per_tick, times_fired) +/datum/reagent/inverse/hercuri/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - owner.adjustOrganLoss(ORGAN_SLOT_LIVER, 2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) //Makes it so you can't abuse it with pyroxadone very easily (liver dies from 25u unless it's fully upgraded) + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) //Makes it so you can't abuse it with pyroxadone very easily (liver dies from 25u unless it's fully upgraded) + . = UPDATE_MOB_HEALTH var/heating = 10 * creation_purity * REM * seconds_per_tick * TEMPERATURE_DAMAGE_COEFFICIENT - owner.adjust_bodytemperature(heating) //hot hot - if(ishuman(owner)) - var/mob/living/carbon/human/human = owner + affected_mob.adjust_bodytemperature(heating) //hot hot + if(ishuman(affected_mob)) + var/mob/living/carbon/human/human = affected_mob human.adjust_coretemperature(heating) /datum/reagent/inverse/healing/tirimol @@ -317,16 +321,17 @@ Basically, we fill the time between now and 2s from now with hands based off the addiction_types = list(/datum/addiction/medicine = 5) //Makes patients fall asleep, then boosts the purirty of their medicine reagents if they're asleep -/datum/reagent/inverse/healing/tirimol/on_mob_life(mob/living/carbon/owner, seconds_per_tick, times_fired) +/datum/reagent/inverse/healing/tirimol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() switch(current_cycle) - if(1 to 10)//same delay as chloral hydrate + if(2 to 11)//same delay as chloral hydrate if(prob(50)) - owner.emote("yawn") - if(10 to INFINITY) - owner.Sleeping(40) + affected_mob.emote("yawn") + if(11 to INFINITY) + affected_mob.Sleeping(40) . = 1 - if(owner.IsSleeping()) - for(var/datum/reagent/reagent as anything in owner.reagents.reagent_list) + if(affected_mob.IsSleeping()) + for(var/datum/reagent/reagent as anything in affected_mob.reagents.reagent_list) if(reagent in cached_reagent_list) continue if(!istype(reagent, /datum/reagent/medicine)) @@ -334,23 +339,22 @@ Basically, we fill the time between now and 2s from now with hands based off the reagent.creation_purity *= 1.25 cached_reagent_list += reagent - else if(!owner.IsSleeping() && length(cached_reagent_list)) + else if(!affected_mob.IsSleeping() && length(cached_reagent_list)) for(var/datum/reagent/reagent as anything in cached_reagent_list) if(!reagent) continue reagent.creation_purity *= 0.8 cached_reagent_list = list() - ..() -/datum/reagent/inverse/healing/tirimol/on_mob_delete(mob/living/owner) - if(owner.IsSleeping()) - owner.visible_message(span_notice("[icon2html(owner, viewers(DEFAULT_MESSAGE_RANGE, src))] [owner] lets out a hearty snore!"))//small way of letting people know the supersnooze is ended +/datum/reagent/inverse/healing/tirimol/on_mob_delete(mob/living/affected_mob) + . = ..() + if(affected_mob.IsSleeping()) + affected_mob.visible_message(span_notice("[icon2html(affected_mob, viewers(DEFAULT_MESSAGE_RANGE, src))] [affected_mob] lets out a hearty snore!"))//small way of letting people know the supersnooze is ended for(var/datum/reagent/reagent as anything in cached_reagent_list) if(!reagent) continue reagent.creation_purity *= 0.8 cached_reagent_list = list() - ..() //convermol //inverse @@ -369,11 +373,11 @@ Basically, we fill the time between now and 2s from now with hands based off the var/cached_cold_level_2 var/cached_cold_level_3 -/datum/reagent/inverse/healing/convermol/on_mob_add(mob/living/owner, amount) +/datum/reagent/inverse/healing/convermol/on_mob_add(mob/living/affected_mob, amount) . = ..() - RegisterSignal(owner, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_gained_organ)) - RegisterSignal(owner, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(on_removed_organ)) - var/obj/item/organ/internal/lungs/lungs = owner.get_organ_slot(ORGAN_SLOT_LUNGS) + RegisterSignal(affected_mob, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_gained_organ)) + RegisterSignal(affected_mob, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(on_removed_organ)) + var/obj/item/organ/internal/lungs/lungs = affected_mob.get_organ_slot(ORGAN_SLOT_LUNGS) if(!lungs) return apply_lung_levels(lungs) @@ -416,11 +420,11 @@ Basically, we fill the time between now and 2s from now with hands based off the lungs.cold_level_2_threshold = cached_cold_level_2 lungs.cold_level_3_threshold = cached_cold_level_3 -/datum/reagent/inverse/healing/convermol/on_mob_delete(mob/living/owner) +/datum/reagent/inverse/healing/convermol/on_mob_delete(mob/living/affected_mob) . = ..() - UnregisterSignal(owner, COMSIG_CARBON_LOSE_ORGAN) - UnregisterSignal(owner, COMSIG_CARBON_GAIN_ORGAN) - var/obj/item/organ/internal/lungs/lungs = owner.get_organ_slot(ORGAN_SLOT_LUNGS) + UnregisterSignal(affected_mob, COMSIG_CARBON_LOSE_ORGAN) + UnregisterSignal(affected_mob, COMSIG_CARBON_GAIN_ORGAN) + var/obj/item/organ/internal/lungs/lungs = affected_mob.get_organ_slot(ORGAN_SLOT_LUNGS) if(!lungs) return restore_lung_levels(lungs) @@ -439,13 +443,13 @@ Basically, we fill the time between now and 2s from now with hands based off the var/poison_interval = (9 SECONDS) -/datum/reagent/inverse/technetium/on_mob_life(mob/living/carbon/owner, seconds_per_tick, times_fired) +/datum/reagent/inverse/technetium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() time_until_next_poison -= seconds_per_tick * (1 SECONDS) if (time_until_next_poison <= 0) time_until_next_poison = poison_interval - owner.adjustToxLoss(creation_purity * 1, required_biotype = affected_biotype) - . = TRUE - ..() + if(affected_mob.adjustToxLoss(creation_purity * 1, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH //Kind of a healing effect, Presumably you're using syrinver to purge so this helps that /datum/reagent/inverse/healing/syriniver @@ -457,7 +461,7 @@ Basically, we fill the time between now and 2s from now with hands based off the var/cached_reagent_list = list() addiction_types = list(/datum/addiction/medicine = 1.75) -/datum/reagent/inverse/healing/syriniver/on_mob_add(mob/living/affected_mob) +/datum/reagent/inverse/healing/syriniver/on_mob_add(mob/living/affected_mob, amount) if(!(iscarbon(affected_mob))) return ..() var/mob/living/carbon/affected_carbon = affected_mob @@ -493,12 +497,14 @@ Basically, we fill the time between now and 2s from now with hands based off the //Heals toxins if it's the only thing present - kinda the oposite of multiver! Maybe that's why it's inverse! /datum/reagent/inverse/healing/monover/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update if(length(affected_mob.reagents.reagent_list) > 1) - affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.5 * seconds_per_tick, required_organ_flag = affected_organ_flags) //Hey! It's everyone's favourite drawback from multiver! - return ..() - affected_mob.adjustToxLoss(-2 * REM * creation_purity * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - return TRUE + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.5 * seconds_per_tick, required_organ_flag = affected_organ_flags) //Hey! It's everyone's favourite drawback from multiver! + else + need_mob_update = affected_mob.adjustToxLoss(-2 * REM * creation_purity * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH ///Can bring a corpse back to life temporarily (if heart is intact) ///Makes wounds bleed more, if it brought someone back, they take additional brute and heart damage @@ -527,9 +533,10 @@ Basically, we fill the time between now and 2s from now with hands based off the ) /datum/reagent/inverse/penthrite/on_mob_dead(mob/living/carbon/affected_mob, seconds_per_tick) + . = ..() var/obj/item/organ/internal/heart/heart = affected_mob.get_organ_slot(ORGAN_SLOT_HEART) if(!heart || heart.organ_flags & ORGAN_FAILING) - return ..() + return metabolization_rate = 0.2 * REM affected_mob.add_traits(trait_buffs, type) affected_mob.set_stat(CONSCIOUS) //This doesn't touch knocked out @@ -544,18 +551,19 @@ Basically, we fill the time between now and 2s from now with hands based off the back_from_the_dead = TRUE affected_mob.emote("gasp") affected_mob.playsound_local(affected_mob, 'sound/health/fastbeat.ogg', 65) - ..() /datum/reagent/inverse/penthrite/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(!back_from_the_dead) - return ..() + return //Following is for those brought back from the dead only REMOVE_TRAIT(affected_mob, TRAIT_KNOCKEDOUT, CRIT_HEALTH_TRAIT) REMOVE_TRAIT(affected_mob, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT) for(var/datum/wound/iter_wound as anything in affected_mob.all_wounds) iter_wound.adjust_blood_flow(1-creation_purity) - affected_mob.adjustBruteLoss(5 * (1-creation_purity) * seconds_per_tick, required_bodytype = affected_bodytype) - affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, (1 + (1-creation_purity)) * seconds_per_tick, required_organ_flag = affected_organ_flags) + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(5 * (1-creation_purity) * seconds_per_tick, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, (1 + (1-creation_purity)) * seconds_per_tick, required_organ_flag = affected_organ_flags) if(affected_mob.health < HEALTH_THRESHOLD_CRIT) affected_mob.add_movespeed_modifier(/datum/movespeed_modifier/reagent/nooartrium) if(affected_mob.health < HEALTH_THRESHOLD_FULLCRIT) @@ -563,19 +571,20 @@ Basically, we fill the time between now and 2s from now with hands based off the var/obj/item/organ/internal/heart/heart = affected_mob.get_organ_slot(ORGAN_SLOT_HEART) if(!heart || heart.organ_flags & ORGAN_FAILING) remove_buffs(affected_mob) - ..() - return TRUE - + if(need_mob_update) + return UPDATE_MOB_HEALTH + /datum/reagent/inverse/penthrite/on_mob_delete(mob/living/carbon/affected_mob) + . = ..() remove_buffs(affected_mob) var/obj/item/organ/internal/heart/heart = affected_mob.get_organ_slot(ORGAN_SLOT_HEART) if(affected_mob.health < -500 || heart.organ_flags & ORGAN_FAILING)//Honestly commendable if you get -500 explosion(affected_mob, light_impact_range = 1, explosion_cause = src) qdel(heart) affected_mob.visible_message(span_boldwarning("[affected_mob]'s heart explodes!")) - return ..() /datum/reagent/inverse/penthrite/overdose_start(mob/living/carbon/affected_mob) + . = ..() if(!back_from_the_dead) return ..() var/obj/item/organ/internal/heart/heart = affected_mob.get_organ_slot(ORGAN_SLOT_HEART) @@ -650,7 +659,7 @@ Basically, we fill the time between now and 2s from now with hands based off the var/datum/brain_trauma/temp_trauma /datum/reagent/inverse/neurine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - .=..() + . = ..() if(temp_trauma) return if(!(SPT_PROB(creation_purity*10, seconds_per_tick))) @@ -658,7 +667,7 @@ Basically, we fill the time between now and 2s from now with hands based off the var/traumalist = subtypesof(/datum/brain_trauma) var/list/forbiddentraumas = list( /datum/brain_trauma/severe/split_personality, // Split personality uses a ghost, I don't want to use a ghost for a temp thing - /datum/brain_trauma/special/obsessed, // Obsessed sets the owner as an antag - I presume this will lead to problems, so we'll remove it + /datum/brain_trauma/special/obsessed, // Obsessed sets the affected_mob as an antag - I presume this will lead to problems, so we'll remove it /datum/brain_trauma/hypnosis, // Hypnosis, same reason as obsessed, plus a bug makes it remain even after the neurowhine purges and then turn into "nothing" on the med reading upon a second application /datum/brain_trauma/special/honorbound, // Designed to be chaplain exclusive ) @@ -671,7 +680,7 @@ Basically, we fill the time between now and 2s from now with hands based off the return /datum/reagent/inverse/neurine/on_mob_delete(mob/living/carbon/affected_mob) - .=..() + . = ..() if(!temp_trauma) return if(istype(temp_trauma, /datum/brain_trauma/special/imaginary_friend))//Good friends stay by you, no matter what @@ -679,7 +688,7 @@ Basically, we fill the time between now and 2s from now with hands based off the affected_mob.cure_trauma_type(temp_trauma, resilience = TRAUMA_RESILIENCE_MAGIC) /datum/reagent/inverse/corazargh - name = "Corazargh" //It's what you yell! Though, if you've a better name feel free. Also an omage to an older chem + name = "Corazargh" //It's what you yell! Though, if you've a better name feel free. Also an homage to an older chem description = "Interferes with the body's natural pacemaker, forcing the patient to manually beat their heart." color = "#5F5F5F" self_consuming = TRUE @@ -688,82 +697,22 @@ Basically, we fill the time between now and 2s from now with hands based off the metabolization_rate = REM chemical_flags = REAGENT_DEAD_PROCESS tox_damage = 0 - ///Weakref to the old heart we're swapping for - var/datum/weakref/original_heart_ref - ///Weakref to the new heart that's temp added - var/datum/weakref/manual_heart_ref -///Creates a new cursed heart and puts the old inside of it, then replaces the position of the old +///Give the victim the manual heart beating component. /datum/reagent/inverse/corazargh/on_mob_metabolize(mob/living/affected_mob) + . = ..() if(!iscarbon(affected_mob)) return var/mob/living/carbon/carbon_mob = affected_mob - var/obj/item/organ/internal/heart/original_heart = affected_mob.get_organ_slot(ORGAN_SLOT_HEART) - if(!original_heart) - return - original_heart_ref = WEAKREF(original_heart) - - var/obj/item/organ/internal/heart/cursed/manual_heart = new(null, src) - manual_heart_ref = WEAKREF(manual_heart) - original_heart.Remove(carbon_mob, special = TRUE) //So we don't suddenly die - original_heart.forceMove(manual_heart) - original_heart.organ_flags |= ORGAN_FROZEN //Not actually frozen, but we want to pause decay - manual_heart.Insert(carbon_mob, special = TRUE) - //these last so instert doesn't call them - RegisterSignal(carbon_mob, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_gained_organ)) - RegisterSignal(carbon_mob, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(on_removed_organ)) - to_chat(affected_mob, span_userdanger("You feel your heart suddenly stop beating on it's own - you'll have to manually beat it!")) - ..() - -///Intercepts the new heart and creates a new cursed heart - putting the old inside of it -/datum/reagent/inverse/corazargh/proc/on_gained_organ(mob/affected_mob, obj/item/organ/organ) - SIGNAL_HANDLER - if(!istype(organ, /obj/item/organ/internal/heart)) + var/obj/item/organ/internal/heart/affected_heart = carbon_mob.get_organ_slot(ORGAN_SLOT_HEART) + if(isnull(affected_heart)) return - // DO NOT REACT TO YOUR OWN HEART ADDITION I SWEAR TO CHRIST - var/obj/item/organ/internal/heart/cursed/manual_heart = manual_heart_ref?.resolve() - if(organ == manual_heart) - return - - var/mob/living/carbon/affected_carbon = affected_mob - var/obj/item/organ/internal/heart/original_heart = organ - original_heart_ref = WEAKREF(original_heart) - original_heart.Remove(affected_carbon, special = TRUE) - if(!manual_heart) - manual_heart = new(null, src) - manual_heart_ref = WEAKREF(manual_heart) - original_heart.forceMove(manual_heart) - original_heart.organ_flags |= ORGAN_FROZEN //Not actually frozen, but we want to pause decay - manual_heart.Insert(affected_carbon, special = TRUE) - -///If we're ejecting out the organ - replace it with the original -/datum/reagent/inverse/corazargh/proc/on_removed_organ(mob/prev_owner, obj/item/organ/organ) - SIGNAL_HANDLER - var/obj/item/organ/internal/heart/cursed/manual_heart = manual_heart_ref?.resolve() - if(organ != manual_heart) - return - var/obj/item/organ/internal/heart/original_heart = original_heart_ref?.resolve() - if(!original_heart) - return - - original_heart.forceMove(manual_heart.loc) - original_heart.organ_flags &= ~ORGAN_FROZEN //enable decay again - QDEL_NULL(manual_heart_ref) + carbon_mob.AddComponent(/datum/component/manual_heart) + return ..() -///We're done - remove the curse and restore the old one +///We're done - remove the curse /datum/reagent/inverse/corazargh/on_mob_end_metabolize(mob/living/affected_mob) - //Do these first so Insert doesn't call them - UnregisterSignal(affected_mob, COMSIG_CARBON_LOSE_ORGAN) - UnregisterSignal(affected_mob, COMSIG_CARBON_GAIN_ORGAN) - if(!iscarbon(affected_mob)) - return - var/mob/living/carbon/affected_carbon = affected_mob - var/obj/item/organ/internal/heart/original_heart = original_heart_ref?.resolve() - if(original_heart) //Mostly a just in case - original_heart.organ_flags &= ~ORGAN_FROZEN //enable decay again - original_heart.Insert(affected_carbon, special = TRUE) - QDEL_NULL(manual_heart_ref) - to_chat(affected_mob, span_userdanger("You feel your heart start beating normally again!")) + qdel(affected_mob.GetComponent(/datum/component/manual_heart)) ..() /datum/reagent/inverse/antihol @@ -777,9 +726,9 @@ Basically, we fill the time between now and 2s from now with hands based off the tox_damage = 0 /datum/reagent/inverse/antihol/on_mob_life(mob/living/carbon/C, seconds_per_tick, times_fired) + . = ..() for(var/datum/reagent/consumable/ethanol/alcohol in C.reagents.reagent_list) alcohol.boozepwr += seconds_per_tick - ..() /datum/reagent/inverse/oculine name = "Oculater" @@ -796,19 +745,19 @@ Basically, we fill the time between now and 2s from now with hands based off the var/headache = FALSE /datum/reagent/inverse/oculine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(headache) return ..() if(SPT_PROB(100 * creation_purity, seconds_per_tick)) affected_mob.become_blind(IMPURE_OCULINE) to_chat(affected_mob, span_danger("You suddenly develop a pounding headache as your vision fluxuates.")) headache = TRUE - ..() /datum/reagent/inverse/oculine/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.cure_blind(IMPURE_OCULINE) if(headache) to_chat(affected_mob, span_notice("Your headache clears up!")) - ..() /datum/reagent/impurity/inacusiate name = "Tinacusiate" @@ -821,18 +770,18 @@ Basically, we fill the time between now and 2s from now with hands based off the liver_damage = 0.1 metabolization_rate = 0.04 * REM ///The random span we start hearing in - var/randomSpan + var/random_span /datum/reagent/impurity/inacusiate/on_mob_metabolize(mob/living/affected_mob, seconds_per_tick, times_fired) - randomSpan = pick(list("clown", "small", "big", "hypnophrase", "alien", "cult", "alert", "danger", "emote", "yell", "brass", "sans", "papyrus", "robot", "his_grace", "phobia")) + . = ..() + random_span = pick("clown", "small", "big", "hypnophrase", "alien", "cult", "alert", "danger", "emote", "yell", "brass", "sans", "papyrus", "robot", "his_grace", "phobia") RegisterSignal(affected_mob, COMSIG_MOVABLE_HEAR, PROC_REF(owner_hear)) - to_chat(affected_mob, span_warning("Your hearing seems to be a bit off!")) - ..() + to_chat(affected_mob, span_warning("Your hearing seems to be a bit off[affected_mob.can_hear() ? "!" : " - wait, that's normal."]")) /datum/reagent/impurity/inacusiate/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() UnregisterSignal(affected_mob, COMSIG_MOVABLE_HEAR) - to_chat(affected_mob, span_notice("You start hearing things normally again.")) - ..() + to_chat(affected_mob, span_notice("You start hearing things normally again[affected_mob.can_hear() ? "" : " - no, wait, no you don't"].")) /datum/reagent/impurity/inacusiate/proc/owner_hear(mob/living/owner, list/hearing_args) SIGNAL_HANDLER @@ -840,5 +789,153 @@ Basically, we fill the time between now and 2s from now with hands based off the // don't skip messages that the owner says or can't understand (since they still make sounds) if(!owner.can_hear()) return + // not technically hearing + var/atom/movable/speaker = hearing_args[HEARING_SPEAKER] + if(!isnull(speaker) && HAS_TRAIT(speaker, TRAIT_SIGN_LANG)) + return + + hearing_args[HEARING_SPANS] |= random_span + +/datum/reagent/inverse/sal_acid + name = "Benzoic Acid" + description = "Robust fertilizer that provides a decent range of benefits for plant life." + taste_description = "flowers" + reagent_state = LIQUID + color = "#e6c843" + ph = 3.4 + tox_damage = 0 + +/datum/reagent/inverse/sal_acid/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) + mytray.adjust_plant_health(round(volume * 0.5)) + mytray.myseed?.adjust_production(-round(volume * 0.2)) + mytray.myseed?.adjust_potency(round(volume * 0.25)) + mytray.myseed?.adjust_yield(round(volume * 0.2)) + +/datum/reagent/inverse/oxandrolone + name = "Oxymetholone" + description = "Anabolic steroid that promotes the growth of muscle during and after exercise." + reagent_state = LIQUID + color = "#520c23" + taste_description = "sweat" + metabolization_rate = 0.4 * REM + overdose_threshold = 25 + ph = 12.2 + tox_damage = 0 + +/datum/reagent/inverse/oxandrolone/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/high_message = pick("You feel unstoppable.", "Giving it EVERYTHING!!", "You feel ready for anything.", "You feel like doing a thousand jumping jacks!") + if(SPT_PROB(2, seconds_per_tick)) + to_chat(affected_mob, span_notice("[high_message]")) + +/datum/reagent/inverse/oxandrolone/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(SPT_PROB(25, seconds_per_tick)) + affected_mob.adjust_bodytemperature(30 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick) + affected_mob.set_jitter_if_lower(3 SECONDS) + affected_mob.adjustStaminaLoss(5 * REM * seconds_per_tick) + else if(SPT_PROB(5, seconds_per_tick)) + affected_mob.vomit(VOMIT_CATEGORY_BLOOD, lost_nutrition = 0, distance = 3) + affected_mob.Paralyze(3 SECONDS) + +/datum/reagent/inverse/salbutamol + name = "Bamethan" + description = "Blood thinner that drastically increases the chance of receiving bleeding wounds." + reagent_state = LIQUID + color = "#ecd4d6" + taste_description = "paint thinner" + ph = 4.5 + metabolization_rate = 0.08 * REM + tox_damage = 0 + metabolized_traits = list(TRAIT_EASYBLEED) + +/datum/reagent/inverse/pen_acid + name = "Pendetide" + description = "Purges basic toxin healing medications and increases the severity of radiation poisoning." + reagent_state = LIQUID + color = "#09ff00" + ph = 3.7 + taste_description = "venom" + metabolization_rate = 0.25 * REM + tox_damage = 0 + +/datum/reagent/inverse/pen_acid/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + holder.remove_reagent(/datum/reagent/medicine/c2/seiver, 5 * REM * seconds_per_tick) + holder.remove_reagent(/datum/reagent/medicine/potass_iodide, 5 * REM * seconds_per_tick) + holder.remove_reagent(/datum/reagent/medicine/c2/multiver, 5 * REM * seconds_per_tick) + + . = ..() + if(HAS_TRAIT(affected_mob, TRAIT_IRRADIATED)) + affected_mob.set_jitter_if_lower(10 SECONDS) + affected_mob.adjust_disgust(3 * REM * seconds_per_tick) + if(SPT_PROB(2.5, seconds_per_tick)) + to_chat(affected_mob, span_warning("A horrible ache spreads in your insides!")) + affected_mob.adjust_confusion_up_to(10 SECONDS, 15 SECONDS) + +/datum/reagent/inverse/atropine + name = "Hyoscyamine" + description = "Slowly regenerates all damaged organs, but cannot restore non-functional organs." + reagent_state = LIQUID + color = "#273333" + ph = 13.6 + metabolization_rate = 0.2 * REM + tox_damage = 0 + overdose_threshold = 40 - hearing_args[HEARING_RAW_MESSAGE] = "[hearing_args[HEARING_RAW_MESSAGE]]" +/datum/reagent/inverse/atropine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_STOMACH, -1 * REM * seconds_per_tick) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, -1 * REM * seconds_per_tick) + if(affected_mob.getToxLoss() <= 25) + need_mob_update = affected_mob.adjustToxLoss(-0.5, updating_health = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH + +/datum/reagent/inverse/atropine/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/static/list/possible_organs = list( + ORGAN_SLOT_HEART, + ORGAN_SLOT_LIVER, + ORGAN_SLOT_LUNGS, + ORGAN_SLOT_STOMACH, + ORGAN_SLOT_EYES, + ORGAN_SLOT_EARS, + ORGAN_SLOT_BRAIN, + ORGAN_SLOT_APPENDIX, + ORGAN_SLOT_TONGUE, + ) + affected_mob.adjustOrganLoss(pick(possible_organs) ,2 * seconds_per_tick) + affected_mob.reagents.remove_reagent(type, 1 * REM * seconds_per_tick) + +/datum/reagent/inverse/ammoniated_mercury + name = "Ammoniated Sludge" + description = "A ghastly looking mess of mercury by-product. Causes bursts of manic hysteria." + reagent_state = LIQUID + color = "#353535" + ph = 10.2 + metabolization_rate = 0.4 * REM + tox_damage = 0 + +/datum/reagent/inverse/ammoniated_mercury/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(SPT_PROB(7.5, seconds_per_tick)) + affected_mob.emote("scream") + affected_mob.say(pick("AAAAAAAHHHHH!!","OOOOH NOOOOOO!!","GGGUUUUHHHHH!!","AIIIIIEEEEEE!!","HAHAHAHAHAAAAAA!!","OORRRGGGHHH!!","AAAAAAAJJJJJJJJJ!!"), forced = type) + +/datum/reagent/inverse/rezadone + name = "Inreziniver" + description = "Makes the user horribly afraid of all things related to carps." + reagent_state = LIQUID + color = "#c92eb4" + ph = 13.9 + metabolization_rate = 0.05 * REM + tox_damage = 0 + +/datum/reagent/inverse/rezadone/on_mob_metabolize(mob/living/carbon/affected_mob) + . = ..() + affected_mob.gain_trauma(/datum/brain_trauma/mild/phobia/carps, TRAUMA_RESILIENCE_ABSOLUTE) + +/datum/reagent/inverse/rezadone/on_mob_end_metabolize(mob/living/carbon/affected_mob) + . = ..() + affected_mob.cure_trauma_type(/datum/brain_trauma/mild/phobia/carps, resilience = TRAUMA_RESILIENCE_ABSOLUTE) diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_toxin_reagents.dm index 947c83c3166af..0872fa6658815 100644 --- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_toxin_reagents.dm @@ -18,7 +18,6 @@ owner.adjust_disgust(50) ..() - //Formaldehyde - Impure Version /datum/reagent/impurity/methanol name = "Methanol" @@ -29,9 +28,10 @@ liver_damage = 0 /datum/reagent/impurity/methanol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/obj/item/organ/internal/eyes/eyes = affected_mob.get_organ_slot(ORGAN_SLOT_EYES) - eyes?.apply_organ_damage(0.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - return ..() + if(eyes?.apply_organ_damage(0.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH //Chloral Hydrate - Impure Version /datum/reagent/impurity/chloralax @@ -43,9 +43,9 @@ liver_damage = 0 /datum/reagent/impurity/chloralax/on_mob_life(mob/living/carbon/owner, seconds_per_tick) - owner.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - + . = ..() + if(owner.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH //Mindbreaker Toxin - Impure Version /datum/reagent/impurity/rosenol @@ -58,12 +58,12 @@ metabolization_rate = 0.5 * REAGENTS_METABOLISM /datum/reagent/impurity/rosenol/on_mob_life(mob/living/carbon/owner, seconds_per_tick) + . = ..() var/obj/item/organ/internal/tongue/tongue = owner.get_organ_slot(ORGAN_SLOT_TONGUE) if(!tongue) - return ..() + return if(SPT_PROB(4.0, seconds_per_tick)) owner.manual_emote("clicks with [owner.p_their()] tongue.") owner.say("Noice.", forced = /datum/reagent/impurity/rosenol) if(SPT_PROB(2.0, seconds_per_tick)) owner.say(pick("Ah! That was a mistake!", "Horrible.", "Watch out everybody, the potato is really hot.", "When I was six I ate a bag of plums.", "And if there is one thing I can't stand it's tomatoes.", "And if there is one thing I love it's tomatoes.", "We had a captain who was so strict, you weren't allowed to breathe in their station.", "The unrobust ones just used to keel over and die, you'd hear them going down behind you."), forced = /datum/reagent/impurity/rosenol) - ..() diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 6e2b12a95eb03..aface2dce34b5 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -9,11 +9,10 @@ /datum/reagent/medicine taste_description = "bitterness" -/datum/reagent/medicine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - current_cycle++ - if(length(reagent_removal_skip_list)) - return - holder.remove_reagent(type, metabolization_rate * seconds_per_tick / affected_mob.metabolism_efficiency) //medicine reagents stay longer if you have a better metabolism +/datum/reagent/medicine/New() + . = ..() + // All medicine metabolizes out slower / stay longer if you have a better metabolism + chemical_flags |= REAGENT_REVERSE_METABOLISM /datum/reagent/medicine/leporazine name = "Leporazine" @@ -23,6 +22,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/leporazine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/target_temp = affected_mob.get_body_temp_normal(apply_change = FALSE) if(affected_mob.bodytemperature > target_temp) affected_mob.adjust_bodytemperature(-40 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, target_temp) @@ -34,7 +34,6 @@ affected_human.adjust_coretemperature(-40 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, target_temp) else if(affected_human.coretemperature < (target_temp + 1)) affected_human.adjust_coretemperature(40 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 0, target_temp) - ..() /datum/reagent/medicine/adminordrazine //An OP chemical for admins name = "Adminordrazine" @@ -42,6 +41,7 @@ color = "#E0BB00" //golden for the gods taste_description = "badmins" chemical_flags = REAGENT_DEAD_PROCESS + metabolized_traits = list(TRAIT_ANALGESIA) /// Flags to fullheal every metabolism tick var/full_heal_flags = ~(HEAL_BRUTE|HEAL_BURN|HEAL_TOX|HEAL_RESTRAINTS|HEAL_REFRESH_ORGANS) @@ -66,12 +66,11 @@ mytray.visible_message(span_warning("Nothing happens...")) /datum/reagent/medicine/adminordrazine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.heal_bodypart_damage(5 * REM * seconds_per_tick, 5 * REM * seconds_per_tick, 0, FALSE, affected_bodytype) - affected_mob.adjustToxLoss(-5 * REM * seconds_per_tick, FALSE, TRUE, affected_biotype) + . = ..() + affected_mob.heal_bodypart_damage(brute = 5 * REM * seconds_per_tick, burn = 5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + affected_mob.adjustToxLoss(-5 * REM * seconds_per_tick, updating_health = FALSE, forced = TRUE, required_biotype = affected_biotype) // Heal everything! That we want to. But really don't heal reagents. Otherwise we'll lose ... us. - affected_mob.fully_heal(full_heal_flags & ~HEAL_ALL_REAGENTS) - ..() - return TRUE + affected_mob.fully_heal(full_heal_flags & ~HEAL_ALL_REAGENTS) // there is no need to return UPDATE_MOB_HEALTH because this proc calls updatehealth() /datum/reagent/medicine/adminordrazine/quantum_heal name = "Quantum Medicine" @@ -87,6 +86,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/synaptizine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_drowsiness(-10 SECONDS * REM * seconds_per_tick) affected_mob.AdjustStun(-20 * REM * seconds_per_tick) affected_mob.AdjustKnockdown(-20 * REM * seconds_per_tick) @@ -97,9 +97,8 @@ holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5 * REM * seconds_per_tick) affected_mob.adjust_hallucinations(-20 SECONDS * REM * seconds_per_tick) if(SPT_PROB(16, seconds_per_tick)) - affected_mob.adjustToxLoss(1, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + if(affected_mob.adjustToxLoss(1, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/synaphydramine name = "Diphen-Synaptizine" @@ -109,6 +108,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/synaphydramine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_drowsiness(-10 SECONDS * REM * seconds_per_tick) if(holder.has_reagent(/datum/reagent/toxin/mindbreaker)) holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5 * REM * seconds_per_tick) @@ -116,9 +116,8 @@ holder.remove_reagent(/datum/reagent/toxin/histamine, 5 * REM * seconds_per_tick) affected_mob.adjust_hallucinations(-20 SECONDS * REM * seconds_per_tick) if(SPT_PROB(16, seconds_per_tick)) - affected_mob.adjustToxLoss(1, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + if(affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/sansufentanyl name = "Sansufentanyl" @@ -128,16 +127,16 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/sansufentanyl/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_confusion_up_to(3 SECONDS * REM * seconds_per_tick, 5 SECONDS) affected_mob.adjust_dizzy_up_to(6 SECONDS * REM * seconds_per_tick, 12 SECONDS) - affected_mob.adjustStaminaLoss(1 * REM * seconds_per_tick) + if(affected_mob.adjustStaminaLoss(1 * REM * seconds_per_tick, updating_stamina = FALSE)) + . = UPDATE_MOB_HEALTH if(SPT_PROB(10, seconds_per_tick)) to_chat(affected_mob, "You feel confused and disoriented.") if(prob(30)) SEND_SOUND(affected_mob, sound('sound/weapons/flash_ring.ogg')) - ..() - return TRUE /datum/reagent/medicine/cryoxadone name = "Cryoxadone" @@ -150,44 +149,28 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/cryoxadone/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() metabolization_rate = REAGENTS_METABOLISM * (0.00001 * (affected_mob.bodytemperature ** 2) + 0.5) if(affected_mob.bodytemperature >= T0C || !HAS_TRAIT(affected_mob, TRAIT_KNOCKEDOUT)) - ..() return var/power = -0.00003 * (affected_mob.bodytemperature ** 2) + 3 - affected_mob.adjustOxyLoss(-3 * power * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustBruteLoss(-power * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-power * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustToxLoss(-power * REM * seconds_per_tick, FALSE, TRUE, affected_biotype) //heals TOXINLOVERs - affected_mob.adjustCloneLoss(-power * REM * seconds_per_tick, FALSE, affected_biotype) + var/need_mob_update + need_mob_update = affected_mob.adjustOxyLoss(-3 * power * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustBruteLoss(-power * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-power * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustToxLoss(-power * REM * seconds_per_tick, updating_health = FALSE, forced = TRUE, required_biotype = affected_biotype) //heals TOXINLOVERs for(var/i in affected_mob.all_wounds) var/datum/wound/iter_wound = i iter_wound.on_xadone(power * REM * seconds_per_tick) REMOVE_TRAIT(affected_mob, TRAIT_DISFIGURED, TRAIT_GENERIC) //fixes common causes for disfiguration - ..() - return TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH // Healing /datum/reagent/medicine/cryoxadone/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) mytray.adjust_plant_health(round(volume * 3)) mytray.adjust_toxic(-round(volume * 3)) -/datum/reagent/medicine/clonexadone - name = "Clonexadone" - description = "A chemical that derives from Cryoxadone. It specializes in healing clone damage, but nothing else. Requires very cold temperatures to properly metabolize, and metabolizes quicker than cryoxadone." - color = "#3D3DC6" - taste_description = "muscle" - ph = 13 - metabolization_rate = 1.5 * REAGENTS_METABOLISM - -/datum/reagent/medicine/clonexadone/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(affected_mob.bodytemperature < T0C) - affected_mob.adjustCloneLoss((0.00006 * (affected_mob.bodytemperature ** 2) - 6) * REM * seconds_per_tick, FALSE) - REMOVE_TRAIT(affected_mob, TRAIT_DISFIGURED, TRAIT_GENERIC) - . = TRUE - metabolization_rate = REAGENTS_METABOLISM * (0.000015 * (affected_mob.bodytemperature ** 2) + 0.75) - ..() - /datum/reagent/medicine/pyroxadone name = "Pyroxadone" description = "A mixture of cryoxadone and slime jelly, that apparently inverses the requirement for its activation." @@ -197,6 +180,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/pyroxadone/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT) var/power = 0 switch(affected_mob.bodytemperature) @@ -209,41 +193,48 @@ if(affected_mob.on_fire) power *= 2 - affected_mob.adjustOxyLoss(-2 * power * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustBruteLoss(-power * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-1.5 * power * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustToxLoss(-power * REM * seconds_per_tick, FALSE, TRUE, affected_biotype) - affected_mob.adjustCloneLoss(-power * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + var/need_mob_update + need_mob_update = affected_mob.adjustOxyLoss(-2 * power * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustBruteLoss(-power * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-1.5 * power * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustToxLoss(-power * REM * seconds_per_tick, updating_health = FALSE, forced = TRUE, required_biotype = affected_biotype) + if(need_mob_update) + . = UPDATE_MOB_HEALTH for(var/i in affected_mob.all_wounds) var/datum/wound/iter_wound = i iter_wound.on_xadone(power * REM * seconds_per_tick) REMOVE_TRAIT(affected_mob, TRAIT_DISFIGURED, TRAIT_GENERIC) - . = TRUE - ..() /datum/reagent/medicine/rezadone name = "Rezadone" - description = "A powder derived from fish toxin, Rezadone can effectively treat genetic damage as well as restoring minor wounds and restoring corpses husked by burns. Overdose will cause intense nausea and minor toxin damage." + description = "A powder derived from fish toxin, Rezadone can effectively restore corpses husked by burns as well as treat minor wounds. Overdose will cause intense nausea and minor toxin damage." reagent_state = SOLID color = "#669900" // rgb: 102, 153, 0 overdose_threshold = 30 ph = 12.2 taste_description = "fish" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + inverse_chem_val = 0.25 + inverse_chem = /datum/reagent/inverse/rezadone +// Rezadone is almost never used in favor of cryoxadone. Hopefully this will change that. // No such luck so far // with clone damage gone, someone will find a better use for rezadone... right? /datum/reagent/medicine/rezadone/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.setCloneLoss(0) //Rezadone is almost never used in favor of cryoxadone. Hopefully this will change that. // No such luck so far - affected_mob.heal_bodypart_damage(1 * REM * seconds_per_tick, 1 * REM * seconds_per_tick) + . = ..() + if(affected_mob.heal_bodypart_damage( + brute = 1 * REM * seconds_per_tick, + burn = 1 * REM * seconds_per_tick, + updating_health = FALSE, + required_bodytype = affected_biotype + )) + . = UPDATE_MOB_HEALTH REMOVE_TRAIT(affected_mob, TRAIT_DISFIGURED, TRAIT_GENERIC) - ..() - . = TRUE /datum/reagent/medicine/rezadone/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + . = ..() + if(affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH affected_mob.set_dizzy_if_lower(10 SECONDS * REM * seconds_per_tick) affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick) - ..() - . = TRUE /datum/reagent/medicine/rezadone/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -262,14 +253,7 @@ metabolization_rate = 0.1 * REAGENTS_METABOLISM ph = 8.1 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/medicine/spaceacillin/on_mob_add(mob/living/L) - . = ..() - ADD_TRAIT(L, TRAIT_VIRUS_RESISTANCE, type) - -/datum/reagent/medicine/spaceacillin/on_mob_delete(mob/living/L) - . = ..() - REMOVE_TRAIT(L, TRAIT_VIRUS_RESISTANCE, type) + added_traits = list(TRAIT_VIRUS_RESISTANCE) //Goon Chems. Ported mainly from Goonstation. Easily mixable (or not so easily) and provide a variety of effects. @@ -282,20 +266,24 @@ overdose_threshold = 25 ph = 10.7 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + inverse_chem_val = 0.3 + inverse_chem = /datum/reagent/inverse/oxandrolone /datum/reagent/medicine/oxandrolone/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update if(affected_mob.getFireLoss() > 25) - affected_mob.adjustFireLoss(-4 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) //Twice as effective as AIURI for severe burns + need_mob_update = affected_mob.adjustFireLoss(-4 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) //Twice as effective as AIURI for severe burns else - affected_mob.adjustFireLoss(-0.5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) //But only a quarter as effective for more minor ones - ..() - . = TRUE + need_mob_update = affected_mob.adjustFireLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) //But only a quarter as effective for more minor ones + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/oxandrolone/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.getFireLoss()) //It only makes existing burns worse - affected_mob.adjustFireLoss(4.5 * REM * seconds_per_tick, FALSE, FALSE, BODYTYPE_ORGANIC) // it's going to be healing either 4 or 0.5 - . = TRUE - ..() + if(affected_mob.adjustFireLoss(4.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_biotype)) // it's going to be healing either 4 or 0.5 + return UPDATE_MOB_HEALTH /datum/reagent/medicine/salglu_solution name = "Saline-Glucose Solution" @@ -312,6 +300,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/salglu_solution/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update if(last_added) affected_mob.blood_volume -= last_added last_added = 0 @@ -321,25 +311,29 @@ last_added = new_blood_level - affected_mob.blood_volume affected_mob.blood_volume = new_blood_level + (extra_regen * REM * seconds_per_tick) if(SPT_PROB(18, seconds_per_tick)) - affected_mob.adjustBruteLoss(-0.5, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-0.5, FALSE, required_bodytype = affected_bodytype) - . = TRUE - ..() + need_mob_update = affected_mob.adjustBruteLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_biotype) + need_mob_update += affected_mob.adjustFireLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/salglu_solution/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update if(SPT_PROB(1.5, seconds_per_tick)) - to_chat(affected_mob, span_warning("You feel salty.")) - holder.add_reagent(/datum/reagent/consumable/salt, 1) - holder.remove_reagent(/datum/reagent/medicine/salglu_solution, 0.5) + if(holder) + to_chat(affected_mob, span_warning("You feel salty.")) + holder.add_reagent(/datum/reagent/consumable/salt, 1) + holder.remove_reagent(/datum/reagent/medicine/salglu_solution, 0.5) else if(SPT_PROB(1.5, seconds_per_tick)) - to_chat(affected_mob, span_warning("You feel sweet.")) - holder.add_reagent(/datum/reagent/consumable/sugar, 1) - holder.remove_reagent(/datum/reagent/medicine/salglu_solution, 0.5) + if(holder) + to_chat(affected_mob, span_warning("You feel sweet.")) + holder.add_reagent(/datum/reagent/consumable/sugar, 1) + holder.remove_reagent(/datum/reagent/medicine/salglu_solution, 0.5) if(SPT_PROB(18, seconds_per_tick)) - affected_mob.adjustBruteLoss(0.5, FALSE, FALSE, BODYTYPE_ORGANIC) - affected_mob.adjustFireLoss(0.5, FALSE, FALSE, BODYTYPE_ORGANIC) - . = TRUE - ..() + need_mob_update = affected_mob.adjustBruteLoss(0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_biotype) + need_mob_update += affected_mob.adjustFireLoss(0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/mine_salve name = "Miner's Salve" @@ -349,12 +343,15 @@ metabolization_rate = 0.4 * REAGENTS_METABOLISM ph = 2.6 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_AFFECTS_WOUNDS + metabolized_traits = list(TRAIT_ANALGESIA) /datum/reagent/medicine/mine_salve/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustBruteLoss(-0.25 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-0.25 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - ..() - return TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(-0.25 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-0.25 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/mine_salve/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE) . = ..() @@ -374,13 +371,13 @@ if(show_message) to_chat(exposed_carbon, span_danger("You feel your injuries fade away to nothing!") ) -/datum/reagent/medicine/mine_salve/on_mob_metabolize(mob/living/metabolizer) +/datum/reagent/medicine/mine_salve/on_mob_metabolize(mob/living/affected_mob) . = ..() - metabolizer.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + affected_mob.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) -/datum/reagent/medicine/mine_salve/on_mob_end_metabolize(mob/living/metabolizer) +/datum/reagent/medicine/mine_salve/on_mob_end_metabolize(mob/living/affected_mob) . = ..() - metabolizer.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + affected_mob.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) /datum/reagent/medicine/mine_salve/on_burn_wound_processing(datum/wound/burn/flesh/burn_wound) burn_wound.sanitization += 0.3 @@ -398,20 +395,24 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/omnizine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustToxLoss(-healing * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustOxyLoss(-healing * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustBruteLoss(-healing * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-healing * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - ..() - . = TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustToxLoss(-healing * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustOxyLoss(-healing * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustBruteLoss(-healing * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-healing * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/omnizine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustToxLoss(1.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustOxyLoss(1.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustBruteLoss(1.5 * REM * seconds_per_tick, FALSE, FALSE, BODYTYPE_ORGANIC) - affected_mob.adjustFireLoss(1.5 * REM * seconds_per_tick, FALSE, FALSE, BODYTYPE_ORGANIC) - ..() - . = TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustToxLoss(1.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustOxyLoss(1.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustBruteLoss(1.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(1.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/omnizine/protozine name = "Protozine" @@ -433,21 +434,21 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/calomel/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() for(var/datum/reagent/target_reagent in affected_mob.reagents.reagent_list) if(istype(target_reagent, /datum/reagent/medicine/calomel)) continue affected_mob.reagents.remove_reagent(target_reagent.type, 3 * REM * seconds_per_tick) var/toxin_amount = round(affected_mob.health / 40, 0.1) - affected_mob.adjustToxLoss(toxin_amount * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - return TRUE + if(affected_mob.adjustToxLoss(toxin_amount * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/calomel/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() for(var/datum/reagent/medicine/calomel/target_reagent in affected_mob.reagents.reagent_list) affected_mob.reagents.remove_reagent(target_reagent.type, 2 * REM * seconds_per_tick) - affected_mob.adjustToxLoss(2.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - return TRUE + if(affected_mob.adjustToxLoss(2.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/ammoniated_mercury name = "Ammoniated Mercury" @@ -461,24 +462,26 @@ overdose_threshold = 10 ph = 7 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + inverse_chem_val = 0.50 + inverse_chem = /datum/reagent/inverse/ammoniated_mercury /datum/reagent/medicine/ammoniated_mercury/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/toxin_chem_amount = 0 for(var/datum/reagent/toxin/target_reagent in affected_mob.reagents.reagent_list) toxin_chem_amount += 1 affected_mob.reagents.remove_reagent(target_reagent.type, 5 * REM * seconds_per_tick) var/toxin_amount = round(affected_mob.getBruteLoss() / 15, 0.1) + round(affected_mob.getFireLoss() / 30, 0.1) - 3 - affected_mob.adjustToxLoss(toxin_amount * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + if(affected_mob.adjustToxLoss(toxin_amount * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH if(toxin_chem_amount == 0) for(var/datum/reagent/medicine/ammoniated_mercury/target_reagent in affected_mob.reagents.reagent_list) affected_mob.reagents.remove_reagent(target_reagent.type, 1 * REM * seconds_per_tick) - ..() - return TRUE /datum/reagent/medicine/ammoniated_mercury/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustToxLoss(3 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - return TRUE + . = ..() + if(affected_mob.adjustToxLoss(3 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/potass_iodide name = "Potassium Iodide" @@ -488,21 +491,13 @@ metabolization_rate = 2 * REAGENTS_METABOLISM ph = 12 //It's a reducing agent chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/medicine/potass_iodide/on_mob_metabolize(mob/living/affected_mob) - . = ..() - ADD_TRAIT(affected_mob, TRAIT_HALT_RADIATION_EFFECTS, "[type]") - -/datum/reagent/medicine/potass_iodide/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_HALT_RADIATION_EFFECTS, "[type]") - return ..() + metabolized_traits = list(TRAIT_HALT_RADIATION_EFFECTS) /datum/reagent/medicine/potass_iodide/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if (HAS_TRAIT(affected_mob, TRAIT_IRRADIATED)) - affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, required_biotype = affected_biotype) - - ..() - return TRUE + . = ..() + if(HAS_TRAIT(affected_mob, TRAIT_IRRADIATED)) + if(affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/pen_acid name = "Pentetic Acid" @@ -512,22 +507,17 @@ metabolization_rate = 0.5 * REAGENTS_METABOLISM ph = 1 //One of the best buffers, NEVERMIND! chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/medicine/pen_acid/on_mob_metabolize(mob/living/affected_mob) - . = ..() - ADD_TRAIT(affected_mob, TRAIT_HALT_RADIATION_EFFECTS, "[type]") - -/datum/reagent/medicine/pen_acid/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_HALT_RADIATION_EFFECTS, "[type]") - return ..() + inverse_chem_val = 0.4 + inverse_chem = /datum/reagent/inverse/pen_acid + metabolized_traits = list(TRAIT_HALT_RADIATION_EFFECTS) /datum/reagent/medicine/pen_acid/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustToxLoss(-2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + . = ..() + if(affected_mob.adjustToxLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH for(var/datum/reagent/R in affected_mob.reagents.reagent_list) if(R != src) affected_mob.reagents.remove_reagent(R.type, 2 * REM * seconds_per_tick) - ..() - . = TRUE /datum/reagent/medicine/sal_acid name = "Salicylic Acid" @@ -538,20 +528,24 @@ overdose_threshold = 25 ph = 2.1 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + inverse_chem_val = 0.3 + inverse_chem = /datum/reagent/inverse/sal_acid /datum/reagent/medicine/sal_acid/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update if(affected_mob.getBruteLoss() > 25) - affected_mob.adjustBruteLoss(-4 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) + need_mob_update = affected_mob.adjustBruteLoss(-4 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) else - affected_mob.adjustBruteLoss(-0.5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - ..() - . = TRUE + need_mob_update = affected_mob.adjustBruteLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/sal_acid/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.getBruteLoss()) //It only makes existing bruises worse - affected_mob.adjustBruteLoss(4.5 * REM * seconds_per_tick, FALSE, FALSE, BODYTYPE_ORGANIC) // it's going to be healing either 4 or 0.5 - . = TRUE - ..() + if(affected_mob.adjustBruteLoss(4.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC)) // it's going to be healing either 4 or 0.5 + return UPDATE_MOB_HEALTH /datum/reagent/medicine/salbutamol name = "Salbutamol" @@ -561,16 +555,21 @@ metabolization_rate = 0.25 * REAGENTS_METABOLISM ph = 2 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + inverse_chem_val = 0.25 + inverse_chem = /datum/reagent/inverse/salbutamol /datum/reagent/medicine/salbutamol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOxyLoss(-3 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOxyLoss(-3 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) if(affected_mob.losebreath >= 4) var/obj/item/organ/internal/lungs/affected_lungs = affected_mob.get_organ_slot(ORGAN_SLOT_LUNGS) var/our_respiration_type = affected_lungs ? affected_lungs.respiration_type : affected_mob.mob_respiration_type // use lungs' respiration type or mob_respiration_type if no lungs if(our_respiration_type & affected_respiration_type) affected_mob.losebreath -= 2 * REM * seconds_per_tick - ..() - . = TRUE + need_mob_update = TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/ephedrine name = "Ephedrine" @@ -585,30 +584,30 @@ addiction_types = list(/datum/addiction/stimulants = 4) //1.6 per 2 seconds inverse_chem = /datum/reagent/inverse/corazargh inverse_chem_val = 0.4 + metabolized_traits = list(TRAIT_BATON_RESISTANCE) /datum/reagent/medicine/ephedrine/on_mob_metabolize(mob/living/affected_mob) - ..() + . = ..() affected_mob.add_movespeed_modifier(/datum/movespeed_modifier/reagent/ephedrine) - ADD_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type) /datum/reagent/medicine/ephedrine/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/ephedrine) - REMOVE_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type) - ..() /datum/reagent/medicine/ephedrine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(SPT_PROB(10 * (1-creation_purity), seconds_per_tick) && iscarbon(affected_mob)) + . = ..() + if(SPT_PROB(10 * (1.5-creation_purity), seconds_per_tick) && iscarbon(affected_mob)) var/obj/item/I = affected_mob.get_active_held_item() if(I && affected_mob.dropItemToGround(I)) to_chat(affected_mob, span_notice("Your hands spaz out and you drop what you were holding!")) affected_mob.set_jitter_if_lower(20 SECONDS) affected_mob.AdjustAllImmobility(-20 * REM * seconds_per_tick * normalise_creation_purity()) - affected_mob.adjustStaminaLoss(-1 * REM * seconds_per_tick * normalise_creation_purity(), FALSE) - ..() - return TRUE + affected_mob.adjustStaminaLoss(-1 * REM * seconds_per_tick * normalise_creation_purity(), updating_stamina = FALSE) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/ephedrine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(1 * (1 + (1-normalise_creation_purity())), seconds_per_tick) && iscarbon(affected_mob)) var/datum/disease/D = new /datum/disease/heart_failure affected_mob.ForceContractDisease(D) @@ -619,10 +618,9 @@ to_chat(affected_mob, span_notice("[pick("Your head pounds.", "You feel a tight pain in your chest.", "You find it hard to stay still.", "You feel your heart practically beating out of your chest.")]")) if(SPT_PROB(18 * (1 + (1-normalise_creation_purity())), seconds_per_tick)) - affected_mob.adjustToxLoss(1, FALSE, required_biotype = affected_biotype) + affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) affected_mob.losebreath++ - . = TRUE - return TRUE + return UPDATE_MOB_HEALTH /datum/reagent/medicine/diphenhydramine name = "Diphenhydramine" @@ -634,11 +632,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/diphenhydramine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(5, seconds_per_tick)) affected_mob.adjust_drowsiness(2 SECONDS) affected_mob.adjust_jitter(-2 SECONDS * REM * seconds_per_tick) holder.remove_reagent(/datum/reagent/toxin/histamine, 3 * REM * seconds_per_tick) - ..() /datum/reagent/medicine/morphine name = "Morphine" @@ -650,34 +648,34 @@ ph = 8.96 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED addiction_types = list(/datum/addiction/opioids = 10) + metabolized_traits = list(TRAIT_ANALGESIA) /datum/reagent/medicine/morphine/on_mob_metabolize(mob/living/affected_mob) - ..() + . = ..() affected_mob.add_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown) /datum/reagent/medicine/morphine/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.remove_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown) - ..() /datum/reagent/medicine/morphine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(current_cycle >= 5) + . = ..() + if(current_cycle > 5) affected_mob.add_mood_event("numb", /datum/mood_event/narcotic_medium, name) switch(current_cycle) - if(11) + if(12) to_chat(affected_mob, span_warning("You start to feel tired...") ) - if(12 to 24) + if(13 to 25) affected_mob.adjust_drowsiness(2 SECONDS * REM * seconds_per_tick) - if(24 to INFINITY) + if(25 to INFINITY) affected_mob.Sleeping(40 * REM * seconds_per_tick) - . = TRUE - ..() /datum/reagent/medicine/morphine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(18, seconds_per_tick)) affected_mob.drop_all_held_items() affected_mob.set_dizzy_if_lower(4 SECONDS) affected_mob.set_jitter_if_lower(4 SECONDS) - ..() /datum/reagent/medicine/oculine @@ -686,7 +684,7 @@ reagent_state = LIQUID color = "#404040" //oculine is dark grey, inacusiate is light grey metabolization_rate = 0.25 * REAGENTS_METABOLISM - taste_description = "dull toxin" + taste_description = "earthy bitterness" purity = REAGENT_STANDARD_PURITY ph = 10 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED @@ -729,33 +727,31 @@ restore_eyesight(prev_affected_mob, eyes) /datum/reagent/medicine/oculine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/normalized_purity = normalise_creation_purity() affected_mob.adjust_temp_blindness(-4 SECONDS * REM * seconds_per_tick * normalized_purity) affected_mob.adjust_eye_blur(-4 SECONDS * REM * seconds_per_tick * normalized_purity) var/obj/item/organ/internal/eyes/eyes = affected_mob.get_organ_slot(ORGAN_SLOT_EYES) - if(isnull(eyes)) - return ..() - - // Healing eye damage will cure nearsightedness and blindness from ... eye damage - eyes.apply_organ_damage(-2 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags) - // If our eyes are seriously damaged, we have a probability of causing eye blur while healing depending on purity - if(eyes.damaged && IS_ORGANIC_ORGAN(eyes) && SPT_PROB(16 - min(normalized_purity * 6, 12), seconds_per_tick)) - // While healing, gives some eye blur - if(affected_mob.is_blind_from(EYE_DAMAGE)) - to_chat(affected_mob, span_warning("Your vision slowly returns...")) - affected_mob.adjust_eye_blur(20 SECONDS) - else if(affected_mob.is_nearsighted_from(EYE_DAMAGE)) - to_chat(affected_mob, span_warning("The blackness in your peripheral vision begins to fade.")) - affected_mob.adjust_eye_blur(5 SECONDS) - - return ..() || TRUE + if(eyes) + // Healing eye damage will cure nearsightedness and blindness from ... eye damage + if(eyes.apply_organ_damage(-2 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags)) + . = UPDATE_MOB_HEALTH + // If our eyes are seriously damaged, we have a probability of causing eye blur while healing depending on purity + if(eyes.damaged && IS_ORGANIC_ORGAN(eyes) && SPT_PROB(16 - min(normalized_purity * 6, 12), seconds_per_tick)) + // While healing, gives some eye blur + if(affected_mob.is_blind_from(EYE_DAMAGE)) + to_chat(affected_mob, span_warning("Your vision slowly returns...")) + affected_mob.adjust_eye_blur(20 SECONDS) + else if(affected_mob.is_nearsighted_from(EYE_DAMAGE)) + to_chat(affected_mob, span_warning("The blackness in your peripheral vision begins to fade.")) + affected_mob.adjust_eye_blur(5 SECONDS) /datum/reagent/medicine/oculine/on_mob_delete(mob/living/affected_mob) + . = ..() var/obj/item/organ/internal/eyes/eyes = affected_mob.get_organ_slot(ORGAN_SLOT_EYES) if(!eyes) return restore_eyesight(affected_mob, eyes) - ..() /datum/reagent/medicine/inacusiate name = "Inacusiate" @@ -783,12 +779,12 @@ message = composer.compose_message(affected_mob, message_language, message, null, spans, message_mods) /datum/reagent/medicine/inacusiate/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/obj/item/organ/internal/ears/ears = affected_mob.get_organ_slot(ORGAN_SLOT_EARS) if(!ears) - return ..() + return ears.adjustEarDamage(-4 * REM * seconds_per_tick * normalise_creation_purity(), -4 * REM * seconds_per_tick * normalise_creation_purity()) - ..() - return TRUE + return UPDATE_MOB_HEALTH /datum/reagent/medicine/inacusiate/on_mob_delete(mob/living/affected_mob) . = ..() @@ -803,22 +799,20 @@ overdose_threshold = 35 ph = 12 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/medicine/atropine/on_mob_add(mob/living/affected_mob) - . = ..() - ADD_TRAIT(affected_mob, TRAIT_PREVENT_IMPLANT_AUTO_EXPLOSION, "[type]") - -/datum/reagent/medicine/atropine/on_mob_delete(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_PREVENT_IMPLANT_AUTO_EXPLOSION, "[type]") - return ..() + inverse_chem_val = 0.35 + inverse_chem = /datum/reagent/inverse/atropine + added_traits = list(TRAIT_PREVENT_IMPLANT_AUTO_EXPLOSION) /datum/reagent/medicine/atropine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.health <= affected_mob.crit_threshold) - affected_mob.adjustToxLoss(-2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustBruteLoss(-2* REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-2 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustOxyLoss(-5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - . = TRUE + var/need_mob_update + need_mob_update = affected_mob.adjustToxLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustBruteLoss(-2* REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustOxyLoss(-5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + if(need_mob_update) + . = UPDATE_MOB_HEALTH var/obj/item/organ/internal/lungs/affected_lungs = affected_mob.get_organ_slot(ORGAN_SLOT_LUNGS) var/our_respiration_type = affected_lungs ? affected_lungs.respiration_type : affected_mob.mob_respiration_type if(our_respiration_type & affected_respiration_type) @@ -826,14 +820,13 @@ if(SPT_PROB(10, seconds_per_tick)) affected_mob.set_dizzy_if_lower(10 SECONDS) affected_mob.set_jitter_if_lower(10 SECONDS) - ..() /datum/reagent/medicine/atropine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustToxLoss(0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - . = TRUE + . = ..() + if(affected_mob.adjustToxLoss(0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH affected_mob.set_dizzy_if_lower(2 SECONDS * REM * seconds_per_tick) affected_mob.set_jitter_if_lower(2 SECONDS * REM * seconds_per_tick) - ..() /datum/reagent/medicine/epinephrine name = "Epinephrine" @@ -844,51 +837,56 @@ overdose_threshold = 30 ph = 10.2 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/medicine/epinephrine/on_mob_metabolize(mob/living/carbon/affected_mob) - ..() - ADD_TRAIT(affected_mob, TRAIT_NOCRITDAMAGE, type) - -/datum/reagent/medicine/epinephrine/on_mob_end_metabolize(mob/living/carbon/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_NOCRITDAMAGE, type) - ..() + metabolized_traits = list(TRAIT_NOCRITDAMAGE) /datum/reagent/medicine/epinephrine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - . = TRUE + . = ..() if(holder.has_reagent(/datum/reagent/toxin/lexorin)) - holder.remove_reagent(/datum/reagent/toxin/lexorin, 2 * REM * seconds_per_tick) - holder.remove_reagent(/datum/reagent/medicine/epinephrine, 1 * REM * seconds_per_tick) if(SPT_PROB(10, seconds_per_tick)) holder.add_reagent(/datum/reagent/toxin/histamine, 4) - ..() - return FALSE + return + + var/need_mob_update if(affected_mob.health <= affected_mob.crit_threshold) - affected_mob.adjustToxLoss(-0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustBruteLoss(-0.5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-0.5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustOxyLoss(-0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update = affected_mob.adjustToxLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustBruteLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustOxyLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) if(affected_mob.losebreath >= 4) var/obj/item/organ/internal/lungs/affected_lungs = affected_mob.get_organ_slot(ORGAN_SLOT_LUNGS) var/our_respiration_type = affected_lungs ? affected_lungs.respiration_type : affected_mob.mob_respiration_type if(our_respiration_type & affected_respiration_type) affected_mob.losebreath -= 2 * REM * seconds_per_tick + need_mob_update = TRUE if(affected_mob.losebreath < 0) affected_mob.losebreath = 0 - affected_mob.adjustStaminaLoss(-0.5 * REM * seconds_per_tick, 0) + need_mob_update = TRUE + need_mob_update += affected_mob.adjustStaminaLoss(-0.5 * REM * seconds_per_tick, updating_stamina = FALSE) if(SPT_PROB(10, seconds_per_tick)) affected_mob.AdjustAllImmobility(-20) + need_mob_update = TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH + +/datum/reagent/medicine/epinephrine/metabolize_reagent(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + if(holder.has_reagent(/datum/reagent/toxin/lexorin)) + holder.remove_reagent(/datum/reagent/toxin/lexorin, 2 * REM * seconds_per_tick) + holder.remove_reagent(/datum/reagent/medicine/epinephrine, 1 * REM * seconds_per_tick) return ..() /datum/reagent/medicine/epinephrine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(18, REM * seconds_per_tick)) - affected_mob.adjustStaminaLoss(2.5, 0) - affected_mob.adjustToxLoss(1, FALSE, required_biotype = affected_biotype) + var/need_mob_update + need_mob_update = affected_mob.adjustStaminaLoss(2.5 * REM * seconds_per_tick, updating_stamina = FALSE) + need_mob_update += affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) var/obj/item/organ/internal/lungs/affected_lungs = affected_mob.get_organ_slot(ORGAN_SLOT_LUNGS) var/our_respiration_type = affected_lungs ? affected_lungs.respiration_type : affected_mob.mob_respiration_type if(our_respiration_type & affected_respiration_type) affected_mob.losebreath++ - . = TRUE - ..() + need_mob_update = TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/strange_reagent name = "Strange Reagent" @@ -897,7 +895,6 @@ color = "#A0E85E" metabolization_rate = 1.25 * REAGENTS_METABOLISM taste_description = "magnets" - harmful = TRUE ph = 0.5 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /// The amount of damage a single unit of this will heal @@ -969,7 +966,7 @@ return exposed_mob.visible_message(span_warning("[exposed_mob]'s body starts convulsing!")) - exposed_mob.notify_ghost_cloning("Your body is being revived with Strange Reagent!") + exposed_mob.notify_revival("Your body is being revived with Strange Reagent!") exposed_mob.do_jitter_animation(10) // we factor in healing needed when determing if we do anything @@ -990,11 +987,13 @@ return ..() /datum/reagent/medicine/strange_reagent/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/damage_at_random = rand(0, 250)/100 //0 to 2.5 - affected_mob.adjustBruteLoss(damage_at_random * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(damage_at_random * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - ..() - return TRUE + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(damage_at_random * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(damage_at_random * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/mannitol name = "Mannitol" @@ -1007,25 +1006,19 @@ purity = REAGENT_STANDARD_PURITY inverse_chem = /datum/reagent/inverse inverse_chem_val = 0.45 + metabolized_traits = list(TRAIT_TUMOR_SUPPRESSED) //Having mannitol in you will pause the brain damage from brain tumor (so it heals an even 2 brain damage instead of 1.8) /datum/reagent/medicine/mannitol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -2 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags) - ..() - return TRUE - -//Having mannitol in you will pause the brain damage from brain tumor (so it heals an even 2 brain damage instead of 1.8) -/datum/reagent/medicine/mannitol/on_mob_metabolize(mob/living/carbon/affected_mob) - . = ..() - ADD_TRAIT(affected_mob, TRAIT_TUMOR_SUPPRESSED, TRAIT_GENERIC) - -/datum/reagent/medicine/mannitol/on_mob_end_metabolize(mob/living/carbon/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_TUMOR_SUPPRESSED, TRAIT_GENERIC) . = ..() + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -2 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/mannitol/overdose_start(mob/living/affected_mob) + . = ..() to_chat(affected_mob, span_notice("You suddenly feel E N L I G H T E N E D!")) /datum/reagent/medicine/mannitol/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(65, seconds_per_tick)) return var/list/tips @@ -1037,7 +1030,6 @@ tips = world.file2list("strings/chemistrytips.txt") var/message = pick(tips) send_tip_of_the_round(affected_mob, message) - return ..() /datum/reagent/medicine/neurine name = "Neurine" @@ -1047,12 +1039,12 @@ purity = REAGENT_STANDARD_PURITY inverse_chem_val = 0.5 inverse_chem = /datum/reagent/inverse/neurine + added_traits = list(TRAIT_ANTICONVULSANT) ///brain damage level when we first started taking the chem var/initial_bdamage = 200 /datum/reagent/medicine/neurine/on_mob_add(mob/living/affected_mob, amount) . = ..() - ADD_TRAIT(affected_mob, TRAIT_ANTICONVULSANT, name) if(!iscarbon(affected_mob)) return var/mob/living/carbon/affected_carbon = affected_mob @@ -1061,7 +1053,6 @@ /datum/reagent/medicine/neurine/on_mob_delete(mob/living/affected_mob) . = ..() - REMOVE_TRAIT(affected_mob, TRAIT_ANTICONVULSANT, name) if(!iscarbon(affected_mob)) return var/mob/living/carbon/affected_carbon = affected_mob @@ -1069,15 +1060,16 @@ affected_carbon.setOrganLoss(ORGAN_SLOT_BRAIN, initial_bdamage) /datum/reagent/medicine/neurine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(holder.has_reagent(/datum/reagent/consumable/ethanol/neurotoxin)) holder.remove_reagent(/datum/reagent/consumable/ethanol/neurotoxin, 5 * REM * seconds_per_tick * normalise_creation_purity()) if(SPT_PROB(8 * normalise_creation_purity(), seconds_per_tick)) affected_mob.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC) - ..() /datum/reagent/medicine/neurine/on_mob_dead(mob/living/carbon/affected_mob, seconds_per_tick) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags) - ..() + . = ..() + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/mutadone name = "Mutadone" @@ -1088,11 +1080,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/mutadone/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.remove_status_effect(/datum/status_effect/jitter) if(affected_mob.has_dna()) affected_mob.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA), TRUE) - if(!QDELETED(affected_mob)) //We were a monkey, now a human - ..() /datum/reagent/medicine/antihol name = "Antihol" @@ -1114,13 +1105,13 @@ ) /datum/reagent/medicine/antihol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() for(var/effect in status_effects_to_clear) affected_mob.remove_status_effect(effect) - affected_mob.reagents.remove_all_type(/datum/reagent/consumable/ethanol, 3 * REM * seconds_per_tick * normalise_creation_purity(), FALSE, TRUE) - affected_mob.adjustToxLoss(-0.2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + affected_mob.reagents.remove_reagent(/datum/reagent/consumable/ethanol, 3 * REM * seconds_per_tick * normalise_creation_purity(), include_subtypes = TRUE) + if(affected_mob.adjustToxLoss(-0.2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH affected_mob.adjust_drunk_effect(-10 * REM * seconds_per_tick * normalise_creation_purity()) - ..() - . = TRUE /datum/reagent/medicine/antihol/expose_mob(mob/living/carbon/exposed_carbon, methods=TOUCH, reac_volume) . = ..() @@ -1139,35 +1130,36 @@ ph = 8.7 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE addiction_types = list(/datum/addiction/stimulants = 4) //0.8 per 2 seconds + metabolized_traits = list(TRAIT_BATON_RESISTANCE, TRAIT_ANALGESIA) /datum/reagent/medicine/stimulants/on_mob_metabolize(mob/living/affected_mob) - ..() + . = ..() affected_mob.add_movespeed_modifier(/datum/movespeed_modifier/reagent/stimulants) - ADD_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type) /datum/reagent/medicine/stimulants/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/stimulants) - REMOVE_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type) - ..() /datum/reagent/medicine/stimulants/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.health < 50 && affected_mob.health > 0) - affected_mob.adjustOxyLoss(-1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustBruteLoss(-1 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-1 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) + var/need_mob_update + need_mob_update += affected_mob.adjustOxyLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustBruteLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + . = UPDATE_MOB_HEALTH affected_mob.AdjustAllImmobility(-60 * REM * seconds_per_tick) - affected_mob.adjustStaminaLoss(-5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - . = TRUE + affected_mob.adjustStaminaLoss(-5 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) /datum/reagent/medicine/stimulants/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(18, seconds_per_tick)) - affected_mob.adjustStaminaLoss(2.5, FALSE, required_biotype = affected_biotype) - affected_mob.adjustToxLoss(1, FALSE, required_biotype = affected_biotype) + affected_mob.adjustStaminaLoss(2.5, updating_stamina = FALSE, required_biotype = affected_biotype) + affected_mob.adjustToxLoss(1, updating_health = FALSE, required_biotype = affected_biotype) affected_mob.losebreath++ - . = TRUE - ..() + return UPDATE_MOB_HEALTH /datum/reagent/medicine/insulin name = "Insulin" @@ -1179,10 +1171,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/insulin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(affected_mob.AdjustSleeping(-20 * REM * seconds_per_tick)) - . = TRUE + . = ..() + affected_mob.AdjustSleeping(-20 * REM * seconds_per_tick) holder.remove_reagent(/datum/reagent/consumable/sugar, 3 * REM * seconds_per_tick) - ..() //Trek Chems, used primarily by medibots. Only heals a specific damage type, but is very efficient. @@ -1195,9 +1186,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/medicine/inaprovaline/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.losebreath >= 5) affected_mob.losebreath -= 5 * REM * seconds_per_tick - ..() + return UPDATE_MOB_HEALTH /datum/reagent/medicine/regen_jelly name = "Regenerative Jelly" @@ -1219,12 +1211,14 @@ exposed_human.set_haircolor(color, update = TRUE) /datum/reagent/medicine/regen_jelly/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustBruteLoss(-1.5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-1.5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustOxyLoss(-1.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustToxLoss(-1.5 * REM * seconds_per_tick, FALSE, TRUE, affected_biotype) //heals TOXINLOVERs - ..() - . = TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(-1.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-1.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustOxyLoss(-1.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustToxLoss(-1.5 * REM * seconds_per_tick, updating_health = FALSE, forced = TRUE, required_biotype = affected_biotype) //heals TOXINLOVERs + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/syndicate_nanites //Used exclusively by Syndicate medical cyborgs name = "Restorative Nanites" @@ -1236,21 +1230,21 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/medicine/syndicate_nanites/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustBruteLoss(-5 * REM * seconds_per_tick, FALSE) //A ton of healing - this is a 50 telecrystal investment. - affected_mob.adjustFireLoss(-5 * REM * seconds_per_tick, FALSE) - affected_mob.adjustOxyLoss(-15 * REM * seconds_per_tick, FALSE) - affected_mob.adjustToxLoss(-5 * REM * seconds_per_tick, FALSE) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -15 * REM * seconds_per_tick) - affected_mob.adjustCloneLoss(-3 * REM * seconds_per_tick, FALSE) - ..() - . = TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(-5 * REM * seconds_per_tick, updating_health = FALSE) //A ton of healing - this is a 50 telecrystal investment. + need_mob_update += affected_mob.adjustFireLoss(-5 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustOxyLoss(-15 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustToxLoss(-5 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -15 * REM * seconds_per_tick) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/syndicate_nanites/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) //wtb flavortext messages that hint that you're vomitting up robots + . = ..() if(SPT_PROB(13, seconds_per_tick)) - affected_mob.reagents.remove_reagent(type, metabolization_rate*15) // ~5 units at a rate of 0.4 but i wanted a nice number in code + affected_mob.reagents.remove_reagent(type, metabolization_rate * 15) // ~5 units at a rate of 0.4 but i wanted a nice number in code affected_mob.vomit(vomit_flags = VOMIT_CATEGORY_DEFAULT, vomit_type = /obj/effect/decal/cleanable/vomit/nanites, lost_nutrition = 20) // nanite safety protocols make your body expel them to prevent harmies - ..() - . = TRUE /datum/reagent/medicine/earthsblood //Created by ambrosia gaia plants name = "Earthsblood" @@ -1261,50 +1255,46 @@ ph = 11 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED addiction_types = list(/datum/addiction/hallucinogens = 14) + metabolized_traits = list(TRAIT_PACIFISM) /datum/reagent/medicine/earthsblood/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(current_cycle <= 25) //10u has to be processed before u get into THE FUN ZONE - affected_mob.adjustBruteLoss(-1 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-1 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustOxyLoss(-0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustToxLoss(-0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustCloneLoss(-0.1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustStaminaLoss(-0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1 * REM * seconds_per_tick, 150, affected_organ_flags) //This does, after all, come from ambrosia, and the most powerful ambrosia in existence, at that! + . = ..() + var/need_mob_update + if(current_cycle < 25) //10u has to be processed before u get into THE FUN ZONE + need_mob_update = affected_mob.adjustBruteLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustOxyLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustToxLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustStaminaLoss(-0.5 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1 * REM * seconds_per_tick, 150, affected_organ_flags) //This does, after all, come from ambrosia, and the most powerful ambrosia in existence, at that! else - affected_mob.adjustBruteLoss(-5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) //slow to start, but very quick healing once it gets going - affected_mob.adjustFireLoss(-5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustOxyLoss(-3 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustToxLoss(-3 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustCloneLoss(-1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - affected_mob.adjustStaminaLoss(-3 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + need_mob_update = affected_mob.adjustBruteLoss(-5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) //slow to start, but very quick healing once it gets going + need_mob_update += affected_mob.adjustFireLoss(-5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustOxyLoss(-3 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustToxLoss(-3 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustStaminaLoss(-3 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2 * REM * seconds_per_tick, 150, affected_organ_flags) affected_mob.adjust_jitter_up_to(6 SECONDS * REM * seconds_per_tick, 1 MINUTES) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2 * REM * seconds_per_tick, 150, affected_organ_flags) if(SPT_PROB(5, seconds_per_tick)) affected_mob.say(return_hippie_line(), forced = /datum/reagent/medicine/earthsblood) affected_mob.adjust_drugginess_up_to(20 SECONDS * REM * seconds_per_tick, 30 SECONDS * REM * seconds_per_tick) - ..() - . = TRUE - -/datum/reagent/medicine/earthsblood/on_mob_metabolize(mob/living/affected_mob) - ..() - ADD_TRAIT(affected_mob, TRAIT_PACIFISM, type) - -/datum/reagent/medicine/earthsblood/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_PACIFISM, type) - ..() + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/earthsblood/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_hallucinations_up_to(10 SECONDS * REM * seconds_per_tick, 120 SECONDS) - if(current_cycle > 25) - affected_mob.adjustToxLoss(4 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - if(current_cycle > 100) //podpeople get out reeeeeeeeeeeeeeeeeeeee - affected_mob.adjustToxLoss(6 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + var/need_mob_update + if(current_cycle > 26) + need_mob_update = affected_mob.adjustToxLoss(4 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + if(current_cycle > 101) //podpeople get out reeeeeeeeeeeeeeeeeeeee + need_mob_update += affected_mob.adjustToxLoss(6 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) if(iscarbon(affected_mob)) var/mob/living/carbon/hippie = affected_mob hippie.gain_trauma(/datum/brain_trauma/severe/pacifism) - ..() - . = TRUE + + if(need_mob_update) + return UPDATE_MOB_HEALTH /// Returns a hippie-esque string for the person affected by the reagent to say. /datum/reagent/medicine/earthsblood/proc/return_hippie_line() @@ -1327,9 +1317,9 @@ metabolization_rate = 0.4 * REAGENTS_METABOLISM ph = 4.3 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - harmful = TRUE /datum/reagent/medicine/haloperidol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() for(var/datum/reagent/drug/R in affected_mob.reagents.reagent_list) affected_mob.reagents.remove_reagent(R.type, 5 * REM * seconds_per_tick) affected_mob.adjust_drowsiness(4 SECONDS * REM * seconds_per_tick) @@ -1340,11 +1330,12 @@ if (affected_mob.get_timed_status_effect_duration(/datum/status_effect/hallucination) >= 10 SECONDS) affected_mob.adjust_hallucinations(-10 SECONDS * REM * seconds_per_tick) + var/need_mob_update = FALSE if(SPT_PROB(10, seconds_per_tick)) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 50, affected_organ_flags) - affected_mob.adjustStaminaLoss(2.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - return TRUE + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 50, affected_organ_flags) + need_mob_update += affected_mob.adjustStaminaLoss(2.5 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH //used for changeling's adrenaline power /datum/reagent/medicine/changelingadrenaline @@ -1355,29 +1346,29 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/medicine/changelingadrenaline/on_mob_life(mob/living/carbon/metabolizer, seconds_per_tick, times_fired) - ..() + . = ..() metabolizer.AdjustAllImmobility(-20 * REM * seconds_per_tick) - metabolizer.adjustStaminaLoss(-10 * REM * seconds_per_tick, 0) + if(metabolizer.adjustStaminaLoss(-10 * REM * seconds_per_tick, updating_stamina = FALSE)) + . = UPDATE_MOB_HEALTH metabolizer.set_jitter_if_lower(20 SECONDS * REM * seconds_per_tick) metabolizer.set_dizzy_if_lower(20 SECONDS * REM * seconds_per_tick) - return TRUE /datum/reagent/medicine/changelingadrenaline/on_mob_metabolize(mob/living/affected_mob) - ..() + . = ..() affected_mob.add_traits(list(TRAIT_SLEEPIMMUNE, TRAIT_BATON_RESISTANCE), type) affected_mob.add_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown) /datum/reagent/medicine/changelingadrenaline/on_mob_end_metabolize(mob/living/affected_mob) - ..() + . = ..() affected_mob.remove_traits(list(TRAIT_SLEEPIMMUNE, TRAIT_BATON_RESISTANCE), type) affected_mob.remove_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown) affected_mob.remove_status_effect(/datum/status_effect/dizziness) affected_mob.remove_status_effect(/datum/status_effect/jitter) /datum/reagent/medicine/changelingadrenaline/overdose_process(mob/living/metabolizer, seconds_per_tick, times_fired) - metabolizer.adjustToxLoss(1 * REM * seconds_per_tick, FALSE) - ..() - return TRUE + . = ..() + if(metabolizer.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/changelinghaste name = "Changeling Haste" @@ -1387,17 +1378,17 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/medicine/changelinghaste/on_mob_metabolize(mob/living/affected_mob) - ..() + . = ..() affected_mob.add_movespeed_modifier(/datum/movespeed_modifier/reagent/changelinghaste) /datum/reagent/medicine/changelinghaste/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/changelinghaste) - ..() /datum/reagent/medicine/changelinghaste/on_mob_life(mob/living/carbon/metabolizer, seconds_per_tick, times_fired) - metabolizer.adjustToxLoss(2 * REM * seconds_per_tick, FALSE) - ..() - return TRUE + . = ..() + if(metabolizer.adjustToxLoss(2 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/higadrite name = "Higadrite" @@ -1405,14 +1396,7 @@ color = "#FF3542" self_consuming = TRUE chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/medicine/higadrite/on_mob_metabolize(mob/living/affected_mob) - . = ..() - ADD_TRAIT(affected_mob, TRAIT_STABLELIVER, type) - -/datum/reagent/medicine/higadrite/on_mob_end_metabolize(mob/living/affected_mob) - ..() - REMOVE_TRAIT(affected_mob, TRAIT_STABLELIVER, type) + metabolized_traits = list(TRAIT_STABLELIVER) /datum/reagent/medicine/cordiolis_hepatico name = "Cordiolis Hepatico" @@ -1422,17 +1406,18 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/medicine/cordiolis_hepatico/on_mob_add(mob/living/affected_mob) - ..() + . = ..() affected_mob.add_traits(list(TRAIT_STABLELIVER, TRAIT_STABLEHEART), type) /datum/reagent/medicine/cordiolis_hepatico/on_mob_end_metabolize(mob/living/affected_mob) - ..() + . = ..() affected_mob.remove_traits(list(TRAIT_STABLELIVER, TRAIT_STABLEHEART), type) /datum/reagent/medicine/muscle_stimulant name = "Muscle Stimulant" description = "A potent chemical that allows someone under its influence to be at full physical ability even when under massive amounts of pain." chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE + metabolized_traits = list(TRAIT_ANALGESIA) /datum/reagent/medicine/muscle_stimulant/on_mob_metabolize(mob/living/affected_mob) . = ..() @@ -1453,31 +1438,28 @@ var/overdose_progress = 0 // to track overdose progress ph = 7.89 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/medicine/modafinil/on_mob_metabolize(mob/living/affected_mob) - ADD_TRAIT(affected_mob, TRAIT_SLEEPIMMUNE, type) - ..() - -/datum/reagent/medicine/modafinil/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_SLEEPIMMUNE, type) - ..() + metabolized_traits = list(TRAIT_SLEEPIMMUNE) /datum/reagent/medicine/modafinil/on_mob_life(mob/living/carbon/metabolizer, seconds_per_tick, times_fired) - if(!overdosed) // We do not want any effects on OD - overdose_threshold = overdose_threshold + ((rand(-10, 10) / 10) * REM * seconds_per_tick) // for extra fun - metabolizer.AdjustAllImmobility(-5 * REM * seconds_per_tick) - metabolizer.adjustStaminaLoss(-0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - metabolizer.set_jitter_if_lower(1 SECONDS * REM * seconds_per_tick) - metabolization_rate = 0.005 * REAGENTS_METABOLISM * rand(5, 20) // randomizes metabolism between 0.02 and 0.08 per second - . = TRUE - ..() + . = ..() + if(overdosed) // We do not want any effects on OD + return + overdose_threshold = overdose_threshold + ((rand(-10, 10) / 10) * REM * seconds_per_tick) // for extra fun + metabolizer.AdjustAllImmobility(-5 * REM * seconds_per_tick) + metabolizer.adjustStaminaLoss(-0.5 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + metabolizer.set_jitter_if_lower(1 SECONDS * REM * seconds_per_tick) + metabolization_rate = 0.005 * REAGENTS_METABOLISM * rand(5, 20) // randomizes metabolism between 0.02 and 0.08 per second + return UPDATE_MOB_HEALTH /datum/reagent/medicine/modafinil/overdose_start(mob/living/affected_mob) + . = ..() to_chat(affected_mob, span_userdanger("You feel awfully out of breath and jittery!")) metabolization_rate = 0.025 * REAGENTS_METABOLISM // sets metabolism to 0.005 per second on overdose /datum/reagent/medicine/modafinil/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() overdose_progress++ + var/need_mob_update switch(overdose_progress) if(1 to 40) affected_mob.adjust_jitter_up_to(2 SECONDS * REM * seconds_per_tick, 20 SECONDS) @@ -1485,29 +1467,31 @@ affected_mob.set_dizzy_if_lower(10 SECONDS * REM * seconds_per_tick) if(SPT_PROB(30, seconds_per_tick)) affected_mob.losebreath++ + need_mob_update = TRUE if(41 to 80) - affected_mob.adjustOxyLoss(0.1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustStaminaLoss(0.1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + need_mob_update = affected_mob.adjustOxyLoss(0.1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustStaminaLoss(0.1 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) affected_mob.adjust_jitter_up_to(2 SECONDS * REM * seconds_per_tick, 40 SECONDS) affected_mob.adjust_stutter_up_to(2 SECONDS * REM * seconds_per_tick, 40 SECONDS) affected_mob.set_dizzy_if_lower(20 SECONDS * REM * seconds_per_tick) if(SPT_PROB(30, seconds_per_tick)) affected_mob.losebreath++ + need_mob_update = TRUE if(SPT_PROB(10, seconds_per_tick)) to_chat(affected_mob, span_userdanger("You have a sudden fit!")) affected_mob.emote("moan") affected_mob.Paralyze(20) // you should be in a bad spot at this point unless epipen has been used if(81) to_chat(affected_mob, span_userdanger("You feel too exhausted to continue!")) // at this point you will eventually die unless you get charcoal - affected_mob.adjustOxyLoss(0.1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustStaminaLoss(0.1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) + need_mob_update = affected_mob.adjustOxyLoss(0.1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustStaminaLoss(0.1 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) if(82 to INFINITY) REMOVE_TRAIT(affected_mob, TRAIT_SLEEPIMMUNE, type) affected_mob.Sleeping(100 * REM * seconds_per_tick) - affected_mob.adjustOxyLoss(1.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustStaminaLoss(1.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - return TRUE + need_mob_update += affected_mob.adjustOxyLoss(1.5 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustStaminaLoss(1.5 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/psicodine name = "Psicodine" @@ -1518,30 +1502,22 @@ overdose_threshold = 30 ph = 9.12 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/medicine/psicodine/on_mob_metabolize(mob/living/affected_mob) - ..() - ADD_TRAIT(affected_mob, TRAIT_FEARLESS, type) - -/datum/reagent/medicine/psicodine/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_FEARLESS, type) - ..() + metabolized_traits = list(TRAIT_FEARLESS) /datum/reagent/medicine/psicodine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_jitter(-12 SECONDS * REM * seconds_per_tick) affected_mob.adjust_dizzy(-12 SECONDS * REM * seconds_per_tick) affected_mob.adjust_confusion(-6 SECONDS * REM * seconds_per_tick) affected_mob.disgust = max(affected_mob.disgust - (6 * REM * seconds_per_tick), 0) if(affected_mob.mob_mood != null && affected_mob.mob_mood.sanity <= SANITY_NEUTRAL) // only take effect if in negative sanity and then... affected_mob.mob_mood.set_sanity(min(affected_mob.mob_mood.sanity + (5 * REM * seconds_per_tick), SANITY_NEUTRAL)) // set minimum to prevent unwanted spiking over neutral - ..() - . = TRUE /datum/reagent/medicine/psicodine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_hallucinations_up_to(10 SECONDS * REM * seconds_per_tick, 120 SECONDS) - affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - . = TRUE + if(affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/metafactor name = "Mitogen Metabolism Factor" @@ -1555,12 +1531,13 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/metafactor/overdose_start(mob/living/carbon/affected_mob) + . = ..() metabolization_rate = 2 * REAGENTS_METABOLISM /datum/reagent/medicine/metafactor/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(13, seconds_per_tick)) affected_mob.vomit(VOMIT_CATEGORY_DEFAULT) - ..() /datum/reagent/medicine/silibinin name = "Silibinin" @@ -1571,9 +1548,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/silibinin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, -2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)//Add a chance to cure liver trauma once implemented. - ..() - . = TRUE + . = ..() + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, -2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) // Add a chance to cure liver trauma once implemented. + return UPDATE_MOB_HEALTH /datum/reagent/medicine/polypyr //This is intended to be an ingredient in advanced chems. name = "Polypyrylium Oligomers" @@ -1587,9 +1564,11 @@ /datum/reagent/medicine/polypyr/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) //I wanted a collection of small positive effects, this is as hard to obtain as coniine after all. . = ..() - affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, -0.25 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - affected_mob.adjustBruteLoss(-0.35 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - return TRUE + var/need_mob_update + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, -0.25 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + need_mob_update += affected_mob.adjustBruteLoss(-0.35 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/polypyr/expose_mob(mob/living/carbon/human/exposed_human, methods=TOUCH, reac_volume) . = ..() @@ -1600,9 +1579,9 @@ exposed_human.update_body_parts() /datum/reagent/medicine/polypyr/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - ..() - . = TRUE + . = ..() + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/granibitaluri name = "Granibitaluri" //achieve "GRANular" amounts of C2 @@ -1614,17 +1593,21 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/granibitaluri/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/healamount = max(0.5 - round(0.01 * (affected_mob.getBruteLoss() + affected_mob.getFireLoss()), 0.1), 0) //base of 0.5 healing per cycle and loses 0.1 healing for every 10 combined brute/burn damage you have - affected_mob.adjustBruteLoss(-healamount * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - affected_mob.adjustFireLoss(-healamount * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - ..() - . = TRUE + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(-healamount * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-healamount * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/granibitaluri/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) - . = TRUE - affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) - affected_mob.adjustToxLoss(0.2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) //Only really deadly if you eat over 100u - ..() + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.2 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + need_mob_update += affected_mob.adjustToxLoss(0.2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) //Only really deadly if you eat over 100u + if(need_mob_update) + return UPDATE_MOB_HEALTH // helps bleeding wounds clot faster /datum/reagent/medicine/coagulant @@ -1641,27 +1624,22 @@ /// For tracking when we tell the person we're no longer bleeding var/was_working chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + metabolized_traits = list(TRAIT_COAGULATING) /datum/reagent/medicine/coagulant/on_mob_metabolize(mob/living/affected_mob) - ADD_TRAIT(affected_mob, TRAIT_COAGULATING, /datum/reagent/medicine/coagulant) - + . = ..() if(ishuman(affected_mob)) var/mob/living/carbon/human/blood_boy = affected_mob blood_boy.physiology?.bleed_mod *= passive_bleed_modifier - return ..() - /datum/reagent/medicine/coagulant/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_COAGULATING, /datum/reagent/medicine/coagulant) - + . = ..() if(was_working) to_chat(affected_mob, span_warning("The medicine thickening your blood loses its effect!")) if(ishuman(affected_mob)) var/mob/living/carbon/human/blood_boy = affected_mob blood_boy.physiology?.bleed_mod /= passive_bleed_modifier - return ..() - /datum/reagent/medicine/coagulant/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() if(!affected_mob.blood_volume || !affected_mob.all_wounds) @@ -1690,19 +1668,21 @@ if(SPT_PROB(7.5, seconds_per_tick)) affected_mob.losebreath += rand(2, 4) - affected_mob.adjustOxyLoss(rand(1, 3), required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + affected_mob.adjustOxyLoss(rand(1, 3), updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) if(prob(30)) to_chat(affected_mob, span_danger("You can feel your blood clotting up in your veins!")) else if(prob(10)) to_chat(affected_mob, span_userdanger("You feel like your blood has stopped moving!")) - affected_mob.adjustOxyLoss(rand(3, 4), required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + affected_mob.adjustOxyLoss(rand(3, 4) * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) if(prob(50)) var/obj/item/organ/internal/lungs/our_lungs = affected_mob.get_organ_slot(ORGAN_SLOT_LUNGS) - our_lungs.apply_organ_damage(1) + our_lungs.apply_organ_damage(1 * REM * seconds_per_tick) else var/obj/item/organ/internal/heart/our_heart = affected_mob.get_organ_slot(ORGAN_SLOT_HEART) - our_heart.apply_organ_damage(1) + our_heart.apply_organ_damage(1 * REM * seconds_per_tick) + + return UPDATE_MOB_HEALTH // i googled "natural coagulant" and a couple of results came up for banana peels, so after precisely 30 more seconds of research, i now dub grinding banana peels good for your blood /datum/reagent/medicine/coagulant/banana_peel @@ -1745,11 +1725,11 @@ ph = 10.6 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED -/datum/reagent/medicine/ondansetron/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired) +/datum/reagent/medicine/ondansetron/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() if(SPT_PROB(8, seconds_per_tick)) - M.adjust_drowsiness(2 SECONDS * REM * seconds_per_tick) - if(SPT_PROB(15, seconds_per_tick) && !M.getStaminaLoss()) - M.adjustStaminaLoss(10) - . = TRUE - M.adjust_disgust(-10 * REM * seconds_per_tick) + affected_mob.adjust_drowsiness(2 SECONDS * REM * seconds_per_tick) + if(SPT_PROB(15, seconds_per_tick) && !affected_mob.getStaminaLoss()) + if(affected_mob.adjustStaminaLoss(10 * REM * seconds_per_tick, updating_stamina = FALSE)) + . = UPDATE_MOB_HEALTH + affected_mob.adjust_disgust(-10 * REM * seconds_per_tick) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 0c4b59a1870f2..25751c5903319 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -50,6 +50,14 @@ else if((methods & TOUCH) && (strain.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS)) exposed_mob.ContactContractDisease(strain) + if(data && data["resistances"]) + if(methods & (INGEST|INJECT)) //have to inject or ingest it. no curefoam/cheap curesprays + for(var/stuff in exposed_mob.diseases) + var/datum/disease/infection = stuff + if(infection.GetDiseaseID() in data["resistances"]) + if(!infection.bypasses_immunity) + infection.cure(add_resistance = FALSE) + if(iscarbon(exposed_mob)) var/mob/living/carbon/exposed_carbon = exposed_mob if(exposed_carbon.get_blood_id() == type && ((methods & INJECT) || ((methods & INGEST) && HAS_TRAIT(exposed_carbon, TRAIT_DRINKS_BLOOD)))) @@ -154,7 +162,7 @@ for(var/thing in exposed_mob.diseases) var/datum/disease/infection = thing if(infection.GetDiseaseID() in data) - infection.cure() + infection.cure(add_resistance = TRUE) LAZYOR(exposed_mob.disease_resistances, data) /datum/reagent/vaccine/on_merge(list/data) @@ -274,6 +282,7 @@ . = ..() if(affected_mob.blood_volume) affected_mob.blood_volume += 0.1 * REM * seconds_per_tick // water is good for you! + affected_mob.adjust_drunk_effect(-0.25 * REM * seconds_per_tick) // and even sobers you up slowly!! // For weird backwards situations where water manages to get added to trays nutrients, as opposed to being snowflaked away like usual. /datum/reagent/water/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) @@ -302,6 +311,8 @@ /datum/reagent/water/salt/expose_mob(mob/living/exposed_mob, methods, reac_volume) . = ..() + if(!iscarbon(exposed_mob)) + return var/mob/living/carbon/carbies = exposed_mob if(!(methods & (PATCH|TOUCH|VAPOR))) return @@ -334,8 +345,9 @@ color = "#E0E8EF" // rgb: 224, 232, 239 self_consuming = TRUE //divine intervention won't be limited by the lack of a liver ph = 7.5 //God is alkaline - chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS|REAGENT_UNAFFECTED_BY_METABOLISM // Operates at fixed metabolism for balancing memes. default_container = /obj/item/reagent_containers/cup/glass/bottle/holywater + metabolized_traits = list(TRAIT_HOLY) /datum/glass_style/drinking_glass/holywater required_drink_type = /datum/reagent/water/holywater @@ -343,62 +355,60 @@ desc = "A glass of holy water." icon_state = "glass_clear" +/datum/reagent/water/holywater/on_new(list/data) + // Tracks the total amount of deciseconds that the reagent has been metab'd for, for the purpose of deconversion + if(isnull(data)) + data = list("deciseconds_metabolized" = 0) + else if(isnull(data["deciseconds_metabolized"])) + data["deciseconds_metabolized"] = 0 + + return ..() + // Holy water. Unlike water, which is nuked, stays in and heals the plant a little with the power of the spirits. Also ALSO increases instability. /datum/reagent/water/holywater/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) mytray.adjust_waterlevel(round(volume)) mytray.adjust_plant_health(round(volume * 0.1)) mytray.myseed?.adjust_instability(round(volume * 0.15)) -/datum/reagent/water/holywater/on_mob_metabolize(mob/living/affected_mob) - ..() - ADD_TRAIT(affected_mob, TRAIT_HOLY, type) - /datum/reagent/water/holywater/on_mob_add(mob/living/affected_mob, amount) . = ..() - if(data) - data["misc"] = 0 - -/datum/reagent/water/holywater/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_HOLY, type) - ..() + if(IS_CULTIST(affected_mob)) + to_chat(affected_mob, span_userdanger("A vile holiness begins to spread its shining tendrils through your mind, purging the Geometer of Blood's influence!")) -/datum/reagent/water/holywater/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) +/datum/reagent/water/holywater/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - if(IS_CULTIST(exposed_mob)) - to_chat(exposed_mob, span_userdanger("A vile holiness begins to spread its shining tendrils through your mind, purging the Geometer of Blood's influence!")) -/datum/reagent/water/holywater/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(affected_mob.blood_volume) - affected_mob.blood_volume += 0.1 * REM * seconds_per_tick // water is good for you! - if(!data) - data = list("misc" = 0) + data["deciseconds_metabolized"] += (seconds_per_tick * 1 SECONDS * REM) + + affected_mob.adjust_jitter_up_to(4 SECONDS * REM * seconds_per_tick, 20 SECONDS) - data["misc"] += seconds_per_tick SECONDS * REM - affected_mob.adjust_jitter_up_to(4 SECONDS * seconds_per_tick, 20 SECONDS) if(IS_CULTIST(affected_mob)) for(var/datum/action/innate/cult/blood_magic/BM in affected_mob.actions) - to_chat(affected_mob, span_cultlarge("Your blood rites falter as holy water scours your body!")) + var/removed_any = FALSE for(var/datum/action/innate/cult/blood_spell/BS in BM.spells) + removed_any = TRUE qdel(BS) - if(data["misc"] >= (25 SECONDS)) // 10 units - affected_mob.adjust_stutter_up_to(4 SECONDS * seconds_per_tick, 20 SECONDS) + if(removed_any) + to_chat(affected_mob, span_cultlarge("Your blood rites falter as holy water scours your body!")) + + if(data["deciseconds_metabolized"] >= (25 SECONDS)) // 10 units + affected_mob.adjust_stutter_up_to(4 SECONDS * REM * seconds_per_tick, 20 SECONDS) affected_mob.set_dizzy_if_lower(10 SECONDS) if(IS_CULTIST(affected_mob) && SPT_PROB(10, seconds_per_tick)) affected_mob.say(pick("Av'te Nar'Sie","Pa'lid Mors","INO INO ORA ANA","SAT ANA!","Daim'niodeis Arc'iai Le'eones","R'ge Na'sie","Diabo us Vo'iscum","Eld' Mon Nobis"), forced = "holy water") if(prob(10)) affected_mob.visible_message(span_danger("[affected_mob] starts having a seizure!"), span_userdanger("You have a seizure!")) affected_mob.Unconscious(12 SECONDS) - to_chat(affected_mob, "[pick("Your blood is your bond - you are nothing without it", "Do not forget your place", \ - "All that power, and you still fail?", "If you cannot scour this poison, I shall scour your meager life!")].") - if(data["misc"] >= (1 MINUTES)) // 24 units + to_chat(affected_mob, span_cultlarge("[pick("Your blood is your bond - you are nothing without it", "Do not forget your place", \ + "All that power, and you still fail?", "If you cannot scour this poison, I shall scour your meager life!")].")) + + if(data["deciseconds_metabolized"] >= (1 MINUTES)) // 24 units if(IS_CULTIST(affected_mob)) affected_mob.mind.remove_antag_datum(/datum/antagonist/cult) - affected_mob.Unconscious(100) + affected_mob.Unconscious(10 SECONDS) affected_mob.remove_status_effect(/datum/status_effect/jitter) affected_mob.remove_status_effect(/datum/status_effect/speech/stutter) - holder.remove_reagent(type, volume) // maybe this is a little too perfect and a max() cap on the statuses would be better?? - return - holder.remove_reagent(type, 1 * REAGENTS_METABOLISM * seconds_per_tick) //fixed consumption to prevent balancing going out of whack + holder?.remove_reagent(type, volume) // maybe this is a little too perfect and a max() cap on the statuses would be better?? /datum/reagent/water/holywater/expose_turf(turf/exposed_turf, reac_volume) . = ..() @@ -464,25 +474,48 @@ ph = 6.5 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED +/datum/reagent/fuel/unholywater/on_mob_metabolize(mob/living/affected_mob) + . = ..() + if(IS_CULTIST(affected_mob)) + ADD_TRAIT(affected_mob, TRAIT_COAGULATING, type) + /datum/reagent/fuel/unholywater/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + + var/need_mob_update = FALSE if(IS_CULTIST(affected_mob)) affected_mob.adjust_drowsiness(-10 SECONDS * REM * seconds_per_tick) affected_mob.AdjustAllImmobility(-40 * REM * seconds_per_tick) - affected_mob.adjustStaminaLoss(-10 * REM * seconds_per_tick, 0) - affected_mob.adjustToxLoss(-2 * REM * seconds_per_tick, 0) - affected_mob.adjustOxyLoss(-2 * REM * seconds_per_tick, 0) - affected_mob.adjustBruteLoss(-2 * REM * seconds_per_tick, 0) - affected_mob.adjustFireLoss(-2 * REM * seconds_per_tick, 0) + need_mob_update += affected_mob.adjustStaminaLoss(-10 * REM * seconds_per_tick, updating_stamina = FALSE) + need_mob_update += affected_mob.adjustToxLoss(-2 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustOxyLoss(-2 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustBruteLoss(-2 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustFireLoss(-2 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update = TRUE if(ishuman(affected_mob) && affected_mob.blood_volume < BLOOD_VOLUME_NORMAL) affected_mob.blood_volume += 3 * REM * seconds_per_tick + + var/datum/wound/bloodiest_wound + + for(var/datum/wound/iter_wound as anything in affected_mob.all_wounds) + if(iter_wound.blood_flow && iter_wound.blood_flow > bloodiest_wound?.blood_flow) + bloodiest_wound = iter_wound + + if(bloodiest_wound) + bloodiest_wound.adjust_blood_flow(-2 * REM * seconds_per_tick) + else // Will deal about 90 damage when 50 units are thrown - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * REM * seconds_per_tick, 150) - affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, 0) - affected_mob.adjustFireLoss(1 * REM * seconds_per_tick, 0) - affected_mob.adjustOxyLoss(1 * REM * seconds_per_tick, 0) - affected_mob.adjustBruteLoss(1 * REM * seconds_per_tick, 0) - ..() - return TRUE + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * REM * seconds_per_tick, 150) + need_mob_update += affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustFireLoss(1 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustOxyLoss(1 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustBruteLoss(1 * REM * seconds_per_tick, updating_health = FALSE) + if(need_mob_update) + return UPDATE_MOB_HEALTH + +/datum/reagent/fuel/unholywater/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() + REMOVE_TRAIT(affected_mob, TRAIT_COAGULATING, type) //We don't cult check here because potentially our imbiber may no longer be a cultist for whatever reason! It doesn't purge holy water, after all! /datum/reagent/hellwater //if someone has this in their system they've really pissed off an eldrich god name = "Hell Water" @@ -492,13 +525,17 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/hellwater/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.set_fire_stacks(min(affected_mob.fire_stacks + (1.5 * seconds_per_tick), 5)) affected_mob.ignite_mob() //Only problem with igniting people is currently the commonly available fire suits make you immune to being on fire - affected_mob.adjustToxLoss(0.5*seconds_per_tick, 0) - affected_mob.adjustFireLoss(0.5*seconds_per_tick, 0) //Hence the other damages... ain't I a bastard? + var/need_mob_update + need_mob_update = affected_mob.adjustToxLoss(0.5*seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustFireLoss(0.5*seconds_per_tick, updating_health = FALSE) //Hence the other damages... ain't I a bastard? affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2.5*seconds_per_tick, 150) - holder.remove_reagent(type, 0.5*seconds_per_tick) - return TRUE + if(holder) + holder.remove_reagent(type, 0.5 * seconds_per_tick) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/medicine/omnizine/godblood name = "Godblood" @@ -580,38 +617,24 @@ exposed_human.skin_tone = "mixed3" //take current alien color and darken it slightly else if(HAS_TRAIT(exposed_human, TRAIT_MUTANT_COLORS) && !HAS_TRAIT(exposed_human, TRAIT_FIXED_MUTANT_COLORS)) - var/newcolor = "" - var/string = exposed_human.dna.features["mcolor"] - var/len = length(string) - var/char = "" - var/ascii = 0 - for(var/i=1, i <= len, i += length(char)) - char = string[i] - ascii = text2ascii(char) - switch(ascii) - if(48) - newcolor += "0" - if(49 to 57) - newcolor += ascii2text(ascii-1) //numbers 1 to 9 - if(97) - newcolor += "9" - if(98 to 102) - newcolor += ascii2text(ascii-1) //letters b to f lowercase - if(65) - newcolor += "9" - if(66 to 70) - newcolor += ascii2text(ascii+31) //letters B to F - translates to lowercase - else - break - if(ReadHSV(newcolor)[3] >= ReadHSV("#7F7F7F")[3]) - exposed_human.dna.features["mcolor"] = newcolor + var/list/existing_color = rgb2num(exposed_human.dna.features["mcolor"]) + var/list/darkened_color = list() + // Reduces each part of the color by 16 + for(var/channel in existing_color) + darkened_color += max(channel - 17, 0) + + var/new_color = rgb(darkened_color[1], darkened_color[2], darkened_color[3]) + var/list/new_hsv = rgb2hsv(new_color) + // Can't get too dark now + if(new_hsv[3] >= 50) + exposed_human.dna.features["mcolor"] = new_color exposed_human.update_body(is_creating = TRUE) if((methods & INGEST) && show_message) to_chat(exposed_mob, span_notice("That tasted horrible.")) - /datum/reagent/spraytan/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() metabolization_rate = 1 * REAGENTS_METABOLISM if(ishuman(affected_mob)) @@ -637,13 +660,18 @@ affected_mob.visible_message("[affected_mob] flexes [affected_mob.p_their()] arms.") if(SPT_PROB(5, seconds_per_tick)) affected_mob.say(pick("Shit was SO cash.", "You are everything bad in the world.", "What sports do you play, other than 'jack off to naked drawn Japanese people?'", "Don???t be a stranger. Just hit me with your best shot.", "My name is John and I hate every single one of you."), forced = /datum/reagent/spraytan) - ..() - return #define MUT_MSG_IMMEDIATE 1 #define MUT_MSG_EXTENDED 2 #define MUT_MSG_ABOUT2TURN 3 +/// the current_cycle threshold / iterations needed before one can transform +#define CYCLES_TO_TURN 20 +/// the cycle at which 'immediate' mutation text begins displaying +#define CYCLES_MSG_IMMEDIATE 6 +/// the cycle at which 'extended' mutation text begins displaying +#define CYCLES_MSG_EXTENDED 16 + /datum/reagent/mutationtoxin name = "Stable Mutation Toxin" description = "A humanizing toxin." @@ -656,21 +684,20 @@ "Your limbs begin to take on a different shape." = MUT_MSG_EXTENDED, "Your appendages begin morphing." = MUT_MSG_EXTENDED, "You feel as though you're about to change at any moment!" = MUT_MSG_ABOUT2TURN) - var/cycles_to_turn = 20 //the current_cycle threshold / iterations needed before one can transform /datum/reagent/mutationtoxin/on_mob_life(mob/living/carbon/human/affected_mob, seconds_per_tick, times_fired) - . = TRUE + . = ..() if(!istype(affected_mob)) return - if(!(affected_mob.dna?.species) || !(affected_mob.mob_biotypes & MOB_ORGANIC)) + if(!(affected_mob.dna?.species) || !(affected_mob.mob_biotypes & affected_biotype)) return if(SPT_PROB(5, seconds_per_tick)) var/list/pick_ur_fav = list() var/filter = NONE - if(current_cycle <= (cycles_to_turn*0.3)) + if(current_cycle <= CYCLES_MSG_IMMEDIATE) filter = MUT_MSG_IMMEDIATE - else if(current_cycle <= (cycles_to_turn*0.8)) + else if(current_cycle <= CYCLES_MSG_EXTENDED) filter = MUT_MSG_EXTENDED else filter = MUT_MSG_ABOUT2TURN @@ -680,13 +707,12 @@ pick_ur_fav += i to_chat(affected_mob, span_warning("[pick(pick_ur_fav)]")) - if(current_cycle >= cycles_to_turn) + if(current_cycle >= CYCLES_TO_TURN) var/datum/species/species_type = race affected_mob.set_species(species_type) holder.del_reagent(type) to_chat(affected_mob, span_warning("You've become \a [lowertext(initial(species_type.name))]!")) return - ..() /datum/reagent/mutationtoxin/classic //The one from plasma on green slimes name = "Mutation Toxin" @@ -748,13 +774,13 @@ var/species_type = pick(subtypesof(/datum/species/jelly)) affected_mob.set_species(species_type) holder.del_reagent(type) - return TRUE - if(current_cycle >= cycles_to_turn) //overwrite since we want subtypes of jelly + return UPDATE_MOB_HEALTH + if(current_cycle >= CYCLES_TO_TURN) //overwrite since we want subtypes of jelly var/datum/species/species_type = pick(subtypesof(race)) affected_mob.set_species(species_type) holder.del_reagent(type) to_chat(affected_mob, span_warning("You've become \a [initial(species_type.name)]!")) - return TRUE + return UPDATE_MOB_HEALTH return ..() /datum/reagent/mutationtoxin/golem @@ -827,6 +853,10 @@ #undef MUT_MSG_EXTENDED #undef MUT_MSG_ABOUT2TURN +#undef CYCLES_TO_TURN +#undef CYCLES_MSG_IMMEDIATE +#undef CYCLES_MSG_EXTENDED + /datum/reagent/mulligan name = "Mulligan Toxin" description = "This toxin will rapidly change the DNA of humanoid beings. Commonly used by Syndicate spies and assassins in need of an emergency ID change." @@ -836,7 +866,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/mulligan/on_mob_life(mob/living/carbon/human/affected_mob, seconds_per_tick, times_fired) - ..() + . = ..() if (!istype(affected_mob)) return to_chat(affected_mob, span_warning("You grit your teeth in pain as your body rapidly mutates!")) @@ -878,10 +908,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/serotrotium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(ishuman(affected_mob)) - if(SPT_PROB(3.5, seconds_per_tick)) - affected_mob.emote(pick("twitch","drool","moan","gasp")) - ..() + . = ..() + if(SPT_PROB(3.5, seconds_per_tick)) + affected_mob.emote(pick("twitch","drool","moan","gasp")) /datum/reagent/oxygen name = "Oxygen" @@ -956,13 +985,13 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/mercury/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && !isspaceturf(affected_mob.loc)) + . = ..() + if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && isturf(affected_mob.loc) && !isgroundlessturf(affected_mob.loc)) step(affected_mob, pick(GLOB.cardinals)) if(SPT_PROB(3.5, seconds_per_tick)) affected_mob.emote(pick("twitch","drool","moan")) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5*seconds_per_tick) - ..() - return TRUE + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5*seconds_per_tick)) + return UPDATE_MOB_HEALTH /datum/reagent/sulfur name = "Sulfur" @@ -1009,9 +1038,9 @@ /datum/reagent/chlorine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.take_bodypart_damage(0.5*REM*seconds_per_tick, 0) - . = TRUE - ..() + . = ..() + if(affected_mob.take_bodypart_damage(0.5*REM*seconds_per_tick, 0)) + return UPDATE_MOB_HEALTH /datum/reagent/fluorine name = "Fluorine" @@ -1030,9 +1059,9 @@ mytray.adjust_weedlevel(-rand(1, 4)) /datum/reagent/fluorine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustToxLoss(0.5*REM*seconds_per_tick, 0) - . = TRUE - ..() + . = ..() + if(affected_mob.adjustToxLoss(0.5*REM*seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/sodium name = "Sodium" @@ -1068,11 +1097,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/lithium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(!HAS_TRAIT(affected_mob, TRAIT_IMMOBILIZED) && !isspaceturf(affected_mob.loc) && isturf(affected_mob.loc)) + . = ..() + if(!HAS_TRAIT(affected_mob, TRAIT_IMMOBILIZED) && isturf(affected_mob.loc) && !isgroundlessturf(affected_mob.loc)) step(affected_mob, pick(GLOB.cardinals)) if(SPT_PROB(2.5, seconds_per_tick)) affected_mob.emote(pick("twitch","drool","moan")) - ..() /datum/reagent/glycerol name = "Glycerol" @@ -1112,9 +1141,9 @@ ph = 6 /datum/reagent/iron/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.blood_volume < BLOOD_VOLUME_NORMAL) affected_mob.blood_volume += 0.25 * seconds_per_tick - ..() /datum/reagent/gold name = "Gold" @@ -1148,9 +1177,9 @@ var/tox_damage = 0.5 /datum/reagent/uranium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustToxLoss(tox_damage * seconds_per_tick * REM) - ..() - return TRUE + . = ..() + if(affected_mob.adjustToxLoss(tox_damage * seconds_per_tick * REM, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/uranium/expose_turf(turf/exposed_turf, reac_volume) . = ..() @@ -1194,12 +1223,12 @@ do_teleport(exposed_mob, get_turf(exposed_mob), (reac_volume / 5), asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) //4 tiles per crystal /datum/reagent/bluespace/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(current_cycle > 10 && SPT_PROB(7.5, seconds_per_tick)) to_chat(affected_mob, span_warning("You feel unstable...")) affected_mob.set_jitter_if_lower(2 SECONDS) current_cycle = 1 addtimer(CALLBACK(affected_mob, TYPE_PROC_REF(/mob/living, bluespace_shuffle)), 30) - ..() /mob/living/proc/bluespace_shuffle() do_teleport(src, get_turf(src), 5, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) @@ -1246,9 +1275,9 @@ exposed_mob.adjust_fire_stacks(reac_volume / 10) /datum/reagent/fuel/on_mob_life(mob/living/carbon/victim, seconds_per_tick, times_fired) - victim.adjustToxLoss(0.5 * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - return TRUE + . = ..() + if(victim.adjustToxLoss(0.5 * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/fuel/expose_turf(turf/exposed_turf, reac_volume) . = ..() @@ -1315,11 +1344,13 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/space_cleaner/ez_clean/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustBruteLoss(1.665*seconds_per_tick) - affected_mob.adjustFireLoss(1.665*seconds_per_tick) - affected_mob.adjustToxLoss(1.665*seconds_per_tick) - ..() - return TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(1.665*seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustFireLoss(1.665*seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustToxLoss(1.665*seconds_per_tick, updating_health = FALSE) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/space_cleaner/ez_clean/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -1337,6 +1368,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/cryptobiolin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.set_dizzy_if_lower(2 SECONDS) // Cryptobiolin adjusts the mob's confusion down to 20 seconds if it's higher, @@ -1348,8 +1380,6 @@ else if(confusion_left > 20 SECONDS) affected_mob.set_confusion(20 SECONDS) - ..() - /datum/reagent/impedrezene name = "Impedrezene" description = "Impedrezene is a narcotic that impedes one's ability by slowing down the higher brain cell functions." @@ -1360,8 +1390,8 @@ addiction_types = list(/datum/addiction/opioids = 10) /datum/reagent/impedrezene/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_jitter(-5 SECONDS * seconds_per_tick) - . = FALSE if(SPT_PROB(55, seconds_per_tick)) affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2) . = TRUE @@ -1369,7 +1399,6 @@ affected_mob.adjust_drowsiness(6 SECONDS) if(SPT_PROB(5, seconds_per_tick)) affected_mob.emote("drool") - ..() /datum/reagent/cyborg_mutation_nanomachines name = "Nanomachines" @@ -1520,15 +1549,16 @@ exposed_mob.adjust_drowsiness(drowsiness_to_apply) /datum/reagent/nitrous_oxide/on_mob_metabolize(mob/living/affected_mob) + . = ..() if(!HAS_TRAIT(affected_mob, TRAIT_COAGULATING)) //IF the mob does not have a coagulant in them, we add the blood mess trait to make the bleed quicker ADD_TRAIT(affected_mob, TRAIT_BLOODY_MESS, type) - return ..() /datum/reagent/nitrous_oxide/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() REMOVE_TRAIT(affected_mob, TRAIT_BLOODY_MESS, type) - return ..() /datum/reagent/nitrous_oxide/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_drowsiness(4 SECONDS * REM * seconds_per_tick) if(!HAS_TRAIT(affected_mob, TRAIT_BLOODY_MESS) && !HAS_TRAIT(affected_mob, TRAIT_COAGULATING)) //So long as they do not have a coagulant, if they did not have the bloody mess trait, they do now @@ -1540,7 +1570,6 @@ if(SPT_PROB(10, seconds_per_tick)) affected_mob.losebreath += 2 affected_mob.adjust_confusion_up_to(2 SECONDS, 5 SECONDS) - ..() /////////////////////////Colorful Powder//////////////////////////// //For colouring in /proc/mix_color_from_reagents @@ -1684,10 +1713,10 @@ ph = 3 /datum/reagent/plantnutriment/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(tox_prob, seconds_per_tick)) - affected_mob.adjustToxLoss(1, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + if(affected_mob.adjustToxLoss(1, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/plantnutriment/eznutriment name = "E-Z Nutrient" @@ -1785,8 +1814,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/stable_plasma/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjustPlasma(10 * REM * seconds_per_tick) - ..() /datum/reagent/iodine name = "Iodine" @@ -1808,7 +1837,7 @@ /datum/reagent/carpet/expose_turf(turf/exposed_turf, reac_volume) if(isopenturf(exposed_turf) && exposed_turf.turf_flags & IS_SOLID && !istype(exposed_turf, /turf/open/floor/carpet)) - exposed_turf.PlaceOnTop(carpet_type, flags = CHANGETURF_INHERIT_AIR) + exposed_turf.place_on_top(carpet_type, flags = CHANGETURF_INHERIT_AIR) ..() /datum/reagent/carpet/black @@ -2113,9 +2142,9 @@ color = pick(random_color_list) /datum/reagent/colorful_reagent/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(can_colour_mobs) affected_mob.add_atom_colour(pick(random_color_list), WASHABLE_COLOUR_PRIORITY) - return ..() /// Colors anything it touches a random color. /datum/reagent/colorful_reagent/expose_atom(atom/exposed_atom, reac_volume) @@ -2161,15 +2190,17 @@ /datum/reagent/barbers_aid/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message=TRUE, touch_protection=FALSE) . = ..() - if(!(methods & (TOUCH|VAPOR)) || !ishuman(exposed_mob) || HAS_TRAIT(exposed_mob, TRAIT_BALD) || HAS_TRAIT(exposed_mob, TRAIT_SHAVED)) + if(!(methods & (TOUCH|VAPOR)) || !ishuman(exposed_mob) || (HAS_TRAIT(exposed_mob, TRAIT_BALD) && HAS_TRAIT(exposed_mob, TRAIT_SHAVED))) return var/mob/living/carbon/human/exposed_human = exposed_mob - var/datum/sprite_accessory/hair/picked_hair = pick(GLOB.hairstyles_list) - var/datum/sprite_accessory/facial_hair/picked_beard = pick(GLOB.facial_hairstyles_list) - to_chat(exposed_human, span_notice("Hair starts sprouting from your scalp.")) - exposed_human.set_facial_hairstyle(picked_beard, update = FALSE) - exposed_human.set_hairstyle(picked_hair, update = TRUE) + if(!HAS_TRAIT(exposed_human, TRAIT_SHAVED)) + var/datum/sprite_accessory/facial_hair/picked_beard = pick(GLOB.facial_hairstyles_list) + exposed_human.set_facial_hairstyle(picked_beard, update = FALSE) + if(!HAS_TRAIT(exposed_human, TRAIT_BALD)) + var/datum/sprite_accessory/hair/picked_hair = pick(GLOB.hairstyles_list) + exposed_human.set_hairstyle(picked_hair, update = TRUE) + to_chat(exposed_human, span_notice("Hair starts sprouting from your [HAS_TRAIT(exposed_human, TRAIT_BALD) ? "face" : "scalp"].")) /datum/reagent/concentrated_barbers_aid name = "Concentrated Barber's Aid" @@ -2182,17 +2213,19 @@ /datum/reagent/concentrated_barbers_aid/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message=TRUE, touch_protection=FALSE) . = ..() - if(!(methods & (TOUCH|VAPOR)) || !ishuman(exposed_mob) || HAS_TRAIT(exposed_mob, TRAIT_BALD) || HAS_TRAIT(exposed_mob, TRAIT_SHAVED)) + if(!(methods & (TOUCH|VAPOR)) || !ishuman(exposed_mob) || (HAS_TRAIT(exposed_mob, TRAIT_BALD) && HAS_TRAIT(exposed_mob, TRAIT_SHAVED))) return var/mob/living/carbon/human/exposed_human = exposed_mob - to_chat(exposed_human, span_notice("Your hair starts growing at an incredible speed!")) - exposed_human.set_facial_hairstyle("Beard (Very Long)", update = FALSE) - exposed_human.set_hairstyle("Very Long Hair", update = TRUE) + if(!HAS_TRAIT(exposed_human, TRAIT_SHAVED)) + exposed_human.set_facial_hairstyle("Beard (Very Long)", update = FALSE) + if(!HAS_TRAIT(exposed_human, TRAIT_BALD)) + exposed_human.set_hairstyle("Very Long Hair", update = TRUE) + to_chat(exposed_human, span_notice("Your[HAS_TRAIT(exposed_human, TRAIT_BALD) ? " facial" : ""] hair starts growing at an incredible speed!")) /datum/reagent/concentrated_barbers_aid/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - if(current_cycle > 20 / creation_purity) + if(current_cycle > 21 / creation_purity) if(!ishuman(affected_mob)) return var/mob/living/carbon/human/human_mob = affected_mob @@ -2340,9 +2373,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/royal_bee_jelly/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(1, seconds_per_tick)) affected_mob.say(pick("Bzzz...","BZZ BZZ","Bzzzzzzzzzzz..."), forced = "royal bee jelly") - ..() //Misc reagents @@ -2374,8 +2407,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/magillitis/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - ..() - if((ishuman(affected_mob)) && current_cycle >= 10) + . = ..() + if((ishuman(affected_mob)) && current_cycle > 10) affected_mob.gorillize() /datum/reagent/growthserum @@ -2387,6 +2420,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/growthserum/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/newsize = current_size switch(volume) if(0 to 19) @@ -2402,12 +2436,11 @@ affected_mob.update_transform(newsize/current_size) current_size = newsize - ..() /datum/reagent/growthserum/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.update_transform(RESIZE_DEFAULT_SIZE/current_size) current_size = RESIZE_DEFAULT_SIZE - ..() /datum/reagent/plastic_polymers name = "Plastic Polymers" @@ -2467,14 +2500,7 @@ metabolization_rate = 0.25 * REAGENTS_METABOLISM ph = 15 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/pax/on_mob_metabolize(mob/living/affected_mob) - ..() - ADD_TRAIT(affected_mob, TRAIT_PACIFISM, type) - -/datum/reagent/pax/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_PACIFISM, type) - ..() + metabolized_traits = list(TRAIT_PACIFISM) /datum/reagent/bz_metabolites name = "BZ Metabolites" @@ -2483,21 +2509,14 @@ taste_description = "acrid cinnamon" metabolization_rate = 0.2 * REAGENTS_METABOLISM chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE - -/datum/reagent/bz_metabolites/on_mob_metabolize(mob/living/ling) - ..() - ADD_TRAIT(ling, CHANGELING_HIVEMIND_MUTE, type) - -/datum/reagent/bz_metabolites/on_mob_end_metabolize(mob/living/ling) - ..() - REMOVE_TRAIT(ling, CHANGELING_HIVEMIND_MUTE, type) + metabolized_traits = list(TRAIT_CHANGELING_HIVEMIND_MUTE) /datum/reagent/bz_metabolites/on_mob_life(mob/living/carbon/target, seconds_per_tick, times_fired) + . = ..() if(target.mind) - var/datum/antagonist/changeling/changeling = target.mind.has_antag_datum(/datum/antagonist/changeling) + var/datum/antagonist/changeling/changeling = IS_CHANGELING(target) if(changeling) changeling.adjust_chemicals(-2 * REM * seconds_per_tick) - return ..() /datum/reagent/pax/peaceborg name = "Synthpax" @@ -2513,12 +2532,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/peaceborg/confuse/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_confusion_up_to(3 SECONDS * REM * seconds_per_tick, 5 SECONDS) affected_mob.adjust_dizzy_up_to(6 SECONDS * REM * seconds_per_tick, 12 SECONDS) if(SPT_PROB(10, seconds_per_tick)) to_chat(affected_mob, "You feel confused and disoriented.") - ..() /datum/reagent/peaceborg/tire name = "Tiring Solution" @@ -2528,14 +2547,14 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/peaceborg/tire/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() var/healthcomp = (100 - affected_mob.health) //DOES NOT ACCOUNT FOR ADMINBUS THINGS THAT MAKE YOU HAVE MORE THAN 200/210 HEALTH, OR SOMETHING OTHER THAN A HUMAN PROCESSING THIS. . = FALSE if(affected_mob.getStaminaLoss() < (45 - healthcomp)) //At 50 health you would have 200 - 150 health meaning 50 compensation. 60 - 50 = 10, so would only do 10-19 stamina.) - affected_mob.adjustStaminaLoss(10 * REM * seconds_per_tick) - . = TRUE + if(affected_mob.adjustStaminaLoss(10 * REM * seconds_per_tick, updating_stamina = FALSE)) + . = UPDATE_MOB_HEALTH if(SPT_PROB(16, seconds_per_tick)) to_chat(affected_mob, "You should sit down and take a rest...") - ..() /datum/reagent/gondola_mutation_toxin name = "Tranquility" @@ -2543,11 +2562,12 @@ color = "#9A6750" //RGB: 154, 103, 80 taste_description = "inner peace" penetrates_skin = NONE + var/datum/disease/transformation/gondola_disease = /datum/disease/transformation/gondola /datum/reagent/gondola_mutation_toxin/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) . = ..() if((methods & (PATCH|INGEST|INJECT)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) - exposed_mob.ForceContractDisease(new /datum/disease/transformation/gondola(), FALSE, TRUE) + exposed_mob.ForceContractDisease(new gondola_disease, FALSE, TRUE) /datum/reagent/spider_extract @@ -2573,13 +2593,14 @@ desc = "It smells like a carcass, and doesn't look much better." /datum/reagent/yuck/on_mob_add(mob/living/affected_mob) - . = ..() if(HAS_TRAIT(affected_mob, TRAIT_NOHUNGER)) //they can't puke holder.del_reagent(type) + return ..() #define YUCK_PUKE_CYCLES 3 // every X cycle is a puke #define YUCK_PUKES_TO_STUN 3 // hit this amount of pukes in a row to start stunning /datum/reagent/yuck/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(!yuck_cycle) if(SPT_PROB(4, seconds_per_tick)) var/dread = pick("Something is moving in your stomach...", \ @@ -2591,19 +2612,19 @@ var/yuck_cycles = current_cycle - yuck_cycle if(yuck_cycles % YUCK_PUKE_CYCLES == 0) if(yuck_cycles >= YUCK_PUKE_CYCLES * YUCK_PUKES_TO_STUN) - holder.remove_reagent(type, 5) + if(holder) + holder.remove_reagent(type, 5) var/passable_flags = (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM) if(yuck_cycles >= (YUCK_PUKE_CYCLES * YUCK_PUKES_TO_STUN)) passable_flags |= MOB_VOMIT_STUN affected_mob.vomit(vomit_flags = passable_flags, lost_nutrition = rand(14, 26)) - if(holder) - return ..() + #undef YUCK_PUKE_CYCLES #undef YUCK_PUKES_TO_STUN /datum/reagent/yuck/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() yuck_cycle = 0 // reset vomiting - return ..() /datum/reagent/yuck/on_transfer(atom/A, methods=TOUCH, trans_volume) if((methods & INGEST) || !iscarbon(A)) @@ -2665,13 +2686,16 @@ metal_morph(exposed_turf) ///turn an object into a special material -/datum/reagent/metalgen/proc/metal_morph(atom/A) +/datum/reagent/metalgen/proc/metal_morph(atom/target) var/metal_ref = data["material"] if(!metal_ref) return + if(is_type_in_typecache(target, GLOB.blacklisted_metalgen_types)) //some stuff can lead to exploits if transmuted + return + var/metal_amount = 0 - var/list/materials_to_transmute = A.get_material_composition(BREAKDOWN_INCLUDE_ALCHEMY) + var/list/materials_to_transmute = target.get_material_composition() for(var/metal_key in materials_to_transmute) //list with what they're made of metal_amount += materials_to_transmute[metal_key] @@ -2679,9 +2703,8 @@ metal_amount = default_material_amount //some stuff doesn't have materials at all. To still give them properties, we give them a material. Basically doesn't exist var/list/metal_dat = list((metal_ref) = metal_amount) - A.material_flags = applied_material_flags - A.set_custom_materials(metal_dat) - ADD_TRAIT(A, TRAIT_MAT_TRANSMUTED, type) + target.material_flags = applied_material_flags + target.set_custom_materials(metal_dat) /datum/reagent/gravitum name = "Gravitum" @@ -2699,10 +2722,11 @@ addtimer(CALLBACK(exposed_obj, PROC_REF(_RemoveElement), list(/datum/element/forced_gravity, 0)), volume * time_multiplier, TIMER_UNIQUE|TIMER_OVERRIDE) /datum/reagent/gravitum/on_mob_metabolize(mob/living/affected_mob) + . = ..() affected_mob.AddElement(/datum/element/forced_gravity, 0) //0 is the gravity, and in this case weightless - return ..() /datum/reagent/gravitum/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.RemoveElement(/datum/element/forced_gravity, 0) /datum/reagent/cellulose @@ -2722,10 +2746,12 @@ metabolization_rate = 0.75 * REAGENTS_METABOLISM // 5u (WOUND_DETERMINATION_CRITICAL) will last for ~34 seconds chemical_flags = REAGENT_CAN_BE_SYNTHESIZED self_consuming = TRUE + metabolized_traits = list(TRAIT_ANALGESIA) /// Whether we've had at least WOUND_DETERMINATION_SEVERE (2.5u) of determination at any given time. No damage slowdown immunity or indication we're having a second wind if it's just a single moderate wound var/significant = FALSE /datum/reagent/determination/on_mob_end_metabolize(mob/living/carbon/affected_mob) + . = ..() if(significant) var/stam_crash = 0 for(var/thing in affected_mob.all_wounds) @@ -2733,9 +2759,9 @@ stam_crash += (W.severity + 1) * 3 // spike of 3 stam damage per wound severity (moderate = 6, severe = 9, critical = 12) when the determination wears off if it was a combat rush affected_mob.adjustStaminaLoss(stam_crash) affected_mob.remove_status_effect(/datum/status_effect/determined) - ..() /datum/reagent/determination/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(!significant && volume >= WOUND_DETERMINATION_SEVERE) significant = TRUE affected_mob.apply_status_effect(/datum/status_effect/determined) // in addition to the slight healing, limping cooldowns are divided by 4 during the combat high @@ -2747,8 +2773,8 @@ var/obj/item/bodypart/wounded_part = W.limb if(wounded_part) wounded_part.heal_damage(0.25 * REM * seconds_per_tick, 0.25 * REM * seconds_per_tick) - affected_mob.adjustStaminaLoss(-0.25 * REM * seconds_per_tick) // the more wounds, the more stamina regen - ..() + if(affected_mob.adjustStaminaLoss(-0.25 * REM * seconds_per_tick, updating_stamina = FALSE)) // the more wounds, the more stamina regen + return UPDATE_MOB_HEALTH // unholy water, but for heretics. // why couldn't they have both just used the same reagent? @@ -2766,24 +2792,26 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/eldritch/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) - if(IS_HERETIC(drinker)) + . = ..() + var/need_mob_update = FALSE + if(IS_HERETIC_OR_MONSTER(drinker)) drinker.adjust_drowsiness(-10 * REM * seconds_per_tick) drinker.AdjustAllImmobility(-40 * REM * seconds_per_tick) - drinker.adjustStaminaLoss(-10 * REM * seconds_per_tick, FALSE) - drinker.adjustToxLoss(-2 * REM * seconds_per_tick, FALSE, forced = TRUE) - drinker.adjustOxyLoss(-2 * REM * seconds_per_tick, FALSE) - drinker.adjustBruteLoss(-2 * REM * seconds_per_tick, FALSE) - drinker.adjustFireLoss(-2 * REM * seconds_per_tick, FALSE) + need_mob_update += drinker.adjustStaminaLoss(-10 * REM * seconds_per_tick, updating_stamina = FALSE) + need_mob_update += drinker.adjustToxLoss(-2 * REM * seconds_per_tick, updating_health = FALSE, forced = TRUE) + need_mob_update += drinker.adjustOxyLoss(-2 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += drinker.adjustBruteLoss(-2 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += drinker.adjustFireLoss(-2 * REM * seconds_per_tick, updating_health = FALSE) if(drinker.blood_volume < BLOOD_VOLUME_NORMAL) drinker.blood_volume += 3 * REM * seconds_per_tick else - drinker.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * REM * seconds_per_tick, 150) - drinker.adjustToxLoss(2 * REM * seconds_per_tick, FALSE) - drinker.adjustFireLoss(2 * REM * seconds_per_tick, FALSE) - drinker.adjustOxyLoss(2 * REM * seconds_per_tick, FALSE) - drinker.adjustBruteLoss(2 * REM * seconds_per_tick, FALSE) - ..() - return TRUE + need_mob_update = drinker.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * REM * seconds_per_tick, 150) + need_mob_update += drinker.adjustToxLoss(2 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += drinker.adjustFireLoss(2 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += drinker.adjustOxyLoss(2 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += drinker.adjustBruteLoss(2 * REM * seconds_per_tick, updating_health = FALSE) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/universal_indicator name = "Universal Indicator" @@ -2810,10 +2838,16 @@ taste_description = "tiny legs scuttling down the back of your throat" metabolization_rate = 5 * REAGENTS_METABOLISM //1u per second ph = 4.6 // Ants contain Formic Acid - /// How much damage the ants are going to be doing (rises with each tick the ants are in someone's body) - var/ant_damage = 0 + /// Number of ticks the ants have been in the person's body + var/ant_ticks = 0 + /// Amount of damage done per tick the ants have been in the person's system + var/ant_damage = 0.025 /// Tells the debuff how many ants we are being covered with. var/amount_left = 0 + /// Decal to spawn when spilled + var/ants_decal = /obj/effect/decal/cleanable/ants + /// Status effect applied by splashing ants + var/status_effect = /datum/status_effect/ants /// List of possible common statements to scream when eating ants var/static/list/ant_screams = list( "THEY'RE UNDER MY SKIN!!", @@ -2829,26 +2863,25 @@ desc = "Bottoms up...?" /datum/reagent/ants/on_mob_life(mob/living/carbon/victim, seconds_per_tick) - victim.adjustBruteLoss(max(0.1, round((ant_damage * 0.025),0.1))) //Scales with time. Roughly 32 brute with 100u. - ant_damage++ - if(ant_damage < 5) // Makes ant food a little more appetizing, since you won't be screaming as much. - return ..() + . = ..() + victim.adjustBruteLoss(max(0.1, round((ant_ticks * ant_damage),0.1))) //Scales with time. Roughly 32 brute with 100u. + ant_ticks++ + if(ant_ticks < 5) // Makes ant food a little more appetizing, since you won't be screaming as much. + return if(SPT_PROB(5, seconds_per_tick)) if(SPT_PROB(5, seconds_per_tick)) //Super rare statement - victim.say("AUGH NO NOT THE ANTS! NOT THE ANTS! AAAAUUGH THEY'RE IN MY EYES! MY EYES! AUUGH!!", forced = /datum/reagent/ants) + victim.say("AUGH NO NOT THE ANTS! NOT THE ANTS! AAAAUUGH THEY'RE IN MY EYES! MY EYES! AUUGH!!", forced = type) else - victim.say(pick(ant_screams), forced = /datum/reagent/ants) + victim.say(pick(ant_screams), forced = type) if(SPT_PROB(15, seconds_per_tick)) victim.emote("scream") if(SPT_PROB(2, seconds_per_tick)) // Stuns, but purges ants. - victim.vomit(rand(5,10), FALSE, TRUE, 1, TRUE, FALSE, purge_ratio = 1) - ..() - return TRUE + victim.vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = rand(5,10), purge_ratio = 1) /datum/reagent/ants/on_mob_end_metabolize(mob/living/living_anthill) - ant_damage = 0 - to_chat(living_anthill, "You feel like the last of the ants are out of your system.") - return ..() + . = ..() + ant_ticks = 0 + to_chat(living_anthill, span_notice("You feel like the last of the [name] are out of your system.")) /datum/reagent/ants/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -2856,7 +2889,7 @@ return if(methods & (PATCH|TOUCH|VAPOR)) amount_left = round(reac_volume,0.1) - exposed_mob.apply_status_effect(/datum/status_effect/ants, amount_left) + exposed_mob.apply_status_effect(status_effect, amount_left) /datum/reagent/ants/expose_obj(obj/exposed_obj, reac_volume) . = ..() @@ -2875,14 +2908,28 @@ if((reac_volume <= 10)) // Makes sure people don't duplicate ants. return - var/obj/effect/decal/cleanable/ants/pests = exposed_turf.spawn_unique_cleanable(/obj/effect/decal/cleanable/ants) + var/obj/effect/decal/cleanable/ants/pests = exposed_turf.spawn_unique_cleanable(ants_decal) if(!pests) return var/spilled_ants = (round(reac_volume,1) - 5) // To account for ant decals giving 3-5 ants on initialize. - pests.reagents.add_reagent(/datum/reagent/ants, spilled_ants) + pests.reagents.add_reagent(type, spilled_ants) pests.update_ant_damage() +/datum/reagent/ants/fire + name = "Fire ants" + description = "A rare mutation of space ants, born from the heat of a plasma fire. Their bites land a 3.7 on the Schmidt Pain Scale." + color = "#b51f1f" + taste_description = "tiny flaming legs scuttling down the back of your throat" + ant_damage = 0.05 // Roughly 64 brute with 100u + ants_decal = /obj/effect/decal/cleanable/ants/fire + status_effect = /datum/status_effect/ants/fire + +/datum/glass_style/drinking_glass/fire_ants + required_drink_type = /datum/reagent/ants/fire + name = "glass of fire ants" + desc = "This is a terrible idea." + //This is intended to a be a scarce reagent to gate certain drugs and toxins with. Do not put in a synthesizer. Renewable sources of this reagent should be inefficient. /datum/reagent/lead name = "Lead" @@ -2893,9 +2940,9 @@ metabolization_rate = 0.4 * REAGENTS_METABOLISM /datum/reagent/lead/on_mob_life(mob/living/carbon/victim) - victim.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5) - ..() - return TRUE + . = ..() + if(victim.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5)) + return UPDATE_MOB_HEALTH //The main feedstock for kronkaine production, also a shitty stamina healer. /datum/reagent/kronkus_extract @@ -2907,10 +2954,12 @@ addiction_types = list(/datum/addiction/stimulants = 5) /datum/reagent/kronkus_extract/on_mob_life(mob/living/carbon/kronkus_enjoyer) - ..() - kronkus_enjoyer.adjustOrganLoss(ORGAN_SLOT_HEART, 0.1) - kronkus_enjoyer.adjustStaminaLoss(-2, FALSE) - return TRUE + . = ..() + var/need_mob_update + need_mob_update = kronkus_enjoyer.adjustOrganLoss(ORGAN_SLOT_HEART, 0.1) + need_mob_update += kronkus_enjoyer.adjustStaminaLoss(-2, updating_stamina = FALSE) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/brimdust name = "Brimdust" @@ -2922,7 +2971,8 @@ /datum/reagent/brimdust/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - return affected_mob.adjustFireLoss((ispodperson(affected_mob) ? -1 : 1) * seconds_per_tick) + if(affected_mob.adjustFireLoss((ispodperson(affected_mob) ? -1 : 1 * seconds_per_tick), updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/brimdust/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) mytray.adjust_weedlevel(-1) @@ -2950,14 +3000,15 @@ . = ..() metabolizer.add_mood_event(name, /datum/mood_event/love_reagent) -/datum/reagent/love/on_mob_delete(mob/living/deleted_from) +/datum/reagent/love/on_mob_delete(mob/living/affected_mob) . = ..() // When we exit the system we'll leave the moodlet based on the amount we had var/duration_of_moodlet = current_cycle * 20 SECONDS - deleted_from.clear_mood_event(name) - deleted_from.add_mood_event(name, /datum/mood_event/love_reagent, duration_of_moodlet) + affected_mob.clear_mood_event(name) + affected_mob.add_mood_event(name, /datum/mood_event/love_reagent, duration_of_moodlet) /datum/reagent/love/overdose_process(mob/living/metabolizer, seconds_per_tick, times_fired) + . = ..() var/mob/living/carbon/carbon_metabolizer = metabolizer if(!istype(carbon_metabolizer) || !carbon_metabolizer.can_heartattack() || carbon_metabolizer.undergoing_cardiac_arrest()) metabolizer.reagents.del_reagent(type) @@ -2984,6 +3035,7 @@ addtimer(CALLBACK(exposed_obj, TYPE_PROC_REF(/atom/movable/, remove_haunted), HAUNTIUM_REAGENT_TRAIT), volume * 20 SECONDS) /datum/reagent/hauntium/on_mob_metabolize(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() to_chat(affected_mob, span_userdanger("You feel an evil presence inside you!")) if(affected_mob.mob_biotypes & MOB_UNDEAD || HAS_MIND_TRAIT(affected_mob, TRAIT_MORBID)) affected_mob.add_mood_event("morbid_hauntium", /datum/mood_event/morbid_hauntium, name) //8 minutes of slight mood buff if undead or morbid @@ -2991,6 +3043,7 @@ affected_mob.add_mood_event("hauntium_spirits", /datum/mood_event/hauntium_spirits, name) //8 minutes of mood debuff /datum/reagent/hauntium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.mob_biotypes & MOB_UNDEAD || HAS_MIND_TRAIT(affected_mob, TRAIT_MORBID)) //if morbid or undead,acts like an addiction-less drug affected_mob.remove_status_effect(/datum/status_effect/jitter) affected_mob.AdjustStun(-50 * REM * seconds_per_tick) @@ -2998,10 +3051,13 @@ affected_mob.AdjustUnconscious(-50 * REM * seconds_per_tick) affected_mob.AdjustParalyzed(-50 * REM * seconds_per_tick) affected_mob.AdjustImmobilized(-50 * REM * seconds_per_tick) - ..() else - affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, REM * seconds_per_tick) //1 heart damage per tick + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, REM * seconds_per_tick)) //1 heart damage per tick + . = UPDATE_MOB_HEALTH if(SPT_PROB(10, seconds_per_tick)) affected_mob.emote(pick("twitch","choke","shiver","gag")) - ..() - return TRUE + +// The same as gold just with a slower metabolism rate, to make using the Hand of Midas easier. +/datum/reagent/gold/cursed + name = "Cursed Gold" + metabolization_rate = 0.2 * REAGENTS_METABOLISM diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index 6f99273ad4e93..30757231625f4 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -13,9 +13,9 @@ exposed_turf.AddComponent(/datum/component/thermite, reac_volume) /datum/reagent/thermite/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustFireLoss(1 * REM * seconds_per_tick, 0) - ..() - return TRUE + . = ..() + if(affected_mob.adjustFireLoss(1 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/nitroglycerin name = "Nitroglycerin" @@ -47,10 +47,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/clf3/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_fire_stacks(2 * REM * seconds_per_tick) - affected_mob.adjustFireLoss(0.3 * max(affected_mob.fire_stacks, 1) * REM * seconds_per_tick, 0) - ..() - return TRUE + if(affected_mob.adjustFireLoss(0.3 * max(affected_mob.fire_stacks, 1) * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/clf3/expose_turf(turf/exposed_turf, reac_volume) . = ..() @@ -177,10 +177,10 @@ exposed_mob.ignite_mob() /datum/reagent/phlogiston/on_mob_life(mob/living/carbon/metabolizer, seconds_per_tick, times_fired) + . = ..() metabolizer.adjust_fire_stacks(1 * REM * seconds_per_tick) - metabolizer.adjustFireLoss(0.3 * max(metabolizer.fire_stacks, 0.15) * REM * seconds_per_tick, 0) - ..() - return TRUE + if(metabolizer.adjustFireLoss(0.3 * max(metabolizer.fire_stacks, 0.15) * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/napalm name = "Napalm" @@ -201,9 +201,8 @@ mytray.adjust_weedlevel(-rand(5,9)) //At least give them a small reward if they bother. /datum/reagent/napalm/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_fire_stacks(1 * REM * seconds_per_tick) - ..() - return TRUE /datum/reagent/napalm/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -249,6 +248,7 @@ metabolization_rate = 0.05 * REM //slower consumption when dead /datum/reagent/cryostylane/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() metabolization_rate = 0.25 * REM//faster consumption when alive if(affected_mob.reagents.has_reagent(/datum/reagent/oxygen)) affected_mob.reagents.remove_reagent(/datum/reagent/oxygen, 0.5 * REM * seconds_per_tick) @@ -256,7 +256,6 @@ if(ishuman(affected_mob)) var/mob/living/carbon/human/humi = affected_mob humi.adjust_coretemperature(-15 * REM * seconds_per_tick) - ..() /datum/reagent/cryostylane/expose_turf(turf/exposed_turf, reac_volume) . = ..() @@ -280,13 +279,13 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/pyrosium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(holder.has_reagent(/datum/reagent/oxygen)) holder.remove_reagent(/datum/reagent/oxygen, 0.5 * REM * seconds_per_tick) affected_mob.adjust_bodytemperature(15 * REM * seconds_per_tick) if(ishuman(affected_mob)) - var/mob/living/carbon/human/humi = affected_mob - humi.adjust_coretemperature(15 * REM * seconds_per_tick) - ..() + var/mob/living/carbon/human/affected_human = affected_mob + affected_human.adjust_coretemperature(15 * REM * seconds_per_tick) /datum/reagent/pyrosium/burn(datum/reagents/holder) if(holder.has_reagent(/datum/reagent/oxygen)) @@ -306,12 +305,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/teslium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() shock_timer++ if(shock_timer >= rand(5, 30)) //Random shocks are wildly unpredictable shock_timer = 0 affected_mob.electrocute_act(rand(5, 20), "Teslium in their body", 1, SHOCK_NOGLOVES) //SHOCK_NOGLOVES because it's caused from INSIDE of you playsound(affected_mob, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) - ..() /datum/reagent/teslium/on_mob_metabolize(mob/living/carbon/human/affected_mob) . = ..() @@ -334,15 +333,15 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/teslium/energized_jelly/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(isjellyperson(affected_mob)) - shock_timer = 0 //immune to shocks - affected_mob.AdjustAllImmobility(-40 *REM * seconds_per_tick) - affected_mob.adjustStaminaLoss(-2 * REM * seconds_per_tick, 0) - if(is_species(affected_mob, /datum/species/jelly/luminescent)) - var/mob/living/carbon/human/affected_human = affected_mob - var/datum/species/jelly/luminescent/slime_species = affected_human.dna.species - slime_species.extract_cooldown = max(slime_species.extract_cooldown - (2 SECONDS * REM * seconds_per_tick), 0) - ..() + if(!isjellyperson(affected_mob)) //everyone but jellypeople get shocked as normal. + return ..() + affected_mob.AdjustAllImmobility(-40 *REM * seconds_per_tick) + if(affected_mob.adjustStaminaLoss(-2 * REM * seconds_per_tick, updating_stamina = FALSE)) + . = UPDATE_MOB_HEALTH + if(is_species(affected_mob, /datum/species/jelly/luminescent)) + var/mob/living/carbon/human/affected_human = affected_mob + var/datum/species/jelly/luminescent/slime_species = affected_human.dna.species + slime_species.extract_cooldown = max(slime_species.extract_cooldown - (2 SECONDS * REM * seconds_per_tick), 0) /datum/reagent/firefighting_foam name = "Firefighting Foam" diff --git a/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm b/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm index c1da8b7424f2a..d6c4f0009b403 100644 --- a/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm @@ -24,22 +24,23 @@ inverse_chem = null fallback_icon = 'icons/obj/drinks/drink_effects.dmi' fallback_icon_state = "acid_buffer_fallback" - ///The strength of the buffer where (volume/holder.total_volume)*strength. So for 1u added to 50u the ph will decrease by 0.4 - var/strength = 30 //Consumes self on addition and shifts ph /datum/reagent/reaction_agent/acidic_buffer/intercept_reagents_transfer(datum/reagents/target, amount) . = ..() if(!.) return + + //do the ph change + var/message if(target.ph <= ph) - target.my_atom.audible_message(span_warning("The beaker froths as the buffer is added, to no effect.")) - playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE) - holder.remove_reagent(type, amount)//Remove from holder because it's not transferred - return - var/ph_change = -((amount/target.total_volume)*strength) - target.adjust_all_reagents_ph(ph_change, ph, 14) - target.my_atom.audible_message(span_warning("The beaker fizzes as the ph changes!")) + message = "The beaker froths as the buffer is added, to no effect." + else + message = "The beaker froths as the pH changes!" + target.adjust_all_reagents_ph((-(amount / target.total_volume) * BUFFER_IONIZING_STRENGTH)) + + //give feedback & remove from holder because it's not transferred + target.my_atom.audible_message(span_warning(message)) playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE) holder.remove_reagent(type, amount) @@ -51,21 +52,22 @@ inverse_chem = null fallback_icon = 'icons/obj/drinks/drink_effects.dmi' fallback_icon_state = "base_buffer_fallback" - ///The strength of the buffer where (volume/holder.total_volume)*strength. So for 1u added to 50u the ph will increase by 0.4 - var/strength = 30 /datum/reagent/reaction_agent/basic_buffer/intercept_reagents_transfer(datum/reagents/target, amount) . = ..() if(!.) return + + //do the ph change + var/message if(target.ph >= ph) - target.my_atom.audible_message(span_warning("The beaker froths as the buffer is added, to no effect.")) - playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE) - holder.remove_reagent(type, amount)//Remove from holder because it's not transferred - return - var/ph_change = (amount/target.total_volume)*strength - target.adjust_all_reagents_ph(ph_change, 0, ph) - target.my_atom.audible_message(span_warning("The beaker froths as the ph changes!")) + message = "The beaker froths as the buffer is added, to no effect." + else + message = "The beaker froths as the pH changes!" + target.adjust_all_reagents_ph(((amount / target.total_volume) * BUFFER_IONIZING_STRENGTH)) + + //give feedback & remove from holder because it's not transferred + target.my_atom.audible_message(span_warning(message)) playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE) holder.remove_reagent(type, amount) @@ -103,14 +105,14 @@ target.my_atom.audible_message(span_warning("The added reagent doesn't seem to do much.")) holder.remove_reagent(type, amount) +///How much the reaction speed is sped up by - for 5u added to 100u, an additional step of 1 will be done up to a max of 2x +#define SPEED_REAGENT_STRENGTH 20 + /datum/reagent/reaction_agent/speed_agent name = "Tempomyocin" description = "This reagent will consume itself and speed up an ongoing reaction, modifying the current reaction's purity by it's own." ph = 10 color = "#e61f82" - ///How much the reaction speed is sped up by - for 5u added to 100u, an additional step of 1 will be done up to a max of 2x - var/strength = 20 - /datum/reagent/reaction_agent/speed_agent/intercept_reagents_transfer(datum/reagents/target, amount) . = ..() @@ -123,8 +125,10 @@ var/datum/equilibrium/reaction = _reaction if(!reaction) CRASH("[_reaction] is in the reaction list, but is not an equilibrium") - var/power = (amount/reaction.target_vol)*strength + var/power = (amount / reaction.target_vol) * SPEED_REAGENT_STRENGTH power *= creation_purity power = clamp(power, 0, 2) reaction.react_timestep(power, creation_purity) holder.remove_reagent(type, amount) + +#undef SPEED_REAGENT_STRENGTH diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index d655698646ca2..e9bea91fbed64 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -7,7 +7,6 @@ color = "#CF3600" // rgb: 207, 54, 0 taste_description = "bitterness" taste_mult = 1.2 - harmful = TRUE chemical_flags = REAGENT_CAN_BE_SYNTHESIZED ///The amount of toxin damage this will cause when metabolized (also used to calculate liver damage) var/toxpwr = 1.5 @@ -23,10 +22,10 @@ mytray.adjust_toxic(round(volume * 2)) /datum/reagent/toxin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(toxpwr && affected_mob.health > health_required) - affected_mob.adjustToxLoss(toxpwr * REM * normalise_creation_purity() * seconds_per_tick, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + if(affected_mob.adjustToxLoss(toxpwr * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/amatoxin name = "Amatoxin" @@ -64,8 +63,9 @@ exposed_mob.domutcheck() /datum/reagent/toxin/mutagen/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - . = affected_mob.adjustToxLoss(0.5 * seconds_per_tick * REM, required_biotype = affected_biotype) - return ..() || . + . = ..() + if(affected_mob.adjustToxLoss(0.5 * seconds_per_tick * REM, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/mutagen/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) mytray.mutation_roll(user) @@ -98,16 +98,18 @@ return ..() /datum/reagent/toxin/plasma/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(holder.has_reagent(/datum/reagent/medicine/epinephrine)) holder.remove_reagent(/datum/reagent/medicine/epinephrine, 2 * REM * seconds_per_tick) affected_mob.adjustPlasma(20 * REM * seconds_per_tick) - return ..() /datum/reagent/toxin/plasma/on_mob_metabolize(mob/living/carbon/affected_mob) + . = ..() if(HAS_TRAIT(affected_mob, TRAIT_PLASMA_LOVER_METABOLISM)) // sometimes mobs can temporarily metabolize plasma (e.g. plasma fixation disease symptom) toxpwr = 0 /datum/reagent/toxin/plasma/on_mob_end_metabolize(mob/living/carbon/affected_mob) + . = ..() toxpwr = initial(toxpwr) /// Handles plasma boiling. @@ -152,6 +154,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/hot_ice/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(holder.has_reagent(/datum/reagent/medicine/epinephrine)) holder.remove_reagent(/datum/reagent/medicine/epinephrine, 2 * REM * seconds_per_tick) affected_mob.adjustPlasma(20 * REM * seconds_per_tick) @@ -159,13 +162,14 @@ if(ishuman(affected_mob)) var/mob/living/carbon/human/humi = affected_mob humi.adjust_coretemperature(-7 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal()) - return ..() /datum/reagent/toxin/hot_ice/on_mob_metabolize(mob/living/carbon/affected_mob) + . = ..() if(HAS_TRAIT(affected_mob, TRAIT_PLASMA_LOVER_METABOLISM)) toxpwr = 0 /datum/reagent/toxin/hot_ice/on_mob_end_metabolize(mob/living/carbon/affected_mob) + . = ..() toxpwr = initial(toxpwr) /datum/reagent/toxin/lexorin @@ -180,22 +184,20 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/lexorin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - . = TRUE - - if(HAS_TRAIT(affected_mob, TRAIT_NOBREATH)) - . = FALSE - - if(.) + . = ..() + if(!HAS_TRAIT(affected_mob, TRAIT_NOBREATH)) affected_mob.adjustOxyLoss(5 * REM * normalise_creation_purity() * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) affected_mob.losebreath += 2 * REM * normalise_creation_purity() * seconds_per_tick + . = UPDATE_MOB_HEALTH if(SPT_PROB(10, seconds_per_tick)) affected_mob.emote("gasp") - ..() /datum/reagent/toxin/lexorin/on_mob_metabolize(mob/living/affected_mob) + . = ..() RegisterSignal(affected_mob, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(block_breath)) /datum/reagent/toxin/lexorin/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() UnregisterSignal(affected_mob, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(block_breath)) /datum/reagent/toxin/lexorin/proc/block_breath(mob/living/source) @@ -213,14 +215,14 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/slimejelly/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(5, seconds_per_tick)) to_chat(affected_mob, span_danger("Your insides are burning!")) - affected_mob.adjustToxLoss(rand(20, 60), FALSE, required_biotype = affected_biotype) - . = TRUE + if(affected_mob.adjustToxLoss(rand(20, 60), updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH else if(SPT_PROB(23, seconds_per_tick)) - affected_mob.heal_bodypart_damage(5) - . = TRUE - ..() + if(affected_mob.heal_bodypart_damage(5)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/carpotoxin name = "Carpotoxin" @@ -252,9 +254,9 @@ if((data?["method"] & INGEST) && holder_mob.stat != DEAD) holder_mob.fakedeath(type) -/datum/reagent/toxin/zombiepowder/on_mob_end_metabolize(mob/living/holder_mob) - holder_mob.cure_fakedeath(type) - return ..() +/datum/reagent/toxin/zombiepowder/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() + affected_mob.cure_fakedeath(type) /datum/reagent/toxin/zombiepowder/on_transfer(atom/target_atom, methods, trans_volume) . = ..() @@ -265,21 +267,22 @@ zombiepowder.data["method"] |= INGEST /datum/reagent/toxin/zombiepowder/on_mob_life(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() if(HAS_TRAIT(affected_mob, TRAIT_FAKEDEATH) && HAS_TRAIT(affected_mob, TRAIT_DEATHCOMA)) - ..() - return TRUE + return + var/need_mob_update switch(current_cycle) - if(1 to 5) + if(2 to 6) affected_mob.adjust_confusion(1 SECONDS * REM * seconds_per_tick) affected_mob.adjust_drowsiness(2 SECONDS * REM * seconds_per_tick) affected_mob.adjust_slurring(6 SECONDS * REM * seconds_per_tick) - if(5 to 8) - affected_mob.adjustStaminaLoss(40 * REM * seconds_per_tick, 0) - if(9 to INFINITY) + if(6 to 9) + need_mob_update = affected_mob.adjustStaminaLoss(40 * REM * seconds_per_tick, updating_stamina = FALSE) + if(10 to INFINITY) if(affected_mob.stat != DEAD) affected_mob.fakedeath(type) - ..() - return TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/ghoulpowder name = "Ghoul Powder" @@ -292,19 +295,12 @@ taste_description = "death" ph = 14.5 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/toxin/ghoulpowder/on_mob_metabolize(mob/living/affected_mob) - ..() - ADD_TRAIT(affected_mob, TRAIT_FAKEDEATH, type) - -/datum/reagent/toxin/ghoulpowder/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_FAKEDEATH, type) - ..() + metabolized_traits = list(TRAIT_FAKEDEATH) /datum/reagent/toxin/ghoulpowder/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOxyLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - ..() - . = TRUE + . = ..() + if(affected_mob.adjustOxyLoss(1 * REM * seconds_per_tick, FALSE, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/mindbreaker name = "Mindbreaker Toxin" @@ -318,26 +314,17 @@ inverse_chem = /datum/reagent/impurity/rosenol chemical_flags = REAGENT_CAN_BE_SYNTHESIZED addiction_types = list(/datum/addiction/hallucinogens = 18) //7.2 per 2 seconds + metabolized_traits = list(TRAIT_RDS_SUPPRESSED) - -/datum/reagent/toxin/mindbreaker/on_mob_metabolize(mob/living/metabolizer) - . = ..() - ADD_TRAIT(metabolizer, TRAIT_RDS_SUPPRESSED, type) - -/datum/reagent/toxin/mindbreaker/on_mob_end_metabolize(mob/living/metabolizer) +/datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - REMOVE_TRAIT(metabolizer, TRAIT_RDS_SUPPRESSED, type) - -/datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/metabolizer, seconds_per_tick, times_fired) // mindbreaker toxin assuages hallucinations in those plagued with it, mentally - if(metabolizer.has_trauma_type(/datum/brain_trauma/mild/hallucinations)) - metabolizer.remove_status_effect(/datum/status_effect/hallucination) + if(affected_mob.has_trauma_type(/datum/brain_trauma/mild/hallucinations)) + affected_mob.remove_status_effect(/datum/status_effect/hallucination) // otherwise it creates hallucinations. truly a miracle medicine. else - metabolizer.adjust_hallucinations(10 SECONDS * REM * seconds_per_tick) - - return ..() + affected_mob.adjust_hallucinations(10 SECONDS * REM * seconds_per_tick) /datum/reagent/toxin/plantbgone name = "Plant-B-Gone" @@ -374,8 +361,8 @@ var/damage = min(round(0.4 * reac_volume, 0.1), 10) if(exposed_mob.mob_biotypes & MOB_PLANT) // spray bottle emits 5u so it's dealing ~15 dmg per spray - exposed_mob.adjustToxLoss(damage * 20, required_biotype = affected_biotype) - return + if(exposed_mob.adjustToxLoss(damage * 20, required_biotype = affected_biotype)) + return if(!(methods & VAPOR) || !iscarbon(exposed_mob)) return @@ -409,8 +396,9 @@ AddElement(/datum/element/bugkiller_reagent) /datum/reagent/toxin/pestkiller/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - . = affected_mob.adjustToxLoss(2 * toxpwr * REM * seconds_per_tick, updating_health = FALSE, required_biotype = MOB_BUG) - return ..() || . + . = ..() + if(affected_mob.adjustToxLoss(2 * toxpwr * REM * seconds_per_tick, updating_health = FALSE, required_biotype = MOB_BUG)) + return UPDATE_MOB_HEALTH //Pest Spray /datum/reagent/toxin/pestkiller/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) @@ -438,10 +426,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/toxin/spore/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.damageoverlaytemp = 60 affected_mob.update_damage_hud() affected_mob.set_eye_blur_if_lower(6 SECONDS * REM * seconds_per_tick) - return ..() /datum/reagent/toxin/spore_burning name = "Burning Spore Toxin" @@ -453,9 +441,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/toxin/spore_burning/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_fire_stacks(2 * REM * seconds_per_tick) affected_mob.ignite_mob() - return ..() /datum/reagent/toxin/chloralhydrate name = "Chloral Hydrate" @@ -472,18 +460,17 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/chloralhydrate/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() switch(current_cycle) - if(1 to 10) + if(2 to 11) affected_mob.adjust_confusion(2 SECONDS * REM * normalise_creation_purity() * seconds_per_tick) affected_mob.adjust_drowsiness(4 SECONDS * REM * normalise_creation_purity() * seconds_per_tick) - if(10 to 50) + if(11 to 51) affected_mob.Sleeping(40 * REM * normalise_creation_purity() * seconds_per_tick) - . = TRUE - if(51 to INFINITY) + if(52 to INFINITY) affected_mob.Sleeping(40 * REM * normalise_creation_purity() * seconds_per_tick) - affected_mob.adjustToxLoss(1 * (current_cycle - 50) * REM * normalise_creation_purity() * seconds_per_tick, FALSE, required_biotype = affected_biotype) - . = TRUE - ..() + if(affected_mob.adjustToxLoss(1 * (current_cycle - 51) * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/fakebeer //disguised as normal beer for use by emagged brobots name = "B33r" @@ -507,14 +494,14 @@ icon_state = initial(copy_from.icon_state) /datum/reagent/toxin/fakebeer/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() switch(current_cycle) - if(1 to 50) + if(2 to 51) affected_mob.Sleeping(40 * REM * seconds_per_tick) - if(51 to INFINITY) + if(52 to INFINITY) affected_mob.Sleeping(40 * REM * seconds_per_tick) - affected_mob.adjustToxLoss(1 * (current_cycle - 50) * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - . = TRUE - return ..() || . + if(affected_mob.adjustToxLoss(1 * (current_cycle - 50) * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/coffeepowder name = "Coffee Grounds" @@ -558,9 +545,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/mutetoxin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() // Gain approximately 12 seconds * creation purity seconds of silence every metabolism tick. affected_mob.set_silence_if_lower(6 SECONDS * REM * normalise_creation_purity() * seconds_per_tick) - return ..() /datum/reagent/toxin/staminatoxin name = "Tirizene" @@ -572,10 +559,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/staminatoxin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustStaminaLoss(data * REM * seconds_per_tick, 0) + . = ..() + if(affected_mob.adjustStaminaLoss(data * REM * seconds_per_tick, updating_stamina = FALSE)) + . = UPDATE_MOB_HEALTH data = max(data - 1, 3) - ..() - . = TRUE /datum/reagent/toxin/polonium name = "Polonium" @@ -587,12 +574,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/toxin/polonium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if (!HAS_TRAIT(affected_mob, TRAIT_IRRADIATED) && SSradiation.can_irradiate_basic(affected_mob)) affected_mob.AddComponent(/datum/component/irradiated) else - affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, required_biotype = affected_biotype) - . = TRUE - return ..() || . + if(affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/histamine name = "Histamine" @@ -606,6 +593,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/toxin/histamine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(30, seconds_per_tick)) switch(pick(1, 2, 3, 4)) if(1) @@ -618,16 +606,17 @@ if(4) if(prob(75)) to_chat(affected_mob, span_danger("You scratch at an itch.")) - affected_mob.adjustBruteLoss(2*REM, FALSE, required_bodytype = affected_bodytype) - . = TRUE - ..() + if(affected_mob.adjustBruteLoss(2* REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/histamine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOxyLoss(2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - affected_mob.adjustBruteLoss(2 * REM * seconds_per_tick, FALSE, FALSE, BODYTYPE_ORGANIC) - affected_mob.adjustToxLoss(2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - ..() - . = TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOxyLoss(2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update += affected_mob.adjustBruteLoss(2 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustToxLoss(2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/formaldehyde name = "Formaldehyde" @@ -646,14 +635,13 @@ /datum/reagent/toxin/formaldehyde/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) if(liver && HAS_TRAIT(liver, TRAIT_CORONER_METABOLISM)) //mmmm, the forbidden pickle juice - affected_mob.adjustToxLoss(-1, FALSE, required_biotype = affected_biotype) //it counteracts its own toxin damage. - . = TRUE - return ..() + if(affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) //it counteracts its own toxin damage. + return UPDATE_MOB_HEALTH + return else if(SPT_PROB(2.5, seconds_per_tick)) holder.add_reagent(/datum/reagent/toxin/histamine, pick(5,15)) holder.remove_reagent(/datum/reagent/toxin/formaldehyde, 1.2) - else - return ..() + return ..() /datum/reagent/toxin/venom name = "Venom" @@ -670,20 +658,22 @@ var/newsize = 1.1 * RESIZE_DEFAULT_SIZE affected_mob.update_transform(newsize/current_size) current_size = newsize - toxpwr = 0.1 * volume - affected_mob.adjustBruteLoss((0.3 * volume) * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - . = TRUE + + if(affected_mob.adjustBruteLoss((0.3 * volume) * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)) + . = UPDATE_MOB_HEALTH + + // chance to either decay into histamine or go the normal route of toxin metabolization if(SPT_PROB(8, seconds_per_tick)) holder.add_reagent(/datum/reagent/toxin/histamine, pick(5, 10)) holder.remove_reagent(/datum/reagent/toxin/venom, 1.1) else - ..() + return ..() || . /datum/reagent/toxin/venom/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() affected_mob.update_transform(RESIZE_DEFAULT_SIZE/current_size) current_size = RESIZE_DEFAULT_SIZE - ..() /datum/reagent/toxin/fentanyl name = "Fentanyl" @@ -699,15 +689,17 @@ addiction_types = list(/datum/addiction/opioids = 25) /datum/reagent/toxin/fentanyl/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * REM * normalise_creation_purity() * seconds_per_tick, 150) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * REM * normalise_creation_purity() * seconds_per_tick, 150) if(affected_mob.toxloss <= 60) - affected_mob.adjustToxLoss(1 * REM * normalise_creation_purity() * seconds_per_tick, FALSE, required_biotype = affected_biotype) - if(current_cycle >= 4) + need_mob_update += affected_mob.adjustToxLoss(1 * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) + if(current_cycle > 4) affected_mob.add_mood_event("smacked out", /datum/mood_event/narcotic_heavy, name) - if(current_cycle >= 18) + if(current_cycle > 18) affected_mob.Sleeping(40 * REM * normalise_creation_purity() * seconds_per_tick) - ..() - return TRUE + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/cyanide name = "Cyanide" @@ -722,13 +714,17 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/cyanide/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update = FALSE if(SPT_PROB(2.5, seconds_per_tick)) affected_mob.losebreath += 1 + need_mob_update = TRUE if(SPT_PROB(4, seconds_per_tick)) to_chat(affected_mob, span_danger("You feel horrendously weak!")) affected_mob.Stun(40) - affected_mob.adjustToxLoss(2*REM * normalise_creation_purity(), FALSE, required_biotype = affected_biotype) - return ..() + need_mob_update += affected_mob.adjustToxLoss(2*REM * normalise_creation_purity(), updating_health = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/bad_food name = "Bad Food" @@ -755,23 +751,26 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/itching_powder/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + var/need_mob_update = FALSE if(SPT_PROB(8, seconds_per_tick)) to_chat(affected_mob, span_danger("You scratch at your head.")) - affected_mob.adjustBruteLoss(0.2*REM, FALSE, required_bodytype = affected_bodytype) - . = TRUE + need_mob_update += affected_mob.adjustBruteLoss(0.2*REM, FALSE, required_bodytype = affected_bodytype) if(SPT_PROB(8, seconds_per_tick)) to_chat(affected_mob, span_danger("You scratch at your leg.")) - affected_mob.adjustBruteLoss(0.2*REM, FALSE, required_bodytype = affected_bodytype) - . = TRUE + need_mob_update += affected_mob.adjustBruteLoss(0.2*REM, FALSE, required_bodytype = affected_bodytype) if(SPT_PROB(8, seconds_per_tick)) to_chat(affected_mob, span_danger("You scratch at your arm.")) - affected_mob.adjustBruteLoss(0.2*REM, FALSE, required_bodytype = affected_bodytype) - . = TRUE + need_mob_update += affected_mob.adjustBruteLoss(0.2*REM, FALSE, required_bodytype = affected_bodytype) + + if(need_mob_update) + . = UPDATE_MOB_HEALTH + if(SPT_PROB(1.5, seconds_per_tick)) holder.add_reagent(/datum/reagent/toxin/histamine,rand(1,3)) - holder.remove_reagent(/datum/reagent/toxin/itching_powder,1.2) + holder.remove_reagent(/datum/reagent/toxin/itching_powder, 1.2) return - ..() + else + return ..() || . /datum/reagent/toxin/initropidril name = "Initropidril" @@ -784,26 +783,28 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/toxin/initropidril/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(SPT_PROB(13, seconds_per_tick)) - var/picked_option = rand(1,3) - switch(picked_option) - if(1) - affected_mob.Paralyze(60) - . = TRUE - if(2) + . = ..() + if(!SPT_PROB(13, seconds_per_tick)) + return + var/picked_option = rand(1,3) + var/need_mob_update + switch(picked_option) + if(1) + affected_mob.Paralyze(60) + if(2) + affected_mob.losebreath += 10 + affected_mob.adjustOxyLoss(rand(5,25), updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + need_mob_update = TRUE + if(3) + if(!affected_mob.undergoing_cardiac_arrest() && affected_mob.can_heartattack()) + affected_mob.set_heartattack(TRUE) + if(affected_mob.stat == CONSCIOUS) + affected_mob.visible_message(span_userdanger("[affected_mob] clutches at [affected_mob.p_their()] chest as if [affected_mob.p_their()] heart stopped!")) + else affected_mob.losebreath += 10 - affected_mob.adjustOxyLoss(rand(5,25), FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - . = TRUE - if(3) - if(!affected_mob.undergoing_cardiac_arrest() && affected_mob.can_heartattack()) - affected_mob.set_heartattack(TRUE) - if(affected_mob.stat == CONSCIOUS) - affected_mob.visible_message(span_userdanger("[affected_mob] clutches at [affected_mob.p_their()] chest as if [affected_mob.p_their()] heart stopped!")) - else - affected_mob.losebreath += 10 - affected_mob.adjustOxyLoss(rand(5,25), FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - . = TRUE - return ..() || . + need_mob_update = affected_mob.adjustOxyLoss(rand(5,25), updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/pancuronium name = "Pancuronium" @@ -817,12 +818,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/toxin/pancuronium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(current_cycle >= 10) + . = ..() + if(current_cycle > 10) affected_mob.Stun(40 * REM * seconds_per_tick) - . = TRUE if(SPT_PROB(10, seconds_per_tick)) affected_mob.losebreath += 4 - ..() + return UPDATE_MOB_HEALTH /datum/reagent/toxin/sodium_thiopental name = "Sodium Thiopental" @@ -833,21 +834,14 @@ metabolization_rate = 0.75 * REAGENTS_METABOLISM toxpwr = 0 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE - -/datum/reagent/toxin/sodium_thiopental/on_mob_add(mob/living/affected_mob, amount) - . = ..() - ADD_TRAIT(affected_mob, TRAIT_ANTICONVULSANT, name) - -/datum/reagent/toxin/sodium_thiopental/on_mob_delete(mob/living/affected_mob) - . = ..() - REMOVE_TRAIT(affected_mob, TRAIT_ANTICONVULSANT, name) + added_traits = list(TRAIT_ANTICONVULSANT) /datum/reagent/toxin/sodium_thiopental/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(current_cycle >= 10) + . = ..() + if(current_cycle > 10) affected_mob.Sleeping(40 * REM * seconds_per_tick) - affected_mob.adjustStaminaLoss(10 * REM * seconds_per_tick, 0) - ..() - return TRUE + if(affected_mob.adjustStaminaLoss(10 * REM * seconds_per_tick, updating_stamina = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/sulfonal name = "Sulfonal" @@ -863,9 +857,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/sulfonal/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(current_cycle >= 22) + . = ..() + if(current_cycle > 22) affected_mob.Sleeping(40 * REM * normalise_creation_purity() * seconds_per_tick) - return ..() /datum/reagent/toxin/amanitin name = "Amanitin" @@ -879,13 +873,13 @@ var/delayed_toxin_damage = 0 /datum/reagent/toxin/amanitin/on_mob_life(mob/living/affected_mob, seconds_per_tick, times_fired) - delayed_toxin_damage += (seconds_per_tick * 3) . = ..() + delayed_toxin_damage += (seconds_per_tick * 3) /datum/reagent/toxin/amanitin/on_mob_delete(mob/living/affected_mob) + . = ..() affected_mob.log_message("has taken [delayed_toxin_damage] toxin damage from amanitin toxin", LOG_ATTACK) affected_mob.adjustToxLoss(delayed_toxin_damage, required_biotype = affected_biotype) - . = ..() /datum/reagent/toxin/lipolicide name = "Lipolicide" @@ -903,12 +897,12 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/lipolicide/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.nutrition <= NUTRITION_LEVEL_STARVING) - affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - . = TRUE + if(affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH affected_mob.adjust_nutrition(-3 * REM * normalise_creation_purity() * seconds_per_tick) // making the chef more valuable, one meme trap at a time affected_mob.overeatduration = 0 - return ..() || . /datum/reagent/toxin/coniine name = "Coniine" @@ -920,9 +914,10 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/coniine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(affected_mob.losebreath < 5) affected_mob.losebreath = min(affected_mob.losebreath + 5 * REM * seconds_per_tick, 5) - return ..() + return UPDATE_MOB_HEALTH /datum/reagent/toxin/spewium name = "Spewium" @@ -936,8 +931,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/toxin/spewium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - .=..() - if(current_cycle >= 11 && SPT_PROB(min(30, current_cycle), seconds_per_tick)) + . = ..() + if(current_cycle > 11 && SPT_PROB(min(31, current_cycle), seconds_per_tick)) + affected_mob.vomit(10, prob(10), prob(50), rand(0,4), TRUE) var/constructed_flags = (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM) if(prob(10)) constructed_flags |= MOB_VOMIT_BLOOD @@ -946,11 +942,11 @@ affected_mob.vomit(vomit_flags = constructed_flags, distance = rand(0,4)) for(var/datum/reagent/toxin/R in affected_mob.reagents.reagent_list) if(R != src) - affected_mob.reagents.remove_reagent(R.type,1) + affected_mob.reagents.remove_reagent(R.type, 1) /datum/reagent/toxin/spewium/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - if(current_cycle >= 33 && SPT_PROB(7.5, seconds_per_tick)) + if(current_cycle > 33 && SPT_PROB(7.5, seconds_per_tick)) affected_mob.spew_organ() affected_mob.vomit(VOMIT_CATEGORY_BLOOD, lost_nutrition = 0, distance = 4) to_chat(affected_mob, span_userdanger("You feel something lumpy come up as you vomit.")) @@ -965,11 +961,11 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/toxin/curare/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(current_cycle >= 11) + . = ..() + if(current_cycle > 11) affected_mob.Paralyze(60 * REM * seconds_per_tick) - affected_mob.adjustOxyLoss(0.5*REM*seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type) - . = TRUE - ..() + if(affected_mob.adjustOxyLoss(0.5*REM*seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/heparin //Based on a real-life anticoagulant. I'm not a doctor, so this won't be realistic. name = "Heparin" @@ -983,20 +979,13 @@ toxpwr = 0 ph = 11.6 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + metabolized_traits = list(TRAIT_BLOODY_MESS) /datum/reagent/toxin/heparin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) if(holder.has_reagent(/datum/reagent/medicine/coagulant)) //Directly purges coagulants from the system. Get rid of the heparin BEFORE attempting to use coagulants. holder.remove_reagent(/datum/reagent/medicine/coagulant, 2 * REM * seconds_per_tick) return ..() -/datum/reagent/toxin/heparin/on_mob_metabolize(mob/living/affected_mob) - ADD_TRAIT(affected_mob, TRAIT_BLOODY_MESS, /datum/reagent/toxin/heparin) - return ..() - -/datum/reagent/toxin/heparin/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_BLOODY_MESS, /datum/reagent/toxin/heparin) - return ..() - /datum/reagent/toxin/rotatium //Rotatium. Fucks up your rotation and is hilarious name = "Rotatium" description = "A constantly swirling, oddly colourful fluid. Causes the consumer's sense of direction and hand-eye coordination to become wild." @@ -1012,22 +1001,22 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/rotatium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(affected_mob.hud_used) - if(current_cycle >= 20 && (current_cycle % 20) == 0) - var/atom/movable/plane_master_controller/pm_controller = affected_mob.hud_used.plane_master_controllers[PLANE_MASTERS_GAME] - - var/rotation = min(round(current_cycle/20), 89) // By this point the player is probably puking and quitting anyway - for(var/atom/movable/screen/plane_master/plane as anything in pm_controller.get_planes()) - animate(plane, transform = matrix(rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING, loop = -1) - animate(transform = matrix(-rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING) - return ..() + . = ..() + if(!affected_mob.hud_used || (current_cycle < 20 || (current_cycle % 20) == 0)) + return + var/atom/movable/plane_master_controller/pm_controller = affected_mob.hud_used.plane_master_controllers[PLANE_MASTERS_GAME] + + var/rotation = min(round(current_cycle/20), 89) // By this point the player is probably puking and quitting anyway + for(var/atom/movable/screen/plane_master/plane as anything in pm_controller.get_planes()) + animate(plane, transform = matrix(rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING, loop = -1) + animate(transform = matrix(-rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING) /datum/reagent/toxin/rotatium/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() if(affected_mob?.hud_used) var/atom/movable/plane_master_controller/pm_controller = affected_mob.hud_used.plane_master_controllers[PLANE_MASTERS_GAME] for(var/atom/movable/screen/plane_master/plane as anything in pm_controller.get_planes()) animate(plane, transform = matrix(), time = 5, easing = QUAD_EASING) - ..() /datum/reagent/toxin/anacea name = "Anacea" @@ -1045,13 +1034,12 @@ var/remove_amt = 5 if(holder.has_reagent(/datum/reagent/medicine/calomel) || holder.has_reagent(/datum/reagent/medicine/pen_acid)) remove_amt = 0.5 + . = ..() for(var/datum/reagent/medicine/R in affected_mob.reagents.reagent_list) affected_mob.reagents.remove_reagent(R.type, remove_amt * REM * normalise_creation_purity() * seconds_per_tick) - return ..() //ACID - /datum/reagent/toxin/acid name = "Sulfuric Acid" description = "A strong mineral acid with the molecular formula H2SO4." @@ -1115,9 +1103,9 @@ mytray.adjust_weedlevel(-rand(1,4)) /datum/reagent/toxin/acid/fluacid/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustFireLoss((current_cycle/15) * REM * normalise_creation_purity() * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) - . = TRUE - ..() + . = ..() + if(affected_mob.adjustFireLoss(((current_cycle-1)/15) * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/acid/nitracid name = "Nitric Acid" @@ -1131,9 +1119,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/acid/nitracid/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustFireLoss((volume/10) * REM * normalise_creation_purity() * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) //here you go nervar - . = TRUE - ..() + . = ..() + if(affected_mob.adjustFireLoss((volume/10) * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype)) //here you go nervar + return UPDATE_MOB_HEALTH /datum/reagent/toxin/delayed name = "Toxin Microcapsules" @@ -1143,17 +1131,19 @@ var/actual_metaboliztion_rate = REAGENTS_METABOLISM toxpwr = 0 var/actual_toxpwr = 5 - var/delay = 30 + var/delay = 31 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE /datum/reagent/toxin/delayed/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - if(current_cycle > delay) + . = ..() + if(current_cycle <= delay) + return + if(holder) holder.remove_reagent(type, actual_metaboliztion_rate * affected_mob.metabolism_efficiency * seconds_per_tick) - affected_mob.adjustToxLoss(actual_toxpwr * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) - if(SPT_PROB(5, seconds_per_tick)) - affected_mob.Paralyze(20) - . = TRUE - ..() + if(affected_mob.adjustToxLoss(actual_toxpwr * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH + if(SPT_PROB(5, seconds_per_tick)) + affected_mob.Paralyze(20) /datum/reagent/toxin/mimesbane name = "Mime's Bane" @@ -1166,12 +1156,7 @@ ph = 1.7 taste_description = "stillness" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED - -/datum/reagent/toxin/mimesbane/on_mob_metabolize(mob/living/affected_mob) - ADD_TRAIT(affected_mob, TRAIT_EMOTEMUTE, type) - -/datum/reagent/toxin/mimesbane/on_mob_end_metabolize(mob/living/affected_mob) - REMOVE_TRAIT(affected_mob, TRAIT_EMOTEMUTE, type) + metabolized_traits = list(TRAIT_EMOTEMUTE) /datum/reagent/toxin/bonehurtingjuice //oof ouch name = "Bone Hurting Juice" @@ -1187,22 +1172,25 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/bonehurtingjuice/on_mob_add(mob/living/carbon/affected_mob) + . = ..() affected_mob.say("oof ouch my bones", forced = /datum/reagent/toxin/bonehurtingjuice) - return ..() /datum/reagent/toxin/bonehurtingjuice/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustStaminaLoss(7.5 * REM * seconds_per_tick, 0) - if(SPT_PROB(10, seconds_per_tick)) - switch(rand(1, 3)) - if(1) - affected_mob.say(pick("oof.", "ouch.", "my bones.", "oof ouch.", "oof ouch my bones."), forced = /datum/reagent/toxin/bonehurtingjuice) - if(2) - affected_mob.manual_emote(pick("oofs silently.", "looks like [affected_mob.p_their()] bones hurt.", "grimaces, as though [affected_mob.p_their()] bones hurt.")) - if(3) - to_chat(affected_mob, span_warning("Your bones hurt!")) - return ..() || TRUE + . = ..() + if(affected_mob.adjustStaminaLoss(7.5 * REM * seconds_per_tick, updating_stamina = FALSE)) + . = UPDATE_MOB_HEALTH + if(!SPT_PROB(10, seconds_per_tick)) + return + switch(rand(1, 3)) + if(1) + affected_mob.say(pick("oof.", "ouch.", "my bones.", "oof ouch.", "oof ouch my bones."), forced = /datum/reagent/toxin/bonehurtingjuice) + if(2) + affected_mob.manual_emote(pick("oofs silently.", "looks like [affected_mob.p_their()] bones hurt.", "grimaces, as though [affected_mob.p_their()] bones hurt.")) + if(3) + to_chat(affected_mob, span_warning("Your bones hurt!")) /datum/reagent/toxin/bonehurtingjuice/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() if(SPT_PROB(2, seconds_per_tick) && iscarbon(affected_mob)) //big oof var/selected_part = pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) //God help you if the same limb gets picked twice quickly. var/obj/item/bodypart/BP = affected_mob.get_bodypart(selected_part) @@ -1210,11 +1198,11 @@ playsound(affected_mob, get_sfx(SFX_DESECRATION), 50, TRUE, -1) affected_mob.visible_message(span_warning("[affected_mob]'s bones hurt too much!!"), span_danger("Your bones hurt too much!!")) affected_mob.say("OOF!!", forced = /datum/reagent/toxin/bonehurtingjuice) - BP.receive_damage(20, 0, 200, wound_bonus = rand(30, 130)) + if(BP.receive_damage(brute = 20 * REM * seconds_per_tick, burn = 0, blocked = 200, updating_health = FALSE, wound_bonus = rand(30, 130))) + . = UPDATE_MOB_HEALTH else //SUCH A LUST FOR REVENGE!!! to_chat(affected_mob, span_warning("A phantom limb hurts!")) affected_mob.say("Why are we still here, just to suffer?", forced = /datum/reagent/toxin/bonehurtingjuice) - return ..() /datum/reagent/toxin/bungotoxin name = "Bungotoxin" @@ -1227,7 +1215,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/bungotoxin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, 3 * REM * seconds_per_tick) + . = ..() + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, 3 * REM * seconds_per_tick)) + . = UPDATE_MOB_HEALTH // If our mob's currently dizzy from anything else, we will also gain confusion var/mob_dizziness = affected_mob.get_timed_status_effect_duration(/datum/status_effect/confusion) @@ -1235,11 +1225,9 @@ // Gain confusion equal to about half the duration of our current dizziness affected_mob.set_confusion(mob_dizziness / 2) - if(current_cycle >= 12 && SPT_PROB(4, seconds_per_tick)) + if(current_cycle >= 13 && SPT_PROB(4, seconds_per_tick)) var/tox_message = pick("You feel your heart spasm in your chest.", "You feel faint.","You feel you need to catch your breath.","You feel a prickle of pain in your chest.") to_chat(affected_mob, span_notice("[tox_message]")) - . = TRUE - ..() /datum/reagent/toxin/leadacetate name = "Lead Acetate" @@ -1252,13 +1240,16 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/toxin/leadacetate/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) - affected_mob.adjustOrganLoss(ORGAN_SLOT_EARS, 1 * REM * seconds_per_tick) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1 * REM * seconds_per_tick) - . = TRUE + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_EARS, 1 * REM * seconds_per_tick) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1 * REM * seconds_per_tick) + if(need_mob_update) + . = UPDATE_MOB_HEALTH if(SPT_PROB(0.5, seconds_per_tick)) to_chat(affected_mob, span_notice("Ah, what was that? You thought you heard something...")) affected_mob.adjust_confusion(5 SECONDS) - return ..() || . + /datum/reagent/toxin/hunterspider name = "Spider Toxin" description = "A toxic chemical produced by spiders to weaken prey." @@ -1273,8 +1264,8 @@ liver_damage_multiplier = 0 /datum/reagent/toxin/viperspider/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.adjust_hallucinations(10 SECONDS * REM * seconds_per_tick) - return ..() /datum/reagent/toxin/tetrodotoxin name = "Tetrodotoxin" @@ -1294,10 +1285,12 @@ ) /datum/reagent/toxin/tetrodotoxin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() //be ready for a cocktail of symptoms, including: //numbness, nausea, vomit, breath loss, weakness, paralysis and nerve damage/impairment and eventually a heart attack if enough time passes. + var/need_mob_update switch(current_cycle) - if(6 to 12) + if(7 to 13) if(SPT_PROB(20, seconds_per_tick)) affected_mob.set_jitter_if_lower(rand(2 SECONDS, 3 SECONDS) * REM * seconds_per_tick) if(SPT_PROB(5, seconds_per_tick)) @@ -1306,51 +1299,57 @@ to_chat(affected_mob, span_warning("your [tongue.name] feels numb...")) affected_mob.set_slurring_if_lower(5 SECONDS * REM * seconds_per_tick) affected_mob.adjust_disgust(3.5 * REM * seconds_per_tick) - if(12 to 20) + if(13 to 21) silent_toxin = FALSE toxpwr = 0.5 - affected_mob.adjustStaminaLoss(2.5 * REM * seconds_per_tick, 0) + need_mob_update = affected_mob.adjustStaminaLoss(2.5 * REM * seconds_per_tick, updating_stamina = FALSE) if(SPT_PROB(20, seconds_per_tick)) affected_mob.losebreath += 1 * REM * seconds_per_tick + need_mob_update = TRUE if(SPT_PROB(40, seconds_per_tick)) affected_mob.set_jitter_if_lower(rand(2 SECONDS, 3 SECONDS) * REM * seconds_per_tick) affected_mob.adjust_disgust(3 * REM * seconds_per_tick) affected_mob.set_slurring_if_lower(1 SECONDS * REM * seconds_per_tick) - affected_mob.adjustStaminaLoss(2 * REM * seconds_per_tick, 0) + affected_mob.adjustStaminaLoss(2 * REM * seconds_per_tick, updating_stamina = FALSE) if(SPT_PROB(4, seconds_per_tick)) paralyze_limb(affected_mob) + need_mob_update = TRUE if(SPT_PROB(10, seconds_per_tick)) affected_mob.adjust_confusion(rand(6 SECONDS, 8 SECONDS)) - if(20 to 28) + if(21 to 29) toxpwr = 1 - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5) + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5) if(SPT_PROB(40, seconds_per_tick)) affected_mob.losebreath += 2 * REM * seconds_per_tick + need_mob_update = TRUE affected_mob.adjust_disgust(3 * REM * seconds_per_tick) affected_mob.set_slurring_if_lower(3 SECONDS * REM * seconds_per_tick) if(SPT_PROB(5, seconds_per_tick)) to_chat(affected_mob, span_danger("you feel horribly weak.")) - affected_mob.adjustStaminaLoss(5 * REM * seconds_per_tick, 0) + need_mob_update += affected_mob.adjustStaminaLoss(5 * REM * seconds_per_tick, updating_stamina = FALSE) if(SPT_PROB(8, seconds_per_tick)) paralyze_limb(affected_mob) + need_mob_update = TRUE if(SPT_PROB(10, seconds_per_tick)) affected_mob.adjust_confusion(rand(6 SECONDS, 8 SECONDS)) - if(28 to INFINITY) + if(29 to INFINITY) toxpwr = 1.5 - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, BRAIN_DAMAGE_DEATH) + need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, BRAIN_DAMAGE_DEATH) affected_mob.set_silence_if_lower(3 SECONDS * REM * seconds_per_tick) - affected_mob.adjustStaminaLoss(5 * REM * seconds_per_tick, 0) + need_mob_update += affected_mob.adjustStaminaLoss(5 * REM * seconds_per_tick, updating_stamina = FALSE) affected_mob.adjust_disgust(2 * REM * seconds_per_tick) if(SPT_PROB(15, seconds_per_tick)) paralyze_limb(affected_mob) + need_mob_update = TRUE if(SPT_PROB(10, seconds_per_tick)) affected_mob.adjust_confusion(rand(6 SECONDS, 8 SECONDS)) - if(current_cycle >= 38 && !length(traits_not_applied) && SPT_PROB(5, seconds_per_tick) && !affected_mob.undergoing_cardiac_arrest()) + if(current_cycle > 38 && !length(traits_not_applied) && SPT_PROB(5, seconds_per_tick) && !affected_mob.undergoing_cardiac_arrest()) affected_mob.set_heartattack(TRUE) to_chat(affected_mob, span_danger("you feel a burning pain spread throughout your chest, oh no...")) - return ..() + if(need_mob_update) + return UPDATE_MOB_HEALTH /datum/reagent/toxin/tetrodotoxin/proc/paralyze_limb(mob/living/affected_mob) if(!length(traits_not_applied)) @@ -1360,9 +1359,11 @@ traits_not_applied -= added_trait /datum/reagent/toxin/tetrodotoxin/on_mob_metabolize(mob/living/affected_mob) + . = ..() RegisterSignal(affected_mob, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(block_breath)) /datum/reagent/toxin/tetrodotoxin/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() UnregisterSignal(affected_mob, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(block_breath)) // the initial() proc doesn't work for lists. var/list/initial_list = list( @@ -1376,5 +1377,5 @@ /datum/reagent/toxin/tetrodotoxin/proc/block_breath(mob/living/source) SIGNAL_HANDLER - if(current_cycle >= 28) + if(current_cycle > 28) return COMSIG_CARBON_BLOCK_BREATH diff --git a/code/modules/reagents/chemistry/reagents/unique/eigenstasium.dm b/code/modules/reagents/chemistry/reagents/unique/eigenstasium.dm index 70db34460601f..1cb0e6204c2e7 100644 --- a/code/modules/reagents/chemistry/reagents/unique/eigenstasium.dm +++ b/code/modules/reagents/chemistry/reagents/unique/eigenstasium.dm @@ -19,8 +19,8 @@ ph = 3.7 purity = 0.5 creation_purity = 0.5 - inverse_chem = /datum/reagent/impurity/eigenswap - inverse_chem_val = 0 + inverse_chem = /datum/reagent/inverse/eigenswap + inverse_chem_val = 0.1 chemical_flags = REAGENT_DEAD_PROCESS //So if you die with it in your body, you still get teleported back to the location as a corpse data = list("location_created" = null, "ingested" = FALSE)//So we retain the target location and creator between reagent instances ///The creation point assigned during the reaction @@ -77,21 +77,21 @@ return ..() /datum/reagent/eigenstate/on_mob_life(mob/living/carbon/living_mob) + . = ..() if(prob(20)) do_sparks(5,FALSE,living_mob) - return ..() - /datum/reagent/eigenstate/on_mob_delete(mob/living/living_mob) //returns back to original location + . = ..() do_sparks(5,FALSE,living_mob) to_chat(living_mob, span_userdanger("You feel strangely whole again.")) if(!living_mob.reagents.has_reagent(/datum/reagent/stabilizing_agent)) do_teleport(living_mob, location_return, 0, asoundin = 'sound/effects/phasein.ogg') //Teleports home do_sparks(5,FALSE,living_mob) qdel(eigenstate) - return ..() /datum/reagent/eigenstate/overdose_start(mob/living/living_mob) //Overdose, makes you teleport randomly + . = ..() to_chat(living_mob, span_userdanger("You feel like your perspective is being ripped apart as you begin flitting in and out of reality!")) living_mob.set_jitter_if_lower(40 SECONDS) metabolization_rate += 0.5 //So you're not stuck forever teleporting. @@ -101,10 +101,10 @@ return ..() /datum/reagent/eigenstate/overdose_process(mob/living/living_mob) //Overdose, makes you teleport randomly + . = ..() do_sparks(5, FALSE, living_mob) do_teleport(living_mob, get_turf(living_mob), 10, asoundin = 'sound/effects/phasein.ogg') do_sparks(5, FALSE, living_mob) - return ..() //FOR ADDICTION-LIKE EFFECTS, SEE datum/status_effect/eigenstasium diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm index 74e7d40659cbd..c7732e0908a0d 100644 --- a/code/modules/reagents/chemistry/recipes.dm +++ b/code/modules/reagents/chemistry/recipes.dm @@ -46,7 +46,7 @@ var/temp_exponent_factor = 2 /// How sharp the pH exponential curve is (to the power of value) var/ph_exponent_factor = 2 - /// How much the temperature will change (with no intervention) (i.e. for 30u made the temperature will increase by 100, same with 300u. The final temp will always be start + this value, with the exception con beakers with different specific heats) + /// How much the temperature changes per unit of chem used. without REACTION_HEAT_ARBITARY flag the rate of change depends on the holder heat capacity else results are more accurate var/thermic_constant = 50 /// pH change per 1u reaction var/H_ion_release = 0.01 @@ -60,21 +60,6 @@ ///A bitflag var for tagging reagents for the reagent loopup functon var/reaction_tags = NONE -/datum/chemical_reaction/New() - . = ..() - SSticker.OnRoundstart(CALLBACK(src, PROC_REF(update_info))) - -/** - * Updates information during the roundstart - * - * This proc is mainly used by explosives but can be used anywhere else - * You should generally use the special reactions in [/datum/chemical_reaction/randomized] - * But for simple variable edits, like changing the temperature or adding/subtracting required reagents it is better to use this. - */ -/datum/chemical_reaction/proc/update_info() - return - - ///REACTION PROCS /** @@ -161,7 +146,7 @@ var/cached_purity = reagent.purity if((reaction_flags & REACTION_CLEAR_INVERSE) && reagent.inverse_chem) if(reagent.inverse_chem_val > reagent.purity) - holder.remove_reagent(reagent.type, cached_volume, FALSE) + holder.remove_reagent(reagent.type, cached_volume, safety = FALSE) holder.add_reagent(reagent.inverse_chem, cached_volume, FALSE, added_purity = reagent.get_inverse_purity(cached_purity)) return @@ -179,10 +164,11 @@ */ /datum/chemical_reaction/proc/overheated(datum/reagents/holder, datum/equilibrium/equilibrium, step_volume_added) for(var/id in results) - var/datum/reagent/reagent = holder.get_reagent(id) + var/datum/reagent/reagent = holder.has_reagent(id) if(!reagent) return - reagent.volume = round((reagent.volume*0.98), 0.01) //Slowly lower yield per tick + reagent.volume *= 0.98 //Slowly lower yield per tick + holder.update_total() /** * Occurs when a reation is too impure (i.e. it's below purity_min) @@ -199,7 +185,7 @@ /datum/chemical_reaction/proc/overly_impure(datum/reagents/holder, datum/equilibrium/equilibrium, step_volume_added) var/affected_list = results + required_reagents for(var/_reagent in affected_list) - var/datum/reagent/reagent = holder.get_reagent(_reagent) + var/datum/reagent/reagent = holder.has_reagent(_reagent) if(!reagent) continue reagent.purity = clamp((reagent.purity-0.01), 0, 1) //slowly reduce purity of reagents diff --git a/code/modules/reagents/chemistry/recipes/cat2_medicines.dm b/code/modules/reagents/chemistry/recipes/cat2_medicines.dm index ea93cc82e1aa2..c61a773937736 100644 --- a/code/modules/reagents/chemistry/recipes/cat2_medicines.dm +++ b/code/modules/reagents/chemistry/recipes/cat2_medicines.dm @@ -25,7 +25,7 @@ /datum/chemical_reaction/medicine/helbital/overly_impure(datum/reagents/holder, datum/equilibrium/equilibrium, step_volume_added) explode_fire_vortex(holder, equilibrium, 1, 1, "impure") holder.chem_temp += 2.5 - var/datum/reagent/helbital = holder.get_reagent(/datum/reagent/medicine/c2/helbital) + var/datum/reagent/helbital = holder.has_reagent(/datum/reagent/medicine/c2/helbital) if(!helbital) return if(helbital.purity <= 0.25) @@ -41,7 +41,7 @@ /datum/chemical_reaction/medicine/helbital/reaction_finish(datum/reagents/holder, datum/equilibrium/reaction, react_vol) . = ..() - var/datum/reagent/helbital = holder.get_reagent(/datum/reagent/medicine/c2/helbital) + var/datum/reagent/helbital = holder.has_reagent(/datum/reagent/medicine/c2/helbital) if(!helbital) return if(helbital.purity <= 0.1) //So people don't ezmode this by keeping it at min diff --git a/code/modules/reagents/chemistry/recipes/drugs.dm b/code/modules/reagents/chemistry/recipes/drugs.dm index 8822aa787056a..3d25fa5e2b157 100644 --- a/code/modules/reagents/chemistry/recipes/drugs.dm +++ b/code/modules/reagents/chemistry/recipes/drugs.dm @@ -30,7 +30,7 @@ //The less pure it is, the faster it heats up. tg please don't hate me for making your meth even more dangerous /datum/chemical_reaction/methamphetamine/reaction_step(datum/reagents/holder, datum/equilibrium/reaction, delta_t, delta_ph, step_reaction_vol) - var/datum/reagent/meth = holder.get_reagent(/datum/reagent/drug/methamphetamine) + var/datum/reagent/meth = holder.has_reagent(/datum/reagent/drug/methamphetamine) if(!meth)//First step reaction.thermic_mod = (1-delta_ph)*5 return @@ -45,7 +45,7 @@ temp_meth_explosion(holder, equilibrium.reacted_vol) /datum/chemical_reaction/methamphetamine/reaction_finish(datum/reagents/holder, datum/equilibrium/reaction, react_vol) - var/datum/reagent/meth = holder.get_reagent(/datum/reagent/drug/methamphetamine) + var/datum/reagent/meth = holder.has_reagent(/datum/reagent/drug/methamphetamine) if(!meth)//Other procs before this can already blow us up return ..() if(meth.purity < purity_min) diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm index 7c4c9dfe7e951..b3a287707629b 100644 --- a/code/modules/reagents/chemistry/recipes/medicine.dm +++ b/code/modules/reagents/chemistry/recipes/medicine.dm @@ -18,7 +18,7 @@ /datum/chemical_reaction/medicine/rezadone results = list(/datum/reagent/medicine/rezadone = 3) required_reagents = list(/datum/reagent/toxin/carpotoxin = 1, /datum/reagent/cryptobiolin = 1, /datum/reagent/copper = 1) - reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_CLONE + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING /datum/chemical_reaction/medicine/spaceacillin results = list(/datum/reagent/medicine/spaceacillin = 2) @@ -296,18 +296,12 @@ /datum/chemical_reaction/medicine/cryoxadone results = list(/datum/reagent/medicine/cryoxadone = 3) required_reagents = list(/datum/reagent/stable_plasma = 1, /datum/reagent/acetone = 1, /datum/reagent/toxin/mutagen = 1) - reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_PLANT | REACTION_TAG_BRUTE |REACTION_TAG_BURN | REACTION_TAG_TOXIN | REACTION_TAG_OXY | REACTION_TAG_CLONE + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_PLANT | REACTION_TAG_BRUTE |REACTION_TAG_BURN | REACTION_TAG_TOXIN | REACTION_TAG_OXY /datum/chemical_reaction/medicine/pyroxadone results = list(/datum/reagent/medicine/pyroxadone = 2) required_reagents = list(/datum/reagent/medicine/cryoxadone = 1, /datum/reagent/toxin/slimejelly = 1) - reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_BRUTE |REACTION_TAG_BURN | REACTION_TAG_TOXIN | REACTION_TAG_OXY | REACTION_TAG_CLONE - -/datum/chemical_reaction/medicine/clonexadone - results = list(/datum/reagent/medicine/clonexadone = 2) - required_reagents = list(/datum/reagent/medicine/cryoxadone = 1, /datum/reagent/sodium = 1) - required_catalysts = list(/datum/reagent/toxin/plasma = 5) - reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_CLONE + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_BRUTE |REACTION_TAG_BURN | REACTION_TAG_TOXIN | REACTION_TAG_OXY /datum/chemical_reaction/medicine/haloperidol results = list(/datum/reagent/medicine/haloperidol = 5) @@ -359,7 +353,7 @@ new /obj/item/stack/medical/suture/medicated(location) /datum/chemical_reaction/medicine/medmesh - required_reagents = list(/datum/reagent/cellulose = 20, /datum/reagent/consumable/aloejuice = 20, /datum/reagent/space_cleaner/sterilizine = 10) + required_reagents = list(/datum/reagent/cellulose = 10, /datum/reagent/consumable/aloejuice = 20, /datum/reagent/space_cleaner/sterilizine = 10) reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_BURN /datum/chemical_reaction/medicine/medmesh/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm index b3bda10a53a0c..505b46ebc850b 100644 --- a/code/modules/reagents/chemistry/recipes/others.dm +++ b/code/modules/reagents/chemistry/recipes/others.dm @@ -31,7 +31,7 @@ /datum/chemical_reaction/glycerol results = list(/datum/reagent/glycerol = 1) - required_reagents = list(/datum/reagent/consumable/nutriment/fat/oil = 3, /datum/reagent/toxin/acid = 1) + required_reagents = list(/datum/reagent/consumable/nutriment/fat/oil/corn = 3, /datum/reagent/toxin/acid = 1) reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE | REACTION_TAG_EXPLOSIVE /datum/chemical_reaction/sodiumchloride @@ -584,7 +584,7 @@ var/location = get_turf(M) if(iscarbon(M)) if(ismonkey(M)) - M.gib() + M.gib(DROP_ALL_REMAINS) else M.vomit(VOMIT_CATEGORY_BLOOD) new /mob/living/carbon/human/species/monkey(location, TRUE) @@ -730,7 +730,7 @@ reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE /datum/chemical_reaction/metalgen_imprint/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) - var/datum/reagent/metalgen/MM = holder.get_reagent(/datum/reagent/metalgen) + var/datum/reagent/metalgen/MM = holder.has_reagent(/datum/reagent/metalgen) for(var/datum/reagent/R in holder.reagent_list) if(R.material && R.volume >= 40) MM.data["material"] = R.material diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm index 707a1dca350d9..8e74b0ad6f869 100644 --- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm +++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm @@ -15,9 +15,9 @@ /datum/chemical_reaction/reagent_explosion/nitroglycerin/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) - if(holder.has_reagent(/datum/reagent/exotic_stabilizer,round(created_volume / 25, CHEMICAL_QUANTISATION_LEVEL))) + if(holder.has_reagent(/datum/reagent/exotic_stabilizer, created_volume / 25)) return - holder.remove_reagent(/datum/reagent/nitroglycerin, created_volume*2) + holder.remove_reagent(/datum/reagent/nitroglycerin, created_volume * 2) ..() /datum/chemical_reaction/reagent_explosion/nitroglycerin_explosion @@ -35,7 +35,7 @@ /datum/chemical_reaction/reagent_explosion/rdx/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return - holder.remove_reagent(/datum/reagent/rdx, created_volume*2) + holder.remove_reagent(/datum/reagent/rdx, created_volume * 2) ..() /datum/chemical_reaction/reagent_explosion/rdx_explosion @@ -77,11 +77,12 @@ required_temp = 450 strengthdiv = 3 -/datum/chemical_reaction/reagent_explosion/tatp/update_info() - required_temp = 450 + rand(-49,49) //this gets loaded only on round start +/datum/chemical_reaction/reagent_explosion/tatp/New() + . = ..() + required_temp = 450 + rand(-49, 49) /datum/chemical_reaction/reagent_explosion/tatp/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) - if(holder.has_reagent(/datum/reagent/exotic_stabilizer,round(created_volume / 50, CHEMICAL_QUANTISATION_LEVEL))) // we like exotic stabilizer + if(holder.has_reagent(/datum/reagent/exotic_stabilizer, created_volume / 50)) // we like exotic stabilizer return holder.remove_reagent(/datum/reagent/tatp, created_volume) ..() @@ -93,12 +94,12 @@ /datum/chemical_reaction/reagent_explosion/tatp_explosion/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) var/strengthdiv_adjust = created_volume / ( 2100 / initial(strengthdiv)) - strengthdiv = max(initial(strengthdiv) - strengthdiv_adjust + 1.5 ,1.5) //Slightly better than nitroglycerin - . = ..() - return + strengthdiv = max(initial(strengthdiv) - strengthdiv_adjust + 1.5, 1.5) //Slightly better than nitroglycerin + return ..() -/datum/chemical_reaction/reagent_explosion/tatp_explosion/update_info() - required_temp = 550 + rand(-49,49) +/datum/chemical_reaction/reagent_explosion/tatp_explosion/New() + . = ..() + required_temp = 550 + rand(-49, 49) /datum/chemical_reaction/reagent_explosion/penthrite_explosion_epinephrine required_reagents = list(/datum/reagent/medicine/c2/penthrite = 1, /datum/reagent/medicine/epinephrine = 1) @@ -125,16 +126,16 @@ ///special size for anti cult effect var/effective_size = round(created_volume/48) playsound(T, 'sound/effects/pray.ogg', 80, FALSE, effective_size) - for(var/mob/living/simple_animal/revenant/R in get_hearers_in_view(7,T)) + for(var/mob/living/basic/revenant/ghostie in get_hearers_in_view(7,T)) var/deity if(GLOB.deity) deity = GLOB.deity else deity = "Christ" - to_chat(R, span_userdanger("The power of [deity] compels you!")) - R.stun(20) - R.reveal(100) - R.adjustHealth(50) + to_chat(ghostie, span_userdanger("The power of [deity] compels you!")) + ghostie.apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS) + ghostie.apply_status_effect(/datum/status_effect/revenant/revealed, 10 SECONDS) + ghostie.adjust_health(50) for(var/mob/living/carbon/C in get_hearers_in_view(effective_size,T)) if(IS_CULTIST(C)) to_chat(C, span_userdanger("The divine explosion sears you!")) @@ -242,7 +243,7 @@ /datum/chemical_reaction/sorium/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return - holder.remove_reagent(/datum/reagent/sorium, created_volume*4) + holder.remove_reagent(/datum/reagent/sorium, created_volume * 4) var/turf/T = get_turf(holder.my_atom) var/range = clamp(sqrt(created_volume*4), 1, 6) goonchem_vortex(T, 1, range) @@ -265,7 +266,7 @@ /datum/chemical_reaction/liquid_dark_matter/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return - holder.remove_reagent(/datum/reagent/liquid_dark_matter, created_volume*3) + holder.remove_reagent(/datum/reagent/liquid_dark_matter, created_volume * 3) var/turf/T = get_turf(holder.my_atom) var/range = clamp(sqrt(created_volume*3), 1, 6) goonchem_vortex(T, 0, range) @@ -301,7 +302,7 @@ C.Paralyze(60) else C.Stun(100) - holder.remove_reagent(/datum/reagent/flash_powder, created_volume*3) + holder.remove_reagent(/datum/reagent/flash_powder, created_volume * 3) /datum/chemical_reaction/flash_powder_flash required_reagents = list(/datum/reagent/flash_powder = 1) @@ -331,7 +332,7 @@ /datum/chemical_reaction/smoke_powder/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return - holder.remove_reagent(/datum/reagent/smoke_powder, created_volume*3) + holder.remove_reagent(/datum/reagent/smoke_powder, created_volume * 3) var/location = get_turf(holder.my_atom) var/datum/effect_system/fluid_spread/smoke/chem/S = new S.attach(location) @@ -368,7 +369,7 @@ /datum/chemical_reaction/sonic_powder/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return - holder.remove_reagent(/datum/reagent/sonic_powder, created_volume*3) + holder.remove_reagent(/datum/reagent/sonic_powder, created_volume * 3) var/location = get_turf(holder.my_atom) playsound(location, 'sound/effects/bang.ogg', 25, TRUE) for(var/mob/living/carbon/C in get_hearers_in_view(created_volume/3, location)) @@ -488,7 +489,7 @@ determin_ph_range = 0 temp_exponent_factor = 1 ph_exponent_factor = 1 - thermic_constant = -50 //This is the part that cools things down now + thermic_constant = -5 //This is the part that cools things down now H_ion_release = 0 rate_up_lim = 4 purity_min = 0.15 @@ -503,7 +504,7 @@ reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE /datum/chemical_reaction/pyrosium_oxygen/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) - holder.chem_temp += 10*created_volume + holder.expose_temperature(holder.chem_temp + (10 * created_volume), 1) /datum/chemical_reaction/pyrosium results = list(/datum/reagent/pyrosium = 3) @@ -516,8 +517,7 @@ reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE /datum/chemical_reaction/pyrosium/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) - holder.chem_temp = 20 // also cools the fuck down - return + holder.expose_temperature(20, 1) // also cools the fuck down /datum/chemical_reaction/teslium results = list(/datum/reagent/teslium = 3) @@ -542,7 +542,7 @@ reaction_tags = REACTION_TAG_EASY | REACTION_TAG_EXPLOSIVE | REACTION_TAG_DANGEROUS /datum/chemical_reaction/reagent_explosion/teslium_lightning/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) - var/T1 = created_volume * 20 //100 units : Zap 3 times, with powers 2000/5000/12000. Tesla revolvers have a power of 10000 for comparison. + var/T1 = created_volume * 20 //100 units : Zap 3 times, with powers 8e5/2e6/4.8e6. Tesla revolvers have a power of 10000 for comparison. var/T2 = created_volume * 50 var/T3 = created_volume * 120 var/added_delay = 0.5 SECONDS @@ -557,10 +557,11 @@ addtimer(CALLBACK(src, PROC_REF(default_explode), holder, created_volume, modifier, strengthdiv), added_delay) /datum/chemical_reaction/reagent_explosion/teslium_lightning/proc/zappy_zappy(datum/reagents/holder, power) - if(QDELETED(holder.my_atom)) + var/atom/holder_atom = holder.my_atom + if(QDELETED(holder_atom)) return - tesla_zap(holder.my_atom, 7, power, zap_flags) - playsound(holder.my_atom, 'sound/machines/defib_zap.ogg', 50, TRUE) + tesla_zap(source = holder_atom, zap_range = 7, power = power, cutoff = 1e3, zap_flags = zap_flags) + playsound(holder_atom, 'sound/machines/defib_zap.ogg', 50, TRUE) /datum/chemical_reaction/reagent_explosion/teslium_lightning/heat required_temp = 474 @@ -573,7 +574,7 @@ modifier = 1 /datum/chemical_reaction/reagent_explosion/nitrous_oxide/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) - holder.remove_reagent(/datum/reagent/sorium, created_volume*2) + holder.remove_reagent(/datum/reagent/sorium, created_volume * 2) var/turf/turfie = get_turf(holder.my_atom) //generally half as strong as sorium. var/range = clamp(sqrt(created_volume*2), 1, 6) diff --git a/code/modules/reagents/chemistry/recipes/slime_extracts.dm b/code/modules/reagents/chemistry/recipes/slime_extracts.dm index 6b5ddfc14e354..f37f9ebb081c0 100644 --- a/code/modules/reagents/chemistry/recipes/slime_extracts.dm +++ b/code/modules/reagents/chemistry/recipes/slime_extracts.dm @@ -10,7 +10,7 @@ if(!istype(extract)) return FALSE - return extract.Uses > 0 + return extract.extract_uses > 0 /datum/chemical_reaction/slime/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) use_slime_core(holder) @@ -22,7 +22,7 @@ /datum/chemical_reaction/slime/proc/delete_extract(datum/reagents/holder) var/obj/item/slime_extract/M = holder.my_atom - if(M.Uses <= 0 && !results.len) //if the slime doesn't output chemicals + if(M.extract_uses <= 0 && !results.len) //if the slime doesn't output chemicals qdel(M) //Grey @@ -31,8 +31,8 @@ required_container = /obj/item/slime_extract/grey /datum/chemical_reaction/slime/slimespawn/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) - var/mob/living/simple_animal/slime/S = new(get_turf(holder.my_atom), "grey") - S.visible_message(span_danger("Infused with plasma, the core begins to quiver and grow, and a new baby slime emerges from it!")) + var/mob/living/simple_animal/slime/spawning_slime = new(get_turf(holder.my_atom), /datum/slime_type/grey) + spawning_slime.visible_message(span_danger("Infused with plasma, the core begins to quiver and grow, and a new baby slime emerges from it!")) ..() /datum/chemical_reaction/slime/slimeinaprov @@ -319,7 +319,7 @@ slime.docile = FALSE slime.update_name() continue - slime.rabid = 1 + slime.rabid = TRUE slime.visible_message(span_danger("The [slime] is driven into a frenzy!")) ..() @@ -472,7 +472,7 @@ var/turf/T = get_turf(holder.my_atom) new /obj/effect/timestop(T, null, null, null) if(istype(extract)) - if(extract.Uses > 0) + if(extract.extract_uses > 0) var/mob/lastheld = get_mob_by_key(holder.my_atom.fingerprintslast) if(lastheld && !lastheld.equip_to_slot_if_possible(extract, ITEM_SLOT_HANDS, disable_warning = TRUE)) extract.forceMove(get_turf(lastheld)) @@ -527,8 +527,8 @@ S.active = TRUE addtimer(CALLBACK(S, TYPE_PROC_REF(/obj/item/grenade, detonate)), rand(15,60)) else - var/mob/living/simple_animal/slime/random/S = new (get_turf(holder.my_atom)) - S.visible_message(span_danger("Infused with plasma, the core begins to quiver and grow, and a new baby slime emerges from it!")) + var/mob/living/simple_animal/slime/random/random_slime = new (get_turf(holder.my_atom)) + random_slime.visible_message(span_danger("Infused with plasma, the core begins to quiver and grow, and a new baby slime emerges from it!")) ..() /datum/chemical_reaction/slime/slimebomb diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm index 95180392368d2..3ef86fbc4b0fd 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -61,13 +61,16 @@ reagents.add_reagent(/datum/reagent/blood, disease_amount, data) add_initial_reagents() -/obj/item/reagent_containers/examine() +/obj/item/reagent_containers/examine(mob/user) . = ..() if(has_variable_transfer_amount) if(possible_transfer_amounts.len > 1) . += span_notice("Left-click or right-click in-hand to increase or decrease its transfer amount.") else if(possible_transfer_amounts.len) . += span_notice("Left-click or right-click in-hand to view its transfer amount.") + if(isliving(user) && HAS_TRAIT(user, TRAIT_REMOTE_TASTING)) + var/mob/living/living_user = user + living_user.taste(reagents) /obj/item/reagent_containers/create_reagents(max_vol, flags) . = ..() @@ -141,7 +144,7 @@ span_danger("You splash the contents of [src] onto [target][punctuation]"), ignored_mobs = target, ) - + SEND_SIGNAL(target, COMSIG_ATOM_SPLASHED) if (ismob(target)) var/mob/target_mob = target target_mob.show_message( diff --git a/code/modules/reagents/reagent_containers/condiment.dm b/code/modules/reagents/reagent_containers/condiment.dm index 8b8d4470f2fd7..bbd0a84504fac 100644 --- a/code/modules/reagents/reagent_containers/condiment.dm +++ b/code/modules/reagents/reagent_containers/condiment.dm @@ -464,9 +464,12 @@ /// Handles reagents getting added to the condiment pack. /obj/item/reagent_containers/condiment/pack/proc/on_reagent_add(datum/reagents/reagents) SIGNAL_HANDLER - var/main_reagent = reagents.get_master_reagent_id() - if(main_reagent in possible_states) - var/list/temp_list = possible_states[main_reagent] + + var/datum/reagent/main_reagent = reagents.get_master_reagent() + + var/main_reagent_type = main_reagent?.type + if(main_reagent_type in possible_states) + var/list/temp_list = possible_states[main_reagent_type] icon_state = temp_list[1] desc = temp_list[3] else diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm index 7ccc3209ab0d7..bdb104a1cfe0b 100644 --- a/code/modules/reagents/reagent_containers/cups/_cup.dm +++ b/code/modules/reagents/reagent_containers/cups/_cup.dm @@ -125,6 +125,7 @@ var/trans = reagents.trans_to(target, amount_per_transfer_from_this, transferred_by = user) to_chat(user, span_notice("You transfer [trans] unit\s of the solution to [target].")) + SEND_SIGNAL(src, COMSIG_REAGENTS_CUP_TRANSFER_TO, target) else if(target.is_drainable()) //A dispenser. Transfer FROM it TO us. if(!target.reagents.total_volume) @@ -137,6 +138,7 @@ var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this, transferred_by = user) to_chat(user, span_notice("You fill [src] with [trans] unit\s of the contents of [target].")) + SEND_SIGNAL(src, COMSIG_REAGENTS_CUP_TRANSFER_FROM, target) target.update_appearance() @@ -167,34 +169,34 @@ if(hotness && reagents) reagents.expose_temperature(hotness) to_chat(user, span_notice("You heat [name] with [attacking_item]!")) - return + return TRUE //Cooling method if(istype(attacking_item, /obj/item/extinguisher)) var/obj/item/extinguisher/extinguisher = attacking_item if(extinguisher.safety) - return + return TRUE if (extinguisher.reagents.total_volume < 1) to_chat(user, span_warning("\The [extinguisher] is empty!")) - return + return TRUE var/cooling = (0 - reagents.chem_temp) * extinguisher.cooling_power * 2 reagents.expose_temperature(cooling) to_chat(user, span_notice("You cool the [name] with the [attacking_item]!")) playsound(loc, 'sound/effects/extinguish.ogg', 75, TRUE, -3) extinguisher.reagents.remove_all(1) - return + return TRUE if(istype(attacking_item, /obj/item/food/egg)) //breaking eggs var/obj/item/food/egg/attacking_egg = attacking_item if(!reagents) - return - if(reagents.total_volume >= reagents.maximum_volume) + return TRUE + if(reagents.holder_full()) to_chat(user, span_notice("[src] is full.")) else to_chat(user, span_notice("You break [attacking_egg] in [src].")) attacking_egg.reagents.trans_to(src, attacking_egg.reagents.total_volume, transferred_by = user) qdel(attacking_egg) - return + return TRUE return ..() @@ -350,11 +352,8 @@ inhand_icon_state = "bucket" lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - greyscale_colors = "#0085e5" //matches 1:1 with the original sprite color before gag-ification. - greyscale_config = /datum/greyscale_config/buckets - greyscale_config_worn = /datum/greyscale_config/buckets_worn - greyscale_config_inhand_left = /datum/greyscale_config/buckets_inhands_left - greyscale_config_inhand_right = /datum/greyscale_config/buckets_inhands_right + fill_icon_state = "bucket" + fill_icon_thresholds = list(50, 90) custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT * 2) w_class = WEIGHT_CLASS_NORMAL amount_per_transfer_from_this = 20 @@ -380,20 +379,10 @@ fire = 75 acid = 50 -/obj/item/reagent_containers/cup/bucket/Initialize(mapload, vol) - if(greyscale_colors == initial(greyscale_colors)) - set_greyscale(pick(list("#0085e5", COLOR_OFF_WHITE, COLOR_ORANGE_BROWN, COLOR_SERVICE_LIME, COLOR_MOSTLY_PURE_ORANGE, COLOR_FADED_PINK, COLOR_RED, COLOR_YELLOW, COLOR_VIOLET, COLOR_WEBSAFE_DARK_GRAY))) - return ..() - /obj/item/reagent_containers/cup/bucket/wooden name = "wooden bucket" icon_state = "woodbucket" inhand_icon_state = "woodbucket" - greyscale_colors = null - greyscale_config = null - greyscale_config_worn = null - greyscale_config_inhand_left = null - greyscale_config_inhand_right = null custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 2) resistance_flags = FLAMMABLE armor_type = /datum/armor/bucket_wooden @@ -508,6 +497,11 @@ to_chat(user, span_warning("You can't grind this!")) /obj/item/reagent_containers/cup/mortar/proc/grind_item(obj/item/item, mob/living/carbon/human/user) + if(item.flags_1 & HOLOGRAM_1) + to_chat(user, span_notice("You try to grind [item], but it fades away!")) + qdel(item) + return + if(!item.grind(reagents, user)) if(isstack(item)) to_chat(usr, span_notice("[src] attempts to grind as many pieces of [item] as possible.")) @@ -519,6 +513,11 @@ QDEL_NULL(item) /obj/item/reagent_containers/cup/mortar/proc/juice_item(obj/item/item, mob/living/carbon/human/user) + if(item.flags_1 & HOLOGRAM_1) + to_chat(user, span_notice("You try to juice [item], but it fades away!")) + qdel(item) + return + if(!item.juice(reagents, user)) to_chat(user, span_notice("You fail to juice [item].")) return diff --git a/code/modules/reagents/reagent_containers/cups/drinkingglass.dm b/code/modules/reagents/reagent_containers/cups/drinkingglass.dm index c32c83effa96b..adcd2ff79fab9 100644 --- a/code/modules/reagents/reagent_containers/cups/drinkingglass.dm +++ b/code/modules/reagents/reagent_containers/cups/drinkingglass.dm @@ -30,11 +30,12 @@ CALLBACK(src, PROC_REF(on_cup_reset)), \ base_container_type = base_container_type, \ ) + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_cleaned)) /obj/item/reagent_containers/cup/glass/drinkingglass/on_reagent_change(datum/reagents/holder, ...) . = ..() if(!length(reagents.reagent_list)) - renamedByPlayer = FALSE //so new drinks can rename the glass + REMOVE_TRAIT(src, TRAIT_WAS_RENAMED, PEN_LABEL_TRAIT) //so new drinks can rename the glass // Having our icon state change removes fill thresholds /obj/item/reagent_containers/cup/glass/drinkingglass/on_cup_change(datum/glass_style/style) @@ -46,6 +47,22 @@ . = ..() fill_icon_thresholds ||= list(0) +/obj/item/reagent_containers/cup/glass/drinkingglass/examine(mob/user) + . = ..() + if(HAS_TRAIT(src, TRAIT_WAS_RENAMED)) + . += span_notice("This glass has been given a custom name. It can be removed by washing it.") + +/obj/item/reagent_containers/cup/glass/drinkingglass/proc/on_cleaned(obj/source_component, obj/source) + SIGNAL_HANDLER + if(!HAS_TRAIT(src, TRAIT_WAS_RENAMED)) + return + + REMOVE_TRAIT(src, TRAIT_WAS_RENAMED, SHAKER_LABEL_TRAIT) + REMOVE_TRAIT(src, TRAIT_WAS_RENAMED, PEN_LABEL_TRAIT) + name = initial(name) + desc = initial(desc) + update_appearance(UPDATE_NAME | UPDATE_DESC) + //Shot glasses!// // This lets us add shots in here instead of lumping them in with drinks because >logic // // The format for shots is the exact same as iconstates for the drinking glass, except you use a shot glass instead. // @@ -68,13 +85,13 @@ custom_price = PAYCHECK_CREW * 0.4 /obj/item/reagent_containers/cup/glass/drinkingglass/shotglass/update_name(updates) - if(renamedByPlayer) + if(HAS_TRAIT(src, TRAIT_WAS_RENAMED)) return . = ..() name = "[length(reagents.reagent_list) ? "filled " : ""]shot glass" /obj/item/reagent_containers/cup/glass/drinkingglass/shotglass/update_desc(updates) - if(renamedByPlayer) + if(HAS_TRAIT(src, TRAIT_WAS_RENAMED)) return . = ..() if(length(reagents.reagent_list)) @@ -101,6 +118,10 @@ name = "Nuka Cola" list_reagents = list(/datum/reagent/consumable/nuka_cola = 50) +/obj/item/reagent_containers/cup/glass/drinkingglass/filled/pina_colada + name = "Pina Colada" + list_reagents = list(/datum/reagent/consumable/ethanol/pina_colada = 50) + /obj/item/reagent_containers/cup/glass/drinkingglass/filled/half_full name = "half full glass of water" desc = "It's a glass of water. It seems half full. Or is it half empty? You're pretty sure it's full of shit." diff --git a/code/modules/reagents/reagent_containers/cups/drinks.dm b/code/modules/reagents/reagent_containers/cups/drinks.dm index 2cf34da1a6228..cba2f937da49f 100644 --- a/code/modules/reagents/reagent_containers/cups/drinks.dm +++ b/code/modules/reagents/reagent_containers/cups/drinks.dm @@ -15,7 +15,8 @@ /obj/item/reagent_containers/cup/glass/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum, do_splash = TRUE) . = ..() if(!.) //if the bottle wasn't caught - smash(hit_atom, throwingdatum?.thrower, TRUE) + var/mob/thrower = throwingdatum?.get_thrower() + smash(hit_atom, thrower, TRUE) /obj/item/reagent_containers/cup/glass/proc/smash(atom/target, mob/thrower, ranged = FALSE, break_top = FALSE) if(!isGlass) @@ -51,7 +52,7 @@ custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT) has_variable_transfer_amount = FALSE volume = 5 - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY spillable = TRUE resistance_flags = FIRE_PROOF isGlass = FALSE @@ -227,8 +228,8 @@ custom_price = PAYCHECK_LOWER * 0.8 /obj/item/reagent_containers/cup/glass/waterbottle/Initialize(mapload) - . = ..() cap_overlay = mutable_appearance(cap_icon, cap_icon_state) + . = ..() if(cap_on) spillable = FALSE update_appearance() @@ -314,9 +315,9 @@ return if(prob(flip_chance)) // landed upright src.visible_message(span_notice("[src] lands upright!")) - if(throwingdatum.thrower) - var/mob/living/living_thrower = throwingdatum.thrower - living_thrower.add_mood_event("bottle_flip", /datum/mood_event/bottle_flip) + var/mob/living/thrower = throwingdatum?.get_thrower() + if(thrower) + thrower.add_mood_event("bottle_flip", /datum/mood_event/bottle_flip) else // landed on it's side animate(src, transform = matrix(prob(50)? 90 : -90, MATRIX_ROTATE), time = 3, loop = 0) @@ -425,6 +426,7 @@ // itself), in Chemistry-Recipes.dm (for the reaction that changes the components into the drink), and here (for the drinking glass // icon states. + /obj/item/reagent_containers/cup/glass/shaker name = "shaker" desc = "A metal shaker to mix drinks in." @@ -434,14 +436,86 @@ amount_per_transfer_from_this = 10 volume = 100 isGlass = FALSE + /// Whether or not poured drinks should use custom names and descriptions + var/using_custom_drinks = FALSE + /// Name custom drinks will have + var/custom_drink_name = "Custom drink" + /// Description custom drinks will have + var/custom_drink_desc = "Mixed by your favourite bartender!" /obj/item/reagent_containers/cup/glass/shaker/Initialize(mapload) . = ..() + register_context() if(prob(10)) name = "\improper Nanotrasen 20th Anniversary Shaker" desc += " It has an emblazoned Nanotrasen logo on it." icon_state = "shaker_n" +/obj/item/reagent_containers/cup/glass/shaker/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + context[SCREENTIP_CONTEXT_ALT_LMB] = "[using_custom_drinks ? "Disable" : "Enable"] custom drinks" + return CONTEXTUAL_SCREENTIP_SET + +/obj/item/reagent_containers/cup/glass/shaker/examine(mob/user) + . = ..() + . += span_notice("Alt-click to [using_custom_drinks ? "disable" : "enable"] custom drink naming") + if(using_custom_drinks) + . += span_notice("Drinks poured from this shaker will have the following name: [custom_drink_name]") + . += span_notice("Drinks poured from this shaker will have the following description: [custom_drink_desc]") + +/obj/item/reagent_containers/cup/glass/shaker/AltClick(mob/user) + . = ..() + if(!user.can_perform_action(src, NEED_HANDS|FORBID_TELEKINESIS_REACH)) + return + + if(using_custom_drinks) + using_custom_drinks = FALSE + disable_custom_drinks() + balloon_alert(user, "custom drinks disabled") + return + + var/new_name = reject_bad_text(tgui_input_text(user, "Drink name", "Set drink name", custom_drink_name, 45, FALSE), 64) + if(!new_name) + balloon_alert(user, "invalid drink name!") + using_custom_drinks = FALSE + return + + if(!user.can_perform_action(src, NEED_HANDS|FORBID_TELEKINESIS_REACH)) + return + + var/new_desc = reject_bad_text(tgui_input_text(user, "Drink description", "Set drink description", custom_drink_desc, 64, TRUE), 128) + if(!new_desc) + balloon_alert(user, "invalid drink description!") + using_custom_drinks = FALSE + return + + if(!user.can_perform_action(src, NEED_HANDS|FORBID_TELEKINESIS_REACH)) + return + + using_custom_drinks = TRUE + custom_drink_name = new_name + custom_drink_desc = new_desc + + enable_custom_drinks() + balloon_alert(user, "now pouring custom drinks") + +/obj/item/reagent_containers/cup/glass/shaker/proc/enable_custom_drinks() + RegisterSignal(src, COMSIG_REAGENTS_CUP_TRANSFER_TO, PROC_REF(handle_transfer)) + +/obj/item/reagent_containers/cup/glass/shaker/proc/disable_custom_drinks() + UnregisterSignal(src, COMSIG_REAGENTS_CUP_TRANSFER_TO) + +/obj/item/reagent_containers/cup/glass/shaker/proc/handle_transfer(atom/origin, atom/target) + SIGNAL_HANDLER + // Should only work on drinking/shot glasses + if(!istype(target, /obj/item/reagent_containers/cup/glass/drinkingglass)) + return + + var/obj/item/reagent_containers/cup/glass/drinkingglass/target_glass = target + target_glass.name = custom_drink_name + target_glass.desc = custom_drink_desc + ADD_TRAIT(target_glass, TRAIT_WAS_RENAMED, SHAKER_LABEL_TRAIT) + /obj/item/reagent_containers/cup/glass/flask name = "flask" desc = "Every good spaceman knows it's a good idea to bring along a couple of pints of whiskey wherever they go." diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm index e2f445c6a1a71..83754dd571f3e 100644 --- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm +++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm @@ -40,6 +40,12 @@ tool_behaviour = TOOL_ROLLINGPIN // Used to knock out the Chef. toolspeed = 1.3 //it's a little awkward to use, but it's a cylinder alright. +/obj/item/reagent_containers/cup/glass/bottle/Initialize(mapload, vol) + . = ..() + AddComponent(/datum/component/slapcrafting,\ + slapcraft_recipes = list(/datum/crafting_recipe/molotov)\ + ) + /obj/item/reagent_containers/cup/glass/bottle/small name = "small glass bottle" desc = "This blank bottle is unyieldingly anonymous, offering no clues to its contents." @@ -473,7 +479,7 @@ /obj/item/reagent_containers/cup/glass/bottle/amaretto name = "Luini Amaretto" - desc = "A gentle and syrup like drink, tastes of almonds and apricots" + desc = "A gentle, syrupy drink that tastes of almonds and apricots." icon_state = "disaronno" list_reagents = list(/datum/reagent/consumable/ethanol/amaretto = 100) @@ -595,9 +601,11 @@ if(!do_after(user, 2 SECONDS, src)) //takes longer because you are supposed to take the foil off the bottle first return - ///The bonus to success chance that the user gets for being a command role - var/command_bonus = user.mind?.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND ? 20 : 0 - ///The bonus to success chance that the user gets for having a sabrage skillchip installed/otherwise having the trait through other means + //The bonus to success chance that the user gets for being a command role + var/obj/item/organ/internal/liver/liver = user.get_organ_slot(ORGAN_SLOT_LIVER) + var/command_bonus = (!isnull(liver) && HAS_TRAIT(liver, TRAIT_ROYAL_METABOLISM)) ? 20 : 0 + + //The bonus to success chance that the user gets for having a sabrage skillchip installed/otherwise having the trait through other means var/skillchip_bonus = HAS_TRAIT(user, TRAIT_SABRAGE_PRO) ? 35 : 0 //calculate success chance. example: captain's sabre - 15 force = 75% chance var/sabrage_chance = (attacking_item.force * sabrage_success_percentile) + command_bonus + skillchip_bonus @@ -797,10 +805,10 @@ if(istype(contained_reagent, accelerant_type)) firestarter = 1 break + ..() if(firestarter && active) target.fire_act() new /obj/effect/hotspot(get_turf(target)) - ..() /obj/item/reagent_containers/cup/glass/bottle/molotov/attackby(obj/item/I, mob/user, params) if(I.get_temperature() && !active) diff --git a/code/modules/reagents/reagent_containers/cups/soda.dm b/code/modules/reagents/reagent_containers/cups/soda.dm index 4908942b2c068..5bf0eb782c54d 100644 --- a/code/modules/reagents/reagent_containers/cups/soda.dm +++ b/code/modules/reagents/reagent_containers/cups/soda.dm @@ -21,6 +21,12 @@ /// If the can hasn't been opened yet, this is the measure of how fizzed up it is from being shaken or thrown around. When opened, this is rolled as a percentage chance to burst var/fizziness = 0 +/obj/item/reagent_containers/cup/soda_cans/Initialize(mapload, vol) + . = ..() + AddComponent(/datum/component/slapcrafting,\ + slapcraft_recipes = list(/datum/crafting_recipe/improv_explosive)\ + ) + /obj/item/reagent_containers/cup/soda_cans/random/Initialize(mapload) ..() var/T = pick(subtypesof(/obj/item/reagent_containers/cup/soda_cans) - /obj/item/reagent_containers/cup/soda_cans/random) diff --git a/code/modules/reagents/reagent_containers/dropper.dm b/code/modules/reagents/reagent_containers/dropper.dm index cf01c2cd4c2a0..beb6f3e6314cd 100644 --- a/code/modules/reagents/reagent_containers/dropper.dm +++ b/code/modules/reagents/reagent_containers/dropper.dm @@ -20,7 +20,7 @@ return if(reagents.total_volume > 0) - if(target.reagents.total_volume >= target.reagents.maximum_volume) + if(target.reagents.holder_full()) to_chat(user, span_notice("[target] is full.")) return @@ -29,7 +29,7 @@ return var/trans = 0 - var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) + var/fraction = min(amount_per_transfer_from_this / reagents.total_volume, 1) if(ismob(target)) if(ishuman(target)) diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm index 63648375045a6..af246ae6e67dd 100644 --- a/code/modules/reagents/reagent_containers/hypospray.dm +++ b/code/modules/reagents/reagent_containers/hypospray.dm @@ -17,6 +17,8 @@ var/infinite = FALSE /// If TRUE, won't play a noise when injecting. var/stealthy = FALSE + /// If TRUE, the hypospray will be permanently unusable. + var/used_up = FALSE /obj/item/reagent_containers/hypospray/attack_paw(mob/user, list/modifiers) return attack_hand(user, modifiers) @@ -26,8 +28,8 @@ ///Handles all injection checks, injection and logging. /obj/item/reagent_containers/hypospray/proc/inject(mob/living/affected_mob, mob/user) - if(!reagents.total_volume) - to_chat(user, span_warning("[src] is empty!")) + if(used_up) + to_chat(user, span_warning("[src] tip is broken and is now unusable!")) return FALSE if(!iscarbon(affected_mob)) return FALSE @@ -39,7 +41,7 @@ var/contained = english_list(injected) log_combat(user, affected_mob, "attempted to inject", src, "([contained])") - if(reagents.total_volume && (ignore_flags || affected_mob.try_inject(user, injection_flags = INJECT_TRY_SHOW_ERROR_MESSAGE))) // Ignore flag should be checked first or there will be an error message. + if(!used_up && (ignore_flags || affected_mob.try_inject(user, injection_flags = INJECT_TRY_SHOW_ERROR_MESSAGE))) // Ignore flag should be checked first or there will be an error message. to_chat(affected_mob, span_warning("You feel a tiny prick!")) to_chat(user, span_notice("You inject [affected_mob] with [src].")) if(!stealthy) @@ -61,6 +63,8 @@ /obj/item/reagent_containers/hypospray/cmo + volume = 60 + possible_transfer_amounts = list(1,3,5) list_reagents = list(/datum/reagent/medicine/omnizine = 30) resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF @@ -132,8 +136,8 @@ /obj/item/reagent_containers/hypospray/medipen/inject(mob/living/affected_mob, mob/user) . = ..() - if(.) - reagents.maximum_volume = 0 //Makes them useless afterwards + if(. && !reagents.total_volume) + used_up = TRUE //Makes them useless afterwards reagents.flags = NONE update_appearance() diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm index 7c7f97f0cc7fc..fd2ab0eb4931a 100644 --- a/code/modules/reagents/reagent_containers/pill.dm +++ b/code/modules/reagents/reagent_containers/pill.dm @@ -47,13 +47,14 @@ return on_consumption(M, user) ///Runs the consumption code, can be overriden for special effects -/obj/item/reagent_containers/pill/proc/on_consumption(mob/M, mob/user) +/obj/item/reagent_containers/pill/proc/on_consumption(mob/consumer, mob/giver) if(icon_state == "pill4" && prob(5)) //you take the red pill - you stay in Wonderland, and I show you how deep the rabbit hole goes - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), M, span_notice("[pick(strings(REDPILL_FILE, "redpill_questions"))]")), 50) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), consumer, span_notice("[pick(strings(REDPILL_FILE, "redpill_questions"))]")), 50) if(apply_type == INGEST) - SEND_SIGNAL(src, COMSIG_PILL_CONSUMED, eater = M, feeder = user) + SEND_SIGNAL(consumer, COMSIG_LIVING_PILL_CONSUMED, src, giver) + SEND_SIGNAL(src, COMSIG_PILL_CONSUMED, eater = consumer, feeder = giver) if(reagents.total_volume) - reagents.trans_to(M, reagents.total_volume, transferred_by = user, methods = apply_type) + reagents.trans_to(consumer, reagents.total_volume, transferred_by = giver, methods = apply_type) qdel(src) return TRUE diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index 20f72688644ef..52968cac22fe1 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -50,18 +50,23 @@ to_chat(user, span_warning("Not enough left!")) return - spray(target, user) + if(proximity_flag && (target.density || ismob(target))) + // If we're spraying an adjacent mob or a dense object, we start the spray on ITS tile rather than OURs + // This is so we can use a spray bottle to clean stuff like windows without getting blocked by passflags + spray(target, user, get_turf(target)) + else + spray(target, user) - playsound(src.loc, spray_sound, 50, TRUE, -6) + playsound(src, spray_sound, 50, TRUE, -6) user.changeNext_move(CLICK_CD_RANGE*2) user.newtonian_move(get_dir(target, user)) return /// Handles creating a chem puff that travels towards the target atom, exposing reagents to everything it hits on the way. -/obj/item/reagent_containers/spray/proc/spray(atom/target, mob/user) +/obj/item/reagent_containers/spray/proc/spray(atom/target, mob/user, turf/start_turf = get_turf(src)) var/range = max(min(current_range, get_dist(src, target)), 1) - var/obj/effect/decal/chempuff/reagent_puff = new /obj/effect/decal/chempuff(get_turf(src)) + var/obj/effect/decal/chempuff/reagent_puff = new(start_turf) reagent_puff.create_reagents(amount_per_transfer_from_this) var/puff_reagent_left = range //how many turf, mob or dense objet we can react with before we consider the chem puff consumed @@ -74,9 +79,7 @@ var/wait_step = max(round(2+3/range), 2) var/puff_reagent_string = reagent_puff.reagents.get_reagent_log_string() - var/turf/src_turf = get_turf(src) - - log_combat(user, src_turf, "fired a puff of reagents from", src, addition="with a range of \[[range]\], containing [puff_reagent_string].") + log_combat(user, start_turf, "fired a puff of reagents from", src, addition="with a range of \[[range]\], containing [puff_reagent_string].") user.log_message("fired a puff of reagents from \a [src] with a range of \[[range]\] and containing [puff_reagent_string].", LOG_ATTACK) // do_spray includes a series of step_towards and sleeps. As a result, it will handle deletion of the chempuff. @@ -84,11 +87,20 @@ /// Handles exposing atoms to the reagents contained in a spray's chempuff. Deletes the chempuff when it's completed. /obj/item/reagent_containers/spray/proc/do_spray(atom/target, wait_step, obj/effect/decal/chempuff/reagent_puff, range, puff_reagent_left, mob/user) - var/datum/move_loop/our_loop = SSmove_manager.move_towards_legacy(reagent_puff, target, wait_step, timeout = range * wait_step, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY) reagent_puff.user = user reagent_puff.sprayer = src reagent_puff.lifetime = puff_reagent_left reagent_puff.stream = stream_mode + + var/turf/target_turf = get_turf(target) + var/turf/start_turf = get_turf(reagent_puff) + if(target_turf == start_turf) // Don't need to bother movelooping if we don't move + reagent_puff.setDir(user.dir) + reagent_puff.spray_down_turf(target_turf) + reagent_puff.end_life() + return + + var/datum/move_loop/our_loop = SSmove_manager.move_towards_legacy(reagent_puff, target, wait_step, timeout = range * wait_step, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY) reagent_puff.RegisterSignal(our_loop, COMSIG_QDELETING, TYPE_PROC_REF(/obj/effect/decal/chempuff, loop_ended)) reagent_puff.RegisterSignal(our_loop, COMSIG_MOVELOOP_POSTPROCESS, TYPE_PROC_REF(/obj/effect/decal/chempuff, check_move)) @@ -181,7 +193,7 @@ if(do_after(user, 3 SECONDS, user)) if(reagents.total_volume >= amount_per_transfer_from_this)//if not empty user.visible_message(span_suicide("[user] pulls the trigger!")) - spray(user) + spray(user, user) return BRUTELOSS else user.visible_message(span_suicide("[user] pulls the trigger...but \the [src] is empty!")) diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm index beed3d17ba6f4..4366482460e77 100644 --- a/code/modules/reagents/reagent_containers/syringes.dm +++ b/code/modules/reagents/reagent_containers/syringes.dm @@ -145,7 +145,9 @@ /obj/item/reagent_containers/syringe/update_overlays() . = ..() - . += update_reagent_overlay() + var/list/reagent_overlays = update_reagent_overlay() + if(reagent_overlays) + . += reagent_overlays /// Returns a list of overlays to add that relate to the reagents inside the syringe /obj/item/reagent_containers/syringe/proc/update_reagent_overlay() diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index d1b7afd4b7800..5dd5e8b93dd22 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -31,6 +31,8 @@ var/mutable_appearance/assembliesoverlay /// The person who attached an assembly to this dispenser, for bomb logging purposes var/last_rigger = "" + /// is it climbable? some of our wall-mounted dispensers should not have this + var/climbable = FALSE // This check is necessary for assemblies to automatically detect that we are compatible /obj/structure/reagent_dispensers/IsSpecialAssembly() @@ -53,6 +55,9 @@ if(icon_state == "water" && check_holidays(APRIL_FOOLS)) icon_state = "water_fools" + if(climbable) + AddElement(/datum/element/climbable, climb_time = 4 SECONDS, climb_stun = 4 SECONDS) + AddElement(/datum/element/elevation, pixel_shift = 14) /obj/structure/reagent_dispensers/examine(mob/user) . = ..() @@ -178,7 +183,7 @@ // It did not account for how much fuel was actually in the tank at all, just the size of the tank. // I encourage others to better scale these numbers in the future. // As it stands this is a minor nerf in exchange for an easy bombing technique working that has been broken for a while. - switch(volatiles.volume) + switch(fuel_amt) if(25 to 150) explosion(src, light_impact_range = 1, flame_range = 2) if(150 to 300) @@ -192,7 +197,7 @@ qdel(src) /obj/structure/reagent_dispensers/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) if(!disassembled) boom() else @@ -222,7 +227,7 @@ balloon_alert(user, "[leaking ? "opened" : "closed"] [src]'s tap") user.log_message("[leaking ? "opened" : "closed"] [src].", LOG_GAME) tank_leak() - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/structure/reagent_dispensers/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) . = ..() @@ -233,6 +238,7 @@ desc = "A water tank." icon_state = "water" openable = TRUE + climbable = TRUE /obj/structure/reagent_dispensers/watertank/high name = "high-capacity water tank" @@ -247,6 +253,7 @@ reagent_id = /datum/reagent/firefighting_foam tank_volume = 500 openable = TRUE + climbable = TRUE /obj/structure/reagent_dispensers/fueltank name = "fuel tank" @@ -255,6 +262,7 @@ reagent_id = /datum/reagent/fuel openable = TRUE accepts_rig = TRUE + climbable = TRUE /obj/structure/reagent_dispensers/fueltank/Initialize(mapload) . = ..() @@ -419,7 +427,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/reagent_dispensers/wall/virusfood, 30 /obj/structure/reagent_dispensers/plumbed/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/structure/reagent_dispensers/plumbed/storage name = "stationary storage tank" diff --git a/code/modules/reagents/withdrawal/generic_addictions.dm b/code/modules/reagents/withdrawal/generic_addictions.dm index 5c9dd636309cf..de7cf968ae15c 100644 --- a/code/modules/reagents/withdrawal/generic_addictions.dm +++ b/code/modules/reagents/withdrawal/generic_addictions.dm @@ -74,8 +74,8 @@ /datum/addiction/hallucinogens/withdrawal_enters_stage_2(mob/living/carbon/affected_carbon) . = ..() var/atom/movable/plane_master_controller/game_plane_master_controller = affected_carbon.hud_used.plane_master_controllers[PLANE_MASTERS_GAME] - game_plane_master_controller.add_filter("hallucinogen_wave", 10, wave_filter(300, 300, 3, 0, WAVE_SIDEWAYS)) game_plane_master_controller.add_filter("hallucinogen_blur", 10, angular_blur_filter(0, 0, 3)) + game_plane_master_controller.add_filter("hallucinogen_wave", 10, wave_filter(300, 300, 3, 0, WAVE_SIDEWAYS)) /datum/addiction/hallucinogens/withdrawal_enters_stage_3(mob/living/carbon/affected_carbon) diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm index cb1a0800c513a..14108f22bd93d 100644 --- a/code/modules/recycling/conveyor.dm +++ b/code/modules/recycling/conveyor.dm @@ -248,7 +248,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) /obj/machinery/conveyor/proc/conveyable_exit(datum/source, atom/convayable, direction) SIGNAL_HANDLER var/has_conveyor = neighbors["[direction]"] - if(!has_conveyor || !isturf(convayable.loc)) //If you've entered something on us, stop moving + if(convayable.z != z || !has_conveyor || !isturf(convayable.loc)) //If you've entered something on us, stop moving SSmove_manager.stop_looping(convayable, SSconveyors) /obj/machinery/conveyor/proc/start_conveying(atom/movable/moving) diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm index 26ec26d46d4c2..b966ca1db6caa 100644 --- a/code/modules/recycling/disposal/bin.dm +++ b/code/modules/recycling/disposal/bin.dm @@ -61,7 +61,7 @@ RegisterSignal(src, COMSIG_RAT_INTERACT, PROC_REF(on_rat_rummage)) RegisterSignal(src, COMSIG_STORAGE_DUMP_CONTENT, PROC_REF(on_storage_dump)) var/static/list/loc_connections = list( - COMSIG_CARBON_DISARM_COLLIDE = PROC_REF(trash_carbon), + COMSIG_LIVING_DISARM_COLLIDE = PROC_REF(trash_living), COMSIG_TURF_RECEIVE_SWEEPED_ITEMS = PROC_REF(ready_for_trash), ) AddElement(/datum/element/connect_loc, loc_connections) @@ -276,35 +276,31 @@ H.vent_gas(loc) qdel(H) -/obj/machinery/disposal/deconstruct(disassembled = TRUE) +/obj/machinery/disposal/on_deconstruction(disassembled) var/turf/T = loc - if(!(flags_1 & NODECONSTRUCT_1)) - if(stored) - var/obj/structure/disposalconstruct/construct = stored - stored = null - construct.forceMove(T) - transfer_fingerprints_to(construct) - construct.set_anchored(FALSE) - construct.set_density(TRUE) - construct.update_appearance() + if(stored) + var/obj/structure/disposalconstruct/construct = stored + stored = null + construct.forceMove(T) + transfer_fingerprints_to(construct) + construct.set_anchored(FALSE) + construct.set_density(TRUE) + construct.update_appearance() for(var/atom/movable/AM in src) //out, out, darned crowbar! AM.forceMove(T) - ..() ///How disposal handles getting a storage dump from a storage object -/obj/machinery/disposal/proc/on_storage_dump(datum/source, obj/item/storage_source, mob/user) +/obj/machinery/disposal/proc/on_storage_dump(datum/source, datum/storage/storage, mob/user) SIGNAL_HANDLER . = STORAGE_DUMP_HANDLED - to_chat(user, span_notice("You dump out [storage_source] into [src].")) + to_chat(user, span_notice("You dump out [storage.parent] into [src].")) - for(var/obj/item/to_dump in storage_source) - if(to_dump.loc != storage_source) - continue - if(user.active_storage != storage_source && to_dump.on_found(user)) + for(var/obj/item/to_dump in storage.real_location) + if(user.active_storage != storage && to_dump.on_found(user)) return - if(!storage_source.atom_storage.attempt_remove(to_dump, src, silent = TRUE)) + if(!storage.attempt_remove(to_dump, src, silent = TRUE)) continue to_dump.pixel_x = to_dump.base_pixel_x + rand(-5, 5) to_dump.pixel_y = to_dump.base_pixel_y + rand(-5, 5) @@ -386,7 +382,8 @@ /obj/machinery/disposal/bin/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) if(isitem(AM) && AM.CanEnterDisposals()) - if((throwingdatum.thrower && HAS_TRAIT(throwingdatum.thrower, TRAIT_THROWINGARM)) || prob(75)) + var/mob/thrower = throwingdatum?.get_thrower() + if((thrower && HAS_TRAIT(thrower, TRAIT_THROWINGARM)) || prob(75)) AM.forceMove(src) visible_message(span_notice("[AM] lands in [src].")) update_appearance() @@ -402,13 +399,6 @@ pressure_charging = TRUE update_appearance() -/obj/machinery/disposal/bin/update_appearance(updates) - . = ..() - if((machine_stat & (BROKEN|NOPOWER)) || panel_open) - luminosity = 0 - return - luminosity = 1 - /obj/machinery/disposal/bin/update_overlays() . = ..() if(machine_stat & BROKEN) @@ -557,17 +547,17 @@ return COMPONENT_RAT_INTERACTED /// Handles a carbon mob getting shoved into the disposal bin -/obj/machinery/disposal/proc/trash_carbon(datum/source, mob/living/carbon/shover, mob/living/carbon/target, shove_blocked) +/obj/machinery/disposal/proc/trash_living(datum/source, mob/living/shover, mob/living/target, shove_flags, obj/item/weapon) SIGNAL_HANDLER - if(!shove_blocked) + if((shove_flags & SHOVE_KNOCKDOWN_BLOCKED) || !(shove_flags & SHOVE_BLOCKED)) return target.Knockdown(SHOVE_KNOCKDOWN_SOLID) target.forceMove(src) target.visible_message(span_danger("[shover.name] shoves [target.name] into \the [src]!"), - span_userdanger("You're shoved into \the [src] by [target.name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src) + span_userdanger("You're shoved into \the [src] by [target.name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, shover) to_chat(src, span_danger("You shove [target.name] into \the [src]!")) - log_combat(shover, target, "shoved", "into [src] (disposal bin)") - return COMSIG_CARBON_SHOVE_HANDLED + log_combat(shover, target, "shoved", "into [src] (disposal bin)[weapon ? " with [weapon]" : ""]") + return COMSIG_LIVING_SHOVE_HANDLED ///Called when a push broom is trying to sweep items onto the turf this object is standing on. Garbage will be moved inside. /obj/machinery/disposal/proc/ready_for_trash(datum/source, obj/item/pushbroom/broom, mob/user, list/items_to_sweep) diff --git a/code/modules/recycling/disposal/construction.dm b/code/modules/recycling/disposal/construction.dm index b1556ca679f88..4b8fef129244e 100644 --- a/code/modules/recycling/disposal/construction.dm +++ b/code/modules/recycling/disposal/construction.dm @@ -33,12 +33,12 @@ pipename = initial(pipe_type.name) - AddComponent(/datum/component/simple_rotation, AfterRotation = CALLBACK(src, PROC_REF(AfterRotation))) + AddComponent(/datum/component/simple_rotation, post_rotation = CALLBACK(src, PROC_REF(post_rotation))) AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) if(flip) var/datum/component/simple_rotation/rotcomp = GetComponent(/datum/component/simple_rotation) - rotcomp.Rotate(usr, ROTATION_FLIP) // this only gets used by pipes created by RPDs or pipe dispensers + rotcomp.rotate(usr, ROTATION_FLIP) // this only gets used by pipes created by RPDs or pipe dispensers update_appearance(UPDATE_ICON) @@ -86,7 +86,7 @@ dpdir |= REVERSE_DIR(dir) return dpdir -/obj/structure/disposalconstruct/proc/AfterRotation(mob/user, degrees) +/obj/structure/disposalconstruct/proc/post_rotation(mob/user, degrees) if(degrees == ROTATION_FLIP) var/obj/structure/disposalpipe/temp = pipe_type if(initial(temp.flip_type)) diff --git a/code/modules/recycling/disposal/eject.dm b/code/modules/recycling/disposal/eject.dm index febd4f5d604e0..523f034703c10 100644 --- a/code/modules/recycling/disposal/eject.dm +++ b/code/modules/recycling/disposal/eject.dm @@ -2,11 +2,33 @@ * General proc used to expel a holder's contents through src (for bins holder is also the src). */ /obj/proc/pipe_eject(obj/holder, direction, throw_em = TRUE, turf/target, throw_range = 5, throw_speed = 1) - var/turf/src_T = get_turf(src) - for(var/A in holder) - var/atom/movable/AM = A - AM.forceMove(src_T) - SEND_SIGNAL(AM, COMSIG_MOVABLE_PIPE_EJECTING, direction) - if(throw_em && !QDELETED(AM)) - var/turf/T = target || get_offset_target_turf(loc, rand(5)-rand(5), rand(5)-rand(5)) - AM.throw_at(T, throw_range, throw_speed) + var/turf/origin_turf = get_turf(src) + var/turf/target_turf + if(isnull(target)) // done up here as a safety + target_turf = get_offset_target_turf(loc, rand(5) - rand(5), rand(5) - rand(5)) + else + target_turf = target + + if(QDELETED(origin_turf)) + stack_trace("pipe_eject() attempted to operate on a qdeleted turf! In order to avoid sending things to nullspace, we are going to send everything directly to the target turf instead.") + origin_turf = target_turf + + var/list/contents_to_eject = holder.contents + var/list/contents_to_throw = list() + + for(var/atom/movable/thing in contents_to_eject) + thing.forceMove(origin_turf) + SEND_SIGNAL(thing, COMSIG_MOVABLE_PIPE_EJECTING, direction) + if(QDELETED(thing)) + continue + + contents_to_throw += thing + + if(!throw_em) + return + + for(var/atom/movable/throwable as anything in contents_to_throw) + if(isnull(target)) // we want the thrown things to be spread out a bit if we weren't given a target + target_turf = get_offset_target_turf(loc, rand(5) - rand(5), rand(5) - rand(5)) + + throwable.throw_at(target_turf, throw_range, throw_speed) diff --git a/code/modules/recycling/disposal/holder.dm b/code/modules/recycling/disposal/holder.dm index cf9ae8f6b4e10..2d964d0f8fb3f 100644 --- a/code/modules/recycling/disposal/holder.dm +++ b/code/modules/recycling/disposal/holder.dm @@ -42,7 +42,7 @@ if(M.client) M.reset_perspective(src) hasmob = TRUE - RegisterSignal(M, COMSIG_LIVING_RESIST, PROC_REF(struggle_prep), M) + RegisterSignal(M, COMSIG_LIVING_RESIST, PROC_REF(struggle_prep)) //Checks 1 contents level deep. This means that players can be sent through disposals mail... //...but it should require a second person to open the package. (i.e. person inside a wrapped locker) @@ -179,12 +179,12 @@ /// Merge two holder objects, used when a holder meets a stuck holder /obj/structure/disposalholder/proc/merge(obj/structure/disposalholder/other) - for(var/A in other) - var/atom/movable/AM = A - AM.forceMove(src) // move everything in other holder to this one - if(ismob(AM)) - var/mob/M = AM - M.reset_perspective(src) // if a client mob, update eye to follow this holder + for(var/atom/movable/movable as anything in other) + movable.forceMove(src) // move everything in other holder to this one + if(ismob(movable)) + var/mob/mob = movable + mob.reset_perspective(src) // if a client mob, update eye to follow this holder + RegisterSignal(mob, COMSIG_LIVING_RESIST, PROC_REF(struggle_prep)) hasmob = TRUE if(destinationTag == 0 && other.destinationTag != 0) destinationTag = other.destinationTag diff --git a/code/modules/recycling/disposal/outlet.dm b/code/modules/recycling/disposal/outlet.dm index 4327567fb1746..4fa9f1e0aab15 100644 --- a/code/modules/recycling/disposal/outlet.dm +++ b/code/modules/recycling/disposal/outlet.dm @@ -47,6 +47,11 @@ /obj/structure/disposaloutlet/Destroy() if(trunk) + // preemptively expel the contents from the trunk + // in case the outlet is deleted before expel_holder could be called. + var/obj/structure/disposalholder/holder = locate() in trunk + if(holder) + trunk.expel(holder) trunk.linked = null trunk = null QDEL_NULL(stored) @@ -60,15 +65,15 @@ if((start_eject + 30) < world.time) start_eject = world.time playsound(src, 'sound/machines/warning-buzzer.ogg', 50, FALSE, FALSE) - addtimer(CALLBACK(src, PROC_REF(expel_holder), H, TRUE), 20) + addtimer(CALLBACK(src, PROC_REF(expel_holder), H, TRUE), 2 SECONDS) else - addtimer(CALLBACK(src, PROC_REF(expel_holder), H), 20) + addtimer(CALLBACK(src, PROC_REF(expel_holder), H), 2 SECONDS) /obj/structure/disposaloutlet/proc/expel_holder(obj/structure/disposalholder/H, playsound=FALSE) if(playsound) playsound(src, 'sound/machines/hiss.ogg', 50, FALSE, FALSE) - if(!H) + if(QDELETED(H)) return pipe_eject(H, dir, TRUE, target, eject_range, eject_speed) diff --git a/code/modules/recycling/disposal/pipe.dm b/code/modules/recycling/disposal/pipe.dm index 30573746f5e2c..69519874cd2ca 100644 --- a/code/modules/recycling/disposal/pipe.dm +++ b/code/modules/recycling/disposal/pipe.dm @@ -171,7 +171,7 @@ // called when pipe is cut with welder /obj/structure/disposalpipe/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) if(disassembled) if(spawn_pipe) var/obj/structure/disposalconstruct/construct = stored diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm index e6e98cf73761b..10108c8304819 100644 --- a/code/modules/recycling/sortingmachinery.dm +++ b/code/modules/recycling/sortingmachinery.dm @@ -83,7 +83,7 @@ if(do_after(user, 50, target = object)) if(!user || user.stat != CONSCIOUS || user.loc != object || object.loc != src) return - to_chat(user, span_notice("You successfully removed [object]'s wrapping !")) + to_chat(user, span_notice("You successfully removed [object]'s wrapping!")) object.forceMove(loc) unwrap_contents() post_unwrap_contents(user) @@ -258,7 +258,7 @@ /obj/item/dest_tagger name = "destination tagger" desc = "Used to set the destination of properly wrapped packages." - icon = 'icons/obj/device.dmi' + icon = 'icons/obj/devices/tool.dmi' icon_state = "cargo tagger" worn_icon_state = "cargotagger" var/currTag = 0 //Destinations are stored in code\globalvars\lists\flavor_misc.dm @@ -267,7 +267,7 @@ inhand_icon_state = "electronic" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BELT /obj/item/dest_tagger/borg @@ -325,7 +325,7 @@ /obj/item/sales_tagger name = "sales tagger" desc = "A scanner that lets you tag wrapped items for sale, splitting the profit between you and cargo." - icon = 'icons/obj/device.dmi' + icon = 'icons/obj/devices/scanner.dmi' icon_state = "sales tagger" worn_icon_state = "salestagger" inhand_icon_state = "electronic" diff --git a/code/modules/religion/burdened/burdened_trauma.dm b/code/modules/religion/burdened/burdened_trauma.dm index b6a1052dab4a6..51e763dbcb570 100644 --- a/code/modules/religion/burdened/burdened_trauma.dm +++ b/code/modules/religion/burdened/burdened_trauma.dm @@ -38,7 +38,7 @@ COMSIG_CARBON_LOSE_MUTATION, COMSIG_CARBON_GAIN_TRAUMA, COMSIG_CARBON_LOSE_TRAUMA, - )) + )) return ..() /** @@ -119,24 +119,7 @@ return INVOKE_ASYNC(knower, TYPE_PROC_REF(/mob/living/carbon/human, slow_psykerize)) -/// Signal to decrease burden_level (see update_burden proc) if an organ is added -/datum/brain_trauma/special/burdened/proc/organ_added_burden(mob/burdened, obj/item/organ/new_organ, special) - SIGNAL_HANDLER - - if(special) //aheals - return - - if(istype(new_organ, /obj/item/organ/internal/eyes)) - var/obj/item/organ/internal/eyes/new_eyes = new_organ - if(new_eyes.tint < TINT_BLIND) //unless you added unworking eyes (flashlight eyes), this is removing burden - update_burden(FALSE) - return - else if(istype(new_organ, /obj/item/organ/internal/appendix)) - return - - update_burden(increase = FALSE)//working organ - -/datum/brain_trauma/special/burdened/proc/is_burdensome_to_lose_organ(mob/burdened, obj/item/organ/old_organ, special) +/datum/brain_trauma/special/burdened/proc/is_burdensome_organ(mob/burdened, obj/item/organ/organ, special) if(special) //aheals return if(!ishuman(burdened)) @@ -168,20 +151,28 @@ if(!burdened_species.mutantliver) critical_slots -= ORGAN_SLOT_LIVER - if(!(old_organ.slot in critical_slots)) + if(!(organ.slot in critical_slots)) return FALSE - else if(istype(old_organ, /obj/item/organ/internal/eyes)) - var/obj/item/organ/internal/eyes/old_eyes = old_organ - if(old_eyes.tint < TINT_BLIND) //unless you were already blinded by them (flashlight eyes), this is adding burden! + else if(istype(organ, /obj/item/organ/internal/eyes)) + var/obj/item/organ/internal/eyes/eyes = organ + if(eyes.tint < TINT_BLIND) //unless you were already blinded by them (flashlight eyes), this is adding burden! return TRUE return FALSE return TRUE +/// Signal to decrease burden_level (see update_burden proc) if an organ is added +/datum/brain_trauma/special/burdened/proc/organ_added_burden(mob/burdened, obj/item/organ/new_organ, special) + SIGNAL_HANDLER + + if(is_burdensome_organ(burdened, new_organ, special)) + update_burden(increase = FALSE)//working organ + /// Signal to increase burden_level (see update_burden proc) if an organ is removed /datum/brain_trauma/special/burdened/proc/organ_removed_burden(mob/burdened, obj/item/organ/old_organ, special) SIGNAL_HANDLER - update_burden(increase = TRUE)//lost organ + if(is_burdensome_organ(burdened, old_organ, special)) + update_burden(increase = TRUE) //lost organ /// Signal to decrease burden_level (see update_burden proc) if a limb is added /datum/brain_trauma/special/burdened/proc/limbs_added_burden(datum/source, obj/item/bodypart/new_limb, special) @@ -192,7 +183,7 @@ update_burden(increase = FALSE) /// Signal to increase burden_level (see update_burden proc) if a limb is removed -/datum/brain_trauma/special/burdened/proc/limbs_removed_burden(datum/source, obj/item/bodypart/old_limb, special) +/datum/brain_trauma/special/burdened/proc/limbs_removed_burden(datum/source, obj/item/bodypart/old_limb, special, dismembered) SIGNAL_HANDLER if(special) //something we don't wanna consider, like instaswapping limbs diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm index 4499030642d93..d67919cd38031 100644 --- a/code/modules/religion/burdened/psyker.dm +++ b/code/modules/religion/burdened/psyker.dm @@ -10,12 +10,12 @@ organ_traits = list(TRAIT_ADVANCEDTOOLUSER, TRAIT_LITERATE, TRAIT_CAN_STRIP, TRAIT_ANTIMAGIC_NO_SELFBLOCK) w_class = WEIGHT_CLASS_NORMAL -/obj/item/organ/internal/brain/psyker/on_insert(mob/living/carbon/inserted_into) +/obj/item/organ/internal/brain/psyker/on_mob_insert(mob/living/carbon/inserted_into) . = ..() inserted_into.AddComponent(/datum/component/echolocation, blocking_trait = TRAIT_DUMB, echo_group = "psyker", echo_icon = "psyker", color_path = /datum/client_colour/psyker) inserted_into.AddComponent(/datum/component/anti_magic, antimagic_flags = MAGIC_RESISTANCE_MIND) -/obj/item/organ/internal/brain/psyker/on_remove(mob/living/carbon/removed_from) +/obj/item/organ/internal/brain/psyker/on_mob_remove(mob/living/carbon/removed_from) . = ..() qdel(removed_from.GetComponent(/datum/component/echolocation)) qdel(removed_from.GetComponent(/datum/component/anti_magic)) @@ -36,7 +36,7 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE bodypart_traits = list(TRAIT_DISFIGURED, TRAIT_BALD, TRAIT_SHAVED) - head_flags = HEAD_LIPS|HEAD_EYEHOLES|HEAD_DEBRAIN + head_flags = HEAD_DEBRAIN /obj/item/bodypart/head/psyker/try_attach_limb(mob/living/carbon/new_head_owner, special, abort) . = ..() @@ -82,9 +82,9 @@ qdel(old_head) var/obj/item/organ/internal/brain/psyker/psyker_brain = new() old_brain.before_organ_replacement(psyker_brain) - old_brain.Remove(src, special = TRUE, no_id_transfer = TRUE) + old_brain.Remove(src, special = TRUE, movement_flags = NO_ID_TRANSFER) qdel(old_brain) - psyker_brain.Insert(src, special = TRUE, drop_if_replaced = FALSE) + psyker_brain.Insert(src, special = TRUE, movement_flags = DELETE_IF_REPLACED) if(old_eyes) qdel(old_eyes) return TRUE @@ -183,7 +183,7 @@ on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \ effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \ ) - AddElement(/datum/element/bane, target_type = /mob/living/simple_animal/revenant, damage_multiplier = 0, added_damage = 25) + AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25) name = pick(possible_names) desc = possible_names[name] @@ -317,8 +317,8 @@ var/atom/movable/plane_master_controller/game_plane_master_controller = owner.hud_used?.plane_master_controllers[PLANE_MASTERS_GAME] if(!game_plane_master_controller) return FALSE - game_plane_master_controller.add_filter("psychic_wave", 10, wave_filter(240, 240, 3, 0, WAVE_SIDEWAYS)) game_plane_master_controller.add_filter("psychic_blur", 10, angular_blur_filter(0, 0, 3)) + game_plane_master_controller.add_filter("psychic_wave", 10, wave_filter(240, 240, 3, 0, WAVE_SIDEWAYS)) return TRUE /datum/status_effect/psychic_projection/on_remove() diff --git a/code/modules/religion/festival/festival_violin.dm b/code/modules/religion/festival/festival_violin.dm new file mode 100644 index 0000000000000..82431352685bc --- /dev/null +++ b/code/modules/religion/festival/festival_violin.dm @@ -0,0 +1,29 @@ +/obj/item/instrument/violin/festival + name = "Cogitandi Fidis" + desc = "A violin that holds a special interest in the songs played from its strings." + icon_state = "holy_violin" + inhand_icon_state = "holy_violin" + +/obj/item/instrument/violin/festival/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_INSTRUMENT_START, PROC_REF(on_instrument_start)) + +/// signal fired when the festival instrument starts to play. +/obj/item/instrument/violin/festival/proc/on_instrument_start(datum/source, datum/song/starting_song, atom/player) + SIGNAL_HANDLER + + if(!starting_song || !isliving(player)) + return + analyze_song(starting_song, player) + +///Reports some relevant information when the song begins playing. +/obj/item/instrument/violin/festival/proc/analyze_song(datum/song/song, mob/living/playing_song) + var/list/analysis = list() + //check tempo and lines + var/song_length = song.lines.len * song.tempo + analysis += span_revenbignotice("[src] speaks to you...") + analysis += span_revennotice("\"This song has [song.lines.len] lines and a tempo of [song.tempo].\"") + analysis += span_revennotice("\"Multiplying these together gives a song length of [song_length].\"") + analysis += span_revennotice("\"To get a bonus effect from [GLOB.deity] upon finishing a performance, you need a song length of [FESTIVAL_SONG_LONG_ENOUGH].\"") + + to_chat(playing_song, analysis.Join("\n")) diff --git a/code/modules/religion/festival/instrument_rites.dm b/code/modules/religion/festival/instrument_rites.dm index a1c94c92425dc..d8537f5845ea0 100644 --- a/code/modules/religion/festival/instrument_rites.dm +++ b/code/modules/religion/festival/instrument_rites.dm @@ -1,9 +1,64 @@ +/datum/religion_rites/holy_violin + name = "Cogitandi Fidis" + desc = "Creates a holy violin that can analyze songs played from it." + ritual_length = 6 SECONDS + ritual_invocations = list("A servant of jubilee is needed ...") + invoke_msg = "... A great mind for musical matters!" + favor_cost = 20 //you only need one + +/datum/religion_rites/holy_violin/invoke_effect(mob/living/user, atom/religious_tool) + . = ..() + var/turf/tool_turf = get_turf(religious_tool) + var/obj/item/instrument/violin/fidis = new /obj/item/instrument/violin/festival(get_turf(religious_tool)) + fidis.visible_message(span_notice("[fidis] appears!")) + playsound(tool_turf, 'sound/effects/pray.ogg', 50, TRUE) + +/datum/religion_rites/portable_song_tuning + name = "Portable Song Tuning" + desc = "Empowers an instrument on the table to work as a portable altar for tuning songs. Will need to be recharged after 5 rites." + ritual_length = 6 SECONDS + ritual_invocations = list("Allow me to bring your holy inspirations ...") + invoke_msg = "... And send them with the winds my tunes ride with!" + favor_cost = 10 + ///instrument to empower + var/obj/item/instrument/instrument_target + +/datum/religion_rites/portable_song_tuning/perform_rite(mob/living/user, atom/religious_tool) + for(var/obj/item/instrument/could_empower in get_turf(religious_tool)) + instrument_target = could_empower + return ..() + to_chat(user, span_warning("You need to place an instrument on [religious_tool] to do this!")) + return FALSE + +/datum/religion_rites/portable_song_tuning/invoke_effect(mob/living/user, atom/movable/religious_tool) + ..() + var/obj/item/instrument/empower_target = instrument_target + var/turf/tool_turf = get_turf(religious_tool) + instrument_target = null + if(QDELETED(empower_target) || !(tool_turf == empower_target.loc)) //check if the instrument is still there + to_chat(user, span_warning("Your target left the altar!")) + return FALSE + empower_target.visible_message(span_notice("[empower_target] glows for a moment.")) + playsound(tool_turf, 'sound/effects/pray.ogg', 50, TRUE) + var/list/allowed_rites_from_bible = subtypesof(/datum/religion_rites/song_tuner) + empower_target.AddComponent( \ + /datum/component/religious_tool, \ + operation_flags = RELIGION_TOOL_INVOKE, \ + force_catalyst_afterattack = FALSE, \ + after_sect_select_cb = null, \ + catalyst_type = /obj/item/book/bible, \ + charges = 5, \ + rite_types_allowlist = allowed_rites_from_bible, \ + ) + return TRUE + ///prototype for rites that tune a song. /datum/religion_rites/song_tuner name = "Tune Song" desc = "this is a prototype." ritual_length = 10 SECONDS favor_cost = 10 + auto_delete = FALSE ///if repeats count as continuations instead of a song's end, TRUE var/repeats_okay = TRUE ///personal message sent to the chaplain as feedback for their chosen song @@ -20,6 +75,16 @@ to_chat(user, span_notice(song_invocation_message)) user.AddComponent(/datum/component/smooth_tunes, src, repeats_okay, particles_path, glow_color) +/** + * Song effect applied when the performer starts playing. + * + * Arguments: + * * performer - A human starting the song + * * song_source - parent of the smooth_tunes component. This is limited to the compatible items of said component, which currently includes mobs and objects so we'll have to type appropriately. + */ +/datum/religion_rites/song_tuner/proc/performer_start_effect(mob/living/carbon/human/performer, atom/song_source) + return + /** * Perform the song effect. * @@ -60,6 +125,28 @@ /datum/religion_rites/song_tuner/evangelism/finish_effect(mob/living/carbon/human/listener, atom/song_source) listener.add_mood_event("blessing", /datum/mood_event/blessing) +/datum/religion_rites/song_tuner/light + name = "Illuminating Solo" + desc = "Sing a bright song, lighting up the area around you. At the end of the song, you'll give some illumination to listeners." + particles_path = /particles/musical_notes/light + song_invocation_message = "You've prepared a bright song!" + song_start_message = span_notice("This music simply glows!") + glow_color = "#fcff44" + repeats_okay = FALSE + favor_cost = 0 + /// lighting object that makes chaplain glow + var/obj/effect/dummy/lighting_obj/moblight/performer_light_obj + +/datum/religion_rites/song_tuner/light/performer_start_effect(mob/living/carbon/human/performer, atom/song_source) + performer_light_obj = performer.mob_light(8, color = LIGHT_COLOR_DIM_YELLOW) + +/datum/religion_rites/song_tuner/light/Destroy() + QDEL_NULL(performer_light_obj) + . = ..() + +/datum/religion_rites/song_tuner/light/finish_effect(mob/living/carbon/human/listener, atom/song_source) + listener.apply_status_effect(/datum/status_effect/song/light) + /datum/religion_rites/song_tuner/nullwave name = "Nullwave Vibrato" desc = "Sing a dull song, protecting those who listen from magic." diff --git a/code/modules/religion/honorbound/honorbound_rites.dm b/code/modules/religion/honorbound/honorbound_rites.dm index 6ba557d5a30e2..88eef0340ea64 100644 --- a/code/modules/religion/honorbound/honorbound_rites.dm +++ b/code/modules/religion/honorbound/honorbound_rites.dm @@ -1,3 +1,6 @@ +/// how much favor is gained when someone joins the crusade and is deaconized +#define DEACONIZE_FAVOR_GAIN 300 + ///Makes the person holy, but they now also have to follow the honorbound code (CBT). Actually earns favor, convincing others to uphold the code (tm) is not easy /datum/religion_rites/deaconize name = "Join Crusade" @@ -59,12 +62,12 @@ if(joining_now.mind.has_antag_datum(/datum/antagonist/cult))//what the fuck?! to_chat(user, span_warning("[GLOB.deity] has seen a true, dark evil in [joining_now]'s heart, and they have been smitten!")) playsound(get_turf(religious_tool), 'sound/effects/pray.ogg', 50, TRUE) - joining_now.gib(TRUE) + joining_now.gib(DROP_ORGANS|DROP_BODYPARTS) return FALSE var/datum/brain_trauma/special/honorbound/honor = user.has_trauma_type(/datum/brain_trauma/special/honorbound) if(joining_now in honor.guilty) honor.guilty -= joining_now - GLOB.religious_sect.adjust_favor(200, user) + GLOB.religious_sect.adjust_favor(DEACONIZE_FAVOR_GAIN, user) to_chat(user, span_notice("[GLOB.deity] has bound [joining_now] to the code! They are now a holy role! (albeit the lowest level of such)")) joining_now.mind.holy_role = HOLY_ROLE_DEACON GLOB.religious_sect.on_conversion(joining_now) @@ -151,7 +154,8 @@
1.) Thou shalt not attack the unready!
Those who are not ready for battle should not be wrought low. The evil of this world must lose - in a fair battle if you are to conquer them completely. + in a fair battle if you are to conquer them completely. Lesser creatures are given the benefit of + being unready, keep that in mind.

2.) Thou shalt not attack the just!
@@ -162,7 +166,9 @@
3.) Thou shalt not attack the innocent!
There is no honor on a pre-emptive strike, unless they are truly evil vermin. - Those who are guilty will either lay a hand on you first, or you may declare their evil. + Those who are guilty will either lay a hand on you first, or you may declare their evil. Mindless, lesser + creatures cannot be considered innocent, nor evil. They are beings of passion and function, and + may be dispatched as such if their passions misalign with the pursuits of a better world.

4.) Thou shalt not use profane magicks!
@@ -172,3 +178,5 @@ been allowed as it is a school focused on the light and mending of this world. "} return ..() + +#undef DEACONIZE_FAVOR_GAIN diff --git a/code/modules/religion/honorbound/honorbound_trauma.dm b/code/modules/religion/honorbound/honorbound_trauma.dm index 29152e5bc7299..399bf6765edf6 100644 --- a/code/modules/religion/honorbound/honorbound_trauma.dm +++ b/code/modules/religion/honorbound/honorbound_trauma.dm @@ -1,3 +1,6 @@ +/// one reason for declaring guilty is specifically checked for, keeping it as a define to avoid future mistakes +#define GUILT_REASON_DECLARATION "from your declaration." + ///Honorbound prevents you from attacking the unready, the just, or the innocent /datum/brain_trauma/special/honorbound name = "Dogmatic Compulsions" @@ -45,38 +48,59 @@ if(!isliving(clickingon)) return - var/mob/living/clickedmob = clickingon + var/mob/living/clicked_mob = clickingon var/obj/item/weapon = honorbound.get_active_held_item() - if(!honorbound.DirectAccess(clickedmob) && !isgun(weapon)) + if(!honorbound.DirectAccess(clicked_mob) && !isgun(weapon)) return if(weapon?.item_flags & NOBLUDGEON) return - if(!honorbound.combat_mode && (HAS_TRAIT(clickedmob, TRAIT_ALLOWED_HONORBOUND_ATTACK) || ((!weapon || !weapon.force) && !LAZYACCESS(modifiers, RIGHT_CLICK)))) + if(!honorbound.combat_mode && (HAS_TRAIT(clicked_mob, TRAIT_ALLOWED_HONORBOUND_ATTACK) || ((!weapon || !weapon.force) && !LAZYACCESS(modifiers, RIGHT_CLICK)))) return - if(!is_honorable(honorbound, clickedmob)) + if(!(clicked_mob in guilty)) + check_visible_guilt(clicked_mob) + if(!is_honorable(honorbound, clicked_mob)) return (COMSIG_MOB_CANCEL_CLICKON) +/// Checks a mob for any obvious signs of evil, and applies a guilty reason for each. +/datum/brain_trauma/special/honorbound/proc/check_visible_guilt(mob/living/attacked_mob) + //will most likely just hit nuke ops but good catch-all. WON'T hit traitors + if(ROLE_SYNDICATE in attacked_mob.faction) + guilty(attacked_mob, "for their misaligned association with the Syndicate!") + //not an antag datum check so it applies to wizard minions as well + if(ROLE_WIZARD in attacked_mob.faction) + guilty(attacked_mob, "for blasphemous magicks!") + if(HAS_TRAIT(attacked_mob, TRAIT_CULT_HALO)) + guilty(attacked_mob, "for blasphemous worship!") + if(attacked_mob.mind) + var/datum/mind/guilty_conscience = attacked_mob.mind + if(guilty_conscience.has_antag_datum(/datum/antagonist/abductor)) + guilty(attacked_mob, "for their blatant surgical malice...") + if(guilty_conscience.has_antag_datum(/datum/antagonist/nightmare)) + guilty(attacked_mob, "for being a light-consuming nightmare!") + if(guilty_conscience.has_antag_datum(/datum/antagonist/ninja)) + guilty(attacked_mob, "for their misaligned association with the Spider Clan!") + var/datum/antagonist/heretic/heretic_datum = guilty_conscience.has_antag_datum(/datum/antagonist/heretic) + if(heretic_datum?.ascended) + guilty(attacked_mob, "for blasphemous, heretical, out of control worship!") + /** * Called by hooked signals whenever someone attacks the person with this trauma * Checks if the attacker should be considered guilty and adds them to the guilty list if true * * Arguments: * * user: person who attacked the honorbound - * * declaration: if this wasn't an attack, but instead the honorbound spending favor on declaring this person guilty + * * reason: why this person is now guilty (future pr idea: letting honorbound print a receipt for why someone is guilty? lol) */ -/datum/brain_trauma/special/honorbound/proc/guilty(mob/living/user, declaration = FALSE) +/datum/brain_trauma/special/honorbound/proc/guilty(mob/living/user, reason = "for no particular reason!") if(user in guilty) return var/datum/mind/guilty_conscience = user.mind - if(guilty_conscience && !declaration) //sec and medical are immune to becoming guilty through attack (we don't check holy because holy shouldn't be able to attack eachother anyways) + if(guilty_conscience && reason != GUILT_REASON_DECLARATION) //sec and medical are immune to becoming guilty through attack (we don't check holy because holy shouldn't be able to attack eachother anyways) var/datum/job/job = guilty_conscience.assigned_role if(job.departments_bitflags & (DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SECURITY)) return - if(declaration) - to_chat(owner, span_notice("[user] is now considered guilty by [GLOB.deity] from your declaration.")) - else - to_chat(owner, span_notice("[user] is now considered guilty by [GLOB.deity] for attacking you first.")) + to_chat(owner, span_notice("[user] is now considered guilty by [GLOB.deity] [reason]")) to_chat(user, span_danger("[GLOB.deity] no longer considers you innocent!")) guilty += user @@ -84,7 +108,7 @@ /datum/brain_trauma/special/honorbound/proc/on_attacked(mob/source, mob/attacker, attack_flags) SIGNAL_HANDLER if(!(attack_flags & (ATTACKER_STAMINA_ATTACK|ATTACKER_SHOVING))) - guilty(attacker) + guilty(attacker, "for attacking [source] first.") /** * Called by attack_honor signal to check whether an attack should be allowed or not @@ -95,6 +119,7 @@ */ /datum/brain_trauma/special/honorbound/proc/is_honorable(mob/living/carbon/human/honorbound_human, mob/living/target_creature) var/is_guilty = (target_creature in guilty) + var/is_human = ishuman(target_creature) //THE UNREADY (Applies over ANYTHING else!) if(honorbound_human == target_creature) return TRUE //oh come on now @@ -102,7 +127,7 @@ to_chat(honorbound_human, span_warning("There is no honor in attacking the unready.")) return FALSE //THE JUST (Applies over guilt except for med, so you best be careful!) - if(ishuman(target_creature)) + if(is_human) var/mob/living/carbon/human/target_human = target_creature var/datum/job/job = target_human.mind?.assigned_role var/is_holy = target_human.mind?.holy_role @@ -112,9 +137,9 @@ if(job?.departments_bitflags & DEPARTMENT_BITFLAG_MEDICAL && !is_guilty) to_chat(honorbound_human, span_warning("If you truly think this healer is not innocent, declare them guilty.")) return FALSE - //THE INNOCENT - if(!is_guilty) - to_chat(honorbound_human, span_warning("There is nothing righteous in attacking the innocent.")) + //THE INNOCENT (human and borg exclusive) + if(!is_guilty && (is_human || issilicon(target_creature))) + to_chat(target_creature, span_warning("There is nothing righteous in attacking the innocent.")) return FALSE return TRUE @@ -262,4 +287,6 @@ /datum/action/cooldown/spell/pointed/declare_evil/cast(mob/living/cast_on) . = ..() GLOB.religious_sect.adjust_favor(-required_favor, owner) - honor_trauma.guilty(cast_on, declaration = TRUE) + honor_trauma.guilty(cast_on, GUILT_REASON_DECLARATION) + +#undef GUILT_REASON_DECLARATION diff --git a/code/modules/religion/pyre_rites.dm b/code/modules/religion/pyre/pyre_rites.dm similarity index 90% rename from code/modules/religion/pyre_rites.dm rename to code/modules/religion/pyre/pyre_rites.dm index d974ef756f269..79f95ad6af6ae 100644 --- a/code/modules/religion/pyre_rites.dm +++ b/code/modules/religion/pyre/pyre_rites.dm @@ -10,9 +10,9 @@ name = "Unmelting Protection" desc = "Grants fire immunity to any piece of clothing." ritual_length = 12 SECONDS - ritual_invocations = list("And so to support the holder of the Ever-Burning candle...", + ritual_invocations = list("And so to support the holder of the Ever-Burning candle ...", "... allow this unworthy apparel to serve you ...", - "... make it strong enough to burn a thousand time and more ...") + "... make it strong enough to burn a thousand times and more ...") invoke_msg = "... Come forth in your new form, and join the unmelting wax of the one true flame!" favor_cost = 1000 ///the piece of clothing that will be fireproofed, only one per rite @@ -43,7 +43,7 @@ /datum/religion_rites/burning_sacrifice name = "Burning Offering" - desc = "Sacrifice a buckled burning corpse for favor, the more burn damage the corpse has the more favor you will receive." + desc = "Sacrifice a buckled burning or husked corpse for favor, the more burn damage the corpse has the more favor you will receive." ritual_length = 15 SECONDS ritual_invocations = list("Burning body ...", "... cleansed by the flame ...", @@ -71,8 +71,8 @@ if(chosen_sacrifice.stat != DEAD) to_chat(user, span_warning("You can only sacrifice dead bodies, this one is still alive!")) return FALSE - if(!chosen_sacrifice.on_fire) - to_chat(user, span_warning("This corpse needs to be on fire to be sacrificed!")) + if(!chosen_sacrifice.on_fire && !HAS_TRAIT_FROM(chosen_sacrifice, TRAIT_HUSK, BURN)) + to_chat(user, span_warning("This corpse needs to be on fire or husked to be sacrificed!")) return FALSE return ..() @@ -82,8 +82,8 @@ to_chat(user, span_warning("The right sacrifice is no longer on the altar!")) chosen_sacrifice = null return FALSE - if(!chosen_sacrifice.on_fire) - to_chat(user, span_warning("The sacrifice is no longer on fire, it needs to burn until the end of the rite!")) + if(!chosen_sacrifice.on_fire && !HAS_TRAIT_FROM(chosen_sacrifice, TRAIT_HUSK, BURN)) + to_chat(user, span_warning("The sacrifice has to be on fire or husked to finish the end of the rite!")) chosen_sacrifice = null return FALSE if(chosen_sacrifice.stat != DEAD) @@ -92,7 +92,7 @@ return FALSE var/favor_gained = 100 + round(chosen_sacrifice.getFireLoss()) GLOB.religious_sect.adjust_favor(favor_gained, user) - to_chat(user, span_notice("[GLOB.deity] absorbs the burning corpse and any trace of fire with it. [GLOB.deity] rewards you with [favor_gained] favor.")) + to_chat(user, span_notice("[GLOB.deity] absorbs the charred corpse and any trace of fire with it. [GLOB.deity] rewards you with [favor_gained] favor.")) chosen_sacrifice.dust(force = TRUE) playsound(get_turf(religious_tool), 'sound/effects/supermatter.ogg', 50, TRUE) chosen_sacrifice = null diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index 0a2486595b2a6..ebb8a69a7e574 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -38,6 +38,8 @@ var/altar_icon_state /// Currently Active (non-deleted) rites var/list/active_rites + /// Chance that we fail a bible blessing. + var/smack_chance = DEFAULT_SMACK_CHANCE /// Whether the structure has CANDLE OVERLAYS! var/candle_overlay = TRUE @@ -55,21 +57,28 @@ /// Activates once selected and on newjoins, oriented around people who become holy. /datum/religion_sect/proc/on_conversion(mob/living/chap) SHOULD_CALL_PARENT(TRUE) - to_chat(chap, "\"[quote]\"") - to_chat(chap, "[desc]") + to_chat(chap, span_boldnotice("\"[quote]\"")) + to_chat(chap, span_notice("[desc]")) + +/// Activates if religious sect is reset by admins, should clean up anything you added on conversion. +/datum/religion_sect/proc/on_deconversion(mob/living/chap) + SHOULD_CALL_PARENT(TRUE) + to_chat(chap, span_boldnotice("You have lost the approval of \the [name].")) + if(chap.mind.holy_role == HOLY_ROLE_HIGHPRIEST) + to_chat(chap, span_notice("Return to an altar to reform your sect.")) /// Returns TRUE if the item can be sacrificed. Can be modified to fit item being tested as well as person offering. Returning TRUE will stop the attackby sequence and proceed to on_sacrifice. -/datum/religion_sect/proc/can_sacrifice(obj/item/I, mob/living/chap) +/datum/religion_sect/proc/can_sacrifice(obj/item/sacrifice, mob/living/chap) . = TRUE if(chap.mind.holy_role == HOLY_ROLE_DEACON) to_chat(chap, "You are merely a deacon of [GLOB.deity], and therefore cannot perform rites.") return - if(!is_type_in_typecache(I,desired_items_typecache)) + if(!is_type_in_typecache(sacrifice, desired_items_typecache)) return FALSE /// Activates when the sect sacrifices an item. This proc has NO bearing on the attackby sequence of other objects when used in conjunction with the religious_tool component. -/datum/religion_sect/proc/on_sacrifice(obj/item/I, mob/living/chap) - return adjust_favor(default_item_favor,chap) +/datum/religion_sect/proc/on_sacrifice(obj/item/sacrifice, mob/living/chap) + return adjust_favor(default_item_favor, chap) /// Returns a description for religious tools /datum/religion_sect/proc/tool_examine(mob/living/holy_creature) @@ -82,7 +91,7 @@ . = favor //if favor = 5 and we want to subtract 10, we'll only be able to subtract 5 if((favor + amount > max_favor)) . = (max_favor-favor) //if favor = 5 and we want to add 10 with a max of 10, we'll only be able to add 5 - favor = clamp(0,max_favor, favor+amount) + favor = clamp(0, max_favor, favor+amount) /// Sets favor to a specific amount. Can provide optional features based on a user. /datum/religion_sect/proc/set_favor(amount = 0, mob/living/chap) @@ -108,7 +117,7 @@ if(hurt_limbs.len) for(var/X in hurt_limbs) var/obj/item/bodypart/affecting = X - if(affecting.heal_damage(heal_amt, heal_amt, BODYTYPE_ORGANIC)) + if(affecting.heal_damage(heal_amt, heal_amt, required_bodytype = BODYTYPE_ORGANIC)) blessed.update_damage_overlays() blessed.visible_message(span_notice("[chap] heals [blessed] with the power of [GLOB.deity]!")) to_chat(blessed, span_boldnotice("May the power of [GLOB.deity] compel you to be healed!")) @@ -116,6 +125,10 @@ blessed.add_mood_event("blessing", /datum/mood_event/blessing) return TRUE +/// What happens if we bless a corpse? By default just do the default smack behavior +/datum/religion_sect/proc/sect_dead_bless(mob/living/target, mob/living/chap) + return FALSE + /**** Nanotrasen Approved God ****/ /datum/religion_sect/puritanism @@ -161,7 +174,7 @@ eth_stomach.adjust_charge(60) did_we_charge = TRUE - //if we're not targetting a robot part we stop early + //if we're not targeting a robot part we stop early var/obj/item/bodypart/bodypart = blessed.get_bodypart(chap.zone_selected) if(IS_ORGANIC_LIMB(bodypart)) if(!did_we_charge) @@ -183,16 +196,17 @@ blessed.add_mood_event("blessing", /datum/mood_event/blessing) return TRUE -/datum/religion_sect/mechanical/on_sacrifice(obj/item/I, mob/living/chap) - var/obj/item/stock_parts/cell/the_cell = I - if(!istype(the_cell)) //how... +/datum/religion_sect/mechanical/on_sacrifice(obj/item/stock_parts/cell/power_cell, mob/living/chap) + if(!istype(power_cell)) return - if(the_cell.charge < 300) - to_chat(chap,span_notice("[GLOB.deity] does not accept pity amounts of power.")) + + if(power_cell.charge < 300) + to_chat(chap, span_notice("[GLOB.deity] does not accept pity amounts of power.")) return - adjust_favor(round(the_cell.charge/300), chap) - to_chat(chap, span_notice("You offer [the_cell]'s power to [GLOB.deity], pleasing them.")) - qdel(I) + + adjust_favor(round(power_cell.charge/300), chap) + to_chat(chap, span_notice("You offer [power_cell]'s power to [GLOB.deity], pleasing them.")) + qdel(power_cell) return TRUE /**** Pyre God ****/ @@ -220,7 +234,7 @@ /datum/religion_sect/pyre/on_sacrifice(obj/item/flashlight/flare/candle/offering, mob/living/user) if(!istype(offering)) return - if(!offering.on) + if(!offering.light_on) to_chat(user, span_notice("The candle needs to be lit to be offered!")) return to_chat(user, span_notice("[GLOB.deity] is pleased with your sacrifice.")) @@ -264,7 +278,7 @@ var/list/hurt_limbs = blessed.get_damaged_bodyparts(1, 1, BODYTYPE_ORGANIC) if(hurt_limbs.len) for(var/obj/item/bodypart/affecting as anything in hurt_limbs) - if(affecting.heal_damage(heal_amt, heal_amt, BODYTYPE_ORGANIC)) + if(affecting.heal_damage(heal_amt, heal_amt, required_bodytype = BODYTYPE_ORGANIC)) blessed.update_damage_overlays() blessed.visible_message(span_notice("[chap] barters a heal for [blessed] from [GLOB.deity]!")) to_chat(blessed, span_boldnotice("May the power of [GLOB.deity] compel you to be healed! Thank you for choosing [GLOB.deity]!")) @@ -278,11 +292,12 @@ name = "Punished God" quote = "To feel the freedom, you must first understand captivity." desc = "Incapacitate yourself in any way possible. Bad mutations, lost limbs, traumas, \ - even addictions. You will learn the secrets of the universe from your defeated shell." + even addictions. You will learn the secrets of the universe from your defeated shell." tgui_icon = "user-injured" altar_icon_state = "convertaltar-burden" alignment = ALIGNMENT_NEUT candle_overlay = FALSE + smack_chance = 0 rites_list = list(/datum/religion_rites/nullrod_transformation) /datum/religion_sect/burden/on_conversion(mob/living/carbon/human/new_convert) @@ -290,16 +305,85 @@ if(!ishuman(new_convert)) to_chat(new_convert, span_warning("[GLOB.deity] needs higher level creatures to fully comprehend the suffering. You are not burdened.")) return - new_convert.gain_trauma(/datum/brain_trauma/special/burdened, TRAUMA_RESILIENCE_MAGIC) + new_convert.gain_trauma(/datum/brain_trauma/special/burdened, TRAUMA_RESILIENCE_ABSOLUTE) + +/datum/religion_sect/burden/on_deconversion(mob/living/carbon/human/new_convert) + if (ishuman(new_convert)) + new_convert.cure_trauma_type(/datum/brain_trauma/special/burdened, TRAUMA_RESILIENCE_ABSOLUTE) + return ..() /datum/religion_sect/burden/tool_examine(mob/living/carbon/human/burdened) //display burden level - if(!ishuman(burdened)) - return FALSE - var/datum/brain_trauma/special/burdened/burden = burdened.has_trauma_type(/datum/brain_trauma/special/burdened) - if(burden) - return "You are at burden level [burden.burden_level]/9." + if(ishuman(burdened)) + var/datum/brain_trauma/special/burdened/burden = burdened.has_trauma_type(/datum/brain_trauma/special/burdened) + if(burden) + return "You are at burden level [burden.burden_level]/9." return "You are not burdened." +/datum/religion_sect/burden/sect_bless(mob/living/carbon/target, mob/living/carbon/chaplain) + if(!istype(target) || !istype(chaplain)) + return FALSE + var/datum/brain_trauma/special/burdened/burden = chaplain.has_trauma_type(/datum/brain_trauma/special/burdened) + if(!burden) + return FALSE + var/burden_modifier = max(1 - 0.07 * burden.burden_level, 0.01) + var/transferred = FALSE + var/list/hurt_limbs = target.get_damaged_bodyparts(1, 1, BODYTYPE_ORGANIC) + target.get_wounded_bodyparts(BODYTYPE_ORGANIC) + var/list/chaplains_limbs = list() + for(var/obj/item/bodypart/possible_limb in chaplain.bodyparts) + if(IS_ORGANIC_LIMB(possible_limb)) + chaplains_limbs += possible_limb + if(length(chaplains_limbs)) + for(var/obj/item/bodypart/affected_limb as anything in hurt_limbs) + var/obj/item/bodypart/chaplains_limb = chaplain.get_bodypart(affected_limb.body_zone) + if(!chaplains_limb || !IS_ORGANIC_LIMB(chaplains_limb)) + chaplains_limb = pick(chaplains_limbs) + var/brute_damage = affected_limb.brute_dam + var/burn_damage = affected_limb.burn_dam + if((brute_damage || burn_damage)) + transferred = TRUE + affected_limb.heal_damage(brute_damage, burn_damage, required_bodytype = BODYTYPE_ORGANIC) + chaplains_limb.receive_damage(brute_damage * burden_modifier, burn_damage * burden_modifier, forced = TRUE, wound_bonus = CANT_WOUND) + for(var/datum/wound/iter_wound as anything in affected_limb.wounds) + transferred = TRUE + iter_wound.remove_wound() + iter_wound.apply_wound(chaplains_limb) + if(HAS_TRAIT_FROM(target, TRAIT_HUSK, BURN)) + transferred = TRUE + target.cure_husk(BURN) + chaplain.become_husk(BURN) + var/toxin_damage = target.getToxLoss() + if(toxin_damage && !HAS_TRAIT(chaplain, TRAIT_TOXIMMUNE)) + transferred = TRUE + target.adjustToxLoss(-toxin_damage) + chaplain.adjustToxLoss(toxin_damage * burden_modifier, forced = TRUE) + var/suffocation_damage = target.getOxyLoss() + if(suffocation_damage && !HAS_TRAIT(chaplain, TRAIT_NOBREATH)) + transferred = TRUE + target.adjustOxyLoss(-suffocation_damage) + chaplain.adjustOxyLoss(suffocation_damage * burden_modifier, forced = TRUE) + if(!HAS_TRAIT(chaplain, TRAIT_NOBLOOD)) + if(target.blood_volume < BLOOD_VOLUME_SAFE) + var/target_blood_data = target.get_blood_data(target.get_blood_id()) + var/chaplain_blood_data = chaplain.get_blood_data(chaplain.get_blood_id()) + var/transferred_blood_amount = min(chaplain.blood_volume, BLOOD_VOLUME_SAFE - target.blood_volume) + if(transferred_blood_amount && (chaplain_blood_data["blood_type"] in get_safe_blood(target_blood_data["blood_type"]))) + transferred = TRUE + chaplain.transfer_blood_to(target, transferred_blood_amount, forced = TRUE) + if(target.blood_volume > BLOOD_VOLUME_EXCESS) + target.transfer_blood_to(chaplain, target.blood_volume - BLOOD_VOLUME_EXCESS, forced = TRUE) + target.update_damage_overlays() + chaplain.update_damage_overlays() + if(transferred) + target.visible_message(span_notice("[chaplain] takes on [target]'s burden!")) + to_chat(target, span_boldnotice("May the power of [GLOB.deity] compel you to be healed!")) + playsound(chaplain, SFX_PUNCH, 25, vary = TRUE, extrarange = -1) + target.add_mood_event("blessing", /datum/mood_event/blessing) + else + to_chat(chaplain, span_warning("They hold no burden!")) + return TRUE + +/datum/religion_sect/burden/sect_dead_bless(mob/living/target, mob/living/chaplain) + return sect_bless(target, chaplain) /datum/religion_sect/honorbound name = "Honorbound God" @@ -333,6 +417,11 @@ return FALSE new_convert.gain_trauma(/datum/brain_trauma/special/honorbound, TRAUMA_RESILIENCE_MAGIC) +/datum/religion_sect/honorbound/on_deconversion(mob/living/carbon/human/new_convert) + if (ishuman(new_convert)) + new_convert.cure_trauma_type(/datum/brain_trauma/special/honorbound, TRAUMA_RESILIENCE_MAGIC) + return ..() + #define MINIMUM_YUCK_REQUIRED 5 /datum/religion_sect/maintenance @@ -413,7 +502,10 @@ alignment = ALIGNMENT_GOOD candle_overlay = FALSE rites_list = list( + /datum/religion_rites/holy_violin, + /datum/religion_rites/portable_song_tuning, /datum/religion_rites/song_tuner/evangelism, + /datum/religion_rites/song_tuner/light, /datum/religion_rites/song_tuner/nullwave, /datum/religion_rites/song_tuner/pain, /datum/religion_rites/song_tuner/lullaby, diff --git a/code/modules/religion/religion_structures.dm b/code/modules/religion/religion_structures.dm index e9dcfffff4842..1b30d021268cf 100644 --- a/code/modules/religion/religion_structures.dm +++ b/code/modules/religion/religion_structures.dm @@ -18,6 +18,7 @@ reflect_sect_in_icons() GLOB.chaplain_altars += src AddElement(/datum/element/climbable) + AddElement(/datum/element/elevation, pixel_shift = 12) /obj/structure/altar_of_gods/Destroy() GLOB.chaplain_altars -= src @@ -61,7 +62,10 @@ . += list(span_notice("Chaplains: [chaplains].")) /obj/structure/altar_of_gods/proc/reflect_sect_in_icons() - if(GLOB.religious_sect) + if(isnull(GLOB.religious_sect)) + icon = initial(icon) + icon_state = initial(icon_state) + else sect_to_altar = GLOB.religious_sect if(sect_to_altar.altar_icon) icon = sect_to_altar.altar_icon diff --git a/code/modules/requests/request_manager.dm b/code/modules/requests/request_manager.dm index 41e46aa53c256..dd6d8d42a480e 100644 --- a/code/modules/requests/request_manager.dm +++ b/code/modules/requests/request_manager.dm @@ -25,7 +25,7 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new) /// List where requests can be accessed by ID var/list/requests_by_id = list() -/datum/request_manager/Destroy(force, ...) +/datum/request_manager/Destroy(force) QDEL_LIST(requests) return ..() diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm index 56220956182b5..febb25add5301 100644 --- a/code/modules/research/anomaly/anomaly_core.dm +++ b/code/modules/research/anomaly/anomaly_core.dm @@ -23,7 +23,7 @@ /obj/item/assembly/signaler/anomaly/manual_suicide(mob/living/carbon/user) user.visible_message(span_suicide("[user]'s [src] is reacting to the radio signal, warping [user.p_their()] body!")) user.set_suicide(TRUE) - user.gib() + user.gib(DROP_ALL_REMAINS) /obj/item/assembly/signaler/anomaly/attack_self() return diff --git a/code/modules/research/anomaly/anomaly_refinery.dm b/code/modules/research/anomaly/anomaly_refinery.dm index ce09ebb917627..1805a25231833 100644 --- a/code/modules/research/anomaly/anomaly_refinery.dm +++ b/code/modules/research/anomaly/anomaly_refinery.dm @@ -110,7 +110,7 @@ /obj/machinery/research/anomaly_refinery/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/research/anomaly_refinery/screwdriver_act(mob/living/user, obj/item/tool) if(!default_deconstruction_screwdriver(user, "[base_icon_state]-off", "[base_icon_state]", tool)) @@ -295,7 +295,7 @@ return FALSE tank_to_target = (tank_to_target == inserted_bomb.tank_one) ? inserted_bomb.tank_two : inserted_bomb.tank_one -/obj/machinery/research/anomaly_refinery/on_deconstruction() +/obj/machinery/research/anomaly_refinery/on_deconstruction(disassembled) eject_bomb() eject_core() return ..() diff --git a/code/modules/research/anomaly/raw_anomaly.dm b/code/modules/research/anomaly/raw_anomaly.dm index 8d103388d37ac..2df844e4bb808 100644 --- a/code/modules/research/anomaly/raw_anomaly.dm +++ b/code/modules/research/anomaly/raw_anomaly.dm @@ -9,7 +9,7 @@ /obj/item/raw_anomaly_core name = "raw anomaly core" desc = "You shouldn't be seeing this. Someone screwed up." - icon = 'icons/obj/assemblies/new_assemblies.dmi' + icon = 'icons/obj/devices/new_assemblies.dmi' icon_state = "broken_state" /// Anomaly type diff --git a/code/modules/research/bepis.dm b/code/modules/research/bepis.dm deleted file mode 100644 index 2f038b2751159..0000000000000 --- a/code/modules/research/bepis.dm +++ /dev/null @@ -1,295 +0,0 @@ -//This system is designed to act as an in-between for cargo and science, and the first major money sink in the game outside of just buying things from cargo (As of 10/9/19, anyway). - -//economics defined values, subject to change should anything be too high or low in practice. - -#define MACHINE_OPERATION 100000 -#define MACHINE_OVERLOAD 500000 -#define MAJOR_THRESHOLD (6*CARGO_CRATE_VALUE) -#define MINOR_THRESHOLD (4*CARGO_CRATE_VALUE) -#define STANDARD_DEVIATION (2*CARGO_CRATE_VALUE) -#define PART_CASH_OFFSET_AMOUNT (0.5*CARGO_CRATE_VALUE) - -/obj/machinery/rnd/bepis - name = "\improper B.E.P.I.S. Chamber" - desc = "A high fidelity testing device which unlocks the secrets of the known universe using the two most powerful substances available to man: excessive amounts of electricity and capital." - icon = 'icons/obj/machines/bepis.dmi' - icon_state = "chamber" - base_icon_state = "chamber" - density = TRUE - layer = ABOVE_MOB_LAYER - plane = GAME_PLANE_UPPER - circuit = /obj/item/circuitboard/machine/bepis - - ///How much cash the UI and machine are depositing at a time. - var/banking_amount = 100 - ///How much stored player cash exists within the machine. - var/banked_cash = 0 - ///Payer's bank account. - var/datum/bank_account/account - ///Name on the payer's bank account. - var/account_name - ///When the BEPIS fails to hand out any reward, the ERROR cause will be a randomly picked string displayed on the UI. - var/error_cause = null - - //Vars related to probability and chance of success for testing, using gaussian normal distribution. - ///How much cash you will need to obtain a Major Tech Disk reward. - var/major_threshold = MAJOR_THRESHOLD - ///How much cash you will need to obtain a minor invention reward. - var/minor_threshold = MINOR_THRESHOLD - ///The standard deviation of the BEPIS's gaussian normal distribution. - var/std = STANDARD_DEVIATION - - //Stock part variables - ///Multiplier that lowers how much the BEPIS' power costs are. Maximum of 1, upgraded to a minimum of 0.7. See RefreshParts. - var/power_saver = 1 - ///Variability on the money you actively spend on the BEPIS, with higher inaccuracy making the most change, good and bad to spent cash. - var/inaccuracy_percentage = 1.5 - ///How much "cash" is added to your inserted cash efforts for free. Based on manipulator stock part level. - var/positive_cash_offset = 0 - ///How much "cost" is removed from both the minor and major threshold costs. Based on laser stock part level. - var/negative_cash_offset = 0 - ///List of objects that constitute your minor rewards. All rewards are unique or rare outside of the BEPIS. - var/minor_rewards = list( - //To add a new minor reward, add it here. - /obj/item/stack/circuit_stack/full, - /obj/item/pen/survival, - /obj/item/circuitboard/machine/sleeper/party, - /obj/item/toy/sprayoncan, - ) - -/obj/machinery/rnd/bepis/attackby(obj/item/O, mob/user, params) - if(!is_operational) - to_chat(user, span_notice("[src] can't accept money when it's not functioning.")) - return - if(istype(O, /obj/item/holochip) || istype(O, /obj/item/stack/spacecash)) - var/deposit_value = O.get_item_credit_value() - banked_cash += deposit_value - qdel(O) - say("Deposited [deposit_value] credits into storage.") - update_appearance() - return - if(isidcard(O)) - var/obj/item/card/id/Card = O - if(Card.registered_account) - account = Card.registered_account - account_name = Card.registered_name - say("New account detected. Console Updated.") - else - say("No account detected on card. Aborting.") - return - return ..() - -/obj/machinery/rnd/bepis/screwdriver_act(mob/living/user, obj/item/tool) - return default_deconstruction_screwdriver(user, "chamber_open", "chamber", tool) - -/obj/machinery/rnd/bepis/screwdriver_act_secondary(mob/living/user, obj/item/tool) - return default_deconstruction_screwdriver(user, "chamber_open", "chamber", tool) - -/obj/machinery/rnd/bepis/RefreshParts() - . = ..() - var/C = 0 - var/M = 0 - var/L = 0 - var/S = 0 - for(var/datum/stock_part/capacitor/capacitor in component_parts) - C += ((capacitor.tier - 1) * 0.1) - power_saver = 1 - C - for(var/datum/stock_part/servo/servo in component_parts) - M += ((servo.tier - 1) * PART_CASH_OFFSET_AMOUNT) - positive_cash_offset = M - for(var/datum/stock_part/micro_laser/Laser in component_parts) - L += ((Laser.tier - 1) * PART_CASH_OFFSET_AMOUNT) - negative_cash_offset = L - for(var/datum/stock_part/scanning_module/scanning_module in component_parts) - S += ((scanning_module.tier - 1) * 0.25) - inaccuracy_percentage = (1.5 - S) - -/obj/machinery/rnd/bepis/update_icon_state() - if(panel_open == TRUE) - icon_state = "[base_icon_state]_open" - return ..() - if((use_power == ACTIVE_POWER_USE) && (banked_cash > 0) && (is_operational)) - icon_state = "[base_icon_state]_active_loaded" - return ..() - if (((use_power == IDLE_POWER_USE) && (banked_cash > 0)) || (banked_cash > 0) && (!is_operational)) - icon_state = "[base_icon_state]_loaded" - return ..() - if(use_power == ACTIVE_POWER_USE && is_operational) - icon_state = "[base_icon_state]_active" - return ..() - if(((use_power == IDLE_POWER_USE) && (banked_cash == 0)) || (!is_operational)) - icon_state = base_icon_state - return ..() - return ..() - -/obj/machinery/rnd/bepis/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "Bepis", name) - ui.open() - RefreshParts() - if(isliving(user)) - var/mob/living/customer = user - account = customer.get_bank_account() - -/obj/machinery/rnd/bepis/ui_data(mob/user) - var/list/data = list() - var/powered = FALSE - var/zvalue = ((banking_amount + banked_cash) - (major_threshold - positive_cash_offset - negative_cash_offset))/(std) - var/std_success = 0 - var/prob_success = 0 - //Admittedly this is messy, but not nearly as messy as the alternative, which is jury-rigging an entire Z-table into the code, or making an adaptive z-table. - var/z = abs(zvalue) - if(z > 0 && z <= 0.5) - std_success = 19.1 - else if(z > 0.5 && z <= 1.0) - std_success = 34.1 - else if(z > 1.0 && z <= 1.5) - std_success = 43.3 - else if(z > 1.5 && z <= 2.0) - std_success = 47.7 - else if(z > 2.0 && z <= 2.5) - std_success = 49.4 - else - std_success = 50 - if(zvalue > 0) - prob_success = 50 + std_success - else if(zvalue == 0) - prob_success = 50 - else - prob_success = 50 - std_success - - if(use_power == ACTIVE_POWER_USE) - powered = TRUE - data["account_owner"] = account_name - data["amount"] = banking_amount - data["stored_cash"] = account?.account_balance - data["mean_value"] = (major_threshold - positive_cash_offset - negative_cash_offset) - data["error_name"] = error_cause - data["power_saver"] = power_saver - data["accuracy_percentage"] = inaccuracy_percentage * 100 - data["positive_cash_offset"] = positive_cash_offset - data["negative_cash_offset"] = negative_cash_offset - data["manual_power"] = powered ? FALSE : TRUE - data["silicon_check"] = issilicon(user) - data["success_estimate"] = prob_success - return data - -/obj/machinery/rnd/bepis/ui_act(action,params) - . = ..() - if(.) - return - switch(action) - if("begin_experiment") - if(use_power == IDLE_POWER_USE) - return - depositcash() - if(banked_cash == 0) - say("Please select funds to deposit to begin testing.") - return - calcsuccess() - use_power(MACHINE_OPERATION * power_saver) //This thing should eat your APC battery if you're not careful. - update_use_power(IDLE_POWER_USE) //Machine shuts off after use to prevent spam and look better visually. - update_appearance() - if("amount") - var/input = text2num(params["amount"]) - if(input) - banking_amount = input - if("toggle_power") - if(use_power == ACTIVE_POWER_USE) - update_use_power(IDLE_POWER_USE) - else - update_use_power(ACTIVE_POWER_USE) - update_appearance() - if("account_reset") - if(use_power == IDLE_POWER_USE) - return - account_name = "" - account = null - say("Account settings reset.") - . = TRUE - -/** - * Proc that handles the user's account to deposit credits for the BEPIS. - * Handles success and fail cases for transferring credits, then logs the transaction and uses small amounts of power. - **/ -/obj/machinery/rnd/bepis/proc/depositcash() - var/deposit_value = 0 - deposit_value = banking_amount - if(deposit_value == 0) - update_appearance() - say("Attempting to deposit 0 credits. Aborting.") - return - deposit_value = clamp(round(deposit_value, 1), 1, 10000) - if(!account) - say("Cannot find user account. Please swipe a valid ID.") - return - if(!account.has_money(deposit_value)) - say("You do not possess enough credits.") - return - account.adjust_money(-deposit_value, "Vending: B.E.P.I.S. Chamber") //The money vanishes, not paid to any accounts. - SSblackbox.record_feedback("amount", "BEPIS_credits_spent", deposit_value) - log_econ("[deposit_value] credits were inserted into [src] by [account.account_holder]") - banked_cash += deposit_value - use_power(1000 * power_saver) - return - -/** - * Proc used to determine the experiment math and results all in one. - * Uses banked_cash and stock part levels to determine minor, major, and real gauss values for the BEPIS to hold. - * If by the end real is larger than major, You get a tech disk. If all the disks are earned or you at least beat minor, you get a minor reward. - **/ - -/obj/machinery/rnd/bepis/proc/calcsuccess() - var/turf/dropturf = null - var/gauss_major = 0 - var/gauss_minor = 0 - var/gauss_real = 0 - - var/turf/my_turf = get_turf(src) - var/list/turfs = TURF_NEIGHBORS(my_turf) //NO MORE DISCS IN WINDOWS - while(length(turfs)) - var/turf/T = pick_n_take(turfs) - if(T.is_blocked_turf(TRUE)) - continue - else - dropturf = T - break - - if (!dropturf) - dropturf = drop_location() - gauss_major = (gaussian(major_threshold, std) - negative_cash_offset) //This is the randomized profit value that this experiment has to surpass to unlock a tech. - gauss_minor = (gaussian(minor_threshold, std) - negative_cash_offset) //And this is the threshold to instead get a minor prize. - gauss_real = (gaussian(banked_cash, std*inaccuracy_percentage) + positive_cash_offset) //this is the randomized profit value that your experiment expects to give. - say("Real: [gauss_real]. Minor: [gauss_minor]. Major: [gauss_major].") - flick("chamber_flash",src) - update_appearance() - banked_cash = 0 - if((gauss_real >= gauss_major)) //Major Success. - if(SSresearch.techweb_nodes_experimental.len > 0) - say("Experiment concluded with major success. New technology node discovered on technology disc.") - new /obj/item/disk/design_disk/bepis/remove_tech(dropturf,1) - return - say("Expended all available experimental technology nodes. Resorting to minor rewards.") - if(gauss_real >= gauss_minor) //Minor Success. - var/reward = pick(minor_rewards) - new reward(dropturf) - say("Experiment concluded with partial success. Dispensing compiled research efforts.") - return - if(gauss_real <= -1) //Critical Failure - say("ERROR: CRITICAL MACHIME MALFUNCTI- ON. CURRENCY IS NOT CRASH. CANNOT COMPUTE COMMAND: 'make bucks'") //not a typo, for once. - new /mob/living/basic/deer(dropturf, 1) - use_power(MACHINE_OVERLOAD * power_saver) //To prevent gambling at low cost and also prevent spamming for infinite deer. - return - //Minor Failure - error_cause = pick("attempted to sell grey products to American dominated market.","attempted to sell gray products to British dominated market.","placed wild assumption that PDAs would go out of style.","simulated product #76 damaged brand reputation mortally.","simulated business model resembled 'pyramid scheme' by 98.7%.","product accidently granted override access to all station doors.") - say("Experiment concluded with zero product viability. Cause of error: [error_cause]") - return - - -#undef MACHINE_OPERATION -#undef MACHINE_OVERLOAD -#undef MAJOR_THRESHOLD -#undef MINOR_THRESHOLD -#undef STANDARD_DEVIATION -#undef PART_CASH_OFFSET_AMOUNT diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm index f5421be6da69a..b196a06d30118 100644 --- a/code/modules/research/designs.dm +++ b/code/modules/research/designs.dm @@ -32,14 +32,14 @@ other types of metals and chemistry for reagents). /// List of materials required to create one unit of the product. Format is (typepath or caregory) -> amount var/list/materials = list() /// The amount of time required to create one unit of the product. - var/construction_time + var/construction_time = 3.2 SECONDS /// The typepath of the object produced by this design var/build_path = null /// Reagent produced by this design. Currently only supported by the biogenerator. var/make_reagent /// What categories this design falls under. Used for sorting in production machines. var/list/category = list() - /// List of reagents required to create one unit of the product. + /// List of reagents required to create one unit of the product. Currently only supported by the limb grower. var/list/reagents_list = list() /// The maximum number of units of whatever is produced by this can be produced in one go. var/maxstack = 1 diff --git a/code/modules/research/designs/autolathe/engineering_designs.dm b/code/modules/research/designs/autolathe/engineering_designs.dm index 4d065ff1dda2c..6249f5c645a1f 100644 --- a/code/modules/research/designs/autolathe/engineering_designs.dm +++ b/code/modules/research/designs/autolathe/engineering_designs.dm @@ -392,3 +392,37 @@ RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS, ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + +/datum/design/tram_controller + name = "Tram Controller Cabinet" + id = "tram_controller" + build_type = PROTOLATHE + materials = list( + /datum/material/titanium = SHEET_MATERIAL_AMOUNT * 4, + /datum/material/iron = SHEET_MATERIAL_AMOUNT * 2, + /datum/material/gold = SHEET_MATERIAL_AMOUNT * 7, + /datum/material/silver = SHEET_MATERIAL_AMOUNT * 7, + /datum/material/diamond = SHEET_MATERIAL_AMOUNT * 4, + ) + build_path = /obj/item/wallframe/tram/controller + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS, + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + +/datum/design/tram_display + name = "Tram Indicator Display" + id = "tram_display" + build_type = PROTOLATHE + materials = list( + /datum/material/titanium = SHEET_MATERIAL_AMOUNT * 4, + /datum/material/iron = SHEET_MATERIAL_AMOUNT * 1, + /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2, + ) + build_path = /obj/item/wallframe/indicator_display + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS, + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING diff --git a/code/modules/research/designs/autolathe/mining.dm b/code/modules/research/designs/autolathe/mining.dm new file mode 100644 index 0000000000000..bc83d27123d6e --- /dev/null +++ b/code/modules/research/designs/autolathe/mining.dm @@ -0,0 +1,32 @@ +// Autolathe-able circuitboards for starting with boulder processing machines. +/datum/design/board/smelter + name = "Boulder Smelter" + desc = "A circuitboard for a boulder smelter. Lowtech enough to be printed from the lathe." + id = "b_smelter" + build_type = AUTOLATHE + materials = list( + /datum/material/glass = SHEET_MATERIAL_AMOUNT, + /datum/material/iron = SHEET_MATERIAL_AMOUNT, + ) + build_path = /obj/item/circuitboard/machine/smelter + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_CARGO, + ) + departmental_flags = DEPARTMENT_BITFLAG_CARGO + +/datum/design/board/refinery + name = "Boulder Refinery" + desc = "A circuitboard for a boulder refinery. Lowtech enough to be printed from the lathe." + id = "b_refinery" + build_type = AUTOLATHE + materials = list( + /datum/material/glass = SHEET_MATERIAL_AMOUNT, + /datum/material/iron = SHEET_MATERIAL_AMOUNT, + ) + build_path = /obj/item/circuitboard/machine/refinery + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_CARGO, + ) + departmental_flags = DEPARTMENT_BITFLAG_CARGO diff --git a/code/modules/research/designs/autolathe/service_designs.dm b/code/modules/research/designs/autolathe/service_designs.dm index ea65fe3ef380e..6b8d7f474fca4 100644 --- a/code/modules/research/designs/autolathe/service_designs.dm +++ b/code/modules/research/designs/autolathe/service_designs.dm @@ -142,6 +142,18 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE +/datum/design/tongs + name = "Tongs" + id = "tongs" + build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 2) + build_path = /obj/item/kitchen/tongs + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_KITCHEN, + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE + /datum/design/tray name = "Serving Tray" id = "servingtray" @@ -515,6 +527,18 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE +/datum/design/fish_case + name = "Stasis Fish Case" + id = "fish_case" + build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT, /datum/material/plastic = SMALL_MATERIAL_AMOUNT) + build_path = /obj/item/storage/fish_case + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SERVICE, + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE + /datum/design/ticket_machine name = "Ticket Machine Frame" id = "ticket_machine" @@ -556,3 +580,14 @@ RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS, ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE + +/datum/design/barcode_scanner + name = "Barcode Scanner" + id = "barcode_scanner" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 2) + build_path = /obj/item/barcodescanner + category = list( + RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_SERVICE, + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE diff --git a/code/modules/research/designs/biogenerator_designs.dm b/code/modules/research/designs/biogenerator_designs.dm index f07ef21a8e7c6..14d5c12eb431c 100644 --- a/code/modules/research/designs/biogenerator_designs.dm +++ b/code/modules/research/designs/biogenerator_designs.dm @@ -185,3 +185,11 @@ materials = list(/datum/material/biomass = 1) build_path = /obj/item/rollingpaper category = list(RND_CATEGORY_INITIAL, RND_CATEGORY_BIO_MATERIALS) + +/datum/design/candle + name = "Candle" + id = "candle" + build_type = BIOGENERATOR + materials = list(/datum/material/biomass = 3) + build_path = /obj/item/flashlight/flare/candle + category = list(RND_CATEGORY_INITIAL, RND_CATEGORY_BIO_MATERIALS) diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 1c44e8bc4fb26..a73b445933394 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -358,16 +358,6 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE -/datum/design/board/bepis - name = "B.E.P.I.S. Board" - desc = "The circuit board for a B.E.P.I.S." - id = "bepis" - build_path = /obj/item/circuitboard/machine/bepis - category = list( - RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH - ) - departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_CARGO - /datum/design/board/protolathe name = "Protolathe Board" desc = "The circuit board for a protolathe." @@ -542,6 +532,16 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE +/datum/design/board/microwave_engineering + name = "Wireless Microwave Board" + desc = "The circuit board for a cell-powered microwave." + id = "microwave_engineering" + build_path = /obj/item/circuitboard/machine/microwave/engineering + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_KITCHEN + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_ENGINEERING + /datum/design/board/gibber name = "Gibber Board" desc = "The circuit board for a gibber." @@ -661,7 +661,7 @@ category = list( RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_SECURITY ) - departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SECURITY /datum/design/board/vendor name = "Vendor Board" @@ -734,6 +734,26 @@ ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SCIENCE +/datum/design/board/crossing_signal + name = "Crossing Signal Board" + desc = "The circuit board for a tram crossing signal." + id = "crossing_signal" + build_path = /obj/item/circuitboard/machine/crossing_signal + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_TELECOMMS + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + +/datum/design/board/guideway_sensor + name = "Guideway Sensor Board" + desc = "The circuit board for a tram proximity sensor." + id = "guideway_sensor" + build_path = /obj/item/circuitboard/machine/guideway_sensor + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_TELECOMMS + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + /datum/design/board/limbgrower name = "Limb Grower Board" desc = "The circuit board for a limb grower." @@ -1100,9 +1120,31 @@ name = "Machine Design (Bot Navigational Beacon)" desc = "The circuit board for a beacon that aids bot navigation." id = "botnavbeacon" - build_type = IMPRINTER build_path = /obj/item/circuitboard/machine/navbeacon category = list( RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ROBOTICS ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING + +/datum/design/board/fishing_portal_generator + name = "Fishing Portal Generator Board" + desc = "The circuit board for the fishing portal generator" + id = "fishing_portal_generator" + build_path = /obj/item/circuitboard/machine/fishing_portal_generator + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_SERVICE + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/board/brm + name = "Boulder Retrieval Matrix" + id = "brm" + materials = list( + /datum/material/glass = SHEET_MATERIAL_AMOUNT, + ) + build_path = /obj/item/circuitboard/machine/brm + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_TELEPORT, + ) + departmental_flags = DEPARTMENT_BITFLAG_CARGO diff --git a/code/modules/research/designs/mecha_designs.dm b/code/modules/research/designs/mecha_designs.dm index c495bdc9e47b5..21deb54280cd3 100644 --- a/code/modules/research/designs/mecha_designs.dm +++ b/code/modules/research/designs/mecha_designs.dm @@ -226,9 +226,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -243,9 +244,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_ammo/scattershot materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*3) - construction_time = 20 + construction_time = 2 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -260,9 +262,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/carbine materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -277,9 +280,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_ammo/incendiary materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*3) - construction_time = 20 + construction_time = 2 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -294,9 +298,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/energy/ion materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/silver=SHEET_MATERIAL_AMOUNT*3,/datum/material/uranium=SHEET_MATERIAL_AMOUNT) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -311,9 +316,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/energy/tesla materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/silver=SHEET_MATERIAL_AMOUNT*4) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -328,9 +334,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/energy/laser materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -345,9 +352,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -362,9 +370,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/energy/disabler materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -379,9 +388,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*11,/datum/material/gold=SHEET_MATERIAL_AMOUNT*3,/datum/material/silver=SHEET_MATERIAL_AMOUNT*4) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -396,9 +406,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_ammo/flashbang materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*2,/datum/material/gold=SMALL_MATERIAL_AMOUNT*5) - construction_time = 20 + construction_time = 2 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -413,9 +424,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/breaching materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*11,/datum/material/gold=SHEET_MATERIAL_AMOUNT*3,/datum/material/silver=SHEET_MATERIAL_AMOUNT*4) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -430,9 +442,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_ammo/missiles_pep materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*4,/datum/material/gold=SMALL_MATERIAL_AMOUNT*5) - construction_time = 20 + construction_time = 2 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -447,9 +460,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/clusterbang materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/gold=SHEET_MATERIAL_AMOUNT*5,/datum/material/uranium=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -464,9 +478,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_ammo/clusterbang materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*3,/datum/material/gold=HALF_SHEET_MATERIAL_AMOUNT * 1.5,/datum/material/uranium=HALF_SHEET_MATERIAL_AMOUNT * 1.5) - construction_time = 20 + construction_time = 2 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_WEAPONS, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -481,11 +496,12 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/wormhole_generator materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MISC, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -501,11 +517,12 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/teleporter materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/diamond=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MISC, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -521,11 +538,12 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/rcd materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*15,/datum/material/gold=SHEET_MATERIAL_AMOUNT*10,/datum/material/plasma=SHEET_MATERIAL_AMOUNT*12.5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*10) - construction_time = 1200 + construction_time = 2 MINUTES category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MISC, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -541,7 +559,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/thrusters/gas materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*12.5,/datum/material/titanium=SHEET_MATERIAL_AMOUNT * 2.5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*1.5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MODULES, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -561,11 +579,12 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/gravcatapult materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MISC, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -581,11 +600,12 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/repair_droid materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5,/datum/material/gold=HALF_SHEET_MATERIAL_AMOUNT,/datum/material/silver=SHEET_MATERIAL_AMOUNT) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MODULES, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -601,11 +621,12 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/armor/anticcw_armor_booster materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/silver=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MODULES, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -621,11 +642,12 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/armor/antiproj_armor_booster materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/gold=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MODULES, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -641,7 +663,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/drill/diamonddrill materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/diamond=SHEET_MATERIAL_AMOUNT*3.25) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MINING, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MINING, @@ -656,10 +678,11 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*4, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plasma =SHEET_MATERIAL_AMOUNT) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MINING, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -675,10 +698,11 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/energy/mecha_kineticgun materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*4, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MINING, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -694,9 +718,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MINING, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -710,9 +735,10 @@ build_type = MECHFAB build_path = /obj/item/mecha_ammo/lmg materials = list(/datum/material/iron= SHEET_MATERIAL_AMOUNT *2) - construction_time = 20 + construction_time = 2 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MINING, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT @@ -726,7 +752,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/medical/sleeper materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MEDICAL, RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT @@ -740,7 +766,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/medical/syringe_gun materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*1.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT) - construction_time = 200 + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MEDICAL, RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT @@ -753,7 +779,7 @@ id = "mech_medi_beam" build_type = MECHFAB materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*7.5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*4, /datum/material/plasma =SHEET_MATERIAL_AMOUNT*1.5, /datum/material/gold = SHEET_MATERIAL_AMOUNT*4, /datum/material/diamond =SHEET_MATERIAL_AMOUNT) - construction_time = 250 + construction_time = 25 SECONDS build_path = /obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MEDICAL, diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm index 8290be9b2ee0a..08c31feb06e87 100644 --- a/code/modules/research/designs/mechfabricator_designs.dm +++ b/code/modules/research/designs/mechfabricator_designs.dm @@ -5,7 +5,7 @@ build_type = MECHFAB build_path = /obj/item/robot_suit materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5) - construction_time = 500 + construction_time = 50 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG + RND_SUBCATEGORY_MECHFAB_CYBORG_CHASSIS ) @@ -16,7 +16,7 @@ build_type = MECHFAB build_path = /obj/item/bodypart/chest/robot materials = list(/datum/material/iron= SHEET_MATERIAL_AMOUNT*20) - construction_time = 350 + construction_time = 35 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG + RND_SUBCATEGORY_MECHFAB_CYBORG_CHASSIS ) @@ -27,7 +27,7 @@ build_type = MECHFAB build_path = /obj/item/bodypart/head/robot materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 350 + construction_time = 35 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG + RND_SUBCATEGORY_MECHFAB_CYBORG_CHASSIS ) @@ -38,7 +38,7 @@ build_type = MECHFAB build_path = /obj/item/bodypart/arm/left/robot materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 200 + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG + RND_SUBCATEGORY_MECHFAB_CYBORG_CHASSIS ) @@ -49,7 +49,7 @@ build_type = MECHFAB build_path = /obj/item/bodypart/arm/right/robot materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 200 + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG + RND_SUBCATEGORY_MECHFAB_CYBORG_CHASSIS ) @@ -60,7 +60,7 @@ build_type = MECHFAB build_path = /obj/item/bodypart/leg/left/robot materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 200 + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG + RND_SUBCATEGORY_MECHFAB_CYBORG_CHASSIS ) @@ -71,11 +71,73 @@ build_type = MECHFAB build_path = /obj/item/bodypart/leg/right/robot materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 200 + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG + RND_SUBCATEGORY_MECHFAB_CYBORG_CHASSIS ) +//Advanced Robotic Limbs + +/datum/design/advanced_l_arm + name = "Advanced Left Arm" + id = "advanced_l_arm" + build_type = MECHFAB + build_path = /obj/item/bodypart/arm/left/robot/advanced + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/titanium=SHEET_MATERIAL_AMOUNT*3, + /datum/material/gold=SHEET_MATERIAL_AMOUNT*3, + ) + construction_time = 20 SECONDS + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ADVANCED_LIMBS + ) + +/datum/design/advanced_r_arm + name = "Advanced Right Arm" + id = "advanced_r_arm" + build_type = MECHFAB + build_path = /obj/item/bodypart/arm/right/robot/advanced + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/titanium=SHEET_MATERIAL_AMOUNT*3, + /datum/material/gold=SHEET_MATERIAL_AMOUNT*3, + ) + construction_time = 20 SECONDS + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ADVANCED_LIMBS + ) + +/datum/design/advanced_l_leg + name = "Advanced Left Leg" + id = "advanced_l_leg" + build_type = MECHFAB + build_path = /obj/item/bodypart/leg/left/robot/advanced + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/titanium=SHEET_MATERIAL_AMOUNT*3, + /datum/material/gold=SHEET_MATERIAL_AMOUNT*3, + ) + construction_time = 20 SECONDS + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ADVANCED_LIMBS + ) + +/datum/design/advanced_r_leg + name = "Advanced Right Leg" + id = "advanced_r_leg" + build_type = MECHFAB + build_path = /obj/item/bodypart/leg/right/robot/advanced + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/titanium=SHEET_MATERIAL_AMOUNT*3, + /datum/material/gold=SHEET_MATERIAL_AMOUNT*3, + ) + construction_time = 20 SECONDS + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_ADVANCED_LIMBS + ) + //Ripley /datum/design/ripley_chassis name = "Exosuit Chassis (APLU \"Ripley\")" @@ -83,7 +145,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/chassis/ripley materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -93,8 +155,11 @@ id = "ripley_torso" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/ripley_torso - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/glass =SHEET_MATERIAL_AMOUNT*3.75) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*3.75, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -105,7 +170,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/ripley_left_arm materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5) - construction_time = 150 + construction_time = 15 SECONDS category = list( RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -116,7 +181,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/ripley_right_arm materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5) - construction_time = 150 + construction_time = 15 SECONDS category = list( RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -127,7 +192,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/ripley_left_leg materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5) - construction_time = 150 + construction_time = 15 SECONDS category = list( RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -138,7 +203,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/ripley_right_leg materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5) - construction_time = 150 + construction_time = 15 SECONDS category = list( RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -150,7 +215,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/chassis/odysseus materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -161,7 +226,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/odysseus_torso materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*6) - construction_time = 180 + construction_time = 18 SECONDS category = list( RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -171,8 +236,11 @@ id = "odysseus_head" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/odysseus_head - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*3,/datum/material/glass =SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*3, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*5 + ) + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -183,7 +251,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/odysseus_left_arm materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*3) - construction_time = 120 + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -194,7 +262,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/odysseus_right_arm materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*3) - construction_time = 120 + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -205,7 +273,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/odysseus_left_leg materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*3.5) - construction_time = 130 + construction_time = 13 SECONDS category = list( RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -216,7 +284,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/odysseus_right_leg materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*3.5) - construction_time = 130 + construction_time = 13 SECONDS category = list( RND_CATEGORY_MECHFAB_ODYSSEUS + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -228,7 +296,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/chassis/gygax materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -238,8 +306,13 @@ id = "gygax_torso" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/gygax_torso - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/glass =SHEET_MATERIAL_AMOUNT*5,/datum/material/gold=SHEET_MATERIAL_AMOUNT, /datum/material/silver=SHEET_MATERIAL_AMOUNT) - construction_time = 300 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*5, + /datum/material/gold=SHEET_MATERIAL_AMOUNT, + /datum/material/silver=SHEET_MATERIAL_AMOUNT, + ) + construction_time = 30 SECONDS category = list( RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -249,8 +322,13 @@ id = "gygax_head" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/gygax_head - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/gold=SHEET_MATERIAL_AMOUNT, /datum/material/silver=SHEET_MATERIAL_AMOUNT) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5, + /datum/material/gold=SHEET_MATERIAL_AMOUNT, + /datum/material/silver=SHEET_MATERIAL_AMOUNT, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -260,8 +338,12 @@ id = "gygax_left_arm" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/gygax_left_arm - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, /datum/material/gold=HALF_SHEET_MATERIAL_AMOUNT, /datum/material/silver=HALF_SHEET_MATERIAL_AMOUNT) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/gold=HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/silver=HALF_SHEET_MATERIAL_AMOUNT, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -271,8 +353,12 @@ id = "gygax_right_arm" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/gygax_right_arm - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, /datum/material/gold=HALF_SHEET_MATERIAL_AMOUNT, /datum/material/silver=HALF_SHEET_MATERIAL_AMOUNT) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/gold=HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/silver=HALF_SHEET_MATERIAL_AMOUNT, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -282,8 +368,12 @@ id = "gygax_left_leg" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/gygax_left_leg - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, /datum/material/gold=SHEET_MATERIAL_AMOUNT, /datum/material/silver=SHEET_MATERIAL_AMOUNT) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/gold=HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/silver=HALF_SHEET_MATERIAL_AMOUNT, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -293,8 +383,12 @@ id = "gygax_right_leg" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/gygax_right_leg - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, /datum/material/gold=SHEET_MATERIAL_AMOUNT, /datum/material/silver=SHEET_MATERIAL_AMOUNT) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/gold=HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/silver=HALF_SHEET_MATERIAL_AMOUNT, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -304,8 +398,13 @@ id = "gygax_armor" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/gygax_armor - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5,/datum/material/gold=SHEET_MATERIAL_AMOUNT*5, /datum/material/silver=SHEET_MATERIAL_AMOUNT*5, /datum/material/titanium=SHEET_MATERIAL_AMOUNT*5) - construction_time = 600 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/gold=SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*5, + /datum/material/titanium=SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 60 SECONDS category = list( RND_CATEGORY_MECHFAB_GYGAX + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -317,7 +416,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/chassis/durand materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*12.5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -327,8 +426,12 @@ id = "durand_torso" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/durand_torso - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*12.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*5) - construction_time = 300 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*12.5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 30 SECONDS category = list( RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -338,8 +441,12 @@ id = "durand_head" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/durand_head - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5,/datum/material/silver=SHEET_MATERIAL_AMOUNT) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -349,8 +456,11 @@ id = "durand_left_arm" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/durand_left_arm - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*2) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*2, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -360,8 +470,11 @@ id = "durand_right_arm" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/durand_right_arm - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*2) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*2, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -371,8 +484,11 @@ id = "durand_left_leg" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/durand_left_leg - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*2) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*2, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -382,8 +498,11 @@ id = "durand_right_leg" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/durand_right_leg - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*2) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*2, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -393,8 +512,12 @@ id = "durand_armor" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/durand_armor - materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT * 300,/datum/material/uranium=SHEET_MATERIAL_AMOUNT*12.5,/datum/material/titanium=SHEET_MATERIAL_AMOUNT*10) - construction_time = 600 + materials = list( + /datum/material/iron=SMALL_MATERIAL_AMOUNT * 300, + /datum/material/uranium=SHEET_MATERIAL_AMOUNT*12.5, + /datum/material/titanium=SHEET_MATERIAL_AMOUNT*10, + ) + construction_time = 60 SECONDS category = list( RND_CATEGORY_MECHFAB_DURAND + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -406,7 +529,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/chassis/honker materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -416,8 +539,12 @@ id = "honk_torso" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/honker_torso - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/glass =SHEET_MATERIAL_AMOUNT*5,/datum/material/bananium=SHEET_MATERIAL_AMOUNT*5) - construction_time = 300 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*5, + /datum/material/bananium=SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 30 SECONDS category = list( RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -427,8 +554,12 @@ id = "honk_head" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/honker_head - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5,/datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5, + /datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -438,8 +569,11 @@ id = "honk_left_arm" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/honker_left_arm - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5,/datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -449,8 +583,11 @@ id = "honk_right_arm" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/honker_right_arm - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5,/datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -460,8 +597,11 @@ id = "honk_left_leg" build_type = MECHFAB build_path =/obj/item/mecha_parts/part/honker_left_leg - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -471,8 +611,11 @@ id = "honk_right_leg" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/honker_right_leg - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -484,7 +627,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/chassis/phazon materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -494,8 +637,12 @@ id = "phazon_torso" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/phazon_torso - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*17.5,/datum/material/glass =SHEET_MATERIAL_AMOUNT*5,/datum/material/plasma=SHEET_MATERIAL_AMOUNT*10) - construction_time = 300 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*17.5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*5, + /datum/material/plasma=SHEET_MATERIAL_AMOUNT*10, + ) + construction_time = 30 SECONDS category = list( RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -505,8 +652,12 @@ id = "phazon_head" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/phazon_head - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5,/datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5,/datum/material/plasma=SHEET_MATERIAL_AMOUNT*5) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5, + /datum/material/plasma=SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -516,8 +667,11 @@ id = "phazon_left_arm" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/phazon_left_arm - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/plasma=SHEET_MATERIAL_AMOUNT*5) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/plasma=SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -527,8 +681,11 @@ id = "phazon_right_arm" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/phazon_right_arm - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/plasma=SHEET_MATERIAL_AMOUNT*5) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/plasma=SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -538,8 +695,11 @@ id = "phazon_left_leg" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/phazon_left_leg - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/plasma=SHEET_MATERIAL_AMOUNT*5) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/plasma=SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -549,8 +709,11 @@ id = "phazon_right_leg" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/phazon_right_leg - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/plasma=SHEET_MATERIAL_AMOUNT*5) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/plasma=SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -560,8 +723,12 @@ id = "phazon_armor" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/phazon_armor - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*12.5,/datum/material/plasma=SHEET_MATERIAL_AMOUNT*10,/datum/material/titanium=SHEET_MATERIAL_AMOUNT*10) - construction_time = 300 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*12.5, + /datum/material/plasma=SHEET_MATERIAL_AMOUNT*10, + /datum/material/titanium=SHEET_MATERIAL_AMOUNT*10, + ) + construction_time = 30 SECONDS category = list( RND_CATEGORY_MECHFAB_PHAZON + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -573,7 +740,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/chassis/savannah_ivanov materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -583,8 +750,11 @@ id = "savannah_ivanov_torso" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_torso - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/glass =SHEET_MATERIAL_AMOUNT*3.75) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*3.75, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -594,8 +764,11 @@ id = "savannah_ivanov_head" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_head - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*3,/datum/material/glass =SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*3, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -606,7 +779,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_left_arm materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5) - construction_time = 150 + construction_time = 15 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -617,7 +790,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_right_arm materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5) - construction_time = 150 + construction_time = 15 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -628,7 +801,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/chassis/savannah_ivanov materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*12.5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -638,8 +811,12 @@ id = "savannah_ivanov_torso" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_torso - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*12.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*5) - construction_time = 300 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*12.5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 30 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -649,8 +826,12 @@ id = "savannah_ivanov_head" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_head - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5,/datum/material/silver=SHEET_MATERIAL_AMOUNT) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -660,8 +841,11 @@ id = "savannah_ivanov_left_arm" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_left_arm - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*2) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*2, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -671,8 +855,11 @@ id = "savannah_ivanov_right_arm" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_right_arm - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*2) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*2, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -682,8 +869,11 @@ id = "savannah_ivanov_left_leg" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_left_leg - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*2) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*2, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -693,8 +883,11 @@ id = "savannah_ivanov_right_leg" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_right_leg - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5,/datum/material/silver=SHEET_MATERIAL_AMOUNT*2) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/silver=SHEET_MATERIAL_AMOUNT*2, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -704,8 +897,12 @@ id = "savannah_ivanov_armor" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/savannah_ivanov_armor - materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT * 300,/datum/material/uranium=SHEET_MATERIAL_AMOUNT*12.5,/datum/material/titanium=SHEET_MATERIAL_AMOUNT*10) - construction_time = 600 + materials = list( + /datum/material/iron=SMALL_MATERIAL_AMOUNT * 300, + /datum/material/uranium=SHEET_MATERIAL_AMOUNT*12.5, + /datum/material/titanium=SHEET_MATERIAL_AMOUNT*10, + ) + construction_time = 60 SECONDS category = list( RND_CATEGORY_MECHFAB_SAVANNAH_IVANOV + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -717,7 +914,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/chassis/clarke materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_CLARKE + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -727,8 +924,11 @@ id = "clarke_torso" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/clarke_torso - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/glass =SHEET_MATERIAL_AMOUNT*3.75) - construction_time = 200 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*3.75, + ) + construction_time = 20 SECONDS category = list( RND_CATEGORY_MECHFAB_CLARKE + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -738,8 +938,11 @@ id = "clarke_head" build_type = MECHFAB build_path = /obj/item/mecha_parts/part/clarke_head - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*3,/datum/material/glass =SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*3, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_CLARKE + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -750,7 +953,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/clarke_left_arm materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5) - construction_time = 150 + construction_time = 15 SECONDS category = list( RND_CATEGORY_MECHFAB_CLARKE + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -761,7 +964,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/part/clarke_right_arm materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*7.5) - construction_time = 150 + construction_time = 15 SECONDS category = list( RND_CATEGORY_MECHFAB_CLARKE + RND_SUBCATEGORY_MECHFAB_CHASSIS ) @@ -772,31 +975,54 @@ id = "ripleyupgrade" build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/ripleyupgrade - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/plasma=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 - category = list( - RND_CATEGORY_MECHFAB_EQUIPMENT, - RND_CATEGORY_MECHFAB_RIPLEY + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/plasma=SHEET_MATERIAL_AMOUNT*5, ) + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MODULES, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, ) +/datum/design/paddyupgrade + name = "Ripley MK-I to Paddy Conversion Kit" + id = "paddyupgrade" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/mecha_equipment/ripleyupgrade/paddy + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT * 10, + /datum/material/glass = SHEET_MATERIAL_AMOUNT * 5, + /datum/material/titanium = SHEET_MATERIAL_AMOUNT *5, + ) + construction_time = 10 SECONDS + category = list( + RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MODULES, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_CHASSIS, + ) + /datum/design/mech_hydraulic_clamp name = "Hydraulic Clamp" id = "mech_hydraulic_clamp" build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( - RND_CATEGORY_MECHFAB_EQUIPMENT, - RND_CATEGORY_MECHFAB_RIPLEY + RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MISC, + RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, ) + +/datum/design/mech_hydraulic_claw + name = "Hydraulic Claw" + id = "mech_hydraulic_claw" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/mecha_equipment/weapon/paddy_claw + materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MISC, - RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, + RND_CATEGORY_MECHFAB_PADDY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, ) /datum/design/mech_drill @@ -805,7 +1031,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/drill materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MINING, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -822,8 +1048,11 @@ id = "mech_mscanner" build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/mining_scanner - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT * 2.5,/datum/material/glass = SHEET_MATERIAL_AMOUNT *1.25) - construction_time = 50 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT * 2.5, + /datum/material/glass = SHEET_MATERIAL_AMOUNT *1.25, + ) + construction_time = 5 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MINING, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -836,7 +1065,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/extinguisher materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MISC, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -848,8 +1077,13 @@ id = "mech_generator" build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/generator - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5,/datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT,/datum/material/silver=SHEET_MATERIAL_AMOUNT,/datum/material/plasma=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 100 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/silver=SHEET_MATERIAL_AMOUNT, + /datum/material/plasma=SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MISC, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -866,8 +1100,11 @@ id = "mech_mousetrap_mortar" build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 300 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 30 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_HONK, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT @@ -879,8 +1116,11 @@ id = "mech_banana_mortar" build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 300 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/bananium=SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 30 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_HONK, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT @@ -892,8 +1132,11 @@ id = "mech_honker" build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/honker - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/bananium=SHEET_MATERIAL_AMOUNT*5) - construction_time = 500 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/bananium=SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 50 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_HONK, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT @@ -905,8 +1148,11 @@ id = "mech_punching_face" build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*10,/datum/material/bananium=SHEET_MATERIAL_AMOUNT*3.75) - construction_time = 400 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*10, + /datum/material/bananium=SHEET_MATERIAL_AMOUNT*3.75, + ) + construction_time = 40 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_HONK, RND_CATEGORY_MECHFAB_HONK + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT @@ -919,7 +1165,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/radio materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*2.5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MINING, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -937,7 +1183,7 @@ build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_equipment/air_tank materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MINING, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -959,7 +1205,7 @@ build_type = MECHFAB build_path = /obj/item/borg/upgrade/rename materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 120 + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ALL ) @@ -969,8 +1215,11 @@ id = "borg_upgrade_restart" build_type = MECHFAB build_path = /obj/item/borg_restart_board - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*10, /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 120 + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT*10, + /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ALL ) @@ -980,8 +1229,13 @@ id = "borg_upgrade_thrusters" build_type = MECHFAB build_path = /obj/item/borg/upgrade/thrusters - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*3, /datum/material/plasma =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/uranium =SHEET_MATERIAL_AMOUNT*3) - construction_time = 120 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*3, + /datum/material/plasma =SHEET_MATERIAL_AMOUNT * 2.5, + /datum/material/uranium =SHEET_MATERIAL_AMOUNT*3, + ) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ALL ) @@ -991,8 +1245,13 @@ id = "borg_upgrade_disablercooler" build_type = MECHFAB build_path = /obj/item/borg/upgrade/disablercooler - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*10, /datum/material/glass =SHEET_MATERIAL_AMOUNT*3, /datum/material/gold =SHEET_MATERIAL_AMOUNT, /datum/material/diamond =SHEET_MATERIAL_AMOUNT) - construction_time = 120 + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT*10, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*3, + /datum/material/gold =SHEET_MATERIAL_AMOUNT, + /datum/material/diamond =SHEET_MATERIAL_AMOUNT, + ) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_SECURITY ) @@ -1002,8 +1261,12 @@ id = "borg_upgrade_diamonddrill" build_type = MECHFAB build_path = /obj/item/borg/upgrade/ddrill - materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*3, /datum/material/diamond =SHEET_MATERIAL_AMOUNT) - construction_time = 80 + materials = list( + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*3, + /datum/material/diamond =SHEET_MATERIAL_AMOUNT, + ) + construction_time = 8 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_MINING ) @@ -1013,8 +1276,12 @@ id = "borg_upgrade_holding" build_type = MECHFAB build_path = /obj/item/borg/upgrade/soh - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*5, /datum/material/gold =SHEET_MATERIAL_AMOUNT, /datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT) - construction_time = 40 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*5, + /datum/material/gold =SHEET_MATERIAL_AMOUNT, + /datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT, + ) + construction_time = 4 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_MINING ) @@ -1024,8 +1291,12 @@ id = "borg_upgrade_lavaproof" build_type = MECHFAB build_path = /obj/item/borg/upgrade/lavaproof - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*5, /datum/material/plasma =SHEET_MATERIAL_AMOUNT*2, /datum/material/titanium =SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 120 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*5, + /datum/material/plasma =SHEET_MATERIAL_AMOUNT*2, + /datum/material/titanium =SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_MINING ) @@ -1035,8 +1306,12 @@ id = "borg_syndicate_module" build_type = MECHFAB build_path = /obj/item/borg/upgrade/syndicate - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*7.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, /datum/material/diamond =SHEET_MATERIAL_AMOUNT*5) - construction_time = 120 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/diamond =SHEET_MATERIAL_AMOUNT*5, + ) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ALL ) @@ -1046,8 +1321,12 @@ id = "borg_transform_clown" build_type = MECHFAB build_path = /obj/item/borg/upgrade/transform/clown - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*7.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, /datum/material/bananium =HALF_SHEET_MATERIAL_AMOUNT) - construction_time = 120 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/bananium =HALF_SHEET_MATERIAL_AMOUNT, + ) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ALL ) @@ -1057,8 +1336,11 @@ id = "borg_upgrade_selfrepair" build_type = MECHFAB build_path = /obj/item/borg/upgrade/selfrepair - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*7.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5) - construction_time = 80 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, + ) + construction_time = 8 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ALL ) @@ -1068,8 +1350,13 @@ id = "borg_upgrade_expandedsynthesiser" build_type = MECHFAB build_path = /obj/item/borg/upgrade/hypospray/expanded - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*7.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, /datum/material/plasma =SHEET_MATERIAL_AMOUNT*4, /datum/material/uranium =SHEET_MATERIAL_AMOUNT*4) - construction_time = 80 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/plasma =SHEET_MATERIAL_AMOUNT*4, + /datum/material/uranium =SHEET_MATERIAL_AMOUNT*4, + ) + construction_time = 8 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_MEDICAL ) @@ -1079,8 +1366,13 @@ id = "borg_upgrade_piercinghypospray" build_type = MECHFAB build_path = /obj/item/borg/upgrade/piercing_hypospray - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*7.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, /datum/material/titanium =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/diamond =SHEET_MATERIAL_AMOUNT * 1.5) - construction_time = 80 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/titanium =SHEET_MATERIAL_AMOUNT * 2.5, + /datum/material/diamond =SHEET_MATERIAL_AMOUNT * 1.5, + ) + construction_time = 8 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_MEDICAL ) @@ -1090,8 +1382,13 @@ id = "borg_upgrade_defibrillator" build_type = MECHFAB build_path = /obj/item/borg/upgrade/defib - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*4, /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/silver =SHEET_MATERIAL_AMOUNT*2, /datum/material/gold =SHEET_MATERIAL_AMOUNT * 1.5) - construction_time = 80 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*4, + /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5, + /datum/material/silver =SHEET_MATERIAL_AMOUNT*2, + /datum/material/gold =SHEET_MATERIAL_AMOUNT * 1.5, + ) + construction_time = 8 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_MEDICAL ) @@ -1101,8 +1398,12 @@ id = "borg_upgrade_surgicalprocessor" build_type = MECHFAB build_path = /obj/item/borg/upgrade/processor - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT*2, /datum/material/silver =SHEET_MATERIAL_AMOUNT*2) - construction_time = 40 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*2, + /datum/material/silver =SHEET_MATERIAL_AMOUNT*2, + ) + construction_time = 4 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_MEDICAL ) @@ -1112,8 +1413,11 @@ id = "borg_upgrade_trashofholding" build_type = MECHFAB build_path = /obj/item/borg/upgrade/tboh - materials = list(/datum/material/gold =SHEET_MATERIAL_AMOUNT, /datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT) - construction_time = 40 + materials = list( + /datum/material/gold =SHEET_MATERIAL_AMOUNT, + /datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT, + ) + construction_time = 4 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_JANITOR ) @@ -1123,8 +1427,11 @@ id = "borg_upgrade_advancedmop" build_type = MECHFAB build_path = /obj/item/borg/upgrade/amop - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT, /datum/material/glass =SHEET_MATERIAL_AMOUNT) - construction_time = 40 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT, + /datum/material/glass =SHEET_MATERIAL_AMOUNT, + ) + construction_time = 4 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_JANITOR ) @@ -1134,8 +1441,11 @@ id = "borg_upgrade_prt" build_type = MECHFAB build_path = /obj/item/borg/upgrade/prt - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*1.125, /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT*0.75) //same price as a cautery - construction_time = 40 + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT*1.125, + /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT*0.75, + ) + construction_time = 4 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_JANITOR ) @@ -1145,7 +1455,10 @@ id = "borg_upgrade_rolling_table" build_type = MECHFAB build_path = /obj/item/borg/upgrade/rolling_table - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*10, /datum/material/titanium = SMALL_MATERIAL_AMOUNT*7.5) //steeper price than a regular rolling table, with some added titanium to make up for the relative rarity of regular rolling tables + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT*10, + /datum/material/titanium = SMALL_MATERIAL_AMOUNT*7.5, + ) //steeper price than a regular rolling table, with some added titanium to make up for the relative rarity of regular rolling tables construction_time = 4 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_SERVICE @@ -1156,7 +1469,12 @@ id = "borg_upgrade_condiment_synthesizer" build_type = MECHFAB build_path = /obj/item/borg/upgrade/condiment_synthesizer - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*7.5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*6, /datum/material/plasma = SHEET_MATERIAL_AMOUNT*3, /datum/material/uranium = SHEET_MATERIAL_AMOUNT*3) //a bit cheaper than an expanded hypo for medical borg, + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/glass = SHEET_MATERIAL_AMOUNT*6, + /datum/material/plasma = SHEET_MATERIAL_AMOUNT*3, + /datum/material/uranium = SHEET_MATERIAL_AMOUNT*3, + ) //a bit cheaper than an expanded hypo for medical borg, construction_time = 4 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_SERVICE @@ -1167,7 +1485,11 @@ id = "borg_upgrade_silicon_knife" build_type = MECHFAB build_path = /obj/item/borg/upgrade/silicon_knife - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*7.5, /datum/material/gold = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/silver = HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/gold = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/silver = HALF_SHEET_MATERIAL_AMOUNT, + ) construction_time = 4 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_SERVICE @@ -1178,12 +1500,15 @@ id = "borg_upgrade_drink_apparatus" build_type = MECHFAB build_path = /obj/item/borg/upgrade/drink_app - materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass = SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/glass = SHEET_MATERIAL_AMOUNT, + ) construction_time = 4 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_SERVICE ) - + /datum/design/borg_upgrade_service_apparatus name = "Service Apparatus" id = "borg_upgrade_service_apparatus" @@ -1200,7 +1525,10 @@ id = "borg_upgrade_service_cookbook" build_type = MECHFAB build_path = /obj/item/borg/upgrade/service_cookbook - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*7.5, /datum/material/diamond = HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT*7.5, + /datum/material/diamond = HALF_SHEET_MATERIAL_AMOUNT, + ) construction_time = 4 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_SERVICE @@ -1211,8 +1539,11 @@ id = "borg_upgrade_expand" build_type = MECHFAB build_path = /obj/item/borg/upgrade/expand - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*100, /datum/material/titanium =SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 120 + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT*100, + /datum/material/titanium =SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ALL ) @@ -1222,8 +1553,12 @@ id = "borg_ai_control" build_type = MECHFAB build_path = /obj/item/borg/upgrade/ai - materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT*1.2, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT * 1.5, /datum/material/gold =SMALL_MATERIAL_AMOUNT * 2) - construction_time = 50 + materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT*1.2, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT * 1.5, + /datum/material/gold =SMALL_MATERIAL_AMOUNT * 2, + ) + construction_time = 5 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG + RND_SUBCATEGORY_MECHFAB_CYBORG_CONTROL_INTERFACES ) @@ -1234,8 +1569,22 @@ id = "borg_upgrade_rped" build_type = MECHFAB build_path = /obj/item/borg/upgrade/rped - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*5, /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5) - construction_time = 120 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*5, + /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5, + ) + construction_time = 12 SECONDS + category = list( + RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ENGINEERING + ) + +/datum/design/borg_upgrade_inducer + name = "Cyborg inducer" + id = "borg_upgrade_inducer" + build_type = MECHFAB + build_path = /obj/item/borg/upgrade/inducer + materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 5, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/silver = SHEET_MATERIAL_AMOUNT * 2) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ENGINEERING ) @@ -1245,8 +1594,11 @@ id = "borg_upgrade_circuitapp" build_type = MECHFAB build_path = /obj/item/borg/upgrade/circuit_app - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT, /datum/material/titanium =SMALL_MATERIAL_AMOUNT*5) - construction_time = 120 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT, + /datum/material/titanium =SMALL_MATERIAL_AMOUNT*5, + ) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ENGINEERING ) @@ -1256,8 +1608,11 @@ id = "borg_upgrade_beakerapp" build_type = MECHFAB build_path = /obj/item/borg/upgrade/beaker_app - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT, /datum/material/glass = SHEET_MATERIAL_AMOUNT*1.125) //Need glass for the new beaker too - construction_time = 120 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT, + /datum/material/glass = SHEET_MATERIAL_AMOUNT*1.125, + ) //Need glass for the new beaker too + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_MEDICAL ) @@ -1267,8 +1622,11 @@ id = "borg_upgrade_pinpointer" build_type = MECHFAB build_path = /obj/item/borg/upgrade/pinpointer - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) - construction_time = 120 + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, + ) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_MEDICAL ) @@ -1278,8 +1636,11 @@ id = "borg_upgrade_broomer" build_type = MECHFAB build_path = /obj/item/borg/upgrade/broomer - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*2, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) - construction_time = 120 + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*2, + /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, + ) + construction_time = 12 SECONDS category = list( RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_JANITOR ) @@ -1289,8 +1650,11 @@ desc = "The Warrior's bland acronym, MMI, obscures the true horror of this monstrosity." id = "mmi" build_type = MECHFAB - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) - construction_time = 75 + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, + ) + construction_time = 7.5 SECONDS build_path = /obj/item/mmi category = list( RND_CATEGORY_MECHFAB_CYBORG + RND_SUBCATEGORY_MECHFAB_CYBORG_CONTROL_INTERFACES @@ -1310,8 +1674,12 @@ desc = "The latest in Artificial Intelligences." id = "mmi_posi" build_type = MECHFAB - materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT*1.7, /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT*1.35, /datum/material/gold =SMALL_MATERIAL_AMOUNT*5) - construction_time = 75 + materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT*1.7, + /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT*1.35, + /datum/material/gold =SMALL_MATERIAL_AMOUNT*5 + ) + construction_time = 7.5 SECONDS build_path = /obj/item/mmi/posibrain category = list( RND_CATEGORY_MECHFAB_CYBORG + RND_SUBCATEGORY_MECHFAB_CYBORG_CONTROL_INTERFACES @@ -1325,7 +1693,7 @@ build_type = MECHFAB build_path =/obj/item/mecha_parts/mecha_tracking materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*5) - construction_time = 50 + construction_time = 5 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_MISC, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_SUPPORTED_EQUIPMENT, @@ -1342,8 +1710,12 @@ id = "mecha_tracking_ai_control" build_type = MECHFAB build_path = /obj/item/mecha_parts/mecha_tracking/ai_control - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, /datum/material/silver =SMALL_MATERIAL_AMOUNT * 2) - construction_time = 50 + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, + /datum/material/silver =SMALL_MATERIAL_AMOUNT * 2, + ) + construction_time = 5 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_CONTROL_INTERFACES, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_CONTROL_INTERFACES, @@ -1361,8 +1733,13 @@ id = "mecha_camera" build_type = MECHFAB build_path = /obj/item/mecha_parts/camera_kit - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, /datum/material/plasma =SMALL_MATERIAL_AMOUNT * 2, /datum/material/titanium =SMALL_MATERIAL_AMOUNT * 2) - construction_time = 50 + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, + /datum/material/plasma =SMALL_MATERIAL_AMOUNT * 2, + /datum/material/titanium =SMALL_MATERIAL_AMOUNT * 2, + ) + construction_time = 5 SECONDS category = list( RND_CATEGORY_MECHFAB_EQUIPMENT + RND_SUBCATEGORY_MECHFAB_EQUIPMENT_CONTROL_INTERFACES, RND_CATEGORY_MECHFAB_RIPLEY + RND_SUBCATEGORY_MECHFAB_CONTROL_INTERFACES, @@ -1379,8 +1756,11 @@ desc = "When a problem arises, SCIENCE is the solution." id = "sflash" build_type = MECHFAB - materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 7.5, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 7.5) - construction_time = 100 + materials = list( + /datum/material/iron = SMALL_MATERIAL_AMOUNT * 7.5, + /datum/material/glass = SMALL_MATERIAL_AMOUNT * 7.5, + ) + construction_time = 10 SECONDS build_path = /obj/item/assembly/flash/handheld category = list( RND_CATEGORY_MECHFAB_CYBORG @@ -1397,7 +1777,10 @@ desc = "A 'Nakamura Engineering' designed shell for a Modular Suit." id = "mod_shell" build_type = MECHFAB - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*5, /datum/material/plasma =SHEET_MATERIAL_AMOUNT * 2.5) + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*5, + /datum/material/plasma =SHEET_MATERIAL_AMOUNT * 2.5, + ) construction_time = 25 SECONDS build_path = /obj/item/mod/construction/shell category = list( @@ -1457,7 +1840,11 @@ desc = "External plating for a MODsuit." id = "mod_plating_standard" build_type = MECHFAB - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*3, /datum/material/glass =SHEET_MATERIAL_AMOUNT*1.5, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*3, + /datum/material/glass =SHEET_MATERIAL_AMOUNT*1.5, + /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT, + ) construction_time = 15 SECONDS build_path = /obj/item/mod/construction/plating category = list( @@ -1476,7 +1863,12 @@ name = "MOD Engineering Plating" id = "mod_plating_engineering" build_path = /obj/item/mod/construction/plating/engineering - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*3, /datum/material/gold =SHEET_MATERIAL_AMOUNT, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*3, + /datum/material/gold =SHEET_MATERIAL_AMOUNT, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT, + ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING research_icon_state = "engineering-plating" @@ -1484,7 +1876,12 @@ name = "MOD Atmospheric Plating" id = "mod_plating_atmospheric" build_path = /obj/item/mod/construction/plating/atmospheric - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*3, /datum/material/titanium =SHEET_MATERIAL_AMOUNT, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*3, + /datum/material/titanium =SHEET_MATERIAL_AMOUNT, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT, + ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING research_icon_state = "atmospheric-plating" @@ -1492,7 +1889,12 @@ name = "MOD Medical Plating" id = "mod_plating_medical" build_path = /obj/item/mod/construction/plating/medical - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*3, /datum/material/silver =SHEET_MATERIAL_AMOUNT, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*3, + /datum/material/silver =SHEET_MATERIAL_AMOUNT, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT, + ) departmental_flags = DEPARTMENT_BITFLAG_MEDICAL research_icon_state = "medical-plating" @@ -1500,7 +1902,12 @@ name = "MOD Security Plating" id = "mod_plating_security" build_path = /obj/item/mod/construction/plating/security - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*3, /datum/material/uranium =SHEET_MATERIAL_AMOUNT, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*3, + /datum/material/uranium =SHEET_MATERIAL_AMOUNT, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT, + ) departmental_flags = DEPARTMENT_BITFLAG_SECURITY research_icon_state = "security-plating" @@ -1508,7 +1915,12 @@ name = "MOD Cosmohonk Plating" id = "mod_plating_cosmohonk" build_path = /obj/item/mod/construction/plating/cosmohonk - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT*3, /datum/material/bananium =SHEET_MATERIAL_AMOUNT, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT*3, + /datum/material/bananium =SHEET_MATERIAL_AMOUNT, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT, + ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE research_icon_state = "cosmohonk-plating" @@ -1517,7 +1929,10 @@ desc = "A paint kit for Modular Suits." id = "mod_paint_kit" build_type = MECHFAB - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plastic =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plastic =SMALL_MATERIAL_AMOUNT*5, + ) construction_time = 5 SECONDS build_path = /obj/item/mod/paint category = list( @@ -1529,7 +1944,11 @@ desc = "A neck-worn piece of gear that can call with another MODlink-compatible device." id = "modlink_scryer" build_type = MECHFAB - materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/gold = SMALL_MATERIAL_AMOUNT * 3, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 3) + materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/gold = SMALL_MATERIAL_AMOUNT * 3, + /datum/material/glass = SMALL_MATERIAL_AMOUNT * 3, + ) construction_time = 5 SECONDS build_path = /obj/item/clothing/neck/link_scryer category = list( @@ -1542,7 +1961,10 @@ name = "MOD Module" build_type = MECHFAB construction_time = 1 SECONDS - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_GENERAL @@ -1556,19 +1978,28 @@ /datum/design/module/mod_storage name = "Storage Module" id = "mod_storage" - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT *1.25, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT *1.25, + /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/storage /datum/design/module/mod_storage_expanded name = "Expanded Storage Module" id = "mod_storage_expanded" - materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/uranium =SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, + /datum/material/uranium =SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/storage/large_capacity /datum/design/module/mod_visor_medhud name = "Medical Visor Module" id = "mod_visor_medhud" - materials = list(/datum/material/silver =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/silver =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/visor/medhud category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_MEDICAL @@ -1577,7 +2008,10 @@ /datum/design/module/mod_visor_diaghud name = "Diagnostic Visor Module" id = "mod_visor_diaghud" - materials = list(/datum/material/gold =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/gold =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/visor/diaghud category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SCIENCE @@ -1585,7 +2019,10 @@ /datum/design/module/mod_visor_sechud name = "Security Visor Module" id = "mod_visor_sechud" - materials = list(/datum/material/titanium =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/titanium =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/visor/sechud category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY @@ -1593,7 +2030,10 @@ /datum/design/module/mod_visor_meson name = "Meson Visor Module" id = "mod_visor_meson" - materials = list(/datum/material/uranium =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/uranium =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/visor/meson category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SUPPLY @@ -1601,7 +2041,10 @@ /datum/design/module/mod_visor_welding name = "Welding Protection Module" id = "mod_welding" - materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/welding category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_ENGINEERING @@ -1609,7 +2052,10 @@ /datum/design/module/mod_t_ray name = "T-Ray Scanner Module" id = "mod_t_ray" - materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/t_ray category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_ENGINEERING @@ -1617,7 +2063,10 @@ /datum/design/module/mod_health_analyzer name = "Health Analyzer Module" id = "mod_health_analyzer" - materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/health_analyzer category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_MEDICAL @@ -1626,7 +2075,10 @@ /datum/design/module/mod_stealth name = "Cloak Module" id = "mod_stealth" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/bluespace =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/bluespace =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/stealth category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY @@ -1640,7 +2092,10 @@ /datum/design/module/mod_magboot name = "Magnetic Stabilizator Module" id = "mod_magboot" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/gold =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/gold =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/magboot category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_ENGINEERING @@ -1649,7 +2104,10 @@ /datum/design/module/mod_mag_harness name = "Magnetic Harness Module" id = "mod_mag_harness" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT * 1.5, /datum/material/silver =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT * 1.5, + /datum/material/silver =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/magnetic_harness category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY @@ -1658,7 +2116,10 @@ /datum/design/module/mod_tether name = "Emergency Tether Module" id = "mod_tether" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/silver =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/silver =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/tether category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_ENGINEERING @@ -1673,7 +2134,10 @@ /datum/design/module/mod_rad_protection name = "Radiation Protection Module" id = "mod_rad_protection" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/rad_protection category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_ENGINEERING @@ -1681,13 +2145,19 @@ /datum/design/module/mod_emp_shield name = "EMP Shield Module" id = "mod_emp_shield" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/emp_shield /datum/design/module/mod_flashlight name = "Flashlight Module" id = "mod_flashlight" - materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/flashlight /datum/design/module/mod_reagent_scanner @@ -1702,7 +2172,10 @@ /datum/design/module/mod_gps name = "Internal GPS Module" id = "mod_gps" - materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/gps category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SUPPLY @@ -1711,7 +2184,10 @@ /datum/design/module/mod_constructor name = "Constructor Module" id = "mod_constructor" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/titanium =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/titanium =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/constructor category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_ENGINEERING @@ -1719,7 +2195,10 @@ /datum/design/module/mod_quick_carry name = "Quick Carry Module" id = "mod_quick_carry" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/titanium =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/titanium =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/quick_carry category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_MEDICAL @@ -1734,13 +2213,19 @@ /datum/design/module/mod_thermal_regulator name = "Thermal Regulator Module" id = "mod_thermal_regulator" - materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/thermal_regulator /datum/design/module/mod_injector name = "Injector Module" id = "mod_injector" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/diamond =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/diamond =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/injector category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_MEDICAL @@ -1749,7 +2234,10 @@ /datum/design/module/mod_bikehorn name = "Bike Horn Module" id = "mod_bikehorn" - materials = list(/datum/material/plastic =SMALL_MATERIAL_AMOUNT*5, /datum/material/iron =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/plastic =SMALL_MATERIAL_AMOUNT*5, + /datum/material/iron =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/bikehorn category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SERVICE @@ -1758,7 +2246,10 @@ /datum/design/module/mod_microwave_beam name = "Microwave Beam Module" id = "mod_microwave_beam" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/uranium =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/uranium =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/microwave_beam category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SERVICE @@ -1767,7 +2258,10 @@ /datum/design/module/mod_waddle name = "Waddle Module" id = "mod_waddle" - materials = list(/datum/material/plastic =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/plastic =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/waddle category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SERVICE @@ -1785,7 +2279,10 @@ /datum/design/module/mod_drill name = "Drill Module" id = "mod_drill" - materials = list(/datum/material/silver =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/iron =SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/silver =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/iron =SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/drill category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SUPPLY @@ -1803,7 +2300,10 @@ /datum/design/module/mod_organ_thrower name = "Organ Thrower Module" id = "mod_organ_thrower" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/organ_thrower category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_MEDICAL @@ -1812,31 +2312,46 @@ /datum/design/module/mod_pathfinder name = "Pathfinder Module" id = "mod_pathfinder" - materials = list(/datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/pathfinder /datum/design/module/mod_dna_lock name = "DNA Lock Module" id = "mod_dna_lock" - materials = list(/datum/material/diamond =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/diamond =SMALL_MATERIAL_AMOUNT*5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/dna_lock /datum/design/module/mod_plasma_stabilizer name = "Plasma Stabilizer Module" id = "mod_plasma" - materials = list(/datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/plasma_stabilizer /datum/design/module/mod_glove_translator name = "Glove Translator Module" id = "mod_sign_radio" - materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 7.5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron = SMALL_MATERIAL_AMOUNT * 7.5, + /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/signlang_radio /datum/design/module/mister_atmos name = "Resin Mister Module" id = "mod_mister_atmos" - materials = list(/datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/titanium =HALF_SHEET_MATERIAL_AMOUNT * 1.5) + materials = list( + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/titanium =HALF_SHEET_MATERIAL_AMOUNT * 1.5, + ) build_path = /obj/item/mod/module/mister/atmos category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_ENGINEERING @@ -1845,7 +2360,10 @@ /datum/design/module/mod_holster name = "Holster Module" id = "mod_holster" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT * 1.5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT * 1.5, + /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/holster category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY @@ -1854,7 +2372,12 @@ /datum/design/module/mod_sonar name = "Active Sonar Module" id = "mod_sonar" - materials = list(/datum/material/titanium = SMALL_MATERIAL_AMOUNT * 2.5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/gold =SMALL_MATERIAL_AMOUNT*5, /datum/material/uranium = SMALL_MATERIAL_AMOUNT * 2.5) + materials = list( + /datum/material/titanium = SMALL_MATERIAL_AMOUNT * 2.5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/gold =SMALL_MATERIAL_AMOUNT*5, + /datum/material/uranium = SMALL_MATERIAL_AMOUNT * 2.5, + ) build_path = /obj/item/mod/module/active_sonar category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY @@ -1863,7 +2386,10 @@ /datum/design/module/projectile_dampener name = "Projectile Dampener Module" id = "mod_projectile_dampener" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/bluespace =SMALL_MATERIAL_AMOUNT*5) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/bluespace =SMALL_MATERIAL_AMOUNT*5, + ) build_path = /obj/item/mod/module/projectile_dampener category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY @@ -1872,7 +2398,11 @@ /datum/design/module/surgicalprocessor name = "Surgical Processor Module" id = "mod_surgicalprocessor" - materials = list(/datum/material/titanium = SMALL_MATERIAL_AMOUNT * 2.5, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/silver =HALF_SHEET_MATERIAL_AMOUNT * 1.5) + materials = list( + /datum/material/titanium = SMALL_MATERIAL_AMOUNT * 2.5, + /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/silver =HALF_SHEET_MATERIAL_AMOUNT * 1.5, + ) build_path = /obj/item/mod/module/surgical_processor category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_MEDICAL @@ -1881,7 +2411,11 @@ /datum/design/module/threadripper name = "Thread Ripper Module" id = "mod_threadripper" - materials = list(/datum/material/titanium = SMALL_MATERIAL_AMOUNT * 2.5, /datum/material/plastic =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/silver =HALF_SHEET_MATERIAL_AMOUNT * 1.5) + materials = list( + /datum/material/titanium = SMALL_MATERIAL_AMOUNT * 2.5, + /datum/material/plastic =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/silver =HALF_SHEET_MATERIAL_AMOUNT * 1.5, + ) build_path = /obj/item/mod/module/thread_ripper category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_MEDICAL @@ -1890,7 +2424,11 @@ /datum/design/module/defibrillator name = "Defibrillator Module" id = "mod_defib" - materials = list(/datum/material/titanium = SMALL_MATERIAL_AMOUNT * 2.5, /datum/material/diamond =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/silver =HALF_SHEET_MATERIAL_AMOUNT * 1.5) + materials = list( + /datum/material/titanium = SMALL_MATERIAL_AMOUNT * 2.5, + /datum/material/diamond =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/silver =HALF_SHEET_MATERIAL_AMOUNT * 1.5, + ) build_path = /obj/item/mod/module/defibrillator category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_MEDICAL @@ -1912,7 +2450,10 @@ /datum/design/module/patienttransport name = "Patient Transport Module" id = "mod_patienttransport" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/bluespace =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/bluespace =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/criminalcapture/patienttransport category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_MEDICAL @@ -1921,7 +2462,10 @@ /datum/design/module/criminalcapture name = "Criminal Capture Module" id = "mod_criminalcapture" - materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/bluespace =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/bluespace =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/criminalcapture category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY @@ -1931,7 +2475,10 @@ /datum/design/module/disposal name = "Disposal Connector Module" id = "mod_disposal" - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT *1.25, /datum/material/titanium =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT *1.25, + /datum/material/titanium =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/disposal_connector category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SUPPLY @@ -1940,7 +2487,11 @@ /datum/design/module/joint_torsion name = "Joint Torsion Ratchet Module" id = "mod_joint_torsion" - materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/gold = SMALL_MATERIAL_AMOUNT*2.5, /datum/material/titanium = SMALL_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/gold = SMALL_MATERIAL_AMOUNT*2.5, + /datum/material/titanium = SMALL_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/joint_torsion category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUITS_MISC @@ -1949,7 +2500,11 @@ /datum/design/module/recycler name = "Recycler Module" id = "mod_recycler" - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT, /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plastic = SMALL_MATERIAL_AMOUNT*2) + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT, + /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plastic = SMALL_MATERIAL_AMOUNT*2, + ) build_path = /obj/item/mod/module/recycler category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SERVICE @@ -1958,7 +2513,12 @@ /datum/design/module/shooting_assistant name = "Shooting Assistant Module" id = "mod_shooting" - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT, /datum/material/silver = SMALL_MATERIAL_AMOUNT*2, /datum/material/gold = SMALL_MATERIAL_AMOUNT, /datum/material/diamond = SMALL_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT, + /datum/material/silver = SMALL_MATERIAL_AMOUNT*2, + /datum/material/gold = SMALL_MATERIAL_AMOUNT, + /datum/material/diamond = SMALL_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/shooting_assistant category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY @@ -1968,7 +2528,11 @@ /datum/design/module/mod_antigrav name = "Anti-Gravity Module" id = "mod_antigrav" - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT *1.25, /datum/material/glass =SHEET_MATERIAL_AMOUNT, /datum/material/uranium =SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT *1.25, + /datum/material/glass =SHEET_MATERIAL_AMOUNT, + /datum/material/uranium =SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/anomaly_locked/antigrav category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SCIENCE @@ -1977,7 +2541,11 @@ /datum/design/module/mod_teleporter name = "Teleporter Module" id = "mod_teleporter" - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT *1.25, /datum/material/glass =SHEET_MATERIAL_AMOUNT, /datum/material/bluespace =SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT *1.25, + /datum/material/glass =SHEET_MATERIAL_AMOUNT, + /datum/material/bluespace =SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/anomaly_locked/teleporter category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SCIENCE @@ -1986,7 +2554,12 @@ /datum/design/module/mod_kinesis name = "Kinesis Module" id = "mod_kinesis" - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT *1.25, /datum/material/glass =SHEET_MATERIAL_AMOUNT, /datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/bluespace =HALF_SHEET_MATERIAL_AMOUNT) + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT *1.25, + /datum/material/glass =SHEET_MATERIAL_AMOUNT, + /datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/bluespace =HALF_SHEET_MATERIAL_AMOUNT, + ) build_path = /obj/item/mod/module/anomaly_locked/kinesis category = list( RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_ENGINEERING diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index 2bb779e318bc7..46e43b0ac6661 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -352,13 +352,35 @@ desc = "A portable, foldable version of the medical bed. Perfect for paramedics or whenever you have mass casualties!" id = "medicalbed_emergency" build_type = PROTOLATHE | AWAY_LATHE - materials = list(/datum/material/titanium = SHEET_MATERIAL_AMOUNT * 2.7, /datum/material/plastic = SHEET_MATERIAL_AMOUNT * 1.7, /datum/material/diamond = SMALL_MATERIAL_AMOUNT * 5, /datum/material/bluespace = SMALL_MATERIAL_AMOUNT * 5) + materials = list(/datum/material/titanium = SHEET_MATERIAL_AMOUNT * 2.7, /datum/material/plastic = SHEET_MATERIAL_AMOUNT * 1.7) build_path = /obj/item/emergency_bed category = list( RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MEDICAL ) departmental_flags = DEPARTMENT_BITFLAG_MEDICAL +/datum/design/penlight + name = "Penlight" + id = "penlight" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*0.5) + build_path = /obj/item/flashlight/pen + category = list( + RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_MEDICAL + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + +/datum/design/penlight_paramedic + name = "Paramedic Penlight" + id = "penlight_paramedic" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*1) + build_path = /obj/item/flashlight/pen/paramedic + category = list( + RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_MEDICAL + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + ///////////////////////////////////////// //////////Cybernetic Implants//////////// ///////////////////////////////////////// @@ -368,7 +390,7 @@ desc = "This simple implant adds an internals connector to your back, allowing you to use internals without a mask and protecting you from being choked." id = "ci-breather" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 35 + construction_time = 3.5 SECONDS materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*2.5) build_path = /obj/item/organ/internal/cyberimp/mouth/breathing_tube category = list( @@ -382,7 +404,7 @@ id = "ci-surgery" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB materials = list (/datum/material/iron = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT * 1.5, /datum/material/silver =HALF_SHEET_MATERIAL_AMOUNT * 1.5) - construction_time =SMALL_MATERIAL_AMOUNT * 2 + construction_time = 2 SECONDS build_path = /obj/item/organ/internal/cyberimp/arm/surgery category = list( RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_IMPLANTS_UTILITY @@ -395,7 +417,7 @@ id = "ci-toolset" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB materials = list (/datum/material/iron = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT * 1.5, /datum/material/silver =HALF_SHEET_MATERIAL_AMOUNT * 1.5) - construction_time =SMALL_MATERIAL_AMOUNT * 2 + construction_time = 2 SECONDS build_path = /obj/item/organ/internal/cyberimp/arm/toolset category = list( RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_IMPLANTS_UTILITY @@ -407,7 +429,7 @@ desc = "These cybernetic eyes will display a medical HUD over everything you see. Wiggle eyes to control." id = "ci-medhud" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 50 + construction_time = 5 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*6, @@ -425,7 +447,7 @@ desc = "These cybernetic eyes will display a security HUD over everything you see. Wiggle eyes to control." id = "ci-sechud" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 50 + construction_time = 5 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*6, @@ -443,7 +465,7 @@ desc = "These cybernetic eyes will display a diagnostic HUD over everything you see. Wiggle eyes to control." id = "ci-diaghud" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 50 + construction_time = 5 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*6, @@ -461,7 +483,7 @@ desc = "These cybernetic eyes will give you X-ray vision. Blinking is futile." id = "ci-xray" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 60 + construction_time = 6 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*6, @@ -488,7 +510,7 @@ desc = "These cybernetic eyes will give you Thermal vision. Vertical slit pupil included." id = "ci-thermals" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 60 + construction_time = 6 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*6, @@ -513,7 +535,7 @@ desc = "This cybernetic brain implant will allow you to force your hand muscles to contract, preventing item dropping. Twitch ear to toggle." id = "ci-antidrop" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 60 + construction_time = 6 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*6, @@ -531,7 +553,7 @@ desc = "This implant will automatically give you back control over your central nervous system, reducing downtime when stunned." id = "ci-antistun" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 60 + construction_time = 6 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*6, @@ -549,7 +571,7 @@ desc = "This implant with synthesize and pump into your bloodstream a small amount of nutriment when you are starving." id = "ci-nutriment" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 + construction_time = 4 SECONDS materials = list( /datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, @@ -566,7 +588,7 @@ desc = "This implant with synthesize and pump into your bloodstream a small amount of nutriment when you are hungry." id = "ci-nutrimentplus" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 50 + construction_time = 5 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*6, @@ -584,7 +606,7 @@ desc = "This implant will attempt to revive you if you lose consciousness. For the faint of heart!" id = "ci-reviver" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 60 + construction_time = 6 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*8, /datum/material/glass = SMALL_MATERIAL_AMOUNT*8, @@ -602,7 +624,7 @@ desc = "This implant will allow you to use gas from environment or your internals for propulsion in zero-gravity areas." id = "ci-thrusters" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 80 + construction_time = 8 SECONDS materials = list( /datum/material/iron = SHEET_MATERIAL_AMOUNT*2, /datum/material/glass =SHEET_MATERIAL_AMOUNT, @@ -657,28 +679,64 @@ /datum/design/implant_chem name = "Chemical Implant Case" - desc = "A glass case containing an implant." + desc = "A glass case containing a chemical implant." id = "implant_chem" build_type = PROTOLATHE | AWAY_LATHE - materials = list(/datum/material/glass = SMALL_MATERIAL_AMOUNT*7) + materials = list(/datum/material/glass = SMALL_MATERIAL_AMOUNT * 7) build_path = /obj/item/implantcase/chem category = list( - RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_IMPLANTS_MISC + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_IMPLANTS_SECURITY ) departmental_flags = DEPARTMENT_BITFLAG_SECURITY | DEPARTMENT_BITFLAG_MEDICAL /datum/design/implant_tracking name = "Tracking Implant Case" - desc = "A glass case containing an implant." + desc = "A glass case containing a tracking implant." id = "implant_tracking" build_type = PROTOLATHE | AWAY_LATHE - materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 5, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 5) build_path = /obj/item/implantcase/tracking category = list( - RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_IMPLANTS_MISC + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_IMPLANTS_SECURITY ) departmental_flags = DEPARTMENT_BITFLAG_SECURITY | DEPARTMENT_BITFLAG_MEDICAL +/datum/design/implant_beacon + name = "Beacon Implant Case" + desc = "A glass case containing a beacon implant." + id = "implant_beacon" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 5, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 5, /datum/material/bluespace = SMALL_MATERIAL_AMOUNT * 3) + build_path = /obj/item/implantcase/beacon + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_IMPLANTS_SECURITY + ) + departmental_flags = DEPARTMENT_BITFLAG_SECURITY + +/datum/design/implant_bluespace + name = "Bluespace Grounding Implant Case" + desc = "A glass case containing a teleport blocker implant." + id = "implant_bluespace" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 5, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 5, /datum/material/bluespace = SMALL_MATERIAL_AMOUNT * 3) + build_path = /obj/item/implantcase/teleport_blocker + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_IMPLANTS_SECURITY + ) + departmental_flags = DEPARTMENT_BITFLAG_SECURITY + +/datum/design/implant_exile + name = "Exile Implant Case" + desc = "A glass case containing an exile implant." + id = "implant_exile" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 5, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 5, /datum/material/titanium = SMALL_MATERIAL_AMOUNT * 3) + build_path = /obj/item/implantcase/exile + category = list( + RND_CATEGORY_CYBERNETICS + RND_SUBCATEGORY_CYBERNETICS_IMPLANTS_SECURITY + ) + departmental_flags = DEPARTMENT_BITFLAG_SECURITY + //Cybernetic organs /datum/design/cybernetic_liver @@ -686,7 +744,7 @@ desc = "A basic cybernetic liver." id = "cybernetic_liver" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 + construction_time = 4 SECONDS materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) build_path = /obj/item/organ/internal/liver/cybernetic category = list( @@ -709,7 +767,7 @@ name = "Upgraded Cybernetic Liver" desc = "An upgraded cybernetic liver." id = "cybernetic_liver_tier3" - construction_time = 50 + construction_time = 5 SECONDS materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, /datum/material/silver=SMALL_MATERIAL_AMOUNT*5) build_path = /obj/item/organ/internal/liver/cybernetic/tier3 category = list( @@ -722,7 +780,7 @@ desc = "A basic cybernetic heart." id = "cybernetic_heart" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 + construction_time = 4 SECONDS materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) build_path = /obj/item/organ/internal/heart/cybernetic category = list( @@ -745,7 +803,7 @@ name = "Upgraded Cybernetic Heart" desc = "An upgraded cybernetic heart." id = "cybernetic_heart_tier3" - construction_time = 50 + construction_time = 5 SECONDS materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, /datum/material/silver=SMALL_MATERIAL_AMOUNT*5) build_path = /obj/item/organ/internal/heart/cybernetic/tier3 category = list( @@ -758,7 +816,7 @@ desc = "A basic pair of cybernetic lungs." id = "cybernetic_lungs" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 + construction_time = 4 SECONDS materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) build_path = /obj/item/organ/internal/lungs/cybernetic category = list( @@ -781,7 +839,7 @@ name = "Upgraded Cybernetic Lungs" desc = "A pair of upgraded cybernetic lungs." id = "cybernetic_lungs_tier3" - construction_time = 50 + construction_time = 5 SECONDS materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, /datum/material/silver =SMALL_MATERIAL_AMOUNT*5) build_path = /obj/item/organ/internal/lungs/cybernetic/tier3 category = list( @@ -794,7 +852,7 @@ desc = "A basic cybernetic stomach." id = "cybernetic_stomach" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 + construction_time = 4 SECONDS materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5) build_path = /obj/item/organ/internal/stomach/cybernetic category = list( @@ -817,7 +875,7 @@ name = "Upgraded Cybernetic Stomach" desc = "An upgraded cybernetic stomach." id = "cybernetic_stomach_tier3" - construction_time = 50 + construction_time = 5 SECONDS materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5, /datum/material/glass =SMALL_MATERIAL_AMOUNT*5, /datum/material/silver =SMALL_MATERIAL_AMOUNT*5) build_path = /obj/item/organ/internal/stomach/cybernetic/tier3 category = list( @@ -830,7 +888,7 @@ desc = "A Basic pair of cybernetic ears." id = "cybernetic_ears" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 30 + construction_time = 3 SECONDS materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*2.5, /datum/material/glass = SMALL_MATERIAL_AMOUNT*4) build_path = /obj/item/organ/internal/ears/cybernetic category = list( @@ -843,7 +901,7 @@ desc = "A pair of cybernetic ears." id = "cybernetic_ears_u" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 + construction_time = 4 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*5, /datum/material/glass = SMALL_MATERIAL_AMOUNT*5, @@ -860,7 +918,7 @@ desc = "A pair of whisper-sensitive cybernetic ears." id = "cybernetic_ears_whisper" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 + construction_time = 4 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*5, /datum/material/glass = SMALL_MATERIAL_AMOUNT*5, @@ -877,7 +935,7 @@ desc = "A pair of wall-penetrating cybernetic ears." id = "cybernetic_ears_xray" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 + construction_time = 4 SECONDS materials = list( /datum/material/iron = SMALL_MATERIAL_AMOUNT*5, /datum/material/glass = SMALL_MATERIAL_AMOUNT*5, @@ -894,7 +952,7 @@ desc = "A basic pair of cybernetic eyes." id = "cybernetic_eyes" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 30 + construction_time = 3 SECONDS materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*2.5, /datum/material/glass = SMALL_MATERIAL_AMOUNT*4) build_path = /obj/item/organ/internal/eyes/robotic/basic category = list( @@ -927,7 +985,7 @@ desc = "These reactive micro-shields will protect you from welders and flashes without obscuring your vision." id = "ci-welding" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 + construction_time = 4 SECONDS materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass = SMALL_MATERIAL_AMOUNT*4) build_path = /obj/item/organ/internal/eyes/robotic/shield category = list( @@ -945,7 +1003,7 @@ desc = "A pair of cybernetic eyes that can emit multicolored light" id = "ci-gloweyes" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB - construction_time = 40 + construction_time = 4 SECONDS materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*6, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) build_path = /obj/item/organ/internal/eyes/robotic/glow category = list( @@ -1114,6 +1172,13 @@ surgery = /datum/surgery/advanced/wing_reconstruction research_icon_state = "surgery_chest" +/datum/design/surgery/advanced_plastic_surgery + name = "Advanced Plastic Surgery" + desc = "An advanced form of the plastic surgery, allowing oneself to remodel someone's face and voice based off a picture of someones face" + surgery = /datum/surgery/plastic_surgery/advanced + id = "surgery_advanced_plastic_surgery" + research_icon_state = "surgery_head" + /datum/design/surgery/experimental_dissection name = "Experimental Dissection" desc = "An experimental surgical procedure that dissects bodies in exchange for research points at ancient R&D consoles." diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm index 3f4d4b8f8ec2a..ae8f7e43a4078 100644 --- a/code/modules/research/designs/misc_designs.dm +++ b/code/modules/research/designs/misc_designs.dm @@ -411,7 +411,7 @@ category = list( RND_CATEGORY_STOCK_PARTS + RND_SUBCATEGORY_STOCK_PARTS_MISC ) - departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SERVICE /datum/design/oxygen_tank name = "Oxygen Tank" @@ -588,7 +588,6 @@ id = "paint_remover" build_type = PROTOLATHE | AWAY_LATHE materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT) - reagents_list = list(/datum/reagent/acetone = 60) build_path = /obj/item/paint/paint_remover category = list( RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_JANITORIAL @@ -820,6 +819,17 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SECURITY +/datum/design/sec_pen + name = "Security Pen" + id = "sec_pen" + build_type = PROTOLATHE | AUTOLATHE + materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT) + build_path = /obj/item/pen/red/security + category = list( + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SECURITY + ) + departmental_flags = DEPARTMENT_BITFLAG_SECURITY + /datum/design/plumbing_rcd name = "Plumbing Constructor" id = "plumbing_rcd" diff --git a/code/modules/research/designs/power_designs.dm b/code/modules/research/designs/power_designs.dm index ea617f36428b3..3548d8ac38d41 100644 --- a/code/modules/research/designs/power_designs.dm +++ b/code/modules/research/designs/power_designs.dm @@ -8,7 +8,7 @@ id = "basic_cell" build_type = PROTOLATHE | AWAY_LATHE | AUTOLATHE |MECHFAB materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 7, /datum/material/glass =SMALL_MATERIAL_AMOUNT * 0.5) - construction_time=100 + construction_time = 10 SECONDS build_path = /obj/item/stock_parts/cell/empty category = list( RND_CATEGORY_STOCK_PARTS + RND_SUBCATEGORY_STOCK_PARTS_1 @@ -21,7 +21,7 @@ id = "high_cell" build_type = PROTOLATHE | AWAY_LATHE | AUTOLATHE | MECHFAB materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 7, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 0.6) - construction_time=100 + construction_time = 10 SECONDS build_path = /obj/item/stock_parts/cell/high/empty category = list( RND_CATEGORY_STOCK_PARTS + RND_SUBCATEGORY_STOCK_PARTS_2 @@ -34,7 +34,7 @@ id = "super_cell" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 7, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 0.7) - construction_time=100 + construction_time = 10 SECONDS build_path = /obj/item/stock_parts/cell/super/empty category = list( RND_CATEGORY_STOCK_PARTS + RND_SUBCATEGORY_STOCK_PARTS_3 @@ -47,7 +47,7 @@ id = "hyper_cell" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 7, /datum/material/gold = SMALL_MATERIAL_AMOUNT * 1.5, /datum/material/silver = SMALL_MATERIAL_AMOUNT * 1.5, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 0.8) - construction_time=100 + construction_time = 10 SECONDS build_path = /obj/item/stock_parts/cell/hyper/empty category = list( RND_CATEGORY_STOCK_PARTS + RND_SUBCATEGORY_STOCK_PARTS_3 @@ -60,7 +60,7 @@ id = "bluespace_cell" build_type = PROTOLATHE | AWAY_LATHE | MECHFAB materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 8, /datum/material/gold = SMALL_MATERIAL_AMOUNT * 1.2, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 1.6, /datum/material/diamond = SMALL_MATERIAL_AMOUNT * 1.6, /datum/material/titanium =SMALL_MATERIAL_AMOUNT * 3, /datum/material/bluespace =SMALL_MATERIAL_AMOUNT) - construction_time=100 + construction_time = 10 SECONDS build_path = /obj/item/stock_parts/cell/bluespace/empty category = list( RND_CATEGORY_STOCK_PARTS + RND_SUBCATEGORY_STOCK_PARTS_4 @@ -77,7 +77,19 @@ category = list( RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_ENGINEERING ) - departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/inducerengi + name = "Inducer" + desc = "The NT-75 Electromagnetic Power Inducer can wirelessly induce electric charge in an object, allowing you to recharge power cells without having to remove them." + id = "inducerengi" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 1.5, /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT) + build_path = /obj/item/inducer/empty + category = list( + RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_ENGINEERING + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING /datum/design/board/pacman name = "PACMAN Board" @@ -95,7 +107,7 @@ id = "turbine_part_compressor" build_type = PROTOLATHE | AWAY_LATHE materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS build_path = /obj/item/turbine_parts/compressor category = list( RND_CATEGORY_STOCK_PARTS + RND_SUBCATEGORY_STOCK_PARTS_TURBINE @@ -108,7 +120,7 @@ id = "turbine_part_rotor" build_type = PROTOLATHE | AWAY_LATHE materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS build_path = /obj/item/turbine_parts/rotor category = list( RND_CATEGORY_STOCK_PARTS + RND_SUBCATEGORY_STOCK_PARTS_TURBINE @@ -121,7 +133,7 @@ id = "turbine_part_stator" build_type = PROTOLATHE | AWAY_LATHE materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*5) - construction_time = 100 + construction_time = 10 SECONDS build_path = /obj/item/turbine_parts/stator category = list( RND_CATEGORY_STOCK_PARTS + RND_SUBCATEGORY_STOCK_PARTS_TURBINE diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm index 59cfd2643cec5..e33a1a7f2ab28 100644 --- a/code/modules/research/designs/weapon_designs.dm +++ b/code/modules/research/designs/weapon_designs.dm @@ -217,18 +217,6 @@ departmental_flags = DEPARTMENT_BITFLAG_SECURITY autolathe_exportable = FALSE -/datum/design/decloner - name = "Decloner Part Kit (Lethal)" - desc = "Your opponent will bubble into a messy pile of goop." - id = "decloner" - build_type = PROTOLATHE | AWAY_LATHE - materials = list(/datum/material/gold =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/uranium = SHEET_MATERIAL_AMOUNT * 5) - build_path = /obj/item/weaponcrafting/gunkit/decloner - category = list( - RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_KITS - ) - departmental_flags = DEPARTMENT_BITFLAG_SECURITY - autolathe_exportable = FALSE /datum/design/rapidsyringe name = "Rapid Syringe Gun" @@ -365,6 +353,20 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SECURITY +/datum/design/lasershell + name = "Scatter Laser Shotgun Shell (Lethal)" + desc = "A high-tech shotgun shell which houses an internal capacitor and laser focusing crystal inside of a shell casing. \ + Able to be fired from conventional ballistic shotguns with minimal rifling degradation. Also leaves most targets covered \ + in grotesque burns." + id = "lasershell" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 2, /datum/material/gold = HALF_SHEET_MATERIAL_AMOUNT) + build_path = /obj/item/ammo_casing/shotgun/scatterlaser + category = list( + RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO + ) + departmental_flags = DEPARTMENT_BITFLAG_SECURITY + /datum/design/techshell name = "Unloaded Technological Shotshell" desc = "A high-tech shotgun shell which can be crafted into more advanced shells to produce unique effects. \ diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm index a3b4a9a88660a..3606dd67e2e60 100644 --- a/code/modules/research/designs/wiremod_designs.dm +++ b/code/modules/research/designs/wiremod_designs.dm @@ -61,6 +61,11 @@ id = "comp_trigonometry" build_path = /obj/item/circuit_component/trigonometry +/datum/design/component/arctan2 + name = "Arctangent 2 Component" + id = "comp_arctan2" + build_path = /obj/item/circuit_component/arctan2 + /datum/design/component/clock name = "Clock Component" id = "comp_clock" @@ -352,15 +357,10 @@ id = "comp_pinpointer" build_path = /obj/item/circuit_component/pinpointer -/datum/design/component/bci - category = list( - RND_CATEGORY_CIRCUITRY + RND_SUBCATEGORY_CIRCUITRY_BCI_COMPONENTS - ) - -/datum/design/component/bci/bci_action - name = "BCI Action Component" - id = "comp_bci_action" - build_path = /obj/item/circuit_component/equipment_action/bci +/datum/design/component/equipment_action + name = "Equipment Action Component" + id = "comp_equip_action" + build_path = /obj/item/circuit_component/equipment_action /datum/design/component/bci/object_overlay name = "Object Overlay Component" @@ -412,11 +412,6 @@ id = "comp_filter_list" build_path = /obj/item/circuit_component/filter_list -/datum/design/component/mod_action - name = "MOD Action Component" - id = "comp_mod_action" - build_path = /obj/item/circuit_component/equipment_action/mod - /datum/design/component/id_getter name = "ID Getter Component" id = "comp_id_getter" diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm index 2d1e786208cfd..e9e34bfd806f0 100644 --- a/code/modules/research/destructive_analyzer.dm +++ b/code/modules/research/destructive_analyzer.dm @@ -1,225 +1,203 @@ -/* -Destructive Analyzer - -It is used to destroy hand-held objects and advance technological research. Controls are in the linked R&D console. - -Note: Must be placed within 3 tiles of the R&D Console -*/ +///How much power it costs to deconstruct an item. +#define DESTRUCTIVE_ANALYZER_POWER_USAGE (BASE_MACHINE_IDLE_CONSUMPTION * 2.5) +///The 'ID' for deconstructing items for Research points instead of nodes. +#define DESTRUCTIVE_ANALYZER_DESTROY_POINTS "research_points" + +/** + * ## Destructive Analyzer + * It is used to destroy hand-held objects and advance technological research. + */ /obj/machinery/rnd/destructive_analyzer name = "destructive analyzer" desc = "Learn science by destroying things!" icon_state = "d_analyzer" base_icon_state = "d_analyzer" circuit = /obj/item/circuitboard/machine/destructive_analyzer - var/decon_mod = 0 -/obj/machinery/rnd/destructive_analyzer/RefreshParts() +/obj/machinery/rnd/destructive_analyzer/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) . = ..() - var/T = 0 - for(var/datum/stock_part/stock_part in component_parts) - T += stock_part.tier - decon_mod = T - -/obj/machinery/rnd/destructive_analyzer/proc/ConvertReqString2List(list/source_list) - var/list/temp_list = params2list(source_list) - for(var/O in temp_list) - temp_list[O] = text2num(temp_list[O]) - return temp_list - -/obj/machinery/rnd/destructive_analyzer/Insert_Item(obj/item/O, mob/living/user) - if(!user.combat_mode) - . = 1 - if(!is_insertion_ready(user)) - return - if(!user.transferItemToLoc(O, src)) - to_chat(user, span_warning("\The [O] is stuck to your hand, you cannot put it in the [src.name]!")) - return - busy = TRUE - loaded_item = O - to_chat(user, span_notice("You add the [O.name] to the [src.name]!")) - flick("d_analyzer_la", src) - addtimer(CALLBACK(src, PROC_REF(finish_loading)), 10) - updateUsrDialog() -/obj/machinery/rnd/destructive_analyzer/proc/finish_loading() - update_appearance() - reset_busy() + var/screentip_set = FALSE + if(loaded_item) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Remove Item" + screentip_set = TRUE + else if(!isnull(held_item)) + context[SCREENTIP_CONTEXT_LMB] = "Insert Item" + screentip_set = TRUE -/obj/machinery/rnd/destructive_analyzer/update_icon_state() - icon_state = "[base_icon_state][loaded_item ? "_l" : null]" - return ..() + if(screentip_set) + . = CONTEXTUAL_SCREENTIP_SET -/obj/machinery/rnd/destructive_analyzer/proc/destroy_item(obj/item/thing, innermode = FALSE) - if(QDELETED(thing) || QDELETED(src)) - return FALSE - if(!innermode) - flick("d_analyzer_process", src) - busy = TRUE - addtimer(CALLBACK(src, PROC_REF(reset_busy)), 24) - use_power(250) - if(thing == loaded_item) - loaded_item = null - var/list/food = thing.GetDeconstructableContents() - for(var/obj/item/innerthing in food) - destroy_item(innerthing, TRUE) - for(var/mob/living/victim in thing) - if(victim.stat != DEAD) - victim.investigate_log("has been killed by a destructive analyzer.", INVESTIGATE_DEATHS) - victim.death() +/obj/machinery/rnd/destructive_analyzer/examine(mob/user) + . = ..() + if(!in_range(user, src) && !isobserver(user)) + return - qdel(thing) - loaded_item = null - if (!innermode) - update_appearance() + if(loaded_item) + . += span_notice("[EXAMINE_HINT("Left-Click")] to remove loaded item inside.") + else + . += span_notice("An item can be loaded inside via [EXAMINE_HINT("Left-Click")].") + +/obj/machinery/rnd/destructive_analyzer/attackby(obj/item/weapon, mob/living/user, params) + if(user.combat_mode) + return ..() + if(!is_insertion_ready(user)) + return ..() + if(!user.transferItemToLoc(weapon, src)) + to_chat(user, span_warning("\The [weapon] is stuck to your hand, you cannot put it in the [name]!")) + return TRUE + busy = TRUE + loaded_item = weapon + to_chat(user, span_notice("You add the [weapon.name] to the [name]!")) + flick("[base_icon_state]_la", src) + addtimer(CALLBACK(src, PROC_REF(finish_loading)), 1 SECONDS) return TRUE -/obj/machinery/rnd/destructive_analyzer/proc/user_try_decon_id(id, mob/user) - if(!istype(loaded_item)) - return FALSE +/obj/machinery/rnd/destructive_analyzer/AltClick(mob/user) + . = ..() + unload_item() - if (id && id != RESEARCH_MATERIAL_DESTROY_ID) - var/datum/techweb_node/TN = SSresearch.techweb_node_by_id(id) - if(!istype(TN)) - return FALSE - var/dpath = loaded_item.type - var/list/worths = TN.boost_item_paths[dpath] - var/list/differences = list() - var/list/already_boosted = stored_research.boosted_nodes[TN.id] - for(var/i in worths) - var/used = already_boosted? already_boosted[i] : 0 - var/value = min(worths[i], TN.research_costs[i]) - used - if(value > 0) - differences[i] = value - if(length(worths) && !length(differences)) - return FALSE - var/choice = tgui_alert(user, "Are you sure you want to destroy [loaded_item] to [!length(worths) ? "reveal [TN.display_name]" : "boost [TN.display_name] by [json_encode(differences)] point\s"]?", "Destructive Analyzer", list("Proceed", "Cancel")) - if(choice != "Proceed") - return FALSE - if(QDELETED(loaded_item) || QDELETED(src)) - return FALSE - SSblackbox.record_feedback("nested tally", "item_deconstructed", 1, list("[TN.id]", "[loaded_item.type]")) - if(destroy_item(loaded_item)) - stored_research.boost_with_item(SSresearch.techweb_node_by_id(TN.id), dpath) +/obj/machinery/rnd/destructive_analyzer/update_icon_state() + icon_state = "[base_icon_state][loaded_item ? "_l" : null]" + return ..() +/obj/machinery/rnd/destructive_analyzer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DestructiveAnalyzer") + ui.open() + +/obj/machinery/rnd/destructive_analyzer/ui_data(mob/user) + var/list/data = list() + data["server_connected"] = !!stored_research + data["node_data"] = list() + if(loaded_item) + data["item_icon"] = icon2base64(getFlatIcon(image(icon = loaded_item.icon, icon_state = loaded_item.icon_state), no_anim = TRUE)) + data["indestructible"] = !(loaded_item.resistance_flags & INDESTRUCTIBLE) + data["loaded_item"] = loaded_item + data["already_deconstructed"] = !!stored_research.deconstructed_items[loaded_item.type] + var/list/points = techweb_item_point_check(loaded_item) + data["recoverable_points"] = techweb_point_display_generic(points) + + var/list/boostable_nodes = techweb_item_unlock_check(loaded_item) + for(var/id in boostable_nodes) + var/datum/techweb_node/unlockable_node = SSresearch.techweb_node_by_id(id) + var/list/node_data = list() + node_data["node_name"] = unlockable_node.display_name + node_data["node_id"] = unlockable_node.id + node_data["node_hidden"] = !!stored_research.hidden_nodes[unlockable_node.id] + data["node_data"] += list(node_data) else - var/list/point_value = techweb_item_point_check(loaded_item) - if(stored_research.deconstructed_items[loaded_item.type]) - point_value = list() - var/user_mode_string = "" - if(length(point_value)) - user_mode_string = " for [json_encode(point_value)] points" - var/choice = tgui_alert(usr, "Are you sure you want to destroy [loaded_item][user_mode_string]?",, list("Proceed", "Cancel")) - if(choice == "Cancel") - return FALSE - if(QDELETED(loaded_item) || QDELETED(src)) - return FALSE - destroy_item(loaded_item) - return TRUE + data["loaded_item"] = null + return data + +/obj/machinery/rnd/destructive_analyzer/ui_static_data(mob/user) + var/list/data = list() + data["research_point_id"] = DESTRUCTIVE_ANALYZER_DESTROY_POINTS + return data + +/obj/machinery/rnd/destructive_analyzer/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) + return + + var/mob/user = usr + switch(action) + if("eject_item") + if(busy) + balloon_alert(user, "already busy!") + return TRUE + if(loaded_item) + unload_item() + return TRUE + if("deconstruct") + if(!user_try_decon_id(params["deconstruct_id"])) + say("Destructive analysis failed!") + return TRUE + +//This allows people to put syndicate screwdrivers in the machine. Secondary act still passes. +/obj/machinery/rnd/destructive_analyzer/screwdriver_act(mob/living/user, obj/item/tool) + return FALSE +///Drops the loaded item where it can and nulls it. /obj/machinery/rnd/destructive_analyzer/proc/unload_item() if(!loaded_item) return FALSE - loaded_item.forceMove(get_turf(src)) + playsound(loc, 'sound/machines/terminal_insert_disc.ogg', 30, FALSE) + loaded_item.forceMove(drop_location()) loaded_item = null - update_appearance() + update_appearance(UPDATE_ICON) return TRUE -/obj/machinery/rnd/destructive_analyzer/ui_interact(mob/user) - . = ..() - var/datum/browser/popup = new(user, "destructive_analyzer", name, 900, 600) - popup.set_content(ui_deconstruct()) - popup.open() +///Called in a timer callback after loading something into it, this handles resetting the 'busy' state back to its initial state +///So the machine can be used. +/obj/machinery/rnd/destructive_analyzer/proc/finish_loading() + update_appearance(UPDATE_ICON) + reset_busy() -/obj/machinery/rnd/destructive_analyzer/proc/ui_deconstruct() //Legacy code - var/list/l = list() - if(!loaded_item) - l += "
No item loaded. Standing-by...
" - else - l += "
[RDSCREEN_NOBREAK]" - l += "
[icon2html(loaded_item, usr)][loaded_item.name] Eject
[RDSCREEN_NOBREAK]" - l += "Select a node to boost by deconstructing this item. This item can boost:" +/** + * Destroys an item by going through all its contents (including itself) and calling destroy_item_individual + * Args: + * gain_research_points - Whether deconstructing each individual item should check for research points to boost. + */ +/obj/machinery/rnd/destructive_analyzer/proc/destroy_item(gain_research_points = FALSE) + if(QDELETED(loaded_item) || QDELETED(src)) + return FALSE + flick("[base_icon_state]_process", src) + busy = TRUE + addtimer(CALLBACK(src, PROC_REF(reset_busy)), 2.4 SECONDS) + use_power(DESTRUCTIVE_ANALYZER_POWER_USAGE) + var/list/all_contents = loaded_item.get_all_contents() + for(var/innerthing in all_contents) + destroy_item_individual(innerthing, gain_research_points) - var/anything = FALSE - var/list/boostable_nodes = techweb_item_boost_check(loaded_item) - for(var/id in boostable_nodes) - anything = TRUE - var/list/worth = boostable_nodes[id] - var/datum/techweb_node/N = SSresearch.techweb_node_by_id(id) - - l += "
[RDSCREEN_NOBREAK]" - if (stored_research.researched_nodes[N.id]) // already researched - l += "[N.display_name]" - l += "This node has already been researched." - else if(!length(worth)) // reveal only - if (stored_research.hidden_nodes[N.id]) - l += "[N.display_name]" - l += "This node will be revealed." - else - l += "[N.display_name]" - l += "This node has already been revealed." - else // boost by the difference - var/list/differences = list() - var/list/already_boosted = stored_research.boosted_nodes[N.id] - for(var/i in worth) - var/already_boosted_amount = already_boosted? stored_research.boosted_nodes[N.id][i] : 0 - var/amt = min(worth[i], N.research_costs[i]) - already_boosted_amount - if(amt > 0) - differences[i] = amt - if (length(differences)) - l += "[N.display_name]" - l += "This node will be boosted with the following:
[techweb_point_display_generic(differences)]" - else - l += "[N.display_name]" - l += "This node has already been boosted." - l += "
[RDSCREEN_NOBREAK]" - - var/list/point_values = techweb_item_point_check(loaded_item) - if(point_values) - anything = TRUE - l += "
[RDSCREEN_NOBREAK]" - if (stored_research.deconstructed_items[loaded_item.type]) - l += "Point Deconstruction" - l += "This item's points have already been claimed." - else - l += "Point Deconstruction" - l += "This item is worth:
[techweb_point_display_generic(point_values)]!" - l += "
[RDSCREEN_NOBREAK]" - - if(!(loaded_item.resistance_flags & INDESTRUCTIBLE)) - l += "
Destroy Item" - l += "
[RDSCREEN_NOBREAK]" - anything = TRUE - - if (!anything) - l += "Nothing!" - - l += "
" - - for(var/i in 1 to length(l)) - if(!findtextEx(l[i], RDSCREEN_NOBREAK)) - l[i] += "
" - . = l.Join("") - return replacetextEx(., RDSCREEN_NOBREAK, "") - -/obj/machinery/rnd/destructive_analyzer/Topic(raw, ls) - . = ..() - if(.) - return + loaded_item = null + update_appearance(UPDATE_ICON) + return TRUE - add_fingerprint(usr) - usr.set_machine(src) +/** + * Destroys the individual provided item + * Args: + * thing - The thing being destroyed. Generally an object, but it can be a mob too, such as intellicards and pAIs. + * gain_research_points - Whether deconstructing this should give research points to the stored techweb, if applicable. + */ +/obj/machinery/rnd/destructive_analyzer/proc/destroy_item_individual(obj/item/thing, gain_research_points = FALSE) + if(isliving(thing)) + var/mob/living/mob_thing = thing + if(mob_thing.stat != DEAD) + mob_thing.investigate_log("has been killed by a destructive analyzer.", INVESTIGATE_DEATHS) + mob_thing.death() + var/list/point_value = techweb_item_point_check(thing) + if(point_value && !stored_research.deconstructed_items[thing.type]) + stored_research.deconstructed_items[thing.type] = TRUE + stored_research.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = point_value)) + qdel(thing) + +/** + * Attempts to destroy the loaded item using a provided research id. + * Args: + * id - The techweb ID node that we're meant to unlock if applicable. + */ +/obj/machinery/rnd/destructive_analyzer/proc/user_try_decon_id(id) + if(!istype(loaded_item)) + return FALSE + if(isnull(id)) + return FALSE - if(ls["eject_item"]) //Eject the item inside the destructive analyzer. - if(busy) - to_chat(usr, span_danger("The destructive analyzer is busy at the moment.")) - return - if(loaded_item) - unload_item() - if(ls["deconstruct"]) - if(!user_try_decon_id(ls["deconstruct"], usr)) - say("Destructive analysis failed!") + var/item_type = loaded_item.type + if(id == DESTRUCTIVE_ANALYZER_DESTROY_POINTS) + if(!destroy_item(gain_research_points = TRUE)) + return FALSE + return TRUE - updateUsrDialog() + var/datum/techweb_node/node_to_discover = SSresearch.techweb_node_by_id(id) + if(!istype(node_to_discover)) + return FALSE + if(!destroy_item()) + return FALSE + SSblackbox.record_feedback("nested tally", "item_deconstructed", 1, list("[node_to_discover.id]", "[item_type]")) + stored_research.unhide_node(node_to_discover) + return TRUE -/obj/machinery/rnd/destructive_analyzer/screwdriver_act(mob/living/user, obj/item/tool) - return FALSE +#undef DESTRUCTIVE_ANALYZER_DESTROY_POINTS +#undef DESTRUCTIVE_ANALYZER_POWER_USAGE diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index c4983ad6c814f..8d7a3d3db1c49 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -26,6 +26,7 @@ density = TRUE use_power = IDLE_POWER_USE circuit = /obj/item/circuitboard/machine/experimentor + interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN|INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_OPEN_SILICON var/recentlyExperimented = 0 /// Weakref to the first ian we can find at init var/datum/weakref/tracked_ian_ref @@ -41,12 +42,6 @@ var/static/list/valid_items //valid items for special reactions like transforming var/list/critical_items_typecache //items that can cause critical reactions -/obj/machinery/rnd/experimentor/proc/ConvertReqString2List(list/source_list) - var/list/temp_list = params2list(source_list) - for(var/O in temp_list) - temp_list[O] = text2num(temp_list[O]) - return temp_list - /obj/machinery/rnd/experimentor/proc/valid_items() RETURN_TYPE(/list) @@ -95,7 +90,7 @@ . = ..() tracked_ian_ref = WEAKREF(locate(/mob/living/basic/pet/dog/corgi/ian) in GLOB.mob_living_list) - tracked_runtime_ref = WEAKREF(locate(/mob/living/simple_animal/pet/cat/runtime) in GLOB.mob_living_list) + tracked_runtime_ref = WEAKREF(locate(/mob/living/basic/pet/cat/runtime) in GLOB.mob_living_list) critical_items_typecache = typecacheof(list( /obj/item/construction/rcd, @@ -103,7 +98,6 @@ /obj/item/aicard, /obj/item/storage/backpack/holding, /obj/item/slime_extract, - /obj/item/onetankbomb, /obj/item/transfer_valve)) /obj/machinery/rnd/experimentor/RefreshParts() @@ -122,143 +116,132 @@ if(in_range(user, src) || isobserver(user)) . += span_notice("The status display reads: Malfunction probability reduced by [malfunction_probability_coeff]%.
Cooldown interval between experiments at [resetTime*0.1] seconds.") -/obj/machinery/rnd/experimentor/proc/checkCircumstances(obj/item/O) - //snowflake check to only take "made" bombs - if(istype(O, /obj/item/transfer_valve)) - var/obj/item/transfer_valve/T = O - if(!T.tank_one || !T.tank_two || !T.attached_device) - return FALSE +/obj/machinery/rnd/experimentor/attackby(obj/item/weapon, mob/living/user, params) + if(user.combat_mode) + return ..() + if(!is_insertion_ready(user)) + return ..() + if(!user.transferItemToLoc(weapon, src)) + to_chat(user, span_warning("\The [weapon] is stuck to your hand, you cannot put it in the [name]!")) + return TRUE + loaded_item = weapon + to_chat(user, span_notice("You add [weapon] to the machine.")) + flick("h_lathe_load", src) return TRUE -/obj/machinery/rnd/experimentor/Insert_Item(obj/item/O, mob/living/user) - if(!user.combat_mode) - . = 1 - if(!is_insertion_ready(user)) - return - if(!user.transferItemToLoc(O, src)) - return - loaded_item = O - to_chat(user, span_notice("You add [O] to the machine.")) - flick("h_lathe_load", src) - /obj/machinery/rnd/experimentor/default_deconstruction_crowbar(obj/item/O) ejectItem() - . = ..(O) - -/obj/machinery/rnd/experimentor/ui_interact(mob/user) - var/list/dat = list("
") - if(loaded_item) - dat += "Loaded Item: [loaded_item]" - - dat += "
Available tests:" - dat += "Poke" - dat += "Irradiate" - dat += "Gas" - dat += "Burn" - dat += "Freeze" - dat += "Destroy
" - if(istype(loaded_item,/obj/item/relic)) - dat += "Discover" - dat += "Eject" - var/list/listin = techweb_item_boost_check(src) - if(listin) - var/list/output = list("Research Boost Data:") - var/list/res = list("Already researched:") - var/list/boosted = list("Already boosted:") - for(var/node_id in listin) - var/datum/techweb_node/N = SSresearch.techweb_node_by_id(node_id) - var/str = "[N.display_name]: [listin[N]] points." - var/datum/techweb/science_web = locate(/datum/techweb/science) in SSresearch.techwebs - if(science_web.researched_nodes[N.id]) - res += str - else if(science_web.boosted_nodes[N.id]) - boosted += str - if(science_web.visible_nodes[N.id]) //JOY OF DISCOVERY! - output += str - output += boosted + res - dat += output - else - dat += "Nothing loaded." - dat += "Refresh" - dat += "Close
" - var/datum/browser/popup = new(user, "experimentor","Experimentor", 700, 400, src) - popup.set_content(dat.Join("
")) - popup.open() - onclose(user, "experimentor") - -/obj/machinery/rnd/experimentor/Topic(href, href_list) - if(..()) + return ..(O) + +/obj/machinery/rnd/experimentor/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new (user, src, "Experimentator") + ui.open() + +/obj/machinery/rnd/experimentor/ui_data(mob/user) + var/list/data = list() + + data["hasItem"] = !!loaded_item + data["isOnCooldown"] = recentlyExperimented + data["isServerConnected"] = !!stored_research + + if(!isnull(loaded_item)) + var/list/item_data = list() + + item_data["name"] = loaded_item.name + item_data["icon"] = icon2base64(getFlatIcon(loaded_item, no_anim = TRUE)) + item_data["isRelic"] = istype(loaded_item, /obj/item/relic) + + item_data["associatedNodes"] = list() + var/list/unlockable_nodes = techweb_item_unlock_check(loaded_item) + for(var/node_id in unlockable_nodes) + var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_id) + + item_data["associatedNodes"] += list(list( + "name" = node.display_name, + "isUnlocked" = !(node_id in stored_research.hidden_nodes), + )) + + data["loadedItem"] = item_data + + return data + +/obj/machinery/rnd/experimentor/ui_act(action, list/params) + . = ..() + if(.) return - usr.set_machine(src) - var/scantype = href_list["function"] - var/obj/item/process = locate(href_list["item"]) in src + switch(action) + if("eject") + ejectItem() + return TRUE + + if("experiment") + var/reaction = text2num(params["id"]) + if(isnull(reaction)) + return - if(href_list["close"]) - usr << browse(null, "window=experimentor") + try_perform_experiment(reaction) + return TRUE + +/obj/machinery/rnd/experimentor/proc/ejectItem(delete = FALSE) + if(isnull(loaded_item)) return - else if(scantype == "eject") - ejectItem() - else if(scantype == "refresh") - updateUsrDialog() - else - if(recentlyExperimented) - to_chat(usr, span_warning("[src] has been used too recently!")) - else if(!loaded_item) - to_chat(usr, span_warning("[src] is not currently loaded!")) - else if(!process || process != loaded_item) //Interface exploit protection (such as hrefs or swapping items with interface set to old item) - to_chat(usr, span_danger("Interface failure detected in [src]. Please try again.")) - else - var/dotype - if(text2num(scantype) == SCANTYPE_DISCOVER) - dotype = SCANTYPE_DISCOVER - else - dotype = matchReaction(process,scantype) - experiment(dotype,process) - use_power(750) - if(dotype != FAIL) - var/list/nodes = techweb_item_boost_check(process) - var/picked = pick_weight(nodes) //This should work. - stored_research.boost_with_item(SSresearch.techweb_node_by_id(picked), process.type) - updateUsrDialog() - -/obj/machinery/rnd/experimentor/proc/matchReaction(matching,reaction) - var/obj/item/D = matching - if(D) - var/list/item_reactions = item_reactions() - if(item_reactions.Find("[D.type]")) - var/tor = item_reactions["[D.type]"] - if(tor == text2num(reaction)) - return tor - else - return FAIL - else - return FAIL - else + + if(delete) + QDEL_NULL(loaded_item) + return + + var/atom/drop_atom = get_step(src, EAST) || drop_location() + if(cloneMode) + visible_message(span_notice("A duplicate of \the [loaded_item] pops out!")) + new loaded_item.type(drop_atom) + cloneMode = FALSE + return + + loaded_item.forceMove(drop_atom) + loaded_item = null + +/obj/machinery/rnd/experimentor/proc/match_reaction(obj/item/matching, target_reaction) + PRIVATE_PROC(TRUE) + if(isnull(matching) || isnull(target_reaction)) return FAIL -/obj/machinery/rnd/experimentor/proc/ejectItem(delete=FALSE) - if(loaded_item) - if(cloneMode) - visible_message(span_notice("A duplicate [loaded_item] pops out!")) - var/type_to_make = loaded_item.type - new type_to_make(get_turf(pick(oview(1,src)))) - cloneMode = FALSE - return - var/turf/dropturf = get_turf(pick(view(1,src))) - if(!dropturf) //Failsafe to prevent the object being lost in the void forever. - dropturf = drop_location() - loaded_item.forceMove(dropturf) - if(delete) - qdel(loaded_item) - loaded_item = null + var/list/item_reactions = item_reactions() + if("[matching.type]" in item_reactions) + var/associated_reaction = item_reactions["[matching.type]"] + if(associated_reaction == target_reaction) + return associated_reaction + + return FAIL + +/obj/machinery/rnd/experimentor/proc/try_perform_experiment(reaction) + PRIVATE_PROC(TRUE) + if(isnull(stored_research)) + return + + if(recentlyExperimented) + return + + if(isnull(loaded_item)) + return + + if(reaction != SCANTYPE_DISCOVER) + reaction = match_reaction(loaded_item, reaction) + + if(reaction != FAIL) + var/picked_node_id = pick(techweb_item_unlock_check(loaded_item)) + stored_research.unhide_node(SSresearch.techweb_node_by_id(picked_node_id)) + + experiment(reaction, loaded_item) + use_power(750) /obj/machinery/rnd/experimentor/proc/throwSmoke(turf/where) var/datum/effect_system/fluid_spread/smoke/smoke = new smoke.set_up(0, holder = src, location = where) smoke.start() - /obj/machinery/rnd/experimentor/proc/experiment(exp,obj/item/exp_on) recentlyExperimented = 1 icon_state = "[base_icon_state]_wloop" @@ -517,7 +500,7 @@ tracked_runtime.forceMove(drop_location()) investigate_log("Experimentor has stolen Runtime!", INVESTIGATE_EXPERIMENTOR) else - new /mob/living/simple_animal/pet/cat(loc) + new /mob/living/basic/pet/cat(loc) investigate_log("Experimentor failed to steal runtime, and instead spawned a new cat.", INVESTIGATE_EXPERIMENTOR) ejectItem(TRUE) if(globalMalf > 76 && globalMalf < 98) @@ -575,7 +558,7 @@ /obj/item/relic name = "strange object" desc = "What mysteries could this hold? Maybe Research & Development could find out." - icon = 'icons/obj/assemblies/assemblies.dmi' + icon = 'icons/obj/devices/assemblies.dmi' var/realName = "defined object" var/revealed = FALSE var/realProc @@ -647,11 +630,11 @@ /mob/living/basic/crab, /mob/living/basic/lizard, /mob/living/basic/mouse, + /mob/living/basic/parrot, + /mob/living/basic/pet/cat, /mob/living/basic/pet/dog/corgi, /mob/living/basic/pet/dog/pug, /mob/living/basic/pet/fox, - /mob/living/simple_animal/parrot/natural, - /mob/living/simple_animal/pet/cat, ) for(var/counter in 1 to rand(1, 25)) var/mobType = pick(valid_animals) diff --git a/code/modules/research/machinery/_production.dm b/code/modules/research/machinery/_production.dm index e5a426d3a1ef1..f95145b4b9406 100644 --- a/code/modules/research/machinery/_production.dm +++ b/code/modules/research/machinery/_production.dm @@ -1,42 +1,39 @@ /obj/machinery/rnd/production name = "technology fabricator" desc = "Makes researched and prototype items with materials and energy." - layer = BELOW_OBJ_LAYER /// The efficiency coefficient. Material costs and print times are multiplied by this number; - /// better parts result in a higher efficiency (and lower value). var/efficiency_coeff = 1 - /// The material storage used by this fabricator. var/datum/component/remote_materials/materials - - /// Which departments forego the lathe tax when using this lathe. + /// Which departments are allowed to process this design var/allowed_department_flags = ALL - - /// What's flick()'d on print. + /// Icon state when production has started var/production_animation - /// The types of designs this fabricator can print. var/allowed_buildtypes = NONE - /// All designs in the techweb that can be fabricated by this machine, since the last update. var/list/datum/design/cached_designs - /// What color is this machine's stripe? Leave null to not have a stripe. var/stripe_color = null - - /// Does this charge the user's ID on fabrication? - var/charges_tax = TRUE + ///direction we output onto (if 0, on top of us) + var/drop_direction = 0 /obj/machinery/rnd/production/Initialize(mapload) . = ..() cached_designs = list() + materials = AddComponent( /datum/component/remote_materials, \ mapload, \ - mat_container_flags = BREAKDOWN_FLAGS_LATHE, \ + mat_container_signals = list( \ + COMSIG_MATCONTAINER_ITEM_CONSUMED = TYPE_PROC_REF(/obj/machinery/rnd, local_material_insert) + ) \ ) + + RegisterSignal(src, COMSIG_SILO_ITEM_CONSUMED, TYPE_PROC_REF(/obj/machinery/rnd/production, silo_material_insert)) + AddComponent( /datum/component/payment, \ 0, \ @@ -45,10 +42,48 @@ TRUE, \ ) - create_reagents(0, OPENCONTAINER) - RefreshParts() update_icon(UPDATE_OVERLAYS) +/obj/machinery/rnd/production/Destroy() + materials = null + cached_designs = null + return ..() + + +// Stuff for the stripe on the department machines +/obj/machinery/rnd/production/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver) + . = ..() + + update_icon(UPDATE_OVERLAYS) + +/obj/machinery/rnd/production/update_overlays() + . = ..() + + if(!stripe_color) + return + + var/mutable_appearance/stripe = mutable_appearance('icons/obj/machines/research.dmi', "protolathe_stripe[panel_open ? "_t" : ""]") + stripe.color = stripe_color + . += stripe + +/obj/machinery/rnd/production/examine(mob/user) + . = ..() + if(!in_range(user, src) && !isobserver(user)) + return + + . += span_notice("Material usage cost at [efficiency_coeff * 100]%") + if(drop_direction) + . += span_notice("Currently configured to drop printed objects [dir2text(drop_direction)].") + . += span_notice("[EXAMINE_HINT("Alt-click")] to reset.") + else + . += span_notice("[EXAMINE_HINT("Drag")] towards a direction (while next to it) to change drop direction.") + +/obj/machinery/rnd/production/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(drop_direction) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Reset Drop" + return CONTEXTUAL_SCREENTIP_SET + /obj/machinery/rnd/production/connect_techweb(datum/techweb/new_techweb) if(stored_research) UnregisterSignal(stored_research, list(COMSIG_TECHWEB_ADD_DESIGN, COMSIG_TECHWEB_REMOVE_DESIGN)) @@ -59,24 +94,14 @@ RegisterSignals( stored_research, list(COMSIG_TECHWEB_ADD_DESIGN, COMSIG_TECHWEB_REMOVE_DESIGN), - PROC_REF(on_techweb_update) + TYPE_PROC_REF(/obj/machinery/rnd/production, on_techweb_update) ) update_designs() -/obj/machinery/rnd/production/Destroy() - materials = null - cached_designs = null - return ..() - -/obj/machinery/rnd/production/proc/on_techweb_update() - SIGNAL_HANDLER - - // We're probably going to get more than one update (design) at a time, so batch - // them together. - addtimer(CALLBACK(src, PROC_REF(update_designs)), 2 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) - /// Updates the list of designs this fabricator can print. /obj/machinery/rnd/production/proc/update_designs() + PROTECTED_PROC(TRUE) + var/previous_design_count = cached_designs.len cached_designs.Cut() @@ -95,12 +120,100 @@ update_static_data_for_all_viewers() +/obj/machinery/rnd/production/proc/on_techweb_update() + SIGNAL_HANDLER + + // We're probably going to get more than one update (design) at a time, so batch + // them together. + addtimer(CALLBACK(src, PROC_REF(update_designs)), 2 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) + +///When materials are instered via silo link +/obj/machinery/rnd/proc/silo_material_insert(obj/machinery/rnd/machine, container, obj/item/item_inserted, last_inserted_id, list/mats_consumed, amount_inserted) + SIGNAL_HANDLER + + process_item(item_inserted, mats_consumed, amount_inserted) + +/** + * Consumes power for the item inserted either into silo or local storage. + * Arguments + * + * * obj/item/item_inserted - the item to process + * * list/mats_consumed - list of mats consumed + * * amount_inserted - amount of material actually processed + */ +/obj/machinery/rnd/proc/process_item(obj/item/item_inserted, list/mats_consumed, amount_inserted) + PRIVATE_PROC(TRUE) + + //we use initial(active_power_usage) because higher tier parts will have higher active usage but we have no benifit from it + if(directly_use_power(ROUND_UP((amount_inserted / (MAX_STACK_SIZE * SHEET_MATERIAL_AMOUNT)) * 0.01 * initial(active_power_usage)))) + var/mat_name = "iron" + + var/highest_mat = 0 + for(var/datum/material/mat as anything in mats_consumed) + var/present_mat = mats_consumed[mat] + if(present_mat > highest_mat) + mat_name = initial(mat.name) + if(mat_name == "silver" || mat_name == "titanium" || mat_name == "plastic") //these materials have similar appearances so use an common overlay for them + mat_name = "shiny" + highest_mat = present_mat + + flick_animation(mat_name) +/** + * Plays an visual animation when materials are inserted + * Arguments + * + * * mat_name - the name of the material we are trying to animate on the machine + */ +/obj/machinery/rnd/proc/flick_animation(mat_name) + PROTECTED_PROC(TRUE) + SHOULD_CALL_PARENT(FALSE) + + flick_overlay_view(mutable_appearance('icons/obj/machines/research.dmi', "protolathe_[mat_name]"), 1 SECONDS) + +///When materials are instered into local storage +/obj/machinery/rnd/proc/local_material_insert(container, obj/item/item_inserted, last_inserted_id, list/mats_consumed, amount_inserted, atom/context) + SIGNAL_HANDLER + + process_item(item_inserted, mats_consumed, amount_inserted) + /obj/machinery/rnd/production/RefreshParts() . = ..() - calculate_efficiency() + if(materials) + var/total_storage = 0 + for(var/datum/stock_part/matter_bin/bin in component_parts) + total_storage += bin.tier * 37.5 * SHEET_MATERIAL_AMOUNT + materials.set_local_size(total_storage) + + efficiency_coeff = compute_efficiency() + update_static_data_for_all_viewers() +///Computes this machines cost efficiency based on the available parts +/obj/machinery/rnd/production/proc/compute_efficiency() + PROTECTED_PROC(TRUE) + + var/efficiency = 1.2 + for(var/datum/stock_part/servo/servo in component_parts) + efficiency -= servo.tier * 0.1 + + return efficiency + +/** + * The cost efficiency for an particular design + * Arguments + * + * * path - the design path to check for + */ +/obj/machinery/rnd/production/proc/build_efficiency(path) + PRIVATE_PROC(TRUE) + SHOULD_BE_PURE(TRUE) + + if(ispath(path, /obj/item/stack/sheet) || ispath(path, /obj/item/stack/ore/bluespace_crystal)) + return 1 + else + return efficiency_coeff + /obj/machinery/rnd/production/ui_assets(mob/user) return list( get_asset_datum(/datum/asset/spritesheet/sheetmaterials), @@ -108,10 +221,7 @@ ) /obj/machinery/rnd/production/ui_interact(mob/user, datum/tgui/ui) - user.set_machine(src) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) ui = new(user, src, "Fabricator") ui.open() @@ -124,16 +234,13 @@ var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs) var/size32x32 = "[spritesheet.name]32x32" - var/max_multiplier = INFINITY var/coefficient for(var/datum/design/design in cached_designs) var/cost = list() - max_multiplier = INFINITY coefficient = build_efficiency(design.build_path) for(var/datum/material/mat in design.materials) cost[mat.name] = OPTIMAL_COST(design.materials[mat] * coefficient) - max_multiplier = min(max_multiplier, 50, round(materials.mat_container.get_material_amount(mat) / cost[mat.name])) var/icon_size = spritesheet.icon_size_id(design.id) designs[design.id] = list( @@ -142,9 +249,7 @@ "cost" = cost, "id" = design.id, "categories" = design.category, - "icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]", - "constructionTime" = 0, - "maxmult" = max_multiplier + "icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]" ) data["designs"] = designs @@ -163,186 +268,175 @@ return data -/obj/machinery/rnd/production/ui_act(action, list/params) +/obj/machinery/rnd/production/ui_act(action, list/params, datum/tgui/ui) . = ..() - if(.) return - . = TRUE - switch (action) if("remove_mat") var/datum/material/material = locate(params["ref"]) - var/amount = text2num(params["amount"]) - // SAFETY: eject_sheets checks for valid mats - materials.eject_sheets(material, amount) - - if("build") - user_try_print_id(params["ref"], params["amount"]) + if(!istype(material)) + return -/// Updates the fabricator's efficiency coefficient based on the installed parts. -/obj/machinery/rnd/production/proc/calculate_efficiency() - efficiency_coeff = 1 + var/amount = params["amount"] + if(isnull(amount)) + return - if(reagents) - reagents.maximum_volume = 0 + amount = text2num(amount) + if(isnull(amount)) + return - for(var/obj/item/reagent_containers/cup/beaker in component_parts) - reagents.maximum_volume += beaker.volume - beaker.reagents.trans_to(src, beaker.reagents.total_volume) + //we use initial(active_power_usage) because higher tier parts will have higher active usage but we have no benifit from it + if(!directly_use_power(ROUND_UP((amount / MAX_STACK_SIZE) * 0.01 * initial(active_power_usage)))) + say("No power to dispense sheets") + return - if(materials) - var/total_storage = 0 - - for(var/datum/stock_part/matter_bin/bin in component_parts) - total_storage += bin.tier * (37.5*SHEET_MATERIAL_AMOUNT) - - materials.set_local_size(total_storage) - - var/total_rating = 1.2 - - for(var/datum/stock_part/servo/servo in component_parts) - total_rating -= servo.tier * 0.1 - - efficiency_coeff = max(total_rating, 0) - -/obj/machinery/rnd/production/on_deconstruction() - for(var/obj/item/reagent_containers/cup/G in component_parts) - reagents.trans_to(G, G.reagents.maximum_volume) + materials.eject_sheets(material, amount) + return TRUE - return ..() + if("build") + if(busy) + say("Warning: fabricator is busy!") + return + + //validate design + var/design_id = params["ref"] + if(!design_id) + return + var/datum/design/design = stored_research.researched_designs[design_id] ? SSresearch.techweb_design_by_id(design_id) : null + if(!istype(design)) + return FALSE + if(!(isnull(allowed_department_flags) || (design.departmental_flags & allowed_department_flags))) + say("This fabricator does not have the necessary keys to decrypt this design.") + return FALSE + if(design.build_type && !(design.build_type & allowed_buildtypes)) + say("This fabricator does not have the necessary manipulation systems for this design.") + return FALSE + + //validate print quantity + var/print_quantity = params["amount"] + if(isnull(print_quantity)) + return + print_quantity = text2num(print_quantity) + if(isnull(print_quantity)) + return + print_quantity = clamp(print_quantity, 1, 50) + + //efficiency for this design, stacks use exact materials + var/coefficient = build_efficiency(design.build_path) + + //check for materials + if(!materials.can_use_resource()) + return + if(!materials.mat_container.has_materials(design.materials, coefficient, print_quantity)) + say("Not enough materials to complete prototype[print_quantity > 1 ? "s" : ""].") + return FALSE + + //compute power & time to print 1 item + var/charge_per_item = 0 + for(var/material in design.materials) + charge_per_item += design.materials[material] + charge_per_item = ROUND_UP((charge_per_item / (MAX_STACK_SIZE * SHEET_MATERIAL_AMOUNT)) * coefficient * 0.05 * active_power_usage) + var/build_time_per_item = (design.construction_time * design.lathe_time_factor) ** 0.8 + + //start production + busy = TRUE + SStgui.update_uis(src) + if(production_animation) + icon_state = production_animation + var/turf/target_location + if(drop_direction) + target_location = get_step(src, drop_direction) + if(isclosedturf(target_location)) + target_location = get_turf(src) + else + target_location = get_turf(src) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, print_quantity, build_time_per_item, coefficient, charge_per_item, target_location), build_time_per_item) + + return TRUE + +/** + * Callback for start_making, actually makes the item + * Arguments + * + * * datum/design/design - the design we are trying to print + * * items_remaining - the number of designs left out to print + * * build_time_per_item - the time taken to print 1 item + * * material_cost_coefficient - the cost efficiency to print 1 design + * * charge_per_item - the amount of power to print 1 item + * * turf/target - the location to drop the printed item on +*/ +/obj/machinery/rnd/production/proc/do_make_item(datum/design/design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, turf/target) + PROTECTED_PROC(TRUE) + + if(!items_remaining) // how + finalize_build() + return -/obj/machinery/rnd/production/proc/do_print(path, amount) - for(var/i in 1 to amount) - new path(get_turf(src)) + if(!is_operational || !directly_use_power(charge_per_item)) + say("Unable to continue production, power failure.") + finalize_build() + return + if(!materials.can_use_resource()) + finalize_build() + return - SSblackbox.record_feedback("nested tally", "item_printed", amount, list("[type]", "[path]")) + var/is_stack = ispath(design.build_path, /obj/item/stack) + var/list/design_materials = design.materials + if(!materials.mat_container.has_materials(design_materials, material_cost_coefficient, is_stack ? items_remaining : 1)) + say("Unable to continue production, missing materials.") + return + materials.use_materials(design_materials, material_cost_coefficient, is_stack ? items_remaining : 1, "built", "[design.name]") -/obj/machinery/rnd/production/proc/build_efficiency(path) - if(ispath(path, /obj/item/stack/sheet) || ispath(path, /obj/item/stack/ore/bluespace_crystal)) - return 1 + var/atom/movable/created + if(is_stack) + created = new design.build_path(target, items_remaining) else - return efficiency_coeff - -/obj/machinery/rnd/production/proc/user_try_print_id(design_id, print_quantity) - if(!design_id) - return FALSE - - if(istext(print_quantity)) - print_quantity = text2num(print_quantity) - - if(isnull(print_quantity)) - print_quantity = 1 - - var/datum/design/design = stored_research.researched_designs[design_id] ? SSresearch.techweb_design_by_id(design_id) : null + created = new design.build_path(target) + split_materials_uniformly(design_materials, material_cost_coefficient, created) - if(!istype(design)) - return FALSE + created.pixel_x = created.base_pixel_x + rand(-6, 6) + created.pixel_y = created.base_pixel_y + rand(-6, 6) - if(busy) - say("Warning: fabricator is busy!") - return FALSE - - if(!(isnull(allowed_department_flags) || (design.departmental_flags & allowed_department_flags))) - say("This fabricator does not have the necessary keys to decrypt this design.") - return FALSE - - if(design.build_type && !(design.build_type & allowed_buildtypes)) - say("This fabricator does not have the necessary manipulation systems for this design.") - return FALSE - - if(!materials.mat_container) - say("No connection to material storage, please contact the quartermaster.") - return FALSE - - if(materials.on_hold()) - say("Mineral access is on hold, please contact the quartermaster.") - return FALSE - - print_quantity = clamp(print_quantity, 1, 50) - var/coefficient = build_efficiency(design.build_path) - - //check if sufficient materials/reagents are available - if(!materials.mat_container.has_materials(design.materials, coefficient, print_quantity)) - say("Not enough materials to complete prototype[print_quantity > 1? "s" : ""].") - return FALSE - for(var/reagent in design.reagents_list) - if(!reagents.has_reagent(reagent, design.reagents_list[reagent] * print_quantity * coefficient)) - say("Not enough reagents to complete prototype[print_quantity > 1? "s" : ""].") - return FALSE - - //use power - var/power = active_power_usage - for(var/material in design.materials) - power += round(design.materials[material] * print_quantity / 35) - power = min(active_power_usage, power) - use_power(power) - - // Charge the lathe tax at least once per ten items. - var/total_cost = LATHE_TAX * max(round(print_quantity / 10), 1) - if(!charges_tax) - total_cost = 0 - if(isliving(usr)) - var/mob/living/user = usr - var/obj/item/card/id/card = user.get_idcard(TRUE) - - if(!card && istype(user.pulling, /obj/item/card/id)) - card = user.pulling - - if(card && card.registered_account) - var/datum/bank_account/our_acc = card.registered_account - if(our_acc.account_job.departments_bitflags & allowed_department_flags) - total_cost = 0 // We are not charging crew for printing their own supplies and equipment. - if(attempt_charge(src, usr, total_cost) & COMPONENT_OBJ_CANCEL_CHARGE) - say("Insufficient funds to complete prototype. Please present a holochip or valid ID card.") - return FALSE - if(iscyborg(usr)) - var/mob/living/silicon/robot/borg = usr - if(!borg.cell) - return FALSE - borg.cell.use(SILICON_LATHE_TAX) - - //consume materials - materials.use_materials(design.materials, coefficient, print_quantity, "built", "[design.name]") - for(var/reagent in design.reagents_list) - reagents.remove_reagent(reagent, design.reagents_list[reagent] * print_quantity * coefficient) - //produce item - busy = TRUE - if(production_animation) - flick(production_animation, src) - var/time_coefficient = design.lathe_time_factor * efficiency_coeff - addtimer(CALLBACK(src, PROC_REF(reset_busy)), (30 * time_coefficient * print_quantity) ** 0.5) - addtimer(CALLBACK(src, PROC_REF(do_print), design.build_path, print_quantity), (32 * time_coefficient * print_quantity) ** 0.8) - update_static_data_for_all_viewers() + if(is_stack) + items_remaining = 0 + else + items_remaining -= 1 - return TRUE + if(!items_remaining) + finalize_build() + return + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, target), build_time_per_item) -// Stuff for the stripe on the department machines -/obj/machinery/rnd/production/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver) - . = ..() +/// Resets the busy flag +/// Called at the end of do_make_item's timer loop +/obj/machinery/rnd/production/proc/finalize_build() + PROTECTED_PROC(TRUE) - update_icon(UPDATE_OVERLAYS) + busy = FALSE + SStgui.update_uis(src) + icon_state = initial(icon_state) -/obj/machinery/rnd/production/update_overlays() +/obj/machinery/rnd/production/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) . = ..() - - if(!stripe_color) + if((!issilicon(usr) && !isAdminGhostAI(usr)) && !Adjacent(usr)) return + if(busy) + balloon_alert(usr, "busy printing!") + return + var/direction = get_dir(src, over_location) + if(!direction) + return + drop_direction = direction + balloon_alert(usr, "dropping [dir2text(drop_direction)]") - var/mutable_appearance/stripe = mutable_appearance('icons/obj/machines/research.dmi', "protolate_stripe") - - if(!panel_open) - stripe.icon_state = "protolathe_stripe" - else - stripe.icon_state = "protolathe_stripe_t" - - stripe.color = stripe_color - - . += stripe - -/obj/machinery/rnd/production/examine(mob/user) +/obj/machinery/rnd/production/AltClick(mob/user) . = ..() - - if(in_range(user, src) || isobserver(user)) - . += span_notice("The status display reads: Storing up to [materials.local_size] material units.
Material consumption at [efficiency_coeff * 100]%.
Build time reduced by [100 - efficiency_coeff * 100]%.") + if(!drop_direction || !can_interact(user)) + return + if(busy) + balloon_alert(user, "busy printing!") + return + balloon_alert(user, "drop direction reset") + drop_direction = 0 diff --git a/code/modules/research/machinery/circuit_imprinter.dm b/code/modules/research/machinery/circuit_imprinter.dm index 8127bc45d0ab0..2dcbde23663ad 100644 --- a/code/modules/research/machinery/circuit_imprinter.dm +++ b/code/modules/research/machinery/circuit_imprinter.dm @@ -2,23 +2,22 @@ name = "circuit imprinter" desc = "Manufactures circuit boards for the construction of machines." icon_state = "circuit_imprinter" - circuit = /obj/item/circuitboard/machine/circuit_imprinter production_animation = "circuit_imprinter_ani" + circuit = /obj/item/circuitboard/machine/circuit_imprinter allowed_buildtypes = IMPRINTER -/obj/machinery/rnd/production/circuit_imprinter/calculate_efficiency() - . = ..() - +/obj/machinery/rnd/production/circuit_imprinter/compute_efficiency() var/rating = 0 - for(var/datum/stock_part/servo/servo in component_parts) - rating += servo.tier // There is only one. + rating += servo.tier + + return 0.5 ** max(rating - 1, 0) // One sheet, half sheet, quarter sheet, eighth sheet. - efficiency_coeff = 0.5 ** max(rating - 1, 0) // One sheet, half sheet, quarter sheet, eighth sheet. +/obj/machinery/rnd/production/circuit_imprinter/flick_animation(mat_name) + return //we presently have no animation /obj/machinery/rnd/production/circuit_imprinter/offstation name = "ancient circuit imprinter" desc = "Manufactures circuit boards for the construction of machines. Its ancient construction may limit its ability to print all known technology." allowed_buildtypes = AWAY_IMPRINTER circuit = /obj/item/circuitboard/machine/circuit_imprinter/offstation - charges_tax = FALSE diff --git a/code/modules/research/machinery/departmental_protolathe.dm b/code/modules/research/machinery/departmental_protolathe.dm index dc0b882ef5176..81e83f8451e59 100644 --- a/code/modules/research/machinery/departmental_protolathe.dm +++ b/code/modules/research/machinery/departmental_protolathe.dm @@ -11,10 +11,6 @@ stripe_color = "#EFB341" payment_department = ACCOUNT_ENG -/obj/machinery/rnd/production/protolathe/department/engineering/no_tax - circuit = /obj/item/circuitboard/machine/protolathe/department/engineering/no_tax - charges_tax = FALSE - /obj/machinery/rnd/production/protolathe/department/service name = "department protolathe (Service)" allowed_department_flags = DEPARTMENT_BITFLAG_SERVICE diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm index b48f64ca9d74a..7a81a5cd36f19 100644 --- a/code/modules/research/machinery/protolathe.dm +++ b/code/modules/research/machinery/protolathe.dm @@ -6,7 +6,7 @@ production_animation = "protolathe_n" allowed_buildtypes = PROTOLATHE -/obj/machinery/rnd/production/protolathe/deconstruct(disassembled) +/obj/machinery/rnd/production/protolathe/on_deconstruction(disassembled) log_game("Protolathe of type [type] [disassembled ? "disassembled" : "deconstructed"] by [key_name(usr)] at [get_area_name(src, TRUE)]") return ..() @@ -23,4 +23,3 @@ desc = "Converts raw materials into useful objects. Its ancient construction may limit its ability to print all known technology." circuit = /obj/item/circuitboard/machine/protolathe/offstation allowed_buildtypes = AWAY_LATHE - charges_tax = FALSE diff --git a/code/modules/research/machinery/techfab.dm b/code/modules/research/machinery/techfab.dm index 38df3fc038df1..7f1428882e7d3 100644 --- a/code/modules/research/machinery/techfab.dm +++ b/code/modules/research/machinery/techfab.dm @@ -3,6 +3,5 @@ desc = "Produces researched prototypes with raw materials and energy." icon_state = "protolathe" circuit = /obj/item/circuitboard/machine/techfab - console_link = FALSE production_animation = "protolathe_n" allowed_buildtypes = PROTOLATHE | IMPRINTER diff --git a/code/modules/research/ordnance/_scipaper.dm b/code/modules/research/ordnance/_scipaper.dm index 38f35e75a7718..f1d94af76316a 100644 --- a/code/modules/research/ordnance/_scipaper.dm +++ b/code/modules/research/ordnance/_scipaper.dm @@ -287,21 +287,20 @@ /// List of ordnance experiments that our partner is willing to accept. If this list is not filled it means the partner will accept everything. var/list/accepted_experiments = list() /// Associative list of which technology the partner might be able to boost and by how much. - var/list/boosted_nodes = list() - + var/list/boostable_nodes = list() /datum/scientific_partner/proc/purchase_boost(datum/techweb/purchasing_techweb, datum/techweb_node/node) if(!allowed_to_boost(purchasing_techweb, node.id)) return FALSE - purchasing_techweb.boost_techweb_node(node, list(TECHWEB_POINT_TYPE_GENERIC=boosted_nodes[node.id])) - purchasing_techweb.scientific_cooperation[type] -= boosted_nodes[node.id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER + purchasing_techweb.boost_techweb_node(node, list(TECHWEB_POINT_TYPE_GENERIC = boostable_nodes[node.id])) + purchasing_techweb.scientific_cooperation[type] -= boostable_nodes[node.id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER return TRUE /datum/scientific_partner/proc/allowed_to_boost(datum/techweb/purchasing_techweb, node_id) - if(purchasing_techweb.scientific_cooperation[type] < (boosted_nodes[node_id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER)) // Too expensive + if(purchasing_techweb.scientific_cooperation[type] < (boostable_nodes[node_id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER)) // Too expensive return FALSE if(!(node_id in purchasing_techweb.get_available_nodes())) // Not currently available return FALSE - if((TECHWEB_POINT_TYPE_GENERIC in purchasing_techweb.boosted_nodes[node_id]) && (purchasing_techweb.boosted_nodes[node_id][TECHWEB_POINT_TYPE_GENERIC] >= boosted_nodes[node_id])) // Already bought or we have a bigger discount + if((TECHWEB_POINT_TYPE_GENERIC in purchasing_techweb.boosted_nodes[node_id]) && (purchasing_techweb.boosted_nodes[node_id][TECHWEB_POINT_TYPE_GENERIC] >= boostable_nodes[node_id])) // Already bought or we have a bigger discount return FALSE return TRUE diff --git a/code/modules/research/ordnance/doppler_array.dm b/code/modules/research/ordnance/doppler_array.dm index aff4503a562b1..a6fe3c9f3ac0e 100644 --- a/code/modules/research/ordnance/doppler_array.dm +++ b/code/modules/research/ordnance/doppler_array.dm @@ -18,7 +18,7 @@ var/obj/item/computer_disk/inserted_disk // Lighting system to better communicate the directions. - light_system = MOVABLE_LIGHT_DIRECTIONAL + light_system = OVERLAY_LIGHT_DIRECTIONAL light_range = 4 light_power = 1 light_color = COLOR_RED @@ -62,7 +62,7 @@ /obj/machinery/doppler_array/wrench_act(mob/living/user, obj/item/tool) default_unfasten_wrench(user, tool) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/doppler_array/screwdriver_act(mob/living/user, obj/item/tool) if(!default_deconstruction_screwdriver(user, "[base_icon_state]", "[base_icon_state]", tool)) @@ -237,7 +237,7 @@ else if (machine_stat & NOPOWER) . += mutable_appearance(icon, "[base_icon_state]_screen-off") -/obj/machinery/doppler_array/on_deconstruction() +/obj/machinery/doppler_array/on_deconstruction(disassembled) eject_disk() . = ..() diff --git a/code/modules/research/ordnance/scipaper_partner.dm b/code/modules/research/ordnance/scipaper_partner.dm index fe302c73bb06c..712ec4b4127e9 100644 --- a/code/modules/research/ordnance/scipaper_partner.dm +++ b/code/modules/research/ordnance/scipaper_partner.dm @@ -3,7 +3,7 @@ flufftext = "A local group of miners are looking for ways to improve their mining output. They are interested in smaller scale explosives." accepted_experiments = list(/datum/experiment/ordnance/explosive/lowyieldbomb) multipliers = list(SCIPAPER_COOPERATION_INDEX = 0.75, SCIPAPER_FUNDING_INDEX = 0.75) - boosted_nodes = list( + boostable_nodes = list( "bluespace_basic" = 2000, "NVGtech" = 1500, "practical_bluespace" = 2500, @@ -16,7 +16,7 @@ name = "Ghost Writing" flufftext = "A nearby research station ran by a very wealthy captain seems to be struggling with their scientific output. They might reward us handsomely if we ghostwrite for them." multipliers = list(SCIPAPER_COOPERATION_INDEX = 0.25, SCIPAPER_FUNDING_INDEX = 2) - boosted_nodes = list( + boostable_nodes = list( "comp_recordkeeping" = 500, "computer_data_disks" = 500, ) @@ -29,7 +29,7 @@ /datum/experiment/ordnance/explosive/pressurebomb, /datum/experiment/ordnance/explosive/hydrogenbomb, ) - boosted_nodes = list( + boostable_nodes = list( "adv_weaponry" = 5000, "weaponry" = 2500, "sec_basic" = 1250, @@ -47,7 +47,7 @@ /datum/experiment/ordnance/gaseous/nitrous_oxide, /datum/experiment/ordnance/gaseous/bz, ) - boosted_nodes = list( + boostable_nodes = list( "cyber_organs" = 750, "cyber_organs_upgraded" = 1000, "genetics" = 500, @@ -63,7 +63,7 @@ /datum/experiment/ordnance/gaseous/noblium, /datum/experiment/ordnance/explosive/nobliumbomb, ) - boosted_nodes = list( + boostable_nodes = list( "engineering" = 5000, "adv_engi" = 5000, "emp_super" = 3000, diff --git a/code/modules/research/ordnance/tank_compressor.dm b/code/modules/research/ordnance/tank_compressor.dm index 85a2cf44836af..830c004acad5e 100644 --- a/code/modules/research/ordnance/tank_compressor.dm +++ b/code/modules/research/ordnance/tank_compressor.dm @@ -85,9 +85,6 @@ update_appearance() return TRUE -/obj/machinery/atmospherics/components/binary/circulator/get_node_connects() - return list(REVERSE_DIR(dir), dir) // airs[2] is input which is facing dir, airs[1] is output which is facing the other side of dir - /obj/machinery/atmospherics/components/binary/tank_compressor/screwdriver_act(mob/living/user, obj/item/tool) if(active || inserted_tank) return FALSE @@ -241,7 +238,7 @@ update_appearance() return ..() -/obj/machinery/atmospherics/components/binary/tank_compressor/on_deconstruction() +/obj/machinery/atmospherics/components/binary/tank_compressor/on_deconstruction(disassembled) eject_tank() eject_disk() return ..() diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index fdfcf9bf72da8..b2b468db120c9 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -236,7 +236,8 @@ Nothing else in the console has ID requirements. /obj/machinery/computer/rdconsole/ui_static_data(mob/user) . = list( - "static_data" = list() + "static_data" = list(), + "point_types_abbreviations" = SSresearch.point_types, ) // Build node cache... diff --git a/code/modules/research/rdmachines.dm b/code/modules/research/rdmachines.dm index 3047c3e1a9ac1..58d7f1f29cf3d 100644 --- a/code/modules/research/rdmachines.dm +++ b/code/modules/research/rdmachines.dm @@ -7,20 +7,22 @@ icon = 'icons/obj/machines/research.dmi' density = TRUE use_power = IDLE_POWER_USE + + ///Are we currently printing a machine var/busy = FALSE + ///Is this machne hacked via wires var/hacked = FALSE - var/console_link = TRUE //allow console link. + ///Is this machine disabled via wires var/disabled = FALSE - var/obj/item/loaded_item = null //the item loaded inside the machine (currently only used by experimentor and destructive analyzer) - /// Ref to global science techweb. + ///Ref to global science techweb. var/datum/techweb/stored_research - -/obj/machinery/rnd/proc/reset_busy() - busy = FALSE + ///The item loaded inside the machine, used by experimentors and destructive analyzers only. + var/obj/item/loaded_item /obj/machinery/rnd/Initialize(mapload) . = ..() set_wires(new /datum/wires/rnd(src)) + register_context() /obj/machinery/rnd/LateInitialize() . = ..() @@ -36,6 +38,46 @@ QDEL_NULL(wires) return ..() +/obj/machinery/rnd/examine(mob/user) + . = ..() + if(!in_range(user, src) && !isobserver(user)) + return + + . += span_notice("A [EXAMINE_HINT("multitool")] with techweb designs can be uploaded here.") + . += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].") + if(panel_open) + . += span_notice("Use a [EXAMINE_HINT("multitool")] or [EXAMINE_HINT("wirecutters")] to interact with wires.") + . += span_notice("The machine can be [EXAMINE_HINT("pried")] apart.") + +/obj/machinery/rnd/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = NONE + if(isnull(held_item)) + return + + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel" + context[SCREENTIP_CONTEXT_RMB] = "[panel_open ? "Close" : "Open"] Panel" + return CONTEXTUAL_SCREENTIP_SET + + if(panel_open) + var/msg + if(held_item.tool_behaviour == TOOL_CROWBAR) + msg = "Deconstruct" + else if(is_wire_tool(held_item)) + msg = "Open Wires" + + if(msg) + context[SCREENTIP_CONTEXT_LMB] = msg + context[SCREENTIP_CONTEXT_RMB] = msg + return CONTEXTUAL_SCREENTIP_SET + else + if(held_item.tool_behaviour == TOOL_MULTITOOL) + var/obj/item/multitool/tool = held_item + if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) + context[SCREENTIP_CONTEXT_LMB] = "Upload Techweb" + context[SCREENTIP_CONTEXT_RMB] = "Upload Techweb" + return CONTEXTUAL_SCREENTIP_SET + ///Called when attempting to connect the machine to a techweb, forgetting the old. /obj/machinery/rnd/proc/connect_techweb(datum/techweb/new_techweb) if(stored_research) @@ -48,101 +90,67 @@ /obj/machinery/rnd/proc/on_connected_techweb() SHOULD_CALL_PARENT(FALSE) -/obj/machinery/rnd/proc/shock(mob/user, prb) - if(machine_stat & (BROKEN|NOPOWER)) // unpowered, no shock - return FALSE - if(!prob(prb)) - return FALSE - do_sparks(5, TRUE, src) - if (electrocute_mob(user, get_area(src), src, 0.7, TRUE)) - return TRUE - else - return FALSE - -/obj/machinery/rnd/attackby(obj/item/O, mob/user, params) - if(is_refillable() && O.is_drainable()) - return FALSE //inserting reagents into the machine - if(Insert_Item(O, user)) - return TRUE - - return ..() +///Reset the state of this machine +/obj/machinery/rnd/proc/reset_busy() + busy = FALSE /obj/machinery/rnd/crowbar_act(mob/living/user, obj/item/tool) return default_deconstruction_crowbar(tool) /obj/machinery/rnd/crowbar_act_secondary(mob/living/user, obj/item/tool) - return default_deconstruction_crowbar(tool) + return crowbar_act(user, tool) /obj/machinery/rnd/screwdriver_act(mob/living/user, obj/item/tool) return default_deconstruction_screwdriver(user, "[initial(icon_state)]_t", initial(icon_state), tool) /obj/machinery/rnd/screwdriver_act_secondary(mob/living/user, obj/item/tool) - return default_deconstruction_screwdriver(user, "[initial(icon_state)]_t", initial(icon_state), tool) + return screwdriver_act(user, tool) /obj/machinery/rnd/multitool_act(mob/living/user, obj/item/multitool/tool) + . = ITEM_INTERACT_BLOCKING if(panel_open) wires.interact(user) - return TRUE + return ITEM_INTERACT_SUCCESS if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) connect_techweb(tool.buffer) - return TRUE - return FALSE + return ITEM_INTERACT_SUCCESS /obj/machinery/rnd/multitool_act_secondary(mob/living/user, obj/item/tool) - if(panel_open) - wires.interact(user) - return TRUE + return multitool_act(user, tool) /obj/machinery/rnd/wirecutter_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING if(panel_open) wires.interact(user) - return TRUE + return ITEM_INTERACT_SUCCESS /obj/machinery/rnd/wirecutter_act_secondary(mob/living/user, obj/item/tool) - if(panel_open) - wires.interact(user) - return TRUE - -//proc used to handle inserting items or reagents into rnd machines -/obj/machinery/rnd/proc/Insert_Item(obj/item/I, mob/user) - return + return wirecutter_act(user, tool) //whether the machine can have an item inserted in its current state. /obj/machinery/rnd/proc/is_insertion_ready(mob/user) if(panel_open) - to_chat(user, span_warning("You can't load [src] while it's opened!")) + balloon_alert(user, "panel open!") return FALSE if(disabled) - to_chat(user, span_warning("The insertion belts of [src] won't engage!")) + balloon_alert(user, "belts disabled!") return FALSE if(busy) - to_chat(user, span_warning("[src] is busy right now.")) + balloon_alert(user, "still busy!") return FALSE if(machine_stat & BROKEN) - to_chat(user, span_warning("[src] is broken.")) + balloon_alert(user, "machine broken!") return FALSE if(machine_stat & NOPOWER) - to_chat(user, span_warning("[src] has no power.")) + balloon_alert(user, "no power!") return FALSE if(loaded_item) - to_chat(user, span_warning("[src] is already loaded.")) + balloon_alert(user, "item already loaded!") return FALSE return TRUE //we eject the loaded item when deconstructing the machine -/obj/machinery/rnd/on_deconstruction() +/obj/machinery/rnd/on_deconstruction(disassembled) if(loaded_item) - loaded_item.forceMove(loc) + loaded_item.forceMove(drop_location()) ..() - -/obj/machinery/rnd/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) - var/stack_name - if(istype(item_inserted, /obj/item/stack/ore/bluespace_crystal)) - stack_name = "bluespace" - use_power(SHEET_MATERIAL_AMOUNT / 10) - else - var/obj/item/stack/S = item_inserted - stack_name = S.name - use_power(min(active_power_usage, (amount_inserted / 100))) - add_overlay("protolathe_[stack_name]") - addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, cut_overlay), "protolathe_[stack_name]"), 10) diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm index 45a0a520fa0d4..e446672bc33a9 100644 --- a/code/modules/research/server.dm +++ b/code/modules/research/server.dm @@ -154,16 +154,17 @@ if(HDD_OVERLOADED) . += "The front panel is dangling open. The hdd inside is destroyed and the wires are all burned." -/obj/machinery/rnd/server/master/tool_act(mob/living/user, obj/item/tool, tool_type) +/obj/machinery/rnd/server/master/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) + if(!tool.tool_behaviour) + return ..() // Only antags are given the training and knowledge to disassemble this thing. if(is_special_character(user)) return ..() - if(user.combat_mode) - return FALSE + return NONE balloon_alert(user, "you can't find an obvious maintenance hatch!") - return TRUE + return ITEM_INTERACT_BLOCKING /obj/machinery/rnd/server/master/attackby(obj/item/attacking_item, mob/user, params) if(istype(attacking_item, /obj/item/computer_disk/hdd_theft)) @@ -229,7 +230,7 @@ to_chat(user, span_notice("You delicately cut the wire. [hdd_wires] wire\s left...")) return TRUE -/obj/machinery/rnd/server/master/on_deconstruction() +/obj/machinery/rnd/server/master/on_deconstruction(disassembled) // If the machine contains a source code HDD, destroying it will negatively impact research speed. Safest to log this. if(source_code_hdd) // Destroyed with a hard drive inside = harm income diff --git a/code/modules/research/stock_parts.dm b/code/modules/research/stock_parts.dm index ee9a88b629d59..fee78cf41db32 100644 --- a/code/modules/research/stock_parts.dm +++ b/code/modules/research/stock_parts.dm @@ -197,12 +197,18 @@ If you create T5+ please take a pass at mech_fabricator.dm. The parts being good /obj/item/storage/part_replacer/cyborg name = "rapid part exchange device" - desc = "Special mechanical module made to store, sort, and apply standard machine parts." + desc = "Special mechanical module made to store, sort, and apply standard machine parts. This one has an extra large compartment for more parts." icon_state = "borgrped" inhand_icon_state = "RPED" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' +/obj/item/storage/part_replacer/cyborg/Initialize(mapload) + . = ..() + atom_storage.max_slots = 400 + atom_storage.max_total_storage = 800 + atom_storage.max_specific_storage = WEIGHT_CLASS_GIGANTIC + /obj/item/storage/part_replacer/proc/get_sorted_parts(ignore_stacks = FALSE) var/list/part_list = list() //Assemble a list of current parts, then sort them by their rating! @@ -224,7 +230,7 @@ If you create T5+ please take a pass at mech_fabricator.dm. The parts being good /obj/item/stock_parts name = "stock part" desc = "What?" - icon = 'icons/obj/assemblies/stock_parts.dmi' + icon = 'icons/obj/devices/stock_parts.dmi' w_class = WEIGHT_CLASS_SMALL var/rating = 1 ///Used when a base part has a different name to higher tiers of part. For example, machine frames want any servo and not just a micro-servo. @@ -465,6 +471,6 @@ If you create T5+ please take a pass at mech_fabricator.dm. The parts being good /obj/item/research//Makes testing much less of a pain -Sieve name = "research" - icon = 'icons/obj/assemblies/stock_parts.dmi' + icon = 'icons/obj/devices/stock_parts.dmi' icon_state = "capacitor" desc = "A debug item for research." diff --git a/code/modules/research/techweb/__techweb_helpers.dm b/code/modules/research/techweb/__techweb_helpers.dm index 2b0a294c606f2..d1d3b6ecfded9 100644 --- a/code/modules/research/techweb/__techweb_helpers.dm +++ b/code/modules/research/techweb/__techweb_helpers.dm @@ -10,19 +10,20 @@ WARNING("Invalid boost information for node \[[id]\]: [message]") SSresearch.invalid_node_boost[id] = message -///Returns an associative list of techweb node datums with values of the boost it gives. var/list/returned = list() -/proc/techweb_item_boost_check(obj/item/I) - if(SSresearch.techweb_boost_items[I.type]) - return SSresearch.techweb_boost_items[I.type] //It should already be formatted in node datum = list(point type = value) +///Returns an associative list of techweb node datums with values of the nodes it unlocks. +/proc/techweb_item_unlock_check(obj/item/I) + if(SSresearch.techweb_unlock_items[I.type]) + return SSresearch.techweb_unlock_items[I.type] //It should already be formatted in node datum = list(point type = value) /proc/techweb_item_point_check(obj/item/I) if(SSresearch.techweb_point_items[I.type]) return SSresearch.techweb_point_items[I.type] + return FALSE /proc/techweb_point_display_generic(pointlist) var/list/ret = list() for(var/i in pointlist) - if(SSresearch.point_types[i]) + if(i in SSresearch.point_types) ret += "[SSresearch.point_types[i]]: [pointlist[i]]" else ret += "ERRORED POINT TYPE: [pointlist[i]]" @@ -31,7 +32,7 @@ /proc/techweb_point_display_rdconsole(pointlist, last_pointlist) var/list/ret = list() for(var/i in pointlist) - var/research_line = "[SSresearch.point_types[i] || "ERRORED POINT TYPE"]: [pointlist[i]]" + var/research_line = "[(i in SSresearch.point_types) || "ERRORED POINT TYPE"]: [pointlist[i]]" if(last_pointlist[i] > 0) research_line += " (+[(last_pointlist[i]) * ((SSresearch.flags & SS_TICKER)? (600 / (world.tick_lag * SSresearch.wait)) : (600 / SSresearch.wait))]/ minute)" ret += research_line diff --git a/code/modules/research/techweb/_techweb.dm b/code/modules/research/techweb/_techweb.dm index 521504da351d0..b4b137d8e2187 100644 --- a/code/modules/research/techweb/_techweb.dm +++ b/code/modules/research/techweb/_techweb.dm @@ -26,7 +26,7 @@ var/list/boosted_nodes = list() /// Hidden nodes. id = TRUE. Used for unhiding nodes when requirements are met by removing the entry of the node. var/list/hidden_nodes = list() - /// Items already deconstructed for a generic point boost, path = list(point_type = points) + /// List of items already deconstructed for research points, preventing infinite research point generation. var/list/deconstructed_items = list() /// Available research points, type = number var/list/research_points = list() @@ -47,11 +47,6 @@ /// Completing these experiments will have a refund. var/list/datum/experiment/skipped_experiment_types = list() - /// If science researches something without completing its discount experiments, - /// they have the option to complete them later for a refund - /// This ratio determines how much of the original discount is refunded - var/skipped_experiment_refund_ratio = 0.66 - ///All RD consoles connected to this individual techweb. var/list/obj/machinery/computer/rdconsole/consoles_accessing = list() ///All research servers connected to this individual techweb. @@ -112,7 +107,7 @@ /datum/techweb/proc/add_point_list(list/pointlist) for(var/i in pointlist) - if(SSresearch.point_types[i] && pointlist[i] > 0) + if((i in SSresearch.point_types) && pointlist[i] > 0) research_points[i] += pointlist[i] /datum/techweb/proc/add_points_all(amount) @@ -123,7 +118,7 @@ /datum/techweb/proc/remove_point_list(list/pointlist) for(var/i in pointlist) - if(SSresearch.point_types[i] && pointlist[i] > 0) + if((i in SSresearch.point_types) && pointlist[i] > 0) research_points[i] = max(0, research_points[i] - pointlist[i]) /datum/techweb/proc/remove_points_all(amount) @@ -134,7 +129,7 @@ /datum/techweb/proc/modify_point_list(list/pointlist) for(var/i in pointlist) - if(SSresearch.point_types[i] && pointlist[i] != 0) + if((i in SSresearch.point_types) && pointlist[i] != 0) research_points[i] = max(0, research_points[i] + pointlist[i]) /datum/techweb/proc/modify_points_all(amount) @@ -175,19 +170,19 @@ return researched_nodes - hidden_nodes /datum/techweb/proc/add_point_type(type, amount) - if(!SSresearch.point_types[type] || (amount <= 0)) + if(!(type in SSresearch.point_types) || (amount <= 0)) return FALSE research_points[type] += amount return TRUE /datum/techweb/proc/modify_point_type(type, amount) - if(!SSresearch.point_types[type]) + if(!(type in SSresearch.point_types)) return FALSE research_points[type] = max(0, research_points[type] + amount) return TRUE /datum/techweb/proc/remove_point_type(type, amount) - if(!SSresearch.point_types[type] || (amount <= 0)) + if(!(type in SSresearch.point_types) || (amount <= 0)) return FALSE research_points[type] = max(0, research_points[type] - amount) return TRUE @@ -283,7 +278,7 @@ var/datum/experiment/experiment = completed_experiment if (experiment == experiment_type) return FALSE - available_experiments += new experiment_type() + available_experiments += new experiment_type(src) /** * Adds a list of experiments to this techweb by their types, ensures that no duplicates are added. @@ -310,13 +305,21 @@ var/refund = skipped_experiment_types[completed_experiment.type] || 0 if(refund > 0) add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = refund)) - result_text += ", refunding [refund] points." + result_text += ", refunding [refund] points" // Nothing more to gain here, but we keep it in the list to prevent double dipping skipped_experiment_types[completed_experiment.type] = -1 - else - result_text += "!" - - log_research("[completed_experiment.name] ([completed_experiment.type]) has been completed on techweb [id]/[organization][refund ? ", refunding [refund] points" : ""].") + var/points_rewarded + if(completed_experiment.points_reward) + add_point_list(completed_experiment.points_reward) + points_rewarded = ",[refund > 0 ? " and" : ""] rewarding " + var/list/english_list_keys = list() + for(var/points_type in completed_experiment.points_reward) + english_list_keys += "[completed_experiment.points_reward[points_type]] [points_type]" + points_rewarded += "[english_list(english_list_keys)] points" + result_text += points_rewarded + result_text += "!" + + log_research("[completed_experiment.name] ([completed_experiment.type]) has been completed on techweb [id]/[organization][refund ? ", refunding [refund] points" : ""][points_rewarded].") return result_text /datum/techweb/proc/printout_points() @@ -345,7 +348,7 @@ for(var/missed_experiment in node.discount_experiments) if(completed_experiments[missed_experiment] || skipped_experiment_types[missed_experiment]) continue - skipped_experiment_types[missed_experiment] = node.discount_experiments[missed_experiment] * skipped_experiment_refund_ratio + skipped_experiment_types[missed_experiment] = node.discount_experiments[missed_experiment] // Gain the experiments from the new node for(var/id in node.unlock_ids) @@ -357,6 +360,10 @@ add_experiments(unlocked_node.discount_experiments) update_node_status(unlocked_node) + // Gain more new experiments + if (node.experiments_to_unlock.len) + add_experiments(node.experiments_to_unlock) + // Unlock what the research actually unlocks for(var/id in node.design_ids) add_design_by_id(id) @@ -388,17 +395,17 @@ LAZYINITLIST(boosted_nodes[node.id]) for(var/point_type in pointlist) boosted_nodes[node.id][point_type] = max(boosted_nodes[node.id][point_type], pointlist[point_type]) - if(node.autounlock_by_boost) - hidden_nodes -= node.id + unhide_node(node) update_node_status(node) return TRUE -/// Boosts a techweb node by using items. -/datum/techweb/proc/boost_with_item(datum/techweb_node/node, itempath) - if(!istype(node) || !ispath(itempath)) +///Removes a node from the hidden_nodes list, making it viewable and researchable (if no experiments are required). +/datum/techweb/proc/unhide_node(datum/techweb_node/node) + if(!istype(node)) return FALSE - var/list/boost_amount = node.boost_item_paths[itempath] - boost_techweb_node(node, boost_amount) + hidden_nodes -= node.id + ///Make it available if the prereq ids are already researched + update_node_status(node) return TRUE /datum/techweb/proc/update_tiers(datum/techweb_node/base) diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm index 2f01252548a21..c36eb88627137 100644 --- a/code/modules/research/techweb/_techweb_node.dm +++ b/code/modules/research/techweb/_techweb_node.dm @@ -16,7 +16,7 @@ var/description = "Why are you seeing this?" /// Whether it starts off hidden var/hidden = FALSE - /// If the tech can be randomly generated by the BEPIS as a reward. MEant to be fully given in tech disks, not researched + /// If the tech can be randomly generated by BEPIS tech as a reward. Meant to be fully given in tech disks, not researched var/experimental = FALSE /// Whether it's available without any research var/starting_node = FALSE @@ -24,8 +24,8 @@ var/list/design_ids = list() /// CALCULATED FROM OTHER NODE'S PREREQUISITIES. Associated list id = TRUE var/list/unlock_ids = list() - /// Associative list, path = list(point type = point_value) - var/list/boost_item_paths = list() + /// List of items you need to deconstruct to unlock this node. + var/list/required_items_to_unlock = list() /// Boosting this will autounlock this node var/autounlock_by_boost = TRUE /// The points cost to research the node, type = amount @@ -36,6 +36,8 @@ var/list/required_experiments = list() /// If completed, these experiments give a specific point amount discount to the node.area var/list/discount_experiments = list() + /// When this node is completed, allows these experiments to be performed. + var/list/experiments_to_unlock = list() /// Whether or not this node should show on the wiki var/show_on_wiki = TRUE diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 2ec692f057561..32d834a19baef 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -14,7 +14,6 @@ "basic_matter_bin", "basic_micro_laser", "basic_scanning", - "bepis", "blast", "bounced_radio", "bowl", @@ -49,7 +48,9 @@ "experimentor", "extinguisher", "fax", + "fish_case", "fishing_rod", + "fishing_portal_generator", "flashlight", "fluid_ducts", "foam_dart", @@ -97,6 +98,7 @@ "sec_dart", "sec_Islug", "sec_rshot", + "sec_pen", "servingtray", "shaker", "shot_glass", @@ -115,6 +117,7 @@ "titaniumglass", "toner_large", "toner", + "tongs", "toy_armblade", "toy_balloon", "toygun", @@ -127,6 +130,14 @@ "voice_analyzer", "watering_can", ) + experiments_to_unlock = list( + /datum/experiment/autopsy/nonhuman, + /datum/experiment/scanning/random/material/medium/one, + /datum/experiment/scanning/random/material/medium/three, + /datum/experiment/scanning/random/material/hard/one, + /datum/experiment/scanning/random/material/hard/two, + /datum/experiment/scanning/people/novel_organs, + ) /datum/techweb_node/mmi id = "mmi" @@ -288,6 +299,7 @@ "plumbing_rcd_service", "plumbing_rcd_sci", "portable_chem_mixer", + "penlight", "retractor", "scalpel", "stethoscope", @@ -307,6 +319,7 @@ design_ids = list( "circuit_multitool", "comp_access_checker", + "comp_arctan2", "comp_arithmetic", "comp_assoc_list_pick", "comp_assoc_list_remove", @@ -406,6 +419,7 @@ "medigel", "medipen_refiller", "pandemic", + "penlight_paramedic", "soda_dispenser", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) @@ -475,6 +489,7 @@ "gibber", "griddle", "microwave", + "microwave_engineering", "monkey_recycler", "oven", "processor", @@ -628,6 +643,7 @@ ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 12500) discount_experiments = list(/datum/experiment/scanning/random/material/easy = 7500) + experiments_to_unlock = list(/datum/experiment/scanning/points/machinery_pinpoint_scan/tier2_microlaser) /datum/techweb_node/adv_engi id = "adv_engi" @@ -837,7 +853,7 @@ design_ids = list( "assembly_shell", "bot_shell", - "comp_mod_action", + "comp_equip_action", "controller_shell", "dispenser_shell", "door_shell", @@ -859,7 +875,6 @@ "bci_implanter", "bci_shell", "comp_bar_overlay", - "comp_bci_action", "comp_counter_overlay", "comp_install_detector", "comp_object_overlay", @@ -908,9 +923,13 @@ /datum/techweb_node/adv_robotics id = "adv_robotics" display_name = "Advanced Robotics Research" - description = "Machines using actual neural networks to simulate human lives." - prereq_ids = list("neural_programming", "robotics") + description = "Advanced synthetic neural networks and synaptic pathways allows for extraordinary leaps in cybernetic intelligence and interfacing." + prereq_ids = list("robotics") design_ids = list( + "advanced_l_arm", + "advanced_r_arm", + "advanced_l_leg", + "advanced_r_leg", "mmi_posi", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) @@ -997,6 +1016,7 @@ "borg_upgrade_lavaproof", "borg_upgrade_rped", "borg_upgrade_hypermod", + "borg_upgrade_inducer", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000) @@ -1093,6 +1113,7 @@ "holosignrestaurant", "holosignbar", "inducer", + "inducerengi", "tray_goggles", "holopad", "vendatray", @@ -1159,10 +1180,11 @@ description = "Computers and how they work." prereq_ids = list("datatheory") design_ids = list( + "bankmachine", + "barcode_scanner", "cargo", "cargorequest", "comconsole", - "bankmachine", "crewconsole", "idcard", "libraryconsole", @@ -1241,6 +1263,19 @@ "s_treatment", ) +/datum/techweb_node/tram + id = "tram" + display_name = "Tram Technology" + description = "Technology for linear induction transportation systems." + prereq_ids = list("telecomms") + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1500) + design_ids = list( + "tram_controller", + "tram_display", + "crossing_signal", + "guideway_sensor", + ) + /datum/techweb_node/integrated_hud id = "integrated_HUDs" display_name = "Integrated HUDs" @@ -1305,12 +1340,24 @@ "c38_trac", "implant_chem", "implant_tracking", + "implant_exile", "implantcase", "implanter", "locator", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) +/datum/techweb_node/advanced_implants + id = "adv_subdermal_implants" + display_name = "Advanced Subdermal Implants" + description = "Subdermal implants that leverage bluespace research to control their bluespace signature." + prereq_ids = list("subdermal_implants", "micro_bluespace") + design_ids = list( + "implant_beacon", + "implant_bluespace", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + /datum/techweb_node/cyber_organs id = "cyber_organs" display_name = "Cybernetic Organs" @@ -1437,6 +1484,9 @@ "superresonator", "triggermod", "mining_scanner", + "brm", + "b_smelter", + "b_refinery", )//e a r l y g a m e) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) @@ -1491,6 +1541,19 @@ required_experiments = list(/datum/experiment/scanning/random/plants/wild) discount_experiments = list(/datum/experiment/scanning/random/plants/traits = 3000) +/datum/techweb_node/fishing + id = "fishing" + display_name = "Fishing Technology" + description = "Cutting edge fishing advancements." + prereq_ids = list("base") + design_ids = list( + "fishing_rod_tech", + "stabilized_hook", + "fish_analyzer", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000) + required_experiments = list(/datum/experiment/scanning/fish) + /datum/techweb_node/exp_tools id = "exp_tools" display_name = "Experimental Tools" @@ -1562,6 +1625,7 @@ design_ids = list( "pin_testing", "tele_shield", + "lasershell", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 20000) discount_experiments = list(/datum/experiment/ordnance/explosive/pressurebomb = 10000) @@ -1835,6 +1899,18 @@ ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) +/datum/techweb_node/paddy + id = "mech_paddy" + display_name = "EXOSUIT: APLU \"Paddy\"" + description = "Paddy exosuit designs" + prereq_ids = list("adv_mecha", "adv_mecha_armor") + design_ids = list( + "paddyupgrade", + "mech_hydraulic_claw" + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000) + discount_experiments = list(/datum/experiment/scanning/points/machinery_tiered_scan/tier3_mechbay = 5000) + /datum/techweb_node/gygax id = "mech_gygax" display_name = "EXOSUIT: Gygax" @@ -1951,7 +2027,7 @@ "mech_proj_armor", ) required_experiments = list(/datum/experiment/scanning/random/mecha_damage_scan) - discount_experiments = list(/datum/experiment/scanning/random/mecha_destroyed_scan = 5000) + discount_experiments = list(/datum/experiment/scanning/random/mecha_equipped_scan = 5000) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000) /datum/techweb_node/mech_scattershot @@ -2106,7 +2182,7 @@ display_name = "Alien Technology" description = "Things used by the greys." prereq_ids = list("biotech","engineering") - boost_item_paths = list( + required_items_to_unlock = list( /obj/item/stack/sheet/mineral/abductor, /obj/item/abductor, /obj/item/cautery/alien, @@ -2149,7 +2225,7 @@ "alien_scalpel", ) - boost_item_paths = list( + required_items_to_unlock = list( /obj/item/abductor, /obj/item/cautery/alien, /obj/item/circuitboard/machine/abductor, @@ -2188,7 +2264,7 @@ "alien_wrench", ) - boost_item_paths = list( + required_items_to_unlock = list( /obj/item/abductor, /obj/item/circuitboard/machine/abductor, /obj/item/crowbar/abductor, @@ -2213,7 +2289,6 @@ "advanced_camera", "ai_cam_upgrade", "borg_syndicate_module", - "decloner", "donksoft_refill", "donksofttoyvendor", "largecrossbow", @@ -2241,12 +2316,12 @@ /datum/techweb_node/syndicate_basic/proc/register_uplink_items() SIGNAL_HANDLER UnregisterSignal(SSearly_assets, COMSIG_SUBSYSTEM_POST_INITIALIZE) - boost_item_paths = list() + required_items_to_unlock = list() for(var/datum/uplink_item/item_path as anything in SStraitor.uplink_items_by_type) var/datum/uplink_item/item = SStraitor.uplink_items_by_type[item_path] if(!item.item || !item.illegal_tech) continue - boost_item_paths |= item.item //allows deconning to unlock. + required_items_to_unlock |= item.item //allows deconning to unlock. ////////////////////////B.E.P.I.S. Locked Techs//////////////////////// @@ -2354,17 +2429,3 @@ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) hidden = TRUE experimental = TRUE - -/datum/techweb_node/fishing - id = "fishing" - display_name = "Fishing Technology" - description = "Cutting edge fishing advancements." - prereq_ids = list("base") - design_ids = list( - "fishing_rod_tech", - "stabilized_hook", - "fish_analyzer", - ) - research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) - hidden = TRUE - experimental = TRUE diff --git a/code/modules/research/xenobiology/crossbreeding/_potions.dm b/code/modules/research/xenobiology/crossbreeding/_potions.dm index 0b5368f53728e..7fb7f10849d3c 100644 --- a/code/modules/research/xenobiology/crossbreeding/_potions.dm +++ b/code/modules/research/xenobiology/crossbreeding/_potions.dm @@ -28,7 +28,7 @@ Slimecrossing Potions return var/path = S.type var/obj/item/slime_extract/C = new path(get_turf(target)) - C.Uses = S.Uses + C.extract_uses = S.extract_uses to_chat(user, span_notice("You pour the potion onto [target], and the fluid solidifies into a copy of it!")) qdel(src) return diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index 5c6ff3c811a6f..a1d4eff50a6ed 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -173,10 +173,13 @@ alert_type = /atom/movable/screen/alert/status_effect/clone_decay /datum/status_effect/slime_clone_decay/tick(seconds_between_ticks) - owner.adjustToxLoss(1, 0) - owner.adjustOxyLoss(1, 0) - owner.adjustBruteLoss(1, 0) - owner.adjustFireLoss(1, 0) + var/need_mob_update + need_mob_update = owner.adjustToxLoss(1, updating_health = FALSE) + need_mob_update += owner.adjustOxyLoss(1, updating_health = FALSE) + need_mob_update += owner.adjustBruteLoss(1, updating_health = FALSE) + need_mob_update += owner.adjustFireLoss(1, updating_health = FALSE) + if(need_mob_update) + owner.updatehealth() owner.color = "#007BA7" /atom/movable/screen/alert/status_effect/bloodchill @@ -470,10 +473,10 @@ colour = SLIME_TYPE_GREY /datum/status_effect/stabilized/grey/tick(seconds_between_ticks) - for(var/mob/living/simple_animal/slime/S in range(1, get_turf(owner))) - if(!(owner in S.Friends)) - to_chat(owner, span_notice("[linked_extract] pulses gently as it communicates with [S].")) - S.set_friendship(owner, 1) + for(var/mob/living/simple_animal/slime/slimes_in_range in range(1, get_turf(owner))) + if(!(owner in slimes_in_range.Friends)) + to_chat(owner, span_notice("[linked_extract] pulses gently as it communicates with [slimes_in_range].")) + slimes_in_range.set_friendship(owner, 1) return ..() /datum/status_effect/stabilized/orange @@ -505,20 +508,24 @@ /datum/status_effect/stabilized/purple/tick(seconds_between_ticks) healed_last_tick = FALSE + var/need_mob_update = FALSE if(owner.getBruteLoss() > 0) - owner.adjustBruteLoss(-0.2) + need_mob_update += owner.adjustBruteLoss(-0.2, updating_health = FALSE) healed_last_tick = TRUE if(owner.getFireLoss() > 0) - owner.adjustFireLoss(-0.2) + need_mob_update += owner.adjustFireLoss(-0.2, updating_health = FALSE) healed_last_tick = TRUE if(owner.getToxLoss() > 0) // Forced, so slimepeople are healed as well. - owner.adjustToxLoss(-0.2, forced = TRUE) + need_mob_update += owner.adjustToxLoss(-0.2, updating_health = FALSE, forced = TRUE) healed_last_tick = TRUE + if(need_mob_update) + owner.updatehealth() + // Technically, "healed this tick" by now. if(healed_last_tick) new /obj/effect/temp_visual/heal(get_turf(owner), "#FF0000") @@ -984,11 +991,9 @@ healing_types += BURN if(owner.getToxLoss() > 0) healing_types += TOX - if(owner.getCloneLoss() > 0) - healing_types += CLONE if(length(healing_types)) - owner.apply_damage_type(-heal_amount, damagetype = pick(healing_types)) + owner.heal_damage_type(heal_amount, damagetype = pick(healing_types)) owner.adjust_nutrition(3) drained.apply_damage(heal_amount * DRAIN_DAMAGE_MULTIPLIER, damagetype = BRUTE, spread_damage = TRUE) diff --git a/code/modules/research/xenobiology/crossbreeding/_weapons.dm b/code/modules/research/xenobiology/crossbreeding/_weapons.dm index 61f4e7a72e0c8..1ad9ce683e475 100644 --- a/code/modules/research/xenobiology/crossbreeding/_weapons.dm +++ b/code/modules/research/xenobiology/crossbreeding/_weapons.dm @@ -29,7 +29,7 @@ Slimecrossing Weapons /obj/item/knife/rainbowknife/afterattack(atom/O, mob/user, proximity) if(proximity && isliving(O)) - damtype = pick(BRUTE, BURN, TOX, OXY, CLONE) + damtype = pick(BRUTE, BURN, TOX, OXY) switch(damtype) if(BRUTE) hitsound = 'sound/weapons/bladeslice.ogg' @@ -47,10 +47,6 @@ Slimecrossing Weapons hitsound = 'sound/effects/space_wind.ogg' attack_verb_continuous = string_list(list("suffocates", "winds", "vacuums")) attack_verb_simple = string_list(list("suffocate", "wind", "vacuum")) - if(CLONE) - hitsound = 'sound/items/geiger/ext1.ogg' - attack_verb_continuous = string_list(list("irradiates", "mutates", "maligns")) - attack_verb_simple = string_list(list("irradiate", "mutate", "malign")) return ..() //Adamantine shield - Chilling Adamantine @@ -128,7 +124,7 @@ Slimecrossing Weapons icon_state = "pulse0_bl" hitsound = 'sound/effects/splat.ogg' -/obj/projectile/magic/bloodchill/on_hit(mob/living/target) +/obj/projectile/magic/bloodchill/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() if(isliving(target)) target.apply_status_effect(/datum/status_effect/bloodchill) diff --git a/code/modules/research/xenobiology/crossbreeding/burning.dm b/code/modules/research/xenobiology/crossbreeding/burning.dm index 0ababd344223d..0103ee40b9198 100644 --- a/code/modules/research/xenobiology/crossbreeding/burning.dm +++ b/code/modules/research/xenobiology/crossbreeding/burning.dm @@ -14,10 +14,10 @@ Burning extracts: create_reagents(10, INJECTABLE | DRAWABLE) /obj/item/slimecross/burning/attack_self(mob/user) - if(!reagents.has_reagent(/datum/reagent/toxin/plasma,10)) + if(!reagents.has_reagent(/datum/reagent/toxin/plasma, 10)) to_chat(user, span_warning("This extract needs to be full of plasma to activate!")) return - reagents.remove_reagent(/datum/reagent/toxin/plasma,10) + reagents.remove_reagent(/datum/reagent/toxin/plasma, 10) to_chat(user, span_notice("You squeeze the extract, and it absorbs the plasma!")) playsound(src, 'sound/effects/bubbles.ogg', 50, TRUE) playsound(src, 'sound/magic/fireball.ogg', 50, TRUE) @@ -32,11 +32,11 @@ Burning extracts: effect_desc = "Creates a hungry and speedy slime that will love you forever." /obj/item/slimecross/burning/grey/do_effect(mob/user) - var/mob/living/simple_animal/slime/S = new(get_turf(user),SLIME_TYPE_GREY) - S.visible_message(span_danger("A baby slime emerges from [src], and it nuzzles [user] before burbling hungrily!")) - S.set_friendship(user, 20) //Gas, gas, gas - S.bodytemperature = T0C + 400 //We gonna step on the gas. - S.set_nutrition(S.get_hunger_nutrition()) //Tonight, we fight! + var/mob/living/simple_animal/slime/new_slime = new(get_turf(user),/datum/slime_type/grey) + new_slime.visible_message(span_danger("A baby slime emerges from [src], and it nuzzles [user] before burbling hungrily!")) + new_slime.set_friendship(user, 20) //Gas, gas, gas + new_slime.bodytemperature = T0C + 400 //We gonna step on the gas. + new_slime.set_nutrition(new_slime.hunger_nutrition) //Tonight, we fight! ..() /obj/item/slimecross/burning/orange @@ -183,12 +183,13 @@ Burning extracts: effect_desc = "Shatters all lights in the current room." /obj/item/slimecross/burning/pyrite/do_effect(mob/user) + var/area/user_area = get_area(user) + if(isnull(user_area.apc)) + user.visible_message(span_danger("[src] releases a colorful wave of energy, but nothing seems to happen.")) + return + + user_area.apc.break_lights() user.visible_message(span_danger("[src] releases a colorful wave of energy, which shatters the lights!")) - var/area/A = get_area(user.loc) - for(var/obj/machinery/light/L in A) //Shamelessly copied from the APC effect. - L.on = TRUE - L.break_light_tube() - stoplag() ..() /obj/item/slimecross/burning/red @@ -197,15 +198,15 @@ Burning extracts: /obj/item/slimecross/burning/red/do_effect(mob/user) user.visible_message(span_danger("[src] pulses a hazy red aura for a moment, which wraps around [user]!")) - for(var/mob/living/simple_animal/slime/S in view(7, get_turf(user))) - if(user in S.Friends) - var/friendliness = S.Friends[user] - S.clear_friends() - S.set_friendship(user, friendliness) + for(var/mob/living/simple_animal/slime/slimes_in_view in view(7, get_turf(user))) + if(user in slimes_in_view.Friends) + var/friendliness = slimes_in_view.Friends[user] + slimes_in_view.clear_friends() + slimes_in_view.set_friendship(user, friendliness) else - S.clear_friends() - S.rabid = 1 - S.visible_message(span_danger("The [S] is driven into a dangerous frenzy!")) + slimes_in_view.clear_friends() + slimes_in_view.rabid = TRUE + slimes_in_view.visible_message(span_danger("The [slimes_in_view] is driven into a dangerous frenzy!")) ..() /obj/item/slimecross/burning/green diff --git a/code/modules/research/xenobiology/crossbreeding/charged.dm b/code/modules/research/xenobiology/crossbreeding/charged.dm index 8941057453ba1..cde5e234afd1e 100644 --- a/code/modules/research/xenobiology/crossbreeding/charged.dm +++ b/code/modules/research/xenobiology/crossbreeding/charged.dm @@ -15,10 +15,10 @@ Charged extracts: create_reagents(10, INJECTABLE | DRAWABLE) /obj/item/slimecross/charged/attack_self(mob/user) - if(!reagents.has_reagent(/datum/reagent/toxin/plasma,10)) + if(!reagents.has_reagent(/datum/reagent/toxin/plasma, 10)) to_chat(user, span_warning("This extract needs to be full of plasma to activate!")) return - reagents.remove_reagent(/datum/reagent/toxin/plasma,10) + reagents.remove_reagent(/datum/reagent/toxin/plasma, 10) to_chat(user, span_notice("You squeeze the extract, and it absorbs the plasma!")) playsound(src, 'sound/effects/bubbles.ogg', 50, TRUE) playsound(src, 'sound/effects/light_flicker.ogg', 50, TRUE) @@ -278,6 +278,6 @@ Charged extracts: /obj/item/slimecross/charged/rainbow/do_effect(mob/user) user.visible_message(span_warning("[src] swells and splits into three new slimes!")) for(var/i in 1 to 3) - var/mob/living/simple_animal/slime/S = new(get_turf(user)) - S.random_colour() + var/mob/living/simple_animal/slime/new_slime = new(get_turf(user)) + new_slime.random_colour() return ..() diff --git a/code/modules/research/xenobiology/crossbreeding/chilling.dm b/code/modules/research/xenobiology/crossbreeding/chilling.dm index c3586437c3276..8890db9a89014 100644 --- a/code/modules/research/xenobiology/crossbreeding/chilling.dm +++ b/code/modules/research/xenobiology/crossbreeding/chilling.dm @@ -14,10 +14,10 @@ Chilling extracts: create_reagents(10, INJECTABLE | DRAWABLE) /obj/item/slimecross/chilling/attack_self(mob/user) - if(!reagents.has_reagent(/datum/reagent/toxin/plasma,10)) + if(!reagents.has_reagent(/datum/reagent/toxin/plasma, 10)) to_chat(user, span_warning("This extract needs to be full of plasma to activate!")) return - reagents.remove_reagent(/datum/reagent/toxin/plasma,10) + reagents.remove_reagent(/datum/reagent/toxin/plasma, 10) to_chat(user, span_notice("You squeeze the extract, and it absorbs the plasma!")) playsound(src, 'sound/effects/bubbles.ogg', 50, TRUE) playsound(src, 'sound/effects/glassbr1.ogg', 50, TRUE) @@ -53,13 +53,15 @@ Chilling extracts: effect_desc = "Injects everyone in the area with some regenerative jelly." /obj/item/slimecross/chilling/purple/do_effect(mob/user) - var/area/A = get_area(get_turf(user)) - if(A.outdoors) + var/area/user_area = get_area(user) + if(user_area.outdoors) to_chat(user, span_warning("[src] can't affect such a large area.")) return user.visible_message(span_notice("[src] shatters, and a healing aura fills the room briefly.")) - for(var/mob/living/carbon/C in A) - C.reagents.add_reagent(/datum/reagent/medicine/regen_jelly,10) + for (var/list/zlevel_turfs as anything in user_area.get_zlevel_turf_lists()) + for(var/turf/area_turf as anything in zlevel_turfs) + for(var/mob/living/carbon/nearby in area_turf) + nearby.reagents?.add_reagent(/datum/reagent/medicine/regen_jelly,10) ..() /obj/item/slimecross/chilling/blue @@ -87,11 +89,14 @@ Chilling extracts: effect_desc = "Recharges the room's APC by 50%." /obj/item/slimecross/chilling/yellow/do_effect(mob/user) - var/area/A = get_area(get_turf(user)) - user.visible_message(span_notice("[src] shatters, and a the air suddenly feels charged for a moment.")) - for(var/obj/machinery/power/apc/C in A) - if(C.cell) - C.cell.charge = min(C.cell.charge + C.cell.maxcharge/2, C.cell.maxcharge) + var/area/user_area = get_area(user) + if(isnull(user_area.apc?.cell)) + user.visible_message(span_notice("[src] shatters, yet the air around you feels normal.")) + return + + var/obj/machinery/power/apc/area_apc = user_area.apc + area_apc.cell.charge = min(area_apc.cell.charge + area_apc.cell.maxcharge / 2, area_apc.cell.maxcharge) + user.visible_message(span_notice("[src] shatters, and the air suddenly feels charged for a moment.")) ..() /obj/item/slimecross/chilling/darkpurple @@ -104,7 +109,7 @@ Chilling extracts: to_chat(user, span_warning("[src] can't affect such a large area.")) return var/filtered = FALSE - for(var/turf/open/T in A) + for(var/turf/open/T in A.get_turfs_from_all_zlevels()) var/datum/gas_mixture/G = T.air if(istype(G)) G.assert_gas(/datum/gas/plasma) @@ -231,9 +236,9 @@ Chilling extracts: /obj/item/slimecross/chilling/red/do_effect(mob/user) var/slimesfound = FALSE - for(var/mob/living/simple_animal/slime/S in view(get_turf(user), 7)) + for(var/mob/living/simple_animal/slime/slimes_in_view in view(get_turf(user), 7)) slimesfound = TRUE - S.docile = TRUE + slimes_in_view.docile = TRUE if(slimesfound) user.visible_message(span_notice("[src] lets out a peaceful ring as it shatters, and nearby slimes seem calm.")) else @@ -332,6 +337,8 @@ Chilling extracts: to_chat(user, span_warning("[src] can't affect such a large area.")) return user.visible_message(span_warning("[src] reflects an array of dazzling colors and light, energy rushing to nearby doors!")) - for(var/obj/machinery/door/airlock/door in area) - new /obj/effect/forcefield/slimewall/rainbow(door.loc) + for (var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists()) + for(var/turf/area_turf as anything in zlevel_turfs) + for(var/obj/machinery/door/airlock/door in area_turf) + new /obj/effect/forcefield/slimewall/rainbow(door.loc) return ..() diff --git a/code/modules/research/xenobiology/crossbreeding/consuming.dm b/code/modules/research/xenobiology/crossbreeding/consuming.dm index 007bacf8bb785..84084b2302e85 100644 --- a/code/modules/research/xenobiology/crossbreeding/consuming.dm +++ b/code/modules/research/xenobiology/crossbreeding/consuming.dm @@ -120,12 +120,14 @@ Consuming extracts: taste = "fruit jam and cough medicine" /obj/item/slime_cookie/purple/do_effect(mob/living/M, mob/user) - M.adjustBruteLoss(-5) - M.adjustFireLoss(-5) - M.adjustToxLoss(-5, forced=1) //To heal slimepeople. - M.adjustOxyLoss(-5) - M.adjustCloneLoss(-5) - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, -5) + var/need_mob_update = FALSE + need_mob_update += M.adjustBruteLoss(-5, updating_health = FALSE) + need_mob_update += M.adjustFireLoss(-5, updating_health = FALSE) + need_mob_update += M.adjustToxLoss(-5, updating_health = FALSE, forced = TRUE) //To heal slimepeople. + need_mob_update += M.adjustOxyLoss(-5, updating_health = FALSE) + need_mob_update += M.adjustOrganLoss(ORGAN_SLOT_BRAIN, -5) + if(need_mob_update) + M.updatehealth() /obj/item/slimecross/consuming/blue colour = SLIME_TYPE_BLUE diff --git a/code/modules/research/xenobiology/crossbreeding/industrial.dm b/code/modules/research/xenobiology/crossbreeding/industrial.dm index 5222ab3608c09..da878b77b5a05 100644 --- a/code/modules/research/xenobiology/crossbreeding/industrial.dm +++ b/code/modules/research/xenobiology/crossbreeding/industrial.dm @@ -32,11 +32,11 @@ Industrial extracts: var/IsWorking = FALSE if(reagents.has_reagent(/datum/reagent/toxin/plasma,amount = 2) && plasmarequired > 1) //Can absorb as much as 2 IsWorking = TRUE - reagents.remove_reagent(/datum/reagent/toxin/plasma,2) + reagents.remove_reagent(/datum/reagent/toxin/plasma, 2) plasmaabsorbed += 2 else if(reagents.has_reagent(/datum/reagent/toxin/plasma,amount = 1)) //Can absorb as little as 1 IsWorking = TRUE - reagents.remove_reagent(/datum/reagent/toxin/plasma,1) + reagents.remove_reagent(/datum/reagent/toxin/plasma, 1) plasmaabsorbed += 1 if(plasmaabsorbed >= plasmarequired) diff --git a/code/modules/research/xenobiology/crossbreeding/recurring.dm b/code/modules/research/xenobiology/crossbreeding/recurring.dm index c6ca420cc04fa..3279e26c92004 100644 --- a/code/modules/research/xenobiology/crossbreeding/recurring.dm +++ b/code/modules/research/xenobiology/crossbreeding/recurring.dm @@ -29,10 +29,10 @@ Recurring extracts: /obj/item/slimecross/recurring/process(seconds_per_tick) if(cooldown > 0) cooldown -= seconds_per_tick - else if(extract.Uses < 10 && extract.Uses > 0) - extract.Uses++ + else if(extract.extract_uses < 10 && extract.extract_uses > 0) + extract.extract_uses++ cooldown = max_cooldown - else if(extract.Uses <= 0) + else if(extract.extract_uses <= 0) extract.visible_message(span_warning("The light inside [extract] flickers and dies out.")) extract.desc = "A tiny, inert core, bleeding dark, cerulean-colored goo." extract.icon_state = "prismatic" diff --git a/code/modules/research/xenobiology/crossbreeding/regenerative.dm b/code/modules/research/xenobiology/crossbreeding/regenerative.dm index f0f395b33a613..4a222e8e982d9 100644 --- a/code/modules/research/xenobiology/crossbreeding/regenerative.dm +++ b/code/modules/research/xenobiology/crossbreeding/regenerative.dm @@ -197,8 +197,8 @@ Regenerative extracts: /obj/item/slimecross/regenerative/green/core_effect(mob/living/target, mob/user) if(isslime(target)) target.visible_message(span_warning("The [target] suddenly changes color!")) - var/mob/living/simple_animal/slime/S = target - S.random_colour() + var/mob/living/simple_animal/slime/target_slime = target + target_slime.random_colour() if(isjellyperson(target)) target.reagents.add_reagent(/datum/reagent/mutationtoxin/jelly,5) diff --git a/code/modules/research/xenobiology/crossbreeding/stabilized.dm b/code/modules/research/xenobiology/crossbreeding/stabilized.dm index eb49f5dc2c3a6..ad5750de9ff05 100644 --- a/code/modules/research/xenobiology/crossbreeding/stabilized.dm +++ b/code/modules/research/xenobiology/crossbreeding/stabilized.dm @@ -53,7 +53,7 @@ Stabilized extracts: if (holder.has_status_effect(effectpath)) return holder.apply_status_effect(effectpath, src) - STOP_PROCESSING(SSobj,src) + return PROCESS_KILL //Colors and subtypes: /obj/item/slimecross/stabilized/grey diff --git a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm index bec3092d8eaf2..08caca838542c 100644 --- a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm +++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm @@ -108,7 +108,7 @@ /datum/reagent/consumable/milk/chocolate_milk = -1) virus_suspectibility = 1.5 - resulting_atoms = list(/mob/living/simple_animal/pet/cat = 1) //The basic cat mobs are all male, so you mightt need a gender swap potion if you want to fill the fortress with kittens. + resulting_atoms = list(/mob/living/basic/pet/cat = 1) /datum/micro_organism/cell_line/corgi desc = "Canid cells" @@ -223,7 +223,7 @@ /datum/reagent/consumable/corn_syrup = -6, /datum/reagent/sulfur = -3) //sulfur repels snakes according to professor google. - resulting_atoms = list(/mob/living/simple_animal/hostile/retaliate/snake = 1) + resulting_atoms = list(/mob/living/basic/snake = 1) /////////////////////////////////////////// @@ -263,7 +263,7 @@ /datum/reagent/medicine/psicodine = -2) //Blob zombies likely wouldn't appreciate psicodine so why this is here virus_suspectibility = 0 - resulting_atoms = list(/mob/living/simple_animal/hostile/blob/blobspore/independent = 2) //These are useless so we might as well spawn 2. + resulting_atoms = list(/mob/living/basic/blob_minion/spore = 2) //These are useless so we might as well spawn 2. /datum/micro_organism/cell_line/blobbernaut desc = "Blobular myocytes" @@ -282,7 +282,7 @@ suppressive_reagents = list(/datum/reagent/consumable/tinlux = -6) virus_suspectibility = 0 - resulting_atoms = list(/mob/living/simple_animal/hostile/blob/blobbernaut/independent = 1) + resulting_atoms = list(/mob/living/basic/blob_minion/blobbernaut = 1) /datum/micro_organism/cell_line/gelatinous_cube desc = "Cubic ooze particles" @@ -481,7 +481,7 @@ /datum/micro_organism/cell_line/clown/fuck_up_growing(obj/machinery/plumbing/growing_vat/vat) vat.visible_message(span_warning("The biological sample in [vat] seems to have created something horrific!")) - var/mob/selected_mob = pick(list(/mob/living/simple_animal/hostile/retaliate/clown/mutant/slow, /mob/living/simple_animal/hostile/retaliate/clown/fleshclown)) + var/mob/selected_mob = pick(list(/mob/living/basic/clown/mutant/slow, /mob/living/basic/clown/fleshclown)) new selected_mob(get_turf(vat)) if(SEND_SIGNAL(vat.biological_sample, COMSIG_SAMPLE_GROWTH_COMPLETED) & SPARE_SAMPLE) @@ -509,7 +509,7 @@ /datum/reagent/consumable/nothing = -2, /datum/reagent/fuel/oil = -1) - resulting_atoms = list(/mob/living/simple_animal/hostile/retaliate/clown/banana = 1) + resulting_atoms = list(/mob/living/basic/clown/banana = 1) /datum/micro_organism/cell_line/clown/glutton desc = "hyperadipogenic clown stem cells" @@ -535,7 +535,7 @@ /datum/reagent/consumable/nothing = -2, /datum/reagent/toxin/bad_food = -1) - resulting_atoms = list(/mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton = 1) + resulting_atoms = list(/mob/living/basic/clown/mutant/glutton = 1) /datum/micro_organism/cell_line/clown/longclown desc = "long clown bits" @@ -558,7 +558,7 @@ /datum/reagent/consumable/nothing = -2, /datum/reagent/sulfur = -1) - resulting_atoms = list(/mob/living/simple_animal/hostile/retaliate/clown/longface = 1) + resulting_atoms = list(/mob/living/basic/clown/longface = 1) /datum/micro_organism/cell_line/frog desc = "anura amphibian cells" @@ -652,7 +652,7 @@ /datum/reagent/drug/nicotine = -1) virus_suspectibility = 0 - resulting_atoms = list(/obj/item/queen_bee = 1) + resulting_atoms = list(/obj/item/queen_bee/bought = 1) /datum/micro_organism/cell_line/queen_bee/fuck_up_growing(obj/machinery/plumbing/growing_vat/vat) //we love job hazards vat.visible_message(span_warning("You hear angry buzzing coming from the inside of the vat!")) @@ -684,30 +684,6 @@ virus_suspectibility = 0 resulting_atoms = list(/mob/living/basic/butterfly = 3) -/datum/micro_organism/cell_line/leaper - desc = "atypical amphibian cells" - required_reagents = list( - /datum/reagent/consumable/nutriment/protein, - /datum/reagent/ants, - /datum/reagent/consumable/eggyolk, - /datum/reagent/medicine/c2/synthflesh) - - supplementary_reagents = list( - /datum/reagent/growthserum = 4, - /datum/reagent/drug/blastoff = 3, - /datum/reagent/drug/space_drugs = 2, - /datum/reagent/consumable/ethanol/eggnog = 2, - /datum/reagent/consumable/vanilla = 2, - /datum/reagent/consumable/banana = 1, - /datum/reagent/consumable/nutriment/vitamin = 1) - - suppressive_reagents = list( - /datum/reagent/toxin/cyanide = -5, - /datum/reagent/consumable/mold = -2, - /datum/reagent/toxin/spore = -1) - - resulting_atoms = list(/mob/living/simple_animal/hostile/jungle/leaper = 1) - /datum/micro_organism/cell_line/mega_arachnid desc = "pseudoarachnoid cells" required_reagents = list( diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm index ac148161b03f2..97e7812190831 100644 --- a/code/modules/research/xenobiology/xenobio_camera.dm +++ b/code/modules/research/xenobiology/xenobio_camera.dm @@ -6,16 +6,15 @@ var/allowed_area = null /mob/camera/ai_eye/remote/xenobio/Initialize(mapload) - var/area/A = get_area(loc) - allowed_area = A.name + var/area/our_area = get_area(loc) + allowed_area = our_area.name . = ..() /mob/camera/ai_eye/remote/xenobio/setLoc(turf/destination, force_update = FALSE) var/area/new_area = get_area(destination) + if(new_area && new_area.name == allowed_area || new_area && (new_area.area_flags & XENOBIOLOGY_COMPATIBLE)) return ..() - else - return /mob/camera/ai_eye/remote/xenobio/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider) . = ..() @@ -31,10 +30,15 @@ networks = list("ss13") circuit = /obj/item/circuitboard/computer/xenobiology + ///The recycler connected to the camera console var/obj/machinery/monkey_recycler/connected_recycler + ///The slimes stored inside the console var/list/stored_slimes + ///The single slime potion stored inside the console var/obj/item/slimepotion/slime/current_potion + ///The maximum amount of slimes that fit in the machine var/max_slimes = 5 + ///The amount of monkey cubes inside the machine var/monkeys = 0 icon_screen = "slime_comp" @@ -53,6 +57,9 @@ actions += new /datum/action/innate/hotkey_help(src) stored_slimes = list() + +/obj/machinery/computer/camera_advanced/xenobio/LateInitialize(mapload) + . = ..() for(var/obj/machinery/monkey_recycler/recycler in GLOB.monkey_recyclers) if(get_area(recycler.loc) == get_area(loc)) connected_recycler = recycler @@ -61,8 +68,8 @@ /obj/machinery/computer/camera_advanced/xenobio/Destroy() QDEL_NULL(current_potion) for(var/thing in stored_slimes) - var/mob/living/simple_animal/slime/S = thing - S.forceMove(drop_location()) + var/mob/living/simple_animal/slime/stored_slime = thing + stored_slime.forceMove(drop_location()) stored_slimes.Cut() if(connected_recycler) connected_recycler.connected -= src @@ -92,13 +99,6 @@ RegisterSignal(user, COMSIG_XENO_SLIME_CLICK_SHIFT, PROC_REF(XenoSlimeClickShift)) RegisterSignal(user, COMSIG_XENO_TURF_CLICK_SHIFT, PROC_REF(XenoTurfClickShift)) - //Checks for recycler on every interact, prevents issues with load order on certain maps. - if(!connected_recycler) - for(var/obj/machinery/monkey_recycler/recycler in GLOB.monkey_recyclers) - if(get_area(recycler.loc) == get_area(loc)) - connected_recycler = recycler - connected_recycler.connected += src - /obj/machinery/computer/camera_advanced/xenobio/remove_eye_control(mob/living/user) UnregisterSignal(user, COMSIG_XENO_SLIME_CLICK_CTRL) UnregisterSignal(user, COMSIG_XENO_TURF_CLICK_CTRL) @@ -108,43 +108,116 @@ UnregisterSignal(user, COMSIG_XENO_TURF_CLICK_SHIFT) ..() -/obj/machinery/computer/camera_advanced/xenobio/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/food/monkeycube)) +/obj/machinery/computer/camera_advanced/xenobio/attackby(obj/item/used_item, mob/user, params) + if(istype(used_item, /obj/item/food/monkeycube)) monkeys++ - to_chat(user, span_notice("You feed [O] to [src]. It now has [monkeys] monkey cubes stored.")) - qdel(O) + to_chat(user, span_notice("You feed [used_item] to [src]. It now has [monkeys] monkey cubes stored.")) + qdel(used_item) return - else if(istype(O, /obj/item/storage/bag)) - var/obj/item/storage/P = O + + if(istype(used_item, /obj/item/storage/bag)) + var/obj/item/storage/storage_bag = used_item var/loaded = FALSE - for(var/obj/G in P.contents) - if(istype(G, /obj/item/food/monkeycube)) + for(var/obj/item_in_bag in storage_bag.contents) + if(istype(item_in_bag, /obj/item/food/monkeycube)) loaded = TRUE monkeys++ - qdel(G) + qdel(item_in_bag) if(loaded) - to_chat(user, span_notice("You fill [src] with the monkey cubes stored in [O]. [src] now has [monkeys] monkey cubes stored.")) + to_chat(user, span_notice("You fill [src] with the monkey cubes stored in [used_item]. [src] now has [monkeys] monkey cubes stored.")) return - else if(istype(O, /obj/item/slimepotion/slime)) + + if(istype(used_item, /obj/item/slimepotion/slime)) var/replaced = FALSE - if(user && !user.transferItemToLoc(O, src)) + if(user && !user.transferItemToLoc(used_item, src)) return if(!QDELETED(current_potion)) current_potion.forceMove(drop_location()) replaced = TRUE - current_potion = O - to_chat(user, span_notice("You load [O] in the console's potion slot[replaced ? ", replacing the one that was there before" : ""].")) + current_potion = used_item + to_chat(user, span_notice("You load [used_item] in the console's potion slot[replaced ? ", replacing the one that was there before" : ""].")) return + ..() -/obj/machinery/computer/camera_advanced/xenobio/multitool_act(mob/living/user, obj/item/multitool/I) +/obj/machinery/computer/camera_advanced/xenobio/multitool_act(mob/living/user, obj/item/multitool/used_multitool) . = ..() - if (istype(I) && istype(I.buffer,/obj/machinery/monkey_recycler)) - to_chat(user, span_notice("You link [src] with [I.buffer] in [I] buffer.")) - connected_recycler = I.buffer + if (istype(used_multitool) && istype(used_multitool.buffer,/obj/machinery/monkey_recycler)) + to_chat(user, span_notice("You link [src] with [used_multitool.buffer] in [used_multitool] buffer.")) + connected_recycler = used_multitool.buffer connected_recycler.connected += src return TRUE +/* +Boilerplate check for a valid area to perform a camera action in. +Checks if the AI eye is on a valid turf and then checks if the target turf is xenobiology compatible +Due to keyboard shortcuts, the second one is not necessarily the remote eye's location. +*/ +/obj/machinery/computer/camera_advanced/xenobio/proc/validate_area(mob/living/user, mob/camera/ai_eye/remote/xenobio/remote_eye, turf/open/target_turf) + if(!GLOB.cameranet.checkTurfVis(remote_eye.loc)) + to_chat(user, span_warning("Target is not near a camera. Cannot proceed.")) + return FALSE + + var/area/turfarea = get_area(target_turf) + if(turfarea.name != remote_eye.allowed_area && !(turfarea.area_flags & XENOBIOLOGY_COMPATIBLE)) + to_chat(user, span_warning("Invalid area. Cannot proceed.")) + return FALSE + + return TRUE + +///Places every slime in storage on target turf +/obj/machinery/computer/camera_advanced/xenobio/proc/slime_place(turf/open/target_turf) + for(var/mob/living/simple_animal/slime/stored_slime in stored_slimes) + stored_slime.forceMove(target_turf) + stored_slime.visible_message(span_notice("[stored_slime] warps in!")) + stored_slimes -= stored_slime + +///Places every slime not controlled by a player into the internal storage, respecting its limits +///Returns TRUE to signal it hitting the limit, in case its being called from a loop and we want it to stop +/obj/machinery/computer/camera_advanced/xenobio/proc/slime_pickup(mob/living/user, mob/living/simple_animal/slime/target_slime) + if(stored_slimes.len >= max_slimes) + to_chat(user, span_warning("Slime storage is full.")) + return TRUE + if(target_slime.ckey) + to_chat(user, span_warning("The slime wiggled free!")) + return FALSE + if(target_slime.buckled) + target_slime.stop_feeding(silent = TRUE) + target_slime.visible_message(span_notice("[target_slime] vanishes in a flash of light!")) + target_slime.forceMove(src) + stored_slimes += target_slime + + return FALSE + +///Places one monkey, if possible +/obj/machinery/computer/camera_advanced/xenobio/proc/feed_slime(mob/living/user, turf/open/target_turf) + if(monkeys < 1) + to_chat(user, span_warning("[src] needs to have at least 1 monkey stored. Currently has [monkeys] monkeys stored.")) + return + + var/mob/living/carbon/human/species/monkey/food = new /mob/living/carbon/human/species/monkey(target_turf, TRUE, user) + if (QDELETED(food)) + return + + food.LAssailant = WEAKREF(user) + monkeys-- + monkeys = round(monkeys, 0.1) //Prevents rounding errors + to_chat(user, span_notice("[src] now has [monkeys] monkeys stored.")) + +///Recycles the target monkey +/obj/machinery/computer/camera_advanced/xenobio/proc/monkey_recycle(mob/living/user, mob/living/target_mob) + if(!ismonkey(target_mob)) + return + if(!target_mob.stat) + return + + target_mob.visible_message(span_notice("[target_mob] vanishes as [p_theyre()] reclaimed for recycling!")) + connected_recycler.use_power(500) + monkeys += connected_recycler.cube_production + monkeys = round(monkeys, 0.1) //Prevents rounding errors + qdel(target_mob) + to_chat(user, span_notice("[src] now has [monkeys] monkeys stored.")) + /datum/action/innate/slime_place name = "Place Slimes" button_icon = 'icons/mob/actions/actions_silicon.dmi' @@ -153,17 +226,14 @@ /datum/action/innate/slime_place/Activate() if(!target || !isliving(owner)) return - var/mob/living/C = owner - var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = target + var/mob/living/owner_mob = owner + var/mob/camera/ai_eye/remote/xenobio/remote_eye = owner_mob.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target + + if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) + return - if(GLOB.cameranet.checkTurfVis(remote_eye.loc)) - for(var/mob/living/simple_animal/slime/S in X.stored_slimes) - S.forceMove(remote_eye.loc) - S.visible_message(span_notice("[S] warps in!")) - X.stored_slimes -= S - else - to_chat(owner, span_warning("Target is not near a camera. Cannot proceed.")) + xeno_console.slime_place(remote_eye.loc) /datum/action/innate/slime_pick_up name = "Pick up Slime" @@ -173,23 +243,16 @@ /datum/action/innate/slime_pick_up/Activate() if(!target || !isliving(owner)) return - var/mob/living/C = owner - var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = target + var/mob/living/owner_mob = owner + var/mob/camera/ai_eye/remote/xenobio/remote_eye = owner_mob.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target - if(GLOB.cameranet.checkTurfVis(remote_eye.loc)) - for(var/mob/living/simple_animal/slime/S in remote_eye.loc) - if(X.stored_slimes.len >= X.max_slimes) - break - if(!S.ckey) - if(S.buckled) - S.Feedstop(silent = TRUE) - S.visible_message(span_notice("[S] vanishes in a flash of light!")) - S.forceMove(X) - X.stored_slimes += S - else - to_chat(owner, span_warning("Target is not near a camera. Cannot proceed.")) + if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) + return + for(var/mob/living/simple_animal/slime/target_slime in remote_eye.loc) + if(xeno_console.slime_pickup(owner_mob, target_slime)) ///Returns true if we hit our slime pickup limit + break /datum/action/innate/feed_slime name = "Feed Slimes" @@ -199,22 +262,14 @@ /datum/action/innate/feed_slime/Activate() if(!target || !isliving(owner)) return - var/mob/living/C = owner - var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = target + var/mob/living/living_owner = owner + var/mob/camera/ai_eye/remote/xenobio/remote_eye = living_owner.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target - if(GLOB.cameranet.checkTurfVis(remote_eye.loc)) - if(X.monkeys >= 1) - var/mob/living/carbon/human/species/monkey/food = new /mob/living/carbon/human/species/monkey(remote_eye.loc, TRUE, owner) - if (!QDELETED(food)) - food.LAssailant = WEAKREF(C) - X.monkeys-- - X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors - to_chat(owner, span_notice("[X] now has [X.monkeys] monkeys stored.")) - else - to_chat(owner, span_warning("[X] needs to have at least 1 monkey stored. Currently has [X.monkeys] monkeys stored.")) - else - to_chat(owner, span_warning("Target is not near a camera. Cannot proceed.")) + if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) + return + + xeno_console.feed_slime(living_owner, remote_eye.loc) /datum/action/innate/monkey_recycle @@ -225,27 +280,20 @@ /datum/action/innate/monkey_recycle/Activate() if(!target || !isliving(owner)) return - var/mob/living/C = owner - var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = target - var/obj/machinery/monkey_recycler/recycler = X.connected_recycler + var/mob/living/owner_mob = owner + var/mob/camera/ai_eye/remote/xenobio/remote_eye = owner_mob.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target + var/obj/machinery/monkey_recycler/recycler = xeno_console.connected_recycler + + if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) + return if(!recycler) to_chat(owner, span_warning("There is no connected monkey recycler. Use a multitool to link one.")) return - if(GLOB.cameranet.checkTurfVis(remote_eye.loc)) - for(var/mob/living/carbon/human/M in remote_eye.loc) - if(!ismonkey(M)) - continue - if(M.stat) - M.visible_message(span_notice("[M] vanishes as [M.p_theyre()] reclaimed for recycling!")) - recycler.use_power(500) - X.monkeys += recycler.cube_production - X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors - qdel(M) - to_chat(owner, span_notice("[X] now has [X.monkeys] monkeys available.")) - else - to_chat(owner, span_warning("Target is not near a camera. Cannot proceed.")) + + for(var/mob/living/carbon/human/target_mob in remote_eye.loc) + xeno_console.monkey_recycle(owner, target_mob) /datum/action/innate/slime_scan name = "Scan Slime" @@ -255,14 +303,15 @@ /datum/action/innate/slime_scan/Activate() if(!target || !isliving(owner)) return - var/mob/living/C = owner - var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control + var/mob/living/owner_mob = owner + var/mob/camera/ai_eye/remote/xenobio/remote_eye = owner_mob.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target + + if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) + return - if(GLOB.cameranet.checkTurfVis(remote_eye.loc)) - for(var/mob/living/simple_animal/slime/S in remote_eye.loc) - slime_scan(S, C) - else - to_chat(owner, span_warning("Target is not near a camera. Cannot proceed.")) + for(var/mob/living/simple_animal/slime/scanned_slime in remote_eye.loc) + slime_scan(scanned_slime, owner_mob) /datum/action/innate/feed_potion name = "Apply Potion" @@ -273,20 +322,20 @@ if(!target || !isliving(owner)) return - var/mob/living/C = owner - var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = target + var/mob/living/owner_mob = owner + var/mob/camera/ai_eye/remote/xenobio/remote_eye = owner_mob.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = target - if(QDELETED(X.current_potion)) + if(!xeno_console.validate_area(owner, remote_eye, remote_eye.loc)) + return + + if(QDELETED(xeno_console.current_potion)) to_chat(owner, span_warning("No potion loaded.")) return - if(GLOB.cameranet.checkTurfVis(remote_eye.loc)) - for(var/mob/living/simple_animal/slime/S in remote_eye.loc) - X.current_potion.attack(S, C) - break - else - to_chat(owner, span_warning("Target is not near a camera. Cannot proceed.")) + for(var/mob/living/simple_animal/slime/potioned_slime in remote_eye.loc) + xeno_console.current_potion.attack(potioned_slime, owner_mob) + break /datum/action/innate/hotkey_help name = "Hotkey Help" @@ -296,156 +345,123 @@ /datum/action/innate/hotkey_help/Activate() if(!target || !isliving(owner)) return - to_chat(owner, "Click shortcuts:") - to_chat(owner, "Shift-click a slime to pick it up, or the floor to drop all held slimes.") - to_chat(owner, "Ctrl-click a slime to scan it.") - to_chat(owner, "Alt-click a slime to feed it a potion.") - to_chat(owner, "Ctrl-click or a dead monkey to recycle it, or the floor to place a new monkey.") + + var/render_list = list() + render_list += "Click shortcuts:" + render_list += "• Shift-click a slime to pick it up, or the floor to drop all held slimes." + render_list += "• Ctrl-click a slime to scan it." + render_list += "• Alt-click a slime to feed it a potion." + render_list += "• Ctrl-click or a dead monkey to recycle it, or the floor to place a new monkey." + + to_chat(owner, examine_block(jointext(render_list, "\n"))) // // Alternate clicks for slime, monkey and open turf if using a xenobio console -//Feeds a potion to slime /mob/living/simple_animal/slime/AltClick(mob/user) SEND_SIGNAL(user, COMSIG_XENO_SLIME_CLICK_ALT, src) ..() -//Picks up slime /mob/living/simple_animal/slime/ShiftClick(mob/user) SEND_SIGNAL(user, COMSIG_XENO_SLIME_CLICK_SHIFT, src) ..() -//Place slimes /turf/open/ShiftClick(mob/user) SEND_SIGNAL(user, COMSIG_XENO_TURF_CLICK_SHIFT, src) ..() -//scans slimes /mob/living/simple_animal/slime/CtrlClick(mob/user) SEND_SIGNAL(user, COMSIG_XENO_SLIME_CLICK_CTRL, src) ..() -//picks up dead monkies /mob/living/carbon/human/species/monkey/CtrlClick(mob/user) SEND_SIGNAL(user, COMSIG_XENO_MONKEY_CLICK_CTRL, src) ..() -//places monkies /turf/open/CtrlClick(mob/user) SEND_SIGNAL(user, COMSIG_XENO_TURF_CLICK_CTRL, src) ..() -// Scans slime -/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickCtrl(mob/living/user, mob/living/simple_animal/slime/S) +/// Scans the target slime +/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickCtrl(mob/living/user, mob/living/simple_animal/slime/target_slime) SIGNAL_HANDLER - if(!GLOB.cameranet.checkTurfVis(S.loc)) - to_chat(user, span_warning("Target is not near a camera. Cannot proceed.")) + + var/mob/camera/ai_eye/remote/xenobio/remote_eye = user.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + + if(!xeno_console.validate_area(user, remote_eye, target_slime.loc)) return - var/mob/living/C = user - var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control - var/area/mobarea = get_area(S.loc) - if(mobarea.name == E.allowed_area || (mobarea.area_flags & XENOBIOLOGY_COMPATIBLE)) - slime_scan(S, C) -//Feeds a potion to slime -/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickAlt(mob/living/user, mob/living/simple_animal/slime/S) + slime_scan(target_slime, user) + +///Feeds a stored potion to a slime +/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickAlt(mob/living/user, mob/living/simple_animal/slime/target_slime) SIGNAL_HANDLER - if(!GLOB.cameranet.checkTurfVis(S.loc)) - to_chat(user, span_warning("Target is not near a camera. Cannot proceed.")) + + var/mob/camera/ai_eye/remote/xenobio/remote_eye = user.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + + if(!xeno_console.validate_area(user, remote_eye, target_slime.loc)) return - var/mob/living/C = user - var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin - var/area/mobarea = get_area(S.loc) - if(QDELETED(X.current_potion)) - to_chat(C, span_warning("No potion loaded.")) + + if(QDELETED(xeno_console.current_potion)) + to_chat(user, span_warning("No potion loaded.")) return - if(mobarea.name == E.allowed_area || (mobarea.area_flags & XENOBIOLOGY_COMPATIBLE)) - INVOKE_ASYNC(X.current_potion, TYPE_PROC_REF(/obj/item/slimepotion/slime, attack), S, C) -//Picks up slime -/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickShift(mob/living/user, mob/living/simple_animal/slime/S) + INVOKE_ASYNC(xeno_console.current_potion, TYPE_PROC_REF(/obj/item/slimepotion/slime, attack), target_slime, user) + +///Picks up a slime, and places them in the internal storage +/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickShift(mob/living/user, mob/living/simple_animal/slime/target_slime) SIGNAL_HANDLER - if(!GLOB.cameranet.checkTurfVis(S.loc)) - to_chat(user, span_warning("Target is not near a camera. Cannot proceed.")) + + var/mob/camera/ai_eye/remote/xenobio/remote_eye = user.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + + if(!xeno_console.validate_area(user, remote_eye, target_slime.loc)) return - var/mob/living/C = user - var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin - var/area/mobarea = get_area(S.loc) - if(mobarea.name == E.allowed_area || (mobarea.area_flags & XENOBIOLOGY_COMPATIBLE)) - if(X.stored_slimes.len >= X.max_slimes) - to_chat(C, span_warning("Slime storage is full.")) - return - if(S.ckey) - to_chat(C, span_warning("The slime wiggled free!")) - return - if(S.buckled) - S.Feedstop(silent = TRUE) - S.visible_message(span_notice("[S] vanishes in a flash of light!")) - S.forceMove(X) - X.stored_slimes += S - -//Place slimes -/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickShift(mob/living/user, turf/open/T) + + xeno_console.slime_pickup(user, target_slime) + +///Places all slimes from the internal storage +/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickShift(mob/living/user, turf/open/target_turf) SIGNAL_HANDLER - if(!GLOB.cameranet.checkTurfVis(T)) - to_chat(user, span_warning("Target is not near a camera. Cannot proceed.")) + var/mob/living/user_mob = user + var/mob/camera/ai_eye/remote/xenobio/remote_eye = user_mob.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + + if(!xeno_console.validate_area(user, remote_eye, target_turf)) return - var/mob/living/C = user - var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin - var/area/turfarea = get_area(T) - if(turfarea.name == E.allowed_area || (turfarea.area_flags & XENOBIOLOGY_COMPATIBLE)) - for(var/mob/living/simple_animal/slime/S in X.stored_slimes) - S.forceMove(T) - S.visible_message(span_notice("[S] warps in!")) - X.stored_slimes -= S - -//Place monkey -/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickCtrl(mob/living/user, turf/open/T) + + slime_place(target_turf) + +///Places a monkey from the internal storage +/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickCtrl(mob/living/user, turf/open/target_turf) SIGNAL_HANDLER - if(!GLOB.cameranet.checkTurfVis(T)) - to_chat(user, span_warning("Target is not near a camera. Cannot proceed.")) + + var/mob/camera/ai_eye/remote/xenobio/remote_eye = user.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + + if(!xeno_console.validate_area(user, remote_eye, target_turf)) return - var/mob/living/C = user - var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin - var/area/turfarea = get_area(T) - if(turfarea.name == E.allowed_area || (turfarea.area_flags & XENOBIOLOGY_COMPATIBLE)) - if(X.monkeys >= 1) - var/mob/living/carbon/human/food = new /mob/living/carbon/human/species/monkey(T, TRUE, C) - if (!QDELETED(food)) - food.LAssailant = WEAKREF(C) - X.monkeys-- - X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors - to_chat(C, span_notice("[X] now has [X.monkeys] monkeys stored.")) - else - to_chat(C, span_warning("[X] needs to have at least 1 monkey stored. Currently has [X.monkeys] monkeys stored.")) - -//Pick up monkey -/obj/machinery/computer/camera_advanced/xenobio/proc/XenoMonkeyClickCtrl(mob/living/user, mob/living/carbon/human/M) + + xeno_console.feed_slime(user, target_turf) + +///Picks up a dead monkey for recycling +/obj/machinery/computer/camera_advanced/xenobio/proc/XenoMonkeyClickCtrl(mob/living/user, mob/living/carbon/human/target_mob) SIGNAL_HANDLER - if(!ismonkey(M)) + if(!ismonkey(target_mob)) return - if(!isturf(M.loc) || !GLOB.cameranet.checkTurfVis(M.loc)) - to_chat(user, span_warning("Target is not near a camera. Cannot proceed.")) + + var/mob/camera/ai_eye/remote/xenobio/remote_eye = user.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/xeno_console = remote_eye.origin + + if(!xeno_console.validate_area(user, remote_eye, target_mob.loc)) return - var/mob/living/C = user - var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin - var/area/mobarea = get_area(M.loc) - if(!X.connected_recycler) - to_chat(C, span_warning("There is no connected monkey recycler. Use a multitool to link one.")) + + if(!xeno_console.connected_recycler) + to_chat(user, span_warning("There is no connected monkey recycler. Use a multitool to link one.")) return - if(mobarea.name == E.allowed_area || (mobarea.area_flags & XENOBIOLOGY_COMPATIBLE)) - if(!M.stat) - return - M.visible_message(span_notice("[M] vanishes as [p_theyre()] reclaimed for recycling!")) - X.connected_recycler.use_power(500) - X.monkeys += connected_recycler.cube_production - X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors - qdel(M) - to_chat(C, span_notice("[X] now has [X.monkeys] monkeys available.")) + + xeno_console.monkey_recycle(user, target_mob) diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index f81f328c0f359..49f7bfa61f392 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -11,28 +11,32 @@ throw_speed = 3 throw_range = 6 grind_results = list() - var/Uses = 1 ///uses before it goes inert - var/qdel_timer = null ///deletion timer, for delayed reactions - var/effectmod ///Which type of crossbred - var/list/activate_reagents = list() ///Reagents required for activation + ///uses before it goes inert + var/extract_uses = 1 + ///deletion timer, for delayed reactions + var/qdel_timer = null + ///Which type of crossbred + var/crossbreed_modification + ///Reagents required for activation + var/list/activate_reagents = list() var/recurring = FALSE /obj/item/slime_extract/examine(mob/user) . = ..() - if(Uses > 1) - . += "It has [Uses] uses remaining." + if(extract_uses > 1) + . += "It has [extract_uses] uses remaining." /obj/item/slime_extract/attackby(obj/item/O, mob/user) if(istype(O, /obj/item/slimepotion/enhancer)) - if(Uses >= 5 || recurring) + if(extract_uses >= 5 || recurring) to_chat(user, span_warning("You cannot enhance this extract further!")) return ..() if(O.type == /obj/item/slimepotion/enhancer) //Seriously, why is this defined here...? to_chat(user, span_notice("You apply the enhancer to the slime extract. It may now be reused one more time.")) - Uses++ + extract_uses++ if(O.type == /obj/item/slimepotion/enhancer/max) to_chat(user, span_notice("You dump the maximizer on the slime extract. It can now be used a total of 5 times!")) - Uses = 5 + extract_uses = 5 qdel(O) ..() @@ -42,7 +46,7 @@ /obj/item/slime_extract/on_grind() . = ..() - if(Uses) + if(extract_uses) grind_results[/datum/reagent/toxin/slimejelly] = 20 /** @@ -63,34 +67,34 @@ * * By using a valid core on a living adult slime, then feeding it nine more of the same type, you can mutate it into more useful items. Not every slime type has an implemented core cross. */ -/obj/item/slime_extract/attack(mob/living/simple_animal/slime/M, mob/user) - if(!isslime(M)) +/obj/item/slime_extract/attack(mob/living/simple_animal/slime/target_slime, mob/user) + if(!isslime(target_slime)) return ..() - if(M.stat) + if(target_slime.stat) to_chat(user, span_warning("The slime is dead!")) return - if(!M.is_adult) + if(target_slime.life_stage != SLIME_LIFE_STAGE_ADULT) to_chat(user, span_warning("The slime must be an adult to cross its core!")) return - if(M.effectmod && M.effectmod != effectmod) + if(target_slime.crossbreed_modification && target_slime.crossbreed_modification != crossbreed_modification) to_chat(user, span_warning("The slime is already being crossed with a different extract!")) return - if(!M.effectmod) - M.effectmod = effectmod + if(!target_slime.crossbreed_modification) + target_slime.crossbreed_modification = crossbreed_modification - M.applied++ + target_slime.applied_crossbreed_amount++ qdel(src) - to_chat(user, span_notice("You feed the slime [src], [M.applied == 1 ? "starting to mutate its core." : "further mutating its core."]")) - playsound(M, 'sound/effects/attackblob.ogg', 50, TRUE) + to_chat(user, span_notice("You feed the slime [src], [target_slime.applied_crossbreed_amount == 1 ? "starting to mutate its core." : "further mutating its core."]")) + playsound(target_slime, 'sound/effects/attackblob.ogg', 50, TRUE) - if(M.applied >= SLIME_EXTRACT_CROSSING_REQUIRED) - M.spawn_corecross() + if(target_slime.applied_crossbreed_amount >= SLIME_EXTRACT_CROSSING_REQUIRED) + target_slime.spawn_corecross() /obj/item/slime_extract/grey name = "grey slime extract" icon_state = "grey slime extract" - effectmod = "reproductive" + crossbreed_modification = "reproductive" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water) /obj/item/slime_extract/grey/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -105,7 +109,7 @@ if(SLIME_ACTIVATE_MAJOR) to_chat(user, span_notice("Your [name] starts pulsing...")) if(do_after(user, 40, target = user)) - var/mob/living/simple_animal/slime/S = new(get_turf(user), SLIME_TYPE_GREY) + var/mob/living/simple_animal/slime/S = new(get_turf(user), /datum/slime_type/grey) playsound(user, 'sound/effects/splat.ogg', 50, TRUE) to_chat(user, span_notice("You spit out [S].")) return 350 @@ -115,7 +119,7 @@ /obj/item/slime_extract/gold name = "gold slime extract" icon_state = "gold slime extract" - effectmod = "symbiont" + crossbreed_modification = "symbiont" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water) @@ -146,7 +150,7 @@ /obj/item/slime_extract/silver name = "silver slime extract" icon_state = "silver slime extract" - effectmod = "consuming" + crossbreed_modification = "consuming" activate_reagents = list(/datum/reagent/toxin/plasma,/datum/reagent/water) @@ -174,7 +178,7 @@ /obj/item/slime_extract/metal name = "metal slime extract" icon_state = "metal slime extract" - effectmod = "industrial" + crossbreed_modification = "industrial" activate_reagents = list(/datum/reagent/toxin/plasma,/datum/reagent/water) /obj/item/slime_extract/metal/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -198,7 +202,7 @@ /obj/item/slime_extract/purple name = "purple slime extract" icon_state = "purple slime extract" - effectmod = "regenerative" + crossbreed_modification = "regenerative" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma) /obj/item/slime_extract/purple/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -217,7 +221,7 @@ /obj/item/slime_extract/darkpurple name = "dark purple slime extract" icon_state = "dark purple slime extract" - effectmod = "self-sustaining" + crossbreed_modification = "self-sustaining" activate_reagents = list(/datum/reagent/toxin/plasma) /obj/item/slime_extract/darkpurple/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -240,7 +244,7 @@ /obj/item/slime_extract/orange name = "orange slime extract" icon_state = "orange slime extract" - effectmod = "burning" + crossbreed_modification = "burning" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water) /obj/item/slime_extract/orange/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -260,7 +264,7 @@ /obj/item/slime_extract/yellow name = "yellow slime extract" icon_state = "yellow slime extract" - effectmod = "charged" + crossbreed_modification = "charged" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water) /obj/item/slime_extract/yellow/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -283,7 +287,7 @@ /obj/item/slime_extract/red name = "red slime extract" icon_state = "red slime extract" - effectmod = "sanguine" + crossbreed_modification = "sanguine" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water) /obj/item/slime_extract/red/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -303,14 +307,13 @@ /obj/item/slime_extract/blue name = "blue slime extract" icon_state = "blue slime extract" - effectmod = "stabilized" + crossbreed_modification = "stabilized" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water) /obj/item/slime_extract/blue/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) switch(activation_type) if(SLIME_ACTIVATE_MINOR) to_chat(user, span_notice("You activate [src]. Your genome feels more stable!")) - user.adjustCloneLoss(-15) user.reagents.add_reagent(/datum/reagent/medicine/mutadone, 10) user.reagents.add_reagent(/datum/reagent/medicine/potass_iodide, 10) return 250 @@ -323,7 +326,7 @@ /obj/item/slime_extract/darkblue name = "dark blue slime extract" icon_state = "dark blue slime extract" - effectmod = "chilling" + crossbreed_modification = "chilling" activate_reagents = list(/datum/reagent/toxin/plasma,/datum/reagent/water) /obj/item/slime_extract/darkblue/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -346,7 +349,7 @@ /obj/item/slime_extract/pink name = "pink slime extract" icon_state = "pink slime extract" - effectmod = "gentle" + crossbreed_modification = "gentle" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma) /obj/item/slime_extract/pink/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -374,7 +377,7 @@ /obj/item/slime_extract/green name = "green slime extract" icon_state = "green slime extract" - effectmod = "mutative" + crossbreed_modification = "mutative" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/uranium/radium) /obj/item/slime_extract/green/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -398,7 +401,7 @@ /obj/item/slime_extract/lightpink name = "light pink slime extract" icon_state = "light pink slime extract" - effectmod = "loyal" + crossbreed_modification = "loyal" activate_reagents = list(/datum/reagent/toxin/plasma) /obj/item/slime_extract/lightpink/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -422,7 +425,7 @@ /obj/item/slime_extract/black name = "black slime extract" icon_state = "black slime extract" - effectmod = "transformative" + crossbreed_modification = "transformative" activate_reagents = list(/datum/reagent/toxin/plasma) /obj/item/slime_extract/black/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -443,7 +446,7 @@ /obj/item/slime_extract/oil name = "oil slime extract" icon_state = "oil slime extract" - effectmod = "detonating" + crossbreed_modification = "detonating" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma) /obj/item/slime_extract/oil/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -460,14 +463,14 @@ to_chat(user, span_userdanger("You explode!")) explosion(user, devastation_range = 1, heavy_impact_range = 3, light_impact_range = 6, explosion_cause = src) user.investigate_log("has been gibbed by an oil slime extract explosion.", INVESTIGATE_DEATHS) - user.gib() + user.gib(DROP_ALL_REMAINS) return to_chat(user, span_notice("You stop feeding [src], and the feeling passes.")) /obj/item/slime_extract/adamantine name = "adamantine slime extract" icon_state = "adamantine slime extract" - effectmod = "crystalline" + crossbreed_modification = "crystalline" activate_reagents = list(/datum/reagent/toxin/plasma) /obj/item/slime_extract/adamantine/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -497,7 +500,7 @@ /obj/item/slime_extract/bluespace name = "bluespace slime extract" icon_state = "bluespace slime extract" - effectmod = "warping" + crossbreed_modification = "warping" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma) var/teleport_ready = FALSE var/teleport_x = 0 @@ -533,7 +536,7 @@ /obj/item/slime_extract/pyrite name = "pyrite slime extract" icon_state = "pyrite slime extract" - effectmod = "prismatic" + crossbreed_modification = "prismatic" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma) /obj/item/slime_extract/pyrite/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -560,7 +563,7 @@ /obj/item/slime_extract/cerulean name = "cerulean slime extract" icon_state = "cerulean slime extract" - effectmod = "recurring" + crossbreed_modification = "recurring" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma) /obj/item/slime_extract/cerulean/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -580,7 +583,7 @@ /obj/item/slime_extract/sepia name = "sepia slime extract" icon_state = "sepia slime extract" - effectmod = "lengthened" + crossbreed_modification = "lengthened" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water) /obj/item/slime_extract/sepia/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -602,7 +605,7 @@ /obj/item/slime_extract/rainbow name = "rainbow slime extract" icon_state = "rainbow slime extract" - effectmod = "hyperchromatic" + crossbreed_modification = "hyperchromatic" activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,"lesser plasma",/datum/reagent/toxin/slimejelly,"holy water and uranium") //Curse this snowflake reagent list. /obj/item/slime_extract/rainbow/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type) @@ -668,7 +671,7 @@ M.rabid = FALSE qdel(src) return - M.docile = 1 + M.docile = TRUE M.set_nutrition(700) to_chat(M, span_warning("You absorb the potion and feel your intense desire to feed melt away.")) to_chat(user, span_notice("You feed the slime the potion, removing its hunger and calming it.")) @@ -705,19 +708,28 @@ balloon_alert(user, "offering...") being_used = TRUE - var/list/candidates = poll_candidates_for_mob("Do you want to play as [dumb_mob.name]?", ROLE_SENTIENCE, ROLE_SENTIENCE, 5 SECONDS, dumb_mob, POLL_IGNORE_SENTIENCE_POTION) // see poll_ignore.dm - if(!LAZYLEN(candidates)) + var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), user, dumb_mob) + dumb_mob.AddComponent(/datum/component/orbit_poll, \ + ignore_key = POLL_IGNORE_SENTIENCE_POTION, \ + job_bans = ROLE_SENTIENCE, \ + to_call = to_call, \ + ) + +/// Assign the chosen ghost to the mob +/obj/item/slimepotion/slime/sentience/proc/on_poll_concluded(mob/user, mob/living/dumb_mob, mob/dead/observer/ghost) + if(isnull(ghost)) balloon_alert(user, "try again later!") being_used = FALSE - return ..() + return - var/mob/dead/observer/C = pick(candidates) - dumb_mob.key = C.key + dumb_mob.key = ghost.key dumb_mob.mind.enslave_mind_to_creator(user) SEND_SIGNAL(dumb_mob, COMSIG_SIMPLEMOB_SENTIENCEPOTION, user) + if(isanimal(dumb_mob)) var/mob/living/simple_animal/smart_animal = dumb_mob smart_animal.sentience_act() + dumb_mob.mind.add_antag_datum(/datum/antagonist/sentient_creature) balloon_alert(user, "success") after_success(user, dumb_mob) @@ -792,22 +804,22 @@ icon = 'icons/obj/medical/chemical.dmi' icon_state = "potred" -/obj/item/slimepotion/slime/steroid/attack(mob/living/simple_animal/slime/M, mob/user) - if(!isslime(M))//If target is not a slime. +/obj/item/slimepotion/slime/steroid/attack(mob/living/simple_animal/slime/target, mob/user) + if(!isslime(target))//If target is not a slime. to_chat(user, span_warning("The steroid only works on baby slimes!")) return ..() - if(M.is_adult) //Can't steroidify adults + if(target.life_stage == SLIME_LIFE_STAGE_ADULT) //Can't steroidify adults to_chat(user, span_warning("Only baby slimes can use the steroid!")) return - if(M.stat) + if(target.stat) to_chat(user, span_warning("The slime is dead!")) return - if(M.cores >= 5) + if(target.cores >= 5) to_chat(user, span_warning("The slime already has the maximum amount of extract!")) return to_chat(user, span_notice("You feed the slime the steroid. It will now produce one more extract.")) - M.cores++ + target.cores++ qdel(src) /obj/item/slimepotion/enhancer @@ -1024,7 +1036,7 @@ throwforce = 10 throw_speed = 3 throw_range = 7 - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY max_amount = 60 turf_type = /turf/open/floor/bluespace merge_type = /obj/item/stack/tile/bluespace @@ -1041,7 +1053,7 @@ throwforce = 10 throw_speed = 0.1 throw_range = 28 - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY max_amount = 60 turf_type = /turf/open/floor/sepia merge_type = /obj/item/stack/tile/sepia @@ -1053,9 +1065,10 @@ /obj/item/areaeditor/blueprints/slime/edit_area() ..() - var/area/A = get_area(src) - for(var/turf/T in A) - T.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - T.add_atom_colour("#2956B2", FIXED_COLOUR_PRIORITY) - A.area_flags |= XENOBIOLOGY_COMPATIBLE + var/area/area = get_area(src) + for (var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists()) + for(var/turf/area_turf as anything in zlevel_turfs) + area_turf.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + area_turf.add_atom_colour("#2956B2", FIXED_COLOUR_PRIORITY) + area.area_flags |= XENOBIOLOGY_COMPATIBLE qdel(src) diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm index 66bf8b3bc93f3..210a1d7dc9d9b 100644 --- a/code/modules/security_levels/keycard_authentication.dm +++ b/code/modules/security_levels/keycard_authentication.dm @@ -55,11 +55,10 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26) /obj/machinery/keycard_auth/ui_status(mob/user) if(isdrone(user)) return UI_CLOSE - if(!isanimal(user)) + if(!isanimal_or_basicmob(user)) return ..() - var/mob/living/simple_animal/A = user - if(!A.dextrous) - to_chat(user, span_warning("You are too primitive to use this device!")) + if(!HAS_TRAIT(user, TRAIT_CAN_HOLD_ITEMS)) + balloon_alert(user, "no hands!") return UI_CLOSE return ..() @@ -161,21 +160,25 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26) GLOBAL_VAR_INIT(emergency_access, FALSE) /proc/make_maint_all_access() - for(var/area/station/maintenance/A in GLOB.areas) - for(var/turf/in_area as anything in A.get_contained_turfs()) - for(var/obj/machinery/door/airlock/D in in_area) - D.emergency = TRUE - D.update_icon(ALL, 0) + for(var/area/station/maintenance/area in GLOB.areas) + for (var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists()) + for(var/turf/area_turf as anything in zlevel_turfs) + for(var/obj/machinery/door/airlock/airlock in area_turf) + airlock.emergency = TRUE + airlock.update_icon(ALL, 0) + minor_announce("Access restrictions on maintenance and external airlocks have been lifted.", "Attention! Station-wide emergency declared!",1) GLOB.emergency_access = TRUE SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "enabled")) /proc/revoke_maint_all_access() - for(var/area/station/maintenance/A in GLOB.areas) - for(var/turf/in_area as anything in A.get_contained_turfs()) - for(var/obj/machinery/door/airlock/D in in_area) - D.emergency = FALSE - D.update_icon(ALL, 0) + for(var/area/station/maintenance/area in GLOB.areas) + for (var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists()) + for(var/turf/area_turf as anything in zlevel_turfs) + for(var/obj/machinery/door/airlock/airlock in area_turf) + airlock.emergency = FALSE + airlock.update_icon(ALL, 0) + minor_announce("Access restrictions in maintenance areas have been restored.", "Attention! Station-wide emergency rescinded:") GLOB.emergency_access = FALSE SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "disabled")) diff --git a/code/modules/security_levels/security_level_datums.dm b/code/modules/security_levels/security_level_datums.dm index 175b79d1c8771..b3402f643c6bf 100644 --- a/code/modules/security_levels/security_level_datums.dm +++ b/code/modules/security_levels/security_level_datums.dm @@ -9,6 +9,8 @@ /datum/security_level /// The name of this security level. var/name = "not set" + /// The color of our announcement divider. + var/announcement_color = "default" /// The numerical level of this security level, see defines for more information. var/number_level = -1 /// The sound that we will play when this security level is set @@ -22,7 +24,7 @@ /// Our announcement when lowering to this level var/lowering_to_announcement /// Our announcement when elevating to this level - var/elevating_to_announcemnt + var/elevating_to_announcement /// Our configuration key for lowering to text, if set, will override the default lowering to announcement. var/lowering_to_configuration_key /// Our configuration key for elevating to text, if set, will override the default elevating to announcement. @@ -33,7 +35,7 @@ if(lowering_to_configuration_key) // I'm not sure about you, but isn't there an easier way to do this? lowering_to_announcement = global.config.Get(lowering_to_configuration_key) if(elevating_to_configuration_key) - elevating_to_announcemnt = global.config.Get(elevating_to_configuration_key) + elevating_to_announcement = global.config.Get(elevating_to_configuration_key) /** * GREEN @@ -42,6 +44,7 @@ */ /datum/security_level/green name = "green" + announcement_color = "green" sound = 'sound/misc/notice2.ogg' // Friendly beep number_level = SEC_LEVEL_GREEN lowering_to_configuration_key = /datum/config_entry/string/alert_green @@ -54,6 +57,7 @@ */ /datum/security_level/blue name = "blue" + announcement_color = "blue" sound = 'sound/misc/notice1.ogg' // Angry alarm number_level = SEC_LEVEL_BLUE lowering_to_configuration_key = /datum/config_entry/string/alert_blue_downto @@ -67,6 +71,7 @@ */ /datum/security_level/red name = "red" + announcement_color = "red" sound = 'sound/misc/notice3.ogg' // More angry alarm number_level = SEC_LEVEL_RED lowering_to_configuration_key = /datum/config_entry/string/alert_red_downto @@ -80,6 +85,7 @@ */ /datum/security_level/delta name = "delta" + announcement_color = "purple" sound = 'sound/misc/airraid.ogg' // Air alarm to signify importance number_level = SEC_LEVEL_DELTA elevating_to_configuration_key = /datum/config_entry/string/alert_delta diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/arrivals.dm index 0ddf272081b6a..bd50a6b68958e 100644 --- a/code/modules/shuttle/arrivals.dm +++ b/code/modules/shuttle/arrivals.dm @@ -34,12 +34,14 @@ areas = list() var/list/new_latejoin = list() - for(var/area/shuttle/arrival/A in GLOB.areas) - for(var/obj/structure/chair/C in A) - new_latejoin += C - if(!console) - console = locate(/obj/machinery/requests_console) in A - areas += A + for(var/area/shuttle/arrival/arrival_area in GLOB.areas) + for (var/list/zlevel_turfs as anything in arrival_area.get_zlevel_turf_lists()) + for(var/turf/arrival_turf as anything in zlevel_turfs) + for(var/obj/structure/chair/shuttle_chair in arrival_turf) + new_latejoin += shuttle_chair + if(isnull(console)) + console = locate() in arrival_turf + areas += arrival_area if(SSjob.latejoin_trackers.len) log_mapping("Map contains predefined latejoin spawn points and an arrivals shuttle. Using the arrivals shuttle.") diff --git a/code/modules/shuttle/assault_pod.dm b/code/modules/shuttle/assault_pod.dm index 609ef685a3f80..3e76fc9671def 100644 --- a/code/modules/shuttle/assault_pod.dm +++ b/code/modules/shuttle/assault_pod.dm @@ -16,7 +16,7 @@ /obj/item/assault_pod name = "Assault Pod Targeting Device" - icon = 'icons/obj/device.dmi' + icon = 'icons/obj/devices/remote.dmi' icon_state = "gangtool-red" inhand_icon_state = "radio" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' diff --git a/code/modules/shuttle/battlecruiser_starfury.dm b/code/modules/shuttle/battlecruiser_starfury.dm index ab1f6802d43ee..e95ff4243f5d4 100644 --- a/code/modules/shuttle/battlecruiser_starfury.dm +++ b/code/modules/shuttle/battlecruiser_starfury.dm @@ -135,7 +135,7 @@ */ /proc/summon_battlecruiser(datum/team/battlecruiser/team) - var/list/candidates = poll_ghost_candidates("Do you wish to be considered for battlecruiser crew?", ROLE_TRAITOR) + var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for battlecruiser crew?", check_jobban = ROLE_TRAITOR, pic_source = /obj/machinery/sleeper/syndie, role_name_text = "battlecruiser crew") shuffle_inplace(candidates) var/datum/map_template/ship = SSmapping.map_templates["battlecruiser_starfury.dmm"] @@ -171,15 +171,11 @@ notify_ghosts( "The battlecruiser has an object of interest: [our_candidate]!", source = our_candidate, - action = NOTIFY_ORBIT, - header = "Something's Interesting!" - ) + ) else notify_ghosts( "The battlecruiser has an object of interest: [spawner]!", source = spawner, - action = NOTIFY_ORBIT, - header="Something's Interesting!" - ) + ) priority_announce("Unidentified armed ship detected near the station.") diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/computer.dm index 4065591582e26..cf53fef368c1b 100644 --- a/code/modules/shuttle/computer.dm +++ b/code/modules/shuttle/computer.dm @@ -13,7 +13,7 @@ icon_keyboard = "tech_key" light_color = LIGHT_COLOR_CYAN req_access = list() - interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON /// ID of the attached shuttle var/shuttleId /// Possible destinations of the attached shuttle diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm index 4a9feda1fc882..c93bd5e8c14e0 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -88,6 +88,11 @@ if(!isliving(usr)) return + var/area/my_area = get_area(src) + if(!istype(my_area, /area/shuttle/escape)) + say("Error - Network connectivity: Console has lost connection to the shuttle.") + return + var/mob/living/user = usr . = FALSE @@ -206,6 +211,10 @@ if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) to_chat(user, span_warning("You need your hands free before you can manipulate [src].")) return + var/area/my_area = get_area(src) + if(!istype(my_area, /area/shuttle/escape)) + say("Error - Network connectivity: Console has lost connection to the shuttle.") + return if(!user?.mind?.get_hijack_speed()) to_chat(user, span_warning("You manage to open a user-mode shell on [src], and hundreds of lines of debugging output fly through your vision. It is probably best to leave this alone.")) return @@ -358,7 +367,13 @@ else SSshuttle.emergency_last_call_loc = null - priority_announce("The emergency shuttle has been called. [red_alert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [timeLeft(600)] minutes.[reason][SSshuttle.emergency_last_call_loc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ][SSshuttle.admin_emergency_no_recall ? "\n\nWarning: Shuttle recall subroutines disabled; Recall not possible." : ""]", null, ANNOUNCER_SHUTTLECALLED, "Priority") + priority_announce( + text = "The emergency shuttle has been called. [red_alert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [(timeLeft(60 SECONDS))] minutes.[reason][SSshuttle.emergency_last_call_loc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ][SSshuttle.admin_emergency_no_recall ? "\n\nWarning: Shuttle recall subroutines disabled; Recall not possible." : ""]", + title = "Emergency Shuttle Dispatched", + sound = ANNOUNCER_SHUTTLECALLED, + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "orange", + ) /obj/docking_port/mobile/emergency/cancel(area/signalOrigin) if(mode != SHUTTLE_CALL) @@ -373,7 +388,13 @@ SSshuttle.emergency_last_call_loc = signalOrigin else SSshuttle.emergency_last_call_loc = null - priority_announce("The emergency shuttle has been recalled.[SSshuttle.emergency_last_call_loc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]", null, ANNOUNCER_SHUTTLERECALLED, "Priority") + priority_announce( + text = "The emergency shuttle has been recalled.[SSshuttle.emergency_last_call_loc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]", + title = "Emergency Shuttle Recalled", + sound = ANNOUNCER_SHUTTLERECALLED, + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "orange", + ) SSticker.emergency_reason = null @@ -462,7 +483,13 @@ mode = SHUTTLE_DOCKED setTimer(SSshuttle.emergency_dock_time) send2adminchat("Server", "The Emergency Shuttle has docked with the station.") - priority_announce("[SSshuttle.emergency] has docked with the station. You have [timeLeft(600)] minutes to board the Emergency Shuttle.", null, ANNOUNCER_SHUTTLEDOCK, "Priority") + priority_announce( + text = "[SSshuttle.emergency] has docked with the station. You have [DisplayTimeText(SSshuttle.emergency_dock_time)] to board the emergency shuttle.", + title = "Emergency Shuttle Arrival", + sound = ANNOUNCER_SHUTTLEDOCK, + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "orange", + ) ShuttleDBStuff() addtimer(CALLBACK(src, PROC_REF(announce_shuttle_events)), 20 SECONDS) @@ -514,7 +541,12 @@ mode = SHUTTLE_ESCAPE launch_status = ENDGAME_LAUNCHED setTimer(SSshuttle.emergency_escape_time * engine_coeff) - priority_announce("The Emergency Shuttle has left the station. Estimate [timeLeft(600)] minutes until the shuttle docks at Central Command.", null, null, "Priority") + priority_announce( + text = "The emergency shuttle has left the station. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].", + title = "Emergency Shuttle Departure", + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "orange", + ) INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts)) SSmapping.mapvote() //If no map vote has been run yet, start one. @@ -579,7 +611,12 @@ mode = SHUTTLE_ESCAPE launch_status = ENDGAME_LAUNCHED setTimer(SSshuttle.emergency_escape_time) - priority_announce("The Emergency Shuttle is preparing for direct jump. Estimate [timeLeft(600)] minutes until the shuttle docks at Central Command.", null, null, "Priority") + priority_announce( + text = "The emergency shuttle is preparing for direct jump. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].", + title = "Emergency Shuttle Transit Failure", + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "orange", + ) ///Generate a list of events to run during the departure /obj/docking_port/mobile/emergency/proc/setup_shuttle_events() @@ -708,7 +745,7 @@ return INITIALIZE_HINT_QDEL /obj/docking_port/stationary/random/icemoon - target_area = /area/icemoon/surface/outdoors + target_area = /area/icemoon/surface/outdoors/unexplored/rivers/no_monsters //Pod suits/pickaxes diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm index 03777cd2d5b54..f8b460a783d0d 100644 --- a/code/modules/shuttle/navigation_computer.dm +++ b/code/modules/shuttle/navigation_computer.dm @@ -95,19 +95,19 @@ var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj the_eye.setDir(shuttle_port.dir) var/turf/origin = locate(shuttle_port.x + x_offset, shuttle_port.y + y_offset, shuttle_port.z) - for(var/V in shuttle_port.shuttle_areas) - var/area/A = V - for(var/turf/T in A) - if(T.z != origin.z) - continue - var/image/I = image('icons/effects/alphacolors.dmi', origin, "red") - var/x_off = T.x - origin.x - var/y_off = T.y - origin.y - I.loc = locate(origin.x + x_off, origin.y + y_off, origin.z) //we have to set this after creating the image because it might be null, and images created in nullspace are immutable. - I.layer = ABOVE_NORMAL_TURF_LAYER - SET_PLANE(I, ABOVE_GAME_PLANE, T) - I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - the_eye.placement_images[I] = list(x_off, y_off) + for(var/area/shuttle_area as anything in shuttle_port.shuttle_areas) + for (var/list/zlevel_turfs as anything in shuttle_area.get_zlevel_turf_lists()) + for(var/turf/shuttle_turf as anything in zlevel_turfs) + if(shuttle_turf.z != origin.z) + continue + var/image/I = image('icons/effects/alphacolors.dmi', origin, "red") + var/x_off = shuttle_turf.x - origin.x + var/y_off = shuttle_turf.y - origin.y + I.loc = locate(origin.x + x_off, origin.y + y_off, origin.z) //we have to set this after creating the image because it might be null, and images created in nullspace are immutable. + I.layer = ABOVE_NORMAL_TURF_LAYER + SET_PLANE(I, ABOVE_GAME_PLANE, shuttle_turf) + I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + the_eye.placement_images[I] = list(x_off, y_off) /obj/machinery/computer/camera_advanced/shuttle_docker/give_eye_control(mob/user) ..() @@ -186,7 +186,7 @@ if(current_user.client) current_user.client.images -= the_eye.placed_images - QDEL_LIST(the_eye.placed_images) + LAZYCLEARLIST(the_eye.placed_images) for(var/image/place_spots as anything in the_eye.placement_images) var/image/newI = image('icons/effects/alphacolors.dmi', the_eye.loc, "blue") @@ -303,8 +303,8 @@ /mob/camera/ai_eye/remote/shuttle_docker visible_icon = FALSE use_static = FALSE - var/list/placement_images = list() - var/list/placed_images = list() + var/list/image/placement_images = list() + var/list/image/placed_images = list() /mob/camera/ai_eye/remote/shuttle_docker/Initialize(mapload, obj/machinery/computer/camera_advanced/origin) src.origin = origin diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm index 89fc0fc6c8ed3..c65a9ad123a59 100644 --- a/code/modules/shuttle/on_move.dm +++ b/code/modules/shuttle/on_move.dm @@ -35,7 +35,7 @@ All ShuttleMove procs go here SSblackbox.record_feedback("tally", "shuttle_gib", 1, M.type) log_shuttle("[key_name(M)] was shuttle gibbed by [shuttle].") M.investigate_log("has been gibbed by [shuttle].", INVESTIGATE_DEATHS) - M.gib() + M.gib(DROP_ALL_REMAINS) else //non-living mobs shouldn't be affected by shuttles, which is why this is an else diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index df4f82424522a..2088e8f3234c8 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -6,7 +6,7 @@ //NORTH default dir /obj/docking_port invisibility = INVISIBILITY_ABSTRACT - icon = 'icons/obj/device.dmi' + icon = 'icons/obj/devices/tracker.dmi' icon_state = "pinonfar" resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF @@ -149,7 +149,7 @@ #ifdef DOCKING_PORT_HIGHLIGHT //Debug proc used to highlight bounding area /obj/docking_port/proc/highlight(_color = "#f00") - invisibility = 0 + SetInvisibility(INVISIBILITY_NONE) SET_PLANE_IMPLICIT(src, GHOST_PLANE) var/list/L = return_coords() var/turf/T0 = locate(L[1],L[2],z) @@ -239,6 +239,9 @@ for(var/turf/T in return_turfs()) T.turf_flags |= NO_RUINS + if(SSshuttle.initialized) + INVOKE_ASYNC(SSshuttle, TYPE_PROC_REF(/datum/controller/subsystem/shuttle, setup_shuttles), list(src)) + #ifdef DOCKING_PORT_HIGHLIGHT highlight("#f00") #endif @@ -511,13 +514,14 @@ var/min_y = -1 var/max_x = WORLDMAXX_CUTOFF var/max_y = WORLDMAXY_CUTOFF - for(var/area/area as anything in shuttle_areas) - for(var/turf/turf in area) - min_x = max(turf.x, min_x) - max_x = min(turf.x, max_x) - min_y = max(turf.y, min_y) - max_y = min(turf.y, max_y) - CHECK_TICK + for(var/area/shuttle_area as anything in shuttle_areas) + for (var/list/zlevel_turfs as anything in shuttle_area.get_zlevel_turf_lists()) + for(var/turf/turf as anything in zlevel_turfs) + min_x = max(turf.x, min_x) + max_x = min(turf.x, max_x) + min_y = max(turf.y, min_y) + max_y = min(turf.y, max_y) + CHECK_TICK if(min_x == -1 || max_x == WORLDMAXX_CUTOFF) CRASH("Failed to locate shuttle boundaries when iterating through shuttle areas, somehow.") @@ -817,12 +821,10 @@ var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir) var/list/ripple_turfs = list() - - for(var/i in 1 to L0.len) + var/stop = min(L0.len, L1.len) + for(var/i in 1 to stop) var/turf/T0 = L0[i] var/turf/T1 = L1[i] - if(!T0 || !T1) - continue // out of bounds if(!istype(T0.loc, area_type) || istype(T0.loc, /area/shuttle/transit)) continue // not part of the shuttle ripple_turfs += T1 diff --git a/code/modules/shuttle/shuttle_events/player_controlled.dm b/code/modules/shuttle/shuttle_events/player_controlled.dm index 40bc29f80175e..77fee390a6876 100644 --- a/code/modules/shuttle/shuttle_events/player_controlled.dm +++ b/code/modules/shuttle/shuttle_events/player_controlled.dm @@ -17,7 +17,7 @@ /// Attempt to grant control of a mob to ghosts before spawning it in. if spawn_anyway_if_no_player = TRUE, we spawn the mob even if there's no ghosts /datum/shuttle_event/simple_spawner/player_controlled/proc/try_grant_ghost_control(spawn_type) - var/list/candidates = poll_ghost_candidates(ghost_alert_string + " (Warning: you will not be able to return to your body!)", role_type, FALSE, 10 SECONDS) + var/list/candidates = SSpolling.poll_ghost_candidates(ghost_alert_string + " (Warning: you will not be able to return to your body!)", check_jobban = role_type, poll_time = 10 SECONDS, pic_source = spawn_type, role_name_text = "shot at shuttle") if(!candidates.len && !spawn_anyway_if_no_player) return diff --git a/code/modules/shuttle/shuttle_events/turbulence.dm b/code/modules/shuttle/shuttle_events/turbulence.dm new file mode 100644 index 0000000000000..bbc136397c2a0 --- /dev/null +++ b/code/modules/shuttle/shuttle_events/turbulence.dm @@ -0,0 +1,48 @@ +/// Repeat the "buckle in or fall over" event a couple times +/datum/shuttle_event/turbulence + name = "Turbulence" + event_probability = 5 + activation_fraction = 0.1 + /// Minimum time to wait between periods of turbulence + var/minimum_interval = 20 SECONDS + /// Maximum time to wait between periods of turbulence + var/maximum_interval = 50 SECONDS + /// Time until we should shake again + COOLDOWN_DECLARE(turbulence_cooldown) + /// How long do we give people to get buckled? + var/warning_interval = 2 SECONDS + +/datum/shuttle_event/turbulence/activate() + . = ..() + minor_announce("Please note, we are entering an area of subspace turbulence. For your own safety, \ + please fasten your belts and remain seated until the vehicle comes to a complete stop.", + title = "Emergency Shuttle", alert = TRUE) + COOLDOWN_START(src, turbulence_cooldown, rand(5 SECONDS, 20 SECONDS)) // Reduced interval after the announcement + +/datum/shuttle_event/turbulence/event_process() + . = ..() + if (!.) + return + if (!COOLDOWN_FINISHED(src, turbulence_cooldown)) + return + COOLDOWN_START(src, turbulence_cooldown, rand(minimum_interval, maximum_interval)) + shake() + addtimer(CALLBACK(src, PROC_REF(knock_down)), warning_interval, TIMER_DELETE_ME) + +/// Warn players to get buckled +/datum/shuttle_event/turbulence/proc/shake() + var/list/mobs = mobs_in_area_type(list(/area/shuttle/escape)) + for(var/mob/living/mob as anything in mobs) + var/shake_intensity = mob.buckled ? 0.25 : 1 + if(mob.client) + shake_camera(mob, 3 SECONDS, shake_intensity) + +/// Knock them down +/datum/shuttle_event/turbulence/proc/knock_down() + if (SSshuttle.emergency.mode != SHUTTLE_ESCAPE) + return // They docked + var/list/mobs = mobs_in_area_type(list(/area/shuttle/escape)) // Not very efficient but check again in case someone was outdoors + for(var/mob/living/mob as anything in mobs) + if(mob.buckled) + continue + mob.Paralyze(3 SECONDS, ignore_canstun = TRUE) diff --git a/code/modules/shuttle/spaceship_navigation_beacon.dm b/code/modules/shuttle/spaceship_navigation_beacon.dm index 4ae91af588a23..2c0b95239b604 100644 --- a/code/modules/shuttle/spaceship_navigation_beacon.dm +++ b/code/modules/shuttle/spaceship_navigation_beacon.dm @@ -5,7 +5,7 @@ icon_state = "beacon_active" base_icon_state = "beacon" density = TRUE - flags_1 = NODECONSTRUCT_1 + obj_flags = parent_type::obj_flags | NO_DECONSTRUCTION /// Locked beacons cannot be jumped to by ships. var/locked = FALSE @@ -101,7 +101,7 @@ /obj/item/folded_navigation_gigabeacon/Initialize(mapload) . = ..() - AddComponent(/datum/component/deployable, 3 SECONDS, /obj/machinery/spaceship_navigation_beacon, delete_on_use = TRUE) + AddComponent(/datum/component/deployable, 3 SECONDS, /obj/machinery/spaceship_navigation_beacon) /obj/item/folded_navigation_gigabeacon/examine() .=..() diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm index 5c1e21c56fb4e..e5b8d5e87ff8a 100644 --- a/code/modules/shuttle/special.dm +++ b/code/modules/shuttle/special.dm @@ -79,7 +79,7 @@ var/obj/machinery/power/emitter/energycannon/magical/our_statue var/list/mob/living/sleepers = list() var/never_spoken = TRUE - flags_1 = NODECONSTRUCT_1 + obj_flags = parent_type::obj_flags | NO_DECONSTRUCTION /obj/structure/table/abductor/wabbajack/Initialize(mapload) . = ..() @@ -90,17 +90,14 @@ . = ..() /obj/structure/table/abductor/wabbajack/process() - var/area = orange(4, src) - if(!our_statue) - for(var/obj/machinery/power/emitter/energycannon/magical/M in area) - our_statue = M - break + if(isnull(our_statue)) + our_statue = locate() in orange(4, src) - if(!our_statue) + if(isnull(our_statue)) name = "inert [initial(name)]" return - else - name = initial(name) + + name = initial(name) var/turf/T = get_turf(src) var/list/found = list() @@ -154,7 +151,7 @@ // Bar staff, GODMODE mobs(as long as they stay in the shuttle) that just want to make sure people have drinks // and a good time. -/mob/living/simple_animal/drone/snowflake/bardrone +/mob/living/basic/drone/snowflake/bardrone name = "Bardrone" desc = "A barkeeping drone, a robot built to tend bars." hacked = TRUE @@ -166,9 +163,8 @@ initial_language_holder = /datum/language_holder/universal default_storage = null -/mob/living/simple_animal/drone/snowflake/bardrone/Initialize(mapload) +/mob/living/basic/drone/snowflake/bardrone/Initialize(mapload) . = ..() - access_card.add_access(list(ACCESS_CENT_BAR)) AddComponentFrom(ROUNDSTART_TRAIT, /datum/component/area_based_godmode, area_type = /area/shuttle/escape, allow_area_subtypes = TRUE) /mob/living/simple_animal/hostile/alien/maid/barmaid @@ -202,7 +198,7 @@ /obj/structure/table/wood/shuttle_bar resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - flags_1 = NODECONSTRUCT_1 + obj_flags = parent_type::obj_flags | NO_DECONSTRUCTION max_integrity = 1000 var/boot_dir = 1 @@ -230,6 +226,9 @@ if(is_bartender_job(human_user.mind?.assigned_role)) return TRUE + if(istype(user, /mob/living/basic/drone/snowflake/bardrone)) + return TRUE + var/obj/item/card/id/ID = user.get_idcard(FALSE) if(ID && (ACCESS_CENT_BAR in ID.access)) return TRUE diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm index 4f8488e1ff079..6e946cca05c4c 100644 --- a/code/modules/shuttle/supply.dm +++ b/code/modules/shuttle/supply.dm @@ -1,38 +1,38 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( /mob/living, - /obj/structure/blob, - /obj/effect/rune, - /obj/item/disk/nuclear, - /obj/machinery/nuclearbomb, - /obj/item/beacon, - /obj/narsie, - /obj/tear_in_reality, - /obj/machinery/teleport/station, - /obj/machinery/teleport/hub, - /obj/machinery/quantumpad, - /obj/effect/mob_spawn, + /obj/docking_port, /obj/effect/hierophant, - /obj/structure/receiving_pad, - /obj/item/warp_cube, - /obj/machinery/rnd/production, //print tracking beacons, send shuttle - /obj/machinery/autolathe, //same - /obj/projectile/beam/wormhole, + /obj/effect/mob_spawn, /obj/effect/portal, - /obj/item/shared_storage, - /obj/structure/extraction_point, - /obj/machinery/syndicatebomb, + /obj/effect/rune, + /obj/item/beacon, + /obj/item/disk/nuclear, + /obj/item/gps, /obj/item/hilbertshotel, - /obj/item/swapper, - /obj/docking_port, - /obj/machinery/launchpad, - /obj/machinery/exodrone_launcher, - /obj/machinery/disposal, - /obj/structure/disposalpipe, /obj/item/mail, + /obj/item/shared_storage, + /obj/item/swapper, + /obj/item/warp_cube, + /obj/machinery/autolathe, // In case you manage to get it to print a beacon while in transit /obj/machinery/camera, - /obj/item/gps, + /obj/machinery/disposal, + /obj/machinery/exodrone_launcher, + /obj/machinery/fax, + /obj/machinery/launchpad, + /obj/machinery/nuclearbomb, + /obj/machinery/quantumpad, + /obj/machinery/rnd/production, + /obj/machinery/syndicatebomb, + /obj/machinery/teleport/hub, + /obj/machinery/teleport/station, + /obj/narsie, + /obj/projectile/beam/wormhole, + /obj/structure/blob, /obj/structure/checkoutmachine, - /obj/machinery/fax + /obj/structure/disposalpipe, + /obj/structure/extraction_point, + /obj/structure/guardian_beacon, + /obj/tear_in_reality, ))) /// How many goody orders we can fit in a lockbox before we upgrade to a crate @@ -59,19 +59,64 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( return ..() /obj/docking_port/mobile/supply/proc/check_blacklist(areaInstances) - for(var/place in areaInstances) - var/area/shuttle/shuttle_area = place - for(var/turf/shuttle_turf in shuttle_area) - for(var/atom/passenger in shuttle_turf.get_all_contents()) - if((is_type_in_typecache(passenger, GLOB.blacklisted_cargo_types) || HAS_TRAIT(passenger, TRAIT_BANNED_FROM_CARGO_SHUTTLE)) && !istype(passenger, /obj/docking_port)) - return FALSE + for(var/area/shuttle_area as anything in areaInstances) + for (var/list/zlevel_turfs as anything in shuttle_area.get_zlevel_turf_lists()) + for(var/turf/shuttle_turf as anything in zlevel_turfs) + for(var/atom/passenger in shuttle_turf.get_all_contents()) + if((is_type_in_typecache(passenger, GLOB.blacklisted_cargo_types) || HAS_TRAIT(passenger, TRAIT_BANNED_FROM_CARGO_SHUTTLE)) && !istype(passenger, /obj/docking_port)) + return FALSE return TRUE +/// Returns anything on the cargo blacklist found within areas_to_check back to the turf of the home docking port via Centcom branded supply pod. +/obj/docking_port/mobile/supply/proc/return_blacklisted_things_home(list/area/areas_to_check, obj/docking_port/stationary/home) + var/list/stuff_to_send_home = list() + for(var/area/shuttle_area as anything in areas_to_check) + for (var/list/zlevel_turfs as anything in shuttle_area.get_zlevel_turf_lists()) + for(var/turf/shuttle_turf as anything in zlevel_turfs) + for(var/atom/passenger in shuttle_turf.get_all_contents()) + if((is_type_in_typecache(passenger, GLOB.blacklisted_cargo_types) || HAS_TRAIT(passenger, TRAIT_BANNED_FROM_CARGO_SHUTTLE)) && !istype(passenger, /obj/docking_port)) + stuff_to_send_home += passenger + + if(!length(stuff_to_send_home)) + return FALSE + + var/obj/structure/closet/supplypod/centcompod/et_go_home = new() + + for(var/atom/movable/et as anything in stuff_to_send_home) + et.forceMove(et_go_home) + + new /obj/effect/pod_landingzone(get_turf(home), et_go_home) + + return stuff_to_send_home + /obj/docking_port/mobile/supply/request(obj/docking_port/stationary/S) if(mode != SHUTTLE_IDLE) return 2 return ..() +/obj/docking_port/mobile/supply/check_dock(obj/docking_port/stationary/S, silent) + . = ..() + + if(!.) + return + + // If we're not trying to dock at Centcom, we don't care. + if(S.shuttle_id != "cargo_away") + return + + // Else we are docking at Centcom, check the blacklist to make sure no contraband was put onto the shuttle mid-transit. + // If there's anything contrabandy, send these items back to the origin docking port. + // This is a sort of catch-all Centcom exploit check. + var/list/stuff_sent_home = return_blacklisted_things_home(shuttle_areas, previous) + if(!length(stuff_sent_home)) + return + + for(var/atom/thing_sent_home as anything in stuff_sent_home) + investigate_log("Blacklisted item found on in-transit Cargo Shuttle: [thing_sent_home] ([thing_sent_home.type])", INVESTIGATE_CARGO) + + message_admins("Blacklisted item found on in-transit Cargo Shuttle. See cargo logs for more details.") + SSshuttle.centcom_message = "Contraband found on Cargo Shuttle. This has been returned via drop pod." + /obj/docking_port/mobile/supply/initiate_docking() if(getDockedId() == "cargo_away") // Buy when we leave home. buy() @@ -91,7 +136,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( var/list/empty_turfs = list() for(var/area/shuttle/shuttle_area as anything in shuttle_areas) - for(var/turf/open/floor/shuttle_turf in shuttle_area) + for(var/turf/open/floor/shuttle_turf in shuttle_area.get_turfs_from_all_zlevels()) if(shuttle_turf.is_blocked_turf()) continue empty_turfs += shuttle_turf @@ -113,52 +158,52 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( var/value = 0 var/purchases = 0 + var/price + var/pack_cost var/list/goodies_by_buyer = list() // if someone orders more than GOODY_FREE_SHIPPING_MAX goodies, we upcharge to a normal crate so they can't carry around 20 combat shotties + var/list/clean_up_orders = list() // orders to remove since we are done with them for(var/datum/supply_order/spawning_order in SSshuttle.shopping_list) if(!empty_turfs.len) break - var/price = spawning_order.pack.get_cost() - if(spawning_order.applied_coupon) - price *= (1 - spawning_order.applied_coupon.discount_pct_off) + price = spawning_order.get_final_cost() + // department orders EARN money for cargo, not the other way around var/datum/bank_account/paying_for_this - - //department orders EARN money for cargo, not the other way around if(!spawning_order.department_destination && spawning_order.charge_on_purchase) if(spawning_order.paying_account) //Someone paid out of pocket paying_for_this = spawning_order.paying_account - var/list/current_buyer_orders = goodies_by_buyer[spawning_order.paying_account] // so we can access the length a few lines down - if(!spawning_order.pack.goody) - price *= 1.1 //TODO make this customizable by the quartermaster - // note this is before we increment, so this is the GOODY_FREE_SHIPPING_MAX + 1th goody to ship. also note we only increment off this step if they successfully pay the fee, so there's no way around it - else if(LAZYLEN(current_buyer_orders) == GOODY_FREE_SHIPPING_MAX) - price += CRATE_TAX - paying_for_this.bank_card_talk("Goody order size exceeds free shipping limit: Assessing [CRATE_TAX] credit S&H fee.") + if(spawning_order.pack.goody) + var/list/current_buyer_orders = goodies_by_buyer[spawning_order.paying_account] + if(LAZYLEN(current_buyer_orders) == GOODY_FREE_SHIPPING_MAX) + price = round(price + CRATE_TAX) + paying_for_this.bank_card_talk("Goody order size exceeds free shipping limit: Assessing [CRATE_TAX] credit S&H fee.") else paying_for_this = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(paying_for_this) if(!paying_for_this.adjust_money(-price, "Cargo: [spawning_order.pack.name]")) if(spawning_order.paying_account) paying_for_this.bank_card_talk("Cargo order #[spawning_order.id] rejected due to lack of funds. Credits required: [price]") + if(!spawning_order.can_be_cancelled) //only if it absolutly cannot be canceled by the player do we cancel it for them + SSshuttle.shopping_list -= spawning_order + clean_up_orders += spawning_order continue + pack_cost = spawning_order.pack.get_cost() if(spawning_order.paying_account) paying_for_this = spawning_order.paying_account if(spawning_order.pack.goody) LAZYADD(goodies_by_buyer[spawning_order.paying_account], spawning_order) - var/reciever_message = "Cargo order #[spawning_order.id] has shipped." + var/receiver_message = "Cargo order #[spawning_order.id] has shipped." if(spawning_order.charge_on_purchase) - reciever_message += " [price] credits have been charged to your bank account" - paying_for_this.bank_card_talk(reciever_message) + receiver_message += " [price] credits have been charged to your bank account" + paying_for_this.bank_card_talk(receiver_message) SSeconomy.track_purchase(paying_for_this, price, spawning_order.pack.name) var/datum/bank_account/department/cargo = SSeconomy.get_dep_account(ACCOUNT_CAR) - cargo.adjust_money(price - spawning_order.pack.get_cost()) //Cargo gets the handling fee - value += spawning_order.pack.get_cost() - SSshuttle.shopping_list -= spawning_order - SSshuttle.order_history += spawning_order - QDEL_NULL(spawning_order.applied_coupon) + cargo.adjust_money(price - pack_cost) //Cargo gets the handling fee + value += pack_cost if(!spawning_order.pack.goody) //we handle goody crates below var/obj/structure/closet/crate = spawning_order.generate(pick_n_take(empty_turfs)) @@ -173,6 +218,10 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( message_admins("\A [spawning_order.pack.name] ordered by [ADMIN_LOOKUPFLW(spawning_order.orderer_ckey)], paid by [from_whom] has shipped.") purchases++ + // done dealing with order. Time to remove & delete it + SSshuttle.shopping_list -= spawning_order + clean_up_orders += spawning_order + // we handle packing all the goodies last, since the type of crate we use depends on how many goodies they ordered. If it's more than GOODY_FREE_SHIPPING_MAX // then we send it in a crate (including the CRATE_TAX cost), otherwise send it in a free shipping case for(var/buyer_key in goodies_by_buyer) @@ -204,7 +253,10 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( order.generateCombo(miscboxes[miscbox], miscbox, misc_contents[miscbox], misc_costs[miscbox]) qdel(order) - SSeconomy.import_total += value + //clean up all dealt with orders + for(var/datum/supply_order/completed_order in clean_up_orders) + qdel(completed_order) + var/datum/bank_account/cargo_budget = SSeconomy.get_dep_account(ACCOUNT_CAR) investigate_log("[purchases] orders in this shipment, worth [value] credits. [cargo_budget.account_balance] credits left.", INVESTIGATE_CARGO) @@ -221,12 +273,14 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( var/datum/export_report/report = new for(var/area/shuttle/shuttle_area as anything in shuttle_areas) - for(var/atom/movable/exporting_atom in shuttle_area) - if(iscameramob(exporting_atom)) - continue - if(exporting_atom.anchored) - continue - export_item_and_contents(exporting_atom, apply_elastic = TRUE, dry_run = FALSE, external_report = report) + for (var/list/zlevel_turfs as anything in shuttle_area.get_zlevel_turf_lists()) + for(var/turf/shuttle_turf as anything in zlevel_turfs) + for(var/atom/movable/exporting_atom in shuttle_turf) + if(iscameramob(exporting_atom)) + continue + if(exporting_atom.anchored) + continue + export_item_and_contents(exporting_atom, apply_elastic = TRUE, dry_run = FALSE, external_report = report) if(report.exported_atoms) report.exported_atoms += "." //ugh @@ -239,14 +293,13 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( msg += export_text + "\n" cargo_budget.adjust_money(report.total_value[exported_datum]) - SSeconomy.export_total += (cargo_budget.account_balance - presale_points) SSshuttle.centcom_message = msg investigate_log("contents sold for [cargo_budget.account_balance - presale_points] credits. Contents: [report.exported_atoms ? report.exported_atoms.Join(",") + "." : "none."] Message: [SSshuttle.centcom_message || "none."]", INVESTIGATE_CARGO) /* Generates a box of mail depending on our exports and imports. Applied in the cargo shuttle sending/arriving, by building the crate if the round is ready to introduce mail based on the economy subsystem. - Then, fills the mail crate with mail, by picking applicable crew who can recieve mail at the time to sending. + Then, fills the mail crate with mail, by picking applicable crew who can receive mail at the time to sending. */ /obj/docking_port/mobile/supply/proc/create_mail() //Early return if there's no mail waiting to prevent taking up a slot. We also don't send mails on sundays or holidays. @@ -255,9 +308,8 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( //spawn crate var/list/empty_turfs = list() - for(var/place as anything in shuttle_areas) - var/area/shuttle/shuttle_area = place - for(var/turf/open/floor/shuttle_floor in shuttle_area) + for(var/area/shuttle/shuttle_area as anything in shuttle_areas) + for(var/turf/open/floor/shuttle_floor in shuttle_area.get_turfs_from_all_zlevels()) if(shuttle_floor.is_blocked_turf()) continue empty_turfs += shuttle_floor diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/syndicate.dm index dcb8748bfcba5..f08446b650da3 100644 --- a/code/modules/shuttle/syndicate.dm +++ b/code/modules/shuttle/syndicate.dm @@ -11,7 +11,7 @@ shuttleId = "syndicate" possible_destinations = "syndicate_away;syndicate_z5;syndicate_ne;syndicate_nw;syndicate_n;syndicate_se;syndicate_sw;syndicate_s;syndicate_custom" resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - flags_1 = NODECONSTRUCT_1 + obj_flags = parent_type::obj_flags | NO_DECONSTRUCTION /obj/machinery/computer/shuttle/syndicate/launch_check(mob/user) . = ..() diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index d9336291cf826..dc30f36149bc6 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -48,7 +48,7 @@ button_icon_state = "spell_default" overlay_icon_state = "bg_spell_border" active_overlay_icon_state = "bg_spell_border_active_red" - check_flags = AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_PHASED panel = "Spells" melee_cooldown_time = 0 SECONDS @@ -140,7 +140,7 @@ // Where the cast chain starts /datum/action/cooldown/spell/PreActivate(atom/target) - if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START) + if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src, target) & COMPONENT_BLOCK_ABILITY_START) return FALSE if(target == owner) target = get_caster_from_target(target) @@ -180,11 +180,6 @@ to_chat(owner, span_warning("Some form of antimagic is preventing you from casting [src]!")) return FALSE - if(!(spell_requirements & SPELL_CASTABLE_WHILE_PHASED) && HAS_TRAIT(owner, TRAIT_MAGICALLY_PHASED)) - if(feedback) - to_chat(owner, span_warning("[src] cannot be cast unless you are completely manifested in the material plane!")) - return FALSE - if(!try_invoke(owner, feedback = feedback)) return FALSE diff --git a/code/modules/spells/spell_types/conjure/_conjure.dm b/code/modules/spells/spell_types/conjure/_conjure.dm index 3afe7c5255754..10b14bd47d55d 100644 --- a/code/modules/spells/spell_types/conjure/_conjure.dm +++ b/code/modules/spells/spell_types/conjure/_conjure.dm @@ -44,7 +44,7 @@ if (spawn_place.overfloor_placed) spawn_place.ChangeTurf(summoned_object_type, flags = CHANGETURF_INHERIT_AIR) else - spawn_place.PlaceOnTop(summoned_object_type, flags = CHANGETURF_INHERIT_AIR) + spawn_place.place_on_top(summoned_object_type, flags = CHANGETURF_INHERIT_AIR) return var/turf/open/open_turf = spawn_place open_turf.replace_floor(summoned_object_type, flags = CHANGETURF_INHERIT_AIR) @@ -61,3 +61,28 @@ /// Called on atoms summoned after they are created, allows extra variable editing and such of created objects /datum/action/cooldown/spell/conjure/proc/post_summon(atom/summoned_object, atom/cast_on) return + +///limits the amount of summons +/datum/action/cooldown/spell/conjure/limit_summons + ///max number of after images + var/max_summons + ///How many clones do we have summoned + var/number_of_summons = 0 + +/datum/action/cooldown/spell/conjure/limit_summons/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(number_of_summons >= max_summons) + return FALSE + return TRUE + +/datum/action/cooldown/spell/conjure/limit_summons/post_summon(atom/summoned_object, atom/cast_on) + RegisterSignals(summoned_object, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(delete_copy)) + number_of_summons++ + +/datum/action/cooldown/spell/conjure/limit_summons/proc/delete_copy(datum/source) + SIGNAL_HANDLER + + UnregisterSignal(source, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH)) + number_of_summons-- diff --git a/code/modules/spells/spell_types/conjure/presents.dm b/code/modules/spells/spell_types/conjure/presents.dm index 057fef9b9b4a8..b630c43225c47 100644 --- a/code/modules/spells/spell_types/conjure/presents.dm +++ b/code/modules/spells/spell_types/conjure/presents.dm @@ -10,5 +10,5 @@ invocation_type = INVOCATION_SHOUT summon_radius = 3 - summon_type = list(/obj/item/a_gift) + summon_type = list(/obj/item/gift) summon_amount = 5 diff --git a/code/modules/spells/spell_types/conjure/simian.dm b/code/modules/spells/spell_types/conjure/simian.dm index 556a78e50127c..aa9aabc681009 100644 --- a/code/modules/spells/spell_types/conjure/simian.dm +++ b/code/modules/spells/spell_types/conjure/simian.dm @@ -14,14 +14,18 @@ invocation_type = INVOCATION_SHOUT summon_radius = 2 - summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/carbon/human/species/monkey/angry, /mob/living/simple_animal/hostile/gorilla/lesser) + summon_type = list( + /mob/living/basic/gorilla/lesser, + /mob/living/carbon/human/species/monkey/angry, + /mob/living/carbon/human/species/monkey/angry, // Listed twice so it's twice as likely, this class doesn't use pick weight + ) summon_amount = 4 /datum/action/cooldown/spell/conjure/simian/level_spell(bypass_cap) . = ..() summon_amount++ // MORE, MOOOOORE if(spell_level == spell_max_level) // We reward the faithful. - summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/simple_animal/hostile/gorilla) + summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/basic/gorilla) spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC // Max level lets you cast it naked, for monkey larp. to_chat(owner, span_notice("Your simian power has reached maximum capacity! You can now cast this spell naked, and you will create adult Gorillas with each cast.")) diff --git a/code/modules/spells/spell_types/conjure_item/lighting_packet.dm b/code/modules/spells/spell_types/conjure_item/lighting_packet.dm index cffddf7e0ce94..2df0c85f470e1 100644 --- a/code/modules/spells/spell_types/conjure_item/lighting_packet.dm +++ b/code/modules/spells/spell_types/conjure_item/lighting_packet.dm @@ -33,7 +33,7 @@ hit_living.electrocute_act(80, src, flags = SHOCK_ILLUSION | SHOCK_NOGLOVES) qdel(src) -/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, quickstart = TRUE) +/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, gentle, quickstart = TRUE) . = ..() if(ishuman(thrower)) var/mob/living/carbon/human/human_thrower = thrower diff --git a/code/modules/spells/spell_types/jaunt/_jaunt.dm b/code/modules/spells/spell_types/jaunt/_jaunt.dm index 4a94f03c0410a..207a7ed8b5be4 100644 --- a/code/modules/spells/spell_types/jaunt/_jaunt.dm +++ b/code/modules/spells/spell_types/jaunt/_jaunt.dm @@ -62,7 +62,7 @@ var/obj/effect/dummy/phased_mob/jaunt = new jaunt_type(loc_override || get_turf(jaunter), jaunter) RegisterSignal(jaunt, COMSIG_MOB_EJECTED_FROM_JAUNT, PROC_REF(on_jaunt_exited)) - spell_requirements |= SPELL_CASTABLE_WHILE_PHASED + check_flags &= ~AB_CHECK_PHASED jaunter.add_traits(list(TRAIT_MAGICALLY_PHASED, TRAIT_RUNECHAT_HIDDEN, TRAIT_WEATHER_IMMUNE), REF(src)) // Don't do the feedback until we have runechat hidden. // Otherwise the text will follow the jaunt holder, which reveals where our caster is travelling. @@ -106,7 +106,7 @@ */ /datum/action/cooldown/spell/jaunt/proc/on_jaunt_exited(obj/effect/dummy/phased_mob/jaunt, mob/living/unjaunter) SHOULD_CALL_PARENT(TRUE) - spell_requirements &= ~SPELL_CASTABLE_WHILE_PHASED + check_flags |= AB_CHECK_PHASED unjaunter.remove_traits(list(TRAIT_MAGICALLY_PHASED, TRAIT_RUNECHAT_HIDDEN, TRAIT_WEATHER_IMMUNE), REF(src)) // This needs to happen at the end, after all the traits and stuff is handled SEND_SIGNAL(unjaunter, COMSIG_MOB_AFTER_EXIT_JAUNT, src) diff --git a/code/modules/spells/spell_types/jaunt/shadow_walk.dm b/code/modules/spells/spell_types/jaunt/shadow_walk.dm index de03f8e15e022..c5a47cd1740c4 100644 --- a/code/modules/spells/spell_types/jaunt/shadow_walk.dm +++ b/code/modules/spells/spell_types/jaunt/shadow_walk.dm @@ -59,7 +59,7 @@ var/healing_rate = 1.5 /// When cooldown is active, you are prevented from moving into tiles that would eject you from your jaunt COOLDOWN_DECLARE(light_step_cooldown) - /// Has the jaunter recently recieved a warning about light? + /// Has the jaunter recently received a warning about light? var/light_alert_given = FALSE /obj/effect/dummy/phased_mob/shadow/Initialize(mapload) @@ -125,7 +125,7 @@ return light_turf.get_lumcount() > light_max // jaunt ends on TRUE /** - * Checks if the user should recieve a warning that they're moving into light. + * Checks if the user should receive a warning that they're moving into light. * * Checks the cooldown for the warning message on moving into the light. * If the message has been displayed, and the cooldown (delay period) is complete, returns TRUE. diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm index 3d1c5b91b81ca..04c3ed47944b9 100644 --- a/code/modules/spells/spell_types/pointed/_pointed.dm +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -64,17 +64,17 @@ build_all_button_icons() return TRUE -/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/click_target) +/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target) var/atom/aim_assist_target - if(aim_assist && isturf(click_target)) + if(aim_assist && isturf(target)) // Find any human in the list. We aren't picky, it's aim assist after all - aim_assist_target = locate(/mob/living/carbon/human) in click_target + aim_assist_target = locate(/mob/living/carbon/human) in target if(!aim_assist_target) // If we didn't find a human, we settle for any living at all - aim_assist_target = locate(/mob/living) in click_target + aim_assist_target = locate(/mob/living) in target - return ..(caller, params, aim_assist_target || click_target) + return ..(caller, params, aim_assist_target || target) /datum/action/cooldown/spell/pointed/is_valid_target(atom/cast_on) if(cast_on == owner) diff --git a/code/modules/spells/spell_types/pointed/finger_guns.dm b/code/modules/spells/spell_types/pointed/finger_guns.dm index 5ed666d9e64ed..24f51a0e90862 100644 --- a/code/modules/spells/spell_types/pointed/finger_guns.dm +++ b/code/modules/spells/spell_types/pointed/finger_guns.dm @@ -46,4 +46,7 @@ /datum/action/cooldown/spell/pointed/projectile/finger_guns/before_cast(atom/cast_on) . = ..() - invocation = span_notice("[cast_on] fires [cast_on.p_their()] finger gun!") + if(isnull(owner)) + invocation = initial(invocation) + else + invocation = span_notice("[owner] fires [owner.p_their()] finger gun!") diff --git a/code/modules/spells/spell_types/pointed/mind_transfer.dm b/code/modules/spells/spell_types/pointed/mind_transfer.dm index 08c638ffe0707..fa401c3b432f6 100644 --- a/code/modules/spells/spell_types/pointed/mind_transfer.dm +++ b/code/modules/spells/spell_types/pointed/mind_transfer.dm @@ -55,11 +55,21 @@ if(!isliving(cast_on)) to_chat(owner, span_warning("You can only swap minds with living beings!")) return FALSE + + if(HAS_TRAIT(cast_on, TRAIT_MIND_TEMPORARILY_GONE)) + to_chat(owner, span_warning("This creature's mind is somewhere else entirely!")) + return FALSE + + if(HAS_TRAIT(cast_on, TRAIT_NO_MINDSWAP)) + to_chat(owner, span_warning("This type of magic can't operate on [cast_on.p_their()] mind!")) + return FALSE + if(is_type_in_typecache(cast_on, blacklisted_mobs)) to_chat(owner, span_warning("This creature is too [pick("powerful", "strange", "arcane", "obscene")] to control!")) return FALSE + if(isguardian(cast_on)) - var/mob/living/simple_animal/hostile/guardian/stand = cast_on + var/mob/living/basic/guardian/stand = cast_on if(stand.summoner && stand.summoner == owner) to_chat(owner, span_warning("Swapping minds with your own guardian would just put you back into your own head!")) return FALSE @@ -86,7 +96,7 @@ var/mob/living/to_swap = cast_on if(isguardian(cast_on)) - var/mob/living/simple_animal/hostile/guardian/stand = cast_on + var/mob/living/basic/guardian/stand = cast_on if(stand.summoner) to_swap = stand.summoner diff --git a/code/modules/spells/spell_types/pointed/swap.dm b/code/modules/spells/spell_types/pointed/swap.dm index ddeb08b4b8ada..6b4a5e45e1437 100644 --- a/code/modules/spells/spell_types/pointed/swap.dm +++ b/code/modules/spells/spell_types/pointed/swap.dm @@ -35,27 +35,27 @@ return FALSE return TRUE -/datum/action/cooldown/spell/pointed/swap/InterceptClickOn(mob/living/caller, params, atom/click_target) +/datum/action/cooldown/spell/pointed/swap/InterceptClickOn(mob/living/caller, params, atom/target) if(LAZYACCESS(params2list(params), RIGHT_CLICK)) if(!IsAvailable(feedback = TRUE)) return FALSE if(!target) return FALSE - if(!isliving(click_target) || isturf(click_target)) + if(!isliving(target) || isturf(target)) // Find any living being in the list. We aren't picky, it's aim assist after all - click_target = locate(/mob/living) in click_target - if(!click_target) + target = locate(/mob/living) in target + if(!target) to_chat(owner, span_warning("You can only select living beings as secondary target!")) return FALSE - if(click_target == owner) + if(target == owner) if(!isnull(second_target)) to_chat(owner, span_notice("You cancel your secondary swap target!")) second_target = null else to_chat(owner, span_warning("You have no secondary swap target!")) return FALSE - second_target = click_target - to_chat(owner, span_notice("You select [click_target.name] as a secondary swap target!")) + second_target = target + to_chat(owner, span_notice("You select [target.name] as a secondary swap target!")) return FALSE return ..() diff --git a/code/modules/spells/spell_types/right_and_wrong.dm b/code/modules/spells/spell_types/right_and_wrong.dm index 017e7fc279df3..27662943af03a 100644 --- a/code/modules/spells/spell_types/right_and_wrong.dm +++ b/code/modules/spells/spell_types/right_and_wrong.dm @@ -42,7 +42,6 @@ GLOBAL_LIST_INIT(summoned_guns, list( /obj/item/gun/energy/e_gun/dragnet, /obj/item/gun/energy/e_gun/turret, /obj/item/gun/energy/pulse/carbine, - /obj/item/gun/energy/decloner, /obj/item/gun/energy/mindflayer, /obj/item/gun/energy/recharge/kinetic_accelerator, /obj/item/gun/energy/plasmacutter/adv, @@ -266,7 +265,7 @@ GLOBAL_LIST_INIT(summoned_magic_objectives, list( /datum/summon_things_controller/New() RegisterSignal(SSdcs, COMSIG_GLOB_CREWMEMBER_JOINED, PROC_REF(on_latejoin)) -/datum/summon_things_controller/Destroy(force, ...) +/datum/summon_things_controller/Destroy(force) . = ..() UnregisterSignal(SSdcs, COMSIG_GLOB_CREWMEMBER_JOINED) diff --git a/code/modules/spells/spell_types/self/basic_heal.dm b/code/modules/spells/spell_types/self/basic_heal.dm index 135b80942062d..f68403ddeeb3f 100644 --- a/code/modules/spells/spell_types/self/basic_heal.dm +++ b/code/modules/spells/spell_types/self/basic_heal.dm @@ -26,5 +26,8 @@ span_warning("A wreath of gentle light passes over [cast_on]!"), span_notice("You wreath yourself in healing light!"), ) - cast_on.adjustBruteLoss(-brute_to_heal, FALSE) - cast_on.adjustFireLoss(-burn_to_heal) + var/need_mob_update = FALSE + need_mob_update += cast_on.adjustBruteLoss(-brute_to_heal, updating_health = FALSE) + need_mob_update += cast_on.adjustFireLoss(-burn_to_heal, updating_health = FALSE) + if(need_mob_update) + cast_on.updatehealth() diff --git a/code/modules/spells/spell_types/self/mime_vow.dm b/code/modules/spells/spell_types/self/mime_vow.dm index 444775e7ff4aa..bd666786b9624 100644 --- a/code/modules/spells/spell_types/self/mime_vow.dm +++ b/code/modules/spells/spell_types/self/mime_vow.dm @@ -1,6 +1,6 @@ /datum/action/cooldown/spell/vow_of_silence - name = "Speech" - desc = "Make (or break) a vow of silence." + name = "Break Vow" + desc = "Break your vow of silence. Permanently." background_icon_state = "bg_mime" overlay_icon_state = "bg_mime_border" button_icon = 'icons/mob/actions/actions_mime.dmi' @@ -8,7 +8,6 @@ panel = "Mime" school = SCHOOL_MIME - cooldown_time = 5 MINUTES spell_requirements = NONE spell_max_level = 1 @@ -20,18 +19,18 @@ /datum/action/cooldown/spell/vow_of_silence/Remove(mob/living/remove_from) . = ..() REMOVE_TRAIT(remove_from, TRAIT_MIMING, "[type]") - remove_from.clear_mood_event("vow") + +/datum/action/cooldown/spell/vow_of_silence/before_cast(atom/cast_on) + if(tgui_alert(usr, "Are you sure? There's no going back.", "Break Vow", list("I'm Sure", "Abort")) != "I'm Sure") + return SPELL_CANCEL_CAST + return ..() /datum/action/cooldown/spell/vow_of_silence/cast(mob/living/carbon/human/cast_on) . = ..() - if(HAS_TRAIT_FROM(cast_on, TRAIT_MIMING, "[type]")) - to_chat(cast_on, span_notice("You break your vow of silence.")) - cast_on.log_message("broke [cast_on.p_their()] vow of silence.", LOG_GAME) - cast_on.add_mood_event("vow", /datum/mood_event/broken_vow) - REMOVE_TRAIT(cast_on, TRAIT_MIMING, "[type]") - else - to_chat(cast_on, span_notice("You make a vow of silence.")) - cast_on.log_message("made a vow of silence.", LOG_GAME) - cast_on.clear_mood_event("vow") - ADD_TRAIT(cast_on, TRAIT_MIMING, "[type]") - cast_on.update_mob_action_buttons() + to_chat(cast_on, span_notice("You break your vow of silence.")) + cast_on.log_message("broke [cast_on.p_their()] vow of silence.", LOG_GAME) + cast_on.add_mood_event("vow", /datum/mood_event/broken_vow) + REMOVE_TRAIT(cast_on, TRAIT_MIMING, "[type]") + var/datum/job/mime/mime_job = SSjob.GetJob(JOB_MIME) + mime_job.total_positions += 1 + qdel(src) diff --git a/code/modules/spells/spell_types/self/personality_commune.dm b/code/modules/spells/spell_types/self/personality_commune.dm deleted file mode 100644 index cd10c2b7736aa..0000000000000 --- a/code/modules/spells/spell_types/self/personality_commune.dm +++ /dev/null @@ -1,56 +0,0 @@ -// This can probably be changed to use mind linker at some point -/datum/action/cooldown/spell/personality_commune - name = "Personality Commune" - desc = "Sends thoughts to your alternate consciousness." - button_icon_state = "telepathy" - cooldown_time = 0 SECONDS - spell_requirements = NONE - - /// Fluff text shown when a message is sent to the pair - var/fluff_text = span_boldnotice("You hear an echoing voice in the back of your head...") - /// The message to send to the corresponding person on cast - var/to_send - -/datum/action/cooldown/spell/personality_commune/New(Target) - . = ..() - if(!istype(target, /datum/brain_trauma/severe/split_personality)) - stack_trace("[type] was created on a target that isn't a /datum/brain_trauma/severe/split_personality, this doesn't work.") - qdel(src) - -/datum/action/cooldown/spell/personality_commune/is_valid_target(atom/cast_on) - return isliving(cast_on) - -/datum/action/cooldown/spell/personality_commune/before_cast(atom/cast_on) - . = ..() - if(. & SPELL_CANCEL_CAST) - return - - var/datum/brain_trauma/severe/split_personality/trauma = target - if(!istype(trauma)) // hypothetically impossible but you never know - return . | SPELL_CANCEL_CAST - - to_send = tgui_input_text(cast_on, "What would you like to tell your other self?", "Commune") - if(QDELETED(src) || QDELETED(trauma) || QDELETED(cast_on) || QDELETED(trauma.owner) || !can_cast_spell()) - return . | SPELL_CANCEL_CAST - if(!to_send) - reset_cooldown() - return . | SPELL_CANCEL_CAST - -// Pillaged and adapted from telepathy code -/datum/action/cooldown/spell/personality_commune/cast(mob/living/cast_on) - . = ..() - var/datum/brain_trauma/severe/split_personality/trauma = target - - var/user_message = span_boldnotice("You concentrate and send thoughts to your other self:") - var/user_message_body = span_notice("[to_send]") - - to_chat(cast_on, "[user_message] [user_message_body]") - - trauma.owner.balloon_alert(trauma.owner, "you hear a voice") - to_chat(trauma.owner, "[fluff_text] [user_message_body]") - - log_directed_talk(cast_on, trauma.owner, to_send, LOG_SAY, "[name]") - for(var/dead_mob in GLOB.dead_mob_list) - if(!isobserver(dead_mob)) - continue - to_chat(dead_mob, "[FOLLOW_LINK(dead_mob, cast_on)] [span_boldnotice("[cast_on] [name]:")] [span_notice("\"[to_send]\" to")] [span_name("[trauma]")]") diff --git a/code/modules/spells/spell_types/self/splattercasting_spell.dm b/code/modules/spells/spell_types/self/splattercasting_spell.dm index 1af0cacd2aa74..184a2afab7ca2 100644 --- a/code/modules/spells/spell_types/self/splattercasting_spell.dm +++ b/code/modules/spells/spell_types/self/splattercasting_spell.dm @@ -29,6 +29,7 @@ merely a vessel for the arcane flow. Soon, all that is left is not pain, but hunger.")) cast_on.set_species(/datum/species/vampire) + cast_on.blood_volume = BLOOD_VOLUME_NORMAL ///for predictable blood total amounts when the spell is first cast. cast_on.AddComponent(/datum/component/splattercasting) diff --git a/code/modules/spells/spell_types/self/stop_time.dm b/code/modules/spells/spell_types/self/stop_time.dm index cab47375eb3a4..d3e937972fdb1 100644 --- a/code/modules/spells/spell_types/self/stop_time.dm +++ b/code/modules/spells/spell_types/self/stop_time.dm @@ -16,9 +16,14 @@ /// The duration of the time stop. var/timestop_duration = 10 SECONDS + /// if TRUE, the owner is immune to all time stop, from anyone + var/owner_is_immune_to_all_timestop = TRUE + /// if TRUE, the owner is immune to their own timestop (but not other people's, if above is FALSE) + var/owner_is_immune_to_self_timestop = TRUE + /datum/action/cooldown/spell/timestop/Grant(mob/grant_to) . = ..() - if(owner) + if(!isnull(owner) && owner_is_immune_to_all_timestop) ADD_TRAIT(owner, TRAIT_TIME_STOP_IMMUNE, REF(src)) /datum/action/cooldown/spell/timestop/Remove(mob/remove_from) @@ -27,4 +32,17 @@ /datum/action/cooldown/spell/timestop/cast(atom/cast_on) . = ..() - new /obj/effect/timestop/magic(get_turf(cast_on), timestop_range, timestop_duration, list(cast_on)) + var/list/default_immune_atoms = list() + if(owner_is_immune_to_self_timestop) + default_immune_atoms += cast_on + new /obj/effect/timestop/magic(get_turf(cast_on), timestop_range, timestop_duration, default_immune_atoms) + +/datum/action/cooldown/spell/timestop/vv_edit_var(var_name, var_value) + . = ..() + if(var_name != NAMEOF(src, owner_is_immune_to_all_timestop) || isnull(owner)) + return + + if(var_value) + ADD_TRAIT(owner, TRAIT_TIME_STOP_IMMUNE, REF(src)) + else + REMOVE_TRAIT(owner, TRAIT_TIME_STOP_IMMUNE, REF(src)) diff --git a/code/modules/spells/spell_types/self/summonitem.dm b/code/modules/spells/spell_types/self/summonitem.dm index 62d6b07816196..ab99f35271d3c 100644 --- a/code/modules/spells/spell_types/self/summonitem.dm +++ b/code/modules/spells/spell_types/self/summonitem.dm @@ -138,7 +138,6 @@ item_to_retrieve = null break - SEND_SIGNAL(holding_mark, COMSIG_MAGIC_RECALL, caster, item_to_retrieve) holding_mark.dropItemToGround(item_to_retrieve) else if(isobj(item_to_retrieve.loc)) @@ -157,15 +156,6 @@ infinite_recursion += 1 - else - // Organs are usually stored in nullspace - if(isorgan(item_to_retrieve)) - var/obj/item/organ/organ = item_to_retrieve - if(organ.owner) - // If this code ever runs I will be happy - log_combat(caster, organ.owner, "magically removed [organ.name] from", addition = "COMBAT MODE: [uppertext(caster.combat_mode)]") - organ.Remove(organ.owner) - if(!item_to_retrieve) return @@ -176,6 +166,8 @@ else item_to_retrieve.forceMove(caster.drop_location()) item_to_retrieve.loc.visible_message(span_warning("[item_to_retrieve] suddenly appears!")) + + SEND_SIGNAL(item_to_retrieve, COMSIG_MAGIC_RECALL, caster, item_to_retrieve) playsound(get_turf(item_to_retrieve), 'sound/magic/summonitems_generic.ogg', 50, TRUE) /datum/action/cooldown/spell/summonitem/abductor diff --git a/code/modules/spells/spell_types/shapeshift/_shape_status.dm b/code/modules/spells/spell_types/shapeshift/_shape_status.dm index 10d42760c91b9..faa84835255a8 100644 --- a/code/modules/spells/spell_types/shapeshift/_shape_status.dm +++ b/code/modules/spells/spell_types/shapeshift/_shape_status.dm @@ -35,7 +35,8 @@ ADD_TRAIT(caster_mob, TRAIT_NO_TRANSFORM, REF(src)) caster_mob.apply_status_effect(/datum/status_effect/grouped/stasis, STASIS_SHAPECHANGE_EFFECT) - RegisterSignal(owner, COMSIG_LIVING_PRE_WABBAJACKED, PROC_REF(on_wabbajacked)) + RegisterSignal(owner, COMSIG_LIVING_PRE_WABBAJACKED, PROC_REF(on_pre_wabbajack)) + RegisterSignal(owner, COMSIG_PRE_MOB_CHANGED_TYPE, PROC_REF(on_pre_type_change)) RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(on_shape_death)) RegisterSignal(caster_mob, COMSIG_LIVING_DEATH, PROC_REF(on_caster_death)) RegisterSignal(caster_mob, COMSIG_QDELETING, PROC_REF(on_caster_deleted)) @@ -53,15 +54,24 @@ // but juuust in case make sure nothing sticks around. caster_mob = null -/// Signal proc for [COMSIG_LIVING_PRE_WABBAJACKED] to prevent us from being Wabbajacked and messed up. -/datum/status_effect/shapechange_mob/proc/on_wabbajacked(mob/living/source, randomized) +/// Called when we're shot by the Wabbajack but before we change into a different mob +/datum/status_effect/shapechange_mob/proc/on_pre_wabbajack(mob/living/source) SIGNAL_HANDLER + on_mob_transformed(source) + return STOP_WABBAJACK + +/// Called when we're turned into a different mob via the change_mob_type proc +/datum/status_effect/shapechange_mob/proc/on_pre_type_change(mob/living/source) + SIGNAL_HANDLER + on_mob_transformed(source) + return COMPONENT_BLOCK_MOB_CHANGE +/// Called when the transformed mob tries to change into a different kind of mob, we wouldn't handle this well so we'll just turn back +/datum/status_effect/shapechange_mob/proc/on_mob_transformed(mob/living/source) var/mob/living/revealed_mob = caster_mob source.visible_message(span_warning("[revealed_mob] gets pulled back to their normal form!")) restore_caster() revealed_mob.Paralyze(10 SECONDS, ignore_canstun = TRUE) - return STOP_WABBAJACK /// Restores the caster back to their human form. /// if kill_caster_after is TRUE, the caster will have death() called on them after restoring. @@ -76,20 +86,27 @@ UnregisterSignal(owner, list(COMSIG_LIVING_PRE_WABBAJACKED, COMSIG_LIVING_DEATH)) UnregisterSignal(caster_mob, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH)) - caster_mob.forceMove(owner.loc) REMOVE_TRAIT(caster_mob, TRAIT_NO_TRANSFORM, REF(src)) caster_mob.remove_status_effect(/datum/status_effect/grouped/stasis, STASIS_SHAPECHANGE_EFFECT) - owner.mind?.transfer_to(caster_mob) + + var/atom/former_loc = owner.loc + owner.moveToNullspace() + caster_mob.forceMove(former_loc) // This is to avoid crushing our former cockroach body if(kill_caster_after) caster_mob.death() after_unchange() - caster_mob = null + + // We're about to remove the status effect and clear owner so we need to cache this + var/mob/living/former_body = owner + + // Do this late as it will destroy the status effect we are in and null a bunch of values we are trying to use + owner.mind?.transfer_to(caster_mob) // Destroy the owner after all's said and done, this will also destroy our status effect (src) // retore_caster() should never reach this point while either the owner or the effect is being qdeleted - qdel(owner) + qdel(former_body) /// Effects done after the casting mob has reverted to their human form. /datum/status_effect/shapechange_mob/proc/after_unchange() @@ -115,7 +132,7 @@ // Our caster inside was gibbed, mirror the gib to our mob if(gibbed) - owner.gib() + owner.gib(DROP_ALL_REMAINS) // Otherwise our caster died, just make our mob die else @@ -154,9 +171,9 @@ source_spell.Grant(owner) if(source_spell.convert_damage) - var/damage_to_apply = owner.maxHealth * ((caster_mob.maxHealth - caster_mob.health) / caster_mob.maxHealth) + var/damage_to_apply = owner.maxHealth * (caster_mob.get_total_damage() / caster_mob.maxHealth) - owner.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, wound_bonus = CANT_WOUND) + owner.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, spread_damage = TRUE, wound_bonus = CANT_WOUND) owner.blood_volume = caster_mob.blood_volume for(var/datum/action/bodybound_action as anything in caster_mob.actions) @@ -186,11 +203,9 @@ if(QDELETED(source_spell) || !source_spell.convert_damage) return - if(caster_mob.stat != DEAD) - caster_mob.revive(HEAL_DAMAGE) - - var/damage_to_apply = caster_mob.maxHealth * ((owner.maxHealth - owner.health) / owner.maxHealth) - caster_mob.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, wound_bonus = CANT_WOUND) + caster_mob.fully_heal(HEAL_DAMAGE) // Remove all of our damage before setting our health to a proportion of the former transformed mob's health + var/damage_to_apply = caster_mob.maxHealth * (owner.get_total_damage() / owner.maxHealth) + caster_mob.apply_damage(damage_to_apply, source_spell.convert_damage_type, forced = TRUE, spread_damage = TRUE, wound_bonus = CANT_WOUND) caster_mob.blood_volume = owner.blood_volume diff --git a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm index 8acd6ca92475e..59c9ffdde3b0b 100644 --- a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm +++ b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm @@ -128,11 +128,11 @@ new gib_type(get_turf(possible_vent)) playsound(possible_vent, 'sound/effects/reee.ogg', 75, TRUE) - priority_announce("We detected a pipe blockage around [get_area(get_turf(cast_on))], please dispatch someone to investigate.", "Central Command") + priority_announce("We detected a pipe blockage around [get_area(get_turf(cast_on))], please dispatch someone to investigate.", "[command_name()]") // Gib our caster, and make sure to leave nothing behind // (If we leave something behind, it'll drop on the turf of the pipe, which is kinda wrong.) cast_on.investigate_log("has been gibbed by shapeshifting while ventcrawling.", INVESTIGATE_DEATHS) - cast_on.gib(TRUE, TRUE, TRUE) + cast_on.gib() /// Callback for the radial that allows the user to choose their species. /datum/action/cooldown/spell/shapeshift/proc/check_menu(mob/living/caster) @@ -159,6 +159,12 @@ spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB) ADD_TRAIT(new_shape, TRAIT_DONT_WRITE_MEMORY, SHAPESHIFT_TRAIT) // If you shapeshift into a pet subtype we don't want to update Poly's deathcount or something when you die + // Make sure that if you shapechanged into a bot, the AI can't just turn you off. + var/mob/living/simple_animal/bot/polymorph_bot = new_shape + if (istype(polymorph_bot)) + polymorph_bot.bot_cover_flags |= BOT_COVER_EMAGGED + polymorph_bot.bot_mode_flags &= ~BOT_MODE_REMOTE_ENABLED + return new_shape /// Actually does the un-shapeshift, from the caster. (Caster is a shapeshifted mob.) diff --git a/code/modules/spells/spell_types/shapeshift/shapechange.dm b/code/modules/spells/spell_types/shapeshift/shapechange.dm index 2e890eed6325c..dd2597d00970c 100644 --- a/code/modules/spells/spell_types/shapeshift/shapechange.dm +++ b/code/modules/spells/spell_types/shapeshift/shapechange.dm @@ -12,10 +12,10 @@ spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC possible_shapes = list( + /mob/living/basic/carp/magic/chaos, + /mob/living/basic/construct/juggernaut/mystic, /mob/living/basic/mouse, /mob/living/basic/pet/dog/corgi, - /mob/living/basic/carp/magic/chaos, - /mob/living/simple_animal/bot/secbot/ed209, /mob/living/basic/spider/giant/viper/wizard, - /mob/living/simple_animal/hostile/construct/juggernaut/mystic, + /mob/living/simple_animal/bot/secbot/ed209, ) diff --git a/code/modules/spells/spell_types/touch/scream_for_me.dm b/code/modules/spells/spell_types/touch/scream_for_me.dm index 231b6927e504b..d3c142d703921 100644 --- a/code/modules/spells/spell_types/touch/scream_for_me.dm +++ b/code/modules/spells/spell_types/touch/scream_for_me.dm @@ -2,7 +2,7 @@ /datum/action/cooldown/spell/touch/scream_for_me name = "Scream For Me" desc = "This wicked spell inflicts many severe wounds on your target, causing them to \ - likely bleed to death unless they recieve immediate medical attention." + likely bleed to death unless they receive immediate medical attention." button_icon_state = "scream_for_me" sound = null //trust me, you'll hear their wounds diff --git a/code/modules/spells/spell_types/touch/smite.dm b/code/modules/spells/spell_types/touch/smite.dm index bcd234979c662..7bef97c8a6582 100644 --- a/code/modules/spells/spell_types/touch/smite.dm +++ b/code/modules/spells/spell_types/touch/smite.dm @@ -48,7 +48,7 @@ return TRUE victim.investigate_log("has been gibbed by the smite spell.", INVESTIGATE_DEATHS) - victim.gib() + victim.gib(DROP_ALL_REMAINS) return TRUE /obj/item/melee/touch_attack/smite @@ -57,3 +57,13 @@ icon = 'icons/obj/weapons/hand.dmi' icon_state = "disintegrate" inhand_icon_state = "disintegrate" + +/obj/item/melee/touch_attack/smite/suicide_act(mob/living/user) + + user.visible_message(span_suicide("[user] spreads [user.p_their()] arms apart, lightning arcing between them! It looks like [user.p_theyre()] going out with a bang!")) + user.say("SHIA KAZING!!", forced = "smite suicide") + do_sparks(4, FALSE, get_turf(user)) + explosion(user, heavy_impact_range = 2, explosion_cause = src) //Cheap explosion imitation because putting detonate() here causes runtimes + user.gib(DROP_BODYPARTS) + qdel(src) + return MANUAL_SUICIDE diff --git a/code/modules/spells/spell_types/tower_of_babel.dm b/code/modules/spells/spell_types/tower_of_babel.dm index 3c652579b7f83..618711a8d9563 100644 --- a/code/modules/spells/spell_types/tower_of_babel.dm +++ b/code/modules/spells/spell_types/tower_of_babel.dm @@ -29,7 +29,7 @@ GLOBAL_DATUM(tower_of_babel, /datum/tower_of_babel) curse_of_babel(target) -/datum/tower_of_babel/Destroy(force, ...) +/datum/tower_of_babel/Destroy(force) . = ..() UnregisterSignal(SSdcs, COMSIG_GLOB_CREWMEMBER_JOINED) diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm index 9ffa24f3c4357..beb154b645c4a 100644 --- a/code/modules/station_goals/bsa.dm +++ b/code/modules/station_goals/bsa.dm @@ -39,7 +39,7 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) /obj/machinery/bsa/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool, time = 1 SECONDS) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/bsa/back name = "Bluespace Artillery Generator" @@ -202,7 +202,6 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) /obj/machinery/bsa/full/proc/get_layer() top_layer = mutable_appearance(icon, layer = ABOVE_MOB_LAYER) - SET_PLANE_EXPLICIT(top_layer, GAME_PLANE_UPPER, src) switch(dir) if(WEST) top_layer.icon_state = "top_west" @@ -241,14 +240,19 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) point.Beam(target, icon_state = "bsa_beam", time = 5 SECONDS, maxdistance = world.maxx) //ZZZAP new /obj/effect/temp_visual/bsa_splash(point, dir) - notify_ghosts("The Bluespace Artillery has been fired!", source = bullseye, header = "KABOOM!") + notify_ghosts( + "The Bluespace Artillery has been fired!", + source = bullseye, + header = "KABOOM!", + ) + if(!blocker) - message_admins("[ADMIN_LOOKUPFLW(user)] has launched an artillery strike targeting [ADMIN_VERBOSEJMP(bullseye)].") - user.log_message("has launched an artillery strike targeting [AREACOORD(bullseye)].", LOG_GAME) + message_admins("[ADMIN_LOOKUPFLW(user)] has launched a bluespace artillery strike targeting [ADMIN_VERBOSEJMP(bullseye)].") + user.log_message("has launched a bluespace artillery strike targeting [AREACOORD(bullseye)].", LOG_GAME) explosion(bullseye, devastation_range = ex_power, heavy_impact_range = ex_power*2, light_impact_range = ex_power*4, explosion_cause = src) else - message_admins("[ADMIN_LOOKUPFLW(user)] has launched an artillery strike targeting [ADMIN_VERBOSEJMP(bullseye)] but it was blocked by [blocker] at [ADMIN_VERBOSEJMP(target)].") - user.log_message("has launched an artillery strike targeting [AREACOORD(bullseye)] but it was blocked by [blocker] at [AREACOORD(target)].", LOG_GAME) + message_admins("[ADMIN_LOOKUPFLW(user)] has launched a bluespace artillery strike targeting [ADMIN_VERBOSEJMP(bullseye)] but it was blocked by [blocker] at [ADMIN_VERBOSEJMP(target)].") + user.log_message("has launched a bluespace artillery strike targeting [AREACOORD(bullseye)] but it was blocked by [blocker] at [AREACOORD(target)].", LOG_GAME) /obj/machinery/bsa/full/proc/reload() @@ -338,7 +342,7 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) if(isnull(options[victim])) return target = options[victim] - log_game("[key_name(user)] has aimed the artillery strike at [target].") + log_game("[key_name(user)] has aimed the bluespace artillery strike at [target].") /obj/machinery/computer/bsa_control/proc/get_target_name() diff --git a/code/modules/station_goals/dna_vault.dm b/code/modules/station_goals/dna_vault.dm index 03f69c2664cae..5c146758d123f 100644 --- a/code/modules/station_goals/dna_vault.dm +++ b/code/modules/station_goals/dna_vault.dm @@ -101,12 +101,11 @@ F.parent = src fillers += F - if(SSticker.mode) - var/datum/station_goal/dna_vault/dna_vault_goal = locate() in GLOB.station_goals - if (!isnull(dna_vault_goal)) - animals_max = dna_vault_goal.animal_count - plants_max = dna_vault_goal.plant_count - dna_max = dna_vault_goal.human_count + var/datum/station_goal/dna_vault/dna_vault_goal = SSstation.get_station_goal(/datum/station_goal/dna_vault) + if(!isnull(dna_vault_goal)) + animals_max = dna_vault_goal.animal_count + plants_max = dna_vault_goal.plant_count + dna_max = dna_vault_goal.human_count return ..() diff --git a/code/modules/station_goals/generate_goals.dm b/code/modules/station_goals/generate_goals.dm new file mode 100644 index 0000000000000..b37543d6de8d4 --- /dev/null +++ b/code/modules/station_goals/generate_goals.dm @@ -0,0 +1 @@ +/// Creates the initial station goals. diff --git a/code/modules/station_goals/meteor_shield.dm b/code/modules/station_goals/meteor_shield.dm index 7b16606013b21..84a61395a4b9c 100644 --- a/code/modules/station_goals/meteor_shield.dm +++ b/code/modules/station_goals/meteor_shield.dm @@ -14,8 +14,9 @@ // Satellites be actived to generate a shield that will block unorganic matter from passing it. /datum/station_goal/station_shield name = "Station Shield" - var/coverage_goal = 500 requires_space = TRUE + var/coverage_goal = 500 + VAR_PRIVATE/cached_coverage_length /datum/station_goal/station_shield/get_report() return list( @@ -37,17 +38,24 @@ /datum/station_goal/station_shield/check_completion() if(..()) return TRUE - if(get_coverage() >= coverage_goal) + update_coverage() + if(cached_coverage_length >= coverage_goal) return TRUE return FALSE -/datum/station_goal/proc/get_coverage() +/datum/station_goal/station_shield/proc/get_coverage() + return cached_coverage_length + +/// Gets the coverage of all active meteor shield satellites +/// Can be expensive, ensure you need this before calling it +/datum/station_goal/station_shield/proc/update_coverage() var/list/coverage = list() for(var/obj/machinery/satellite/meteor_shield/shield_satt as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/satellite/meteor_shield)) if(!shield_satt.active || !is_station_level(shield_satt.z)) continue - coverage |= view(shield_satt.kill_range, shield_satt) - return coverage.len + for(var/turf/covered in view(shield_satt.kill_range, shield_satt)) + coverage |= covered + cached_coverage_length = length(coverage) /obj/machinery/satellite/meteor_shield name = "\improper Meteor Shield Satellite" @@ -89,7 +97,7 @@ /obj/machinery/satellite/meteor_shield/process() if(obj_flags & EMAGGED) //kills the processing because emagged meteor shields no longer stop meteors in any way - return ..() + return PROCESS_KILL if(!active) return for(var/obj/effect/meteor/meteor_to_destroy in GLOB.meteor_list) @@ -109,6 +117,9 @@ if(obj_flags & EMAGGED) update_emagged_meteor_sat(user) + var/datum/station_goal/station_shield/goal = SSstation.get_station_goal(/datum/station_goal/station_shield) + goal?.update_coverage() + /obj/machinery/satellite/meteor_shield/Destroy() . = ..() if(obj_flags & EMAGGED) diff --git a/code/modules/station_goals/station_goal.dm b/code/modules/station_goals/station_goal.dm index 30ed0b78b6c2f..d7fd5e7f75248 100644 --- a/code/modules/station_goals/station_goal.dm +++ b/code/modules/station_goals/station_goal.dm @@ -1,6 +1,3 @@ -/// List of available station goals for the crew to be working on -GLOBAL_LIST_EMPTY_TYPED(station_goals, /datum/station_goal) - /datum/station_goal var/name = "Generic Goal" var/weight = 1 //In case of multiple goals later. @@ -30,13 +27,8 @@ GLOBAL_LIST_EMPTY_TYPED(station_goals, /datum/station_goal) else return "
  • [name] : [span_redtext("Failed!")]
  • " -/datum/station_goal/Destroy() - GLOB.station_goals -= src - return ..() - /datum/station_goal/Topic(href, href_list) ..() - if(!check_rights(R_ADMIN) || !usr.client.holder.CheckAdminHref(href, href_list)) return @@ -45,3 +37,15 @@ GLOBAL_LIST_EMPTY_TYPED(station_goals, /datum/station_goal) send_report() else if(href_list["remove"]) qdel(src) + +/datum/station_goal/New() + if(type in SSstation.goals_by_type) + stack_trace("Creating a new station_goal of type [type] when one already exists in SSstation.goals_by_type this is not supported anywhere. I trust you tho") + else + SSstation.goals_by_type[type] = src + return ..() + +/datum/station_goal/Destroy(force) + if(SSstation.goals_by_type[type] == src) + SSstation.goals_by_type -= type + return ..() diff --git a/code/modules/surgery/autopsy.dm b/code/modules/surgery/autopsy.dm index 2d106cfd4415e..6ff32f8b465c9 100644 --- a/code/modules/surgery/autopsy.dm +++ b/code/modules/surgery/autopsy.dm @@ -10,7 +10,8 @@ ) /datum/surgery/autopsy/can_start(mob/user, mob/living/patient) - . = ..() + if(!..()) + return FALSE if(patient.stat != DEAD) return FALSE if(HAS_TRAIT_FROM(patient, TRAIT_DISSECTED, AUTOPSY_TRAIT)) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 8d3c8fb5a04ac..c81b691980ad9 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -6,6 +6,7 @@ w_class = WEIGHT_CLASS_SMALL icon = 'icons/mob/human/bodyparts.dmi' icon_state = "" //Leave this blank! Bodyparts are built using overlays + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 //actually mindblowing /// The icon for Organic limbs using greyscale VAR_PROTECTED/icon_greyscale = DEFAULT_BODYPART_ICON_ORGANIC ///The icon for non-greyscale limbs @@ -16,10 +17,12 @@ VAR_PROTECTED/icon_invisible = 'icons/mob/human/bodyparts.dmi' ///The type of husk for building an iconstate var/husk_type = "humanoid" + ///The color to multiply the greyscaled husk sprites by. Can be null. Old husk sprite chest color is #A6A6A6 + var/husk_color = "#A6A6A6" layer = BELOW_MOB_LAYER //so it isn't hidden behind objects when on the floor grind_results = list(/datum/reagent/bone_dust = 10, /datum/reagent/consumable/liquidgibs = 5) // robotic bodyparts and chests/heads cannot be ground /// The mob that "owns" this limb - /// DO NOT MODIFY DIRECTLY. Use set_owner() + /// DO NOT MODIFY DIRECTLY. Use update_owner() var/mob/living/carbon/owner /// If this limb can be scarred. @@ -76,7 +79,7 @@ // Damage variables ///A mutiplication of the burn and brute damage that the limb's stored damage contributes to its attached mob's overall wellbeing. - var/body_damage_coeff = 1 + var/body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_TOTAL ///The current amount of brute damage the limb has var/brute_dam = 0 ///The current amount of burn damage the limb has @@ -126,7 +129,6 @@ var/list/damage_examines = list( BRUTE = DEFAULT_BRUTE_EXAMINE_TEXT, BURN = DEFAULT_BURN_EXAMINE_TEXT, - CLONE = DEFAULT_CLONE_EXAMINE_TEXT, ) // Wounds related variables @@ -152,13 +154,10 @@ /// How much generic bleedstacks we have on this bodypart var/generic_bleedstacks /// If we have a gauze wrapping currently applied (not including splints) - var/obj/item/stack/current_gauze + var/obj/item/stack/medical/gauze/current_gauze /// If something is currently grasping this bodypart and trying to staunch bleeding (see [/obj/item/hand_item/self_grasp]) var/obj/item/hand_item/self_grasp/grasped_by - ///A list of all the external organs we've got stored to draw horns, wings and stuff with (special because we are actually in the limbs unlike normal organs :/ ) - ///If someone ever comes around to making all organs exist in the bodyparts, you can just remove this and use a typed loop - var/list/obj/item/organ/external/external_organs = list() ///A list of all bodypart overlays to draw var/list/bodypart_overlays = list() @@ -166,6 +165,8 @@ var/attack_type = BRUTE /// the verb used for an unarmed attack when using this limb, such as arm.unarmed_attack_verb = punch var/unarmed_attack_verb = "bump" + /// if we have a special attack verb for hitting someone who is grappled by us, it goes here. + var/grappled_attack_verb /// what visual effect is used when this limb is used to strike someone. var/unarmed_attack_effect = ATTACK_EFFECT_PUNCH /// Sounds when this bodypart is used in an umarmed attack @@ -175,8 +176,8 @@ var/unarmed_damage_low = 1 ///Highest possible punch damage this bodypart can ive. var/unarmed_damage_high = 1 - ///Damage at which attacks from this bodypart will stun - var/unarmed_stun_threshold = 2 + ///Determines the accuracy bonus, armor penetration and knockdown probability. + var/unarmed_effectiveness = 10 /// How many pixels this bodypart will offset the top half of the mob, used for abnormally sized torsos and legs var/top_offset = 0 @@ -215,7 +216,6 @@ wound_resistance = reset_fantasy_variable("wound_resistance", wound_resistance) return ..() - /obj/item/bodypart/Initialize(mapload) . = ..() if(can_be_disabled) @@ -232,31 +232,40 @@ refresh_bleed_rate() /obj/item/bodypart/Destroy() - if(owner) - owner.remove_bodypart(src) - set_owner(null) + if(owner && !QDELETED(owner)) + forced_removal(special = FALSE, dismembered = TRUE, move_to_floor = FALSE) + update_owner(null) for(var/wound in wounds) qdel(wound) // wounds is a lazylist, and each wound removes itself from it on deletion. if(length(wounds)) stack_trace("[type] qdeleted with [length(wounds)] uncleared wounds") wounds.Cut() - if(length(external_organs)) - for(var/obj/item/organ/external/external_organ as anything in external_organs) - external_organs -= external_organ - qdel(external_organ) // It handles removing its references to this limb on its own. + owner = null + + for(var/atom/movable/movable in contents) + qdel(movable) - external_organs = list() QDEL_LIST_ASSOC_VAL(feature_offsets) return ..() -/obj/item/bodypart/forceMove(atom/destination) //Please. Never forcemove a limb if its's actually in use. This is only for borgs. - SHOULD_CALL_PARENT(TRUE) +/obj/item/bodypart/ex_act(severity, target) + if(owner) //trust me bro you dont want this + return FALSE + return ..() - . = ..() - if(isturf(destination)) - update_icon_dropped() + +/obj/item/bodypart/proc/on_forced_removal(atom/old_loc, dir, forced, list/old_locs) + SIGNAL_HANDLER + + forced_removal(special = FALSE, dismembered = TRUE, move_to_floor = FALSE) + +/// In-case someone, somehow only teleports someones limb +/obj/item/bodypart/proc/forced_removal(special, dismembered, move_to_floor) + drop_limb(special, dismembered, move_to_floor) + + update_icon_dropped() /obj/item/bodypart/examine(mob/user) SHOULD_CALL_PARENT(TRUE) @@ -345,7 +354,6 @@ var/stuck_word = embedded_thing.isEmbedHarmless() ? "stuck" : "embedded" check_list += "\t There is \a [embedded_thing] [stuck_word] in your [name]!" - /obj/item/bodypart/blob_act() receive_damage(max_damage, wound_bonus = CANT_WOUND) @@ -404,31 +412,24 @@ var/atom/drop_loc = drop_location() if(IS_ORGANIC_LIMB(src)) playsound(drop_loc, 'sound/misc/splort.ogg', 50, TRUE, -1) - seep_gauze(9999) // destroy any existing gauze if any exists - for(var/obj/item/organ/bodypart_organ in get_organs()) - bodypart_organ.transfer_to_limb(src, owner) - for(var/obj/item/organ/external/external in external_organs) - external.remove_from_limb() - external.forceMove(drop_loc) - for(var/obj/item/item_in_bodypart in src) - item_in_bodypart.forceMove(drop_loc) - update_icon_dropped() - -///since organs aren't actually stored in the bodypart themselves while attached to a person, we have to query the owner for what we should have -/obj/item/bodypart/proc/get_organs() - SHOULD_CALL_PARENT(TRUE) - RETURN_TYPE(/list) + QDEL_NULL(current_gauze) - if(!owner) - return FALSE + for(var/obj/item/organ/bodypart_organ in contents) + if(bodypart_organ.organ_flags & ORGAN_UNREMOVABLE) + continue + if(owner) + bodypart_organ.Remove(bodypart_organ.owner) + else + if(bodypart_organ.bodypart_remove(src)) + if(drop_loc) //can be null if being deleted + bodypart_organ.forceMove(get_turf(drop_loc)) - var/list/bodypart_organs - for(var/obj/item/organ/organ_check as anything in owner.organs) //internal organs inside the dismembered limb are dropped. - if(check_zone(organ_check.zone) == body_zone) - LAZYADD(bodypart_organs, organ_check) // this way if we don't have any, it'll just return null + if(drop_loc) //can be null during deletion + for(var/atom/movable/movable as anything in src) + movable.forceMove(drop_loc) - return bodypart_organs + update_icon_dropped() //Return TRUE to get whatever mob this is in to update health. /obj/item/bodypart/proc/on_life(seconds_per_tick, times_fired) @@ -452,16 +453,20 @@ * attack_direction - The direction the bodypart is attacked from, used to send blood flying in the opposite direction. * damage_source - The source of damage, typically a weapon. */ -/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, blocked = 0, updating_health = TRUE, required_bodytype = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, damage_source) +/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, blocked = 0, updating_health = TRUE, forced = FALSE, required_bodytype = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, damage_source) SHOULD_CALL_PARENT(TRUE) - var/hit_percent = (100-blocked)/100 + var/hit_percent = forced ? 1 : (100-blocked)/100 if((!brute && !burn) || hit_percent <= 0) return FALSE - if(owner && (owner.status_flags & GODMODE)) - return FALSE //godmode - if(required_bodytype && !(bodytype & required_bodytype)) - return FALSE + if (!forced) + if(!isnull(owner)) + if (owner.status_flags & GODMODE) + return FALSE + if (SEND_SIGNAL(owner, COMSIG_CARBON_LIMB_DAMAGED, src, brute, burn) & COMPONENT_PREVENT_LIMB_DAMAGE) + return FALSE + if(required_bodytype && !(bodytype & required_bodytype)) + return FALSE var/dmg_multi = CONFIG_GET(number/damage_multiplier) * hit_percent brute = round(max(brute * dmg_multi * brute_modifier, 0), DAMAGE_PRECISION) @@ -632,10 +637,10 @@ //Heals brute and burn damage for the organ. Returns 1 if the damage-icon states changed at all. //Damage cannot go below zero. //Cannot remove negative damage (i.e. apply damage) -/obj/item/bodypart/proc/heal_damage(brute, burn, required_bodytype, updating_health = TRUE) +/obj/item/bodypart/proc/heal_damage(brute, burn, updating_health = TRUE, forced = FALSE, required_bodytype) SHOULD_CALL_PARENT(TRUE) - if(required_bodytype && !(bodytype & required_bodytype)) //So we can only heal certain kinds of limbs, ie robotic vs organic. + if(!forced && required_bodytype && !(bodytype & required_bodytype)) //So we can only heal certain kinds of limbs, ie robotic vs organic. return if(brute) @@ -651,7 +656,6 @@ cremation_progress = min(0, cremation_progress - ((brute_dam + burn_dam)*(100/max_damage))) return update_bodypart_damage_state() - ///Sets the damage of a bodypart when it is created. /obj/item/bodypart/proc/set_initial_damage(brute_damage, burn_damage) set_brute_dam(brute_damage) @@ -666,7 +670,6 @@ . = brute_dam brute_dam = new_value - ///Proc to hook behavior associated to the change of the burn_dam variable's value. /obj/item/bodypart/proc/set_burn_dam(new_value) PROTECTED_PROC(TRUE) @@ -678,8 +681,7 @@ //Returns total damage. /obj/item/bodypart/proc/get_damage() - var/total = brute_dam + burn_dam - return total + return brute_dam + burn_dam //Checks disabled status thresholds /obj/item/bodypart/proc/update_disabled() @@ -722,7 +724,6 @@ last_maxed = FALSE set_disabled(FALSE) - ///Proc to change the value of the `disabled` variable and react to the event of its change. /obj/item/bodypart/proc/set_disabled(new_disabled) SHOULD_CALL_PARENT(TRUE) @@ -738,71 +739,91 @@ owner.update_health_hud() //update the healthdoll owner.update_body() +/// Proc to change the value of the `owner` variable and react to the event of its change. +/obj/item/bodypart/proc/update_owner(new_owner) + SHOULD_NOT_OVERRIDE(TRUE) -///Proc to change the value of the `owner` variable and react to the event of its change. -/obj/item/bodypart/proc/set_owner(new_owner) - SHOULD_CALL_PARENT(TRUE) if(owner == new_owner) return FALSE //`null` is a valid option, so we need to use a num var to make it clear no change was made. - var/mob/living/carbon/old_owner = owner - owner = new_owner - SEND_SIGNAL(src, COMSIG_BODYPART_CHANGED_OWNER, new_owner, old_owner) - var/needs_update_disabled = FALSE //Only really relevant if there's an owner - if(old_owner) - if(held_index) - old_owner.on_lost_hand(src) - if(old_owner.hud_used) - var/atom/movable/screen/inventory/hand/hand = old_owner.hud_used.hand_slots["[held_index]"] - if(hand) - hand.update_appearance() - old_owner.update_worn_gloves() - if(speed_modifier) - old_owner.update_bodypart_speed_modifier() - if(length(bodypart_traits)) - old_owner.remove_traits(bodypart_traits, bodypart_trait_source) - if(initial(can_be_disabled)) - if(HAS_TRAIT(old_owner, TRAIT_NOLIMBDISABLE)) - if(!owner || !HAS_TRAIT(owner, TRAIT_NOLIMBDISABLE)) - set_can_be_disabled(initial(can_be_disabled)) - needs_update_disabled = TRUE - UnregisterSignal(old_owner, list( - SIGNAL_REMOVETRAIT(TRAIT_NOLIMBDISABLE), - SIGNAL_ADDTRAIT(TRAIT_NOLIMBDISABLE), - SIGNAL_REMOVETRAIT(TRAIT_NOBLOOD), - SIGNAL_ADDTRAIT(TRAIT_NOBLOOD), - )) - UnregisterSignal(old_owner, COMSIG_ATOM_RESTYLE) - if(owner) - if(held_index) - owner.on_added_hand(src, held_index) - if(owner.hud_used) - var/atom/movable/screen/inventory/hand/hand = owner.hud_used.hand_slots["[held_index]"] - if(hand) - hand.update_appearance() - owner.update_worn_gloves() - if(speed_modifier) - owner.update_bodypart_speed_modifier() - if(length(bodypart_traits)) - owner.add_traits(bodypart_traits, bodypart_trait_source) - if(initial(can_be_disabled)) - if(HAS_TRAIT(owner, TRAIT_NOLIMBDISABLE)) - set_can_be_disabled(FALSE) - needs_update_disabled = FALSE - RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_NOLIMBDISABLE), PROC_REF(on_owner_nolimbdisable_trait_loss)) - RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOLIMBDISABLE), PROC_REF(on_owner_nolimbdisable_trait_gain)) - // Bleeding stuff - RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_NOBLOOD), PROC_REF(on_owner_nobleed_loss)) - RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOBLOOD), PROC_REF(on_owner_nobleed_gain)) - - if(needs_update_disabled) - update_disabled() - RegisterSignal(owner, COMSIG_ATOM_RESTYLE, PROC_REF(on_attempt_feature_restyle_mob)) + SEND_SIGNAL(src, COMSIG_BODYPART_CHANGED_OWNER, new_owner, owner) + + if(owner) + . = owner //return value is old owner + clear_ownership(owner) + if(new_owner) + apply_ownership(new_owner) refresh_bleed_rate() - return old_owner + return . + +/// Run all necessary procs to remove a limbs ownership and remove the appropriate signals and traits +/obj/item/bodypart/proc/clear_ownership(mob/living/carbon/old_owner) + SHOULD_CALL_PARENT(TRUE) + + owner = null + + if(speed_modifier) + old_owner.update_bodypart_speed_modifier() + if(length(bodypart_traits)) + old_owner.remove_traits(bodypart_traits, bodypart_trait_source) + + UnregisterSignal(old_owner, list( + SIGNAL_REMOVETRAIT(TRAIT_NOLIMBDISABLE), + SIGNAL_ADDTRAIT(TRAIT_NOLIMBDISABLE), + SIGNAL_REMOVETRAIT(TRAIT_NOBLOOD), + SIGNAL_ADDTRAIT(TRAIT_NOBLOOD), + )) + + UnregisterSignal(old_owner, COMSIG_ATOM_RESTYLE) + +/// Apply ownership of a limb to someone, giving the appropriate traits, updates and signals +/obj/item/bodypart/proc/apply_ownership(mob/living/carbon/new_owner) + SHOULD_CALL_PARENT(TRUE) + + owner = new_owner + + if(speed_modifier) + owner.update_bodypart_speed_modifier() + if(length(bodypart_traits)) + owner.add_traits(bodypart_traits, bodypart_trait_source) + + if(initial(can_be_disabled)) + if(HAS_TRAIT(owner, TRAIT_NOLIMBDISABLE)) + set_can_be_disabled(FALSE) + + // Listen to disable traits being added + RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_NOLIMBDISABLE), PROC_REF(on_owner_nolimbdisable_trait_loss)) + RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOLIMBDISABLE), PROC_REF(on_owner_nolimbdisable_trait_gain)) + + // Listen to no blood traits being added + RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_NOBLOOD), PROC_REF(on_owner_nobleed_loss)) + RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOBLOOD), PROC_REF(on_owner_nobleed_gain)) + + if(can_be_disabled) + update_disabled() + + RegisterSignal(owner, COMSIG_ATOM_RESTYLE, PROC_REF(on_attempt_feature_restyle_mob)) + + forceMove(owner) + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_forced_removal)) //this must be set after we moved, or we insta gib + +/// Called on addition of a bodypart +/obj/item/bodypart/proc/on_adding(mob/living/carbon/new_owner) + SHOULD_CALL_PARENT(TRUE) + + item_flags |= ABSTRACT + ADD_TRAIT(src, TRAIT_NODROP, ORGAN_INSIDE_BODY_TRAIT) + +/// Called on removal of a bodypart. +/obj/item/bodypart/proc/on_removal(mob/living/carbon/old_owner) + SHOULD_CALL_PARENT(TRUE) + + UnregisterSignal(src, COMSIG_MOVABLE_MOVED) + + item_flags &= ~ABSTRACT + REMOVE_TRAIT(src, TRAIT_NODROP, ORGAN_INSIDE_BODY_TRAIT) -/obj/item/bodypart/proc/on_removal() if(!length(bodypart_traits)) return @@ -832,7 +853,6 @@ )) set_disabled(FALSE) - ///Called when TRAIT_PARALYSIS is added to the limb. /obj/item/bodypart/proc/on_paralysis_trait_gain(obj/item/bodypart/source) PROTECTED_PROC(TRUE) @@ -841,7 +861,6 @@ if(can_be_disabled) set_disabled(TRUE) - ///Called when TRAIT_PARALYSIS is removed from the limb. /obj/item/bodypart/proc/on_paralysis_trait_loss(obj/item/bodypart/source) PROTECTED_PROC(TRUE) @@ -850,7 +869,6 @@ if(can_be_disabled) update_disabled() - ///Called when TRAIT_NOLIMBDISABLE is added to the owner. /obj/item/bodypart/proc/on_owner_nolimbdisable_trait_gain(mob/living/carbon/source) PROTECTED_PROC(TRUE) @@ -858,7 +876,6 @@ set_can_be_disabled(FALSE) - ///Called when TRAIT_NOLIMBDISABLE is removed from the owner. /obj/item/bodypart/proc/on_owner_nolimbdisable_trait_loss(mob/living/carbon/source) PROTECTED_PROC(TRUE) @@ -885,7 +902,7 @@ SHOULD_CALL_PARENT(TRUE) if(IS_ORGANIC_LIMB(src)) - if(owner && HAS_TRAIT(owner, TRAIT_HUSK)) + if(!(bodypart_flags & BODYPART_UNHUSKABLE) && owner && HAS_TRAIT(owner, TRAIT_HUSK)) dmg_overlay_type = "" //no damage overlay shown when husked is_husked = TRUE else if(owner && HAS_TRAIT(owner, TRAIT_INVISIBLE_MAN)) @@ -941,8 +958,8 @@ icon_state = initial(icon_state)//no overlays found, we default back to initial icon. return for(var/image/img as anything in standing) - img.pixel_x = px_x - img.pixel_y = px_y + img.pixel_x += px_x + img.pixel_y += px_y add_overlay(standing) ///Generates an /image for the limb to be used as an overlay @@ -966,16 +983,6 @@ var/image/limb = image(layer = -BODYPARTS_LAYER, dir = image_dir) var/image/aux - // Handles making bodyparts look husked - if(is_husked) - limb.icon = icon_husk - limb.icon_state = "[husk_type]_husk_[body_zone]" - icon_exists(limb.icon, limb.icon_state, scream = TRUE) //Prints a stack trace on the first failure of a given iconstate. - . += limb - if(aux_zone) //Hand shit - aux = image(limb.icon, "[husk_type]_husk_[aux_zone]", -aux_layer, image_dir) - . += aux - // Handles invisibility (not alpha or actual invisibility but invisibility) if(is_invisible) limb.icon = icon_invisible @@ -984,38 +991,42 @@ return . // Normal non-husk handling - if(!is_husked) // This is the MEAT of limb icon code - limb.icon = icon_greyscale - if(!should_draw_greyscale || !icon_greyscale) - limb.icon = icon_static - - if(is_dimorphic) //Does this type of limb have sexual dimorphism? - limb.icon_state = "[limb_id]_[body_zone]_[limb_gender]" - else - limb.icon_state = "[limb_id]_[body_zone]" + limb.icon = icon_greyscale + if(!should_draw_greyscale || !icon_greyscale) + limb.icon = icon_static - icon_exists(limb.icon, limb.icon_state, TRUE) //Prints a stack trace on the first failure of a given iconstate. + if(is_dimorphic) //Does this type of limb have sexual dimorphism? + limb.icon_state = "[limb_id]_[body_zone]_[limb_gender]" + else + limb.icon_state = "[limb_id]_[body_zone]" - . += limb + icon_exists(limb.icon, limb.icon_state, TRUE) //Prints a stack trace on the first failure of a given iconstate. - if(aux_zone) //Hand shit - aux = image(limb.icon, "[limb_id]_[aux_zone]", -aux_layer, image_dir) - . += aux + . += limb - draw_color = variable_color - if(should_draw_greyscale) //Should the limb be colored outside of a forced color? - draw_color ||= (species_color) || (skin_tone && skintone2hex(skin_tone)) + if(aux_zone) //Hand shit + aux = image(limb.icon, "[limb_id]_[aux_zone]", -aux_layer, image_dir) + . += aux + draw_color = variable_color + if(should_draw_greyscale) //Should the limb be colored outside of a forced color? + draw_color ||= (species_color) || (skin_tone && skintone2hex(skin_tone)) - if(draw_color) - limb.color = "[draw_color]" - if(aux_zone) - aux.color = "[draw_color]" + if(is_husked) + huskify_image(thing_to_husk = limb) + if(aux) + huskify_image(thing_to_husk = aux) + draw_color = husk_color + if(draw_color) + limb.color = "[draw_color]" + if(aux_zone) + aux.color = "[draw_color]" //EMISSIVE CODE START // For some reason this was applied as an overlay on the aux image and limb image before. // I am very sure that this is unnecessary, and i need to treat it as part of the return list // to be able to mask it proper in case this limb is a leg. + if(!is_husked) if(blocks_emissive != EMISSIVE_BLOCK_NONE) var/atom/location = loc || owner || src var/mutable_appearance/limb_em_block = emissive_blocker(limb.icon, limb.icon_state, location, layer = limb.layer, alpha = limb.alpha) @@ -1051,6 +1062,17 @@ return . +/obj/item/bodypart/proc/huskify_image(image/thing_to_husk, draw_blood = TRUE) + var/icon/husk_icon = new(thing_to_husk.icon) + husk_icon.ColorTone(HUSK_COLOR_TONE) + thing_to_husk.icon = husk_icon + if(draw_blood) + var/mutable_appearance/husk_blood = mutable_appearance(icon_husk, "[husk_type]_husk_[body_zone]") + husk_blood.blend_mode = BLEND_INSET_OVERLAY + husk_blood.appearance_flags |= RESET_COLOR + husk_blood.dir = thing_to_husk.dir + thing_to_husk.add_overlay(husk_blood) + ///Add a bodypart overlay and call the appropriate update procs /obj/item/bodypart/proc/add_bodypart_overlay(datum/bodypart_overlay/overlay) bodypart_overlays += overlay @@ -1178,12 +1200,12 @@ if(BLEED_OVERLAY_LOW to BLEED_OVERLAY_MED) new_bleed_icon = "[body_zone]_1" if(BLEED_OVERLAY_MED to BLEED_OVERLAY_GUSH) - if(owner.body_position == LYING_DOWN || IS_IN_STASIS(owner) || owner.stat == DEAD) + if(owner.body_position == LYING_DOWN || HAS_TRAIT(owner, TRAIT_STASIS) || owner.stat == DEAD) new_bleed_icon = "[body_zone]_2s" else new_bleed_icon = "[body_zone]_2" if(BLEED_OVERLAY_GUSH to INFINITY) - if(IS_IN_STASIS(owner) || owner.stat == DEAD) + if(HAS_TRAIT(owner, TRAIT_STASIS) || owner.stat == DEAD) new_bleed_icon = "[body_zone]_2s" else new_bleed_icon = "[body_zone]_3" @@ -1212,17 +1234,18 @@ * Arguments: * * gauze- Just the gauze stack we're taking a sheet from to apply here */ -/obj/item/bodypart/proc/apply_gauze(obj/item/stack/gauze) - if(!istype(gauze) || !gauze.absorption_capacity) +/obj/item/bodypart/proc/apply_gauze(obj/item/stack/medical/gauze/new_gauze) + if(!istype(new_gauze) || !new_gauze.absorption_capacity) return var/newly_gauzed = FALSE if(!current_gauze) newly_gauzed = TRUE QDEL_NULL(current_gauze) - current_gauze = new gauze.type(src, 1) - gauze.use(1) + current_gauze = new new_gauze.type(src, 1) + new_gauze.use(1) + current_gauze.gauzed_bodypart = src if(newly_gauzed) - SEND_SIGNAL(src, COMSIG_BODYPART_GAUZED, gauze) + SEND_SIGNAL(src, COMSIG_BODYPART_GAUZED, current_gauze, new_gauze) /** * seep_gauze() is for when a gauze wrapping absorbs blood or pus from wounds, lowering its absorption capacity. @@ -1239,7 +1262,6 @@ if(current_gauze.absorption_capacity <= 0) owner.visible_message(span_danger("\The [current_gauze.name] on [owner]'s [name] falls away in rags."), span_warning("\The [current_gauze.name] on your [name] falls away in rags."), vision_distance=COMBAT_MESSAGE_RANGE) QDEL_NULL(current_gauze) - SEND_SIGNAL(src, COMSIG_BODYPART_GAUZE_DESTROYED) ///Loops through all of the bodypart's external organs and update's their color. /obj/item/bodypart/proc/recolor_external_organs() @@ -1287,9 +1309,10 @@ else update_icon_dropped() +// Note: Does NOT return EMP protection value from parent call or pass it on to subtypes /obj/item/bodypart/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_WIRES || !IS_ROBOTIC_LIMB(src)) + var/protection = ..() + if((protection & EMP_PROTECT_WIRES) || !IS_ROBOTIC_LIMB(src)) return FALSE // with defines at the time of writing, this is 2 brute and 1.5 burn @@ -1306,16 +1329,14 @@ burn_damage *= 2 receive_damage(brute_damage, burn_damage) - do_sparks(number = 1, cardinal_only = FALSE, source = owner) - var/damage_percent_to_max = (get_damage() / max_damage) - if (time_needed && (damage_percent_to_max >= robotic_emp_paralyze_damage_percent_threshold)) - owner.visible_message(span_danger("[owner]'s [src] seems to malfunction!")) + do_sparks(number = 1, cardinal_only = FALSE, source = owner || src) + + if(can_be_disabled && (get_damage() / max_damage) >= robotic_emp_paralyze_damage_percent_threshold) ADD_TRAIT(src, TRAIT_PARALYSIS, EMP_TRAIT) - addtimer(CALLBACK(src, PROC_REF(un_paralyze)), time_needed) - return TRUE + addtimer(TRAIT_CALLBACK_REMOVE(src, TRAIT_PARALYSIS, EMP_TRAIT), time_needed) + owner?.visible_message(span_danger("[owner]'s [plaintext_zone] seems to malfunction!")) -/obj/item/bodypart/proc/un_paralyze() - REMOVE_TRAITS_IN(src, EMP_TRAIT) + return TRUE /// Returns the generic description of our BIO_EXTERNAL feature(s), prioritizing certain ones over others. Returns error on failure. /obj/item/bodypart/proc/get_external_description() diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 5a7343f8a0b3a..fcbde3c76712b 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -26,7 +26,7 @@ if (wounding_type) LAZYSET(limb_owner.body_zone_dismembered_by, body_zone, wounding_type) - drop_limb() + drop_limb(dismembered = TRUE) limb_owner.update_equipment_speed_mods() // Update in case speed affecting item unequipped by dismemberment var/turf/owner_location = limb_owner.loc @@ -67,7 +67,7 @@ if(wounding_type != WOUND_BURN && isturf(chest_owner.loc) && can_bleed()) chest_owner.add_splatter_floor(chest_owner.loc) playsound(get_turf(chest_owner), 'sound/misc/splort.ogg', 80, TRUE) - for(var/obj/item/organ/organ as anything in chest_owner.organs) + for(var/obj/item/organ/organ in contents) var/org_zone = check_zone(organ.zone) if(org_zone != BODY_ZONE_CHEST) continue @@ -75,37 +75,28 @@ organ.forceMove(chest_owner.loc) . += organ - for(var/obj/item/organ/external/ext_organ as anything in src.external_organs) - if(!(ext_organ.organ_flags & ORGAN_UNREMOVABLE)) - ext_organ.Remove(chest_owner) - ext_organ.forceMove(chest_owner.loc) - . += ext_organ - if(cavity_item) cavity_item.forceMove(chest_owner.loc) . += cavity_item cavity_item = null ///limb removal. The "special" argument is used for swapping a limb with a new one without the effects of losing a limb kicking in. -/obj/item/bodypart/proc/drop_limb(special, dismembered) +/obj/item/bodypart/proc/drop_limb(special, dismembered, move_to_floor = TRUE) if(!owner) return var/atom/drop_loc = owner.drop_location() - SEND_SIGNAL(owner, COMSIG_CARBON_REMOVE_LIMB, src, dismembered) - SEND_SIGNAL(src, COMSIG_BODYPART_REMOVED, owner, dismembered) + SEND_SIGNAL(owner, COMSIG_CARBON_REMOVE_LIMB, src, special, dismembered) + SEND_SIGNAL(src, COMSIG_BODYPART_REMOVED, owner, special, dismembered) update_limb(dropping_limb = TRUE) bodypart_flags &= ~BODYPART_IMPLANTED //limb is out and about, it can't really be considered an implant - owner.remove_bodypart(src) + owner.remove_bodypart(src, special) for(var/datum/scar/scar as anything in scars) scar.victim = null LAZYREMOVE(owner.all_scars, scar) - for(var/obj/item/organ/external/ext_organ as anything in external_organs) - ext_organ.transfer_to_limb(src, null) //Null is the second arg because the bodypart is being removed from it's owner. - - var/mob/living/carbon/phantom_owner = set_owner(null) // so we can still refer to the guy who lost their limb after said limb forgets 'em + var/mob/living/carbon/phantom_owner = update_owner(null) // so we can still refer to the guy who lost their limb after said limb forgets 'em for(var/datum/wound/wound as anything in wounds) wound.remove_wound(TRUE) @@ -129,28 +120,23 @@ to_chat(phantom_owner, span_warning("You feel your [mutation] deactivating from the loss of your [body_zone]!")) phantom_owner.dna.force_lose(mutation) - for(var/obj/item/organ/organ as anything in phantom_owner.organs) //internal organs inside the dismembered limb are dropped. - var/org_zone = check_zone(organ.zone) - if(org_zone != body_zone) - continue - organ.transfer_to_limb(src, phantom_owner) - update_icon_dropped() phantom_owner.update_health_hud() //update the healthdoll phantom_owner.update_body() phantom_owner.update_body_parts() - if(!drop_loc) // drop_loc = null happens when a "dummy human" used for rendering icons on prefs screen gets its limbs replaced. - qdel(src) - return - if(bodypart_flags & BODYPART_PSEUDOPART) drop_organs(phantom_owner) //Psuedoparts shouldn't have organs, but just in case qdel(src) return - forceMove(drop_loc) - SEND_SIGNAL(phantom_owner, COMSIG_CARBON_POST_REMOVE_LIMB, src, dismembered) + if(move_to_floor) + if(!drop_loc) // drop_loc = null happens when a "dummy human" used for rendering icons on prefs screen gets its limbs replaced. + qdel(src) + return + forceMove(drop_loc) + + SEND_SIGNAL(phantom_owner, COMSIG_CARBON_POST_REMOVE_LIMB, src, special, dismembered) /** * get_mangled_state() is relevant for flesh and bone bodyparts, and returns whether this bodypart has mangled skin, mangled bone, or both (or neither i guess) @@ -199,48 +185,13 @@ var/datum/wound/loss/dismembering = new return dismembering.apply_dismember(src, wounding_type) -///Transfers the organ to the limb, and to the limb's owner, if it has one. This is done on drop_limb(). -/obj/item/organ/proc/transfer_to_limb(obj/item/bodypart/bodypart, mob/living/carbon/bodypart_owner) - Remove(bodypart_owner) - add_to_limb(bodypart) - -///Adds the organ to a bodypart, used in transfer_to_limb() -/obj/item/organ/proc/add_to_limb(obj/item/bodypart/bodypart) - forceMove(bodypart) - -///Removes the organ from the limb, placing it into nullspace. -/obj/item/organ/proc/remove_from_limb() - moveToNullspace() - -/obj/item/organ/internal/brain/transfer_to_limb(obj/item/bodypart/head/head, mob/living/carbon/human/head_owner) - Remove(head_owner) //Changeling brain concerns are now handled in Remove - forceMove(head) - head.brain = src - if(brainmob) - head.brainmob = brainmob - brainmob = null - head.brainmob.forceMove(head) - head.brainmob.set_stat(DEAD) - -/obj/item/organ/internal/eyes/transfer_to_limb(obj/item/bodypart/head/head, mob/living/carbon/human/head_owner) - head.eyes = src - ..() - -/obj/item/organ/internal/ears/transfer_to_limb(obj/item/bodypart/head/head, mob/living/carbon/human/head_owner) - head.ears = src - ..() - -/obj/item/organ/internal/tongue/transfer_to_limb(obj/item/bodypart/head/head, mob/living/carbon/human/head_owner) - head.tongue = src - ..() - -/obj/item/bodypart/chest/drop_limb(special) +/obj/item/bodypart/chest/drop_limb(special, dismembered, move_to_floor = TRUE) if(special) return ..() //if this is not a special drop, this is a mistake return FALSE -/obj/item/bodypart/arm/drop_limb(special) +/obj/item/bodypart/arm/drop_limb(special, dismembered, move_to_floor = TRUE) var/mob/living/carbon/arm_owner = owner if(special || !arm_owner) @@ -260,10 +211,10 @@ associated_hand?.update_appearance() if(arm_owner.gloves) arm_owner.dropItemToGround(arm_owner.gloves, TRUE) + . = ..() arm_owner.update_worn_gloves() //to remove the bloody hands overlay - return ..() -/obj/item/bodypart/leg/drop_limb(special) +/obj/item/bodypart/leg/drop_limb(special, dismembered, move_to_floor = TRUE) if(owner && !special) if(owner.legcuffed) owner.legcuffed.forceMove(owner.drop_location()) //At this point bodypart is still in nullspace @@ -274,7 +225,7 @@ owner.dropItemToGround(owner.shoes, TRUE) return ..() -/obj/item/bodypart/head/drop_limb(special) +/obj/item/bodypart/head/drop_limb(special, dismembered, move_to_floor = TRUE) if(!special) //Drop all worn head items for(var/obj/item/head_item as anything in list(owner.glasses, owner.ears, owner.wear_mask, owner.head)) @@ -330,8 +281,6 @@ SEND_SIGNAL(new_limb_owner, COMSIG_CARBON_ATTACH_LIMB, src, special) SEND_SIGNAL(src, COMSIG_BODYPART_ATTACHED, new_limb_owner, special) - moveToNullspace() - set_owner(new_limb_owner) new_limb_owner.add_bodypart(src) LAZYREMOVE(new_limb_owner.body_zone_dismembered_by, body_zone) @@ -344,8 +293,10 @@ qdel(attach_surgery) break - for(var/obj/item/organ/limb_organ in contents) - limb_organ.Insert(new_limb_owner, TRUE) + for(var/obj/item/organ/organ as anything in new_limb_owner.organs) + if(deprecise_zone(organ.zone) != body_zone) + continue + organ.bodypart_insert(src) for(var/datum/wound/wound as anything in wounds) // we have to remove the wound from the limb wound list first, so that we can reapply it fresh with the new person @@ -359,7 +310,7 @@ scar.victim = new_limb_owner LAZYADD(new_limb_owner.all_scars, scar) - if(!special && new_limb_owner.mob_mood.has_mood_of_category("dismembered_[body_zone]")) + if(new_limb_owner.mob_mood?.has_mood_of_category("dismembered_[body_zone]")) new_limb_owner.clear_mood_event("dismembered_[body_zone]") new_limb_owner.add_mood_event("phantom_pain_[body_zone]", /datum/mood_event/reattachment, src) @@ -385,15 +336,6 @@ if(!.) return - if(brain) - brain = null - if(tongue) - tongue = null - if(ears) - ears = null - if(eyes) - eyes = null - if(old_real_name) new_head_owner.real_name = old_real_name real_name = new_head_owner.real_name @@ -421,6 +363,14 @@ new_head_owner.update_body() new_head_owner.update_damage_overlays() +/obj/item/bodypart/arm/try_attach_limb(mob/living/carbon/new_arm_owner, special = FALSE) + . = ..() + + if(!.) + return + + new_arm_owner.update_worn_gloves() // To apply bloody hands overlay + /mob/living/carbon/proc/regenerate_limbs(list/excluded_zones = list()) SEND_SIGNAL(src, COMSIG_CARBON_REGENERATE_LIMBS, excluded_zones) var/list/zone_list = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 149490b4a11ab..0373401d9047c 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -3,7 +3,7 @@ desc = "Didn't make sense not to live for fun, your brain gets smart but your head gets dumb." icon = 'icons/mob/human/bodyparts.dmi' icon_state = "default_human_head" - max_damage = 200 + max_damage = LIMB_MAX_HP_CORE body_zone = BODY_ZONE_HEAD body_part = HEAD plaintext_zone = "head" @@ -23,15 +23,9 @@ unarmed_miss_sound = 'sound/weapons/bite.ogg' unarmed_damage_low = 1 // Yeah, biteing is pretty weak, blame the monkey super-nerf unarmed_damage_high = 3 - unarmed_stun_threshold = 4 + unarmed_effectiveness = 0 bodypart_trait_source = HEAD_TRAIT - var/mob/living/brain/brainmob //The current occupant. - var/obj/item/organ/internal/brain/brain //The brain organ - var/obj/item/organ/internal/eyes/eyes - var/obj/item/organ/internal/ears/ears - var/obj/item/organ/internal/tongue/tongue - /// Do we show the information about missing organs upon being examined? Defaults to TRUE, useful for Dullahan heads. var/show_organs_on_examine = TRUE @@ -76,11 +70,6 @@ ///Current lipstick trait, if any (such as TRAIT_KISS_OF_DEATH) var/stored_lipstick_trait - /// Draw this head as "debrained" - VAR_PROTECTED/show_debrained = FALSE - /// Draw this head as missing eyes - VAR_PROTECTED/show_eyeless = FALSE - /// Offset to apply to equipment worn on the ears var/datum/worn_feature_offset/worn_ears_offset /// Offset to apply to equipment worn on the eyes @@ -92,13 +81,17 @@ /// Offset to apply to overlays placed on the face var/datum/worn_feature_offset/worn_face_offset -/obj/item/bodypart/head/Destroy() - QDEL_NULL(brainmob) //order is sensitive, see warning in Exited() below - QDEL_NULL(brain) - QDEL_NULL(eyes) - QDEL_NULL(ears) - QDEL_NULL(tongue) + VAR_PROTECTED + /// Draw this head as "debrained" + show_debrained = FALSE + + /// Draw this head as missing eyes + show_eyeless = FALSE + /// Can this head be dismembered normally? + can_dismember = FALSE + +/obj/item/bodypart/head/Destroy() QDEL_NULL(worn_ears_offset) QDEL_NULL(worn_glasses_offset) QDEL_NULL(worn_mask_offset) @@ -106,35 +99,18 @@ QDEL_NULL(worn_face_offset) return ..() -/obj/item/bodypart/head/Exited(atom/movable/gone, direction) - if(gone == brain) - brain = null - update_icon_dropped() - if(!QDELETED(brainmob)) //this shouldn't happen without badminnery. - message_admins("Brainmob: ([ADMIN_LOOKUPFLW(brainmob)]) was left stranded in [src] at [ADMIN_VERBOSEJMP(src)] without a brain!") - brainmob.log_message(", brainmob, was left stranded in [src] without a brain", LOG_GAME) - if(gone == brainmob) - brainmob = null - if(gone == eyes) - eyes = null - update_icon_dropped() - if(gone == ears) - ears = null - if(gone == tongue) - tongue = null - return ..() - /obj/item/bodypart/head/examine(mob/user) . = ..() if(show_organs_on_examine && IS_ORGANIC_LIMB(src)) + var/obj/item/organ/internal/brain/brain = locate(/obj/item/organ/internal/brain) in src if(!brain) . += span_info("The brain has been removed from [src].") - else if(brain.suicided || (brainmob && HAS_TRAIT(brainmob, TRAIT_SUICIDED))) + else if(brain.suicided || (brain.brainmob && HAS_TRAIT(brain.brainmob, TRAIT_SUICIDED))) . += span_info("There's a miserable expression on [real_name]'s face; they must have really hated life. There's no hope of recovery.") - else if(brainmob?.health <= HEALTH_THRESHOLD_DEAD) - . += span_info("It's leaking some kind of... clear fluid? The brain inside must be in pretty bad shape.") - else if(brainmob) - if(brainmob.key || brainmob.get_ghost(FALSE, TRUE)) + else if(brain.brainmob) + if(brain.brainmob?.health <= HEALTH_THRESHOLD_DEAD) + . += span_info("It's leaking some kind of... clear fluid? The brain inside must be in pretty bad shape.") + if(brain.brainmob.key || brain.brainmob.get_ghost(FALSE, TRUE)) . += span_info("Its muscles are twitching slightly... It seems to have some life still in it.") else . += span_info("It's completely lifeless. Perhaps there'll be a chance for them later.") @@ -143,48 +119,32 @@ else . += span_info("It's completely lifeless.") - if(!eyes) + if(!(locate(/obj/item/organ/internal/eyes) in src)) . += span_info("[real_name]'s eyes have been removed.") - if(!ears) + if(!(locate(/obj/item/organ/internal/ears) in src)) . += span_info("[real_name]'s ears have been removed.") - if(!tongue) + if(!(locate(/obj/item/organ/internal/tongue) in src)) . += span_info("[real_name]'s tongue has been removed.") /obj/item/bodypart/head/can_dismember(obj/item/item) + if (!can_dismember) + return FALSE + if(owner.stat < HARD_CRIT) return FALSE + return ..() /obj/item/bodypart/head/drop_organs(mob/user, violent_removal) - var/atom/drop_loc = drop_location() - for(var/obj/item/head_item in src) - if(head_item == brain) - if(user) - user.visible_message(span_warning("[user] saws [src] open and pulls out a brain!"), span_notice("You saw [src] open and pull out a brain.")) - if(brainmob) - brainmob.container = null - brain.brainmob = brainmob - brainmob = null - if(violent_removal && prob(rand(80, 100))) //ghetto surgery can damage the brain. - to_chat(user, span_warning("[brain] was damaged in the process!")) - brain.set_organ_damage(brain.maxHealth) - brain.forceMove(drop_loc) - brain = null - update_icon_dropped() - else - if(istype(head_item, /obj/item/reagent_containers/pill)) - for(var/datum/action/item_action/hands_free/activate_pill/pill_action in head_item.actions) - qdel(pill_action) - else if(isorgan(head_item)) - var/obj/item/organ/organ = head_item - if(organ.organ_flags & ORGAN_UNREMOVABLE) - continue - head_item.forceMove(drop_loc) - eyes = null - ears = null - tongue = null + if(user) + user.visible_message(span_warning("[user] saws [src] open and pulls out a brain!"), span_notice("You saw [src] open and pull out a brain.")) + var/obj/item/organ/internal/brain/brain = locate(/obj/item/organ/internal/brain) in src + if(brain && violent_removal && prob(90)) //ghetto surgery can damage the brain. + to_chat(user, span_warning("[brain] was damaged in the process!")) + brain.set_organ_damage(brain.maxHealth) + update_limb() return ..() @@ -205,6 +165,7 @@ . += get_hair_and_lips_icon(dropped) // We need to get the eyes if we are dropped (ugh) if(dropped) + var/obj/item/organ/internal/eyes/eyes = locate(/obj/item/organ/internal/eyes) in src // This is a bit of copy/paste code from eyes.dm:generate_body_overlay if(eyes?.eye_icon_state && (head_flags & HEAD_EYESPRITES)) var/image/eye_left = image('icons/mob/human/human_face.dmi', "[eyes.eye_icon_state]_l", -BODY_LAYER, SOUTH) @@ -267,7 +228,7 @@ px_x = 0 px_y = 0 bodypart_flags = BODYPART_UNREMOVABLE - max_damage = 500 + max_damage = LIMB_MAX_HP_ALIEN_CORE bodytype = BODYTYPE_HUMANOID | BODYTYPE_ALIEN | BODYTYPE_ORGANIC /obj/item/bodypart/head/larva @@ -280,5 +241,5 @@ px_x = 0 px_y = 0 bodypart_flags = BODYPART_UNREMOVABLE - max_damage = 50 + max_damage = LIMB_MAX_HP_ALIEN_LARVA bodytype = BODYTYPE_LARVA_PLACEHOLDER | BODYTYPE_ORGANIC diff --git a/code/modules/surgery/bodyparts/head_hair_and_lips.dm b/code/modules/surgery/bodyparts/head_hair_and_lips.dm index 010ed9d111615..aa359c887de9a 100644 --- a/code/modules/surgery/bodyparts/head_hair_and_lips.dm +++ b/code/modules/surgery/bodyparts/head_hair_and_lips.dm @@ -49,12 +49,12 @@ else show_eyeless = FALSE else - if(!hair_hidden && !brain) + if(!hair_hidden && !(locate(/obj/item/organ/internal/brain) in src)) show_debrained = TRUE else show_debrained = FALSE - if(!eyes) + if(!(locate(/obj/item/organ/internal/eyes) in src)) show_eyeless = TRUE else show_eyeless = FALSE @@ -114,16 +114,17 @@ var/facial_hair_gradient_style = LAZYACCESS(gradient_styles, GRADIENT_FACIAL_HAIR_KEY) if(facial_hair_gradient_style) var/facial_hair_gradient_color = LAZYACCESS(gradient_colors, GRADIENT_FACIAL_HAIR_KEY) - var/image/facial_hair_gradient_overlay = get_gradient_overlay(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER, GLOB.facial_hair_gradients_list[facial_hair_gradient_style], facial_hair_gradient_color) + var/image/facial_hair_gradient_overlay = get_gradient_overlay(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER, GLOB.facial_hair_gradients_list[facial_hair_gradient_style], facial_hair_gradient_color, image_dir) . += facial_hair_gradient_overlay var/image/hair_overlay if(!(show_debrained && (head_flags & HEAD_DEBRAIN)) && !hair_hidden && hairstyle && (head_flags & HEAD_HAIR)) - sprite_accessory = GLOB.hairstyles_list[hairstyle] - if(sprite_accessory) + var/datum/sprite_accessory/hair/hair_sprite_accessory = GLOB.hairstyles_list[hairstyle] + if(hair_sprite_accessory) //Overlay - hair_overlay = image(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER, image_dir) + hair_overlay = image(hair_sprite_accessory.icon, hair_sprite_accessory.icon_state, -HAIR_LAYER, image_dir) hair_overlay.alpha = hair_alpha + hair_overlay.pixel_y = hair_sprite_accessory.y_offset //Emissive blocker if(blocks_emissive != EMISSIVE_BLOCK_NONE) hair_overlay.overlays += emissive_blocker(hair_overlay.icon, hair_overlay.icon_state, location, alpha = hair_alpha) @@ -134,7 +135,8 @@ var/hair_gradient_style = LAZYACCESS(gradient_styles, GRADIENT_HAIR_KEY) if(hair_gradient_style) var/hair_gradient_color = LAZYACCESS(gradient_colors, GRADIENT_HAIR_KEY) - var/image/hair_gradient_overlay = get_gradient_overlay(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER, GLOB.hair_gradients_list[hair_gradient_style], hair_gradient_color) + var/image/hair_gradient_overlay = get_gradient_overlay(hair_sprite_accessory.icon, hair_sprite_accessory.icon_state, -HAIR_LAYER, GLOB.hair_gradients_list[hair_gradient_style], hair_gradient_color, image_dir) + hair_gradient_overlay.pixel_y = hair_sprite_accessory.y_offset . += hair_gradient_overlay if(show_debrained && (head_flags & HEAD_DEBRAIN)) @@ -197,12 +199,12 @@ return eyeless_overlay /// Returns an appropriate hair/facial hair gradient overlay -/obj/item/bodypart/head/proc/get_gradient_overlay(file, icon, layer, datum/sprite_accessory/gradient, grad_color) +/obj/item/bodypart/head/proc/get_gradient_overlay(file, icon, layer, datum/sprite_accessory/gradient, grad_color, image_dir) RETURN_TYPE(/mutable_appearance) var/mutable_appearance/gradient_overlay = mutable_appearance(layer = layer) - var/icon/temp = icon(gradient.icon, gradient.icon_state) - var/icon/temp_hair = icon(file, icon) + var/icon/temp = icon(gradient.icon, gradient.icon_state, image_dir) + var/icon/temp_hair = icon(file, icon, image_dir) temp.Blend(temp_hair, ICON_ADD) gradient_overlay.icon = temp gradient_overlay.color = grad_color diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index f80364fcb5db2..52671b5fbc16c 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -15,6 +15,7 @@ /mob/living/carbon/proc/del_and_replace_bodypart(obj/item/bodypart/new_limb, special) var/obj/item/bodypart/old_limb = get_bodypart(new_limb.body_zone) if(old_limb) + old_limb.drop_limb(special = TRUE) qdel(old_limb) new_limb.try_attach_limb(src, special = special) @@ -172,7 +173,7 @@ /mob/living/carbon/proc/synchronize_bodytypes() var/all_limb_flags = NONE for(var/obj/item/bodypart/limb as anything in bodyparts) - for(var/obj/item/organ/external/ext_organ as anything in limb.external_organs) + for(var/obj/item/organ/external/ext_organ in limb) all_limb_flags |= ext_organ.external_bodytypes all_limb_flags |= limb.bodytype @@ -215,3 +216,5 @@ . = "#fff4e6" if("orange") . = "#ffc905" + if("green") + . = "#a8e61d" diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index c3924163784e5..2031ea4c72309 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -3,7 +3,7 @@ name = BODY_ZONE_CHEST desc = "It's impolite to stare at a person's chest." icon_state = "default_human_chest" - max_damage = 200 + max_damage = LIMB_MAX_HP_CORE body_zone = BODY_ZONE_CHEST body_part = CHEST plaintext_zone = "chest" @@ -32,9 +32,21 @@ var/datum/worn_feature_offset/worn_suit_offset /// Offset to apply to equipment worn on the neck var/datum/worn_feature_offset/worn_neck_offset + /// Which functional (i.e. flightpotion) wing types (if any) does this bodypart support? If count is >1 a radial menu is used to choose between all icons in list + var/list/wing_types = list(/obj/item/organ/external/wings/functional/angel) + +/obj/item/bodypart/chest/forced_removal(dismembered, special, move_to_floor) + var/mob/living/carbon/old_owner = owner + ..(special = TRUE) //special because we're self destructing + + //If someones chest is teleported away, they die pretty hard + if(!old_owner) + return + message_admins("[ADMIN_LOOKUPFLW(old_owner)] was gibbed after their chest teleported to [ADMIN_VERBOSEJMP(loc)].") + old_owner.gib(DROP_ALL_REMAINS) /obj/item/bodypart/chest/can_dismember(obj/item/item) - if(owner.stat < HARD_CRIT || !get_organs()) + if(owner.stat < HARD_CRIT || !contents.len) return FALSE return ..() @@ -79,8 +91,9 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE bodypart_flags = BODYPART_UNREMOVABLE - max_damage = 500 + max_damage = LIMB_MAX_HP_ALIEN_CORE acceptable_bodytype = BODYTYPE_HUMANOID + wing_types = NONE /obj/item/bodypart/chest/larva icon = 'icons/mob/human/species/alien/bodyparts.dmi' @@ -90,9 +103,10 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE bodypart_flags = BODYPART_UNREMOVABLE - max_damage = 50 + max_damage = LIMB_MAX_HP_ALIEN_LARVA bodytype = BODYTYPE_LARVA_PLACEHOLDER | BODYTYPE_ORGANIC acceptable_bodytype = BODYTYPE_LARVA_PLACEHOLDER + wing_types = NONE /// Parent Type for arms, should not appear in game. /obj/item/bodypart/arm @@ -100,19 +114,21 @@ desc = "Hey buddy give me a HAND and report this to the github because you shouldn't be seeing this." attack_verb_continuous = list("slaps", "punches") attack_verb_simple = list("slap", "punch") - max_damage = 50 + max_damage = LIMB_MAX_HP_DEFAULT aux_layer = BODYPARTS_HIGH_LAYER - body_damage_coeff = 0.75 + body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_DEFAULT can_be_disabled = TRUE unarmed_attack_verb = "punch" /// The classic punch, wonderfully classic and completely random - unarmed_damage_low = 1 + grappled_attack_verb = "pummel" + unarmed_damage_low = 5 unarmed_damage_high = 10 - unarmed_stun_threshold = 10 body_zone = BODY_ZONE_L_ARM /// Datum describing how to offset things worn on the hands of this arm, note that an x offset won't do anything here var/datum/worn_feature_offset/worn_glove_offset /// Datum describing how to offset things held in the hands of this arm, the x offset IS functional here var/datum/worn_feature_offset/held_hand_offset + /// The noun to use when referring to this arm's appendage, e.g. "hand" or "paw" + var/appendage_noun = "hand" biological_state = BIO_STANDARD_JOINTED @@ -121,6 +137,40 @@ QDEL_NULL(held_hand_offset) return ..() +/// We need to clear out hand hud items and appearance, so do that here +/obj/item/bodypart/arm/clear_ownership(mob/living/carbon/old_owner) + ..() + + old_owner.update_worn_gloves() + + if(!held_index) + return + + old_owner.on_lost_hand(src) + + if(!old_owner.hud_used) + return + + var/atom/movable/screen/inventory/hand/hand = old_owner.hud_used.hand_slots["[held_index]"] + hand?.update_appearance() + +/// We need to add hand hud items and appearance, so do that here +/obj/item/bodypart/arm/apply_ownership(mob/living/carbon/new_owner) + ..() + + new_owner.update_worn_gloves() + + if(!held_index) + return + + new_owner.on_added_hand(src, held_index) + + if(!new_owner.hud_used) + return + + var/atom/movable/screen/inventory/hand/hand = new_owner.hud_used.hand_slots["[held_index]"] + hand.update_appearance() + /obj/item/bodypart/arm/left name = "left arm" desc = "Did you know that the word 'sinister' stems originally from the \ @@ -137,27 +187,22 @@ px_y = 0 bodypart_trait_source = LEFT_ARM_TRAIT - -/obj/item/bodypart/arm/left/set_owner(new_owner) - . = ..() - if(. == FALSE) - return - if(owner) - if(HAS_TRAIT(owner, TRAIT_PARALYSIS_L_ARM)) - ADD_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_ARM) - RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_ARM), PROC_REF(on_owner_paralysis_loss)) - else - REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_ARM) - RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_ARM), PROC_REF(on_owner_paralysis_gain)) - if(.) - var/mob/living/carbon/old_owner = . - if(HAS_TRAIT(old_owner, TRAIT_PARALYSIS_L_ARM)) - UnregisterSignal(old_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_ARM)) - if(!owner || !HAS_TRAIT(owner, TRAIT_PARALYSIS_L_ARM)) - REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_ARM) - else - UnregisterSignal(old_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_ARM)) - +/obj/item/bodypart/arm/left/apply_ownership(mob/living/carbon/new_owner) + if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_L_ARM)) + ADD_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_ARM) + RegisterSignal(new_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_ARM), PROC_REF(on_owner_paralysis_loss)) + else + REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_ARM) + RegisterSignal(new_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_ARM), PROC_REF(on_owner_paralysis_gain)) + ..() + +/obj/item/bodypart/arm/left/clear_ownership(mob/living/carbon/old_owner) + if(HAS_TRAIT(old_owner, TRAIT_PARALYSIS_L_ARM)) + UnregisterSignal(old_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_ARM)) + REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_ARM) + else + UnregisterSignal(old_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_ARM)) + ..() ///Proc to react to the owner gaining the TRAIT_PARALYSIS_L_ARM trait. /obj/item/bodypart/arm/left/proc/on_owner_paralysis_gain(mob/living/carbon/source) @@ -166,7 +211,6 @@ UnregisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_ARM)) RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_ARM), PROC_REF(on_owner_paralysis_loss)) - ///Proc to react to the owner losing the TRAIT_PARALYSIS_L_ARM trait. /obj/item/bodypart/arm/left/proc/on_owner_paralysis_loss(mob/living/carbon/source) SIGNAL_HANDLER @@ -174,7 +218,6 @@ UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_ARM)) RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_ARM), PROC_REF(on_owner_paralysis_gain)) - /obj/item/bodypart/arm/left/set_disabled(new_disabled) . = ..() if(isnull(.) || !owner) @@ -184,7 +227,7 @@ if(bodypart_disabled) owner.set_usable_hands(owner.usable_hands - 1) if(owner.stat < UNCONSCIOUS) - to_chat(owner, span_userdanger("You lose control of your [name]!")) + to_chat(owner, span_userdanger("You lose control of your [plaintext_zone]!")) if(held_index) owner.dropItemToGround(owner.get_item_for_held_index(held_index)) else if(!bodypart_disabled) @@ -194,7 +237,6 @@ var/atom/movable/screen/inventory/hand/hand_screen_object = owner.hud_used.hand_slots["[held_index]"] hand_screen_object?.update_appearance() - /obj/item/bodypart/arm/left/monkey icon = 'icons/mob/human/species/monkey/bodyparts.dmi' icon_static = 'icons/mob/human/species/monkey/bodyparts.dmi' @@ -210,7 +252,8 @@ dmg_overlay_type = SPECIES_MONKEY unarmed_damage_low = 1 /// monkey punches must be really weak, considering they bite people instead and their bites are weak as hell. unarmed_damage_high = 2 - unarmed_stun_threshold = 3 + unarmed_effectiveness = 0 + appendage_noun = "paw" /obj/item/bodypart/arm/left/alien icon = 'icons/mob/human/species/alien/bodyparts.dmi' @@ -222,9 +265,9 @@ px_y = 0 bodypart_flags = BODYPART_UNREMOVABLE can_be_disabled = FALSE - max_damage = 100 + max_damage = LIMB_MAX_HP_ALIEN_LIMBS should_draw_greyscale = FALSE - + appendage_noun = "scythe-like hand" /obj/item/bodypart/arm/right name = "right arm" @@ -241,26 +284,22 @@ px_y = 0 bodypart_trait_source = RIGHT_ARM_TRAIT -/obj/item/bodypart/arm/right/set_owner(new_owner) - . = ..() - if(. == FALSE) - return - if(owner) - if(HAS_TRAIT(owner, TRAIT_PARALYSIS_R_ARM)) - ADD_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_ARM) - RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_ARM), PROC_REF(on_owner_paralysis_loss)) - else - REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_ARM) - RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_ARM), PROC_REF(on_owner_paralysis_gain)) - if(.) - var/mob/living/carbon/old_owner = . - if(HAS_TRAIT(old_owner, TRAIT_PARALYSIS_R_ARM)) - UnregisterSignal(old_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_ARM)) - if(!owner || !HAS_TRAIT(owner, TRAIT_PARALYSIS_R_ARM)) - REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_ARM) - else - UnregisterSignal(old_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_ARM)) - +/obj/item/bodypart/arm/right/apply_ownership(mob/living/carbon/new_owner) + if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_R_ARM)) + ADD_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_ARM) + RegisterSignal(new_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_ARM), PROC_REF(on_owner_paralysis_loss)) + else + REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_ARM) + RegisterSignal(new_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_ARM), PROC_REF(on_owner_paralysis_gain)) + ..() + +/obj/item/bodypart/arm/right/clear_ownership(mob/living/carbon/old_owner) + if(HAS_TRAIT(old_owner, TRAIT_PARALYSIS_R_ARM)) + UnregisterSignal(old_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_ARM)) + REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_ARM) + else + UnregisterSignal(old_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_ARM)) + ..() ///Proc to react to the owner gaining the TRAIT_PARALYSIS_R_ARM trait. /obj/item/bodypart/arm/right/proc/on_owner_paralysis_gain(mob/living/carbon/source) @@ -269,7 +308,6 @@ UnregisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_ARM)) RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_ARM), PROC_REF(on_owner_paralysis_loss)) - ///Proc to react to the owner losing the TRAIT_PARALYSIS_R_ARM trait. /obj/item/bodypart/arm/right/proc/on_owner_paralysis_loss(mob/living/carbon/source) SIGNAL_HANDLER @@ -277,7 +315,6 @@ UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_ARM)) RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_ARM), PROC_REF(on_owner_paralysis_gain)) - /obj/item/bodypart/arm/right/set_disabled(new_disabled) . = ..() if(isnull(.) || !owner) @@ -287,7 +324,7 @@ if(bodypart_disabled) owner.set_usable_hands(owner.usable_hands - 1) if(owner.stat < UNCONSCIOUS) - to_chat(owner, span_userdanger("You lose control of your [name]!")) + to_chat(owner, span_userdanger("You lose control of your [plaintext_zone]!")) if(held_index) owner.dropItemToGround(owner.get_item_for_held_index(held_index)) else if(!bodypart_disabled) @@ -297,7 +334,6 @@ var/atom/movable/screen/inventory/hand/hand_screen_object = owner.hud_used.hand_slots["[held_index]"] hand_screen_object?.update_appearance() - /obj/item/bodypart/arm/right/monkey icon = 'icons/mob/human/species/monkey/bodyparts.dmi' icon_static = 'icons/mob/human/species/monkey/bodyparts.dmi' @@ -313,7 +349,8 @@ dmg_overlay_type = SPECIES_MONKEY unarmed_damage_low = 1 unarmed_damage_high = 2 - unarmed_stun_threshold = 3 + unarmed_effectiveness = 0 + appendage_noun = "paw" /obj/item/bodypart/arm/right/alien icon = 'icons/mob/human/species/alien/bodyparts.dmi' @@ -325,8 +362,9 @@ px_y = 0 bodypart_flags = BODYPART_UNREMOVABLE can_be_disabled = FALSE - max_damage = 100 + max_damage = LIMB_MAX_HP_ALIEN_LIMBS should_draw_greyscale = FALSE + appendage_noun = "scythe-like hand" /// Parent Type for legs, should not appear in game. /obj/item/bodypart/leg @@ -334,15 +372,15 @@ desc = "This item shouldn't exist. Talk about breaking a leg. Badum-Tss!" attack_verb_continuous = list("kicks", "stomps") attack_verb_simple = list("kick", "stomp") - max_damage = 50 - body_damage_coeff = 0.75 + max_damage = LIMB_MAX_HP_DEFAULT + body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_DEFAULT can_be_disabled = TRUE unarmed_attack_effect = ATTACK_EFFECT_KICK body_zone = BODY_ZONE_L_LEG unarmed_attack_verb = "kick" // The lovely kick, typically only accessable by attacking a grouded foe. 1.5 times better than the punch. - unarmed_damage_low = 2 + unarmed_damage_low = 7 unarmed_damage_high = 15 - unarmed_stun_threshold = 10 + unarmed_effectiveness = 15 /// Datum describing how to offset things worn on the foot of this leg, note that an x offset won't do anything here var/datum/worn_feature_offset/worn_foot_offset @@ -365,35 +403,30 @@ can_be_disabled = TRUE bodypart_trait_source = LEFT_LEG_TRAIT -/obj/item/bodypart/leg/left/set_owner(new_owner) - . = ..() - if(. == FALSE) - return - if(owner) - if(HAS_TRAIT(owner, TRAIT_PARALYSIS_L_LEG)) - ADD_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_LEG) - RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_LEG), PROC_REF(on_owner_paralysis_loss)) - else - REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_LEG) - RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_LEG), PROC_REF(on_owner_paralysis_gain)) - if(.) - var/mob/living/carbon/old_owner = . - if(HAS_TRAIT(old_owner, TRAIT_PARALYSIS_L_LEG)) - UnregisterSignal(old_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_LEG)) - if(!owner || !HAS_TRAIT(owner, TRAIT_PARALYSIS_L_LEG)) - REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_LEG) - else - UnregisterSignal(old_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_LEG)) - - -///Proc to react to the owner gaining the TRAIT_PARALYSIS_L_LEG trait. +/obj/item/bodypart/leg/left/apply_ownership(mob/living/carbon/new_owner) + if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_L_LEG)) + ADD_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_LEG) + RegisterSignal(new_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_LEG), PROC_REF(on_owner_paralysis_loss)) + else + REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_LEG) + RegisterSignal(new_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_LEG), PROC_REF(on_owner_paralysis_gain)) + ..() + +/obj/item/bodypart/leg/left/clear_ownership(mob/living/carbon/old_owner) + if(HAS_TRAIT(old_owner, TRAIT_PARALYSIS_L_LEG)) + UnregisterSignal(old_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_LEG)) + REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_LEG) + else + UnregisterSignal(old_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_LEG)) + ..() + +///Proc to react to the owner gaining the TRAIT_PARALYSIS_L_ARM trait. /obj/item/bodypart/leg/left/proc/on_owner_paralysis_gain(mob/living/carbon/source) SIGNAL_HANDLER ADD_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_L_LEG) UnregisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_LEG)) RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_LEG), PROC_REF(on_owner_paralysis_loss)) - ///Proc to react to the owner losing the TRAIT_PARALYSIS_L_LEG trait. /obj/item/bodypart/leg/left/proc/on_owner_paralysis_loss(mob/living/carbon/source) SIGNAL_HANDLER @@ -401,7 +434,6 @@ UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_L_LEG)) RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_L_LEG), PROC_REF(on_owner_paralysis_gain)) - /obj/item/bodypart/leg/left/set_disabled(new_disabled) . = ..() if(isnull(.) || !owner) @@ -411,7 +443,7 @@ if(bodypart_disabled) owner.set_usable_legs(owner.usable_legs - 1) if(owner.stat < UNCONSCIOUS) - to_chat(owner, span_userdanger("You lose control of your [name]!")) + to_chat(owner, span_userdanger("You lose control of your [plaintext_zone]!")) else if(!bodypart_disabled) owner.set_usable_legs(owner.usable_legs + 1) @@ -430,7 +462,7 @@ dmg_overlay_type = SPECIES_MONKEY unarmed_damage_low = 2 unarmed_damage_high = 3 - unarmed_stun_threshold = 4 + unarmed_effectiveness = 0 /obj/item/bodypart/leg/left/alien icon = 'icons/mob/human/species/alien/bodyparts.dmi' @@ -442,7 +474,7 @@ px_y = 0 bodypart_flags = BODYPART_UNREMOVABLE can_be_disabled = FALSE - max_damage = 100 + max_damage = LIMB_MAX_HP_ALIEN_LIMBS should_draw_greyscale = FALSE /obj/item/bodypart/leg/right @@ -459,26 +491,22 @@ px_y = 12 bodypart_trait_source = RIGHT_LEG_TRAIT -/obj/item/bodypart/leg/right/set_owner(new_owner) - . = ..() - if(. == FALSE) - return - if(owner) - if(HAS_TRAIT(owner, TRAIT_PARALYSIS_R_LEG)) - ADD_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_LEG) - RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_LEG), PROC_REF(on_owner_paralysis_loss)) - else - REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_LEG) - RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_LEG), PROC_REF(on_owner_paralysis_gain)) - if(.) - var/mob/living/carbon/old_owner = . - if(HAS_TRAIT(old_owner, TRAIT_PARALYSIS_R_LEG)) - UnregisterSignal(old_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_LEG)) - if(!owner || !HAS_TRAIT(owner, TRAIT_PARALYSIS_R_LEG)) - REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_LEG) - else - UnregisterSignal(old_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_LEG)) - +/obj/item/bodypart/leg/right/apply_ownership(mob/living/carbon/new_owner) + if(HAS_TRAIT(new_owner, TRAIT_PARALYSIS_R_LEG)) + ADD_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_LEG) + RegisterSignal(new_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_LEG), PROC_REF(on_owner_paralysis_loss)) + else + REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_LEG) + RegisterSignal(new_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_LEG), PROC_REF(on_owner_paralysis_gain)) + ..() + +/obj/item/bodypart/leg/right/clear_ownership(mob/living/carbon/old_owner) + if(HAS_TRAIT(old_owner, TRAIT_PARALYSIS_R_LEG)) + UnregisterSignal(old_owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_LEG)) + REMOVE_TRAIT(src, TRAIT_PARALYSIS, TRAIT_PARALYSIS_R_LEG) + else + UnregisterSignal(old_owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_LEG)) + ..() ///Proc to react to the owner gaining the TRAIT_PARALYSIS_R_LEG trait. /obj/item/bodypart/leg/right/proc/on_owner_paralysis_gain(mob/living/carbon/source) @@ -487,7 +515,6 @@ UnregisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_PARALYSIS_R_LEG)) RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_PARALYSIS_R_LEG), PROC_REF(on_owner_paralysis_loss)) - ///Proc to react to the owner losing the TRAIT_PARALYSIS_R_LEG trait. /obj/item/bodypart/leg/right/proc/on_owner_paralysis_loss(mob/living/carbon/source) SIGNAL_HANDLER @@ -505,7 +532,7 @@ if(bodypart_disabled) owner.set_usable_legs(owner.usable_legs - 1) if(owner.stat < UNCONSCIOUS) - to_chat(owner, span_userdanger("You lose control of your [name]!")) + to_chat(owner, span_userdanger("You lose control of your [plaintext_zone]!")) else if(!bodypart_disabled) owner.set_usable_legs(owner.usable_legs + 1) @@ -524,7 +551,7 @@ dmg_overlay_type = SPECIES_MONKEY unarmed_damage_low = 2 unarmed_damage_high = 3 - unarmed_stun_threshold = 4 + unarmed_effectiveness = 0 /obj/item/bodypart/leg/right/alien icon = 'icons/mob/human/species/alien/bodyparts.dmi' @@ -536,7 +563,7 @@ px_y = 0 bodypart_flags = BODYPART_UNREMOVABLE can_be_disabled = FALSE - max_damage = 100 + max_damage = LIMB_MAX_HP_ALIEN_LIMBS should_draw_greyscale = FALSE /obj/item/bodypart/leg/right/tallboy diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm index 37b6cef989750..3f5e6972ed6b5 100644 --- a/code/modules/surgery/bodyparts/robot_bodyparts.dm +++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm @@ -19,7 +19,7 @@ inhand_icon_state = "buildpipe" icon = 'icons/mob/augmentation/augments.dmi' icon_static = 'icons/mob/augmentation/augments.dmi' - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY icon_state = "borg_l_arm" is_dimorphic = FALSE should_draw_greyscale = FALSE @@ -40,8 +40,9 @@ biological_state = (BIO_ROBOTIC|BIO_JOINTED) - damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) disabling_threshold_percentage = 1 + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/arm/right/robot name = "cyborg right arm" @@ -51,7 +52,7 @@ icon_static = 'icons/mob/augmentation/augments.dmi' icon = 'icons/mob/augmentation/augments.dmi' limb_id = BODYPART_ID_ROBOTIC - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY icon_state = "borg_r_arm" is_dimorphic = FALSE should_draw_greyscale = FALSE @@ -74,7 +75,8 @@ biological_state = (BIO_ROBOTIC|BIO_JOINTED) - damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/leg/left/robot name = "cyborg left leg" @@ -84,7 +86,7 @@ icon_static = 'icons/mob/augmentation/augments.dmi' icon = 'icons/mob/augmentation/augments.dmi' limb_id = BODYPART_ID_ROBOTIC - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY icon_state = "borg_l_leg" is_dimorphic = FALSE should_draw_greyscale = FALSE @@ -107,19 +109,21 @@ biological_state = (BIO_ROBOTIC|BIO_JOINTED) - damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/leg/left/robot/emp_act(severity) . = ..() - if(!.) + if(!. || isnull(owner)) return + var/knockdown_time = AUGGED_LEG_EMP_KNOCKDOWN_TIME if (severity == EMP_HEAVY) knockdown_time *= 2 owner.Knockdown(knockdown_time) if(owner.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB)) // So the message isn't duplicated. If they were stunned beforehand by something else, then the message not showing makes more sense anyways. return - to_chat(owner, span_danger("As your [src] unexpectedly malfunctions, it causes you to fall to the ground!")) + to_chat(owner, span_danger("As your [plaintext_zone] unexpectedly malfunctions, it causes you to fall to the ground!")) /obj/item/bodypart/leg/right/robot name = "cyborg right leg" @@ -129,7 +133,7 @@ icon_static = 'icons/mob/augmentation/augments.dmi' icon = 'icons/mob/augmentation/augments.dmi' limb_id = BODYPART_ID_ROBOTIC - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY icon_state = "borg_r_leg" is_dimorphic = FALSE should_draw_greyscale = FALSE @@ -152,19 +156,21 @@ biological_state = (BIO_ROBOTIC|BIO_JOINTED) - damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/leg/right/robot/emp_act(severity) . = ..() - if(!.) + if(!. || isnull(owner)) return + var/knockdown_time = AUGGED_LEG_EMP_KNOCKDOWN_TIME if (severity == EMP_HEAVY) knockdown_time *= 2 owner.Knockdown(knockdown_time) if(owner.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB)) // So the message isn't duplicated. If they were stunned beforehand by something else, then the message not showing makes more sense anyways. return - to_chat(owner, span_danger("As your [src] unexpectedly malfunctions, it causes you to fall to the ground!")) + to_chat(owner, span_danger("As your [plaintext_zone] unexpectedly malfunctions, it causes you to fall to the ground!")) /obj/item/bodypart/chest/robot name = "cyborg torso" @@ -173,7 +179,7 @@ icon_static = 'icons/mob/augmentation/augments.dmi' icon = 'icons/mob/augmentation/augments.dmi' limb_id = BODYPART_ID_ROBOTIC - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY icon_state = "borg_chest" is_dimorphic = FALSE should_draw_greyscale = FALSE @@ -194,16 +200,19 @@ biological_state = (BIO_ROBOTIC) - damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) + bodypart_flags = BODYPART_UNHUSKABLE + + robotic_emp_paralyze_damage_percent_threshold = 0.6 + + wing_types = list(/obj/item/organ/external/wings/functional/robotic) var/wired = FALSE var/obj/item/stock_parts/cell/cell = null - robotic_emp_paralyze_damage_percent_threshold = 0.6 - /obj/item/bodypart/chest/robot/emp_act(severity) . = ..() - if(!.) + if(!. || isnull(owner)) return var/stun_time = 0 @@ -219,7 +228,7 @@ var/damage_percent_to_max = (get_damage() / max_damage) if (stun_time && (damage_percent_to_max >= robotic_emp_paralyze_damage_percent_threshold)) - to_chat(owner, span_danger("Your [src]'s logic boards temporarily become unresponsive!")) + to_chat(owner, span_danger("Your [plaintext_zone]'s logic boards temporarily become unresponsive!")) owner.Stun(stun_time) owner.Shake(pixelshiftx = shift_x, pixelshifty = shift_y, duration = shake_duration) @@ -233,8 +242,46 @@ /obj/item/bodypart/chest/robot/Destroy() QDEL_NULL(cell) + UnregisterSignal(src, COMSIG_BODYPART_ATTACHED) return ..() +/obj/item/bodypart/chest/robot/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_BODYPART_ATTACHED, PROC_REF(on_attached)) + RegisterSignal(src, COMSIG_BODYPART_REMOVED, PROC_REF(on_detached)) + +/obj/item/bodypart/chest/robot/proc/on_attached(obj/item/bodypart/chest/robot/this_bodypart, mob/living/carbon/human/new_owner) + SIGNAL_HANDLER + + RegisterSignals(new_owner, list(COMSIG_CARBON_POST_ATTACH_LIMB, COMSIG_CARBON_POST_REMOVE_LIMB), PROC_REF(check_limbs)) + +/obj/item/bodypart/chest/robot/proc/on_detached(obj/item/bodypart/chest/robot/this_bodypart, mob/living/carbon/human/old_owner) + SIGNAL_HANDLER + + UnregisterSignal(old_owner, list(COMSIG_CARBON_POST_ATTACH_LIMB, COMSIG_CARBON_POST_REMOVE_LIMB)) + +/obj/item/bodypart/chest/robot/proc/check_limbs() + SIGNAL_HANDLER + + var/all_robotic = TRUE + for(var/obj/item/bodypart/part in owner.bodyparts) + all_robotic = all_robotic && IS_ROBOTIC_LIMB(part) + + if(all_robotic) + owner.add_traits(list( + TRAIT_RESISTCOLD, + TRAIT_RESISTHEAT, + TRAIT_RESISTLOWPRESSURE, + TRAIT_RESISTHIGHPRESSURE, + ), AUGMENTATION_TRAIT) + else + owner.remove_traits(list( + TRAIT_RESISTCOLD, + TRAIT_RESISTHEAT, + TRAIT_RESISTLOWPRESSURE, + TRAIT_RESISTHIGHPRESSURE, + ), AUGMENTATION_TRAIT) + /obj/item/bodypart/chest/robot/attackby(obj/item/weapon, mob/user, params) if(istype(weapon, /obj/item/stock_parts/cell)) if(cell) @@ -306,7 +353,7 @@ icon_static = 'icons/mob/augmentation/augments.dmi' icon = 'icons/mob/augmentation/augments.dmi' limb_id = BODYPART_ID_ROBOTIC - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY icon_state = "borg_head" is_dimorphic = FALSE should_draw_greyscale = FALSE @@ -327,9 +374,10 @@ biological_state = (BIO_ROBOTIC) - damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) + damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT) head_flags = HEAD_EYESPRITES + bodypart_flags = BODYPART_UNHUSKABLE var/obj/item/assembly/flash/handheld/flash1 = null var/obj/item/assembly/flash/handheld/flash2 = null @@ -338,9 +386,10 @@ /obj/item/bodypart/head/robot/emp_act(severity) . = ..() - if(!.) + if(!. || isnull(owner)) return - to_chat(owner, span_danger("Your [src]'s optical transponders glitch out and malfunction!")) + + to_chat(owner, span_danger("Your [plaintext_zone]'s optical transponders glitch out and malfunction!")) var/glitch_duration = AUGGED_HEAD_EMP_GLITCH_DURATION if (severity == EMP_HEAVY) @@ -416,10 +465,8 @@ return ..() // Prosthetics - Cheap, mediocre, and worse than organic limbs -// The fact they dont have a internal biotype means theyre a lot weaker defensively, -// since they skip slash and go right to blunt -// They are VERY easy to delimb as a result -// HP is also reduced just in case this isnt enough +// Actively make you less healthy by being on your body, contributing a whopping 250% to overall health at only 20 max health +// They also suck to punch with. /obj/item/bodypart/arm/left/robot/surplus name = "surplus prosthetic left arm" @@ -428,7 +475,11 @@ icon = 'icons/mob/augmentation/surplus_augments.dmi' burn_modifier = 1 brute_modifier = 1 - max_damage = PROSTHESIS_MAX_HP + unarmed_damage_low = 1 + unarmed_damage_high = 5 + unarmed_effectiveness = 0 //Bro, you look huge. + max_damage = LIMB_MAX_HP_PROSTHESIS + body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_PROSTHESIS biological_state = (BIO_METAL|BIO_JOINTED) @@ -439,7 +490,11 @@ icon = 'icons/mob/augmentation/surplus_augments.dmi' burn_modifier = 1 brute_modifier = 1 - max_damage = PROSTHESIS_MAX_HP + unarmed_damage_low = 1 + unarmed_damage_high = 5 + unarmed_effectiveness = 0 + max_damage = LIMB_MAX_HP_PROSTHESIS + body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_PROSTHESIS biological_state = (BIO_METAL|BIO_JOINTED) @@ -450,7 +505,11 @@ icon = 'icons/mob/augmentation/surplus_augments.dmi' brute_modifier = 1 burn_modifier = 1 - max_damage = PROSTHESIS_MAX_HP + unarmed_damage_low = 2 + unarmed_damage_high = 10 + unarmed_effectiveness = 0 + max_damage = LIMB_MAX_HP_PROSTHESIS + body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_PROSTHESIS biological_state = (BIO_METAL|BIO_JOINTED) @@ -461,10 +520,60 @@ icon = 'icons/mob/augmentation/surplus_augments.dmi' brute_modifier = 1 burn_modifier = 1 - max_damage = PROSTHESIS_MAX_HP + unarmed_damage_low = 2 + unarmed_damage_high = 10 + unarmed_effectiveness = 0 + max_damage = LIMB_MAX_HP_PROSTHESIS + body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_PROSTHESIS biological_state = (BIO_METAL|BIO_JOINTED) +// Advanced Limbs: More durable, high punching force + +/obj/item/bodypart/arm/left/robot/advanced + name = "advanced robotic left arm" + desc = "An advanced cybernetic arm, capable of greater feats of strength and durability." + icon_static = 'icons/mob/augmentation/advanced_augments.dmi' + icon = 'icons/mob/augmentation/advanced_augments.dmi' + unarmed_damage_low = 5 + unarmed_damage_high = 13 + unarmed_effectiveness = 20 + max_damage = LIMB_MAX_HP_ADVANCED + body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_ADVANCED + +/obj/item/bodypart/arm/right/robot/advanced + name = "advanced robotic right arm" + desc = "An advanced cybernetic arm, capable of greater feats of strength and durability." + icon_static = 'icons/mob/augmentation/advanced_augments.dmi' + icon = 'icons/mob/augmentation/advanced_augments.dmi' + unarmed_damage_low = 5 + unarmed_damage_high = 13 + unarmed_effectiveness = 20 + max_damage = LIMB_MAX_HP_ADVANCED + body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_ADVANCED + +/obj/item/bodypart/leg/left/robot/advanced + name = "advanced robotic left leg" + desc = "An advanced cybernetic leg, capable of greater feats of strength and durability." + icon_static = 'icons/mob/augmentation/advanced_augments.dmi' + icon = 'icons/mob/augmentation/advanced_augments.dmi' + unarmed_damage_low = 7 + unarmed_damage_high = 17 + unarmed_effectiveness = 20 + max_damage = LIMB_MAX_HP_ADVANCED + body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_ADVANCED + +/obj/item/bodypart/leg/right/robot/advanced + name = "heavy robotic right leg" + desc = "An advanced cybernetic leg, capable of greater feats of strength and durability." + icon_static = 'icons/mob/augmentation/advanced_augments.dmi' + icon = 'icons/mob/augmentation/advanced_augments.dmi' + unarmed_damage_low = 7 + unarmed_damage_high = 17 + unarmed_effectiveness = 20 + max_damage = LIMB_MAX_HP_ADVANCED + body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_ADVANCED + #undef ROBOTIC_LIGHT_BRUTE_MSG #undef ROBOTIC_MEDIUM_BRUTE_MSG #undef ROBOTIC_HEAVY_BRUTE_MSG diff --git a/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm index 2215b388320e8..3eeafa6f4e1a8 100644 --- a/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm @@ -22,6 +22,7 @@ is_dimorphic = FALSE dmg_overlay_type = null brute_modifier = 1.25 //ethereal are weak to brute damages + wing_types = NONE /obj/item/bodypart/chest/ethereal/update_limb(dropping_limb, is_creating) . = ..() @@ -36,6 +37,7 @@ dmg_overlay_type = null attack_type = BURN //burn bish unarmed_attack_verb = "burn" + grappled_attack_verb = "scorch" unarmed_attack_sound = 'sound/weapons/etherealhit.ogg' unarmed_miss_sound = 'sound/weapons/etherealmiss.ogg' brute_modifier = 1.25 //ethereal are weak to brute damage @@ -53,6 +55,7 @@ dmg_overlay_type = null attack_type = BURN // bish buzz unarmed_attack_verb = "burn" + grappled_attack_verb = "scorch" unarmed_attack_sound = 'sound/weapons/etherealhit.ogg' unarmed_miss_sound = 'sound/weapons/etherealmiss.ogg' brute_modifier = 1.25 //ethereal are weak to brute damage diff --git a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm index c5f7ff87346d3..24178d9bd1610 100644 --- a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm @@ -8,11 +8,13 @@ icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi' limb_id = SPECIES_LIZARD is_dimorphic = TRUE + wing_types = list(/obj/item/organ/external/wings/functional/dragon) /obj/item/bodypart/arm/left/lizard icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi' limb_id = SPECIES_LIZARD unarmed_attack_verb = "slash" + grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slash.ogg' unarmed_miss_sound = 'sound/weapons/slashmiss.ogg' @@ -21,6 +23,7 @@ icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi' limb_id = SPECIES_LIZARD unarmed_attack_verb = "slash" + grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slash.ogg' unarmed_miss_sound = 'sound/weapons/slashmiss.ogg' diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index 408afea6679d8..83758f920c4cb 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -4,37 +4,48 @@ is_dimorphic = FALSE burn_modifier = 2 head_flags = HEAD_EYESPRITES|HEAD_DEBRAIN + biological_state = (BIO_FLESH|BIO_BLOODED) /obj/item/bodypart/chest/snail limb_id = SPECIES_SNAIL is_dimorphic = FALSE burn_modifier = 2 + biological_state = (BIO_FLESH|BIO_BLOODED) + wing_types = NONE /obj/item/bodypart/arm/left/snail limb_id = SPECIES_SNAIL unarmed_attack_verb = "slap" unarmed_attack_effect = ATTACK_EFFECT_DISARM - unarmed_damage_high = 0.5 //snails are soft and squishy + unarmed_damage_low = 1 + unarmed_damage_high = 2 //snails are soft and squishy burn_modifier = 2 + biological_state = (BIO_FLESH|BIO_BLOODED) /obj/item/bodypart/arm/right/snail limb_id = SPECIES_SNAIL unarmed_attack_verb = "slap" unarmed_attack_effect = ATTACK_EFFECT_DISARM - unarmed_damage_high = 0.5 + unarmed_damage_low = 1 + unarmed_damage_high = 2 //snails are soft and squishy burn_modifier = 2 + biological_state = (BIO_FLESH|BIO_BLOODED) /obj/item/bodypart/leg/left/snail limb_id = SPECIES_SNAIL - unarmed_damage_high = 0.5 + unarmed_damage_low = 1 + unarmed_damage_high = 2 //snails are soft and squishy burn_modifier = 2 speed_modifier = 3 //disgustingly slow + biological_state = (BIO_FLESH|BIO_BLOODED) /obj/item/bodypart/leg/right/snail limb_id = SPECIES_SNAIL - unarmed_damage_high = 0.5 + unarmed_damage_low = 1 + unarmed_damage_high = 2 //snails are soft and squishy burn_modifier = 2 speed_modifier = 3 //disgustingly slow + biological_state = (BIO_FLESH|BIO_BLOODED) ///ABDUCTOR /obj/item/bodypart/head/abductor @@ -47,6 +58,7 @@ limb_id = SPECIES_ABDUCTOR is_dimorphic = FALSE should_draw_greyscale = FALSE + wing_types = NONE /obj/item/bodypart/arm/left/abductor limb_id = SPECIES_ABDUCTOR @@ -81,27 +93,28 @@ is_dimorphic = TRUE dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage + wing_types = list(/obj/item/organ/external/wings/functional/slime) /obj/item/bodypart/arm/left/jelly - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage /obj/item/bodypart/arm/right/jelly - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage /obj/item/bodypart/leg/left/jelly - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage /obj/item/bodypart/leg/right/jelly - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage @@ -117,13 +130,14 @@ biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_SLIMEPERSON is_dimorphic = TRUE + wing_types = list(/obj/item/organ/external/wings/functional/slime) /obj/item/bodypart/arm/left/slime - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_SLIMEPERSON /obj/item/bodypart/arm/right/slime - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_SLIMEPERSON /obj/item/bodypart/leg/left/slime @@ -131,7 +145,7 @@ limb_id = SPECIES_SLIMEPERSON /obj/item/bodypart/leg/right/slime - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_SLIMEPERSON ///LUMINESCENT @@ -145,21 +159,22 @@ biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_LUMINESCENT is_dimorphic = TRUE + wing_types = list(/obj/item/organ/external/wings/functional/slime) /obj/item/bodypart/arm/left/luminescent - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_LUMINESCENT /obj/item/bodypart/arm/right/luminescent - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_LUMINESCENT /obj/item/bodypart/leg/left/luminescent - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_LUMINESCENT /obj/item/bodypart/leg/right/luminescent - biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED) + biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_LUMINESCENT ///ZOMBIE @@ -168,11 +183,13 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE head_flags = HEAD_EYESPRITES|HEAD_DEBRAIN + can_dismember = TRUE /obj/item/bodypart/chest/zombie limb_id = SPECIES_ZOMBIE is_dimorphic = FALSE should_draw_greyscale = FALSE + wing_types = NONE /obj/item/bodypart/arm/left/zombie limb_id = SPECIES_ZOMBIE @@ -211,10 +228,12 @@ limb_id = SPECIES_PODPERSON is_dimorphic = TRUE burn_modifier = 1.25 + wing_types = NONE /obj/item/bodypart/arm/left/pod limb_id = SPECIES_PODPERSON unarmed_attack_verb = "slash" + grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slice.ogg' unarmed_miss_sound = 'sound/weapons/slashmiss.ogg' @@ -223,6 +242,7 @@ /obj/item/bodypart/arm/right/pod limb_id = SPECIES_PODPERSON unarmed_attack_verb = "slash" + grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slice.ogg' unarmed_miss_sound = 'sound/weapons/slashmiss.ogg' @@ -247,6 +267,7 @@ limb_id = SPECIES_FLYPERSON is_dimorphic = TRUE should_draw_greyscale = FALSE + wing_types = list(/obj/item/organ/external/wings/functional/fly) /obj/item/bodypart/arm/left/fly limb_id = SPECIES_FLYPERSON @@ -277,6 +298,7 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE burn_modifier = 1.5 + wing_types = NONE /obj/item/bodypart/arm/left/shadow limb_id = SPECIES_SHADOW @@ -312,6 +334,7 @@ should_draw_greyscale = FALSE dmg_overlay_type = null head_flags = NONE + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/chest/skeleton biological_state = BIO_BONE @@ -319,30 +342,36 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE dmg_overlay_type = null + bodypart_flags = BODYPART_UNHUSKABLE + wing_types = list(/obj/item/organ/external/wings/functional/skeleton) /obj/item/bodypart/arm/left/skeleton biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_SKELETON should_draw_greyscale = FALSE dmg_overlay_type = null + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/arm/right/skeleton biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_SKELETON should_draw_greyscale = FALSE dmg_overlay_type = null + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/leg/left/skeleton biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_SKELETON should_draw_greyscale = FALSE dmg_overlay_type = null + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/leg/right/skeleton biological_state = (BIO_BONE|BIO_JOINTED) limb_id = SPECIES_SKELETON should_draw_greyscale = FALSE dmg_overlay_type = null + bodypart_flags = BODYPART_UNHUSKABLE ///MUSHROOM /obj/item/bodypart/head/mushroom @@ -356,36 +385,56 @@ is_dimorphic = TRUE bodypart_traits = list(TRAIT_NO_JUMPSUIT) burn_modifier = 1.25 + wing_types = NONE /obj/item/bodypart/arm/left/mushroom limb_id = SPECIES_MUSHROOM unarmed_damage_low = 6 unarmed_damage_high = 14 - unarmed_stun_threshold = 14 + unarmed_effectiveness = 15 burn_modifier = 1.25 /obj/item/bodypart/arm/right/mushroom limb_id = SPECIES_MUSHROOM unarmed_damage_low = 6 unarmed_damage_high = 14 - unarmed_stun_threshold = 14 + unarmed_effectiveness = 15 burn_modifier = 1.25 /obj/item/bodypart/leg/left/mushroom limb_id = SPECIES_MUSHROOM unarmed_damage_low = 9 unarmed_damage_high = 21 - unarmed_stun_threshold = 14 + unarmed_effectiveness = 20 burn_modifier = 1.25 - speed_modifier = 0.75 //big big fungus + speed_modifier = 0.75 /obj/item/bodypart/leg/right/mushroom limb_id = SPECIES_MUSHROOM unarmed_damage_low = 9 unarmed_damage_high = 21 - unarmed_stun_threshold = 14 + unarmed_effectiveness = 20 burn_modifier = 1.25 - speed_modifier = 0.75 //big fungus big fungus + speed_modifier = 0.75 + +/// Dullahan head preserves organs inside it +/obj/item/bodypart/head/dullahan + throwforce = 25 // It's also a potent weapon + show_organs_on_examine = FALSE + speech_span = null + +/obj/item/bodypart/head/dullahan/Entered(obj/item/organ/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if (!isorgan(arrived)) + return + arrived.organ_flags |= ORGAN_FROZEN + +/obj/item/bodypart/head/dullahan/Exited(obj/item/organ/gone, direction) + . = ..() + if (!isorgan(gone)) + return + gone.organ_flags &= ~ORGAN_FROZEN + //GOLEM /obj/item/bodypart/head/golem @@ -437,6 +486,7 @@ should_draw_greyscale = FALSE dmg_overlay_type = null bodypart_traits = list(TRAIT_NO_JUMPSUIT) + wing_types = NONE /obj/item/bodypart/chest/golem/Initialize(mapload) worn_belt_offset = new( @@ -458,7 +508,7 @@ bodypart_traits = list(TRAIT_CHUNKYFINGERS, TRAIT_FIST_MINING) unarmed_damage_low = 5 unarmed_damage_high = 14 - unarmed_stun_threshold = 11 + unarmed_effectiveness = 20 /obj/item/bodypart/arm/left/golem/Initialize(mapload) held_hand_offset = new( @@ -469,17 +519,16 @@ ) return ..() -/obj/item/bodypart/arm/left/golem/set_owner(new_owner) +/obj/item/bodypart/arm/left/golem/clear_ownership(mob/living/carbon/old_owner) . = ..() - if (. == FALSE) - return - if (owner) - owner.AddComponentFrom(REF(src), /datum/component/shovel_hands) - if (isnull(.)) - return - var/mob/living/carbon/old_owner = . + old_owner.RemoveComponentSource(REF(src), /datum/component/shovel_hands) +/obj/item/bodypart/arm/left/golem/apply_ownership(mob/living/carbon/new_owner) + . = ..() + + new_owner.AddComponentFrom(REF(src), /datum/component/shovel_hands) + /obj/item/bodypart/arm/right/golem icon = 'icons/mob/human/species/golems.dmi' icon_static = 'icons/mob/human/species/golems.dmi' @@ -492,7 +541,7 @@ bodypart_traits = list(TRAIT_CHUNKYFINGERS, TRAIT_FIST_MINING) unarmed_damage_low = 5 unarmed_damage_high = 14 - unarmed_stun_threshold = 11 + unarmed_effectiveness = 20 /obj/item/bodypart/arm/right/golem/Initialize(mapload) held_hand_offset = new( @@ -503,17 +552,16 @@ ) return ..() -/obj/item/bodypart/arm/right/golem/set_owner(new_owner) +/obj/item/bodypart/arm/right/golem/clear_ownership(mob/living/carbon/old_owner) . = ..() - if (. == FALSE) - return - if (owner) - owner.AddComponentFrom(REF(src), /datum/component/shovel_hands) - if (isnull(.)) - return - var/mob/living/carbon/old_owner = . + old_owner.RemoveComponentSource(REF(src), /datum/component/shovel_hands) +/obj/item/bodypart/arm/right/golem/apply_ownership(mob/living/carbon/new_owner) + . = ..() + + new_owner.AddComponentFrom(REF(src), /datum/component/shovel_hands) + /obj/item/bodypart/leg/left/golem icon = 'icons/mob/human/species/golems.dmi' icon_static = 'icons/mob/human/species/golems.dmi' @@ -525,7 +573,7 @@ dmg_overlay_type = null unarmed_damage_low = 7 unarmed_damage_high = 21 - unarmed_stun_threshold = 11 + unarmed_effectiveness = 25 /obj/item/bodypart/leg/right/golem icon = 'icons/mob/human/species/golems.dmi' @@ -538,4 +586,46 @@ dmg_overlay_type = null unarmed_damage_low = 7 unarmed_damage_high = 21 - unarmed_stun_threshold = 11 + unarmed_effectiveness = 25 + +///flesh + +/obj/item/bodypart/arm/left/flesh + limb_id = BODYPART_ID_MEAT + should_draw_greyscale = FALSE + +/obj/item/bodypart/arm/left/flesh/Initialize(mapload, dont_spawn_flesh = FALSE) + . = ..() + if(!dont_spawn_flesh) + new /mob/living/basic/living_limb_flesh(src, src) + ADD_TRAIT(src, TRAIT_IGNORED_BY_LIVING_FLESH, BODYPART_TRAIT) + +/obj/item/bodypart/arm/right/flesh + limb_id = BODYPART_ID_MEAT + should_draw_greyscale = FALSE + +/obj/item/bodypart/arm/right/flesh/Initialize(mapload, dont_spawn_flesh = FALSE) + . = ..() + if(!dont_spawn_flesh) + new /mob/living/basic/living_limb_flesh(src, src) + ADD_TRAIT(src, TRAIT_IGNORED_BY_LIVING_FLESH, BODYPART_TRAIT) + +/obj/item/bodypart/leg/left/flesh + limb_id = BODYPART_ID_MEAT + should_draw_greyscale = FALSE + +/obj/item/bodypart/leg/left/flesh/Initialize(mapload, dont_spawn_flesh = FALSE) + . = ..() + if(!dont_spawn_flesh) + new /mob/living/basic/living_limb_flesh(src, src) + ADD_TRAIT(src, TRAIT_IGNORED_BY_LIVING_FLESH, BODYPART_TRAIT) + +/obj/item/bodypart/leg/right/flesh + limb_id = BODYPART_ID_MEAT + should_draw_greyscale = FALSE + +/obj/item/bodypart/leg/right/flesh/Initialize(mapload, dont_spawn_flesh = FALSE) + . = ..() + if(!dont_spawn_flesh) + new /mob/living/basic/living_limb_flesh(src, src) + ADD_TRAIT(src, TRAIT_IGNORED_BY_LIVING_FLESH, BODYPART_TRAIT) diff --git a/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm index ed6c91ed0fc10..faed53371af60 100644 --- a/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm @@ -14,6 +14,7 @@ limb_id = SPECIES_MOTH is_dimorphic = TRUE should_draw_greyscale = FALSE + wing_types = list(/obj/item/organ/external/wings/functional/moth/megamoth, /obj/item/organ/external/wings/functional/moth/mothra) /obj/item/bodypart/arm/left/moth icon = 'icons/mob/human/species/moth/bodyparts.dmi' @@ -22,6 +23,7 @@ limb_id = SPECIES_MOTH should_draw_greyscale = FALSE unarmed_attack_verb = "slash" + grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slash.ogg' unarmed_miss_sound = 'sound/weapons/slashmiss.ogg' @@ -33,6 +35,7 @@ limb_id = SPECIES_MOTH should_draw_greyscale = FALSE unarmed_attack_verb = "slash" + grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slash.ogg' unarmed_miss_sound = 'sound/weapons/slashmiss.ogg' diff --git a/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm index f478d522d5690..8ba27c2cdf9d0 100644 --- a/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm @@ -10,6 +10,7 @@ brute_modifier = 1.5 //Plasmemes are weak burn_modifier = 1.5 //Plasmemes are weak head_flags = HEAD_EYESPRITES + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/chest/plasmaman icon = 'icons/mob/human/species/plasmaman/bodyparts.dmi' @@ -22,6 +23,8 @@ dmg_overlay_type = null brute_modifier = 1.5 //Plasmemes are weak burn_modifier = 1.5 //Plasmemes are weak + bodypart_flags = BODYPART_UNHUSKABLE + wing_types = NONE /obj/item/bodypart/arm/left/plasmaman icon = 'icons/mob/human/species/plasmaman/bodyparts.dmi' @@ -33,6 +36,7 @@ dmg_overlay_type = null brute_modifier = 1.5 //Plasmemes are weak burn_modifier = 1.5 //Plasmemes are weak + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/arm/right/plasmaman icon = 'icons/mob/human/species/plasmaman/bodyparts.dmi' @@ -44,6 +48,7 @@ dmg_overlay_type = null brute_modifier = 1.5 //Plasmemes are weak burn_modifier = 1.5 //Plasmemes are weak + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/leg/left/plasmaman icon = 'icons/mob/human/species/plasmaman/bodyparts.dmi' @@ -55,6 +60,7 @@ dmg_overlay_type = null brute_modifier = 1.5 //Plasmemes are weak burn_modifier = 1.5 //Plasmemes are weak + bodypart_flags = BODYPART_UNHUSKABLE /obj/item/bodypart/leg/right/plasmaman icon = 'icons/mob/human/species/plasmaman/bodyparts.dmi' @@ -66,3 +72,4 @@ dmg_overlay_type = null brute_modifier = 1.5 //Plasmemes are weak burn_modifier = 1.5 //Plasmemes are weak + bodypart_flags = BODYPART_UNHUSKABLE diff --git a/code/modules/surgery/bodyparts/wounds.dm b/code/modules/surgery/bodyparts/wounds.dm index 1b50dbc8fd169..1fc16c7ca8f8d 100644 --- a/code/modules/surgery/bodyparts/wounds.dm +++ b/code/modules/surgery/bodyparts/wounds.dm @@ -67,12 +67,15 @@ if(HAS_TRAIT(owner, TRAIT_EASYDISMEMBER)) damage *= 1.1 + if(HAS_TRAIT(owner, TRAIT_EASYBLEED) && ((woundtype == WOUND_PIERCE) || (woundtype == WOUND_SLASH))) + damage *= 1.5 + var/base_roll = rand(1, round(damage ** WOUND_DAMAGE_EXPONENT)) var/injury_roll = base_roll injury_roll += check_woundings_mods(woundtype, damage, wound_bonus, bare_wound_bonus) var/list/series_wounding_mods = check_series_wounding_mods() - if(injury_roll > WOUND_DISMEMBER_OUTRIGHT_THRESH && prob(get_damage() / max_damage * 100)) + if(injury_roll > WOUND_DISMEMBER_OUTRIGHT_THRESH && prob(get_damage() / max_damage * 100) && can_dismember()) var/datum/wound/loss/dismembering = new dismembering.apply_dismember(src, woundtype, outright = TRUE, attack_direction = attack_direction) return @@ -85,7 +88,7 @@ // quick re-check to see if bare_wound_bonus applies, for the benefit of log_wound(), see about getting the check from check_woundings_mods() somehow if(ishuman(owner)) var/mob/living/carbon/human/human_wearer = owner - var/list/clothing = human_wearer.clothingonpart(src) + var/list/clothing = human_wearer.get_clothing_on_part(src) for(var/obj/item/clothing/clothes_check as anything in clothing) // unlike normal armor checks, we tabluate these piece-by-piece manually so we can also pass on appropriate damage the clothing's limbs if necessary if(clothes_check.get_armor_rating(WOUND)) @@ -120,10 +123,13 @@ possible_wounds -= other_path continue - while (length(possible_wounds)) + while (TRUE) var/datum/wound/possible_wound = pick_weight(possible_wounds) - var/datum/wound_pregen_data/possible_pregen_data = GLOB.all_wound_pregen_data[possible_wound] + if (isnull(possible_wound)) + break + possible_wounds -= possible_wound + var/datum/wound_pregen_data/possible_pregen_data = GLOB.all_wound_pregen_data[possible_wound] var/datum/wound/replaced_wound for(var/datum/wound/existing_wound as anything in wounds) @@ -242,7 +248,7 @@ if(owner && ishuman(owner)) var/mob/living/carbon/human/human_owner = owner - var/list/clothing = human_owner.clothingonpart(src) + var/list/clothing = human_owner.get_clothing_on_part(src) for(var/obj/item/clothing/clothes as anything in clothing) // unlike normal armor checks, we tabluate these piece-by-piece manually so we can also pass on appropriate damage the clothing's limbs if necessary armor_ablation += clothes.get_armor_rating(WOUND) diff --git a/code/modules/surgery/bone_mending.dm b/code/modules/surgery/bone_mending.dm index 48114fe6d04ac..87fc3db0af2c4 100644 --- a/code/modules/surgery/bone_mending.dm +++ b/code/modules/surgery/bone_mending.dm @@ -20,13 +20,6 @@ /datum/surgery_step/close, ) -/datum/surgery/repair_bone_hairline/can_start(mob/living/user, mob/living/carbon/target) - . = ..() - if(.) - var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected) - return(targeted_bodypart.get_wound_type(targetable_wound)) - - ///// Repair Compound Fracture (Critical) /datum/surgery/repair_bone_compound name = "Repair Compound Fracture" @@ -49,12 +42,6 @@ /datum/surgery_step/close, ) -/datum/surgery/repair_bone_compound/can_start(mob/living/user, mob/living/carbon/target) - . = ..() - if(.) - var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected) - return(targeted_bodypart.get_wound_type(targetable_wound)) - //SURGERY STEPS ///// Repair Hairline Fracture (Severe) @@ -153,15 +140,18 @@ var/obj/item/stack/used_stack = tool used_stack.use(1) +#define IMPLEMENTS_THAT_FIX_BONES list( \ + /obj/item/stack/medical/bone_gel = 100, \ + /obj/item/stack/sticky_tape/surgical = 100, \ + /obj/item/stack/sticky_tape/super = 50, \ + /obj/item/stack/sticky_tape = 30, \ +) + ///// Repair Compound Fracture (Crticial) /datum/surgery_step/repair_bone_compound name = "repair compound fracture (bone gel/tape)" - implements = list( - /obj/item/stack/medical/bone_gel = 100, - /obj/item/stack/sticky_tape/surgical = 100, - /obj/item/stack/sticky_tape/super = 50, - /obj/item/stack/sticky_tape = 30) + implements = IMPLEMENTS_THAT_FIX_BONES time = 40 /datum/surgery_step/repair_bone_compound/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) @@ -200,3 +190,79 @@ if(isstack(tool)) var/obj/item/stack/used_stack = tool used_stack.use(1) + +/// Surgery to repair cranial fissures +/datum/surgery/cranial_reconstruction + name = "Cranial reconstruction" + surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB + targetable_wound = /datum/wound/cranial_fissure + possible_locs = list( + BODY_ZONE_HEAD, + ) + steps = list( + /datum/surgery_step/clamp_bleeders/discard_skull_debris, + /datum/surgery_step/repair_skull + ) + +/datum/surgery_step/clamp_bleeders/discard_skull_debris + name = "discard skull debris (hemostat)" + implements = list( + TOOL_HEMOSTAT = 100, + TOOL_WIRECUTTER = 40, + TOOL_SCREWDRIVER = 40, + ) + time = 2.4 SECONDS + preop_sound = 'sound/surgery/hemostat1.ogg' + +/datum/surgery_step/clamp_bleeders/discard_skull_debris/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results( + user, + target, + span_notice("You begin to discard the smaller skull debris in [target]'s [parse_zone(target_zone)]..."), + span_notice("[user] begins to discard the smaller skull debris in [target]'s [parse_zone(target_zone)]..."), + span_notice("[user] begins to poke around in [target]'s [parse_zone(target_zone)]..."), + ) + + display_pain(target, "Your brain feels like it's getting stabbed by little shards of glass!") + +/datum/surgery_step/repair_skull + name = "repair skull (bone gel/tape)" + implements = IMPLEMENTS_THAT_FIX_BONES + time = 4 SECONDS + +/datum/surgery_step/repair_skull/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) + ASSERT(surgery.operated_wound, "Repairing skull without a wound") + + display_results( + user, + target, + span_notice("You begin to repair [target]'s skull as best you can..."), + span_notice("[user] begins to repair [target]'s skull with [tool]."), + span_notice("[user] begins to repair [target]'s skull."), + ) + + display_pain(target, "You can feel pieces of your skull rubbing against your brain!") + +/datum/surgery_step/repair_skull/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) + if (isnull(surgery.operated_wound)) + to_chat(user, span_warning("[target]'s skull is fine!")) + return ..() + + + if (isstack(tool)) + var/obj/item/stack/used_stack = tool + used_stack.use(1) + + display_results( + user, + target, + span_notice("You successfully repair [target]'s skull."), + span_notice("[user] successfully repairs [target]'s skull with [tool]."), + span_notice("[user] successfully repairs [target]'s skull.") + ) + + qdel(surgery.operated_wound) + + return ..() + +#undef IMPLEMENTS_THAT_FIX_BONES diff --git a/code/modules/surgery/brain_surgery.dm b/code/modules/surgery/brain_surgery.dm index 294b87afb866f..f9de1e01514d9 100644 --- a/code/modules/surgery/brain_surgery.dm +++ b/code/modules/surgery/brain_surgery.dm @@ -24,10 +24,7 @@ failure_sound = 'sound/surgery/organ2.ogg' /datum/surgery/brain_surgery/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/internal/brain/target_brain = target.get_organ_slot(ORGAN_SLOT_BRAIN) - if(!target_brain) - return FALSE - return TRUE + return target.get_organ_slot(ORGAN_SLOT_BRAIN) && ..() /datum/surgery_step/fix_brain/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) display_results( diff --git a/code/modules/surgery/burn_dressing.dm b/code/modules/surgery/burn_dressing.dm index c22a8f4be9734..61be9056f6c18 100644 --- a/code/modules/surgery/burn_dressing.dm +++ b/code/modules/surgery/burn_dressing.dm @@ -20,12 +20,14 @@ ) /datum/surgery/debride/can_start(mob/living/user, mob/living/carbon/target) - if(!istype(target)) - return FALSE - if(..()) - var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected) - var/datum/wound/burn/flesh/burn_wound = targeted_bodypart.get_wound_type(targetable_wound) - return(burn_wound && burn_wound.infestation > 0) + . = ..() + if(!.) + return . + + var/datum/wound/burn/flesh/burn_wound = target.get_bodypart(user.zone_selected).get_wound_type(targetable_wound) + // Should be guaranteed to have the wound by this point + ASSERT(burn_wound, "[type] on [target] has no burn wound when it should have been guaranteed to have one by can_start") + return burn_wound.infestation > 0 //SURGERY STEPS diff --git a/code/modules/surgery/core_removal.dm b/code/modules/surgery/core_removal.dm index 4ada9e7b59a32..aa4028997e762 100644 --- a/code/modules/surgery/core_removal.dm +++ b/code/modules/surgery/core_removal.dm @@ -16,9 +16,7 @@ ) /datum/surgery/core_removal/can_start(mob/user, mob/living/target) - if(target.stat == DEAD) - return TRUE - return FALSE + return target.stat == DEAD && ..() //extract brain /datum/surgery_step/extract_core @@ -49,10 +47,10 @@ span_notice("[user] successfully extracts a core from [target]!"), ) - new target_slime.coretype(target_slime.loc) + new target_slime.slime_type.core_type(target_slime.loc) if(target_slime.cores <= 0) - target_slime.icon_state = "[target_slime.colour] baby slime dead-nocore" + target_slime.icon_state = "[target_slime.slime_type.colour] baby slime dead-nocore" return ..() else return FALSE diff --git a/code/modules/surgery/coronary_bypass.dm b/code/modules/surgery/coronary_bypass.dm index 7536598fe7ddd..af08987fd9398 100644 --- a/code/modules/surgery/coronary_bypass.dm +++ b/code/modules/surgery/coronary_bypass.dm @@ -14,10 +14,9 @@ /datum/surgery/coronary_bypass/can_start(mob/user, mob/living/carbon/target) var/obj/item/organ/internal/heart/target_heart = target.get_organ_slot(ORGAN_SLOT_HEART) - if(target_heart) - if(target_heart.damage > 60 && !target_heart.operated) - return TRUE - return FALSE + if(isnull(target_heart) || target_heart.damage < 60 || target_heart.operated) + return FALSE + return ..() //an incision but with greater bleed, and a 90% base success chance diff --git a/code/modules/surgery/ear_surgery.dm b/code/modules/surgery/ear_surgery.dm index 416857bafb256..4333b00913ba3 100644 --- a/code/modules/surgery/ear_surgery.dm +++ b/code/modules/surgery/ear_surgery.dm @@ -23,10 +23,7 @@ time = 64 /datum/surgery/ear_surgery/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/internal/ears/target_ears = target.get_organ_slot(ORGAN_SLOT_EARS) - if(!target_ears) - return FALSE - return TRUE + return target.get_organ_slot(ORGAN_SLOT_EARS) && ..() /datum/surgery_step/fix_ears/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) display_results( diff --git a/code/modules/surgery/experimental_dissection.dm b/code/modules/surgery/experimental_dissection.dm index 6ae5e447e0bf2..69c246d9e8de5 100644 --- a/code/modules/surgery/experimental_dissection.dm +++ b/code/modules/surgery/experimental_dissection.dm @@ -10,16 +10,19 @@ /datum/surgery_step/experimental_dissection, /datum/surgery_step/close, ) - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_MORBID_CURIOSITY + surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_MORBID_CURIOSITY possible_locs = list(BODY_ZONE_CHEST) target_mobtypes = list(/mob/living) /datum/surgery/advanced/experimental_dissection/can_start(mob/user, mob/living/target) . = ..() + if(!.) + return . if(HAS_TRAIT_FROM(target, TRAIT_DISSECTED, EXPERIMENTAL_SURGERY_TRAIT)) return FALSE if(target.stat != DEAD) return FALSE + return . /datum/surgery_step/experimental_dissection name = "dissection" @@ -44,8 +47,7 @@ var/obj/item/research_notes/hand_dossier = user.get_inactive_held_item() hand_dossier.merge(the_dossier) - var/obj/item/bodypart/target_chest = target.get_bodypart(BODY_ZONE_CHEST) - target.apply_damage(80, BRUTE, target_chest) + target.apply_damage(80, BRUTE, BODY_ZONE_CHEST) ADD_TRAIT(target, TRAIT_DISSECTED, EXPERIMENTAL_SURGERY_TRAIT) return ..() @@ -61,8 +63,7 @@ var/obj/item/research_notes/hand_dossier = user.get_inactive_held_item() hand_dossier.merge(the_dossier) - var/obj/item/bodypart/L = target.get_bodypart(BODY_ZONE_CHEST) - target.apply_damage(80, BRUTE, L) + target.apply_damage(80, BRUTE, BODY_ZONE_CHEST) return TRUE ///Calculates how many research points dissecting 'target' is worth. diff --git a/code/modules/surgery/eye_surgery.dm b/code/modules/surgery/eye_surgery.dm index 7b6d7844ee6c4..fb759baca8771 100644 --- a/code/modules/surgery/eye_surgery.dm +++ b/code/modules/surgery/eye_surgery.dm @@ -21,11 +21,7 @@ time = 64 /datum/surgery/eye_surgery/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/internal/eyes/target_eyes = target.get_organ_slot(ORGAN_SLOT_EYES) - if(!target_eyes) - to_chat(user, span_warning("It's hard to do surgery on someone's eyes when [target.p_they()] [target.p_do()]n't have any.")) - return FALSE - return TRUE + return target.get_organ_slot(ORGAN_SLOT_EYES) && ..() /datum/surgery_step/fix_eyes/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) display_results( diff --git a/code/modules/surgery/gastrectomy.dm b/code/modules/surgery/gastrectomy.dm index 417e6716eb57b..a86805e3e5825 100644 --- a/code/modules/surgery/gastrectomy.dm +++ b/code/modules/surgery/gastrectomy.dm @@ -16,10 +16,9 @@ /datum/surgery/gastrectomy/can_start(mob/user, mob/living/carbon/target) var/obj/item/organ/internal/stomach/target_stomach = target.get_organ_slot(ORGAN_SLOT_STOMACH) - if(target_stomach) - if(target_stomach.damage > 50 && !target_stomach.operated) - return TRUE - return FALSE + if(isnull(target_stomach) || target_stomach.damage < 50 || target_stomach.operated) + return FALSE + return ..() ////Gastrectomy, because we truly needed a way to repair stomachs. //95% chance of success to be consistent with most organ-repairing surgeries. @@ -72,4 +71,3 @@ span_warning("[user] cuts the wrong part of [target]'s stomach!"), ) display_pain(target, "Your stomach throbs with pain; it's not getting any better!") - diff --git a/code/modules/surgery/healing.dm b/code/modules/surgery/healing.dm index efef97cca74f3..e2e65003a3231 100644 --- a/code/modules/surgery/healing.dm +++ b/code/modules/surgery/healing.dm @@ -2,7 +2,7 @@ target_mobtypes = list(/mob/living) requires_bodypart_type = NONE replaced_by = /datum/surgery - surgery_flags = SURGERY_IGNORE_CLOTHES | SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB + surgery_flags = SURGERY_IGNORE_CLOTHES | SURGERY_REQUIRE_RESTING possible_locs = list(BODY_ZONE_CHEST) steps = list( /datum/surgery_step/incise, @@ -18,8 +18,11 @@ /datum/surgery/healing/can_start(mob/user, mob/living/patient) . = ..() + if(!.) + return . if(!(patient.mob_biotypes & (MOB_ORGANIC|MOB_HUMANOID))) return FALSE + return . /datum/surgery/healing/New(surgery_target, surgery_location, surgery_bodypart) ..() @@ -27,7 +30,8 @@ steps = list( /datum/surgery_step/incise/nobleed, healing_step_type, //hehe cheeky - /datum/surgery_step/close) + /datum/surgery_step/close, + ) /datum/surgery_step/heal name = "repair body (hemostat)" diff --git a/code/modules/surgery/hepatectomy.dm b/code/modules/surgery/hepatectomy.dm index 1d609836a8e7d..934e6589e9df5 100644 --- a/code/modules/surgery/hepatectomy.dm +++ b/code/modules/surgery/hepatectomy.dm @@ -15,10 +15,9 @@ /datum/surgery/hepatectomy/can_start(mob/user, mob/living/carbon/target) var/obj/item/organ/internal/liver/target_liver = target.get_organ_slot(ORGAN_SLOT_LIVER) - if(target_liver) - if(target_liver.damage > 50 && !target_liver.operated) - return TRUE - return FALSE + if(isnull(target_liver) || target_liver.damage < 50 || target_liver.operated) + return FALSE + return ..() ////hepatectomy, removes damaged parts of the liver so that the liver may regenerate properly //95% chance of success, not 100 because organs are delicate diff --git a/code/modules/surgery/implant_removal.dm b/code/modules/surgery/implant_removal.dm index 7f43d56ffb1b0..a2b9eb33cbf06 100644 --- a/code/modules/surgery/implant_removal.dm +++ b/code/modules/surgery/implant_removal.dm @@ -2,6 +2,7 @@ name = "Implant Removal" target_mobtypes = list(/mob/living) possible_locs = list(BODY_ZONE_CHEST) + surgery_flags = SURGERY_REQUIRE_RESTING steps = list( /datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, @@ -83,6 +84,7 @@ name = "Implant Removal" requires_bodypart_type = BODYTYPE_ROBOTIC target_mobtypes = list(/mob/living/carbon/human) // Simpler mobs don't have bodypart types + surgery_flags = parent_type::surgery_flags | SURGERY_REQUIRE_LIMB steps = list( /datum/surgery_step/mechanic_open, /datum/surgery_step/open_hatch, diff --git a/code/modules/surgery/lipoplasty.dm b/code/modules/surgery/lipoplasty.dm index f635d0da02032..1d2e6b6838167 100644 --- a/code/modules/surgery/lipoplasty.dm +++ b/code/modules/surgery/lipoplasty.dm @@ -10,9 +10,9 @@ ) /datum/surgery/lipoplasty/can_start(mob/user, mob/living/carbon/target) - if(HAS_TRAIT(target, TRAIT_FAT) && target.nutrition >= NUTRITION_LEVEL_WELL_FED) - return TRUE - return FALSE + if(!HAS_TRAIT(target, TRAIT_FAT) || target.nutrition < NUTRITION_LEVEL_WELL_FED) + return FALSE + return ..() //cut fat @@ -24,6 +24,10 @@ /obj/item/hatchet = 35, /obj/item/knife/butcher = 25) time = 64 + preop_sound = list( + /obj/item/circular_saw = 'sound/surgery/saw.ogg', + /obj/item = 'sound/surgery/scalpel1.ogg', + ) /datum/surgery_step/cut_fat/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) user.visible_message(span_notice("[user] begins to cut away [target]'s excess fat."), span_notice("You begin to cut away [target]'s excess fat...")) @@ -55,6 +59,8 @@ TOOL_SCREWDRIVER = 45, TOOL_WIRECUTTER = 35) time = 32 + preop_sound = 'sound/surgery/retractor1.ogg' + success_sound = 'sound/surgery/retractor2.ogg' /datum/surgery_step/remove_fat/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) display_results( diff --git a/code/modules/surgery/lobectomy.dm b/code/modules/surgery/lobectomy.dm index 56e256534f7fb..83f9279818b8d 100644 --- a/code/modules/surgery/lobectomy.dm +++ b/code/modules/surgery/lobectomy.dm @@ -13,10 +13,9 @@ /datum/surgery/lobectomy/can_start(mob/user, mob/living/carbon/target) var/obj/item/organ/internal/lungs/target_lungs = target.get_organ_slot(ORGAN_SLOT_LUNGS) - if(target_lungs) - if(target_lungs.damage > 60 && !target_lungs.operated) - return TRUE - return FALSE + if(isnull(target_lungs) || target_lungs.damage < 60 || target_lungs.operated) + return FALSE + return ..() //lobectomy, removes the most damaged lung lobe with a 95% base success chance diff --git a/code/modules/surgery/organ_manipulation.dm b/code/modules/surgery/organ_manipulation.dm index c194c150d6da5..aaa0bc765e1cb 100644 --- a/code/modules/surgery/organ_manipulation.dm +++ b/code/modules/surgery/organ_manipulation.dm @@ -87,7 +87,7 @@ var/obj/item/tool = user.get_active_held_item() if(step.try_op(user, target, user.zone_selected, tool, src, try_to_fail)) return TRUE - if(tool && tool.item_flags) //Mechanic organ manipulation isn't done with just surgery tools + if(tool && tool.tool_behaviour) //Mechanic organ manipulation isn't done with just surgery tools to_chat(user, span_warning("This step requires a different tool!")) return TRUE diff --git a/code/modules/surgery/organic_steps.dm b/code/modules/surgery/organic_steps.dm index a4c087059d6e9..aa697cb107271 100644 --- a/code/modules/surgery/organic_steps.dm +++ b/code/modules/surgery/organic_steps.dm @@ -77,7 +77,7 @@ /datum/surgery_step/clamp_bleeders/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) if(locate(/datum/surgery_step/saw) in surgery.steps) - target.heal_bodypart_damage(20,0) + target.heal_bodypart_damage(20, 0, target_zone = target_zone) if (ishuman(target)) var/mob/living/carbon/human/human_target = target var/obj/item/bodypart/target_bodypart = human_target.get_bodypart(target_zone) @@ -137,7 +137,7 @@ /datum/surgery_step/close/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) if(locate(/datum/surgery_step/saw) in surgery.steps) - target.heal_bodypart_damage(45,0) + target.heal_bodypart_damage(45, 0, target_zone = target_zone) if (ishuman(target)) var/mob/living/carbon/human/human_target = target var/obj/item/bodypart/target_bodypart = human_target.get_bodypart(target_zone) @@ -156,8 +156,8 @@ /obj/item/melee/arm_blade = 75, /obj/item/fireaxe = 50, /obj/item/hatchet = 35, - /obj/item/knife/butcher = 25, - /obj/item = 20) //20% success (sort of) with any sharp item with a force >= 10 + /obj/item/knife/butcher = 35, + /obj/item = 25) //20% success (sort of) with any sharp item with a force >= 10 time = 54 preop_sound = list( /obj/item/circular_saw = 'sound/surgery/saw.ogg', diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 04103648fda62..d7f08a7be35b6 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -6,6 +6,10 @@ throwforce = 0 /// The mob that owns this organ. var/mob/living/carbon/owner = null + /// Reference to the limb we're inside of + var/obj/item/bodypart/bodypart_owner + /// The cached info about the blood this organ belongs to + var/list/blood_dna_info = list("Synthetic DNA" = "O+") // not every organ spawns inside a person /// The body zone this organ is supposed to inhabit. var/zone = BODY_ZONE_CHEST /** @@ -14,7 +18,7 @@ */ var/slot /// Random flags that describe this organ - var/organ_flags = ORGAN_ORGANIC | ORGAN_EDIBLE + var/organ_flags = ORGAN_ORGANIC | ORGAN_EDIBLE | ORGAN_VIRGIN /// Maximum damage the organ can take, ever. var/maxHealth = STANDARD_ORGAN_THRESHOLD /** @@ -57,6 +61,8 @@ var/list/organ_traits /// Status Effects that are given to the holder of the organ. var/list/organ_effects + /// String displayed when the organ has decayed. + var/failing_desc = "has decayed for too long, and has turned a sickly color. It probably won't work without repairs." // Players can look at prefs before atoms SS init, and without this // they would not be able to see external organs, such as moth wings. @@ -73,116 +79,16 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) volume = reagent_vol,\ after_eat = CALLBACK(src, PROC_REF(OnEatFrom))) -/* - * Insert the organ into the select mob. - * - * receiver - the mob who will get our organ - * special - "quick swapping" an organ out - when TRUE, the mob will be unaffected by not having that organ for the moment - * drop_if_replaced - if there's an organ in the slot already, whether we drop it afterwards - */ -/obj/item/organ/proc/Insert(mob/living/carbon/receiver, special = FALSE, drop_if_replaced = TRUE) - SHOULD_CALL_PARENT(TRUE) - - if(!iscarbon(receiver) || owner == receiver) - return FALSE - - var/obj/item/organ/replaced = receiver.get_organ_slot(slot) - if(replaced) - replaced.Remove(receiver, special = TRUE) - if(drop_if_replaced) - replaced.forceMove(get_turf(receiver)) - else - qdel(replaced) - - receiver.organs |= src - receiver.organs_slot[slot] = src - owner = receiver - - // Apply unique side-effects. Return value does not matter. - on_insert(receiver, special) - - return TRUE - -/// Called after the organ is inserted into a mob. -/// Adds Traits, Actions, and Status Effects on the mob in which the organ is impanted. -/// Override this proc to create unique side-effects for inserting your organ. Must be called by overrides. -/obj/item/organ/proc/on_insert(mob/living/carbon/organ_owner, special) - SHOULD_CALL_PARENT(TRUE) - - moveToNullspace() - - for(var/trait in organ_traits) - ADD_TRAIT(organ_owner, trait, REF(src)) - - for(var/datum/action/action as anything in actions) - action.Grant(organ_owner) - - for(var/datum/status_effect/effect as anything in organ_effects) - organ_owner.apply_status_effect(effect, type) - - RegisterSignal(owner, COMSIG_ATOM_EXAMINE, PROC_REF(on_owner_examine)) - SEND_SIGNAL(src, COMSIG_ORGAN_IMPLANTED, organ_owner) - SEND_SIGNAL(organ_owner, COMSIG_CARBON_GAIN_ORGAN, src, special) - -/* - * Remove the organ from the select mob. - * - * * organ_owner - the mob who owns our organ, that we're removing the organ from. - * * special - "quick swapping" an organ out - when TRUE, the mob will be unaffected by not having that organ for the moment - */ -/obj/item/organ/proc/Remove(mob/living/carbon/organ_owner, special = FALSE) - SHOULD_CALL_PARENT(TRUE) - - organ_owner.organs -= src - if(organ_owner.organs_slot[slot] == src) - organ_owner.organs_slot.Remove(slot) - - owner = null - - // Apply or reset unique side-effects. Return value does not matter. - on_remove(organ_owner, special) - - return TRUE - -/// Called after the organ is removed from a mob. -/// Removes Traits, Actions, and Status Effects on the mob in which the organ was impanted. -/// Override this proc to create unique side-effects for removing your organ. Must be called by overrides. -/obj/item/organ/proc/on_remove(mob/living/carbon/organ_owner, special) - SHOULD_CALL_PARENT(TRUE) - - if(!iscarbon(organ_owner)) - stack_trace("Organ removal should not be happening on non carbon mobs: [organ_owner]") - - for(var/trait in organ_traits) - REMOVE_TRAIT(organ_owner, trait, REF(src)) - - for(var/datum/action/action as anything in actions) - action.Remove(organ_owner) - - for(var/datum/status_effect/effect as anything in organ_effects) - organ_owner.remove_status_effect(effect, type) - - UnregisterSignal(organ_owner, COMSIG_ATOM_EXAMINE) - SEND_SIGNAL(src, COMSIG_ORGAN_REMOVED, organ_owner) - SEND_SIGNAL(organ_owner, COMSIG_CARBON_LOSE_ORGAN, src, special) - - var/list/diseases = organ_owner.get_static_viruses() - if(!LAZYLEN(diseases)) - return - - var/list/datum/disease/diseases_to_add = list() - for(var/datum/disease/disease as anything in diseases) - // robotic organs are immune to disease unless 'inorganic biology' symptom is present - if(IS_ROBOTIC_ORGAN(src) && !(disease.infectable_biotypes & MOB_ROBOTIC)) - continue - - // admin or special viruses that should not be reproduced - if(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS)) - continue - - diseases_to_add += disease - if(LAZYLEN(diseases_to_add)) - AddComponent(/datum/component/infective, diseases_to_add) +/obj/item/organ/Destroy() + if(bodypart_owner && !owner && !QDELETED(bodypart_owner)) + bodypart_remove(bodypart_owner) + else if(owner) + // The special flag is important, because otherwise mobs can die + // while undergoing transformation into different mobs. + Remove(owner, special=TRUE) + else + STOP_PROCESSING(SSobj, src) + return ..() /// Add a Trait to an organ that it will give its owner. /obj/item/organ/proc/add_organ_trait(trait) @@ -219,13 +125,12 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /obj/item/organ/proc/on_find(mob/living/finder) return -/** - * Proc that gets called when the organ is surgically removed by someone, can be used for special effects - * Currently only used so surplus organs can explode when surgically removed. - */ -/obj/item/organ/proc/on_surgical_removal(mob/living/user, mob/living/carbon/old_owner, target_zone, obj/item/tool) - SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_ORGAN_SURGICALLY_REMOVED, user, old_owner, target_zone, tool) +/obj/item/organ/wash(clean_types) + . = ..() + + // always add the original dna to the organ after it's washed + if(!IS_ROBOTIC_ORGAN(src) && (clean_types & CLEAN_TYPE_BLOOD)) + add_blood_DNA(blood_dna_info) /obj/item/organ/process(seconds_per_tick, times_fired) return @@ -242,10 +147,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) . += span_notice("It should be inserted in the [parse_zone(zone)].") if(organ_flags & ORGAN_FAILING) - if(IS_ROBOTIC_ORGAN(src)) - . += span_warning("[src] seems to be broken.") - return - . += span_warning("[src] has decayed for too long, and has turned a sickly color. It probably won't work without repairs.") + . += span_warning("[src] [failing_desc]") return if(damage > high_threshold) @@ -268,17 +170,18 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /obj/item/organ/item_action_slot_check(slot,mob/user) return //so we don't grant the organ's action to mobs who pick up the organ. -///Adjusts an organ's damage by the amount "damage_amount", up to a maximum amount, which is by default max damage +///Adjusts an organ's damage by the amount "damage_amount", up to a maximum amount, which is by default max damage. Returns the net change in organ damage. /obj/item/organ/proc/apply_organ_damage(damage_amount, maximum = maxHealth, required_organ_flag = NONE) //use for damaging effects if(!damage_amount) //Micro-optimization. - return + return FALSE maximum = clamp(maximum, 0, maxHealth) // the logical max is, our max if(maximum < damage) - return + return FALSE if(required_organ_flag && !(organ_flags & required_organ_flag)) - return + return FALSE damage = clamp(damage + damage_amount, 0, maximum) - var/mess = check_damage_thresholds(owner) + . = (prev_damage - damage) // return net damage + var/message = check_damage_thresholds(owner) prev_damage = damage if(damage >= maxHealth) @@ -286,8 +189,8 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) else organ_flags &= ~ORGAN_FAILING - if(mess && owner && owner.stat <= SOFT_CRIT) - to_chat(owner, mess) + if(message && owner && owner.stat <= SOFT_CRIT) + to_chat(owner, message) ///SETS an organ's damage to the amount "damage_amount", and in doing so clears or sets the failing flag, good for when you have an effect that should fix an organ if broken /obj/item/organ/proc/set_organ_damage(damage_amount, required_organ_flag = NONE) //use mostly for admin heals @@ -424,4 +327,4 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /// Tries to replace the existing organ on the passed mob with this one, with special handling for replacing a brain without ghosting target /obj/item/organ/proc/replace_into(mob/living/carbon/new_owner) - Insert(new_owner, special = TRUE, drop_if_replaced = FALSE) + return Insert(new_owner, special = TRUE, movement_flags = DELETE_IF_REPLACED) diff --git a/code/modules/surgery/organs/autosurgeon.dm b/code/modules/surgery/organs/autosurgeon.dm index 0987df92bd94e..b577b9f8ec048 100644 --- a/code/modules/surgery/organs/autosurgeon.dm +++ b/code/modules/surgery/organs/autosurgeon.dm @@ -2,7 +2,7 @@ name = "autosurgeon" desc = "A device that automatically inserts an implant, skillchip or organ into the user without the hassle of extensive surgery. \ It has a slot to insert implants or organs and a screwdriver slot for removing accidentally added items." - icon = 'icons/obj/device.dmi' + icon = 'icons/obj/devices/tool.dmi' icon_state = "autosurgeon" inhand_icon_state = "nothing" w_class = WEIGHT_CLASS_SMALL @@ -74,15 +74,21 @@ return if(implant_time) - user.visible_message( "[user] prepares to use [src] on [target].", "You begin to prepare to use [src] on [target].") - if(!do_after(user, (8 SECONDS * surgery_speed), target)) + user.visible_message( + span_notice("[user] prepares to use [src] on [target]."), + span_notice("You begin to prepare to use [src] on [target]."), + ) + if(!do_after(user, (implant_time * surgery_speed), target)) return if(target != user) log_combat(user, target, "autosurgeon implanted [stored_organ] into", "[src]", "in [AREACOORD(target)]") user.visible_message(span_notice("[user] presses a button on [src] as it plunges into [target]'s body."), span_notice("You press a button on [src] as it plunges into [target]'s body.")) else - user.visible_message(span_notice("[user] pressses a button on [src] as it plunges into [user.p_their()] body."), "You press a button on [src] as it plunges into your body.") + user.visible_message( + span_notice("[user] pressses a button on [src] as it plunges into [user.p_their()] body."), + span_notice("You press a button on [src] as it plunges into your body."), + ) stored_organ.Insert(target)//insert stored organ into the user stored_organ = null diff --git a/code/modules/surgery/organs/external/_external_organ.dm b/code/modules/surgery/organs/external/_external_organ.dm index 152ca92f1acd3..540c090d2e430 100644 --- a/code/modules/surgery/organs/external/_external_organ.dm +++ b/code/modules/surgery/organs/external/_external_organ.dm @@ -12,8 +12,6 @@ ///The overlay datum that actually draws stuff on the limb var/datum/bodypart_overlay/mutant/bodypart_overlay - ///Reference to the limb we're inside of - var/obj/item/bodypart/ownerlimb ///If not null, overrides the appearance with this sprite accessory datum var/sprite_accessory_override @@ -25,7 +23,7 @@ ///Set to EXTERNAL_BEHIND, EXTERNAL_FRONT or EXTERNAL_ADJACENT if you want to draw one of those layers as the object sprite. FALSE to use your own ///This will not work if it doesn't have a limb to generate it's icon with var/use_mob_sprite_as_obj_sprite = FALSE - ///Does this organ have any bodytypes to pass to it's ownerlimb? + ///Does this organ have any bodytypes to pass to it's bodypart_owner? var/external_bodytypes = NONE ///Which flags does a 'modification tool' need to have to restyle us, if it all possible (located in code/_DEFINES/mobs) var/restyle_flags = NONE @@ -55,81 +53,53 @@ if(restyle_flags) RegisterSignal(src, COMSIG_ATOM_RESTYLE, PROC_REF(on_attempt_feature_restyle)) -/obj/item/organ/external/Destroy() - if(owner) - Remove(owner, special=TRUE) - else if(ownerlimb) - remove_from_limb() +/obj/item/organ/external/Insert(mob/living/carbon/receiver, special, movement_flags) + . = ..() + receiver.update_body_parts() - return ..() +/obj/item/organ/external/Remove(mob/living/carbon/organ_owner, special, movement_flags) + . = ..() + if(!special) + organ_owner.update_body_parts() -/obj/item/organ/external/Insert(mob/living/carbon/receiver, special, drop_if_replaced) +/obj/item/organ/external/mob_insert(mob/living/carbon/receiver, special, movement_flags) if(!should_external_organ_apply_to(type, receiver)) stack_trace("adding a [type] to a [receiver.type] when it shouldn't be!") - var/obj/item/bodypart/limb = receiver.get_bodypart(deprecise_zone(zone)) - - if(!limb) - return FALSE - . = ..() if(!.) return if(bodypart_overlay.imprint_on_next_insertion) //We only want this set *once* - - bodypart_overlay.set_appearance_from_name(receiver.dna.features[bodypart_overlay.feature_key]) + var/feature_name = receiver.dna.features[bodypart_overlay.feature_key] + if (isnull(feature_name)) + feature_name = receiver.dna.species.external_organs[type] + bodypart_overlay.set_appearance_from_name(feature_name) bodypart_overlay.imprint_on_next_insertion = FALSE - ownerlimb = limb - add_to_limb(ownerlimb) - if(external_bodytypes) receiver.synchronize_bodytypes() receiver.update_body_parts() -/obj/item/organ/external/Remove(mob/living/carbon/organ_owner, special, moving) - . = ..() - - if(ownerlimb) - remove_from_limb() - if(!moving && use_mob_sprite_as_obj_sprite) //so we're being taken out and dropped - update_appearance(UPDATE_OVERLAYS) - - if(organ_owner) +/obj/item/organ/external/mob_remove(mob/living/carbon/organ_owner, special, moving) + if(!special) + organ_owner.synchronize_bodytypes() organ_owner.update_body_parts() + return ..() +/obj/item/organ/external/on_bodypart_insert(obj/item/bodypart/bodypart) + bodypart.add_bodypart_overlay(bodypart_overlay) + return ..() -/obj/item/organ/external/on_remove(mob/living/carbon/organ_owner, special) - . = ..() - color = bodypart_overlay.draw_color // so a pink felinid doesn't drop a gray tail - -///Transfers the organ to the limb, and to the limb's owner, if it has one. -/obj/item/organ/external/transfer_to_limb(obj/item/bodypart/bodypart, mob/living/carbon/bodypart_owner) - if(owner) - Remove(owner, moving = TRUE) - else if(ownerlimb) - remove_from_limb() - - if(bodypart_owner) - Insert(bodypart_owner, TRUE) - else - add_to_limb(bodypart) +/obj/item/organ/external/on_bodypart_remove(obj/item/bodypart/bodypart) + bodypart.remove_bodypart_overlay(bodypart_overlay) -/obj/item/organ/external/add_to_limb(obj/item/bodypart/bodypart) - bodypart.external_organs += src - ownerlimb = bodypart - ownerlimb.add_bodypart_overlay(bodypart_overlay) - return ..() + if(use_mob_sprite_as_obj_sprite) + update_appearance(UPDATE_OVERLAYS) -/obj/item/organ/external/remove_from_limb() - ownerlimb.external_organs -= src - ownerlimb.remove_bodypart_overlay(bodypart_overlay) - if(ownerlimb.owner && external_bodytypes) - ownerlimb.owner.synchronize_bodytypes() - ownerlimb = null + color = bodypart_overlay.draw_color // so a pink felinid doesn't drop a gray tail return ..() /proc/should_external_organ_apply_to(obj/item/organ/external/organpath, mob/living/carbon/target) @@ -163,8 +133,8 @@ if(owner) //are we in a person? owner.update_body_parts() - else if(ownerlimb) //are we in a limb? - ownerlimb.update_icon_dropped() + else if(bodypart_owner) //are we in a limb? + bodypart_owner.update_icon_dropped() //else if(use_mob_sprite_as_obj_sprite) //are we out in the world, unprotected by flesh? /obj/item/organ/external/on_life(seconds_per_tick, times_fired) @@ -179,7 +149,7 @@ //Build the mob sprite and use it as our overlay for(var/external_layer in bodypart_overlay.all_layers) if(bodypart_overlay.layers & external_layer) - . += bodypart_overlay.get_overlay(external_layer, ownerlimb) + . += bodypart_overlay.get_overlay(external_layer, bodypart_owner) ///The horns of a lizard! /obj/item/organ/external/horns @@ -285,16 +255,17 @@ ///Store our old datum here for if our antennae are healed var/original_sprite_datum -/obj/item/organ/external/antennae/Insert(mob/living/carbon/receiver, special, drop_if_replaced) +/obj/item/organ/external/antennae/Insert(mob/living/carbon/receiver, special, movement_flags) . = ..() if(!.) return RegisterSignal(receiver, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_antennae)) RegisterSignal(receiver, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_antennae)) -/obj/item/organ/external/antennae/Remove(mob/living/carbon/organ_owner, special, moving) +/obj/item/organ/external/antennae/Remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() - UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL)) + if(organ_owner) + UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL)) ///check if our antennae can burn off ;_; /obj/item/organ/external/antennae/proc/try_burn_antennae(mob/living/carbon/human/human) diff --git a/code/modules/surgery/organs/external/restyling.dm b/code/modules/surgery/organs/external/restyling.dm index 454e4395ae6b6..7d6be1b6d58e3 100644 --- a/code/modules/surgery/organs/external/restyling.dm +++ b/code/modules/surgery/organs/external/restyling.dm @@ -31,7 +31,7 @@ ///Asks the external organs inside the limb if they can restyle /obj/item/bodypart/proc/attempt_feature_restyle(atom/source, mob/living/trimmer, atom/movable/original_target, body_zone, restyle_type, style_speed) var/list/valid_features = list() - for(var/obj/item/organ/external/feature in external_organs) + for(var/obj/item/organ/external/feature in contents) if(feature.restyle_flags & restyle_type) valid_features.Add(feature) diff --git a/code/modules/surgery/organs/external/spines.dm b/code/modules/surgery/organs/external/spines.dm index 28a5ab3cf1a52..743b7fa8d47f2 100644 --- a/code/modules/surgery/organs/external/spines.dm +++ b/code/modules/surgery/organs/external/spines.dm @@ -14,35 +14,27 @@ bodypart_overlay = /datum/bodypart_overlay/mutant/spines - ///A two-way reference between the tail and the spines because of wagging sprites. Bruh. - var/obj/item/organ/external/tail/lizard/paired_tail - -/obj/item/organ/external/spines/Insert(mob/living/carbon/receiver, special, drop_if_replaced) - . = ..() - if(.) - paired_tail = locate(/obj/item/organ/external/tail/lizard) in receiver.organs //We want specifically a lizard tail, so we don't use the slot. - -/obj/item/organ/external/spines/Remove(mob/living/carbon/organ_owner, special, moving) - . = ..() - if(paired_tail) - paired_tail.paired_spines = null - paired_tail = null - -///Bodypart overlay for spines (wagging gets updated by tail) +/obj/item/organ/external/spines/Insert(mob/living/carbon/receiver, special, movement_flags) + // If we have a tail, attempt to add a tail spines overlay + var/obj/item/organ/external/tail/our_tail = receiver.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) + our_tail?.try_insert_tail_spines(our_tail.bodypart_owner) + return ..() + +/obj/item/organ/external/spines/Remove(mob/living/carbon/organ_owner, special, movement_flags) + // If we have a tail, remove any tail spines overlay + var/obj/item/organ/external/tail/our_tail = organ_owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) + our_tail?.remove_tail_spines(our_tail.bodypart_owner) + return ..() + +///Bodypart overlay for spines /datum/bodypart_overlay/mutant/spines layers = EXTERNAL_ADJACENT|EXTERNAL_BEHIND feature_key = "spines" - ///Spines moth with the tail, so track it - var/wagging = FALSE /datum/bodypart_overlay/mutant/spines/get_global_feature_list() return GLOB.spines_list -/datum/bodypart_overlay/mutant/spines/get_base_icon_state() - return (wagging ? "wagging" : "") + sprite_datum.icon_state //add the wagging tag if we be wagging - /datum/bodypart_overlay/mutant/spines/can_draw_on_bodypart(mob/living/carbon/human/human) . = ..() if(human.wear_suit && (human.wear_suit.flags_inv & HIDEJUMPSUIT)) return FALSE - diff --git a/code/modules/surgery/organs/external/tails.dm b/code/modules/surgery/organs/external/tails.dm index 7798de119ad56..17be616ba7935 100644 --- a/code/modules/surgery/organs/external/tails.dm +++ b/code/modules/surgery/organs/external/tails.dm @@ -7,20 +7,22 @@ zone = BODY_ZONE_PRECISE_GROIN slot = ORGAN_SLOT_EXTERNAL_TAIL - bodypart_overlay = /datum/bodypart_overlay/mutant/tail - dna_block = DNA_TAIL_BLOCK restyle_flags = EXTERNAL_RESTYLE_FLESH + // defaults to cat, but the parent type shouldn't be created regardless + bodypart_overlay = /datum/bodypart_overlay/mutant/tail/cat + ///Does this tail have a wagging sprite, and is it currently wagging? var/wag_flags = NONE ///The original owner of this tail var/original_owner //Yay, snowflake code! + ///The overlay for tail spines, if any + var/datum/bodypart_overlay/mutant/tail_spines/tail_spines_overlay -/obj/item/organ/external/tail/Insert(mob/living/carbon/receiver, special, drop_if_replaced) +/obj/item/organ/external/tail/Insert(mob/living/carbon/receiver, special, movement_flags) . = ..() if(.) - RegisterSignal(receiver, COMSIG_ORGAN_WAG_TAIL, PROC_REF(wag)) original_owner ||= WEAKREF(receiver) receiver.clear_mood_event("tail_lost") @@ -31,58 +33,107 @@ else if(type in receiver.dna.species.external_organs) receiver.add_mood_event("wrong_tail_regained", /datum/mood_event/tail_regained_wrong) -/obj/item/organ/external/tail/Remove(mob/living/carbon/organ_owner, special, moving) - if(wag_flags & WAG_WAGGING) - wag(FALSE) +/obj/item/organ/external/tail/on_bodypart_insert(obj/item/bodypart/bodypart) + var/obj/item/organ/external/spines/our_spines = bodypart.owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_SPINES) + if(our_spines) + try_insert_tail_spines(bodypart) + return ..() +/obj/item/organ/external/tail/on_bodypart_remove(obj/item/bodypart/bodypart) + remove_tail_spines(bodypart) return ..() -/obj/item/organ/external/tail/on_remove(mob/living/carbon/organ_owner, special) +/// If the owner has spines and an appropriate overlay exists, add a tail spines overlay. +/obj/item/organ/external/tail/proc/try_insert_tail_spines(obj/item/bodypart/bodypart) + // Don't insert another overlay if there already is one. + if(tail_spines_overlay) + return + // If this tail doesn't have a valid set of tail spines, don't insert them + var/datum/sprite_accessory/tails/tail_sprite_datum = bodypart_overlay.sprite_datum + if(!istype(tail_sprite_datum)) + return + var/tail_spine_key = tail_sprite_datum.spine_key + if(!tail_spine_key) + return + + tail_spines_overlay = new + tail_spines_overlay.tail_spine_key = tail_spine_key + var/feature_name = bodypart.owner.dna.features["spines"] //tail spines don't live in DNA, but share feature names with regular spines + tail_spines_overlay.set_appearance_from_name(feature_name) + bodypart.add_bodypart_overlay(tail_spines_overlay) + +/// If we have a tail spines overlay, delete it +/obj/item/organ/external/tail/proc/remove_tail_spines(obj/item/bodypart/bodypart) + if(!tail_spines_overlay) + return + bodypart.remove_bodypart_overlay(tail_spines_overlay) + QDEL_NULL(tail_spines_overlay) + +/obj/item/organ/external/tail/on_mob_remove(mob/living/carbon/organ_owner, special) . = ..() - UnregisterSignal(organ_owner, COMSIG_ORGAN_WAG_TAIL) + if(wag_flags & WAG_WAGGING) + stop_wag(organ_owner) if(type in organ_owner.dna.species.external_organs) organ_owner.add_mood_event("tail_lost", /datum/mood_event/tail_lost) organ_owner.add_mood_event("tail_balance_lost", /datum/mood_event/tail_balance_lost) +///We need some special behaviour for accessories, wrapped here so we can easily add more interactions later +///Accepts an optional timeout after which we remove the tail wagging +///Returns false if the wag worked, true otherwise +/obj/item/organ/external/tail/proc/start_wag(mob/living/carbon/organ_owner, stop_after = INFINITY) + if(wag_flags & WAG_WAGGING || !(wag_flags & WAG_ABLE)) // we are already wagging + return FALSE + if(organ_owner.stat == DEAD || organ_owner != owner) // no wagging when owner is dead or tail has been disembodied + return FALSE -/obj/item/organ/external/tail/proc/wag(mob/user, start = TRUE, stop_after = 0) - if(!(wag_flags & WAG_ABLE)) - return - - if(start) - start_wag() - if(stop_after) - addtimer(CALLBACK(src, PROC_REF(wag), FALSE), stop_after, TIMER_STOPPABLE|TIMER_DELETE_ME) - else - stop_wag() - owner.update_body_parts() + if(stop_after != INFINITY) + addtimer(CALLBACK(src, PROC_REF(stop_wag), organ_owner), stop_after, TIMER_STOPPABLE|TIMER_DELETE_ME) -///We need some special behaviour for accessories, wrapped here so we can easily add more interactions later -/obj/item/organ/external/tail/proc/start_wag() var/datum/bodypart_overlay/mutant/tail/accessory = bodypart_overlay wag_flags |= WAG_WAGGING accessory.wagging = TRUE + if(tail_spines_overlay) //if there are spines, they should wag with the tail + tail_spines_overlay.wagging = TRUE + organ_owner.update_body_parts() + RegisterSignal(organ_owner, COMSIG_LIVING_DEATH, PROC_REF(owner_died)) + return TRUE + +/obj/item/organ/external/tail/proc/owner_died(mob/living/carbon/organ_owner) // Resisting the urge to replace owner with daddy + SIGNAL_HANDLER + stop_wag(organ_owner) ///We need some special behaviour for accessories, wrapped here so we can easily add more interactions later -/obj/item/organ/external/tail/proc/stop_wag() - var/datum/bodypart_overlay/mutant/tail/accessory = bodypart_overlay - wag_flags &= ~WAG_WAGGING - accessory.wagging = FALSE +///Returns false if the wag stopping worked, true otherwise +/obj/item/organ/external/tail/proc/stop_wag(mob/living/carbon/organ_owner) + if(!(wag_flags & WAG_ABLE)) + return FALSE -///Tail parent type (which is MONKEEEEEEEEEEE by default), with wagging functionality + var/succeeded = FALSE + if(wag_flags & WAG_WAGGING) + wag_flags &= ~WAG_WAGGING + succeeded = TRUE + + var/datum/bodypart_overlay/mutant/tail/tail_overlay = bodypart_overlay + tail_overlay.wagging = FALSE + if(tail_spines_overlay) //if there are spines, they should stop wagging with the tail + tail_spines_overlay.wagging = FALSE + if(isnull(organ_owner)) + return succeeded + + organ_owner.update_body_parts() + UnregisterSignal(organ_owner, COMSIG_LIVING_DEATH) + return succeeded + +///Tail parent type, with wagging functionality /datum/bodypart_overlay/mutant/tail layers = EXTERNAL_FRONT|EXTERNAL_BEHIND - feature_key = "tail_monkey" var/wagging = FALSE /datum/bodypart_overlay/mutant/tail/get_base_icon_state() return (wagging ? "wagging_" : "") + sprite_datum.icon_state //add the wagging tag if we be wagging -/datum/bodypart_overlay/mutant/tail/get_global_feature_list() - return GLOB.tails_list - /datum/bodypart_overlay/mutant/tail/can_draw_on_bodypart(mob/living/carbon/human/human) if(human.wear_suit && (human.wear_suit.flags_inv & HIDEJUMPSUIT)) return FALSE @@ -112,6 +163,9 @@ color_source = NONE feature_key = "tail_monkey" +/datum/bodypart_overlay/mutant/tail/monkey/get_global_feature_list() + return GLOB.tails_list_monkey + /obj/item/organ/external/tail/lizard name = "lizard tail" desc = "A severed lizard tail. Somewhere, no doubt, a lizard hater is very pleased with themselves." @@ -121,34 +175,6 @@ wag_flags = WAG_ABLE dna_block = DNA_LIZARD_TAIL_BLOCK - ///A reference to the paired_spines, since for some fucking reason tail spines are tied to the spines themselves. - var/obj/item/organ/external/spines/paired_spines - -/obj/item/organ/external/tail/lizard/Insert(mob/living/carbon/receiver, special, drop_if_replaced) - . = ..() - if(.) - paired_spines = ownerlimb.owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_SPINES) - paired_spines?.paired_tail = src - -/obj/item/organ/external/tail/lizard/Remove(mob/living/carbon/organ_owner, special, moving) - . = ..() - if(paired_spines) - paired_spines.paired_tail = null - paired_spines = null - -/obj/item/organ/external/tail/lizard/start_wag() - . = ..() - - if(paired_spines) - var/datum/bodypart_overlay/mutant/spines/accessory = paired_spines.bodypart_overlay - accessory.wagging = TRUE - -/obj/item/organ/external/tail/lizard/stop_wag() - . = ..() - - if(paired_spines) - var/datum/bodypart_overlay/mutant/spines/accessory = paired_spines.bodypart_overlay - accessory.wagging = FALSE ///Lizard tail bodypart overlay datum /datum/bodypart_overlay/mutant/tail/lizard @@ -160,3 +186,23 @@ /obj/item/organ/external/tail/lizard/fake name = "fabricated lizard tail" desc = "A fabricated severed lizard tail. This one's made of synthflesh. Probably not usable for lizard wine." + +///Bodypart overlay for tail spines. Handled by the tail - has no actual organ associated. +/datum/bodypart_overlay/mutant/tail_spines + layers = EXTERNAL_ADJACENT|EXTERNAL_BEHIND + feature_key = "tailspines" + ///Spines wag when the tail does + var/wagging = FALSE + /// Key for tail spine states, depends on the shape of the tail. Defined in the tail sprite datum. + var/tail_spine_key = NONE + +/datum/bodypart_overlay/mutant/tail_spines/get_global_feature_list() + return GLOB.tail_spines_list + +/datum/bodypart_overlay/mutant/tail_spines/get_base_icon_state() + return (!isnull(tail_spine_key) ? "[tail_spine_key]_" : "") + (wagging ? "wagging_" : "") + sprite_datum.icon_state // Select the wagging state if appropriate + +/datum/bodypart_overlay/mutant/tail_spines/can_draw_on_bodypart(mob/living/carbon/human/human) + . = ..() + if(human.wear_suit && (human.wear_suit.flags_inv & HIDEJUMPSUIT)) + return FALSE diff --git a/code/modules/surgery/organs/external/wings/functional_wings.dm b/code/modules/surgery/organs/external/wings/functional_wings.dm index 127bd623a080c..aacf6f08f6a5c 100644 --- a/code/modules/surgery/organs/external/wings/functional_wings.dm +++ b/code/modules/surgery/organs/external/wings/functional_wings.dm @@ -26,13 +26,16 @@ ///Are our wings open or closed? var/wings_open = FALSE -/obj/item/organ/external/wings/functional/Insert(mob/living/carbon/receiver, special, drop_if_replaced) + // grind_results = list(/datum/reagent/flightpotion = 5) + food_reagents = list(/datum/reagent/flightpotion = 5) + +/obj/item/organ/external/wings/functional/Insert(mob/living/carbon/receiver, special, movement_flags) . = ..() if(. && isnull(fly)) fly = new fly.Grant(receiver) -/obj/item/organ/external/wings/functional/Remove(mob/living/carbon/organ_owner, special, moving) +/obj/item/organ/external/wings/functional/Remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() fly.Remove(organ_owner) @@ -102,12 +105,12 @@ if(!HAS_TRAIT_FROM(human, TRAIT_MOVE_FLYING, SPECIES_FLIGHT_TRAIT)) human.physiology.stun_mod *= 2 human.add_traits(list(TRAIT_NO_FLOATING_ANIM, TRAIT_MOVE_FLYING), SPECIES_FLIGHT_TRAIT) - passtable_on(human, SPECIES_TRAIT) + passtable_on(human, SPECIES_FLIGHT_TRAIT) open_wings() else human.physiology.stun_mod *= 0.5 human.remove_traits(list(TRAIT_NO_FLOATING_ANIM, TRAIT_MOVE_FLYING), SPECIES_FLIGHT_TRAIT) - passtable_off(human, SPECIES_TRAIT) + passtable_off(human, SPECIES_FLIGHT_TRAIT) close_wings() human.update_body_parts() @@ -200,3 +203,9 @@ name = "fly wings" desc = "Fly as a fly." sprite_accessory_override = /datum/sprite_accessory/wings/fly + +///slime wings, which relate to slimes. +/obj/item/organ/external/wings/functional/slime + name = "slime wings" + desc = "How does something so squishy even fly?" + sprite_accessory_override = /datum/sprite_accessory/wings/slime diff --git a/code/modules/surgery/organs/external/wings/moth_wings.dm b/code/modules/surgery/organs/external/wings/moth_wings.dm index f13b346ab075f..f4e0f156e703e 100644 --- a/code/modules/surgery/organs/external/wings/moth_wings.dm +++ b/code/modules/surgery/organs/external/wings/moth_wings.dm @@ -14,13 +14,13 @@ ///Store our old datum here for if our burned wings are healed var/original_sprite_datum -/obj/item/organ/external/wings/moth/on_insert(mob/living/carbon/receiver) +/obj/item/organ/external/wings/moth/on_mob_insert(mob/living/carbon/receiver) . = ..() RegisterSignal(receiver, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_wings)) RegisterSignal(receiver, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_wings)) RegisterSignal(receiver, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(update_float_move)) -/obj/item/organ/external/wings/moth/on_remove(mob/living/carbon/organ_owner) +/obj/item/organ/external/wings/moth/on_mob_remove(mob/living/carbon/organ_owner) . = ..() UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_MOVABLE_PRE_MOVE)) REMOVE_TRAIT(organ_owner, TRAIT_FREE_FLOAT_MOVEMENT, REF(src)) diff --git a/code/modules/surgery/organs/internal/_internal_organ.dm b/code/modules/surgery/organs/internal/_internal_organ.dm index eb8629347e6c6..9f67fb3d89914 100644 --- a/code/modules/surgery/organs/internal/_internal_organ.dm +++ b/code/modules/surgery/organs/internal/_internal_organ.dm @@ -5,19 +5,8 @@ . = ..() START_PROCESSING(SSobj, src) -/obj/item/organ/internal/Destroy() - if(owner) - // The special flag is important, because otherwise mobs can die - // while undergoing transformation into different mobs. - Remove(owner, special=TRUE) - else - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/organ/internal/Insert(mob/living/carbon/receiver, special = FALSE, drop_if_replaced = TRUE) +/obj/item/organ/internal/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags) . = ..() - if(!. || !owner) - return // organs_slot must ALWAYS be ordered in the same way as organ_process_order // Otherwise life processing breaks down @@ -25,25 +14,32 @@ STOP_PROCESSING(SSobj, src) -/obj/item/organ/internal/Remove(mob/living/carbon/organ_owner, special = FALSE) +/obj/item/organ/internal/on_mob_remove(mob/living/carbon/organ_owner, special = FALSE) . = ..() - if(organ_owner) - if((organ_flags & ORGAN_VITAL) && !special && !(organ_owner.status_flags & GODMODE)) - if(organ_owner.stat != DEAD) - organ_owner.investigate_log("has been killed by losing a vital organ ([src]).", INVESTIGATE_DEATHS) - organ_owner.death() + if((organ_flags & ORGAN_VITAL) && !special && !(organ_owner.status_flags & GODMODE)) + if(organ_owner.stat != DEAD) + organ_owner.investigate_log("has been killed by losing a vital organ ([src]).", INVESTIGATE_DEATHS) + organ_owner.death() START_PROCESSING(SSobj, src) - /obj/item/organ/internal/process(seconds_per_tick, times_fired) on_death(seconds_per_tick, times_fired) //Kinda hate doing it like this, but I really don't want to call process directly. /obj/item/organ/internal/on_death(seconds_per_tick, times_fired) //runs decay when outside of a person if(organ_flags & (ORGAN_ROBOTIC | ORGAN_FROZEN)) return - apply_organ_damage(decay_factor * maxHealth * seconds_per_tick) + + if(owner) + if(owner.bodytemperature > T0C) + var/air_temperature_factor = min((owner.bodytemperature - T0C) / 20, 1) + apply_organ_damage(decay_factor * maxHealth * seconds_per_tick * air_temperature_factor) + else + var/datum/gas_mixture/exposed_air = return_air() + if(exposed_air && exposed_air.temperature > T0C) + var/air_temperature_factor = min((exposed_air.temperature - T0C) / 20, 1) + apply_organ_damage(decay_factor * maxHealth * seconds_per_tick * air_temperature_factor) /// Called once every life tick on every organ in a carbon's body /// NOTE: THIS IS VERY HOT. Be careful what you put in here diff --git a/code/modules/surgery/organs/internal/appendix/_appendix.dm b/code/modules/surgery/organs/internal/appendix/_appendix.dm index a52479c10a732..e5190f1282ec7 100644 --- a/code/modules/surgery/organs/internal/appendix/_appendix.dm +++ b/code/modules/surgery/organs/internal/appendix/_appendix.dm @@ -34,7 +34,7 @@ if(organ_flags & ORGAN_FAILING) // forced to ensure people don't use it to gain tox as slime person - owner.adjustToxLoss(2 * seconds_per_tick, updating_health = TRUE, forced = TRUE) + owner.adjustToxLoss(2 * seconds_per_tick, forced = TRUE) else if(inflamation_stage) inflamation(seconds_per_tick) else if(SPT_PROB(APPENDICITIS_PROB, seconds_per_tick)) @@ -46,7 +46,11 @@ if(owner) ADD_TRAIT(owner, TRAIT_DISEASELIKE_SEVERITY_MEDIUM, type) owner.med_hud_set_status() - notify_ghosts("[owner] has developed spontaneous appendicitis!", source = owner, action = NOTIFY_ORBIT, header = "Whoa, Sick!") + notify_ghosts( + "[owner] has developed spontaneous appendicitis!", + source = owner, + header = "Whoa, Sick!", + ) /obj/item/organ/internal/appendix/proc/inflamation(seconds_per_tick) var/mob/living/carbon/organ_owner = owner @@ -62,7 +66,7 @@ to_chat(organ_owner, span_warning("You feel a stabbing pain in your abdomen!")) organ_owner.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 5) organ_owner.Stun(rand(40, 60)) - organ_owner.adjustToxLoss(1, updating_health = TRUE, forced = TRUE) + organ_owner.adjustToxLoss(1, forced = TRUE) if(3) if(SPT_PROB(0.5, seconds_per_tick)) organ_owner.vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = 95) @@ -72,12 +76,12 @@ /obj/item/organ/internal/appendix/get_availability(datum/species/owner_species, mob/living/owner_mob) return owner_species.mutantappendix -/obj/item/organ/internal/appendix/on_remove(mob/living/carbon/organ_owner) +/obj/item/organ/internal/appendix/on_mob_remove(mob/living/carbon/organ_owner) . = ..() REMOVE_TRAIT(organ_owner, TRAIT_DISEASELIKE_SEVERITY_MEDIUM, type) organ_owner.med_hud_set_status() -/obj/item/organ/internal/appendix/on_insert(mob/living/carbon/organ_owner) +/obj/item/organ/internal/appendix/on_mob_insert(mob/living/carbon/organ_owner) . = ..() if(inflamation_stage) ADD_TRAIT(organ_owner, TRAIT_DISEASELIKE_SEVERITY_MEDIUM, type) diff --git a/code/modules/surgery/organs/internal/appendix/appendix_golem.dm b/code/modules/surgery/organs/internal/appendix/appendix_golem.dm index 03b076b1b2a13..5510b4bf96796 100644 --- a/code/modules/surgery/organs/internal/appendix/appendix_golem.dm +++ b/code/modules/surgery/organs/internal/appendix/appendix_golem.dm @@ -2,7 +2,7 @@ /obj/item/organ/internal/appendix/golem name = "internal forge" desc = "This expanded digestive chamber allows golems to smelt minerals, provided that they are immersed in lava." - icon_state = "ethereal_heart" + icon_state = "ethereal_heart-off" color = COLOR_GOLEM_GRAY organ_flags = ORGAN_MINERAL /// Action which performs smelting @@ -12,7 +12,7 @@ . = ..() smelter = new(src) -/obj/item/organ/internal/appendix/golem/on_insert(mob/living/carbon/organ_owner) +/obj/item/organ/internal/appendix/golem/on_mob_insert(mob/living/carbon/organ_owner) . = ..() RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(check_for_lava)) @@ -25,7 +25,7 @@ if (smelter.owner != owner) smelter.Grant(owner) -/obj/item/organ/internal/appendix/golem/on_remove(mob/living/carbon/organ_owner) +/obj/item/organ/internal/appendix/golem/on_mob_remove(mob/living/carbon/organ_owner) UnregisterSignal(organ_owner, COMSIG_MOVABLE_MOVED) smelter?.Remove(organ_owner) // Might have been deleted by Destroy already return ..() diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm index 86cdf85513446..a2eeb4eac32ca 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm @@ -76,21 +76,34 @@ to_chat(user, span_notice("You modify [src] to be installed on the [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.")) update_appearance() -/obj/item/organ/internal/cyberimp/arm/on_insert(mob/living/carbon/arm_owner) +/obj/item/organ/internal/cyberimp/arm/on_mob_insert(mob/living/carbon/arm_owner) . = ..() - var/side = zone == BODY_ZONE_R_ARM? RIGHT_HANDS : LEFT_HANDS - hand = arm_owner.hand_bodyparts[side] - if(hand) - RegisterSignal(hand, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_item_attack_self)) //If the limb gets an attack-self, open the menu. Only happens when hand is empty - RegisterSignal(arm_owner, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(dropkey)) //We're nodrop, but we'll watch for the drop hotkey anyway and then stow if possible. + RegisterSignal(arm_owner, COMSIG_CARBON_POST_ATTACH_LIMB, PROC_REF(on_limb_attached)) + RegisterSignal(arm_owner, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(dropkey)) //We're nodrop, but we'll watch for the drop hotkey anyway and then stow if possible. + on_limb_attached(arm_owner, arm_owner.hand_bodyparts[zone == BODY_ZONE_R_ARM ? RIGHT_HANDS : LEFT_HANDS]) -/obj/item/organ/internal/cyberimp/arm/on_remove(mob/living/carbon/arm_owner) +/obj/item/organ/internal/cyberimp/arm/on_mob_remove(mob/living/carbon/arm_owner) . = ..() Retract() + UnregisterSignal(arm_owner, list(COMSIG_CARBON_POST_ATTACH_LIMB, COMSIG_KB_MOB_DROPITEM_DOWN)) + on_limb_detached(hand) + +/obj/item/organ/internal/cyberimp/arm/proc/on_limb_attached(mob/living/carbon/source, obj/item/bodypart/limb) + SIGNAL_HANDLER + if(!limb || QDELETED(limb) || limb.body_zone != zone) + return if(hand) - UnregisterSignal(hand, COMSIG_ITEM_ATTACK_SELF) - UnregisterSignal(arm_owner, COMSIG_KB_MOB_DROPITEM_DOWN) - hand = null + on_limb_detached(hand) + RegisterSignal(limb, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_item_attack_self)) + RegisterSignal(limb, COMSIG_BODYPART_REMOVED, PROC_REF(on_limb_detached)) + hand = limb + +/obj/item/organ/internal/cyberimp/arm/proc/on_limb_detached(obj/item/bodypart/source) + SIGNAL_HANDLER + if(source != hand || QDELETED(hand)) + return + UnregisterSignal(hand, list(COMSIG_ITEM_ATTACK_SELF, COMSIG_BODYPART_REMOVED)) + hand = null /obj/item/organ/internal/cyberimp/arm/proc/on_item_attack_self() SIGNAL_HANDLER @@ -136,6 +149,7 @@ active_item.forceMove(src) UnregisterSignal(active_item, COMSIG_ITEM_ATTACK_SELF) + UnregisterSignal(active_item, COMSIG_ITEM_ATTACK_SELF_SECONDARY) active_item = null playsound(get_turf(owner), retract_sound, 50, TRUE) return TRUE @@ -291,7 +305,7 @@ if(!istype(potential_flash, /obj/item/assembly/flash/armimplant)) continue var/obj/item/assembly/flash/armimplant/flash = potential_flash - flash.arm = WEAKREF(src) // Todo: wipe single letter vars out of assembly code + flash.arm = WEAKREF(src) /obj/item/organ/internal/cyberimp/arm/flash/Extend() . = ..() @@ -325,7 +339,7 @@ if(!istype(potential_flash, /obj/item/assembly/flash/armimplant)) continue var/obj/item/assembly/flash/armimplant/flash = potential_flash - flash.arm = WEAKREF(src) // Todo: wipe single letter vars out of assembly code + flash.arm = WEAKREF(src) /obj/item/organ/internal/cyberimp/arm/surgery name = "surgical toolset implant" @@ -365,8 +379,8 @@ actions_types = list() - ///The amount of damage dealt by the empowered attack. - var/punch_damage = 13 + ///The amount of damage the implant adds to our unarmed attacks. + var/punch_damage = 5 ///IF true, the throw attack will not smash people into walls var/non_harmful_throw = TRUE ///How far away your attack will throw your oponent @@ -378,14 +392,14 @@ ///How long will the implant malfunction if it is EMP'd var/emp_base_duration = 9 SECONDS -/obj/item/organ/internal/cyberimp/arm/muscle/Insert(mob/living/carbon/reciever, special = FALSE, drop_if_replaced = TRUE) +/obj/item/organ/internal/cyberimp/arm/muscle/on_mob_insert(mob/living/carbon/arm_owner) . = ..() - if(ishuman(reciever)) //Sorry, only humans - RegisterSignal(reciever, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand)) + if(ishuman(arm_owner)) //Sorry, only humans + RegisterSignal(arm_owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand)) -/obj/item/organ/internal/cyberimp/arm/muscle/Remove(mob/living/carbon/implant_owner, special = 0) +/obj/item/organ/internal/cyberimp/arm/muscle/on_mob_remove(mob/living/carbon/arm_owner) . = ..() - UnregisterSignal(implant_owner, COMSIG_HUMAN_EARLY_UNARMED_ATTACK) + UnregisterSignal(arm_owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK) /obj/item/organ/internal/cyberimp/arm/muscle/emp_act(severity) . = ..() @@ -402,15 +416,17 @@ /obj/item/organ/internal/cyberimp/arm/muscle/proc/on_attack_hand(mob/living/carbon/human/source, atom/target, proximity, modifiers) SIGNAL_HANDLER - if(source.get_active_hand() != source.get_bodypart(check_zone(zone)) || !proximity) - return + if(source.get_active_hand() != hand || !proximity) + return NONE if(!source.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK)) - return + return NONE if(!isliving(target)) - return + return NONE var/datum/dna/dna = source.has_dna() if(dna?.check_mutation(/datum/mutation/human/hulk)) //NO HULK - return + return NONE + if(!source.can_unarmed_attack()) + return COMPONENT_SKIP_ATTACK var/mob/living/living_target = target source.changeNext_move(CLICK_CD_MELEE) @@ -428,18 +444,23 @@ if(ishuman(target)) var/mob/living/carbon/human/human_target = target - if(human_target.check_shields(source, punch_damage, "[source]'s' [picked_hit_type]")) + if(human_target.check_block(source, punch_damage, "[source]'s' [picked_hit_type]")) source.do_attack_animation(target) playsound(living_target.loc, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1) log_combat(source, target, "attempted to [picked_hit_type]", "muscle implant") return COMPONENT_CANCEL_ATTACK_CHAIN + var/potential_damage = punch_damage + var/obj/item/bodypart/attacking_bodypart = hand + potential_damage += rand(attacking_bodypart.unarmed_damage_low, attacking_bodypart.unarmed_damage_high) + source.do_attack_animation(target, ATTACK_EFFECT_SMASH) playsound(living_target.loc, 'sound/weapons/punch1.ogg', 25, TRUE, -1) var/target_zone = living_target.get_random_valid_zone(source.zone_selected) - var/armor_block = living_target.run_armor_check(target_zone, MELEE) - living_target.apply_damage(punch_damage, BRUTE, target_zone, armor_block) + var/armor_block = living_target.run_armor_check(target_zone, MELEE, armour_penetration = attacking_bodypart.unarmed_effectiveness) + living_target.apply_damage(potential_damage, attacking_bodypart.attack_type, target_zone, armor_block) + living_target.apply_damage(potential_damage*1.5, STAMINA, target_zone, armor_block) if(source.body_position != LYING_DOWN) //Throw them if we are standing var/atom/throw_target = get_edge_target_turf(living_target, source.dir) diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm index 60322a7f8d6a9..513f0794c31cb 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm @@ -53,42 +53,82 @@ var/revive_cost = 0 var/reviving = FALSE COOLDOWN_DECLARE(reviver_cooldown) + COOLDOWN_DECLARE(defib_cooldown) +/obj/item/organ/internal/cyberimp/chest/reviver/on_death(seconds_per_tick, times_fired) + if(isnull(owner)) // owner can be null, on_death() gets called by /obj/item/organ/internal/process() for decay + return + try_heal() // Allows implant to work even on dead people /obj/item/organ/internal/cyberimp/chest/reviver/on_life(seconds_per_tick, times_fired) + try_heal() + +/obj/item/organ/internal/cyberimp/chest/reviver/proc/try_heal() if(reviving) - switch(owner.stat) - if(UNCONSCIOUS, HARD_CRIT, SOFT_CRIT) - addtimer(CALLBACK(src, PROC_REF(heal)), 3 SECONDS) - else - COOLDOWN_START(src, reviver_cooldown, revive_cost) - reviving = FALSE - to_chat(owner, span_notice("Your reviver implant shuts down and starts recharging. It will be ready again in [DisplayTimeText(revive_cost)].")) + if(owner.stat == CONSCIOUS) + COOLDOWN_START(src, reviver_cooldown, revive_cost) + reviving = FALSE + to_chat(owner, span_notice("Your reviver implant shuts down and starts recharging. It will be ready again in [DisplayTimeText(revive_cost)].")) + else + addtimer(CALLBACK(src, PROC_REF(heal)), 3 SECONDS) return if(!COOLDOWN_FINISHED(src, reviver_cooldown) || HAS_TRAIT(owner, TRAIT_SUICIDED)) return - switch(owner.stat) - if(UNCONSCIOUS, HARD_CRIT) - revive_cost = 0 - reviving = TRUE - to_chat(owner, span_notice("You feel a faint buzzing as your reviver implant starts patching your wounds...")) + if(owner.stat != CONSCIOUS) + revive_cost = 0 + reviving = TRUE + to_chat(owner, span_notice("You feel a faint buzzing as your reviver implant starts patching your wounds...")) + COOLDOWN_START(src, defib_cooldown, 8 SECONDS) // 5 seconds after heal proc delay /obj/item/organ/internal/cyberimp/chest/reviver/proc/heal() + if(COOLDOWN_FINISHED(src, defib_cooldown)) + revive_dead() + + /// boolean that stands for if PHYSICAL damage being patched + var/body_damage_patched = FALSE + var/need_mob_update = FALSE if(owner.getOxyLoss()) - owner.adjustOxyLoss(-5) + need_mob_update += owner.adjustOxyLoss(-5, updating_health = FALSE) revive_cost += 5 if(owner.getBruteLoss()) - owner.adjustBruteLoss(-2) + need_mob_update += owner.adjustBruteLoss(-2, updating_health = FALSE) revive_cost += 40 + body_damage_patched = TRUE if(owner.getFireLoss()) - owner.adjustFireLoss(-2) + need_mob_update += owner.adjustFireLoss(-2, updating_health = FALSE) revive_cost += 40 + body_damage_patched = TRUE if(owner.getToxLoss()) - owner.adjustToxLoss(-1) + need_mob_update += owner.adjustToxLoss(-1, updating_health = FALSE) revive_cost += 40 + if(need_mob_update) + owner.updatehealth() + + if(body_damage_patched && prob(35)) // healing is called every few seconds, not every tick + owner.visible_message(span_warning("[owner]'s body twitches a bit."), span_notice("You feel like something is patching your injured body.")) + + +/obj/item/organ/internal/cyberimp/chest/reviver/proc/revive_dead() + if(!COOLDOWN_FINISHED(src, defib_cooldown) || owner.stat != DEAD || owner.can_defib() != DEFIB_POSSIBLE) + return + owner.notify_revival("You are being revived by [src]!") + revive_cost += 10 MINUTES // Additional 10 minutes cooldown after revival. + owner.grab_ghost() + + defib_cooldown += 16 SECONDS // delay so it doesn't spam + + owner.visible_message(span_warning("[owner]'s body convulses a bit.")) + playsound(owner, SFX_BODYFALL, 50, TRUE) + playsound(owner, 'sound/machines/defib_zap.ogg', 75, TRUE, -1) + owner.revive() + owner.emote("gasp") + owner.set_jitter_if_lower(200 SECONDS) + SEND_SIGNAL(owner, COMSIG_LIVING_MINOR_SHOCK) + log_game("[owner] been revived by [src]") + /obj/item/organ/internal/cyberimp/chest/reviver/emp_act(severity) . = ..() @@ -141,7 +181,7 @@ /datum/effect_system/trail_follow/ion \ ) -/obj/item/organ/internal/cyberimp/chest/thrusters/Remove(mob/living/carbon/thruster_owner, special = 0) +/obj/item/organ/internal/cyberimp/chest/thrusters/Remove(mob/living/carbon/thruster_owner, special, movement_flags) if(on) deactivate(silent = TRUE) ..() diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm index 352d4237cc0a2..cec0241ece34c 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm @@ -12,10 +12,26 @@ name = "HUD implant" desc = "These cybernetic eyes will display a HUD over everything you see. Maybe." slot = ORGAN_SLOT_HUD + actions_types = list(/datum/action/item_action/toggle_hud) var/HUD_type = 0 var/HUD_trait = null + /// Whether the HUD implant is on or off + var/toggled_on = TRUE -/obj/item/organ/internal/cyberimp/eyes/hud/Insert(mob/living/carbon/eye_owner, special = FALSE, drop_if_replaced = TRUE) + +/obj/item/organ/internal/cyberimp/eyes/hud/proc/toggle_hud(mob/living/carbon/eye_owner) + if(toggled_on) + if(HUD_type) + var/datum/atom_hud/hud = GLOB.huds[HUD_type] + hud.hide_from(eye_owner) + toggled_on = FALSE + else + if(HUD_type) + var/datum/atom_hud/hud = GLOB.huds[HUD_type] + hud.show_to(eye_owner) + toggled_on = TRUE + +/obj/item/organ/internal/cyberimp/eyes/hud/Insert(mob/living/carbon/eye_owner, special = FALSE, movement_flags) . = ..() if(!.) return @@ -24,14 +40,16 @@ hud.show_to(eye_owner) if(HUD_trait) ADD_TRAIT(eye_owner, HUD_trait, ORGAN_TRAIT) + toggled_on = TRUE -/obj/item/organ/internal/cyberimp/eyes/hud/Remove(mob/living/carbon/eye_owner, special = FALSE) +/obj/item/organ/internal/cyberimp/eyes/hud/Remove(mob/living/carbon/eye_owner, special, movement_flags) . = ..() if(HUD_type) var/datum/atom_hud/hud = GLOB.huds[HUD_type] hud.hide_from(eye_owner) if(HUD_trait) REMOVE_TRAIT(eye_owner, HUD_trait, ORGAN_TRAIT) + toggled_on = FALSE /obj/item/organ/internal/cyberimp/eyes/hud/medical name = "Medical HUD implant" diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm index 0a7332c0dd893..67a02e71d7ea5 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm @@ -4,6 +4,7 @@ desc = "A state-of-the-art implant that improves a baseline's functionality." visual = FALSE organ_flags = ORGAN_ROBOTIC + failing_desc = "seems to be broken." var/implant_color = "#FFFFFF" var/implant_overlay @@ -86,7 +87,7 @@ stored_items = list() -/obj/item/organ/internal/cyberimp/brain/anti_drop/Remove(mob/living/carbon/implant_owner, special = 0) +/obj/item/organ/internal/cyberimp/brain/anti_drop/Remove(mob/living/carbon/implant_owner, special, movement_flags) if(active) ui_action_click() ..() @@ -112,11 +113,11 @@ var/stun_cap_amount = 40 -/obj/item/organ/internal/cyberimp/brain/anti_stun/on_remove(mob/living/carbon/implant_owner) +/obj/item/organ/internal/cyberimp/brain/anti_stun/on_mob_remove(mob/living/carbon/implant_owner) . = ..() UnregisterSignal(implant_owner, signalCache) -/obj/item/organ/internal/cyberimp/brain/anti_stun/on_insert(mob/living/carbon/receiver) +/obj/item/organ/internal/cyberimp/brain/anti_stun/on_mob_insert(mob/living/carbon/receiver) . = ..() RegisterSignals(receiver, signalCache, PROC_REF(on_signal)) diff --git a/code/modules/surgery/organs/internal/ears/_ears.dm b/code/modules/surgery/organs/internal/ears/_ears.dm index ea642879430d1..ad48645e89366 100644 --- a/code/modules/surgery/organs/internal/ears/_ears.dm +++ b/code/modules/surgery/organs/internal/ears/_ears.dm @@ -68,7 +68,7 @@ visual = TRUE damage_multiplier = 2 -/obj/item/organ/internal/ears/cat/on_insert(mob/living/carbon/human/ear_owner) +/obj/item/organ/internal/ears/cat/on_mob_insert(mob/living/carbon/human/ear_owner) . = ..() if(istype(ear_owner) && ear_owner.dna) color = ear_owner.hair_color @@ -76,7 +76,7 @@ ear_owner.dna.update_uf_block(DNA_EARS_BLOCK) ear_owner.update_body() -/obj/item/organ/internal/ears/cat/on_remove(mob/living/carbon/human/ear_owner) +/obj/item/organ/internal/ears/cat/on_mob_remove(mob/living/carbon/human/ear_owner) . = ..() if(istype(ear_owner) && ear_owner.dna) color = ear_owner.hair_color @@ -87,13 +87,13 @@ name = "penguin ears" desc = "The source of a penguin's happy feet." -/obj/item/organ/internal/ears/penguin/on_insert(mob/living/carbon/human/ear_owner) +/obj/item/organ/internal/ears/penguin/on_mob_insert(mob/living/carbon/human/ear_owner) . = ..() if(istype(ear_owner)) to_chat(ear_owner, span_notice("You suddenly feel like you've lost your balance.")) ear_owner.AddElement(/datum/element/waddling) -/obj/item/organ/internal/ears/penguin/on_remove(mob/living/carbon/human/ear_owner) +/obj/item/organ/internal/ears/penguin/on_mob_remove(mob/living/carbon/human/ear_owner) . = ..() if(istype(ear_owner)) to_chat(ear_owner, span_notice("Your sense of balance comes back to you.")) @@ -105,6 +105,7 @@ desc = "A basic cybernetic organ designed to mimic the operation of ears." damage_multiplier = 0.9 organ_flags = ORGAN_ROBOTIC + failing_desc = "seems to be broken." /obj/item/organ/internal/ears/cybernetic/upgraded name = "cybernetic ears" @@ -121,11 +122,11 @@ // The original idea was to use signals to do this not traits. Unfortunately, the star effect used for whispers applies before any relevant signals // This seems like the least invasive solution -/obj/item/organ/internal/ears/cybernetic/whisper/on_insert(mob/living/carbon/ear_owner) +/obj/item/organ/internal/ears/cybernetic/whisper/on_mob_insert(mob/living/carbon/ear_owner) . = ..() ADD_TRAIT(ear_owner, TRAIT_GOOD_HEARING, ORGAN_TRAIT) -/obj/item/organ/internal/ears/cybernetic/whisper/on_remove(mob/living/carbon/ear_owner) +/obj/item/organ/internal/ears/cybernetic/whisper/on_mob_remove(mob/living/carbon/ear_owner) . = ..() REMOVE_TRAIT(ear_owner, TRAIT_GOOD_HEARING, ORGAN_TRAIT) @@ -137,11 +138,11 @@ // Same sensitivity as felinid ears damage_multiplier = 2 -/obj/item/organ/internal/ears/cybernetic/xray/on_insert(mob/living/carbon/ear_owner) +/obj/item/organ/internal/ears/cybernetic/xray/on_mob_insert(mob/living/carbon/ear_owner) . = ..() ADD_TRAIT(ear_owner, TRAIT_XRAY_HEARING, ORGAN_TRAIT) -/obj/item/organ/internal/ears/cybernetic/xray/on_remove(mob/living/carbon/ear_owner) +/obj/item/organ/internal/ears/cybernetic/xray/on_mob_remove(mob/living/carbon/ear_owner) . = ..() REMOVE_TRAIT(ear_owner, TRAIT_XRAY_HEARING, ORGAN_TRAIT) diff --git a/code/modules/surgery/organs/internal/eyes/_eyes.dm b/code/modules/surgery/organs/internal/eyes/_eyes.dm index a6a749f2621a1..f066b308e7b0c 100644 --- a/code/modules/surgery/organs/internal/eyes/_eyes.dm +++ b/code/modules/surgery/organs/internal/eyes/_eyes.dm @@ -52,7 +52,7 @@ /// Native FOV that will be applied if a config is enabled var/native_fov = FOV_90_DEGREES -/obj/item/organ/internal/eyes/Insert(mob/living/carbon/eye_recipient, special = FALSE, drop_if_replaced = FALSE) +/obj/item/organ/internal/eyes/Insert(mob/living/carbon/eye_recipient, special = FALSE, movement_flags = DELETE_IF_REPLACED) // If we don't do this before everything else, heterochromia will be reset leading to eye_color_right no longer being accurate if(ishuman(eye_recipient)) var/mob/living/carbon/human/human_recipient = eye_recipient @@ -94,7 +94,7 @@ if(call_update) affected_human.update_body() -/obj/item/organ/internal/eyes/Remove(mob/living/carbon/eye_owner, special = FALSE) +/obj/item/organ/internal/eyes/Remove(mob/living/carbon/eye_owner, special, movement_flags) . = ..() if(ishuman(eye_owner)) var/mob/living/carbon/human/human_owner = eye_owner @@ -104,7 +104,8 @@ human_owner.eye_color_right = old_eye_color_right if(native_fov) eye_owner.remove_fov_trait(type) - human_owner.update_body() + if(!special) + human_owner.update_body() // Cure blindness from eye damage eye_owner.cure_blind(EYE_DAMAGE) @@ -161,7 +162,7 @@ /obj/item/organ/internal/eyes/apply_organ_damage(damage_amount, maximum = maxHealth, required_organ_flag) . = ..() if(!owner) - return + return FALSE apply_damaged_eye_effects() /// Applies effects to our owner based on how damaged our eyes are @@ -276,14 +277,14 @@ /datum/action/cooldown/golem_ore_sight name = "Ore Resonance" desc = "Causes nearby ores to vibrate, revealing their location." - button_icon = 'icons/obj/device.dmi' + button_icon = 'icons/obj/devices/scanner.dmi' button_icon_state = "manual_mining" check_flags = AB_CHECK_CONSCIOUS cooldown_time = 10 SECONDS /datum/action/cooldown/golem_ore_sight/Activate(atom/target) . = ..() - mineral_scan_pulse(get_turf(target)) + mineral_scan_pulse(get_turf(target), scanner = target) ///Robotic @@ -292,6 +293,7 @@ icon_state = "cybernetic_eyeballs" desc = "Your vision is augmented." organ_flags = ORGAN_ROBOTIC + failing_desc = "seems to be broken." /obj/item/organ/internal/eyes/robotic/emp_act(severity) . = ..() @@ -326,11 +328,11 @@ eye_color_right = "000" sight_flags = SEE_MOBS | SEE_OBJS | SEE_TURFS -/obj/item/organ/internal/eyes/robotic/xray/on_insert(mob/living/carbon/eye_owner) +/obj/item/organ/internal/eyes/robotic/xray/on_mob_insert(mob/living/carbon/eye_owner) . = ..() ADD_TRAIT(eye_owner, TRAIT_XRAY_VISION, ORGAN_TRAIT) -/obj/item/organ/internal/eyes/robotic/xray/on_remove(mob/living/carbon/eye_owner) +/obj/item/organ/internal/eyes/robotic/xray/on_mob_remove(mob/living/carbon/eye_owner) . = ..() REMOVE_TRAIT(eye_owner, TRAIT_XRAY_VISION, ORGAN_TRAIT) @@ -358,18 +360,18 @@ /obj/item/organ/internal/eyes/robotic/flashlight/emp_act(severity) return -/obj/item/organ/internal/eyes/robotic/flashlight/on_insert(mob/living/carbon/victim) +/obj/item/organ/internal/eyes/robotic/flashlight/on_mob_insert(mob/living/carbon/victim) . = ..() if(!eye) eye = new /obj/item/flashlight/eyelight() - eye.on = TRUE + eye.set_light_on(TRUE) eye.forceMove(victim) eye.update_brightness(victim) victim.become_blind(FLASHLIGHT_EYES) -/obj/item/organ/internal/eyes/robotic/flashlight/on_remove(mob/living/carbon/victim) +/obj/item/organ/internal/eyes/robotic/flashlight/on_mob_remove(mob/living/carbon/victim) . = ..() - eye.on = FALSE + eye.set_light_on(FALSE) eye.update_brightness(victim) eye.forceMove(src) victim.cure_blind(FLASHLIGHT_EYES) @@ -400,13 +402,13 @@ /// base icon state for eye overlays var/base_eye_state = "eyes_glow_gs" /// Whether or not to match the eye color to the light or use a custom selection - var/eye_color_mode = MATCH_LIGHT_COLOR + var/eye_color_mode = USE_CUSTOM_COLOR /// The selected color for the light beam itself - var/current_color_string = "#ffffff" + var/light_color_string = "#ffffff" /// The custom selected eye color for the left eye. Defaults to the mob's natural eye color - var/current_left_color_string + var/left_eye_color_string /// The custom selected eye color for the right eye. Defaults to the mob's natural eye color - var/current_right_color_string + var/right_eye_color_string /obj/item/organ/internal/eyes/robotic/glow/Initialize(mapload) . = ..() @@ -419,25 +421,26 @@ /obj/item/organ/internal/eyes/robotic/glow/emp_act() . = ..() - if(!eye.on || . & EMP_PROTECT_SELF) + if(!eye.light_on || . & EMP_PROTECT_SELF) return deactivate(close_ui = TRUE) /// Set the initial color of the eyes on insert to be the mob's previous eye color. -/obj/item/organ/internal/eyes/robotic/glow/Insert(mob/living/carbon/eye_recipient, special = FALSE, drop_if_replaced = FALSE) +/obj/item/organ/internal/eyes/robotic/glow/Insert(mob/living/carbon/eye_recipient, special = FALSE, movement_flags = DELETE_IF_REPLACED) . = ..() - current_color_string = old_eye_color_left - current_left_color_string = old_eye_color_left - current_right_color_string = old_eye_color_right + left_eye_color_string = old_eye_color_left + right_eye_color_string = old_eye_color_right + update_mob_eye_color(eye_recipient) -/obj/item/organ/internal/eyes/robotic/glow/on_insert(mob/living/carbon/eye_recipient) +/obj/item/organ/internal/eyes/robotic/glow/on_mob_insert(mob/living/carbon/eye_recipient) . = ..() deactivate(close_ui = TRUE) eye.forceMove(eye_recipient) -/obj/item/organ/internal/eyes/robotic/glow/on_remove(mob/living/carbon/eye_owner) +/obj/item/organ/internal/eyes/robotic/glow/on_mob_remove(mob/living/carbon/eye_owner) deactivate(eye_owner, close_ui = TRUE) - eye.forceMove(src) + if(!QDELETED(eye)) + eye.forceMove(src) return ..() /obj/item/organ/internal/eyes/robotic/glow/ui_state(mob/user) @@ -466,10 +469,10 @@ data["eyeColor"] = list( mode = eye_color_mode, hasOwner = owner ? TRUE : FALSE, - left = current_left_color_string, - right = current_right_color_string, + left = left_eye_color_string, + right = right_eye_color_string, ) - data["lightColor"] = current_color_string + data["lightColor"] = light_color_string data["range"] = eye.light_range return data @@ -489,7 +492,7 @@ usr, "Choose eye color color:", "High Luminosity Eyes Menu", - current_color_string + light_color_string ) as color|null if(new_color) var/to_update = params["to_update"] @@ -520,9 +523,10 @@ * Turns on the attached flashlight object, updates the mob overlay to be added. */ /obj/item/organ/internal/eyes/robotic/glow/proc/activate() - eye.on = TRUE - if(eye.light_range) // at range 0 we are just going to make the eyes glow emissively, no light overlay + if(eye.light_range) eye.set_light_on(TRUE) + else + eye.light_on = TRUE // at range 0 we are just going to make the eyes glow emissively, no light overlay update_mob_eye_color() /** @@ -536,7 +540,6 @@ /obj/item/organ/internal/eyes/robotic/glow/proc/deactivate(mob/living/carbon/eye_owner = owner, close_ui = FALSE) if(close_ui) SStgui.close_uis(src) - eye.on = FALSE eye.set_light_on(FALSE) update_mob_eye_color(eye_owner) @@ -563,7 +566,7 @@ */ /obj/item/organ/internal/eyes/robotic/glow/proc/set_beam_range(new_range) var/old_light_range = eye.light_range - if(old_light_range == 0 && new_range > 0 && eye.on) // turn bring back the light overlay if we were previously at 0 (aka emissive eyes only) + if(old_light_range == 0 && new_range > 0 && eye.light_on) // turn bring back the light overlay if we were previously at 0 (aka emissive eyes only) eye.light_on = FALSE // this is stupid, but this has to be FALSE for set_light_on() to work. eye.set_light_on(TRUE) eye.set_light_range(clamp(new_range, 0, max_light_beam_distance)) @@ -585,12 +588,12 @@ newcolor_string = newcolor switch(to_update) if(UPDATE_LIGHT) - current_color_string = newcolor_string + light_color_string = newcolor_string eye.set_light_color(newcolor_string) if(UPDATE_EYES_LEFT) - current_left_color_string = newcolor_string + left_eye_color_string = newcolor_string if(UPDATE_EYES_RIGHT) - current_right_color_string = newcolor_string + right_eye_color_string = newcolor_string update_mob_eye_color() @@ -598,7 +601,7 @@ * Toggle the attached flashlight object on or off */ /obj/item/organ/internal/eyes/robotic/glow/proc/toggle_active() - if(eye.on) + if(eye.light_on) deactivate() else activate() @@ -622,16 +625,16 @@ /obj/item/organ/internal/eyes/robotic/glow/proc/update_mob_eye_color(mob/living/carbon/eye_owner = owner) switch(eye_color_mode) if(MATCH_LIGHT_COLOR) - eye_color_left = current_color_string - eye_color_right = current_color_string + eye_color_left = light_color_string + eye_color_right = light_color_string if(USE_CUSTOM_COLOR) - eye_color_left = current_left_color_string - eye_color_right = current_right_color_string + eye_color_left = left_eye_color_string + eye_color_right = right_eye_color_string if(QDELETED(eye_owner) || !ishuman(eye_owner)) //Other carbon mobs don't have eye color. return - - if(!eye.on) + + if(!eye.light_on) eye_icon_state = initial(eye_icon_state) overlay_ignore_lighting = FALSE else @@ -719,16 +722,9 @@ low_light_cutoff = list(5, 12, 20) medium_light_cutoff = list(15, 20, 30) high_light_cutoff = list(30, 35, 50) - var/obj/item/flashlight/eyelight/adapted/adapt_light -/obj/item/organ/internal/eyes/night_vision/maintenance_adapted/on_insert(mob/living/carbon/eye_owner) +/obj/item/organ/internal/eyes/night_vision/maintenance_adapted/on_mob_insert(mob/living/carbon/eye_owner) . = ..() - //add lighting - if(!adapt_light) - adapt_light = new /obj/item/flashlight/eyelight/adapted() - adapt_light.on = TRUE - adapt_light.forceMove(eye_owner) - adapt_light.update_brightness(eye_owner) ADD_TRAIT(eye_owner, TRAIT_UNNATURAL_RED_GLOWY_EYES, ORGAN_TRAIT) /obj/item/organ/internal/eyes/night_vision/maintenance_adapted/on_life(seconds_per_tick, times_fired) @@ -740,10 +736,6 @@ apply_organ_damage(-10) //heal quickly . = ..() -/obj/item/organ/internal/eyes/night_vision/maintenance_adapted/Remove(mob/living/carbon/unadapted, special = FALSE) - //remove lighting - adapt_light.on = FALSE - adapt_light.update_brightness(unadapted) - adapt_light.forceMove(src) +/obj/item/organ/internal/eyes/night_vision/maintenance_adapted/on_mob_remove(mob/living/carbon/unadapted, special = FALSE) REMOVE_TRAIT(unadapted, TRAIT_UNNATURAL_RED_GLOWY_EYES, ORGAN_TRAIT) return ..() diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index ce4b948c65300..2773f588b24da 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -6,58 +6,88 @@ visual = FALSE zone = BODY_ZONE_CHEST slot = ORGAN_SLOT_HEART - + item_flags = NO_BLOOD_ON_ITEM healing_factor = STANDARD_ORGAN_HEALING decay_factor = 2.5 * STANDARD_ORGAN_DECAY //designed to fail around 6 minutes after death - low_threshold_passed = "Prickles of pain appear then die out from within your chest..." - high_threshold_passed = "Something inside your chest hurts, and the pain isn't subsiding. You notice yourself breathing far faster than before." - now_fixed = "Your heart begins to beat again." - high_threshold_cleared = "The pain in your chest has died down, and your breathing becomes more relaxed." + low_threshold_passed = span_info("Prickles of pain appear then die out from within your chest...") + high_threshold_passed = span_warning("Something inside your chest hurts, and the pain isn't subsiding. You notice yourself breathing far faster than before.") + now_fixed = span_info("Your heart begins to beat again.") + high_threshold_cleared = span_info("The pain in your chest has died down, and your breathing becomes more relaxed.") - // Heart attack code is in code/modules/mob/living/carbon/human/life.dm - var/beating = TRUE attack_verb_continuous = list("beats", "thumps") attack_verb_simple = list("beat", "thump") - var/beat = BEAT_NONE//is this mob having a heatbeat sound played? if so, which? - var/failed = FALSE //to prevent constantly running failing code - var/operated = FALSE //whether the heart's been operated on to fix some of its damages + + // Heart attack code is in code/modules/mob/living/carbon/human/life.dm + + /// Whether the heart is currently beating. + /// Do not set this directly. Use Restart() and Stop() instead. + VAR_PRIVATE/beating = TRUE + + /// is this mob having a heatbeat sound played? if so, which? + var/beat = BEAT_NONE + /// whether the heart's been operated on to fix some of its damages + var/operated = FALSE /obj/item/organ/internal/heart/update_icon_state() + . = ..() icon_state = "[base_icon_state]-[beating ? "on" : "off"]" - return ..() -/obj/item/organ/internal/heart/Remove(mob/living/carbon/heartless, special = 0) +/obj/item/organ/internal/heart/Remove(mob/living/carbon/heartless, special, movement_flags) . = ..() if(!special) - addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 120) + addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 12 SECONDS) + beat = BEAT_NONE + owner?.stop_sound_channel(CHANNEL_HEARTBEAT) /obj/item/organ/internal/heart/proc/stop_if_unowned() - if(!owner) + if(QDELETED(src)) + return + if(IS_ROBOTIC_ORGAN(src)) + return + if(isnull(owner)) Stop() /obj/item/organ/internal/heart/attack_self(mob/user) - ..() + . = ..() + if(.) + return + if(!beating) - user.visible_message("[user] squeezes [src] to \ - make it beat again!",span_notice("You squeeze [src] to make it beat again!")) + user.visible_message( + span_notice("[user] squeezes [src] to make it beat again!"), + span_notice("You squeeze [src] to make it beat again!"), + ) Restart() - addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 80) + addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 8 SECONDS) + return TRUE /obj/item/organ/internal/heart/proc/Stop() + if(!beating) + return FALSE + beating = FALSE update_appearance() + beat = BEAT_NONE + owner?.stop_sound_channel(CHANNEL_HEARTBEAT) return TRUE /obj/item/organ/internal/heart/proc/Restart() + if(beating) + return FALSE + beating = TRUE update_appearance() return TRUE /obj/item/organ/internal/heart/OnEatFrom(eater, feeder) . = ..() - beating = FALSE - update_appearance() + Stop() + +/// Checks if the heart is beating. +/// Can be overridden to add more conditions for more complex hearts. +/obj/item/organ/internal/heart/proc/is_beating() + return beating /obj/item/organ/internal/heart/on_life(seconds_per_tick, times_fired) ..() @@ -66,34 +96,32 @@ if(!owner.needs_heart()) return - if(owner.client && beating) - failed = FALSE - var/sound/slowbeat = sound('sound/health/slowbeat.ogg', repeat = TRUE) - var/sound/fastbeat = sound('sound/health/fastbeat.ogg', repeat = TRUE) + // Handle "sudden" heart attack + if(!beating || (organ_flags & ORGAN_FAILING)) + if(owner.can_heartattack() && Stop()) + if(owner.stat == CONSCIOUS) + owner.visible_message(span_danger("[owner] clutches at [owner.p_their()] chest as if [owner.p_their()] heart is stopping!")) + to_chat(owner, span_userdanger("You feel a terrible pain in your chest, as if your heart has stopped!")) + return + + // Beyond deals with sound effects, so nothing needs to be done if no client + if(isnull(owner.client)) + return - if(owner.health <= owner.crit_threshold && beat != BEAT_SLOW) + if(owner.stat == SOFT_CRIT) + if(beat != BEAT_SLOW) beat = BEAT_SLOW - owner.playsound_local(get_turf(owner), slowbeat, 40, 0, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE) to_chat(owner, span_notice("You feel your heart slow down...")) - if(beat == BEAT_SLOW && owner.health > owner.crit_threshold) - owner.stop_sound_channel(CHANNEL_HEARTBEAT) - beat = BEAT_NONE - - if(owner.has_status_effect(/datum/status_effect/jitter)) - if(owner.health > HEALTH_THRESHOLD_FULLCRIT && (!beat || beat == BEAT_SLOW)) - owner.playsound_local(get_turf(owner), fastbeat, 40, 0, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE) - beat = BEAT_FAST - - else if(beat == BEAT_FAST) - owner.stop_sound_channel(CHANNEL_HEARTBEAT) - beat = BEAT_NONE - - if(organ_flags & ORGAN_FAILING && owner.can_heartattack() && !(HAS_TRAIT(src, TRAIT_STABLEHEART))) //heart broke, stopped beating, death imminent... unless you have veins that pump blood without a heart - if(owner.stat == CONSCIOUS) - owner.visible_message(span_danger("[owner] clutches at [owner.p_their()] chest as if [owner.p_their()] heart is stopping!"), \ - span_userdanger("You feel a terrible pain in your chest, as if your heart has stopped!")) - owner.set_heartattack(TRUE) - failed = TRUE + SEND_SOUND(owner, sound('sound/health/slowbeat.ogg', repeat = TRUE, channel = CHANNEL_HEARTBEAT, volume = 40)) + + else if(owner.stat == HARD_CRIT) + if(beat != BEAT_FAST && owner.has_status_effect(/datum/status_effect/jitter)) + SEND_SOUND(owner, sound('sound/health/fastbeat.ogg', repeat = TRUE, channel = CHANNEL_HEARTBEAT, volume = 40)) + beat = BEAT_FAST + + else if(beat != BEAT_NONE) + owner.stop_sound_channel(CHANNEL_HEARTBEAT) + beat = BEAT_NONE /obj/item/organ/internal/heart/get_availability(datum/species/owner_species, mob/living/owner_mob) return owner_species.mutantheart @@ -104,20 +132,12 @@ icon_state = "cursedheart-off" base_icon_state = "cursedheart" decay_factor = 0 - actions_types = list(/datum/action/item_action/organ_action/cursed_heart) - var/last_pump = 0 - var/add_colour = TRUE //So we're not constantly recreating colour datums - /// How long between needed pumps; you can pump one second early var/pump_delay = 3 SECONDS - /// How much blood volume you lose every missed pump, this is a flat amount not a percentage! - var/blood_loss = (BLOOD_VOLUME_NORMAL / 5) // 20% of normal volume, missing five pumps is instant death - - //How much to heal per pump, negative numbers would HURT the player + var/blood_loss = BLOOD_VOLUME_NORMAL * 0.2 var/heal_brute = 0 var/heal_burn = 0 var/heal_oxy = 0 - /obj/item/organ/internal/heart/cursed/attack(mob/living/carbon/human/accursed, mob/living/carbon/human/user, obj/target) if(accursed == user && istype(accursed)) playsound(user,'sound/effects/singlebeat.ogg',40,TRUE) @@ -126,75 +146,15 @@ else return ..() -/// Worker proc that checks logic for if a pump can happen, and applies effects/notifications from doing so -/obj/item/organ/internal/heart/cursed/proc/on_pump(mob/owner) - var/next_pump = last_pump + pump_delay - (1 SECONDS) // pump a second early - if(world.time < next_pump) - to_chat(owner, span_userdanger("Too soon!")) - return - - last_pump = world.time - playsound(owner,'sound/effects/singlebeat.ogg', 40, TRUE) - to_chat(owner, span_notice("Your heart beats.")) - - if(!ishuman(owner)) - return - var/mob/living/carbon/human/accursed = owner - - if(HAS_TRAIT(accursed, TRAIT_NOBLOOD) || !accursed.dna) - return - accursed.blood_volume = min(accursed.blood_volume + (blood_loss * 0.5), BLOOD_VOLUME_MAXIMUM) - accursed.remove_client_colour(/datum/client_colour/cursed_heart_blood) - add_colour = TRUE - accursed.adjustBruteLoss(-heal_brute) - accursed.adjustFireLoss(-heal_burn) - accursed.adjustOxyLoss(-heal_oxy) - -/obj/item/organ/internal/heart/cursed/on_life(seconds_per_tick, times_fired) - if(!owner.client || !ishuman(owner)) // Let's be fair, if you're not here to pump, you're not here to suffer. - last_pump = world.time - return - - if(world.time <= (last_pump + pump_delay)) - return - - var/mob/living/carbon/human/accursed = owner - if(HAS_TRAIT(accursed, TRAIT_NOBLOOD) || !accursed.dna) - return - - accursed.blood_volume = max(accursed.blood_volume - blood_loss, 0) - to_chat(accursed, span_userdanger("You have to keep pumping your blood!")) - if(add_colour) - accursed.add_client_colour(/datum/client_colour/cursed_heart_blood) //bloody screen so real - add_colour = FALSE - -/obj/item/organ/internal/heart/cursed/on_insert(mob/living/carbon/accursed) +/obj/item/organ/internal/heart/cursed/on_mob_insert(mob/living/carbon/accursed) . = ..() - last_pump = world.time // give them time to react - to_chat(accursed, span_userdanger("Your heart has been replaced with a cursed one, you have to pump this one manually otherwise you'll die!")) -/obj/item/organ/internal/heart/cursed/Remove(mob/living/carbon/accursed, special = FALSE) - . = ..() - accursed.remove_client_colour(/datum/client_colour/cursed_heart_blood) - -/datum/action/item_action/organ_action/cursed_heart - name = "Pump your blood" - check_flags = NONE + accursed.AddComponent(/datum/component/manual_heart, pump_delay = pump_delay, blood_loss = blood_loss, heal_brute = heal_brute, heal_burn = heal_burn, heal_oxy = heal_oxy) -//You are now brea- pumping blood manually -/datum/action/item_action/organ_action/cursed_heart/Trigger(trigger_flags) +/obj/item/organ/internal/heart/cursed/on_mob_remove(mob/living/carbon/accursed, special = FALSE) . = ..() - if(!.) - return - var/obj/item/organ/internal/heart/cursed/cursed_heart = target - if(!istype(cursed_heart)) - CRASH("Cursed heart pump action created on non-cursed heart!") - cursed_heart.on_pump(owner) - -/datum/client_colour/cursed_heart_blood - priority = 100 //it's an indicator you're dying, so it's very high priority - colour = "#FF0000" + qdel(accursed.GetComponent(/datum/component/manual_heart)) /obj/item/organ/internal/heart/cybernetic name = "basic cybernetic heart" @@ -203,6 +163,7 @@ base_icon_state = "heart-c" organ_flags = ORGAN_ROBOTIC maxHealth = STANDARD_ORGAN_THRESHOLD*0.75 //This also hits defib timer, so a bit higher than its less important counterparts + failing_desc = "seems to be broken." var/dose_available = FALSE var/rid = /datum/reagent/medicine/epinephrine @@ -291,4 +252,3 @@ owner.heal_overall_damage(brute = 15, burn = 15, required_bodytype = BODYTYPE_ORGANIC) if(owner.reagents.get_reagent_amount(/datum/reagent/medicine/ephedrine) < 20) owner.reagents.add_reagent(/datum/reagent/medicine/ephedrine, 10) - diff --git a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm index f29ee2e731df9..bd30318a72225 100644 --- a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm +++ b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm @@ -1,6 +1,7 @@ /obj/item/organ/internal/heart/ethereal name = "crystal core" - icon_state = "ethereal_heart" //Welp. At least it's more unique in functionaliy. + icon_state = "ethereal_heart-on" + base_icon_state = "ethereal_heart" visual = TRUE //This is used by the ethereal species for color desc = "A crystal-like organ that functions similarly to a heart for Ethereals. It can revive its owner." @@ -18,8 +19,9 @@ /obj/item/organ/internal/heart/ethereal/Initialize(mapload) . = ..() add_atom_colour(ethereal_color, FIXED_COLOUR_PRIORITY) + update_appearance() -/obj/item/organ/internal/heart/ethereal/Insert(mob/living/carbon/heart_owner, special = FALSE, drop_if_replaced = TRUE) +/obj/item/organ/internal/heart/ethereal/Insert(mob/living/carbon/heart_owner, special = FALSE, movement_flags) . = ..() if(!.) return @@ -27,7 +29,7 @@ RegisterSignal(heart_owner, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(on_owner_fully_heal)) RegisterSignal(heart_owner, COMSIG_QDELETING, PROC_REF(owner_deleted)) -/obj/item/organ/internal/heart/ethereal/Remove(mob/living/carbon/heart_owner, special = FALSE) +/obj/item/organ/internal/heart/ethereal/Remove(mob/living/carbon/heart_owner, special, movement_flags) UnregisterSignal(heart_owner, list(COMSIG_MOB_STATCHANGE, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_QDELETING)) REMOVE_TRAIT(heart_owner, TRAIT_CORPSELOCKED, SPECIES_TRAIT) stop_crystalization_process(heart_owner) @@ -36,7 +38,7 @@ /obj/item/organ/internal/heart/ethereal/update_overlays() . = ..() - var/mutable_appearance/shine = mutable_appearance(icon, icon_state = "[icon_state]_shine") + var/mutable_appearance/shine = mutable_appearance(icon, icon_state = "[base_icon_state]_overlay-[beating ? "on" : "off"]") shine.appearance_flags = RESET_COLOR //No color on this, just pure white . += shine @@ -86,12 +88,12 @@ crystalize_timer_id = addtimer(CALLBACK(src, PROC_REF(crystalize), victim), CRYSTALIZE_PRE_WAIT_TIME, TIMER_STOPPABLE) - RegisterSignal(victim, COMSIG_HUMAN_DISARM_HIT, PROC_REF(reset_crystalizing)) + RegisterSignal(victim, COMSIG_LIVING_DISARM_HIT, PROC_REF(reset_crystalizing)) RegisterSignal(victim, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine), override = TRUE) RegisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_take_damage)) ///Ran when disarmed, prevents the ethereal from reviving -/obj/item/organ/internal/heart/ethereal/proc/reset_crystalizing(mob/living/defender, mob/living/attacker, zone) +/obj/item/organ/internal/heart/ethereal/proc/reset_crystalizing(mob/living/defender, mob/living/attacker, zone, obj/item/weapon) SIGNAL_HANDLER defender.visible_message( span_notice("The crystals on [defender] are gently broken off."), @@ -108,7 +110,7 @@ if(!COOLDOWN_FINISHED(src, crystalize_cooldown) || ethereal.stat != DEAD) return //Should probably not happen, but lets be safe. - if(ismob(location) || isitem(location) || HAS_TRAIT_FROM(src, TRAIT_HUSK, CHANGELING_DRAIN)) //Stops crystallization if they are eaten by a dragon, turned into a legion, consumed by his grace, etc. + if(ismob(location) || isitem(location) || iseffect(location) || HAS_TRAIT_FROM(src, TRAIT_HUSK, CHANGELING_DRAIN)) //Stops crystallization if they are eaten by a dragon, turned into a legion, consumed by his grace, etc. to_chat(ethereal, span_warning("You were unable to finish your crystallization, for obvious reasons.")) stop_crystalization_process(ethereal, FALSE) return @@ -118,7 +120,7 @@ ///Stop the crystalization process, unregistering any signals and resetting any variables. /obj/item/organ/internal/heart/ethereal/proc/stop_crystalization_process(mob/living/ethereal, succesful = FALSE) - UnregisterSignal(ethereal, COMSIG_HUMAN_DISARM_HIT) + UnregisterSignal(ethereal, COMSIG_LIVING_DISARM_HIT) UnregisterSignal(ethereal, COMSIG_ATOM_EXAMINE) UnregisterSignal(ethereal, COMSIG_MOB_APPLY_DAMAGE) @@ -139,7 +141,7 @@ return ///Lets you stop the process with enough brute damage -/obj/item/organ/internal/heart/ethereal/proc/on_take_damage(datum/source, damage, damagetype, def_zone) +/obj/item/organ/internal/heart/ethereal/proc/on_take_damage(datum/source, damage, damagetype, def_zone, ...) SIGNAL_HANDLER if(damagetype != BRUTE) return @@ -193,13 +195,13 @@ add_atom_colour(ethereal_heart.ethereal_color, FIXED_COLOUR_PRIORITY) crystal_heal_timer = addtimer(CALLBACK(src, PROC_REF(heal_ethereal)), CRYSTALIZE_HEAL_TIME, TIMER_STOPPABLE) set_light(4, 10, ethereal_heart.ethereal_color) - update_icon() + update_appearance(UPDATE_OVERLAYS) flick("ethereal_crystal_forming", src) addtimer(CALLBACK(src, PROC_REF(start_crystalization)), 1 SECONDS) /obj/structure/ethereal_crystal/proc/start_crystalization() being_built = FALSE - update_icon() + update_appearance(UPDATE_OVERLAYS) /obj/structure/ethereal_crystal/atom_destruction(damage_flag) playsound(get_turf(ethereal_heart.owner), 'sound/effects/ethereal_revive_fail.ogg', 100) diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm index afeb8decde49d..33dcaa83fd1f7 100755 --- a/code/modules/surgery/organs/internal/liver/_liver.dm +++ b/code/modules/surgery/organs/internal/liver/_liver.dm @@ -1,7 +1,7 @@ #define LIVER_DEFAULT_TOX_TOLERANCE 3 //amount of toxins the liver can filter out #define LIVER_DEFAULT_TOX_RESISTANCE 1 //lower values lower how harmful toxins are to the liver #define LIVER_FAILURE_STAGE_SECONDS 60 //amount of seconds before liver failure reaches a new stage -#define MAX_TOXIN_LIVER_DAMAGE 2 //the max damage the liver can recieve per second (~1 min at max damage will destroy liver) +#define MAX_TOXIN_LIVER_DAMAGE 2 //the max damage the liver can receive per second (~1 min at max damage will destroy liver) /obj/item/organ/internal/liver name = "liver" @@ -61,12 +61,12 @@ qdel(GetComponent(/datum/component/squeak)) /// Registers COMSIG_SPECIES_HANDLE_CHEMICAL from owner -/obj/item/organ/internal/liver/on_insert(mob/living/carbon/organ_owner, special) +/obj/item/organ/internal/liver/on_mob_insert(mob/living/carbon/organ_owner, special) . = ..() RegisterSignal(organ_owner, COMSIG_SPECIES_HANDLE_CHEMICAL, PROC_REF(handle_chemical)) /// Unregisters COMSIG_SPECIES_HANDLE_CHEMICAL from owner -/obj/item/organ/internal/liver/on_remove(mob/living/carbon/organ_owner, special) +/obj/item/organ/internal/liver/on_mob_remove(mob/living/carbon/organ_owner, special) . = ..() UnregisterSignal(organ_owner, COMSIG_SPECIES_HANDLE_CHEMICAL) @@ -134,7 +134,7 @@ return var/obj/belly = owner.get_organ_slot(ORGAN_SLOT_STOMACH) - var/list/cached_reagents = owner.reagents.reagent_list + var/list/cached_reagents = owner.reagents?.reagent_list var/liver_damage = 0 var/provide_pain_message = HAS_NO_TOXIN @@ -142,7 +142,7 @@ for(var/datum/reagent/toxin/toxin in cached_reagents) if(toxin.affected_organ_flags && !(organ_flags & toxin.affected_organ_flags)) //this particular toxin does not affect this type of organ continue - var/amount = round(toxin.volume, CHEMICAL_QUANTISATION_LEVEL) // this is an optimization + var/amount = toxin.volume if(belly) amount += belly.reagents.get_reagent_amount(toxin.type) @@ -152,7 +152,7 @@ if(provide_pain_message != HAS_PAINFUL_TOXIN) provide_pain_message = toxin.silent_toxin ? HAS_SILENT_TOXIN : HAS_PAINFUL_TOXIN - owner.reagents.metabolize(owner, seconds_per_tick, times_fired, can_overdose = TRUE) + owner.reagents?.metabolize(owner, seconds_per_tick, times_fired, can_overdose = TRUE) if(liver_damage) apply_organ_damage(min(liver_damage * seconds_per_tick , MAX_TOXIN_LIVER_DAMAGE * seconds_per_tick)) @@ -245,6 +245,7 @@ /obj/item/organ/internal/liver/cybernetic name = "basic cybernetic liver" desc = "A very basic device designed to mimic the functions of a human liver. Handles toxins slightly worse than an organic liver." + failing_desc = "seems to be broken." icon_state = "liver-c" organ_flags = ORGAN_ROBOTIC maxHealth = STANDARD_ORGAN_THRESHOLD*0.5 diff --git a/code/modules/surgery/organs/internal/liver/liver_skeleton.dm b/code/modules/surgery/organs/internal/liver/liver_skeleton.dm index f9b41741b4a34..b57afd245f587 100644 --- a/code/modules/surgery/organs/internal/liver/liver_skeleton.dm +++ b/code/modules/surgery/organs/internal/liver/liver_skeleton.dm @@ -19,8 +19,8 @@ if((. & COMSIG_MOB_STOP_REAGENT_CHECK) || (organ_flags & ORGAN_FAILING)) return if(istype(chem, /datum/reagent/toxin/bonehurtingjuice)) - organ_owner.adjustStaminaLoss(7.5 * REM * seconds_per_tick, 0) - organ_owner.adjustBruteLoss(0.5 * REM * seconds_per_tick, 0) + organ_owner.adjustStaminaLoss(7.5 * REM * seconds_per_tick, updating_stamina = FALSE) + organ_owner.adjustBruteLoss(0.5 * REM * seconds_per_tick, updating_health = FALSE) if(SPT_PROB(10, seconds_per_tick)) switch(rand(1, 3)) if(1) diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 5e4e064806751..78afbd9871e1a 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -47,6 +47,7 @@ var/safe_plasma_min = 0 ///How much breath partial pressure is a safe amount of plasma. 0 means that we are immune to plasma. var/safe_plasma_max = 0.05 + var/n2o_detect_min = 0.08 //Minimum n2o for effects var/n2o_para_min = 1 //Sleeping agent var/n2o_sleep_min = 5 //Sleeping agent var/BZ_trip_balls_min = 1 //BZ gas @@ -153,7 +154,7 @@ add_gas_reaction(/datum/gas/zauker, while_present = PROC_REF(too_much_zauker)) ///Simply exists so that you don't keep any alerts from your previous lack of lungs. -/obj/item/organ/internal/lungs/Insert(mob/living/carbon/receiver, special = FALSE, drop_if_replaced = TRUE) +/obj/item/organ/internal/lungs/Insert(mob/living/carbon/receiver, special = FALSE, movement_flags) . = ..() if(!.) return . @@ -163,7 +164,7 @@ receiver.clear_alert(ALERT_NOT_ENOUGH_PLASMA) receiver.clear_alert(ALERT_NOT_ENOUGH_N2O) -/obj/item/organ/internal/lungs/Remove(mob/living/carbon/organ_owner, special) +/obj/item/organ/internal/lungs/Remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() // This is very "manual" I realize, but it's useful to ensure cleanup for gases we're removing happens // Avoids stuck alerts and such @@ -264,7 +265,7 @@ return var/ratio = (breath.gases[/datum/gas/oxygen][MOLES] / safe_oxygen_max) * 10 - breather.apply_damage_type(clamp(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type) + breather.apply_damage(clamp(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type, spread_damage = TRUE) breather.throw_alert(ALERT_TOO_MUCH_OXYGEN, /atom/movable/screen/alert/too_much_oxy) /// Handles NOT having too much o2. only relevant if safe_oxygen_max has a value @@ -320,10 +321,10 @@ breather.throw_alert(ALERT_TOO_MUCH_CO2, /atom/movable/screen/alert/too_much_co2) breather.Unconscious(6 SECONDS) // Lets hurt em a little, let them know we mean business. - breather.apply_damage_type(3, co2_damage_type) + breather.apply_damage(3, co2_damage_type, spread_damage = TRUE) // They've been in here 30s now, start to kill them for their own good! if((world.time - breather.co2overloadtime) > 30 SECONDS) - breather.apply_damage_type(8, co2_damage_type) + breather.apply_damage(8, co2_damage_type, spread_damage = TRUE) /// Handles NOT having too much co2. only relevant if safe_co2_max has a value /obj/item/organ/internal/lungs/proc/safe_co2(mob/living/carbon/breather, datum/gas_mixture/breath, old_co2_pp) @@ -364,7 +365,7 @@ breather.throw_alert(ALERT_TOO_MUCH_PLASMA, /atom/movable/screen/alert/too_much_plas) var/ratio = (breath.gases[/datum/gas/plasma][MOLES] / safe_plasma_max) * 10 - breather.apply_damage_type(clamp(ratio, plas_breath_dam_min, plas_breath_dam_max), plas_damage_type) + breather.apply_damage(clamp(ratio, plas_breath_dam_min, plas_breath_dam_max), plas_damage_type, spread_damage = TRUE) /// Resets plasma side effects /obj/item/organ/internal/lungs/proc/safe_plasma(mob/living/carbon/breather, datum/gas_mixture/breath, old_plasma_pp) @@ -503,17 +504,18 @@ /obj/item/organ/internal/lungs/proc/too_much_n2o(mob/living/carbon/breather, datum/gas_mixture/breath, n2o_pp, old_n2o_pp) if(n2o_pp < n2o_para_min) // Small amount of N2O, small side-effects. - if(n2o_pp <= 0.01) - if(old_n2o_pp > 0.01) + if(n2o_pp <= n2o_detect_min) + if(old_n2o_pp > n2o_detect_min) return BREATH_LOST return // No alert for small amounts, but the mob randomly feels euphoric. - if(old_n2o_pp >= n2o_para_min || old_n2o_pp <= 0.01) + if(old_n2o_pp >= n2o_para_min || old_n2o_pp <= n2o_detect_min) breather.clear_alert(ALERT_TOO_MUCH_N2O) if(prob(20)) n2o_euphoria = EUPHORIA_ACTIVE breather.emote(pick("giggle", "laugh")) + breather.set_drugginess(30 SECONDS) else n2o_euphoria = EUPHORIA_INACTIVE return @@ -753,11 +755,11 @@ if(!HAS_TRAIT(breather, TRAIT_RESISTCOLD)) // COLD DAMAGE var/cold_modifier = breather.dna.species.coldmod if(breath_temperature < cold_level_3_threshold) - breather.apply_damage_type(cold_level_3_damage*cold_modifier, cold_damage_type) + breather.apply_damage(cold_level_3_damage*cold_modifier, cold_damage_type, spread_damage = TRUE) if(breath_temperature > cold_level_3_threshold && breath_temperature < cold_level_2_threshold) - breather.apply_damage_type(cold_level_2_damage*cold_modifier, cold_damage_type) + breather.apply_damage(cold_level_2_damage*cold_modifier, cold_damage_type, spread_damage = TRUE) if(breath_temperature > cold_level_2_threshold && breath_temperature < cold_level_1_threshold) - breather.apply_damage_type(cold_level_1_damage*cold_modifier, cold_damage_type) + breather.apply_damage(cold_level_1_damage*cold_modifier, cold_damage_type, spread_damage = TRUE) if(breath_temperature < cold_level_1_threshold) if(prob(20)) to_chat(breather, span_warning("You feel [cold_message] in your [name]!")) @@ -765,11 +767,11 @@ if(!HAS_TRAIT(breather, TRAIT_RESISTHEAT)) // HEAT DAMAGE var/heat_modifier = breather.dna.species.heatmod if(breath_temperature > heat_level_1_threshold && breath_temperature < heat_level_2_threshold) - breather.apply_damage_type(heat_level_1_damage*heat_modifier, heat_damage_type) + breather.apply_damage(heat_level_1_damage*heat_modifier, heat_damage_type, spread_damage = TRUE) if(breath_temperature > heat_level_2_threshold && breath_temperature < heat_level_3_threshold) - breather.apply_damage_type(heat_level_2_damage*heat_modifier, heat_damage_type) + breather.apply_damage(heat_level_2_damage*heat_modifier, heat_damage_type, spread_damage = TRUE) if(breath_temperature > heat_level_3_threshold) - breather.apply_damage_type(heat_level_3_damage*heat_modifier, heat_damage_type) + breather.apply_damage(heat_level_3_damage*heat_modifier, heat_damage_type, spread_damage = TRUE) if(breath_temperature > heat_level_1_threshold) if(prob(20)) to_chat(breather, span_warning("You feel [hot_message] in your [name]!")) @@ -837,6 +839,7 @@ /obj/item/organ/internal/lungs/cybernetic name = "basic cybernetic lungs" desc = "A basic cybernetic version of the lungs found in traditional humanoid entities." + failing_desc = "seems to be broken." icon_state = "lungs-c" organ_flags = ORGAN_ROBOTIC maxHealth = STANDARD_ORGAN_THRESHOLD * 0.5 diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm index c817e7209db47..6e985e26329b6 100644 --- a/code/modules/surgery/organs/internal/stomach/_stomach.dm +++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm @@ -55,7 +55,7 @@ var/mob/living/carbon/body = owner // digest food, sent all reagents that can metabolize to the body - for(var/datum/reagent/bit as anything in reagents.reagent_list) + for(var/datum/reagent/bit as anything in reagents?.reagent_list) // If the reagent does not metabolize then it will sit in the stomach // This has an effect on items like plastic causing them to take up space in the stomach @@ -82,7 +82,7 @@ // transfer the reagents over to the body at the rate of the stomach metabolim // this way the body is where all reagents that are processed and react // the stomach manages how fast they are feed in a drip style - reagents.trans_id_to(body, bit.type, amount=amount) + reagents.trans_to(body, amount, target_id = bit.type) //Handle disgust if(body) @@ -93,7 +93,7 @@ return //We are checking if we have nutriment in a damaged stomach. - var/datum/reagent/nutri = locate(/datum/reagent/consumable/nutriment) in reagents.reagent_list + var/datum/reagent/nutri = locate(/datum/reagent/consumable/nutriment) in reagents?.reagent_list //No nutriment found lets exit out if(!nutri) return @@ -262,7 +262,7 @@ disgusted.throw_alert(ALERT_DISGUST, /atom/movable/screen/alert/disgusted) disgusted.add_mood_event("disgust", /datum/mood_event/disgusted) -/obj/item/organ/internal/stomach/Remove(mob/living/carbon/stomach_owner, special = 0) +/obj/item/organ/internal/stomach/Remove(mob/living/carbon/stomach_owner, special, movement_flags) if(ishuman(stomach_owner)) var/mob/living/carbon/human/human_owner = owner human_owner.clear_alert(ALERT_DISGUST) @@ -288,6 +288,7 @@ /obj/item/organ/internal/stomach/cybernetic name = "basic cybernetic stomach" desc = "A basic device designed to mimic the functions of a human stomach" + failing_desc = "seems to be broken." icon_state = "stomach-c" organ_flags = ORGAN_ROBOTIC maxHealth = STANDARD_ORGAN_THRESHOLD * 0.5 diff --git a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm index 4d43b6a3a0aa9..68f9d9428a04a 100644 --- a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm +++ b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm @@ -13,12 +13,12 @@ adjust_charge(-ETHEREAL_CHARGE_FACTOR * seconds_per_tick) handle_charge(owner, seconds_per_tick, times_fired) -/obj/item/organ/internal/stomach/ethereal/on_insert(mob/living/carbon/stomach_owner) +/obj/item/organ/internal/stomach/ethereal/on_mob_insert(mob/living/carbon/stomach_owner) . = ..() RegisterSignal(stomach_owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, PROC_REF(charge)) RegisterSignal(stomach_owner, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(on_electrocute)) -/obj/item/organ/internal/stomach/ethereal/on_remove(mob/living/carbon/stomach_owner) +/obj/item/organ/internal/stomach/ethereal/on_mob_remove(mob/living/carbon/stomach_owner) . = ..() UnregisterSignal(stomach_owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT) UnregisterSignal(stomach_owner, COMSIG_LIVING_ELECTROCUTE_ACT) @@ -92,7 +92,7 @@ playsound(carbon, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5) carbon.cut_overlay(overcharge) - tesla_zap(carbon, 2, crystal_charge*2.5, ZAP_OBJ_DAMAGE | ZAP_LOW_POWER_GEN | ZAP_ALLOW_DUPLICATES) + tesla_zap(source = carbon, zap_range = 2, power = crystal_charge * 2.5, cutoff = 1e3, zap_flags = ZAP_OBJ_DAMAGE | ZAP_LOW_POWER_GEN | ZAP_ALLOW_DUPLICATES) adjust_charge(ETHEREAL_CHARGE_FULL - crystal_charge) carbon.visible_message(span_danger("[carbon] violently discharges energy!"), span_warning("You violently discharge energy!")) diff --git a/code/modules/surgery/organs/internal/stomach/stomach_golem.dm b/code/modules/surgery/organs/internal/stomach/stomach_golem.dm index 79d3976f22d6b..a1f5ce6c70ea8 100644 --- a/code/modules/surgery/organs/internal/stomach/stomach_golem.dm +++ b/code/modules/surgery/organs/internal/stomach/stomach_golem.dm @@ -11,11 +11,11 @@ /// How slow are you if you have absolutely nothing in the tank? var/max_hunger_slowdown = 4 -/obj/item/organ/internal/stomach/golem/on_insert(mob/living/carbon/organ_owner, special) +/obj/item/organ/internal/stomach/golem/on_mob_insert(mob/living/carbon/organ_owner, special) . = ..() RegisterSignal(owner, COMSIG_CARBON_ATTEMPT_EAT, PROC_REF(try_eating)) -/obj/item/organ/internal/stomach/golem/on_remove(mob/living/carbon/organ_owner, special) +/obj/item/organ/internal/stomach/golem/on_mob_remove(mob/living/carbon/organ_owner, special) . = ..() UnregisterSignal(organ_owner, COMSIG_CARBON_ATTEMPT_EAT) organ_owner.remove_movespeed_modifier(/datum/movespeed_modifier/golem_hunger) @@ -74,7 +74,7 @@ return TRUE /datum/status_effect/golem_statued/get_examine_text() - return span_warning("[owner.p_They()] are as still as a statue!") + return span_warning("[owner.p_They()] [owner.p_are()] as still as a statue!") /datum/status_effect/golem_statued/on_remove() owner.visible_message(span_notice("[owner] slowly stirs back into motion!"), span_notice("You have gathered enough strength to move your body once more.")) diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm index 3a23d8a630256..5098d738d305b 100644 --- a/code/modules/surgery/organs/internal/tongue/_tongue.dm +++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm @@ -94,8 +94,10 @@ /obj/item/organ/internal/tongue/proc/handle_speech(datum/source, list/speech_args) SIGNAL_HANDLER - if(speech_args[SPEECH_LANGUAGE] in languages_native) - return FALSE //no changes + if(speech_args[SPEECH_LANGUAGE] in languages_native) // Speaking a native language? + return FALSE // Don't modify speech + if(HAS_TRAIT(source, TRAIT_SIGN_LANG)) // No modifiers for signers - I hate this but I simply cannot get these to combine into one statement + return FALSE // Don't modify speech modify_speech(source, speech_args) /obj/item/organ/internal/tongue/proc/modify_speech(datum/source, list/speech_args) @@ -118,7 +120,7 @@ food_taste_reaction = FOOD_LIKED return food_taste_reaction -/obj/item/organ/internal/tongue/Insert(mob/living/carbon/tongue_owner, special = FALSE, drop_if_replaced = TRUE) +/obj/item/organ/internal/tongue/Insert(mob/living/carbon/tongue_owner, special = FALSE, movement_flags) . = ..() if(!.) return @@ -133,7 +135,7 @@ REMOVE_TRAIT(tongue_owner, TRAIT_AGEUSIA, NO_TONGUE_TRAIT) apply_tongue_effects() -/obj/item/organ/internal/tongue/Remove(mob/living/carbon/tongue_owner, special = FALSE) +/obj/item/organ/internal/tongue/Remove(mob/living/carbon/tongue_owner, special, movement_flags) . = ..() temp_say_mod = "" UnregisterSignal(tongue_owner, COMSIG_MOB_SAY) @@ -146,7 +148,7 @@ /obj/item/organ/internal/tongue/apply_organ_damage(damage_amount, maximum = maxHealth, required_organ_flag) . = ..() if(!owner) - return + return FALSE apply_tongue_effects() /// Applies effects to our owner based on how damaged our tongue is @@ -181,6 +183,7 @@ languages_native = list(/datum/language/draconic) liked_foodtypes = GORE | MEAT | SEAFOOD | NUTS | BUGS disliked_foodtypes = GRAIN | DAIRY | CLOTH | GROSS + voice_filter = @{"[0:a] asplit [out0][out2]; [out0] asetrate=%SAMPLE_RATE%*0.9,aresample=%SAMPLE_RATE%,atempo=1/0.9,aformat=channel_layouts=mono,volume=0.2 [p0]; [out2] asetrate=%SAMPLE_RATE%*1.1,aresample=%SAMPLE_RATE%,atempo=1/1.1,aformat=channel_layouts=mono,volume=0.2[p2]; [p0][0][p2] amix=inputs=3"} /obj/item/organ/internal/tongue/lizard/modify_speech(datum/source, list/speech_args) var/static/regex/lizard_hiss = new("s+", "g") @@ -306,7 +309,7 @@ return // the statue ended up getting destroyed while in nullspace? var/mob/living/carbon/carbon_owner = owner - RegisterSignal(carbon_owner, COMSIG_MOVABLE_MOVED) + UnregisterSignal(carbon_owner, COMSIG_MOVABLE_MOVED) to_chat(carbon_owner, span_userdanger("Your existence as a living creature snaps as your statue form crumbles!")) carbon_owner.forceMove(get_turf(statue)) @@ -475,7 +478,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) say_mod = "hisses" taste_sensitivity = 10 // LIZARDS ARE ALIENS CONFIRMED modifies_speech = TRUE // not really, they just hiss - + voice_filter = @{"[0:a] asplit [out0][out2]; [out0] asetrate=%SAMPLE_RATE%*0.8,aresample=%SAMPLE_RATE%,atempo=1/0.8,aformat=channel_layouts=mono [p0]; [out2] asetrate=%SAMPLE_RATE%*1.2,aresample=%SAMPLE_RATE%,atempo=1/1.2,aformat=channel_layouts=mono[p2]; [p0][0][p2] amix=inputs=3"} // Aliens can only speak alien and a few other languages. /obj/item/organ/internal/tongue/alien/get_possible_languages() return list( @@ -536,6 +539,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) /obj/item/organ/internal/tongue/robot name = "robotic voicebox" desc = "A voice synthesizer that can interface with organic lifeforms." + failing_desc = "seems to be broken." organ_flags = ORGAN_ROBOTIC icon_state = "tonguerobot" say_mod = "states" @@ -545,7 +549,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) taste_sensitivity = 25 // not as good as an organic tongue voice_filter = "alimiter=0.9,acompressor=threshold=0.2:ratio=20:attack=10:release=50:makeup=2,highpass=f=1000" -/obj/item/organ/internal/tongue/robot/can_speak_language(language) +/obj/item/organ/internal/tongue/robot/could_speak_language(datum/language/language_path) return TRUE // THE MAGIC OF ELECTRONICS /obj/item/organ/internal/tongue/robot/modify_speech(datum/source, list/speech_args) @@ -579,6 +583,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) toxic_foodtypes = NONE //no food is particularly toxic to ethereals attack_verb_continuous = list("shocks", "jolts", "zaps") attack_verb_simple = list("shock", "jolt", "zap") + voice_filter = @{"[0:a] asplit [out0][out2]; [out0] asetrate=%SAMPLE_RATE%*0.99,aresample=%SAMPLE_RATE%,volume=0.3 [p0]; [p0][out2] amix=inputs=2"} // Ethereal tongues can speak all default + voltaic /obj/item/organ/internal/tongue/ethereal/get_possible_languages() diff --git a/code/modules/surgery/organs/organ_movement.dm b/code/modules/surgery/organs/organ_movement.dm new file mode 100644 index 0000000000000..073ef4b113730 --- /dev/null +++ b/code/modules/surgery/organs/organ_movement.dm @@ -0,0 +1,232 @@ +// There are two kinds of organ movement: mob movement and limb movement +// If you pull someones brain out, you remove it from the mob and the limb +// If you take someones head off, you remove it from the mob but not the limb +// If you remove the brain from an already decapitated head, you remove it from the limb but not the mob + +// Keep the seperation of limb removal and mob removal absolute + +/* + * Insert the organ into the select mob. + * + * receiver - the mob who will get our organ + * special - "quick swapping" an organ out - when TRUE, the mob will be unaffected by not having that organ for the moment + * movement_flags - Flags for how we behave in movement. See DEFINES/organ_movement for flags + */ +/obj/item/organ/proc/Insert(mob/living/carbon/receiver, special = FALSE, movement_flags) + SHOULD_CALL_PARENT(TRUE) + + mob_insert(receiver, special, movement_flags) + bodypart_insert(limb_owner = receiver, movement_flags = movement_flags) + + return TRUE + +/* + * Remove the organ from the select mob. + * + * * organ_owner - the mob who owns our organ, that we're removing the organ from. Can be null + * * special - "quick swapping" an organ out - when TRUE, the mob will be unaffected by not having that organ for the moment + */ +/obj/item/organ/proc/Remove(mob/living/carbon/organ_owner, special = FALSE, movement_flags) + SHOULD_CALL_PARENT(TRUE) + + mob_remove(organ_owner, special, movement_flags) + bodypart_remove(limb_owner = organ_owner, movement_flags = movement_flags) + +/* + * Insert the organ into the select mob. + * + * receiver - the mob who will get our organ + * special - "quick swapping" an organ out - when TRUE, the mob will be unaffected by not having that organ for the moment + * movement_flags - Flags for how we behave in movement. See DEFINES/organ_movement for flags + */ +/obj/item/organ/proc/mob_insert(mob/living/carbon/receiver, special, movement_flags) + SHOULD_CALL_PARENT(TRUE) + + if(!iscarbon(receiver)) + stack_trace("Tried to insert organ into non-carbon: [receiver.type]") + return + + if(owner == receiver) + stack_trace("Organ receiver is already organ owner") + return + + var/obj/item/organ/replaced = receiver.get_organ_slot(slot) + if(replaced) + replaced.Remove(receiver, special = TRUE) + if(movement_flags & DELETE_IF_REPLACED) + qdel(replaced) + else + replaced.forceMove(get_turf(receiver)) + + if(!IS_ROBOTIC_ORGAN(src) && (organ_flags & ORGAN_VIRGIN)) + blood_dna_info = receiver.get_blood_dna_list() + // need to remove the synethic blood DNA that is initialized + // wash also adds the blood dna again + wash(CLEAN_TYPE_BLOOD) + organ_flags &= ~ORGAN_VIRGIN + + receiver.organs |= src + receiver.organs_slot[slot] = src + owner = receiver + + on_mob_insert(receiver, special) + + return TRUE + +/// Called after the organ is inserted into a mob. +/// Adds Traits, Actions, and Status Effects on the mob in which the organ is impanted. +/// Override this proc to create unique side-effects for inserting your organ. Must be called by overrides. +/obj/item/organ/proc/on_mob_insert(mob/living/carbon/organ_owner, special = FALSE, movement_flags) + SHOULD_CALL_PARENT(TRUE) + + for(var/trait in organ_traits) + ADD_TRAIT(organ_owner, trait, REF(src)) + + for(var/datum/action/action as anything in actions) + action.Grant(organ_owner) + + for(var/datum/status_effect/effect as anything in organ_effects) + organ_owner.apply_status_effect(effect, type) + + RegisterSignal(owner, COMSIG_ATOM_EXAMINE, PROC_REF(on_owner_examine)) + SEND_SIGNAL(src, COMSIG_ORGAN_IMPLANTED, organ_owner) + SEND_SIGNAL(organ_owner, COMSIG_CARBON_GAIN_ORGAN, src, special) + +/// Insert an organ into a limb, assume the limb as always detached and include no owner operations here (except the get_bodypart helper here I guess) +/// Give EITHER a limb OR a limb owner +/obj/item/organ/proc/bodypart_insert(obj/item/bodypart/bodypart, mob/living/carbon/limb_owner, movement_flags) + SHOULD_CALL_PARENT(TRUE) + + if(limb_owner) + bodypart = limb_owner.get_bodypart(deprecise_zone(zone)) + + // The true movement + forceMove(bodypart) + bodypart.contents |= src + bodypart_owner = bodypart + + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(forced_removal)) + + // Apply unique side-effects. Return value does not matter. + on_bodypart_insert(bodypart) + + return TRUE + +/// Add any limb specific effects you might want here +/obj/item/organ/proc/on_bodypart_insert(obj/item/bodypart/limb, movement_flags) + SHOULD_CALL_PARENT(TRUE) + + item_flags |= ABSTRACT + ADD_TRAIT(src, TRAIT_NODROP, ORGAN_INSIDE_BODY_TRAIT) + interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP + +/* + * Remove the organ from the select mob. + * + * * organ_owner - the mob who owns our organ, that we're removing the organ from. Can be null + * * special - "quick swapping" an organ out - when TRUE, the mob will be unaffected by not having that organ for the moment + */ +/obj/item/organ/proc/mob_remove(mob/living/carbon/organ_owner, special = FALSE, movement_flags) + SHOULD_CALL_PARENT(TRUE) + + if(organ_owner) + if(organ_owner.organs_slot[slot] == src) + organ_owner.organs_slot.Remove(slot) + organ_owner.organs -= src + + owner = null + + on_mob_remove(organ_owner, special) + + return TRUE + +/// Called after the organ is removed from a mob. +/// Removes Traits, Actions, and Status Effects on the mob in which the organ was impanted. +/// Override this proc to create unique side-effects for removing your organ. Must be called by overrides. +/obj/item/organ/proc/on_mob_remove(mob/living/carbon/organ_owner, special = FALSE, movement_flags) + SHOULD_CALL_PARENT(TRUE) + + if(!iscarbon(organ_owner)) + stack_trace("Organ removal should not be happening on non carbon mobs: [organ_owner]") + + for(var/trait in organ_traits) + REMOVE_TRAIT(organ_owner, trait, REF(src)) + + for(var/datum/action/action as anything in actions) + action.Remove(organ_owner) + + for(var/datum/status_effect/effect as anything in organ_effects) + organ_owner.remove_status_effect(effect, type) + + UnregisterSignal(organ_owner, COMSIG_ATOM_EXAMINE) + SEND_SIGNAL(src, COMSIG_ORGAN_REMOVED, organ_owner) + SEND_SIGNAL(organ_owner, COMSIG_CARBON_LOSE_ORGAN, src, special) + + var/list/diseases = organ_owner.get_static_viruses() + if(!LAZYLEN(diseases)) + return + + var/list/datum/disease/diseases_to_add = list() + for(var/datum/disease/disease as anything in diseases) + // robotic organs are immune to disease unless 'inorganic biology' symptom is present + if(IS_ROBOTIC_ORGAN(src) && !(disease.infectable_biotypes & MOB_ROBOTIC)) + continue + + // admin or special viruses that should not be reproduced + if(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS)) + continue + + diseases_to_add += disease + + if(LAZYLEN(diseases_to_add)) + AddComponent(/datum/component/infective, diseases_to_add) + +/// Called to remove an organ from a limb. Do not put any mob operations here (except the bodypart_getter at the start) +/// Give EITHER a limb OR a limb_owner +/obj/item/organ/proc/bodypart_remove(obj/item/bodypart/limb, mob/living/carbon/limb_owner, movement_flags) + SHOULD_CALL_PARENT(TRUE) + + if(!isnull(limb_owner)) + limb = limb_owner.get_bodypart(deprecise_zone(zone)) + + UnregisterSignal(src, COMSIG_MOVABLE_MOVED) //DONT MOVE THIS!!!! we moves the organ right after, so we unregister before we move them physically + + // The true movement is here + moveToNullspace() + bodypart_owner.contents -= src + bodypart_owner = null + + on_bodypart_remove(limb) + + return TRUE + +/// Called on limb removal to remove limb specific limb effects or statusses +/obj/item/organ/proc/on_bodypart_remove(obj/item/bodypart/limb, movement_flags) + SHOULD_CALL_PARENT(TRUE) + + if(!IS_ROBOTIC_ORGAN(src) && !(item_flags & NO_BLOOD_ON_ITEM) && !QDELING(src)) + AddElement(/datum/element/decal/blood) + + item_flags &= ~ABSTRACT + REMOVE_TRAIT(src, TRAIT_NODROP, ORGAN_INSIDE_BODY_TRAIT) + interaction_flags_item |= INTERACT_ITEM_ATTACK_HAND_PICKUP + +/// In space station videogame, nothing is sacred. If somehow an organ is removed unexpectedly, handle it properly +/obj/item/organ/proc/forced_removal() + SIGNAL_HANDLER + + if(owner) + Remove(owner) + else if(bodypart_owner) + bodypart_remove(bodypart_owner) + else + stack_trace("Force removed an already removed organ!") + +/** + * Proc that gets called when the organ is surgically removed by someone, can be used for special effects + * Currently only used so surplus organs can explode when surgically removed. + */ +/obj/item/organ/proc/on_surgical_removal(mob/living/user, mob/living/carbon/old_owner, target_zone, obj/item/tool) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_ORGAN_SURGICALLY_REMOVED, user, old_owner, target_zone, tool) + RemoveElement(/datum/element/decal/blood) diff --git a/code/modules/surgery/plastic_surgery.dm b/code/modules/surgery/plastic_surgery.dm index 440d48d1c5ef9..9224c29b2db63 100644 --- a/code/modules/surgery/plastic_surgery.dm +++ b/code/modules/surgery/plastic_surgery.dm @@ -1,3 +1,9 @@ +/// Disk containing info for doing advanced plastic surgery. Spawns in maint and available as a role-restricted item in traitor uplinks. +/obj/item/disk/surgery/advanced_plastic_surgery + name = "Advanced Plastic Surgery Disk" + desc = "The disk provides instructions on how to do an Advanced Plastic Surgery, this surgery allows one-self to completely remake someone's face with that of another. Provided they have a picture of them in their offhand when reshaping the face. With the surgery long becoming obsolete with the rise of genetics technology. This item became an antique to many collectors, With only the cheaper and easier basic form of plastic surgery remaining in use in most places." + surgeries = list(/datum/surgery/plastic_surgery/advanced) + /datum/surgery/plastic_surgery name = "Plastic surgery" surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB | SURGERY_MORBID_CURIOSITY @@ -9,6 +15,43 @@ /datum/surgery_step/close, ) +/datum/surgery/plastic_surgery/advanced + name = "Advanced plastic surgery" + desc = "Surgery allows one-self to completely remake someone's face with that of another. Provided they have a picture of them in their offhand when reshaping the face." + requires_tech = TRUE + steps = list( + /datum/surgery_step/incise, + /datum/surgery_step/retract_skin, + /datum/surgery_step/insert_plastic, + /datum/surgery_step/reshape_face, + /datum/surgery_step/close, + ) + +//Insert plastic step, It ain't called plastic surgery for nothing! :) +/datum/surgery_step/insert_plastic + name = "insert plastic (plastic)" + implements = list( + /obj/item/stack/sheet/plastic = 100, + /obj/item/stack/sheet/meat = 100) + time = 3.2 SECONDS + preop_sound = 'sound/effects/blobattack.ogg' + success_sound = 'sound/effects/attackblob.ogg' + failure_sound = 'sound/effects/blobattack.ogg' + +/datum/surgery_step/insert_plastic/preop(mob/user, mob/living/target, target_zone, obj/item/stack/tool, datum/surgery/surgery) + display_results( + user, + target, + span_notice("You begin to insert [tool] into the incision in [target]'s [parse_zone(target_zone)]..."), + span_notice("[user] begins to insert [tool] into the incision in [target]'s [parse_zone(target_zone)]."), + span_notice("[user] begins to insert [tool] into the incision in [target]'s [parse_zone(target_zone)]."), + ) + display_pain(target, "You feel something inserting just below the skin in your [parse_zone(target_zone)].") + +/datum/surgery_step/insert_plastic/success(mob/user, mob/living/target, target_zone, obj/item/stack/tool, datum/surgery/surgery, default_display_results) + . = ..() + tool.use(1) + //reshape_face /datum/surgery_step/reshape_face name = "reshape face (scalpel)" @@ -43,8 +86,15 @@ else var/list/names = list() if(!isabductor(user)) - for(var/i in 1 to 10) - names += target.dna.species.random_name(target.gender, TRUE) + var/obj/item/offhand = user.get_inactive_held_item() + if(istype(offhand, /obj/item/photo) && istype(surgery, /datum/surgery/plastic_surgery/advanced)) + var/obj/item/photo/disguises = offhand + for(var/namelist as anything in disguises.picture?.names_seen) + names += namelist + else + user.visible_message(span_warning("You have no picture to base the appearance on, reverting to random appearances.")) + for(var/i in 1 to 10) + names += target.dna.species.random_name(target.gender, TRUE) else for(var/_i in 1 to 9) names += "Subject [target.gender == MALE ? "i" : "o"]-[pick("a", "b", "c", "d", "e")]-[rand(10000, 99999)]" diff --git a/code/modules/surgery/prosthetic_replacement.dm b/code/modules/surgery/prosthetic_replacement.dm index b2c8c7485375e..4b285d161bb06 100644 --- a/code/modules/surgery/prosthetic_replacement.dm +++ b/code/modules/surgery/prosthetic_replacement.dm @@ -17,6 +17,8 @@ ) /datum/surgery/prosthetic_replacement/can_start(mob/user, mob/living/carbon/target) + if(!..()) + return FALSE if(!iscarbon(target)) return FALSE var/mob/living/carbon/carbon_target = target diff --git a/code/modules/surgery/repair_puncture.dm b/code/modules/surgery/repair_puncture.dm index bb33c314689bc..9b9071cff89c5 100644 --- a/code/modules/surgery/repair_puncture.dm +++ b/code/modules/surgery/repair_puncture.dm @@ -27,13 +27,13 @@ ) /datum/surgery/repair_puncture/can_start(mob/living/user, mob/living/carbon/target) - if(!istype(target)) - return FALSE . = ..() - if(.) - var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected) - var/datum/wound/burn/flesh/pierce_wound = targeted_bodypart.get_wound_type(targetable_wound) - return(pierce_wound && pierce_wound.blood_flow > 0) + if(!.) + return . + + var/datum/wound/pierce/bleed/pierce_wound = target.get_bodypart(user.zone_selected).get_wound_type(targetable_wound) + ASSERT(pierce_wound, "[type] on [target] has no pierce wound when it should have been guaranteed to have one by can_start") + return pierce_wound.blood_flow > 0 //SURGERY STEPS @@ -45,6 +45,7 @@ TOOL_SCALPEL = 85, TOOL_WIRECUTTER = 40) time = 3 SECONDS + preop_sound = 'sound/surgery/hemostat1.ogg' /datum/surgery_step/repair_innards/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound @@ -104,6 +105,8 @@ TOOL_WELDER = 70, /obj/item = 30) time = 4 SECONDS + preop_sound = 'sound/surgery/cautery1.ogg' + success_sound = 'sound/surgery/cautery2.ogg' /datum/surgery_step/seal_veins/tool_check(mob/user, obj/item/tool) if(implement_type == TOOL_WELDER || implement_type == /obj/item) diff --git a/code/modules/surgery/revival.dm b/code/modules/surgery/revival.dm index f7e9fcce390e9..fc55598643103 100644 --- a/code/modules/surgery/revival.dm +++ b/code/modules/surgery/revival.dm @@ -5,7 +5,7 @@ requires_bodypart_type = NONE possible_locs = list(BODY_ZONE_CHEST) target_mobtypes = list(/mob/living) - surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_MORBID_CURIOSITY + surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_MORBID_CURIOSITY steps = list( /datum/surgery_step/incise, /datum/surgery_step/retract_skin, @@ -75,7 +75,7 @@ span_notice("[user] prepares to shock [target]'s brain with [tool]."), span_notice("[user] prepares to shock [target]'s brain with [tool]."), ) - target.notify_ghost_cloning("Someone is trying to zap your brain.", source = target) + target.notify_revival("Someone is trying to zap your brain.", source = target) /datum/surgery_step/revive/play_preop_sound(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) if(istype(tool, /obj/item/shockpaddles)) @@ -123,6 +123,7 @@ /datum/surgery/revival/carbon possible_locs = list(BODY_ZONE_HEAD) target_mobtypes = list(/mob/living/carbon) + surgery_flags = parent_type::surgery_flags | SURGERY_REQUIRE_LIMB /datum/surgery/revival/carbon/is_valid_target(mob/living/carbon/patient) var/obj/item/organ/internal/brain/target_brain = patient.get_organ_slot(ORGAN_SLOT_BRAIN) diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm index 7320e6ed94753..a555548e43268 100644 --- a/code/modules/surgery/surgery.dm +++ b/code/modules/surgery/surgery.dm @@ -20,17 +20,19 @@ var/list/possible_locs = list() ///Mobs that are valid to have surgery performed on them. var/list/target_mobtypes = list(/mob/living/carbon/human) + ///The person the surgery is being performed on. Funnily enough, it isn't always a carbon. - var/mob/living/carbon/target + VAR_FINAL/mob/living/carbon/target ///The specific bodypart being operated on. - var/obj/item/bodypart/operated_bodypart + VAR_FINAL/obj/item/bodypart/operated_bodypart ///The wound datum that is being operated on. - var/datum/wound/operated_wound - ///Types of wounds this surgery can target. - var/datum/wound/targetable_wound + VAR_FINAL/datum/wound/operated_wound + ///Types of wounds this surgery can target. + var/targetable_wound ///The types of bodyparts that this surgery can have performed on it. Used for augmented surgeries. var/requires_bodypart_type = BODYTYPE_ORGANIC + ///The speed modifier given to the surgery through external means. var/speed_modifier = 0 ///Whether the surgery requires research to do. You need to add a design if using this! @@ -69,6 +71,8 @@ /datum/surgery/proc/can_start(mob/user, mob/living/patient) //FALSE to not show in list + SHOULD_CALL_PARENT(TRUE) + . = TRUE if(replaced_by == /datum/surgery) return FALSE @@ -102,6 +106,7 @@ return FALSE if(type in opcomputer.advanced_surgeries) return TRUE + return . /datum/surgery/proc/next_step(mob/living/user, modifiers) if(location != user.zone_selected) diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index cafa1161693a3..342f984042fec 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -23,7 +23,9 @@ if(!tool) success = TRUE if(iscyborg(user)) - success = TRUE + var/mob/living/silicon/robot/borg = user + if(istype(borg.module_active, /obj/item/borg/cyborghug)) + success = TRUE if(accept_any_item) if(tool && tool_check(user, tool)) @@ -63,11 +65,13 @@ return FALSE -#define SURGERY_SLOWDOWN_CAP_MULTIPLIER 2 //increase to make surgery slower but fail less, and decrease to make surgery faster but fail more +#define SURGERY_SLOWDOWN_CAP_MULTIPLIER 2.5 //increase to make surgery slower but fail less, and decrease to make surgery faster but fail more ///Modifier given to surgery speed for dissected bodies. #define SURGERY_SPEED_DISSECTION_MODIFIER 0.8 ///Modifier given to users with TRAIT_MORBID on certain surgeries #define SURGERY_SPEED_MORBID_CURIOSITY 0.7 +///Modifier given to patients with TRAIT_ANALGESIA +#define SURGERY_SPEED_TRAIT_ANALGESIA 0.8 /datum/surgery_step/proc/initiate(mob/living/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) // Only followers of Asclepius have the ability to use Healing Touch and perform miracle feats of surgery. @@ -93,6 +97,9 @@ if(check_morbid_curiosity(user, tool, surgery)) speed_mod *= SURGERY_SPEED_MORBID_CURIOSITY + if(HAS_TRAIT(target, TRAIT_ANALGESIA)) + speed_mod *= SURGERY_SPEED_TRAIT_ANALGESIA + var/implement_speed_mod = 1 if(implement_type) //this means it isn't a require hand or any item step. implement_speed_mod = implements[implement_type] / 100.0 @@ -258,10 +265,14 @@ */ /datum/surgery_step/proc/display_pain(mob/living/target, pain_message, mechanical_surgery = FALSE) if(target.stat < UNCONSCIOUS) - to_chat(target, span_userdanger(pain_message)) - if(prob(30) && !mechanical_surgery) - target.emote("scream") + if(HAS_TRAIT(target, TRAIT_ANALGESIA)) + to_chat(target, span_notice("You feel a dull, numb sensation as your body is surgically operated on.")) + else + to_chat(target, span_userdanger(pain_message)) + if(prob(30) && !mechanical_surgery) + target.emote("scream") +#undef SURGERY_SPEED_TRAIT_ANALGESIA #undef SURGERY_SPEED_DISSECTION_MODIFIER #undef SURGERY_SPEED_MORBID_CURIOSITY #undef SURGERY_SLOWDOWN_CAP_MULTIPLIER diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm index 49fbecc227169..fee7f389dcaef 100644 --- a/code/modules/surgery/tools.dm +++ b/code/modules/surgery/tools.dm @@ -7,7 +7,7 @@ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*3, /datum/material/glass =SHEET_MATERIAL_AMOUNT * 1.5) - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY item_flags = SURGICAL_TOOL w_class = WEIGHT_CLASS_TINY tool_behaviour = TOOL_RETRACTOR @@ -32,7 +32,7 @@ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' custom_materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*1.25) - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY item_flags = SURGICAL_TOOL w_class = WEIGHT_CLASS_TINY attack_verb_continuous = list("attacks", "pinches") @@ -59,7 +59,7 @@ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/glass = SMALL_MATERIAL_AMOUNT*7.5) - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY item_flags = SURGICAL_TOOL w_class = WEIGHT_CLASS_TINY attack_verb_continuous = list("burns") @@ -93,7 +93,7 @@ hitsound = 'sound/items/welder.ogg' w_class = WEIGHT_CLASS_NORMAL toolspeed = 0.7 - light_system = MOVABLE_LIGHT + light_system = OVERLAY_LIGHT light_range = 1 light_color = COLOR_SOFT_RED @@ -139,7 +139,7 @@ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' hitsound = 'sound/weapons/circsawhit.ogg' custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*3) - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY item_flags = SURGICAL_TOOL force = 15 demolition_mod = 0.5 @@ -182,7 +182,7 @@ inhand_icon_state = "scalpel" lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY item_flags = SURGICAL_TOOL force = 10 demolition_mod = 0.25 @@ -232,7 +232,7 @@ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' hitsound = 'sound/weapons/circsawhit.ogg' mob_throw_hit_sound = 'sound/weapons/pierce.ogg' - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY item_flags = SURGICAL_TOOL force = 15 w_class = WEIGHT_CLASS_NORMAL @@ -259,6 +259,12 @@ butcher_sound = 'sound/weapons/circsawhit.ogg', \ ) //saws are very accurate and fast at butchering + var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/chainsaw) + + AddComponent( + /datum/component/slapcrafting,\ + slapcraft_recipes = slapcraft_recipe_list,\ + ) /obj/item/circular_saw/get_surgery_tool_overlay(tray_extended) return surgical_tray_overlay @@ -288,7 +294,7 @@ /obj/item/surgical_processor //allows medical cyborgs to scan and initiate advanced surgeries name = "surgical processor" desc = "A device for scanning and initiating surgeries from a disk or operating computer." - icon = 'icons/obj/device.dmi' + icon = 'icons/obj/devices/scanner.dmi' icon_state = "surgical_processor" item_flags = NOBLUDGEON // List of surgeries downloaded into the device. @@ -372,7 +378,7 @@ force = 16 w_class = WEIGHT_CLASS_NORMAL toolspeed = 0.7 - light_system = MOVABLE_LIGHT + light_system = OVERLAY_LIGHT light_range = 1 light_color = LIGHT_COLOR_BLUE sharpness = SHARP_EDGED @@ -467,7 +473,7 @@ desc = "A type of heavy duty surgical shears used for achieving a clean separation between limb and patient. Keeping the patient still is imperative to be able to secure and align the shears." icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "shears" - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY item_flags = SURGICAL_TOOL toolspeed = 1 force = 12 @@ -529,6 +535,8 @@ else limb_snip_candidate.dismember() user.visible_message(span_danger("[src] violently slams shut, amputating [patient]'s [candidate_name]."), span_notice("You amputate [patient]'s [candidate_name] with [src].")) + user.log_message("[user] has amputated [patient]'s [candidate_name] with [src]", LOG_GAME) + patient.log_message("[patient]'s [candidate_name] has been amputated by [user] with [src]", LOG_GAME) if(HAS_MIND_TRAIT(user, TRAIT_MORBID)) //Freak user.add_mood_event("morbid_dismemberment", /datum/mood_event/morbid_dismemberment) @@ -553,7 +561,7 @@ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' custom_materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/silver = SHEET_MATERIAL_AMOUNT*1.25) - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY item_flags = SURGICAL_TOOL w_class = WEIGHT_CLASS_SMALL attack_verb_continuous = list("corrects", "properly sets") @@ -591,33 +599,31 @@ ui.open() /obj/item/blood_filter/ui_data(mob/user) - var/list/data = list() - var/list/chem_names = list() + . = list() + + .["whitelist"] = list() for(var/key in whitelist) - chem_names += whitelist[key] - data["whitelist"] = chem_names - return data + .["whitelist"] += whitelist[key] /obj/item/blood_filter/ui_act(action, params) . = ..() if(.) return + . = TRUE switch(action) if("add") - var/selected_reagent = tgui_input_list(usr, "Select reagent to filter", "Whitelist reagent", GLOB.chemical_name_list) + var/selected_reagent = tgui_input_list(usr, "Select reagent to filter", "Whitelist reagent", GLOB.name2reagent) if(!selected_reagent) - return TRUE + return FALSE - var/chem_id = get_chem_id(selected_reagent) + var/datum/reagent/chem_id = GLOB.name2reagent[selected_reagent] if(!chem_id) - return TRUE + return FALSE if(!(chem_id in whitelist)) whitelist[chem_id] = selected_reagent - - if("remove") var/chem_name = params["reagent"] var/chem_id = get_chem_id(chem_name) diff --git a/code/modules/tgchat/README.md b/code/modules/tgchat/README.md new file mode 100644 index 0000000000000..71acb47c458ae --- /dev/null +++ b/code/modules/tgchat/README.md @@ -0,0 +1,30 @@ +## /TG/ Chat + +/TG/ Chat, which will be referred to as TgChat from this point onwards, is a system in which we can send messages to clients in a controlled and semi-reliable manner. The standard way of sending messages to BYOND clients simply dumps whatever you output to them directly into their chat window, however BYOND allows us to load our own code on the client to change this behaviour in a way that allows us to do some pretty neat things. + +### Message Format + +TgChat handles sending messages from the server to the client through the use of JSON payloads, of which the format will change depending on the type of message and the intended client endpoint. An example of the payload for chat messages is as follows: +```json +{ + "sequence": 0, + "content": { + "type": ". . .", // ?optional + "text": ". . .", // ?optional !atleast-one + "html": ". . .", // ?optional !atleast-one + "avoidHighlighting": 0 // ?optional + }, +} +``` + +### Reliability + +In the past there have been issues where BYOND will silently and without reason lose a message we sent to the client, to detect this and recover from it seamlessly TgChat also has a baked in reliability layer. This reliability layer is very primitive, and simply keeps track of received sequence numbers. Should the client receive an unexpected sequence number TgChat asks the server to resend any missing packets. + +### Ping System + +TgChat supports a round trip time ping measurement, which is displayed to the client so they can know how long it takes for their commands and inputs to reach the server. This is done by sending the client a ping request, `ping/soft`, which tells the client to send a ping to the server. When the server receives said ping it sends a reply, `ping/reply`, to the client with a payload containing the current DateTime which the client can reference against the initial ping request. + +### Chat Tabs, Local Storage, and Highlighting + +To make organizing and managing chat easier and more functional for both players and admins, TgChat has the ability to filter out messages based on their primary tag, such as individual departmental radios, to a dedicated chat tab for easier reading and comprehension. These tabs can also be configured to highlist messages based on a simple keyword search. You can set a multitude of different keywords to search for and they will be highlighting for instant alerting of the client. Said tabs, highlighting rules, and your chat history will persist thanks to use of local storage on the client. Using local storage TgChat can ensure that your preferences are saved and maintained between client restarts and switching between other /TG/ servers. Local Storage is also used to keep your chat history aswell, should you need to scroll through your chat logs. diff --git a/code/modules/tgchat/to_chat.dm b/code/modules/tgchat/to_chat.dm index e71e865884513..2b1e48cb6aea9 100644 --- a/code/modules/tgchat/to_chat.dm +++ b/code/modules/tgchat/to_chat.dm @@ -35,23 +35,9 @@ if(text) message["text"] = text if(html) message["html"] = html if(avoid_highlighting) message["avoidHighlighting"] = avoid_highlighting - var/message_blob = TGUI_CREATE_MESSAGE("chat/message", message) - var/message_html = message_to_html(message) - if(islist(target)) - for(var/_target in target) - var/client/client = CLIENT_FROM_VAR(_target) - if(client) - // Send to tgchat - client.tgui_panel?.window.send_raw_message(message_blob) - // Send to old chat - SEND_TEXT(client, message_html) - return - var/client/client = CLIENT_FROM_VAR(target) - if(client) - // Send to tgchat - client.tgui_panel?.window.send_raw_message(message_blob) - // Send to old chat - SEND_TEXT(client, message_html) + + // send it immediately + SSchat.send_immediate(target, message) /** * Sends the message to the recipient (target). diff --git a/code/modules/tgs/core/core.dm b/code/modules/tgs/core/core.dm index 41a0473394525..8be96f27404a4 100644 --- a/code/modules/tgs/core/core.dm +++ b/code/modules/tgs/core/core.dm @@ -42,11 +42,11 @@ var/datum/tgs_version/max_api_version = TgsMaximumApiVersion(); if(version.suite != null && version.minor != null && version.patch != null && version.deprecated_patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter) - TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.") + TGS_ERROR_LOG("Detected unknown Interop API version! Defaulting to latest. Update the DMAPI to fix this problem.") api_datum = /datum/tgs_api/latest if(!api_datum) - TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.") + TGS_ERROR_LOG("Found unsupported Interop API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.") return TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]") @@ -107,6 +107,13 @@ if(api) return api.ApiVersion() +/world/TgsEngine() +#ifdef OPENDREAM + return TGS_ENGINE_TYPE_OPENDREAM +#else + return TGS_ENGINE_TYPE_BYOND +#endif + /world/TgsInstanceName() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) @@ -153,4 +160,9 @@ /world/TgsSecurityLevel() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) - api.SecurityLevel() + return api.SecurityLevel() + +/world/TgsVisibility() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + return api.Visibility() diff --git a/code/modules/tgs/core/datum.dm b/code/modules/tgs/core/datum.dm index 68b0330fe8606..07ce3b684584e 100644 --- a/code/modules/tgs/core/datum.dm +++ b/code/modules/tgs/core/datum.dm @@ -11,6 +11,15 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null) src.event_handler = event_handler src.version = version +/datum/tgs_api/proc/TerminateWorld() + while(TRUE) + TGS_DEBUG_LOG("About to terminate world. Tick: [world.time], sleep_offline: [world.sleep_offline]") + world.sleep_offline = FALSE // https://www.byond.com/forum/post/2894866 + del(world) + world.sleep_offline = FALSE // just in case, this is BYOND after all... + sleep(1) + TGS_DEBUG_LOG("BYOND DIDN'T TERMINATE THE WORLD!!! TICK IS: [world.time], sleep_offline: [world.sleep_offline]") + /datum/tgs_api/latest parent_type = /datum/tgs_api/v5 @@ -57,3 +66,6 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/SecurityLevel() return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/Visibility() + return TGS_UNIMPLEMENTED diff --git a/code/modules/tgs/v4/api.dm b/code/modules/tgs/v4/api.dm index b9a75c4abb489..945e2e4117671 100644 --- a/code/modules/tgs/v4/api.dm +++ b/code/modules/tgs/v4/api.dm @@ -73,7 +73,7 @@ if(cached_json["apiValidateOnly"]) TGS_INFO_LOG("Validating API and exiting...") Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = "[minimum_required_security_level]")) - del(world) + TerminateWorld() security_level = cached_json["securityLevel"] chat_channels_json_path = cached_json["chatChannelsJson"] @@ -188,7 +188,7 @@ requesting_new_port = TRUE if(!world.OpenPort(0)) //open any port TGS_ERROR_LOG("Unable to open random port to retrieve new port![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) + TerminateWorld() //request a new port export_lock = FALSE @@ -196,16 +196,16 @@ if(!new_port_json) TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) + TerminateWorld() var/new_port = new_port_json[TGS4_PARAMETER_DATA] if(!isnum(new_port) || new_port <= 0) TGS_ERROR_LOG("Malformed new port json ([json_encode(new_port_json)])![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) + TerminateWorld() if(new_port != world.port && !world.OpenPort(new_port)) TGS_ERROR_LOG("Unable to open port [new_port]![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) + TerminateWorld() requesting_new_port = FALSE while(export_lock) diff --git a/code/modules/tgs/v5/__interop_version.dm b/code/modules/tgs/v5/__interop_version.dm index 5d3d491a7362b..616263098fd3e 100644 --- a/code/modules/tgs/v5/__interop_version.dm +++ b/code/modules/tgs/v5/__interop_version.dm @@ -1 +1 @@ -"5.6.1" +"5.8.0" diff --git a/code/modules/tgs/v5/_defines.dm b/code/modules/tgs/v5/_defines.dm index f973338daa032..1c7d67d20cdf6 100644 --- a/code/modules/tgs/v5/_defines.dm +++ b/code/modules/tgs/v5/_defines.dm @@ -8,7 +8,6 @@ #define DMAPI5_TOPIC_REQUEST_LIMIT 65528 #define DMAPI5_TOPIC_RESPONSE_LIMIT 65529 -#define DMAPI5_BRIDGE_COMMAND_PORT_UPDATE 0 #define DMAPI5_BRIDGE_COMMAND_STARTUP 1 #define DMAPI5_BRIDGE_COMMAND_PRIME 2 #define DMAPI5_BRIDGE_COMMAND_REBOOT 3 @@ -18,6 +17,7 @@ #define DMAPI5_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier" #define DMAPI5_PARAMETER_CUSTOM_COMMANDS "customCommands" +#define DMAPI5_PARAMETER_TOPIC_PORT "topicPort" #define DMAPI5_CHUNK "chunk" #define DMAPI5_CHUNK_PAYLOAD "payload" @@ -48,6 +48,7 @@ #define DMAPI5_RUNTIME_INFORMATION_REVISION "revision" #define DMAPI5_RUNTIME_INFORMATION_TEST_MERGES "testMerges" #define DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL "securityLevel" +#define DMAPI5_RUNTIME_INFORMATION_VISIBILITY "visibility" #define DMAPI5_CHAT_UPDATE_CHANNELS "channels" @@ -79,6 +80,7 @@ #define DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH 8 #define DMAPI5_TOPIC_COMMAND_SEND_CHUNK 9 #define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10 +#define DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST 11 #define DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE "commandType" #define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand" @@ -88,6 +90,7 @@ #define DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME "newInstanceName" #define DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE "chatUpdate" #define DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION "newServerVersion" +#define DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE "broadcastMessage" #define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE "commandResponse" #define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE "commandResponseMessage" diff --git a/code/modules/tgs/v5/api.dm b/code/modules/tgs/v5/api.dm index 34cc43f8762f7..a5c064a8eaf1e 100644 --- a/code/modules/tgs/v5/api.dm +++ b/code/modules/tgs/v5/api.dm @@ -4,11 +4,16 @@ var/instance_name var/security_level + var/visibility var/reboot_mode = TGS_REBOOT_MODE_NORMAL + /// List of chat messages list()s that attempted to be sent during a topic call. To be bundled in the result of the call var/list/intercepted_message_queue + /// List of chat messages list()s that attempted to be sent during a topic call. To be bundled in the result of the call + var/list/offline_message_queue + var/list/custom_commands var/list/test_merges @@ -16,6 +21,8 @@ var/list/chat_channels var/initialized = FALSE + var/initial_bridge_request_received = FALSE + var/datum/tgs_version/interop_version var/chunked_requests = 0 var/list/chunked_topics = list() @@ -24,7 +31,8 @@ /datum/tgs_api/v5/New() . = ..() - TGS_DEBUG_LOG("V5 API created") + interop_version = version + TGS_DEBUG_LOG("V5 API created: [json_encode(args)]") /datum/tgs_api/v5/ApiVersion() return new /datum/tgs_version( @@ -37,8 +45,8 @@ access_identifier = world.params[DMAPI5_PARAM_ACCESS_IDENTIFIER] var/datum/tgs_version/api_version = ApiVersion() - version = null - var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands())) + version = null // we want this to be the TGS version, not the interop version + var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands(), DMAPI5_PARAMETER_TOPIC_PORT = GetTopicPort())) if(!istype(bridge_response)) TGS_ERROR_LOG("Failed initial bridge request!") return FALSE @@ -50,10 +58,12 @@ if(runtime_information[DMAPI5_RUNTIME_INFORMATION_API_VALIDATE_ONLY]) TGS_INFO_LOG("DMAPI validation, exiting...") - del(world) + TerminateWorld() - version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION]) + initial_bridge_request_received = TRUE + version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION]) // reassigning this because it can change if TGS updates security_level = runtime_information[DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL] + visibility = runtime_information[DMAPI5_RUNTIME_INFORMATION_VISIBILITY] instance_name = runtime_information[DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME] var/list/revisionData = runtime_information[DMAPI5_RUNTIME_INFORMATION_REVISION] @@ -100,10 +110,17 @@ initialized = TRUE return TRUE +/datum/tgs_api/v5/proc/GetTopicPort() +#if defined(OPENDREAM) && defined(OPENDREAM_TOPIC_PORT_EXISTS) + return "[world.opendream_topic_port]" +#else + return null +#endif + /datum/tgs_api/v5/proc/RequireInitialBridgeResponse() TGS_DEBUG_LOG("RequireInitialBridgeResponse()") var/logged = FALSE - while(!version) + while(!initial_bridge_request_received) if(!logged) TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep") logged = TRUE @@ -181,17 +198,7 @@ var/datum/tgs_chat_channel/channel = I ids += channel.id - message2 = UpgradeDeprecatedChatMessage(message2) - - if (!length(channels)) - return - - var/list/data = message2._interop_serialize() - data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = ids - if(intercepted_message_queue) - intercepted_message_queue += list(data) - else - Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data)) + SendChatMessageRaw(message2, ids) /datum/tgs_api/v5/ChatTargetedBroadcast(datum/tgs_message_content/message2, admin_only) var/list/channels = list() @@ -200,26 +207,42 @@ if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only))) channels += channel.id + SendChatMessageRaw(message2, channels) + +/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message2, datum/tgs_chat_user/user) + SendChatMessageRaw(message2, list(user.channel.id)) + +/datum/tgs_api/v5/proc/SendChatMessageRaw(datum/tgs_message_content/message2, list/channel_ids) message2 = UpgradeDeprecatedChatMessage(message2) - if (!length(channels)) + if (!length(channel_ids)) return var/list/data = message2._interop_serialize() - data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channels + data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channel_ids if(intercepted_message_queue) intercepted_message_queue += list(data) - else - Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data)) + return -/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message2, datum/tgs_chat_user/user) - message2 = UpgradeDeprecatedChatMessage(message2) - var/list/data = message2._interop_serialize() - data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = list(user.channel.id) - if(intercepted_message_queue) - intercepted_message_queue += list(data) + if(offline_message_queue) + offline_message_queue += list(data) + return + + if(detached) + offline_message_queue = list(data) + + WaitForReattach(FALSE) + + data = offline_message_queue + offline_message_queue = null + + for(var/queued_message in data) + SendChatDataRaw(queued_message) else - Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data)) + SendChatDataRaw(data) + +/datum/tgs_api/v5/proc/SendChatDataRaw(list/data) + Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data)) /datum/tgs_api/v5/ChatChannelInfo() RequireInitialBridgeResponse() @@ -252,3 +275,7 @@ /datum/tgs_api/v5/SecurityLevel() RequireInitialBridgeResponse() return security_level + +/datum/tgs_api/v5/Visibility() + RequireInitialBridgeResponse() + return visibility diff --git a/code/modules/tgs/v5/bridge.dm b/code/modules/tgs/v5/bridge.dm index 37f58bcdf632e..a0ab359876704 100644 --- a/code/modules/tgs/v5/bridge.dm +++ b/code/modules/tgs/v5/bridge.dm @@ -48,7 +48,9 @@ var/json = CreateBridgeData(command, data, TRUE) var/encoded_json = url_encode(json) - var/url = "http://127.0.0.1:[server_port]/Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]" + var/api_prefix = interop_version.minor >= 8 ? "api/" : "" + + var/url = "http://127.0.0.1:[server_port]/[api_prefix]Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]" return url /datum/tgs_api/v5/proc/CreateBridgeData(command, list/data, needs_auth) @@ -81,11 +83,16 @@ TGS_ERROR_LOG("Failed bridge request: [bridge_request]") return - var/response_json = file2text(export_response["CONTENT"]) - if(!response_json) + var/content = export_response["CONTENT"] + if(!content) TGS_ERROR_LOG("Failed bridge request, missing content!") return + var/response_json = file2text(content) + if(!response_json) + TGS_ERROR_LOG("Failed bridge request, failed to load content!") + return + var/list/bridge_response = json_decode(response_json) if(!bridge_response) TGS_ERROR_LOG("Failed bridge request, bad json: [response_json]") diff --git a/code/modules/tgs/v5/topic.dm b/code/modules/tgs/v5/topic.dm index d7d4712138135..05e6c4e1b2146 100644 --- a/code/modules/tgs/v5/topic.dm +++ b/code/modules/tgs/v5/topic.dm @@ -94,7 +94,7 @@ if(DMAPI5_TOPIC_COMMAND_CHANGE_PORT) var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT] if (!isnum(new_port) || !(new_port > 0)) - return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]") + return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]") if(event_handler != null) event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) @@ -141,7 +141,7 @@ if(DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE) var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT] if (!isnum(new_port) || !(new_port > 0)) - return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]") + return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]") server_port = new_port return TopicResponse() @@ -157,7 +157,7 @@ var/error_message = null if (new_port != null) if (!isnum(new_port) || !(new_port > 0)) - error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]" + error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]" else server_port = new_port @@ -165,7 +165,7 @@ if (!istext(new_version_string)) if(error_message != null) error_message += ", " - error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]]" + error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]" else var/datum/tgs_version/new_version = new(new_version_string) if (event_handler) @@ -175,6 +175,7 @@ var/list/reattach_response = TopicResponse(error_message) reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands() + reattach_response[DMAPI5_PARAMETER_TOPIC_PORT] = GetTopicPort() return reattach_response if(DMAPI5_TOPIC_COMMAND_SEND_CHUNK) @@ -267,4 +268,16 @@ return chunk_to_send + if(DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST) + var/message = topic_parameters[DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE] + if (!istext(message)) + return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE]") + + TGS_WORLD_ANNOUNCE(message) + return TopicResponse() + return TopicResponse("Unknown command: [command]") + +/datum/tgs_api/v5/proc/WorldBroadcast(message) + set waitfor = FALSE + TGS_WORLD_ANNOUNCE(message) diff --git a/code/modules/tgs/v5/undefs.dm b/code/modules/tgs/v5/undefs.dm index c679737dfc496..d531d4b7b9dd1 100644 --- a/code/modules/tgs/v5/undefs.dm +++ b/code/modules/tgs/v5/undefs.dm @@ -8,7 +8,6 @@ #undef DMAPI5_TOPIC_REQUEST_LIMIT #undef DMAPI5_TOPIC_RESPONSE_LIMIT -#undef DMAPI5_BRIDGE_COMMAND_PORT_UPDATE #undef DMAPI5_BRIDGE_COMMAND_STARTUP #undef DMAPI5_BRIDGE_COMMAND_PRIME #undef DMAPI5_BRIDGE_COMMAND_REBOOT @@ -18,6 +17,7 @@ #undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER #undef DMAPI5_PARAMETER_CUSTOM_COMMANDS +#undef DMAPI5_PARAMETER_TOPIC_PORT #undef DMAPI5_CHUNK #undef DMAPI5_CHUNK_PAYLOAD @@ -48,6 +48,7 @@ #undef DMAPI5_RUNTIME_INFORMATION_REVISION #undef DMAPI5_RUNTIME_INFORMATION_TEST_MERGES #undef DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL +#undef DMAPI5_RUNTIME_INFORMATION_VISIBILITY #undef DMAPI5_CHAT_UPDATE_CHANNELS @@ -77,6 +78,9 @@ #undef DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE #undef DMAPI5_TOPIC_COMMAND_HEALTHCHECK #undef DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH +#undef DMAPI5_TOPIC_COMMAND_SEND_CHUNK +#undef DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK +#undef DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST #undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE #undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND @@ -86,6 +90,7 @@ #undef DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME #undef DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE #undef DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION +#undef DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE #undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE #undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm index bcd97a9124516..1168b6c619a19 100644 --- a/code/modules/tgui/external.dm +++ b/code/modules/tgui/external.dm @@ -86,7 +86,7 @@ */ /datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_UI_ACT, usr, action) + SEND_SIGNAL(src, COMSIG_UI_ACT, usr, action, params) // If UI is not interactive or usr calling Topic is not the UI user, bail. if(!ui || ui.status != UI_INTERACTIVE) return TRUE diff --git a/code/modules/tgui/states/greyscale_menu.dm b/code/modules/tgui/states/greyscale_menu.dm index 9de6140e70929..b08c0b8c98069 100644 --- a/code/modules/tgui/states/greyscale_menu.dm +++ b/code/modules/tgui/states/greyscale_menu.dm @@ -9,6 +9,6 @@ GLOBAL_DATUM_INIT(greyscale_menu_state, /datum/ui_state/greyscale_menu_state, ne /datum/ui_state/greyscale_menu_state/can_use_topic(src_object, mob/user) var/datum/greyscale_modify_menu/menu = src_object if(!isatom(menu.target)) - return TRUE + return UI_INTERACTIVE return GLOB.default_state.can_use_topic(menu.target, user) diff --git a/code/modules/tgui/states/notcontained.dm b/code/modules/tgui/states/notcontained.dm index 018e0fa0304c3..b144a375757d0 100644 --- a/code/modules/tgui/states/notcontained.dm +++ b/code/modules/tgui/states/notcontained.dm @@ -28,5 +28,5 @@ GLOBAL_DATUM_INIT(notcontained_state, /datum/ui_state/notcontained_state, new) /mob/living/silicon/notcontained_can_use_topic(src_object) return default_can_use_topic(src_object) // Silicons use default bevhavior. -/mob/living/simple_animal/drone/notcontained_can_use_topic(src_object) +/mob/living/basic/drone/notcontained_can_use_topic(src_object) return default_can_use_topic(src_object) // Drones use default bevhavior. diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index 0f831dfd92654..82d8d794d9afc 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -384,6 +384,8 @@ client << link(href_list["url"]) if("cacheReloaded") reinitialize() + if("chat/resend") + SSchat.handle_resend(client, payload) /datum/tgui_window/vv_edit_var(var_name, var_value) return var_name != NAMEOF(src, id) && ..() diff --git a/code/modules/tgui_input/alert.dm b/code/modules/tgui_input/alert.dm index 4dc197a3c8cdd..4749ef278725e 100644 --- a/code/modules/tgui_input/alert.dm +++ b/code/modules/tgui_input/alert.dm @@ -18,7 +18,11 @@ var/client/client = user user = client.mob else - return + return null + + if(isnull(user.client)) + return null + // A gentle nudge - you should not be using TGUI alert for anything other than a simple message. if(length(buttons) > 3) log_tgui(user, "Error: TGUI Alert initiated with too many buttons. Use a list.", "TguiAlert") @@ -73,7 +77,7 @@ start_time = world.time QDEL_IN(src, timeout) -/datum/tgui_alert/Destroy(force, ...) +/datum/tgui_alert/Destroy(force) SStgui.close_uis(src) state = null QDEL_NULL(buttons) diff --git a/code/modules/tgui_input/checkboxes.dm b/code/modules/tgui_input/checkboxes.dm index ec43bd8914d5a..53b264038dc20 100644 --- a/code/modules/tgui_input/checkboxes.dm +++ b/code/modules/tgui_input/checkboxes.dm @@ -14,13 +14,17 @@ if (!user) user = usr if(!length(items)) - return + return null if (!istype(user)) if (istype(user, /client)) var/client/client = user user = client.mob else - return + return null + + if(isnull(user.client)) + return null + if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input)) return input(user, message, title) as null|anything in items var/datum/tgui_checkbox_input/input = new(user, message, title, items, min_checked, max_checked, timeout, ui_state) @@ -66,7 +70,7 @@ start_time = world.time QDEL_IN(src, timeout) -/datum/tgui_checkbox_input/Destroy(force, ...) +/datum/tgui_checkbox_input/Destroy(force) SStgui.close_uis(src) state = null QDEL_NULL(items) diff --git a/code/modules/tgui_input/list.dm b/code/modules/tgui_input/list.dm index 95daaadb32649..174f16fc7b57c 100644 --- a/code/modules/tgui_input/list.dm +++ b/code/modules/tgui_input/list.dm @@ -14,17 +14,24 @@ if (!user) user = usr if(!length(items)) - return + return null if (!istype(user)) if (istype(user, /client)) var/client/client = user user = client.mob else - return + return null + + if(isnull(user.client)) + return null + /// Client does NOT have tgui_input on: Returns regular input if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input)) return input(user, message, title, default) as null|anything in items var/datum/tgui_list_input/input = new(user, message, title, items, default, timeout, ui_state) + if(input.invalid) + qdel(input) + return input.ui_interact(user) input.wait() if (input) @@ -58,6 +65,8 @@ var/closed /// The TGUI UI state that will be returned in ui_state(). Default: always_state var/datum/ui_state/state + /// Whether the tgui list input is invalid or not (i.e. due to all list entries being null) + var/invalid = FALSE /datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout, ui_state) src.title = title @@ -77,12 +86,15 @@ string_key = avoid_assoc_duplicate_keys(string_key, repeat_items) src.items += string_key src.items_map[string_key] = i + + if(length(src.items) == 0) + invalid = TRUE if (timeout) src.timeout = timeout start_time = world.time QDEL_IN(src, timeout) -/datum/tgui_list_input/Destroy(force, ...) +/datum/tgui_list_input/Destroy(force) SStgui.close_uis(src) state = null QDEL_NULL(items) diff --git a/code/modules/tgui_input/number.dm b/code/modules/tgui_input/number.dm index bcdf495fd82e8..68998acb0331f 100644 --- a/code/modules/tgui_input/number.dm +++ b/code/modules/tgui_input/number.dm @@ -23,7 +23,11 @@ var/client/client = user user = client.mob else - return + return null + + if (isnull(user.client)) + return null + // Client does NOT have tgui_input on: Returns regular input if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input)) var/input_number = input(user, message, title, default) as null|num @@ -88,7 +92,7 @@ if(default > max_value) CRASH("Default value is greater than max value.") -/datum/tgui_input_number/Destroy(force, ...) +/datum/tgui_input_number/Destroy(force) SStgui.close_uis(src) state = null return ..() diff --git a/code/modules/tgui_input/say_modal/modal.dm b/code/modules/tgui_input/say_modal/modal.dm index 8afba448762aa..bc3f8f314e021 100644 --- a/code/modules/tgui_input/say_modal/modal.dm +++ b/code/modules/tgui_input/say_modal/modal.dm @@ -36,6 +36,7 @@ /datum/tgui_say/New(client/client, id) src.client = client window = new(client, id) + winset(client, "tgui_say", "size=1,1;is-visible=0;") window.subscribe(src, PROC_REF(on_message)) window.is_browser = TRUE @@ -62,11 +63,14 @@ */ /datum/tgui_say/proc/load() window_open = FALSE - winshow(client, "tgui_say", FALSE) + + winset(client, "tgui_say", "pos=848,500;size=231,30;is-visible=0;") + window.send_message("props", list( lightMode = client.prefs?.read_preference(/datum/preference/toggle/tgui_say_light_mode), maxLength = max_length, )) + stop_thinking() return TRUE @@ -84,9 +88,7 @@ window_open = TRUE if(payload["channel"] != OOC_CHANNEL && payload["channel"] != ADMIN_CHANNEL) start_thinking() - if(client.typing_indicators) - log_speech_indicators("[key_name(client)] started typing at [loc_name(client.mob)], indicators enabled.") - else + if(!client.typing_indicators) log_speech_indicators("[key_name(client)] started typing at [loc_name(client.mob)], indicators DISABLED.") return TRUE @@ -97,9 +99,7 @@ /datum/tgui_say/proc/close() window_open = FALSE stop_thinking() - if(client.typing_indicators) - log_speech_indicators("[key_name(client)] stopped typing at [loc_name(client.mob)], indicators enabled.") - else + if(!client.typing_indicators) log_speech_indicators("[key_name(client)] stopped typing at [loc_name(client.mob)], indicators DISABLED.") /** diff --git a/code/modules/tgui_input/say_modal/typing.dm b/code/modules/tgui_input/say_modal/typing.dm index 49357fb9b5f85..a6367c088d694 100644 --- a/code/modules/tgui_input/say_modal/typing.dm +++ b/code/modules/tgui_input/say_modal/typing.dm @@ -36,14 +36,14 @@ /datum/preference/toggle/typing_indicator/apply_to_client(client/client, value) client?.typing_indicators = value -/** Sets the mob as "thinking" - with indicator and variable thinking_IC */ +/** Sets the mob as "thinking" - with indicator and the TRAIT_THINKING_IN_CHARACTER trait */ /datum/tgui_say/proc/start_thinking() if(!window_open || !client.typing_indicators) return FALSE /// Special exemptions if(isabductor(client.mob)) return FALSE - client.mob.thinking_IC = TRUE + ADD_TRAIT(client.mob, TRAIT_THINKING_IN_CHARACTER, CURRENTLY_TYPING_TRAIT) client.mob.create_thinking_indicator() /** Removes typing/thinking indicators and flags the mob as not thinking */ @@ -55,10 +55,11 @@ * signals the client mob to revert to the "thinking" icon. */ /datum/tgui_say/proc/start_typing() - client.mob.remove_thinking_indicator() - if(!window_open || !client.typing_indicators || !client.mob.thinking_IC) + var/mob/client_mob = client.mob + client_mob.remove_thinking_indicator() + if(!window_open || !client.typing_indicators || !HAS_TRAIT(client_mob, TRAIT_THINKING_IN_CHARACTER)) return FALSE - client.mob.create_typing_indicator() + client_mob.create_typing_indicator() addtimer(CALLBACK(src, PROC_REF(stop_typing)), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_STOPPABLE) /** @@ -66,16 +67,17 @@ * If the user was typing IC, the thinking indicator is shown. */ /datum/tgui_say/proc/stop_typing() - if(!client?.mob) + if(isnull(client?.mob)) return FALSE - client.mob.remove_typing_indicator() - if(!window_open || !client.typing_indicators || !client.mob.thinking_IC) + var/mob/client_mob = client.mob + client_mob.remove_typing_indicator() + if(!window_open || !client.typing_indicators || !HAS_TRAIT(client_mob, TRAIT_THINKING_IN_CHARACTER)) return FALSE - client.mob.create_thinking_indicator() + client_mob.create_thinking_indicator() /// Overrides for overlay creation /mob/living/create_thinking_indicator() - if(active_thinking_indicator || active_typing_indicator || !thinking_IC || stat != CONSCIOUS ) + if(active_thinking_indicator || active_typing_indicator || stat != CONSCIOUS || !HAS_TRAIT(src, TRAIT_THINKING_IN_CHARACTER)) return FALSE active_thinking_indicator = mutable_appearance('icons/mob/effects/talk.dmi', "[bubble_icon]3", TYPING_LAYER) add_overlay(active_thinking_indicator) @@ -88,7 +90,7 @@ active_thinking_indicator = null /mob/living/create_typing_indicator() - if(active_typing_indicator || active_thinking_indicator || !thinking_IC || stat != CONSCIOUS) + if(active_typing_indicator || active_thinking_indicator || stat != CONSCIOUS || !HAS_TRAIT(src, TRAIT_THINKING_IN_CHARACTER)) return FALSE active_typing_indicator = mutable_appearance('icons/mob/effects/talk.dmi', "[bubble_icon]0", TYPING_LAYER) add_overlay(active_typing_indicator) @@ -101,7 +103,7 @@ active_typing_indicator = null /mob/living/remove_all_indicators() - thinking_IC = FALSE + REMOVE_TRAIT(src, TRAIT_THINKING_IN_CHARACTER, CURRENTLY_TYPING_TRAIT) remove_thinking_indicator() remove_typing_indicator() diff --git a/code/modules/tgui_input/text.dm b/code/modules/tgui_input/text.dm index 811673a4c03aa..f97e0326d58ef 100644 --- a/code/modules/tgui_input/text.dm +++ b/code/modules/tgui_input/text.dm @@ -23,7 +23,11 @@ var/client/client = user user = client.mob else - return + return null + + if(isnull(user.client)) + return null + // Client does NOT have tgui_input on: Returns regular input if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input)) if(encode) @@ -86,7 +90,7 @@ start_time = world.time QDEL_IN(src, timeout) -/datum/tgui_input_text/Destroy(force, ...) +/datum/tgui_input_text/Destroy(force) SStgui.close_uis(src) state = null return ..() diff --git a/code/modules/tooltip/tooltip.dm b/code/modules/tooltip/tooltip.dm index 49539920920c8..22027a3cf8e13 100644 --- a/code/modules/tooltip/tooltip.dm +++ b/code/modules/tooltip/tooltip.dm @@ -100,29 +100,29 @@ Notes: /datum/tooltip/proc/do_hide() winshow(owner, control, FALSE) -/datum/tooltip/Destroy(force, ...) +/datum/tooltip/Destroy(force) last_target = null return ..() //Open a tooltip for user, at a location based on params //Theme is a CSS class in tooltip.html, by default this wrapper chooses a CSS class based on the user's UI_style (Midnight, Plasmafire, Retro, etc) //Includes sanity.checks -/proc/openToolTip(mob/user = null, atom/movable/tip_src = null, params = null,title = "",content = "",theme = "") - if(istype(user)) - if(user.client && user.client.tooltips) - var/ui_style = user.client?.prefs?.read_preference(/datum/preference/choiced/ui_style) - if(!theme && ui_style) - theme = lowertext(ui_style) - if(!theme) - theme = "default" - user.client.tooltips.show(tip_src, params,title,content,theme) +/proc/openToolTip(mob/user = null, atom/movable/tip_src = null, params = null, title = "", content = "", theme = "") + if(!istype(user) || !user.client?.tooltips) + return + var/ui_style = user.client?.prefs?.read_preference(/datum/preference/choiced/ui_style) + if(!theme && ui_style) + theme = lowertext(ui_style) + if(!theme) + theme = "default" + user.client.tooltips.show(tip_src, params, title, content, theme) //Arbitrarily close a user's tooltip //Includes sanity checks. /proc/closeToolTip(mob/user) - if(istype(user)) - if(user.client && user.client.tooltips) - user.client.tooltips.hide() + if(!istype(user) || !user.client?.tooltips) + return + user.client.tooltips.hide() diff --git a/code/modules/transport/_transport_machinery.dm b/code/modules/transport/_transport_machinery.dm new file mode 100644 index 0000000000000..1cbbbdeb24b57 --- /dev/null +++ b/code/modules/transport/_transport_machinery.dm @@ -0,0 +1,199 @@ +/obj/machinery/transport + armor_type = /datum/armor/transport_machinery + max_integrity = 400 + integrity_failure = 0.1 + /// ID of the transport we're associated with for filtering commands + var/configured_transport_id = TRAMSTATION_LINE_1 + /// weakref of the transport we're associated with + var/datum/weakref/transport_ref + var/list/methods_to_fix = list() + var/list/repair_signals + var/static/list/how_do_we_fix_it = list( + "try turning it off and on again with a multitool" = TOOL_MULTITOOL, + "try forcing an unexpected reboot with a multitool" = TOOL_MULTITOOL, + "patch the system's call table with a multitool" = TOOL_MULTITOOL, + "gently reset the invalid memory with a crowbar" = TOOL_CROWBAR, + "secure its ground connection with a wrench" = TOOL_WRENCH, + "tighten some screws with a screwdriver" = TOOL_SCREWDRIVER, + "check its wire voltages with a multitool" = TOOL_MULTITOOL, + "cut some excess wires with wirecutters" = TOOL_WIRECUTTER, + ) + var/malfunctioning = FALSE + +/datum/armor/transport_machinery + melee = 40 + bullet = 10 + laser = 10 + bomb = 45 + fire = 90 + acid = 100 + +/obj/machinery/transport/Initialize(mapload) + . = ..() + if(!id_tag) + id_tag = assign_random_name() + +/obj/machinery/transport/add_context(atom/source, list/context, obj/item/held_item, mob/user) + if(held_item?.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_RMB] = panel_open ? "close panel" : "open panel" + + if(panel_open) + if(malfunctioning || methods_to_fix.len) + context[SCREENTIP_CONTEXT_LMB] = "repair electronics" + if(held_item?.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_RMB] = "deconstruct" + + if(held_item?.tool_behaviour == TOOL_WELDER) + context[SCREENTIP_CONTEXT_LMB] = "repair frame" + + return CONTEXTUAL_SCREENTIP_SET + +/** + * Finds the tram + * + * Locates tram parts in the lift global list after everything is done. + */ +/obj/machinery/transport/proc/link_tram() + for(var/datum/transport_controller/linear/tram/tram as anything in SStransport.transports_by_type[TRANSPORT_TYPE_TRAM]) + if(tram.specific_transport_id != configured_transport_id) + continue + transport_ref = WEAKREF(tram) + log_transport("[id_tag]: Successfuly linked to transport ID [tram.specific_transport_id] [transport_ref]") + break + + if(isnull(transport_ref)) + log_transport("[id_tag]: Tried to find a transport with ID [configured_transport_id], but failed!") + +/obj/machinery/transport/proc/local_fault() + if(malfunctioning || !isnull(repair_signals)) + return + + generate_repair_signals() + malfunctioning = TRUE + set_is_operational(FALSE) + update_appearance() + +/** + * All subtypes have the same method of repair for consistency and predictability + * The key of this assoc list is the "method" of how they're fixing the thing (just flavor for examine), + * and the value is what tool they actually need to use on the thing to fix it + */ +/obj/machinery/transport/proc/generate_repair_signals() + + // Select a few methods of how to fix it + var/list/fix_it_keys = assoc_to_keys(how_do_we_fix_it) + methods_to_fix += pick_n_take(fix_it_keys) + + // Construct the signals + LAZYINITLIST(repair_signals) + for(var/tool_method as anything in methods_to_fix) + repair_signals += COMSIG_ATOM_TOOL_ACT(how_do_we_fix_it[tool_method]) + + // Register signals to make it fixable + if(length(repair_signals)) + RegisterSignals(src, repair_signals, PROC_REF(on_machine_tooled)) + +/obj/machinery/transport/proc/clear_repair_signals() + UnregisterSignal(src, repair_signals) + QDEL_LAZYLIST(repair_signals) + +/obj/machinery/transport/examine(mob/user) + . = ..() + if(methods_to_fix) + for(var/tool_method as anything in methods_to_fix) + . += span_warning("It needs someone to [EXAMINE_HINT(tool_method)].") + if(panel_open) + . += span_notice("It can be deconstructed with a [EXAMINE_HINT("crowbar.")]") + +/** + * Signal proc for [COMSIG_ATOM_TOOL_ACT], from a variety of signals, registered on the machinery. + * + * We allow for someone to stop the event early by using the proper tools, hinted at in examine, on the machine + */ +/obj/machinery/transport/proc/on_machine_tooled(obj/machinery/source, mob/living/user, obj/item/tool) + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(try_fix_machine), source, user, tool) + return ITEM_INTERACT_BLOCKING + +/// Attempts a do_after, and if successful, stops the event +/obj/machinery/transport/proc/try_fix_machine(obj/machinery/transport/machine, mob/living/user, obj/item/tool) + SHOULD_CALL_PARENT(TRUE) + + machine.balloon_alert(user, "percussive maintenance...") + if(!tool.use_tool(machine, user, 7 SECONDS, volume = 50)) + machine.balloon_alert(user, "interrupted!") + return FALSE + + playsound(src, 'sound/machines/synth_yes.ogg', 75, use_reverb = TRUE) + machine.balloon_alert(user, "success!") + UnregisterSignal(src, repair_signals) + QDEL_LAZYLIST(repair_signals) + QDEL_LAZYLIST(methods_to_fix) + malfunctioning = FALSE + set_machine_stat(machine_stat & ~EMAGGED) + set_is_operational(TRUE) + update_appearance() + return TRUE + +/obj/machinery/transport/welder_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return + if(atom_integrity >= max_integrity) + balloon_alert(user, "it doesn't need repairs!") + return TRUE + balloon_alert(user, "repairing...") + if(!tool.use_tool(src, user, 4 SECONDS, amount = 0, volume=50)) + return TRUE + balloon_alert(user, "repaired") + atom_integrity = max_integrity + set_machine_stat(machine_stat & ~BROKEN) + update_appearance() + return TRUE + +/obj/item/wallframe/tram/try_build(obj/structure/tram/on_tram, mob/user) + if(get_dist(on_tram,user) > 1) + balloon_alert(user, "you are too far!") + return + + var/floor_to_tram = get_dir(user, on_tram) + if(!(floor_to_tram in GLOB.cardinals)) + balloon_alert(user, "stand in line with tram wall!") + return + + var/turf/tram_turf = get_turf(user) + var/obj/structure/thermoplastic/tram_floor = locate() in tram_turf + if(!istype(tram_floor)) + balloon_alert(user, "needs tram!") + return + + if(check_wall_item(tram_turf, floor_to_tram, wall_external)) + balloon_alert(user, "already something here!") + return + + return TRUE + +/obj/item/wallframe/tram/attach(obj/structure/tram/on_tram, mob/user) + if(result_path) + playsound(src.loc, 'sound/machines/click.ogg', 75, TRUE) + user.visible_message(span_notice("[user.name] installs [src] on the tram."), + span_notice("You install [src] on the tram."), + span_hear("You hear clicking.")) + var/floor_to_tram = get_dir(user, on_tram) + + var/obj/cabinet = new result_path(get_turf(user), floor_to_tram, TRUE) + cabinet.setDir(floor_to_tram) + + if(pixel_shift) + switch(floor_to_tram) + if(NORTH) + cabinet.pixel_y = pixel_shift + if(SOUTH) + cabinet.pixel_y = -pixel_shift + if(EAST) + cabinet.pixel_x = pixel_shift + if(WEST) + cabinet.pixel_x = -pixel_shift + after_attach(cabinet) + + qdel(src) diff --git a/code/modules/transport/admin.dm b/code/modules/transport/admin.dm new file mode 100644 index 0000000000000..9d68d967e60ae --- /dev/null +++ b/code/modules/transport/admin.dm @@ -0,0 +1,85 @@ +/** + * Helper tool to try and resolve tram controller errors, or reset the contents if someone put a million chickens on the tram + * and now it's slow as hell and lagging things. + */ +/datum/admins/proc/reset_tram() + set name = "Reset Tram" + set category = "Debug" + var/static/list/debug_tram_list = list( + TRAMSTATION_LINE_1, + BIRDSHOT_LINE_1, + BIRDSHOT_LINE_2, + HILBERT_LINE_1, + ) + + if(!check_rights(R_DEBUG)) + return + + var/datum/transport_controller/linear/tram/broken_controller + var/selected_transport_id = tgui_input_list(usr, "Which tram?", "Off the rails", debug_tram_list) + var/reset_type = tgui_input_list(usr, "How hard of a reset?", "How bad is it screwed up", list("Clear Tram Contents", "Controller", "Controller and Contents", "Delete Datum", "Cancel")) + + if(isnull(reset_type) || reset_type == "Cancel") + return + + for(var/datum/transport_controller/linear/tram/transport as anything in SStransport.transports_by_type[TRANSPORT_TYPE_TRAM]) + if(transport.specific_transport_id == selected_transport_id) + broken_controller = transport + break + + if(isnull(broken_controller)) + to_chat(usr, span_warning("Couldn't find a transport controller datum with ID [selected_transport_id]!")) + return + + switch(reset_type) + if("Clear Tram Contents") + var/selection = tgui_alert(usr, "Include player mobs in the clearing?", "Contents reset [selected_transport_id]", list("Contents", "Contents and Players", "Cancel")) + switch(selection) + if("Contents") + broken_controller.reset_lift_contents(foreign_objects = TRUE, foreign_non_player_mobs = TRUE, consider_player_mobs = FALSE) + message_admins("[key_name_admin(usr)] performed a contents reset of tram ID [selected_transport_id].") + log_transport("TC: [selected_transport_id]: [key_name_admin(usr)] performed a contents reset.") + if("Contents and Players") + broken_controller.reset_lift_contents(foreign_objects = TRUE, foreign_non_player_mobs = TRUE, consider_player_mobs = TRUE) + message_admins("[key_name_admin(usr)] performed a contents and player mob reset of tram ID [selected_transport_id].") + log_transport("TC: [selected_transport_id]: [key_name_admin(usr)] performed a contents and player mob reset.") + else + return + + if("Controller") + log_transport("TC: [selected_transport_id]: [key_name_admin(usr)] performed a controller reset, force operational.") + message_admins("[key_name_admin(usr)] performed a controller reset of tram ID [selected_transport_id].") + broken_controller.set_operational(TRUE) + broken_controller.reset_position() + + if("Controller and Contents") + var/selection = tgui_alert(usr, "Include player mobs in the clearing?", "Contents reset [selected_transport_id]", list("Contents", "Contents and Players", "Cancel")) + switch(selection) + if("Contents") + message_admins("[key_name_admin(usr)] performed a contents and controller reset of tram ID [selected_transport_id].") + log_transport("TC: [selected_transport_id]: [key_name_admin(usr)] performed a contents reset. Controller reset, force operational.") + broken_controller.reset_lift_contents(foreign_objects = TRUE, foreign_non_player_mobs = TRUE, consider_player_mobs = FALSE) + if("Contents and Players") + message_admins("[key_name_admin(usr)] performed a contents/player/controller reset of tram ID [selected_transport_id].") + log_transport("TC: [selected_transport_id]: [key_name_admin(usr)] performed a contents and player mob reset. Controller reset, force operational.") + broken_controller.reset_lift_contents(foreign_objects = TRUE, foreign_non_player_mobs = TRUE, consider_player_mobs = TRUE) + else + return + + broken_controller.set_operational(TRUE) + broken_controller.reset_position() + + if("Delete Datum") + var/confirm = tgui_alert(usr, "Deleting [selected_transport_id] will make it unrecoverable this round. Are you sure?", "Delete tram ID [selected_transport_id]", list("Yes", "Cancel")) + if(confirm != "Yes") + return + + var/obj/machinery/transport/tram_controller/tram_cabinet = broken_controller.paired_cabinet + if(!isnull(tram_cabinet)) + tram_cabinet.controller_datum = null + tram_cabinet.update_appearance() + + broken_controller.cycle_doors(CYCLE_OPEN, BYPASS_DOOR_CHECKS) + broken_controller.estop() + qdel(broken_controller) + message_admins("[key_name_admin(usr)] performed a datum delete of tram ID [selected_transport_id].") diff --git a/code/modules/transport/elevator/elev_controller.dm b/code/modules/transport/elevator/elev_controller.dm new file mode 100644 index 0000000000000..aae79cfe0f14b --- /dev/null +++ b/code/modules/transport/elevator/elev_controller.dm @@ -0,0 +1,180 @@ +/obj/machinery/button/elevator + name = "elevator button" + desc = "Go back. Go back. Go back. Can you operate the elevator." + base_icon_state = "tram" + icon_state = "tram" + can_alter_skin = FALSE + light_color = COLOR_DISPLAY_BLUE + device_type = /obj/item/assembly/control/elevator + req_access = list() + id = 1 + +/obj/machinery/button/elevator/Initialize(mapload, ndir, built) + . = ..() + // Kind of a cop-out + AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Call Elevator") + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/button/elevator, 32) + +/obj/item/assembly/control/elevator + name = "elevator controller" + desc = "A small device used to call elevators to the current floor." + /// A weakref to the transport_controller datum we control + var/datum/weakref/lift_weakref + COOLDOWN_DECLARE(elevator_cooldown) + +/obj/item/assembly/control/elevator/Initialize(mapload) + . = ..() + + if(mapload) + return INITIALIZE_HINT_LATELOAD + + var/datum/transport_controller/linear/lift = get_lift() + if(!lift) + return + + lift_weakref = WEAKREF(lift) + +/obj/item/assembly/control/elevator/LateInitialize() + var/datum/transport_controller/linear/lift = get_lift() + if(!lift) + log_mapping("Elevator call button at [AREACOORD(src)] found no associated elevator to link with, this may be a mapping error.") + return + + lift_weakref = WEAKREF(lift) + +// Emagging elevator buttons will disable safeties +/obj/item/assembly/control/elevator/emag_act(mob/user, obj/item/card/emag/emag_card) + if(obj_flags & EMAGGED) + return FALSE + + obj_flags |= EMAGGED + var/datum/transport_controller/linear/lift = lift_weakref?.resolve() + if(!lift) + return FALSE + + for(var/obj/structure/transport/linear/lift_platform as anything in lift.transport_modules) + lift_platform.violent_landing = TRUE + lift_platform.warns_on_down_movement = FALSE + lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed) * 0.5 + + for(var/obj/machinery/door/elevator_door as anything in GLOB.elevator_doors) + if(elevator_door.transport_linked_id != lift.specific_transport_id) + continue + if(elevator_door.obj_flags & EMAGGED) + continue + elevator_door.elevator_status = LIFT_PLATFORM_UNLOCKED + INVOKE_ASYNC(elevator_door, TYPE_PROC_REF(/obj/machinery/door, open), BYPASS_DOOR_CHECKS) + elevator_door.obj_flags |= EMAGGED + + // Note that we can either be emagged by having the button we are inside swiped, + // or by someone emagging the assembly directly after removing it (to be cheeky) + var/atom/balloon_alert_loc = get(src, /obj/machinery/button) || src + balloon_alert_loc.balloon_alert(user, "safeties overridden") + return TRUE + +// Multitooling emagged elevator buttons will fix the safeties +/obj/item/assembly/control/elevator/multitool_act(mob/living/user) + if(!(obj_flags & EMAGGED)) + return ..() + + var/datum/transport_controller/linear/lift = lift_weakref?.resolve() + if(isnull(lift)) + return ..() + + for(var/obj/structure/transport/linear/lift_platform as anything in lift.transport_modules) + lift_platform.violent_landing = initial(lift_platform.violent_landing) + lift_platform.warns_on_down_movement = initial(lift_platform.warns_on_down_movement) + lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed) + + for(var/obj/machinery/door/elevator_door as anything in GLOB.elevator_doors) + if(elevator_door.transport_linked_id != lift.specific_transport_id) + continue + if(!(elevator_door.obj_flags & EMAGGED)) + continue + elevator_door.obj_flags &= ~EMAGGED + INVOKE_ASYNC(elevator_door, TYPE_PROC_REF(/obj/machinery/door, close)) + + // We can only be multitooled directly so just throw up the balloon alert + balloon_alert(user, "safeties reset") + obj_flags &= ~EMAGGED + +/obj/item/assembly/control/elevator/activate(mob/activator) + if(!COOLDOWN_FINISHED(src, elevator_cooldown)) + return + + // Actually try to call the elevator - this sleeps. + // If we failed to call it, play a buzz sound. + if(!call_elevator(activator)) + playsound(loc, 'sound/machines/buzz-two.ogg', 50, TRUE) + + // Finally, give people a chance to get off after it's done before going back off cooldown + COOLDOWN_START(src, elevator_cooldown, 2 SECONDS) + +/// Actually calls the elevator. +/// Returns FALSE if we failed to setup the move. +/// Returns TRUE if the move setup was a success, EVEN IF the move itself fails afterwards +/obj/item/assembly/control/elevator/proc/call_elevator(mob/activator) + // We can't call an elevator that doesn't exist + var/datum/transport_controller/linear/lift = lift_weakref?.resolve() + if(!lift) + loc.balloon_alert(activator, "no elevator connected!") + return FALSE + + // We can't call an elevator that's moving. You may say "you totally can do that", but that's not modelled + if(lift.controls_locked == LIFT_PLATFORM_LOCKED) + loc.balloon_alert(activator, "elevator is moving!") + return FALSE + + // If the elevator is already here, open the doors. + var/obj/structure/transport/linear/prime_lift = lift.return_closest_platform_to_z(loc.z) + if(prime_lift.z == loc.z) + INVOKE_ASYNC(lift, TYPE_PROC_REF(/datum/transport_controller/linear, open_lift_doors_callback)) + loc.balloon_alert(activator, "elevator is here!") + return TRUE + + // At this point, we can start moving. + + // Give the user, if supplied, a balloon alert. + if(activator) + loc.balloon_alert(activator, "elevator called") + + // Actually try to move the lift. This will sleep. + if(!lift.move_to_zlevel(loc.z, CALLBACK(src, PROC_REF(check_button)))) + loc.balloon_alert(activator, "elevator out of service!") + return FALSE + + // From here on all returns are TRUE, as we successfully moved the lift, even if we maybe didn't reach our floor + + // Our button was destroyed mid transit. + if(!check_button()) + return TRUE + + // Our lift platform survived, but it didn't reach our landing z. + if(!QDELETED(prime_lift) && prime_lift.z != loc.z) + if(!QDELETED(activator)) + loc.balloon_alert(activator, "elevator out of service!") + playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + return TRUE + + // Everything went according to plan + if(!QDELETED(activator)) + loc.balloon_alert(activator, "elevator arrived") + + return TRUE + +/// Callback for move_to_zlevel / general proc to check if we're still in a button +/obj/item/assembly/control/elevator/proc/check_button() + if(QDELETED(src) || !istype(loc, /obj/machinery/button)) + return FALSE + return TRUE + +/// Gets the elevator associated with our assembly / button +/obj/item/assembly/control/elevator/proc/get_lift() + for(var/datum/transport_controller/linear/possible_match as anything in SStransport.transports_by_type[TRANSPORT_TYPE_ELEVATOR]) + if(possible_match.specific_transport_id != id) + continue + + return possible_match + + return null diff --git a/code/modules/industrial_lift/elevator/elevator_doors.dm b/code/modules/transport/elevator/elev_doors.dm similarity index 100% rename from code/modules/industrial_lift/elevator/elevator_doors.dm rename to code/modules/transport/elevator/elev_doors.dm diff --git a/code/modules/transport/elevator/elev_indicator.dm b/code/modules/transport/elevator/elev_indicator.dm new file mode 100644 index 0000000000000..9751b44e0ff12 --- /dev/null +++ b/code/modules/transport/elevator/elev_indicator.dm @@ -0,0 +1,170 @@ +/** + * An indicator display aka an elevator hall lantern w/ floor number + */ +/obj/machinery/lift_indicator + name = "elevator indicator" + desc = "Indicates what floor the elevator is at and which way it's going." + icon = 'icons/obj/machines/lift_indicator.dmi' + icon_state = "lift_indo-base" + base_icon_state = "lift_indo-" + max_integrity = 500 + integrity_failure = 0.25 + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.05 + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.02 + anchored = TRUE + density = FALSE + + light_range = 1 + light_power = 1 + light_color = COLOR_DISPLAY_BLUE + + maptext_x = 18 + maptext_y = 20 + maptext_width = 8 + maptext_height = 16 + + /// What specific_transport_id do we link with? + var/linked_elevator_id + /// 'Floors' for display purposes are by default offset by 1 from their actual z-levels + var/lowest_floor_offset = 1 + /// Weakref to the transport. + var/datum/weakref/lift_ref + /// The lowest floor number. Determined by transport module init. + var/lowest_floor_num = 1 + /// Positive for going up, negative going down, 0 for stopped + var/current_lift_direction = 0 + /// The elevator's current floor relative to its lowest floor being 1 + var/current_lift_floor = 1 + +/obj/machinery/lift_indicator/Initialize(mapload) + . = ..() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/lift_indicator/LateInitialize() + . = ..() + + for(var/datum/transport_controller/linear/possible_match as anything in SStransport.transports_by_type[TRANSPORT_TYPE_ELEVATOR]) + if(possible_match.specific_transport_id != linked_elevator_id) + continue + + lift_ref = WEAKREF(possible_match) + RegisterSignal(possible_match, COMSIG_LIFT_SET_DIRECTION, PROC_REF(on_lift_direction)) + +/obj/machinery/lift_indicator/examine(mob/user) + . = ..() + + if(!is_operational) + . += span_notice("The display is dark.") + return + + var/dirtext + switch(current_lift_direction) + if(UP) + dirtext = "travelling upwards" + if(DOWN) + dirtext = "travelling downwards" + else + dirtext = "stopped" + + . += span_notice("The elevator is at floor [current_lift_floor], [dirtext].") + +/** + * Update state, and only process if elevator is moving. + */ +/obj/machinery/lift_indicator/proc/on_lift_direction(datum/source, direction) + SIGNAL_HANDLER + + var/datum/transport_controller/linear/lift = lift_ref?.resolve() + if(!lift) + return + + set_lift_state(direction, current_lift_floor) + update_operating() + +/obj/machinery/lift_indicator/on_set_is_operational() + . = ..() + + update_operating() + +/** + * Update processing state. + * + * Returns whether we are still processing. + */ +/obj/machinery/lift_indicator/proc/update_operating() + // Let process() figure it out to have the logic in one place. + var/should_process = process() != PROCESS_KILL + if(should_process) + begin_processing() + return TRUE + end_processing() + return FALSE + +/obj/machinery/lift_indicator/process() + var/datum/transport_controller/linear/lift = lift_ref?.resolve() + + // Check for stopped states. + if(!lift || !is_operational) + // elevator missing, or we lost power. + set_lift_state(0, 0, force = !is_operational) + return PROCESS_KILL + + use_power(active_power_usage) + + var/obj/structure/transport/linear/lift_part = lift.transport_modules[1] + + if(QDELETED(lift_part)) + set_lift_state(0, 0, force = !is_operational) + return PROCESS_KILL + + // Update + set_lift_state(current_lift_direction, lift.transport_modules[1].z - lowest_floor_offset) + + // elevator's not moving, we're done; we just had to update the floor number one last time. + if(!current_lift_direction) + return PROCESS_KILL + +/** + * Set the state and update appearance. + * + * Arguments: + * new_direction - new arrow state: UP, DOWN, or 0 + * new_floor - set the floor number, eg. 1, 2, 3 + * force_update - force appearance to update even if state didn't change. + */ +/obj/machinery/lift_indicator/proc/set_lift_state(new_direction, new_floor, force = FALSE) + if(new_direction == current_lift_direction && new_floor == current_lift_floor && !force) + return + + current_lift_direction = new_direction + current_lift_floor = new_floor + update_appearance() + +/obj/machinery/lift_indicator/update_appearance(updates) + . = ..() + + if(!is_operational) + set_light(l_on = FALSE) + maptext = "" + return + + set_light(l_on = TRUE) + maptext = "
    [current_lift_floor]
    " + +/obj/machinery/lift_indicator/update_overlays() + . = ..() + + if(!is_operational) + return + + . += emissive_appearance(icon, "[base_icon_state]e", offset_spokesman = src, alpha = src.alpha) + + if(!current_lift_direction) + return + + var/arrow_icon_state = "[base_icon_state][current_lift_direction == UP ? "up" : "down"]" + + . += mutable_appearance(icon, arrow_icon_state) + . += emissive_appearance(icon, "[arrow_icon_state]e", offset_spokesman = src, alpha = src.alpha) + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/lift_indicator, 32) diff --git a/code/modules/transport/elevator/elev_music_zone.dm b/code/modules/transport/elevator/elev_music_zone.dm new file mode 100644 index 0000000000000..1f09a00a68be6 --- /dev/null +++ b/code/modules/transport/elevator/elev_music_zone.dm @@ -0,0 +1,111 @@ +GLOBAL_LIST_EMPTY(elevator_music) + +/obj/effect/abstract/elevator_music_zone + name = "elevator music speaker" + desc = "You can't see this because it's mounted on the roof of the elevator." + anchored = TRUE + invisibility = INVISIBILITY_MAXIMUM // Setting this to ABSTRACT means it isn't moved by the lift + icon = 'icons/obj/art/musician.dmi' + icon_state = "piano" + /// What specific_transport_id do we link with? + var/linked_elevator_id = "" + /// Radius around this map helper in which to play the sound + var/range = 1 + /// Sound loop type to use + var/soundloop_type = /datum/looping_sound/local_forecast + /// Proximity monitor which handles playing sounds to clients + var/datum/proximity_monitor/advanced/elevator_music_area/sound_player + +/obj/effect/abstract/elevator_music_zone/Initialize(mapload) + . = ..() + if (!linked_elevator_id) + log_mapping("No elevator ID for elevator music provided at [AREACOORD(src)].") + return INITIALIZE_HINT_QDEL + + GLOB.elevator_music[linked_elevator_id] = src + sound_player = new(src, range = src.range, soundloop_type = src.soundloop_type) + +/obj/effect/abstract/elevator_music_zone/Destroy(force) + GLOB.elevator_music -= src + return ..() + +/obj/effect/abstract/elevator_music_zone/proc/link_to_panel(atom/elevator_panel) + RegisterSignal(elevator_panel, COMSIG_MACHINERY_POWER_RESTORED, PROC_REF(on_panel_powered)) + RegisterSignal(elevator_panel, COMSIG_MACHINERY_POWER_LOST, PROC_REF(on_panel_depowered)) + RegisterSignal(elevator_panel, COMSIG_QDELETING, PROC_REF(on_panel_destroyed)) + +/// Start sound loops when power is restored +/obj/effect/abstract/elevator_music_zone/proc/on_panel_powered() + SIGNAL_HANDLER + sound_player.turn_on() + +/// Stop sound loops if power is lost +/obj/effect/abstract/elevator_music_zone/proc/on_panel_depowered() + SIGNAL_HANDLER + sound_player.turn_off() + +/// Die if panel is destroyed, although currently they are invincible +/obj/effect/abstract/elevator_music_zone/proc/on_panel_destroyed() + SIGNAL_HANDLER + qdel(src) + +/// Load or unload a looping sound when mobs enter or exit the area +/datum/proximity_monitor/advanced/elevator_music_area + edge_is_a_field = TRUE + /// Are we currently playing sounds? + var/enabled = TRUE + /// Looping sound datum type to play + var/soundloop_type + /// Assoc list of mobs to sound loops currently playing + var/list/tracked_mobs = list() + +/datum/proximity_monitor/advanced/elevator_music_area/New(atom/_host, range, _ignore_if_not_on_turf, soundloop_type) + . = ..() + src.soundloop_type = soundloop_type + +/datum/proximity_monitor/advanced/elevator_music_area/Destroy() + QDEL_LIST_ASSOC_VAL(tracked_mobs) + return ..() + +/datum/proximity_monitor/advanced/elevator_music_area/field_turf_crossed(mob/entered, turf/location) + if (!istype(entered) || !entered.mind) + return + + if (entered in tracked_mobs) + return + + if (entered.client?.prefs.read_preference(/datum/preference/toggle/sound_elevator)) + tracked_mobs[entered] = new soundloop_type(_parent = entered, _direct = TRUE, start_immediately = enabled) + else + tracked_mobs[entered] = null // Still add it to the list so we don't keep making this check + RegisterSignal(entered, COMSIG_QDELETING, PROC_REF(mob_destroyed)) + +/datum/proximity_monitor/advanced/elevator_music_area/field_turf_uncrossed(mob/exited, turf/location) + if (!(exited in tracked_mobs)) + return + if (exited.z == host.z && get_dist(exited, host) <= current_range) + return + qdel(tracked_mobs[exited]) + tracked_mobs -= exited + UnregisterSignal(exited, COMSIG_QDELETING) + +/// Remove references on mob deletion +/datum/proximity_monitor/advanced/elevator_music_area/proc/mob_destroyed(mob/former_mob) + SIGNAL_HANDLER + if (former_mob in tracked_mobs) + qdel(tracked_mobs[former_mob]) + tracked_mobs -= former_mob + +/// Start sound loops playing +/datum/proximity_monitor/advanced/elevator_music_area/proc/turn_on() + enabled = TRUE + for (var/mob as anything in tracked_mobs) + var/datum/looping_sound/loop = tracked_mobs[mob] + loop.start() + +/// Stop active sound loops +/datum/proximity_monitor/advanced/elevator_music_area/proc/turn_off() + enabled = FALSE + for (var/mob as anything in tracked_mobs) + var/datum/looping_sound/loop = tracked_mobs[mob] + loop.stop() diff --git a/code/modules/transport/elevator/elev_panel.dm b/code/modules/transport/elevator/elev_panel.dm new file mode 100644 index 0000000000000..3e9e0e073c19f --- /dev/null +++ b/code/modules/transport/elevator/elev_panel.dm @@ -0,0 +1,397 @@ +/** + * # Elevator control panel + * + * A wallmounted simple machine that controls elevators, + * allowing users to enter a UI to move it up or down + * + * These can be placed in two methods: + * - You can place the control panel on the same turf as an elevator. It will move up and down with the elevator + * - You can place the control panel to the side of an elevator, NOT attached to the elevator. It will remain in position + * - I don't recommend using both methods on the same elevator, as it might result in some jank, but it's functional. + */ +/obj/machinery/elevator_control_panel + name = "elevator panel" + // Fire alarm reference. + desc = "\"In case of emergency, please use the stairs.\" Thus, always use the stairs." + density = FALSE + + icon = 'icons/obj/wallmounts.dmi' + icon_state = "elevpanel0" + base_icon_state = "elevpanel" + + power_channel = AREA_USAGE_ENVIRON + // Indestructible until someone wants to make these constructible, with all the chaos that implies + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + + /// Were we instantiated at mapload? Used to determine when we should link / throw errors + var/maploaded = FALSE + + /// A weakref to the transport_controller datum we control + var/datum/weakref/lift_weakref + /// What specific_transport_id do we link with? + var/linked_elevator_id + + /// A list of all possible destinations this elevator can travel. + /// Assoc list of "Floor name" to "z level of destination". + /// By default the floor names will auto-generate ("Floor 1", "Floor 2", etc). + var/list/linked_elevator_destination + /// If you want to override what each floor is named as, you can do so with this list. + /// Make this an assoc list of "z level you want to rename" to "desired name". + /// So, if you want the z-level 2 destination to be named "Cargo", you would do list("2" = "Cargo"). + /// (Reminder: Z1 gets loaded as Central Command, so your map's bottom Z will be Z2!) + var/list/preset_destination_names + + /// What z-level did we move to last? Used for showing the user in the UI which direction we're moving. + var/last_move_target + /// TimerID to our door reset timer, made by emergency opening doors + var/door_reset_timerid + /// The light mask overlay we use + light_power = 0.5 // Minimums, we want the button to glow if it has a mask, not light an area + light_range = 1.5 + light_color = LIGHT_COLOR_DARK_BLUE + var/light_mask = "elev-light-mask" + +/obj/machinery/elevator_control_panel/Initialize(mapload) + . = ..() + + var/static/list/tool_behaviors = list( + TOOL_MULTITOOL = list(SCREENTIP_CONTEXT_LMB = "Reset Panel"), + ) + AddElement(/datum/element/contextual_screentip_tools, tool_behaviors) + AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Send Elevator") + + // Machinery returns lateload by default via parent, + // this is just here for redundancy's sake. + . = INITIALIZE_HINT_LATELOAD + + maploaded = mapload + // Maploaded panels link in LateInitialize... + if(mapload) + return + + // And non-mapload panels link in Initialize + link_with_lift(log_error = FALSE) + +/obj/machinery/elevator_control_panel/LateInitialize() + . = ..() + // If we weren't maploaded, we probably already linked (or tried to link) in Initialize(). + if(!maploaded) + return + + // This is exclusively for linking in mapload, just to ensure all elevator parts are created, + // and also so we can throw mapping errors to let people know if they messed up setup. + link_with_lift(log_error = TRUE) + +/// Link with associated transport controllers, only log failure to find a lift in LateInit because those are mapped in +/obj/machinery/elevator_control_panel/proc/link_with_lift(log_error = FALSE) + var/datum/transport_controller/linear/lift = get_associated_lift() + if(!lift) + if (log_error) + log_mapping("Elevator control panel at [AREACOORD(src)] found no associated lift to link with, this may be a mapping error.") + return + + lift_weakref = WEAKREF(lift) + populate_destinations_list(lift) + if ((linked_elevator_id in GLOB.elevator_music)) + var/obj/effect/abstract/elevator_music_zone/music = GLOB.elevator_music[linked_elevator_id] + music.link_to_panel(src) + +/obj/machinery/elevator_control_panel/emag_act(mob/user, obj/item/card/emag/emag_card) + if(obj_flags & EMAGGED) + return FALSE + + obj_flags |= EMAGGED + + var/datum/transport_controller/linear/lift = lift_weakref?.resolve() + if(!lift) + return FALSE + + for(var/obj/structure/transport/linear/lift_platform as anything in lift.transport_modules) + lift_platform.violent_landing = TRUE + lift_platform.warns_on_down_movement = FALSE + lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed) * 0.5 + + for(var/obj/machinery/door/elevator_door as anything in GLOB.elevator_doors) + if(elevator_door.transport_linked_id != linked_elevator_id) + continue + if(elevator_door.obj_flags & EMAGGED) + continue + elevator_door.elevator_status = LIFT_PLATFORM_UNLOCKED + INVOKE_ASYNC(elevator_door, TYPE_PROC_REF(/obj/machinery/door, open), BYPASS_DOOR_CHECKS) + elevator_door.obj_flags |= EMAGGED + + playsound(src, SFX_SPARKS, 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + balloon_alert(user, "safeties overridden") + return TRUE + +/obj/machinery/elevator_control_panel/multitool_act(mob/living/user) + var/datum/transport_controller/linear/lift = lift_weakref?.resolve() + if(!lift) + return + + balloon_alert(user, "resetting panel...") + playsound(src, 'sound/machines/locktoggle.ogg', 50, TRUE) + if(!do_after(user, 6 SECONDS, src)) + balloon_alert(user, "interrupted!") + return TRUE + + if(QDELETED(lift) || !length(lift.transport_modules)) + return + + // If we were emagged, reset us + if(obj_flags & EMAGGED) + for(var/obj/structure/transport/linear/lift_platform as anything in lift.transport_modules) + lift_platform.violent_landing = initial(lift_platform.violent_landing) + lift_platform.warns_on_down_movement = initial(lift_platform.warns_on_down_movement) + lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed) + + for(var/obj/machinery/door/elevator_door as anything in GLOB.elevator_doors) + if(elevator_door.transport_linked_id != linked_elevator_id) + continue + if(!(elevator_door.obj_flags & EMAGGED)) + continue + elevator_door.obj_flags &= ~EMAGGED + INVOKE_ASYNC(elevator_door, TYPE_PROC_REF(/obj/machinery/door, close)) + + obj_flags &= ~EMAGGED + + // If we had doors open, stop the timer and reset them + if(door_reset_timerid) + deltimer(door_reset_timerid) + reset_doors() + + // Be vague about whether something was accomplished or not + balloon_alert(user, "panel reset") + playsound(src, 'sound/machines/locktoggle.ogg', 50, TRUE) + + return TRUE + +/// Find the elevator associated with our lift button. +/obj/machinery/elevator_control_panel/proc/get_associated_lift() + for(var/datum/transport_controller/linear/possible_match as anything in SStransport.transports_by_type[TRANSPORT_TYPE_ELEVATOR]) + if(possible_match.specific_transport_id != linked_elevator_id) + continue + + return possible_match + + return null + +/// Goes through and populates the linked_elevator_destination list with all possible destinations the lift can go. +/obj/machinery/elevator_control_panel/proc/populate_destinations_list(datum/transport_controller/linear/linked_lift) + // This list will track all the raw z-levels which we found that we can travel to + var/list/raw_destinations = list() + + // Get a list of all the starting locs our elevator starts at + var/list/starting_locs = list() + for(var/obj/structure/transport/linear/lift_piece as anything in linked_lift.transport_modules) + starting_locs |= lift_piece.locs + // The raw destination list will start with all the z's we start at + raw_destinations |= lift_piece.z + + // Get all destinations below us + add_destinations_in_a_direction_recursively(starting_locs, DOWN, raw_destinations) + // Get all destinations above us + add_destinations_in_a_direction_recursively(starting_locs, UP, raw_destinations) + + linked_elevator_destination = list() + for(var/z_level in raw_destinations) + // Check if this z-level has a preset destination associated. + var/preset_name = preset_destination_names?[num2text(z_level)] + // If we don't have a preset name, use Floor z-1 for the title. + // z - 1 is used because the station z-level is 2, and goes up. + linked_elevator_destination["[z_level]"] = preset_name || "Floor [z_level - 1]" + + // Reverse the destination list. + // By this point the list will go from bottom floor to top floor, + // which is unintuitive when passed to the UI to show to users. + // This way we have the top floors at the top, and the bottom floors the bottom. + reverse_range(linked_elevator_destination) + update_static_data_for_all_viewers() + +/** + * Recursively adds destinations to the list of linked_elevator_destination + * until it fails to find a valid stopping point in the passed direction. + */ +/obj/machinery/elevator_control_panel/proc/add_destinations_in_a_direction_recursively(list/turfs_to_check, direction, list/destinations) + // Only vertical elevators are supported - use trams for horizontal ones. + if(direction != UP && direction != DOWN) + CRASH("[type] was given an invalid direction in add_destinations_in_a_direction_recursively!") + + var/list/turf/checked_turfs = list() + // Go through every turf passed in our list of turfs to check. + for(var/turf/place in turfs_to_check) + // If the place we're checking isn't openspace, then we can't go downwards + if(direction == DOWN && !istype(place, /turf/open/openspace)) + return + + // Check the turf at the next level (either above or below the place we're checking) + var/turf/next_level = get_step_multiz(place, direction) + // No turf = at the edge of a map vertically + if(!next_level) + return + // If the next level above us has a roof, we can't move up + if(direction == UP && !istype(next_level, /turf/open/openspace)) + return + + // Otherwise, we can feasibly move our direction with this turf + checked_turfs += next_level + + // If we somehow found no turfs, BUT made it this far, something definitely went wrong. + if(!length(checked_turfs)) + CRASH("[type] found no turfs in add_destinations_in_a_direction_recursively!") + + // Add the Zs of all the found turfs as possible destinations + for(var/turf/found as anything in checked_turfs) + // We check all turfs we found in case of multi-z memes. + destinations |= found.z + + // And recursively call the proc with all the turfs we found on the next level + add_destinations_in_a_direction_recursively(checked_turfs, direction, destinations) + +/obj/machinery/elevator_control_panel/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ElevatorPanel", name) + ui.open() + +/obj/machinery/elevator_control_panel/ui_status(mob/user) + // We moved up a z-level, probably via the elevator itself, so don't preserve the UI. + if(user.z != z) + return UI_CLOSE + + // Our lift is gone entirely - look, but don't touch. + if(!lift_weakref?.resolve()) + return UI_UPDATE + + // We're non-functional - don't do anything. + if(!check_panel()) + return UI_DISABLED + + // Otherwise, just check default state (is the user conscious and close, etc). + return ..() + +/obj/machinery/elevator_control_panel/ui_data(mob/user) + var/list/data = list() + + data["emergency_level"] = capitalize(SSsecurity_level.get_current_level_as_text()) + data["is_emergency"] = SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED + data["doors_open"] = !!door_reset_timerid + + var/datum/transport_controller/linear/lift = lift_weakref?.resolve() + if(lift) + data["lift_exists"] = TRUE + data["currently_moving"] = lift.controls_locked == LIFT_PLATFORM_LOCKED + data["currently_moving_to_floor"] = last_move_target + data["current_floor"] = lift.transport_modules[1].z + + else + data["lift_exists"] = FALSE + data["currently_moving"] = FALSE + data["current_floor"] = 0 // 0 shows up as "Floor -1" in the UI, which is fine for what it is + + return data + +/obj/machinery/elevator_control_panel/ui_static_data(mob/user) + var/list/data = list() + + data["all_floor_data"] = list() + for(var/destination in linked_elevator_destination) + data["all_floor_data"] += list(list( + "name" = linked_elevator_destination[destination], + "z_level" = text2num(destination), + )) + + return data + +/obj/machinery/elevator_control_panel/ui_act(action, list/params) + . = ..() + if(.) + return + + if(!check_panel()) + return TRUE // We shouldn't be usable right now, update UI + + switch(action) + if("move_lift") + if(!allowed(usr)) + balloon_alert(usr, "access denied!") + return + + var/desired_z = params["z"] + // num2text here is required as the z is stored as strings in the list, but passed here as a number. + if(!(num2text(desired_z) in linked_elevator_destination)) + return TRUE // Something is inaccurate, update UI + + var/datum/transport_controller/linear/lift = lift_weakref?.resolve() + if(!lift || lift.controls_locked == LIFT_PLATFORM_LOCKED) + return TRUE // We shouldn't be moving anything, update UI + + INVOKE_ASYNC(lift, TYPE_PROC_REF(/datum/transport_controller/linear, move_to_zlevel), desired_z, CALLBACK(src, PROC_REF(check_panel)), usr) + last_move_target = desired_z + return TRUE // Succcessfully initiated a move. Regardless of whether it actually works, update the UI + + if("emergency_door") + var/datum/transport_controller/linear/lift = lift_weakref?.resolve() + if(!lift) + return TRUE // Something is wrong, update UI + + // The emergency door button is only available at red alert or higher. + // This is so people don't keep it in emergency mode 100% of the time. + if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_RED) + return TRUE // The security level might have been lowered since last update, so update UI + + // Open all elevator doors, it's an emergency dang it! + lift.update_lift_doors(action = CYCLE_OPEN) + door_reset_timerid = addtimer(CALLBACK(src, PROC_REF(reset_doors)), 3 MINUTES, TIMER_UNIQUE|TIMER_STOPPABLE) + return TRUE // We opened up all the doors, update the UI so the emergency button is replaced correctly + + if("reset_doors") + if(!door_reset_timerid) + return TRUE // All the doors were shut since last update, so update UI + + deltimer(door_reset_timerid) + reset_doors() + return TRUE // We closed all the doors, update the UI so the door button is replaced correctly + +/// Callback for move_to_zlevel to ensure the elevator can continue to move. +/obj/machinery/elevator_control_panel/proc/check_panel() + if(QDELETED(src)) + return FALSE + if(machine_stat & (NOPOWER|BROKEN)) + return FALSE + + return TRUE + +/// Helper proc to go through all of our desetinations and reset all elevator doors, +/// closing doors on z-levels the elevator is away from, and opening doors on the z the elevator is +/obj/machinery/elevator_control_panel/proc/reset_doors() + var/datum/transport_controller/linear/lift = lift_weakref?.resolve() + if(!lift) + return + + var/list/zs_we_are_present_on = lift.get_zs_we_are_on() + var/list/zs_we_are_absent = list() + + for(var/z_level in linked_elevator_destination) + z_level = text2num(z_level) // Stored as strings. + if(z_level in zs_we_are_present_on) + continue + + zs_we_are_absent |= z_level + + // Open all the doors on the zs we should be open on, + // and close all doors we aren't on. Simple enough. + lift.update_lift_doors(zs_we_are_present_on, action = CYCLE_OPEN) + lift.update_lift_doors(zs_we_are_absent, action = CYCLE_CLOSED) + + door_reset_timerid = null + +/obj/machinery/elevator_control_panel/update_overlays() + . = ..() + if(!light_mask) + return + + if(!(machine_stat & (NOPOWER|BROKEN)) && !panel_open) + . += emissive_appearance(icon, light_mask, src, alpha = alpha) + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/elevator_control_panel, 31) diff --git a/code/modules/transport/linear_controller.dm b/code/modules/transport/linear_controller.dm new file mode 100644 index 0000000000000..dd90562deb643 --- /dev/null +++ b/code/modules/transport/linear_controller.dm @@ -0,0 +1,635 @@ +///coordinate and control movement across linked transport_controllers. allows moving large single multitile platforms and many 1 tile platforms. +///also is capable of linking platforms across linked z levels +/datum/transport_controller/linear + ///the lift platforms we consider as part of this transport. ordered in order of lowest z level to highest z level after init. + ///(the sorting algorithm sucks btw) + var/list/obj/structure/transport/linear/transport_modules + + /// Typepath list of what to ignore smashing through, controls all lifts + var/static/list/ignored_smashthroughs = list( + /obj/machinery/power/supermatter_crystal, + /obj/structure/holosign, + /obj/machinery/field, + ) + + ///whether the lift handled by this transport_controller datum is multitile as opposed to nxm platforms per z level + var/modular_set = FALSE + + ///taken from our lift platforms. if true we go through each z level of platforms and attempt to make the lowest left corner platform + ///into one giant multitile object the size of all other platforms on that z level. + var/create_modular_set = FALSE + + ///lift platforms have already been sorted in order of z level. + var/z_sorted = FALSE + + ///transport_id taken from our base lift platform, used to put us into SStransport.transports_by_type + var/transport_id = TRANSPORT_TYPE_ELEVATOR + + ///overridable ID string to link control units to this specific transport_controller datum. created by placing a transport id landmark object + ///somewhere on the tram, if its anywhere on the tram we'll find it in init and set this to whatever it specifies + var/specific_transport_id + + ///bitfield of various transport states + var/controller_status = NONE + + ///if true, the platform cannot be manually moved. + var/controls_locked = FALSE + +/datum/transport_controller/linear/New(obj/structure/transport/linear/transport_module) + transport_id = transport_module.transport_id + create_modular_set = transport_module.create_modular_set + + link_transport_modules(transport_module) + ignored_smashthroughs = typecacheof(ignored_smashthroughs) + + LAZYADDASSOCLIST(SStransport.transports_by_type, transport_id, src) + + for(var/obj/structure/transport/linear/lift as anything in transport_modules) + lift.add_initial_contents() + +/datum/transport_controller/linear/Destroy() + for(var/obj/structure/transport/linear/transport_module as anything in transport_modules) + transport_module.transport_controller_datum = null + transport_modules = null + + LAZYREMOVEASSOC(SStransport.transports_by_type, transport_id, src) + if(isnull(SStransport.transports_by_type)) + SStransport.transports_by_type = list() + + return ..() + +/datum/transport_controller/linear/proc/add_transport_modules(obj/structure/transport/linear/new_transport_module) + if(new_transport_module in transport_modules) + return + for(var/obj/structure/transport/linear/other_module in new_transport_module.loc) + if(other_module != new_transport_module) + stack_trace("there is more than one transport module on a tile when a controller adds it. this causes problems") + qdel(other_module) + + new_transport_module.transport_controller_datum = src + LAZYADD(transport_modules, new_transport_module) + RegisterSignal(new_transport_module, COMSIG_QDELETING, PROC_REF(remove_transport_modules)) + + check_for_landmarks(new_transport_module) + + if(z_sorted)//make sure we dont lose z ordering if we get additional platforms after init + order_platforms_by_z_level() + +/datum/transport_controller/linear/proc/remove_transport_modules(obj/structure/transport/linear/old_transport_module) + SIGNAL_HANDLER + + if(!(old_transport_module in transport_modules)) + return + + old_transport_module.transport_controller_datum = null + LAZYREMOVE(transport_modules, old_transport_module) + UnregisterSignal(old_transport_module, COMSIG_QDELETING) + if(!length(transport_modules)) + qdel(src) + +///Collect all bordered platforms via a simple floodfill algorithm. allows multiz trams because its funny +/datum/transport_controller/linear/proc/link_transport_modules(obj/structure/transport/linear/base_transport_module) + add_transport_modules(base_transport_module) + var/list/possible_expansions = list(base_transport_module) + + while(possible_expansions.len) + for(var/obj/structure/transport/linear/borderline as anything in possible_expansions) + var/list/result = borderline.module_adjacency(src) + if(length(result)) + for(var/obj/structure/transport/linear/transport_module as anything in result) + if(transport_modules.Find(transport_module)) + continue + + add_transport_modules(transport_module) + possible_expansions |= transport_module + + possible_expansions -= borderline + +///check for any landmarks placed inside the locs of the given transport_module +/datum/transport_controller/linear/proc/check_for_landmarks(obj/structure/transport/linear/new_transport_module) + SHOULD_CALL_PARENT(TRUE) + + for(var/turf/platform_loc as anything in new_transport_module.locs) + var/obj/effect/landmark/transport/transport_id/id_giver = locate() in platform_loc + + if(id_giver) + set_info_from_id_landmark(id_giver) + +///set vars and such given an overriding transport_id landmark +/datum/transport_controller/linear/proc/set_info_from_id_landmark(obj/effect/landmark/transport/transport_id/landmark) + SHOULD_CALL_PARENT(TRUE) + + if(!istype(landmark, /obj/effect/landmark/transport/transport_id))//transport_controller subtypes can want differnet id's than the base type wants + return + + if(landmark.specific_transport_id) + specific_transport_id = landmark.specific_transport_id + + qdel(landmark) + +///orders the lift platforms in order of lowest z level to highest z level. +/datum/transport_controller/linear/proc/order_platforms_by_z_level() + //contains nested lists for every z level in the world. why? because its really easy to sort + var/list/platforms_by_z = list() + platforms_by_z.len = world.maxz + + for(var/z in 1 to world.maxz) + platforms_by_z[z] = list() + + for(var/obj/structure/transport/linear/transport_module as anything in transport_modules) + if(QDELETED(transport_module) || !transport_module.z) + transport_modules -= transport_module + continue + + platforms_by_z[transport_module.z] += transport_module + + if(create_modular_set) + for(var/list/z_list as anything in platforms_by_z) + if(!length(z_list)) + continue + + create_modular_set_for_z_level(z_list)//this will subtract all but one platform from the list + + var/list/output = list() + + for(var/list/z_list as anything in platforms_by_z) + output += z_list + + transport_modules = output + + z_sorted = TRUE + +///goes through all platforms in the given list and finds the one in the lower left corner +/datum/transport_controller/linear/proc/create_modular_set_for_z_level(list/obj/structure/transport/linear/platforms_in_z) + var/min_x = INFINITY + var/max_x = 0 + + var/min_y = INFINITY + var/max_y = 0 + + var/z = 0 + + for(var/obj/structure/transport/linear/module_to_sort as anything in platforms_in_z) + if(!z) + if(!module_to_sort.z) + stack_trace("create_modular_set_for_z_level() was given a platform in nullspace or not on a turf!") + platforms_in_z -= module_to_sort + continue + + z = module_to_sort.z + + if(z != module_to_sort.z) + stack_trace("create_modular_set_for_z_level() was given lifts on different z levels!") + platforms_in_z -= module_to_sort + continue + + min_x = min(min_x, module_to_sort.x) + max_x = max(max_x, module_to_sort.x) + + min_y = min(min_y, module_to_sort.y) + max_y = max(max_y, module_to_sort.y) + + var/turf/lower_left_corner_loc = locate(min_x, min_y, z) + if(!lower_left_corner_loc) + CRASH("was unable to find a turf at the lower left corner of this z") + + var/obj/structure/transport/linear/lower_left_corner_transport = locate() in lower_left_corner_loc + + if(!lower_left_corner_transport) + CRASH("there was no transport in the lower left corner of the given transport") + + platforms_in_z.Cut() + platforms_in_z += lower_left_corner_transport//we want to change the list given to us not create a new one. so we do this + + lower_left_corner_transport.create_modular_set(min_x, min_y, max_x, max_y, z) + +///returns the closest transport to the specified atom, prioritizing transports on the same z level. used for comparing distance +/datum/transport_controller/linear/proc/return_closest_platform_to(atom/comparison, allow_multiple_answers = FALSE) + if(!istype(comparison) || !comparison.z) + return FALSE + + var/list/obj/structure/transport/linear/candidate_platforms = list() + + for(var/obj/structure/transport/linear/platform as anything in transport_modules) + if(platform.z == comparison.z) + candidate_platforms += platform + + var/obj/structure/transport/linear/winner = candidate_platforms[1] + var/winner_distance = get_dist(comparison, winner) + + var/list/tied_winners = list(winner) + + for(var/obj/structure/transport/linear/platform_to_sort as anything in candidate_platforms) + var/platform_distance = get_dist(comparison, platform_to_sort) + + if(platform_distance < winner_distance) + winner = platform_to_sort + winner_distance = platform_distance + + if(allow_multiple_answers) + tied_winners = list(winner) + + else if(platform_distance == winner_distance && allow_multiple_answers) + tied_winners += platform_to_sort + + if(allow_multiple_answers) + return tied_winners + + return winner + +/// Returns a platform on the z-level which is vertically closest to the passed target_z +/datum/transport_controller/linear/proc/return_closest_platform_to_z(target_z) + var/obj/structure/transport/linear/found_platform + for(var/obj/structure/transport/linear/lift as anything in transport_modules) + // Already at the same Z-level, we can stop + if(lift.z == target_z) + found_platform = lift + break + + // Set up an initial lift to compare to + if(!found_platform) + found_platform = lift + continue + + // Same level, we can go with the one we currently have + if(lift.z == found_platform.z) + continue + + // If the difference between the current found platform and the target + // if less than the distance between the next lift and the target, + // our current platform is closer to the target than the next one, so we can skip it + if(abs(found_platform.z - target_z) < abs(lift.z - target_z)) + continue + + // The difference is smaller for this lift, so it's closer + found_platform = lift + + return found_platform + +/// Returns a list of all the z-levels our transport is currently on. +/datum/transport_controller/linear/proc/get_zs_we_are_on() + var/list/zs_we_are_present_on = list() + for(var/obj/structure/transport/linear/lift as anything in transport_modules) + zs_we_are_present_on |= lift.z + return zs_we_are_present_on + +///returns all transport modules associated with this transport on the given z level or given atoms z level +/datum/transport_controller/linear/proc/get_platforms_on_level(atom/atom_reference_OR_z_level_number) + var/z = atom_reference_OR_z_level_number + if(isatom(atom_reference_OR_z_level_number)) + z = atom_reference_OR_z_level_number.z + + if(!isnum(z) || z < 0 || z > world.maxz) + return null + + var/list/platforms_in_z = list() + + for(var/obj/structure/transport/linear/lift_to_check as anything in transport_modules) + if(lift_to_check.z) + platforms_in_z += lift_to_check + + return platforms_in_z + +/** + * Moves the platform UP or DOWN, this is what users invoke with their hand. + * This is a SAFE proc, ensuring every part of it moves SANELY. + * + * Arguments: + * going - UP or DOWN directions, where the platform should go. Keep in mind by this point checks of whether it should go up or down have already been done. + * user - Whomever made the movement. + */ +/datum/transport_controller/linear/proc/move_lift_vertically(going, mob/user) + //transport_modules are sorted in order of lowest z to highest z, so going upwards we need to move them in reverse order to not collide + if(going == UP) + var/obj/structure/transport/linear/platform_to_move + var/current_index = length(transport_modules) + + while(current_index > 0) + platform_to_move = transport_modules[current_index] + current_index-- + + platform_to_move.travel(going) + + else if(going == DOWN) + for(var/obj/structure/transport/linear/transport_module as anything in transport_modules) + transport_module.travel(going) + +/** + * Moves the platform after a passed delay. + * + * This is a more "user friendly" or "realistic" move. + * It includes things like: + * - Allowing platform "travel time" + * - Shutting elevator safety doors + * - Sound effects while moving + * - Safety warnings for anyone below the platform (while it's moving downwards) + * + * Arguments: + * duration - required, how long do we wait to move the platform? + * door_duration - optional, how long should we wait to open the doors after arriving? If null, we won't open or close doors + * direction - which direction are we moving the lift? + * user - optional, who is moving the lift? + */ +/datum/transport_controller/linear/proc/move_after_delay(lift_move_duration, door_duration, direction, mob/user) + if(!isnum(lift_move_duration)) + CRASH("[type] move_after_delay called with invalid duration ([lift_move_duration]).") + if(lift_move_duration <= 0 SECONDS) + move_lift_vertically(direction, user) + return + + // Get the lowest or highest platform according to which direction we're moving + var/obj/structure/transport/linear/prime_lift = return_closest_platform_to_z(direction == UP ? world.maxz : 0) + + // If anyone changes the hydraulic sound effect I sure hope they update this variable... + var/hydraulic_sfx_duration = 2 SECONDS + // ...because we use the duration of the sound effect to make it last for roughly the duration of the lift travel + playsound(prime_lift, 'sound/mecha/hydraulic.ogg', 25, vary = TRUE, frequency = clamp(hydraulic_sfx_duration / lift_move_duration, 0.33, 3)) + + // Move the platform after a timer + addtimer(CALLBACK(src, PROC_REF(move_lift_vertically), direction, user), lift_move_duration, TIMER_UNIQUE) + // Open doors after the set duration if supplied + if(isnum(door_duration)) + addtimer(CALLBACK(src, PROC_REF(open_lift_doors_callback)), door_duration, TIMER_UNIQUE) + + // Here on we only care about platforms going DOWN + if(direction != DOWN) + return + + // Okay we're going down, let's try to display some warnings to people below + var/list/turf/lift_locs = list() + for(var/obj/structure/transport/linear/going_to_move as anything in transport_modules) + // This platform has no warnings so we don't even need to worry about it + if(!going_to_move.warns_on_down_movement) + continue + // Collect all the turfs our platform is found at + lift_locs |= going_to_move.locs + + for(var/turf/moving in lift_locs) + // Find what's below the turf that's moving + var/turf/below_us = get_step_multiz(moving, DOWN) + // Hold up the turf below us is also in our locs list. Multi-z? Don't show a warning + if(below_us in lift_locs) + continue + // Display the warning for until we land + new /obj/effect/temp_visual/telegraphing/lift_travel(below_us, lift_move_duration) + +/** + * Simple wrapper for checking if we can move 1 zlevel, and if we can, do said move. + * Locks controls, closes all doors, then moves the platform and re-opens the doors afterwards. + * + * Arguments: + * direction - which direction are we moving? + * lift_move_duration - how long does the move take? can be 0 or null for instant move. + * door_duration - how long does it take for the doors to open after a move? + * user - optional, who moved it? + */ +/datum/transport_controller/linear/proc/simple_move_wrapper(direction, lift_move_duration, mob/user) + if(!Check_lift_move(direction)) + return FALSE + + // Lock controls, to prevent moving-while-moving memes + controls_lock(TRUE) + // Send out a signal that we're going + SEND_SIGNAL(src, COMSIG_LIFT_SET_DIRECTION, direction) + // Close all lift doors + update_lift_doors(action = CYCLE_CLOSED) + + if(isnull(lift_move_duration) || lift_move_duration <= 0 SECONDS) + // Do an instant move + move_lift_vertically(direction, user) + // Open doors on the zs we arrive at + update_lift_doors(get_zs_we_are_on(), action = CYCLE_OPEN) + // And unlock the controls after + controls_lock(FALSE) + return TRUE + + // Do a delayed move + move_after_delay( + lift_move_duration = lift_move_duration, + door_duration = lift_move_duration * 1.5, + direction = direction, + user = user, + ) + + addtimer(CALLBACK(src, PROC_REF(finish_simple_move_wrapper)), lift_move_duration * 1.5) + return TRUE + +/** + * Wrap everything up from simple_move_wrapper finishing its movement + */ +/datum/transport_controller/linear/proc/finish_simple_move_wrapper() + SEND_SIGNAL(src, COMSIG_LIFT_SET_DIRECTION, 0) + controls_lock(FALSE) + +/** + * Moves the platform to the passed z-level. + * + * Checks for validity of the move: Are we moving to the same z-level, can we actually move to that z-level? + * Does NOT check if the controls are currently locked. + * + * Moves to the passed z-level by calling move_after_delay repeatedly until the passed z-level is reached. + * This proc sleeps as it moves. + * + * Arguments: + * target_z - required, the Z we want to move to + * loop_callback - optional, an additional callback invoked during the loop that allows the move to cancel. + * user - optional, who started the move + */ +/datum/transport_controller/linear/proc/move_to_zlevel(target_z, datum/callback/loop_callback, mob/user) + if(!isnum(target_z) || target_z <= 0) + CRASH("[type] move_to_zlevel was passed an invalid target_z ([target_z]).") + + var/obj/structure/transport/linear/prime_lift = return_closest_platform_to_z(target_z) + var/lift_z = prime_lift.z + // We're already at the desired z-level! + if(target_z == lift_z) + return FALSE + + // The amount of z levels between the our and target_z + var/z_difference = abs(target_z - lift_z) + // Direction (up/down) needed to go to reach target_z + var/direction = lift_z < target_z ? UP : DOWN + + // We can't go that way anymore, or possibly ever + if(!Check_lift_move(direction)) + return FALSE + + // Okay we're ready to start moving now. + controls_lock(TRUE) + // Send out a signal that we're going + SEND_SIGNAL(src, COMSIG_LIFT_SET_DIRECTION, direction) + var/travel_speed = prime_lift.elevator_vertical_speed + + // Close all lift doors + update_lift_doors(action = CYCLE_CLOSED) + // Approach the desired z-level one step at a time + for(var/i in 1 to z_difference) + if(!Check_lift_move(direction)) + break + if(loop_callback && !loop_callback.Invoke()) + break + // move_after_delay will set up a timer and cause us to move after a time + move_after_delay( + lift_move_duration = travel_speed, + direction = direction, + user = user, + ) + // and we don't want to send another request until the timer's done + stoplag(travel_speed + 0.1 SECONDS) + if(QDELETED(src) || QDELETED(prime_lift)) + return + + addtimer(CALLBACK(src, PROC_REF(open_lift_doors_callback)), 2 SECONDS) + SEND_SIGNAL(src, COMSIG_LIFT_SET_DIRECTION, 0) + controls_lock(FALSE) + return TRUE + +/** + * Updates all blast doors and shutters that share an ID with our lift. + * + * Arguments: + * on_z_level - optional, only open doors on this z-level or list of z-levels + * action - how do we update the doors? CYCLE_OPEN to make them open, CYCLE_CLOSED to make them shut + */ +/datum/transport_controller/linear/proc/update_lift_doors(on_z_level, action) + + if(!isnull(on_z_level) && !islist(on_z_level)) + on_z_level = list(on_z_level) + + var/played_ding = FALSE + for(var/obj/machinery/door/elevator_door as anything in GLOB.elevator_doors) + if(elevator_door.transport_linked_id != specific_transport_id) + continue + if(on_z_level && !(elevator_door.z in on_z_level)) + continue + switch(action) + if(CYCLE_OPEN) + elevator_door.elevator_status = LIFT_PLATFORM_UNLOCKED + if(!played_ding) + playsound(elevator_door, 'sound/machines/ping.ogg', 50, TRUE) + played_ding = TRUE + addtimer(CALLBACK(elevator_door, TYPE_PROC_REF(/obj/machinery/door, open)), 0.7 SECONDS) + if(CYCLE_CLOSED) + elevator_door.elevator_status = LIFT_PLATFORM_LOCKED + INVOKE_ASYNC(elevator_door, TYPE_PROC_REF(/obj/machinery/door, close)) + else + stack_trace("Elevator lift update_lift_doors called with an improper action ([action]).") + +/// Helper used in callbacks to open all the doors our platform is on +/datum/transport_controller/linear/proc/open_lift_doors_callback() + update_lift_doors(get_zs_we_are_on(), action = CYCLE_OPEN) + +/** + * Moves the platform, this is what users invoke with their hand. + * This is a SAFE proc, ensuring every part of the lift moves SANELY. + * It also locks controls for the (miniscule) duration of the movement, so the elevator cannot be broken by spamming. + */ +/datum/transport_controller/linear/proc/move_transport_horizontally(going) + if(modular_set) + controls_lock(TRUE) + for(var/obj/structure/transport/linear/module_to_move as anything in transport_modules) + module_to_move.travel(going) + + controls_lock(FALSE) + return + + var/max_x = 0 + var/max_y = 0 + var/max_z = 0 + var/min_x = world.maxx + var/min_y = world.maxy + var/min_z = world.maxz + + for(var/obj/structure/transport/linear/transport_module as anything in transport_modules) + max_z = max(max_z, transport_module.z) + min_z = min(min_z, transport_module.z) + + min_x = min(min_x, transport_module.x) + max_x = max(max_x, transport_module.x) + //this assumes that all z levels have identical horizontal bounding boxes + //but if youre still using a non multitile platform at this point + //then its your own problem. it wont runtime it will just be slower than it needs to be if this assumption isnt + //the case + + min_y = min(min_y, transport_module.y) + max_y = max(max_y, transport_module.y) + + for(var/z in min_z to max_z) + //This must be safe way to border tile to tile move of bordered platforms, that excludes platform overlapping. + if(going & WEST) + //Go along the X axis from min to max, from left to right + for(var/x in min_x to max_x) + if(going & NORTH) + //Go along the Y axis from max to min, from up to down + for(var/y in max_y to min_y step -1) + var/obj/structure/transport/linear/transport_module = locate(/obj/structure/transport/linear, locate(x, y, z)) + transport_module?.travel(going) + + else if(going & SOUTH) + //Go along the Y axis from min to max, from down to up + for(var/y in min_y to max_y) + var/obj/structure/transport/linear/transport_module = locate(/obj/structure/transport/linear, locate(x, y, z)) + transport_module?.travel(going) + + else + for(var/y in min_y to max_y) + var/obj/structure/transport/linear/transport_module = locate(/obj/structure/transport/linear, locate(x, y, z)) + transport_module?.travel(going) + else + //Go along the X axis from max to min, from right to left + for(var/x in max_x to min_x step -1) + if(going & NORTH) + //Go along the Y axis from max to min, from up to down + for(var/y in max_y to min_y step -1) + var/obj/structure/transport/linear/transport_module = locate(/obj/structure/transport/linear, locate(x, y, z)) + transport_module?.travel(going) + + else if (going & SOUTH) + for(var/y in min_y to max_y) + var/obj/structure/transport/linear/transport_module = locate(/obj/structure/transport/linear, locate(x, y, z)) + transport_module?.travel(going) + + else + //Go along the Y axis from min to max, from down to up + for(var/y in min_y to max_y) + var/obj/structure/transport/linear/transport_module = locate(/obj/structure/transport/linear, locate(x, y, z)) + transport_module?.travel(going) + +///Check destination turfs +/datum/transport_controller/linear/proc/Check_lift_move(check_dir) + for(var/obj/structure/transport/linear/transport_module as anything in transport_modules) + for(var/turf/bound_turf in transport_module.locs) + var/turf/T = get_step_multiz(transport_module, check_dir) + if(!T)//the edges of multi-z maps + return FALSE + if(check_dir == UP && !istype(T, /turf/open/openspace)) // We don't want to go through the ceiling! + return FALSE + if(check_dir == DOWN && !istype(get_turf(transport_module), /turf/open/openspace)) // No going through the floor! + return FALSE + return TRUE + +/** + * Sets transport controls_locked state. Used to prevent moving mid movement, or cooldowns. + */ +/datum/transport_controller/linear/proc/controls_lock(state) + switch(state) + if(FALSE) + controller_status &= ~CONTROLS_LOCKED + else + controller_status |= CONTROLS_LOCKED + +/** + * resets the contents of all platforms to their original state in case someone put a bunch of shit onto the platform. + * intended to be called by admins. passes all arguments to reset_contents() for each of our platforms. + * + * Arguments: + * * consider_anything_past - number. if > 0 our platforms will only handle foreign contents that exceed this number in each of their locs + * * foreign_objects - bool. if true our platforms will consider /atom/movable's that arent mobs as part of foreign contents + * * foreign_non_player_mobs - bool. if true our platforms consider mobs that dont have a mind to be foreign + * * consider_player_mobs - bool. if true our platforms consider player mobs to be foreign. only works if foreign_non_player_mobs is true as well + */ +/datum/transport_controller/linear/proc/reset_lift_contents(consider_anything_past = 0, foreign_objects = TRUE, foreign_non_player_mobs = TRUE, consider_player_mobs = FALSE) + for(var/obj/structure/transport/linear/lift_to_reset in transport_modules) + lift_to_reset.reset_contents(consider_anything_past, foreign_objects, foreign_non_player_mobs, consider_player_mobs) + + return TRUE diff --git a/code/modules/transport/tram/tram_controller.dm b/code/modules/transport/tram/tram_controller.dm new file mode 100644 index 0000000000000..c7fc895184fe8 --- /dev/null +++ b/code/modules/transport/tram/tram_controller.dm @@ -0,0 +1,1082 @@ +/** + * Tram specific variant of the generic linear transport controller. + * + * Hierarchy + * The sstransport subsystem manages a list of controllers, + * A controller manages a list of transport modules (individual tiles) which together make up a transport unit (in this case a tram) + */ +/datum/transport_controller/linear/tram + ///whether this controller is active (any state we don't accept new orders, not nessecarily moving) + var/controller_active = FALSE + ///whether all required parts of the tram are considered operational + var/controller_operational = TRUE + var/obj/machinery/transport/tram_controller/paired_cabinet + ///if we're travelling, what direction are we going + var/travel_direction = NONE + ///if we're travelling, how far do we have to go + var/travel_remaining = 0 + ///how far in total we'll be travelling + var/travel_trip_length = 0 + + ///multiplier on how much damage/force the tram imparts on things it hits + var/collision_lethality = 1 + var/obj/effect/landmark/transport/nav_beacon/tram/nav/nav_beacon + /// reference to the destination landmarks we consider ourselves "at" or travelling towards. since we potentially span multiple z levels we dont actually + /// know where on us this platform is. as long as we know THAT its on us we can just move the distance and direction between this + /// and the destination landmark. + var/obj/effect/landmark/transport/nav_beacon/tram/platform/idle_platform + /// reference to the destination landmarks we consider ourselves travelling towards. since we potentially span multiple z levels we dont actually + /// know where on us this platform is. as long as we know THAT its on us we can just move the distance and direction between this + /// and the destination landmark. + var/obj/effect/landmark/transport/nav_beacon/tram/platform/destination_platform + + var/current_speed = 0 + var/current_load = 0 + + ///decisecond delay between horizontal movement. cannot make the tram move faster than 1 movement per world.tick_lag. + var/speed_limiter = 0.5 + + ///version of speed_limiter that gets set in init and is considered our base speed if our lift gets slowed down + var/base_speed_limiter = 0.5 + + ///the world.time we should next move at. in case our speed is set to less than 1 movement per tick + var/scheduled_move = INFINITY + + ///whether we have been slowed down automatically + var/recovery_mode = FALSE + + ///how many times we moved while costing more than SStransport.max_time milliseconds per movement. + ///if this exceeds SStransport.max_exceeding_moves + var/recovery_activate_count = 0 + + ///how many times we moved while costing less than 0.5 * SStransport.max_time milliseconds per movement + var/recovery_clear_count = 0 + + var/datum/tram_mfg_info/tram_registration + + var/list/tram_history + +/datum/tram_mfg_info + var/serial_number + var/active = TRUE + var/mfg_date + var/install_location + var/distance_travelled = 0 + var/collisions = 0 + +/** + * Assign registration details to a new tram. + * + * When a new tram is created, we give it a builder's plate with the date it was created. + * We track a few stats about it, and keep a small historical record on the + * information plate inside the tram. + */ +/datum/tram_mfg_info/New(specific_transport_id) + if(GLOB.round_id) + serial_number = "LT306TG[add_leading(GLOB.round_id, 6, "0")]" + else + serial_number = "LT306TG[rand(000000, 999999)]" + + mfg_date = "[CURRENT_STATION_YEAR]-[time2text(world.timeofday, "MM-DD")]" + install_location = specific_transport_id + +/datum/tram_mfg_info/proc/load_from_json(list/json_data) + serial_number = json_data["serial_number"] + active = json_data["active"] + mfg_date = json_data["mfg_date"] + install_location = json_data["install_location"] + distance_travelled = json_data["distance_travelled"] + collisions = json_data["collisions"] + +/datum/tram_mfg_info/proc/export_to_json() + var/list/new_data = list() + new_data["serial_number"] = serial_number + new_data["active"] = active + new_data["mfg_date"] = mfg_date + new_data["install_location"] = install_location + new_data["distance_travelled"] = distance_travelled + new_data["collisions"] = collisions + return new_data + +/** + * Make sure all modules have matching speed limiter vars, pull save data from persistence + * + * We track a few stats about it, and keep a small historical record on the + * information plate inside the tram. + */ +/datum/transport_controller/linear/tram/New(obj/structure/transport/linear/tram/transport_module) + . = ..() + speed_limiter = transport_module.speed_limiter + base_speed_limiter = transport_module.speed_limiter + tram_history = SSpersistence.load_tram_history(specific_transport_id) + var/datum/tram_mfg_info/previous_tram = peek(tram_history) + if(!isnull(previous_tram) && previous_tram.active) + tram_registration = pop(tram_history) + else + tram_registration = new /datum/tram_mfg_info(specific_transport_id) + + check_starting_landmark() + +/** + * If someone VVs the base speed limiter of the tram, copy it to the current active speed limiter. + */ +/datum/transport_controller/linear/tram/vv_edit_var(var_name, var_value) + . = ..() + if(var_name == "base_speed_limiter") + speed_limiter = max(speed_limiter, base_speed_limiter) + +/datum/transport_controller/linear/tram/Destroy() + paired_cabinet = null + set_status_code(SYSTEM_FAULT, TRUE) + SEND_SIGNAL(SStransport, COMSIG_TRANSPORT_ACTIVE, src, FALSE, controller_status, travel_direction, destination_platform) + tram_registration.active = FALSE + SSblackbox.record_feedback("amount", "tram_destroyed", 1) + SSpersistence.save_tram_history(specific_transport_id) + ..() + +/** + * Register transport modules to the controller + * + * Spreads out searching neighbouring tiles for additional transport modules, to combine into one full tram. + * We register to every module's signal that it's collided with something, be it mob, structure, etc. + */ +/datum/transport_controller/linear/tram/add_transport_modules(obj/structure/transport/linear/new_transport_module) + . = ..() + RegisterSignal(new_transport_module, COMSIG_MOVABLE_BUMP, PROC_REF(gracefully_break)) + +/** + * The mapper should have placed the tram at one of the stations, the controller will search for a landmark within + * its control area and set it as its idle position. + */ +/datum/transport_controller/linear/tram/check_for_landmarks(obj/structure/transport/linear/tram/new_transport_module) + . = ..() + for(var/turf/platform_loc as anything in new_transport_module.locs) + var/obj/effect/landmark/transport/nav_beacon/tram/platform/initial_destination = locate() in platform_loc + var/obj/effect/landmark/transport/nav_beacon/tram/nav/beacon = locate() in platform_loc + + if(initial_destination) + idle_platform = initial_destination + destination_platform = initial_destination + + if(beacon) + nav_beacon = beacon + +/** + * Verify tram is in a valid starting location, start the subsystem. + * + * Throw an error if someone mapped a tram with no landmarks available for it to register. + * The processing subsystem starts off because not all maps have elevators/transports. + * Now that the tram is aware of its surroundings, we start the subsystem. + */ +/datum/transport_controller/linear/tram/proc/check_starting_landmark() + if(!idle_platform || !nav_beacon) + CRASH("a tram lift_master was initialized without the required landmarks to give it direction!") + + SStransport.can_fire = TRUE + + return TRUE + +/** + * The tram explodes if it hits a few types of objects. + * + * Signal for when the tram runs into a field of which it cannot go through. + * Stops the train's travel fully, sends a message, and destroys the train. + * Arguments: + * * bumped_atom - The atom this tram bumped into + */ +/datum/transport_controller/linear/tram/proc/gracefully_break(atom/bumped_atom) + SIGNAL_HANDLER + + travel_remaining = 0 + bumped_atom.visible_message(span_userdanger("The [bumped_atom.name] crashes into the field violently!")) + for(var/obj/structure/transport/linear/tram/transport_module as anything in transport_modules) + transport_module.set_travelling(FALSE) + for(var/explosive_target in transport_module.transport_contents) + if(iseffect(explosive_target)) + continue + + if(isliving(explosive_target)) + explosion(explosive_target, devastation_range = rand(0, 1), heavy_impact_range = 2, light_impact_range = 3) //50% chance of gib + + else if(prob(9)) + explosion(explosive_target, devastation_range = 1, heavy_impact_range = 2, light_impact_range = 3) + + explosion(transport_module, devastation_range = 1, heavy_impact_range = 2, light_impact_range = 3) + qdel(transport_module) + + send_transport_active_signal() + +/** + * Calculate the journey details to the requested platform + * + * These will eventually be passed to the transport modules as args telling them where to move. + * We do some sanity checking in case of discrepencany between where the subsystem thinks the + * tram is and where the tram actually is. (For example, moving the landmarks after round start.) + + */ +/datum/transport_controller/linear/tram/proc/calculate_route(obj/effect/landmark/transport/nav_beacon/tram/destination) + if(destination == idle_platform) + return FALSE + + destination_platform = destination + travel_direction = get_dir(nav_beacon, destination_platform) + travel_remaining = get_dist(nav_beacon, destination_platform) + travel_trip_length = travel_remaining + log_transport("TC: [specific_transport_id] trip calculation: src: [nav_beacon.x], [nav_beacon.y], [nav_beacon.z] dst: [destination_platform] [destination_platform.x], [destination_platform.y], [destination_platform.z] = Dir [travel_direction] Dist [travel_remaining].") + return TRUE + +/** + * Handles moving the tram + * + * Called by the subsystem, the controller tells the individual tram parts where to actually go and has extra safety checks + * incase multiple inputs get through, preventing conflicting directions and the tram literally ripping itself apart. + * All of the actual movement is handled by SStransport. + * + * If we're this far all the PRE_DEPARTURE checks should have passed, so we leave the PRE_DEPARTURE status and actually move. + * We send a signal to anything registered that cares about the physical movement of the tram. + * + * Arguments: + * * destination_platform - where the subsystem wants it to go + */ + +/datum/transport_controller/linear/tram/proc/dispatch_transport(obj/effect/landmark/transport/nav_beacon/tram/destination_platform) + log_transport("TC: [specific_transport_id] starting departure.") + set_status_code(PRE_DEPARTURE, FALSE) + if(controller_status & EMERGENCY_STOP) + set_status_code(EMERGENCY_STOP, FALSE) + playsound(paired_cabinet, 'sound/machines/synth_yes.ogg', 40, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + paired_cabinet.say("Controller reset.") + + SEND_SIGNAL(src, COMSIG_TRAM_TRAVEL, idle_platform, destination_platform) + + for(var/obj/structure/transport/linear/tram/transport_module as anything in transport_modules) //only thing everyone needs to know is the new location. + if(transport_module.travelling) //wee woo wee woo there was a double action queued. damn multi tile structs + return //we don't care to undo cover_locked controls, though, as that will resolve itself + transport_module.glide_size_override = DELAY_TO_GLIDE_SIZE(speed_limiter) + transport_module.set_travelling(TRUE) + + scheduled_move = world.time + speed_limiter + + START_PROCESSING(SStransport, src) + +/** + * Tram processing loop + * + * Moves the tram to its set destination. + * When it arrives at its destination perform callback to the post-arrival procs like controls and lights. + * We update the odometer and kill the process until we need to move again. + * + * If the status is EMERGENCY_STOP the tram should immediately come to a stop regardless of the travel_remaining. + * Some extra things happen in an emergency stop (throwing the passengers) and when reset will run a + * recovery procedure to head to the nearest platform and sync logical and physical location data + * (idle_platform and nav_beacon) once the issue is resolved. + */ +/datum/transport_controller/linear/tram/process(seconds_per_tick) + if(isnull(paired_cabinet)) + set_status_code(SYSTEM_FAULT, TRUE) + + if(controller_status & SYSTEM_FAULT || controller_status & EMERGENCY_STOP) + halt_and_catch_fire() + return PROCESS_KILL + + if(!travel_remaining) + if(!controller_operational) + degraded_stop() + return PROCESS_KILL + + normal_stop() + return PROCESS_KILL + + else if(world.time >= scheduled_move) + var/start_time = TICK_USAGE + travel_remaining-- + + move_transport_horizontally(travel_direction) + + var/duration = TICK_USAGE_TO_MS(start_time) + current_load = duration + current_speed = transport_modules[1].glide_size + if(recovery_mode) + if(duration <= (SStransport.max_time / 2)) + recovery_clear_count++ + else + recovery_clear_count = 0 + + if(recovery_clear_count >= SStransport.max_cheap_moves) + speed_limiter = base_speed_limiter + recovery_mode = FALSE + recovery_clear_count = 0 + log_transport("TC: [specific_transport_id] removing speed limiter, performance issue resolved. Last tick was [duration]ms.") + + else if(duration > SStransport.max_time) + recovery_activate_count++ + if(recovery_activate_count >= SStransport.max_exceeding_moves) + message_admins("The tram at [ADMIN_JMP(transport_modules[1])] is taking [duration] ms which is more than [SStransport.max_time] ms per movement for [recovery_activate_count] ticks. Reducing its movement speed until it recovers. If this continues to be a problem you can reset the tram contents to its original state, and clear added objects on the Debug tab.") + log_transport("TC: [specific_transport_id] activating speed limiter due to poor performance. Last tick was [duration]ms.") + speed_limiter = base_speed_limiter * 2 //halves its speed + recovery_mode = TRUE + recovery_activate_count = 0 + else + recovery_activate_count = max(recovery_activate_count - 1, 0) + + scheduled_move = world.time + speed_limiter + +/datum/transport_controller/linear/tram/proc/normal_stop() + cycle_doors(CYCLE_OPEN) + log_transport("TC: [specific_transport_id] trip completed. Info: nav_pos ([nav_beacon.x], [nav_beacon.y], [nav_beacon.z]) idle_pos ([destination_platform.x], [destination_platform.y], [destination_platform.z]).") + addtimer(CALLBACK(src, PROC_REF(unlock_controls)), 2 SECONDS) + if((controller_status & SYSTEM_FAULT) && (nav_beacon.loc == destination_platform.loc)) //position matches between controller and tram, we're back on track + set_status_code(SYSTEM_FAULT, FALSE) + playsound(paired_cabinet, 'sound/machines/synth_yes.ogg', 40, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + paired_cabinet.say("Controller reset.") + log_transport("TC: [specific_transport_id] position data successfully reset.") + speed_limiter = initial(speed_limiter) + idle_platform = destination_platform + tram_registration.distance_travelled += (travel_trip_length - travel_remaining) + travel_trip_length = 0 + current_speed = 0 + current_load = 0 + speed_limiter = initial(speed_limiter) + +/datum/transport_controller/linear/tram/proc/degraded_stop() + log_transport("TC: [specific_transport_id] trip completed with a degraded status. Info: [TC_TS_STATUS] nav_pos ([nav_beacon.x], [nav_beacon.y], [nav_beacon.z]) idle_pos ([destination_platform.x], [destination_platform.y], [destination_platform.z]).") + addtimer(CALLBACK(src, PROC_REF(unlock_controls)), 4 SECONDS) + if(controller_status & SYSTEM_FAULT) + set_status_code(SYSTEM_FAULT, FALSE) + playsound(paired_cabinet, 'sound/machines/synth_yes.ogg', 40, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + paired_cabinet.say("Controller reset.") + log_transport("TC: [specific_transport_id] position data successfully reset. ") + speed_limiter = initial(speed_limiter) + idle_platform = destination_platform + tram_registration.distance_travelled += (travel_trip_length - travel_remaining) + travel_trip_length = 0 + current_speed = 0 + current_load = 0 + speed_limiter = initial(speed_limiter) + var/throw_direction = travel_direction + for(var/obj/structure/transport/linear/tram/module in transport_modules) + module.estop_throw(throw_direction) + +/datum/transport_controller/linear/tram/proc/halt_and_catch_fire() + if(controller_status & SYSTEM_FAULT) + if(!isnull(paired_cabinet)) + playsound(paired_cabinet, 'sound/machines/buzz-sigh.ogg', 60, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + paired_cabinet.say("Controller error. Please contact your engineering department.") + log_transport("TC: [specific_transport_id] Transport Controller failed!") + + if(travel_remaining) + travel_remaining = 0 + var/throw_direction = travel_direction + for(var/obj/structure/transport/linear/tram/module in transport_modules) + module.estop_throw(throw_direction) + + addtimer(CALLBACK(src, PROC_REF(unlock_controls)), 4 SECONDS) + addtimer(CALLBACK(src, PROC_REF(cycle_doors), CYCLE_OPEN), 2 SECONDS) + idle_platform = null + log_transport("TC: [specific_transport_id] Transport Controller needs new position data from the tram.") + tram_registration.distance_travelled += (travel_trip_length - travel_remaining) + travel_trip_length = 0 + current_speed = 0 + current_load = 0 + +/datum/transport_controller/linear/tram/proc/reset_position() + if(idle_platform) + if(get_turf(idle_platform) == get_turf(nav_beacon)) + set_status_code(SYSTEM_FAULT, FALSE) + set_status_code(EMERGENCY_STOP, FALSE) + playsound(paired_cabinet, 'sound/machines/synth_yes.ogg', 40, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + paired_cabinet.say("Controller reset.") + log_transport("TC: [specific_transport_id] Transport Controller reset was requested, but the tram nav data seems correct. Info: nav_pos ([nav_beacon.x], [nav_beacon.y], [nav_beacon.z]) idle_pos ([idle_platform.x], [idle_platform.y], [idle_platform.z]).") + return + + log_transport("TC: [specific_transport_id] performing Transport Controller reset. Locating closest reset beacon to ([nav_beacon.x], [nav_beacon.y], [nav_beacon.z])") + var/tram_velocity_sign + if(travel_direction & (NORTH|SOUTH)) + tram_velocity_sign = travel_direction & NORTH ? OUTBOUND : INBOUND + else + tram_velocity_sign = travel_direction & EAST ? OUTBOUND : INBOUND + + var/reset_beacon = closest_nav_in_travel_dir(nav_beacon, tram_velocity_sign, specific_transport_id) + + if(!reset_beacon) + playsound(paired_cabinet, 'sound/machines/buzz-sigh.ogg', 60, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + paired_cabinet.say("Controller reset failed. Contact manufacturer.") // If you screwed up the tram this bad, I don't even + log_transport("TC: [specific_transport_id] non-recoverable error! Tram is at ([nav_beacon.x], [nav_beacon.y], [nav_beacon.z] [tram_velocity_sign ? "OUTBOUND" : "INBOUND"]) and can't find a reset beacon.") + message_admins("Tram ID [specific_transport_id] is in a non-recoverable error state at [ADMIN_JMP(nav_beacon)]. If it's causing problems, delete the controller datum from the 'Reset Tram' proc in the Debug tab.") + return + + travel_direction = get_dir(nav_beacon, reset_beacon) + travel_remaining = get_dist(nav_beacon, reset_beacon) + travel_trip_length = travel_remaining + destination_platform = reset_beacon + speed_limiter = 1.5 + playsound(paired_cabinet, 'sound/machines/ping.ogg', 40, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + paired_cabinet.say("Peforming controller reset... Navigating to reset point.") + log_transport("TC: [specific_transport_id] trip calculation: src: [nav_beacon.x], [nav_beacon.y], [nav_beacon.z] dst: [destination_platform] [destination_platform.x], [destination_platform.y], [destination_platform.z] = Dir [travel_direction] Dist [travel_remaining].") + cycle_doors(CYCLE_CLOSED) + set_active(TRUE) + set_status_code(CONTROLS_LOCKED, TRUE) + addtimer(CALLBACK(src, PROC_REF(dispatch_transport), reset_beacon), 3 SECONDS) + log_transport("TC: [specific_transport_id] trying to reset at [destination_platform].") + +/datum/transport_controller/linear/tram/proc/estop() + playsound(paired_cabinet, 'sound/machines/buzz-sigh.ogg', 60, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + paired_cabinet.say("Emergency stop activated!") + set_status_code(EMERGENCY_STOP, TRUE) + log_transport("TC: [specific_transport_id] requested emergency stop.") + +/** + * Handles unlocking the tram controls for use after moving + * + * More safety checks to make sure the tram has actually docked properly + * at a location before users are allowed to interact with the tram console again. + * Tram finds its location at this point before fully unlocking controls to the user. + */ +/datum/transport_controller/linear/tram/proc/unlock_controls() + controls_lock(FALSE) + for(var/obj/structure/transport/linear/tram/transport_module as anything in transport_modules) //only thing everyone needs to know is the new location. + transport_module.set_travelling(FALSE) + set_active(FALSE) + +/** + * Sets the active status for the controller and sends a signal to listeners. + * + * The main signal used by most components, it has the active status, the bitfield of the controller's status, its direction, and set destination. + * + * Arguments: + * new_status - The active status of the controller (whether it's busy doing something and not taking commands right now) + */ +/datum/transport_controller/linear/tram/proc/set_active(new_status) + if(controller_active == new_status) + return + + controller_active = new_status + send_transport_active_signal() + log_transport("TC: [specific_transport_id] controller state [controller_active ? "READY > PROCESSING" : "PROCESSING > READY"].") + +/** + * Sets the controller status bitfield + * + * This status var is used by various components like lights, crossing signals, signs + * Sent via signal the listening components will perform required actions based on + * the status codes. + * + * Arguments: + * * code - The status bitflag we're changing + * * value - boolean TRUE/FALSE to set the code + */ +/datum/transport_controller/linear/tram/proc/set_status_code(code, value) + if(code != DOORS_READY) + log_transport("TC: [specific_transport_id] status change [value ? "+" : "-"][english_list(bitfield_to_list(code, TRANSPORT_FLAGS))].") + switch(value) + if(TRUE) + controller_status |= code + if(FALSE) + controller_status &= ~code + else + stack_trace("Transport controller received invalid status code request [code]/[value]") + return + + send_transport_active_signal() + +/datum/transport_controller/linear/tram/proc/send_transport_active_signal() + SEND_SIGNAL(SStransport, COMSIG_TRANSPORT_ACTIVE, src, controller_active, controller_status, travel_direction, destination_platform) + +/** + * Part of the pre-departure list, checks the status of the doors on the tram + * + * Checks if all doors are closed, and updates the status code accordingly. + * + * TODO: this is probably better renamed check_door_status() + */ +/datum/transport_controller/linear/tram/proc/update_status() + for(var/obj/machinery/door/airlock/tram/door as anything in SStransport.doors) + if(door.transport_linked_id != specific_transport_id) + continue + if(door.crushing_in_progress) + log_transport("TC: [specific_transport_id] door [door.id_tag] failed crush status check.") + set_status_code(DOORS_READY, FALSE) + return + + set_status_code(DOORS_READY, TRUE) + +/** + * Cycle all the doors on the tram. + */ +/datum/transport_controller/linear/tram/proc/cycle_doors(door_status, rapid) + switch(door_status) + if(CYCLE_OPEN) + for(var/obj/machinery/door/airlock/tram/door as anything in SStransport.doors) + if(door.transport_linked_id == specific_transport_id) + INVOKE_ASYNC(door, TYPE_PROC_REF(/obj/machinery/door/airlock/tram, open), rapid) + + if(CYCLE_CLOSED) + for(var/obj/machinery/door/airlock/tram/door as anything in SStransport.doors) + if(door.transport_linked_id == specific_transport_id) + INVOKE_ASYNC(door, TYPE_PROC_REF(/obj/machinery/door/airlock/tram, close), rapid) + +/datum/transport_controller/linear/tram/proc/notify_controller(obj/machinery/transport/tram_controller/new_cabinet) + paired_cabinet = new_cabinet + RegisterSignal(new_cabinet, COMSIG_MACHINERY_POWER_LOST, PROC_REF(power_lost)) + RegisterSignal(new_cabinet, COMSIG_MACHINERY_POWER_RESTORED, PROC_REF(power_restored)) + RegisterSignal(new_cabinet, COMSIG_QDELETING, PROC_REF(on_cabinet_qdel)) + log_transport("TC: [specific_transport_id] is now paired with [new_cabinet].") + if(controller_status & SYSTEM_FAULT) + set_status_code(SYSTEM_FAULT, FALSE) + reset_position() + +/datum/transport_controller/linear/tram/proc/on_cabinet_qdel() + paired_cabinet = null + log_transport("TC: [specific_transport_id] received QDEL from controller cabinet.") + set_status_code(SYSTEM_FAULT, TRUE) + send_transport_active_signal() + +/** + * Tram malfunction random event. Set comm error, increase tram lethality. + */ +/datum/transport_controller/linear/tram/proc/start_malf_event() + set_status_code(COMM_ERROR, TRUE) + SEND_TRANSPORT_SIGNAL(COMSIG_COMMS_STATUS, src, FALSE) + paired_cabinet.generate_repair_signals() + collision_lethality = 1.25 + log_transport("TC: [specific_transport_id] starting Tram Malfunction event.") + +/** + * Remove effects of tram malfunction event. + * + * If engineers didn't already repair the tram by the end of the event, + * automagically reset it remotely. + */ +/datum/transport_controller/linear/tram/proc/end_malf_event() + if(!(controller_status & COMM_ERROR)) + return + set_status_code(COMM_ERROR, FALSE) + paired_cabinet.clear_repair_signals() + collision_lethality = initial(collision_lethality) + SEND_TRANSPORT_SIGNAL(COMSIG_COMMS_STATUS, src, TRUE) + log_transport("TC: [specific_transport_id] ending Tram Malfunction event.") + +/datum/transport_controller/linear/tram/proc/register_collision(points = 1) + tram_registration.collisions += points + SEND_TRANSPORT_SIGNAL(COMSIG_TRAM_COLLISION, SSpersistence.tram_hits_this_round) + +/datum/transport_controller/linear/tram/proc/power_lost() + set_operational(FALSE) + log_transport("TC: [specific_transport_id] power lost.") + send_transport_active_signal() + +/datum/transport_controller/linear/tram/proc/power_restored() + set_operational(TRUE) + log_transport("TC: [specific_transport_id] power restored.") + cycle_doors(CYCLE_OPEN) + send_transport_active_signal() + +/datum/transport_controller/linear/tram/proc/set_operational(new_value) + if(controller_operational != new_value) + controller_operational = new_value + +/** + * Returns the closest tram nav beacon to an atom + * + * Creates a list of nav beacons in the requested direction + * and returns the closest to be passed to the industrial_lift + * + * Arguments: source: the starting point to find a beacon + * travel_dir: travel direction in tram form, INBOUND or OUTBOUND + * beacon_type: what list of beacons we pull from + */ +/datum/transport_controller/linear/tram/proc/closest_nav_in_travel_dir(atom/origin, travel_dir, beacon_type) + if(!istype(origin) || !origin.z) + return FALSE + + var/list/obj/effect/landmark/transport/nav_beacon/tram/inbound_candidates = list() + var/list/obj/effect/landmark/transport/nav_beacon/tram/outbound_candidates = list() + + for(var/obj/effect/landmark/transport/nav_beacon/tram/candidate_beacon in SStransport.nav_beacons[beacon_type]) + if(candidate_beacon.z != origin.z || candidate_beacon.z != nav_beacon.z) + continue + + switch(nav_beacon.dir) + if(EAST, WEST) + if(candidate_beacon.y != nav_beacon.y) + continue + else if(candidate_beacon.x < nav_beacon.x) + inbound_candidates += candidate_beacon + else + outbound_candidates += candidate_beacon + if(NORTH, SOUTH) + if(candidate_beacon.x != nav_beacon.x) + continue + else if(candidate_beacon.y < nav_beacon.y) + inbound_candidates += candidate_beacon + else + outbound_candidates += candidate_beacon + + switch(travel_dir) + if(INBOUND) + var/obj/effect/landmark/transport/nav_beacon/tram/nav/selected = get_closest_atom(/obj/effect/landmark/transport/nav_beacon/tram, inbound_candidates, origin) + if(selected) + return selected + stack_trace("No inbound beacon candidate found for [origin]. Cancelling dispatch.") + return FALSE + + if(OUTBOUND) + var/obj/effect/landmark/transport/nav_beacon/tram/nav/selected = get_closest_atom(/obj/effect/landmark/transport/nav_beacon/tram, outbound_candidates, origin) + if(selected) + return selected + stack_trace("No outbound beacon candidate found for [origin]. Cancelling dispatch.") + return FALSE + + else + stack_trace("Tram receieved invalid travel direction [travel_dir]. Cancelling dispatch.") + + return FALSE + +/** + * Moves the tram when hit by an immovable rod + * + * Tells the individual tram parts where to actually go and has an extra safety checks + * incase multiple inputs get through, preventing conflicting directions and the tram + * literally ripping itself apart. all of the actual movement is handled by SStramprocess + * + * Arguments: collided_rod (the immovable rod that hit the tram) + * Return: push_destination (the landmark /obj/effect/landmark/tram/nav that the tram is being pushed to due to the rod's trajectory) + */ +/datum/transport_controller/linear/tram/proc/rod_collision(obj/effect/immovablerod/collided_rod) + log_transport("TC: [specific_transport_id] hit an immovable rod.") + if(!controller_operational) + return + var/rod_velocity_sign + // Determine inbound or outbound + if(collided_rod.dir & (NORTH|SOUTH)) + rod_velocity_sign = collided_rod.dir & NORTH ? OUTBOUND : INBOUND + else + rod_velocity_sign = collided_rod.dir & EAST ? OUTBOUND : INBOUND + + var/obj/effect/landmark/transport/nav_beacon/tram/nav/push_destination = closest_nav_in_travel_dir(origin = nav_beacon, travel_dir = rod_velocity_sign, beacon_type = IMMOVABLE_ROD_DESTINATIONS) + if(!push_destination) + return + travel_direction = get_dir(nav_beacon, push_destination) + travel_remaining = get_dist(nav_beacon, push_destination) + travel_trip_length = travel_remaining + destination_platform = push_destination + log_transport("TC: [specific_transport_id] collided at ([nav_beacon.x], [nav_beacon.y], [nav_beacon.z]) towards [push_destination] ([push_destination.x], [push_destination.y], [push_destination.z]) Dir [travel_direction] Dist [travel_remaining].") + // Don't bother processing crossing signals, where this tram's going there are no signals + //for(var/obj/machinery/transport/crossing_signal/xing as anything in SStransport.crossing_signals) + // xing.temp_malfunction() + priority_announce("In a turn of rather peculiar events, it appears that [GLOB.station_name] has struck an immovable rod. (Don't ask us where it came from.) This has led to a station brakes failure on one of the tram platforms.\n\n\ + Our diligent team of engineers have been informed and they're rushing over - although not quite at the speed of our recently flying tram.\n\n\ + So while we all look in awe at the universe's mysterious sense of humour, please stand clear of the tracks and remember to stand behind the yellow line.", "Braking News") + set_active(TRUE) + set_status_code(CONTROLS_LOCKED, TRUE) + dispatch_transport(destination_platform = push_destination) + return push_destination + + +/datum/transport_controller/linear/tram/slow //for some reason speed is set to initial() in the code but if i touched it it would probably break so + speed_limiter = 3 + base_speed_limiter = 3 + +/** + * The physical cabinet on the tram. Acts as the interface between players and the controller datum. + */ +/obj/machinery/transport/tram_controller + name = "tram controller" + desc = "Makes the tram go, or something. Holds the tram's electronics, controls, and maintenance panel. A sticker above the card reader says 'Engineering access only.'" + icon = 'icons/obj/tram/tram_controllers.dmi' + icon_state = "controller-panel" + anchored = TRUE + density = FALSE + armor_type = /datum/armor/transport_module + resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + max_integrity = 750 + integrity_failure = 0.25 + layer = SIGN_LAYER + req_access = list(ACCESS_TCOMMS) + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.25 + power_channel = AREA_USAGE_ENVIRON + var/datum/transport_controller/linear/tram/controller_datum + /// If the cover is open + var/cover_open = FALSE + /// If the cover is locked + var/cover_locked = TRUE + COOLDOWN_DECLARE(manual_command_cooldown) + +/obj/machinery/transport/tram_controller/hilbert + configured_transport_id = HILBERT_LINE_1 + obj_flags = parent_type::obj_flags | NO_DECONSTRUCTION + +/obj/machinery/transport/tram_controller/Initialize(mapload) + . = ..() + register_context() + if(!id_tag) + id_tag = assign_random_name() + return INITIALIZE_HINT_LATELOAD + +/** + * Mapped or built tram cabinet isn't located on a transport module. + */ +/obj/machinery/transport/tram_controller/LateInitialize(mapload) + . = ..() + SStransport.hello(src, name, id_tag) + find_controller() + update_appearance() + +/obj/machinery/transport/tram_controller/atom_break() + set_machine_stat(machine_stat | BROKEN) + ..() + +/obj/machinery/transport/tram_controller/add_context(atom/source, list/context, obj/item/held_item, mob/user) + if(held_item?.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_RMB] = panel_open ? "close panel" : "open panel" + + if(!held_item) + context[SCREENTIP_CONTEXT_LMB] = cover_open ? "access controls" : "open cabinet" + context[SCREENTIP_CONTEXT_RMB] = cover_open ? "close cabinet" : "toggle lock" + + + if(panel_open) + if(held_item?.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_RMB] = "unscrew cabinet" + if(malfunctioning || methods_to_fix.len) + context[SCREENTIP_CONTEXT_LMB] = "repair electronics" + + if(held_item?.tool_behaviour == TOOL_WELDER) + context[SCREENTIP_CONTEXT_LMB] = "repair frame" + + if(istype(held_item, /obj/item/card/emag) && !(obj_flags & EMAGGED)) + context[SCREENTIP_CONTEXT_LMB] = "emag controller" + + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/transport/tram_controller/update_current_power_usage() + return // We get power from area rectifiers + +/obj/machinery/transport/tram_controller/examine(mob/user) + . = ..() + . += span_notice("The door appears to be [cover_locked ? "locked. Swipe an ID card to unlock" : "unlocked. Swipe an ID card to lock"].") + if(panel_open) + . += span_notice("It is secured to the tram wall with [EXAMINE_HINT("bolts.")]") + . += span_notice("The maintenance panel can be closed with a [EXAMINE_HINT("screwdriver.")]") + else + . += span_notice("The maintenance panel can be opened with a [EXAMINE_HINT("screwdriver.")]") + + if(cover_open) + . += span_notice("The [EXAMINE_HINT("yellow reset button")] resets the tram controller if a problem occurs or needs to be restarted.") + . += span_notice("The [EXAMINE_HINT("red stop button")] immediately stops the tram, requiring a reset afterwards.") + . += span_notice("The cabinet can be closed with a [EXAMINE_HINT("Right-click.")]") + else + . += span_notice("The cabinet can be opened with a [EXAMINE_HINT("Left-click.")]") + + +/obj/machinery/transport/tram_controller/attackby(obj/item/weapon, mob/living/user, params) + if(user.combat_mode || cover_open) + return ..() + + var/obj/item/card/id/id_card = user.get_id_in_hand() + if(!isnull(id_card)) + try_toggle_lock(user, id_card) + return + + return ..() + +/obj/machinery/transport/tram_controller/attack_hand(mob/living/user, params) + . = ..() + if(cover_open) + return + + if(cover_locked) + var/obj/item/card/id/id_card = user.get_idcard(TRUE) + if(isnull(id_card)) + balloon_alert(user, "access denied!") + return + + try_toggle_lock(user, id_card) + return + + toggle_door() + +/obj/machinery/transport/tram_controller/attack_hand_secondary(mob/living/user, params) + . = ..() + + if(!cover_open) + var/obj/item/card/id/id_card = user.get_idcard(TRUE) + if(isnull(id_card)) + balloon_alert(user, "access denied!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + try_toggle_lock(user, id_card) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + toggle_door() + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/transport/tram_controller/proc/toggle_door() + if(!cover_open) + playsound(loc, 'sound/machines/closet_open.ogg', 35, TRUE, -3) + else + playsound(loc, 'sound/machines/closet_close.ogg', 50, TRUE, -3) + cover_open = !cover_open + update_appearance() + +/obj/machinery/transport/tram_controller/proc/try_toggle_lock(mob/living/user, obj/item/card/id_card, params) + if(isnull(id_card)) + id_card = user.get_idcard(TRUE) + if(obj_flags & EMAGGED) + balloon_alert(user, "access controller damaged!") + return FALSE + + if(check_access(id_card)) + cover_locked = !cover_locked + balloon_alert(user, "controls [cover_locked ? "locked" : "unlocked"]") + update_appearance() + return TRUE + + balloon_alert(user, "access denied!") + return FALSE + +/obj/machinery/transport/tram_controller/wrench_act_secondary(mob/living/user, obj/item/tool) + . = ..() + if(panel_open && cover_open) + balloon_alert(user, "unsecuring...") + tool.play_tool_sound(src) + if(!tool.use_tool(src, user, 6 SECONDS)) + return + playsound(loc, 'sound/items/deconstruct.ogg', 50, vary = TRUE) + balloon_alert(user, "unsecured") + deconstruct() + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/transport/tram_controller/screwdriver_act_secondary(mob/living/user, obj/item/tool) + . = ..() + if(!cover_open) + return + + tool.play_tool_sound(src) + panel_open = !panel_open + balloon_alert(user, "[panel_open ? "mounting bolts exposed" : "mounting bolts hidden"]") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/transport/tram_controller/on_deconstruction(disassembled) + var/turf/drop_location = find_obstruction_free_location(1, src) + + if(disassembled) + new /obj/item/wallframe/tram/controller(drop_location) + else + new /obj/item/stack/sheet/mineral/titanium(drop_location, 2) + new /obj/item/stack/sheet/iron(drop_location, 1) + +/** + * Update the blinky lights based on the controller status, allowing to quickly check without opening up the cabinet. + */ +/obj/machinery/transport/tram_controller/update_overlays() + . = ..() + + if(!cover_open) + . += mutable_appearance(icon, "controller-closed") + if(cover_locked) + . += mutable_appearance(icon, "controller-locked") + + else + var/mutable_appearance/controller_door = mutable_appearance(icon, "controller-open") + controller_door.pixel_w = -3 + . += controller_door + + if(machine_stat & NOPOWER) + . += mutable_appearance(icon, "estop") + . += emissive_appearance(icon, "estop", src, alpha = src.alpha) + return + + . += mutable_appearance(icon, "power") + . += emissive_appearance(icon, "power", src, alpha = src.alpha) + + if(!controller_datum) + . += mutable_appearance(icon, "fatal") + . += emissive_appearance(icon, "fatal", src, alpha = src.alpha) + return + + if(controller_datum.controller_status & EMERGENCY_STOP) + . += mutable_appearance(icon, "estop") + . += emissive_appearance(icon, "estop", src, alpha = src.alpha) + return + + if(!(controller_datum.controller_status & DOORS_READY)) + . += mutable_appearance(icon, "doors") + . += emissive_appearance(icon, "doors", src, alpha = src.alpha) + + if(controller_datum.controller_active) + . += mutable_appearance(icon, "active") + . += emissive_appearance(icon, "active", src, alpha = src.alpha) + + if(controller_datum.controller_status & SYSTEM_FAULT) + . += mutable_appearance(icon, "fault") + . += emissive_appearance(icon, "fault", src, alpha = src.alpha) + + else if(controller_datum.controller_status & COMM_ERROR) + . += mutable_appearance(icon, "comms") + . += emissive_appearance(icon, "comms", src, alpha = src.alpha) + + else + . += mutable_appearance(icon, "normal") + . += emissive_appearance(icon, "normal", src, alpha = src.alpha) + +/** + * Find the controller associated with the transport module the cabinet is sitting on. + */ +/obj/machinery/transport/tram_controller/proc/find_controller() + var/obj/structure/transport/linear/tram/tram_structure = locate() in src.loc + if(!tram_structure) + return + + controller_datum = tram_structure.transport_controller_datum + if(!controller_datum) + return + + controller_datum.notify_controller(src) + RegisterSignal(SStransport, COMSIG_TRANSPORT_ACTIVE, PROC_REF(sync_controller)) + +/obj/machinery/transport/tram_controller/hilbert/find_controller() + for(var/datum/transport_controller/linear/tram/tram as anything in SStransport.transports_by_type[TRANSPORT_TYPE_TRAM]) + if(tram.specific_transport_id == configured_transport_id) + controller_datum = tram + break + + if(!controller_datum) + return + + controller_datum.notify_controller(src) + RegisterSignal(SStransport, COMSIG_TRANSPORT_ACTIVE, PROC_REF(sync_controller)) + +/** + * Since the machinery obj is a dumb terminal for the controller datum, sync the display with the status bitfield of the tram + */ +/obj/machinery/transport/tram_controller/proc/sync_controller(source, controller, controller_status, travel_direction, destination_platform) + use_power(active_power_usage) + if(controller != controller_datum) + return + update_appearance() + +/obj/machinery/transport/tram_controller/emag_act(mob/user, obj/item/card/emag/emag_card) + if(obj_flags & EMAGGED) + balloon_alert(user, "already fried!") + return FALSE + obj_flags |= EMAGGED + cover_locked = FALSE + playsound(src, SFX_SPARKS, 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + balloon_alert(user, "access controller shorted") + return TRUE + +/** + * Check if the tram was malfunctioning due to the random event, and if so end the event on repair. + */ +/obj/machinery/transport/tram_controller/try_fix_machine(obj/machinery/transport/machine, mob/living/user, obj/item/tool) + . = ..() + + if(. == FALSE) + return + + if(!controller_datum) + return + + var/datum/round_event/tram_malfunction/malfunction_event = locate(/datum/round_event/tram_malfunction) in SSevents.running + if(malfunction_event) + malfunction_event.end() + + if(controller_datum.controller_status & COMM_ERROR) + controller_datum.set_status_code(COMM_ERROR, FALSE) + +/obj/machinery/transport/tram_controller/ui_interact(mob/user, datum/tgui/ui) + . = ..() + + if(!cover_open && !issiliconoradminghost(user) && !isobserver(user)) + return + + if(!is_operational) + return + + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TramController") + ui.open() + +/obj/machinery/transport/tram_controller/ui_data(mob/user) + var/list/data = list() + + data = list( + "transportId" = controller_datum.specific_transport_id, + "controllerActive" = controller_datum.controller_active, + "controllerOperational" = controller_datum.controller_operational, + "travelDirection" = controller_datum.travel_direction, + "destinationPlatform" = controller_datum.destination_platform, + "idlePlatform" = controller_datum.idle_platform, + "recoveryMode" = controller_datum.recovery_mode, + "currentSpeed" = controller_datum.current_speed, + "currentLoad" = controller_datum.current_load, + "statusSF" = controller_datum.controller_status & SYSTEM_FAULT, + "statusCE" = controller_datum.controller_status & COMM_ERROR, + "statusES" = controller_datum.controller_status & EMERGENCY_STOP, + "statusPD" = controller_datum.controller_status & PRE_DEPARTURE, + "statusDR" = controller_datum.controller_status & DOORS_READY, + "statusCL" = controller_datum.controller_status & CONTROLS_LOCKED, + "statusBS" = controller_datum.controller_status & BYPASS_SENSORS, + ) + + return data + +/obj/machinery/transport/tram_controller/ui_static_data(mob/user) + var/list/data = list() + data["destinations"] = SStransport.detailed_destination_list(controller_datum.specific_transport_id) + + return data + +/obj/machinery/transport/tram_controller/ui_act(action, params) + . = ..() + if (.) + return + + if(!COOLDOWN_FINISHED(src, manual_command_cooldown)) + return + + switch(action) + + if("dispatch") + var/obj/effect/landmark/transport/nav_beacon/tram/platform/destination_platform + for (var/obj/effect/landmark/transport/nav_beacon/tram/platform/destination as anything in SStransport.nav_beacons[controller_datum.specific_transport_id]) + if(destination.name == params["tripDestination"]) + destination_platform = destination + break + + if(!destination_platform) + return FALSE + + SEND_SIGNAL(src, COMSIG_TRANSPORT_REQUEST, controller_datum.specific_transport_id, destination_platform.platform_code) + update_appearance() + + if("estop") + controller_datum.estop() + + if("reset") + controller_datum.reset_position() + + if("dclose") + controller_datum.cycle_doors(CYCLE_CLOSED) + + if("dopen") + controller_datum.cycle_doors(CYCLE_OPEN) + + if("togglesensors") + if(controller_datum.controller_status & BYPASS_SENSORS) + controller_datum.set_status_code(BYPASS_SENSORS, FALSE) + else + controller_datum.set_status_code(BYPASS_SENSORS, TRUE) + + COOLDOWN_START(src, manual_command_cooldown, 2 SECONDS) + +/obj/item/wallframe/tram/controller + name = "tram controller cabinet" + desc = "A box that contains the equipment to control a tram. Just secure to the tram wall." + icon = 'icons/obj/tram/tram_controllers.dmi' + icon_state = "controller-panel" + custom_materials = list(/datum/material/titanium = SHEET_MATERIAL_AMOUNT * 4, /datum/material/iron = SHEET_MATERIAL_AMOUNT * 2, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 2) + result_path = /obj/machinery/transport/tram_controller + pixel_shift = 32 diff --git a/code/modules/transport/tram/tram_controls.dm b/code/modules/transport/tram/tram_controls.dm new file mode 100644 index 0000000000000..db8fe767155d2 --- /dev/null +++ b/code/modules/transport/tram/tram_controls.dm @@ -0,0 +1,253 @@ +/obj/machinery/computer/tram_controls + name = "tram controls" + desc = "An interface for the tram that lets you tell the tram where to go and hopefully it makes it there. I'm here to describe the controls to you, not to inspire confidence." + icon_state = "tram_controls" + base_icon_state = "tram" + icon_screen = TRAMSTATION_LINE_1 + icon_keyboard = null + layer = SIGN_LAYER + density = FALSE + max_integrity = 400 + integrity_failure = 0.1 + power_channel = AREA_USAGE_ENVIRON + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.25 + armor_type = /datum/armor/transport_machinery + circuit = /obj/item/circuitboard/computer/tram_controls + light_color = COLOR_BLUE_LIGHT + light_range = 0 //we dont want to spam SSlighting with source updates every movement + brightness_on = 0 + /// What sign face prefixes we have icons for + var/static/list/available_faces = list() + /// The sign face we're displaying + var/sign_face + /// Weakref to the tram piece we control + var/datum/weakref/transport_ref + /// The ID of the tram we're controlling + var/specific_transport_id = TRAMSTATION_LINE_1 + /// If the sign is adjusted for split type tram windows + var/split_mode = FALSE + +/obj/machinery/computer/tram_controls/split + circuit = /obj/item/circuitboard/computer/tram_controls/split + split_mode = TRUE + +/obj/machinery/computer/tram_controls/split/directional/north + dir = SOUTH + pixel_x = -8 + pixel_y = 32 + +/obj/machinery/computer/tram_controls/split/directional/south + dir = NORTH + pixel_x = 8 + pixel_y = -32 + +/obj/machinery/computer/tram_controls/split/directional/east + dir = WEST + pixel_x = 32 + +/obj/machinery/computer/tram_controls/split/directional/west + dir = EAST + pixel_x = -32 + +/obj/machinery/computer/tram_controls/Initialize(mapload) + . = ..() + var/obj/item/circuitboard/computer/tram_controls/my_circuit = circuit + split_mode = my_circuit.split_mode + +/obj/machinery/computer/tram_controls/LateInitialize() + . = ..() + if(!id_tag) + id_tag = assign_random_name() + SStransport.hello(src, name, id_tag) + RegisterSignal(SStransport, COMSIG_TRANSPORT_RESPONSE, PROC_REF(call_response)) + find_tram() + + var/datum/transport_controller/linear/tram/tram = transport_ref?.resolve() + if(tram) + RegisterSignal(SStransport, COMSIG_TRANSPORT_ACTIVE, PROC_REF(update_display)) + +/obj/machinery/computer/tram_controls/update_current_power_usage() + return // We get power from area rectifiers + +/** + * Finds the tram from the console + * + * Locates tram parts in the lift global list after everything is done. + */ +/obj/machinery/computer/tram_controls/proc/find_tram() + for(var/datum/transport_controller/linear/transport as anything in SStransport.transports_by_type[TRANSPORT_TYPE_TRAM]) + if(transport.specific_transport_id == specific_transport_id) + transport_ref = WEAKREF(transport) + return + +/obj/machinery/computer/tram_controls/ui_state(mob/user) + return GLOB.not_incapacitated_state + +/obj/machinery/computer/tram_controls/ui_status(mob/user,/datum/tgui/ui) + var/datum/transport_controller/linear/tram/tram = transport_ref?.resolve() + + if(tram?.controller_active) + return UI_CLOSE + if(!in_range(user, src) && !isobserver(user)) + return UI_CLOSE + return ..() + +/obj/machinery/computer/tram_controls/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TramControl", name) + ui.open() + +/obj/machinery/computer/tram_controls/ui_data(mob/user) + var/datum/transport_controller/linear/tram/tram_controller = transport_ref?.resolve() + var/list/data = list() + data["moving"] = tram_controller?.controller_active + data["broken"] = (tram_controller ? FALSE : TRUE) || (tram_controller?.paired_cabinet ? FALSE : TRUE) + var/obj/effect/landmark/transport/nav_beacon/tram/platform/current_loc = tram_controller?.idle_platform + if(current_loc) + data["tram_location"] = current_loc.name + return data + +/obj/machinery/computer/tram_controls/ui_static_data(mob/user) + var/list/data = list() + data["destinations"] = get_destinations() + return data + +/** + * Finds the destinations for the tram console gui + * + * Pulls tram landmarks from the landmark gobal list + * and uses those to show the proper icons and destination + * names for the tram console gui. + */ +/obj/machinery/computer/tram_controls/proc/get_destinations() + . = list() + for(var/obj/effect/landmark/transport/nav_beacon/tram/platform/destination as anything in SStransport.nav_beacons[specific_transport_id]) + var/list/this_destination = list() + this_destination["name"] = destination.name + this_destination["dest_icons"] = destination.tgui_icons + this_destination["id"] = destination.platform_code + . += list(this_destination) + +/obj/machinery/computer/tram_controls/ui_act(action, params) + . = ..() + if(.) + return + + switch(action) + if("send") + var/obj/effect/landmark/transport/nav_beacon/tram/platform/destination_platform + for(var/obj/effect/landmark/transport/nav_beacon/tram/platform/destination as anything in SStransport.nav_beacons[specific_transport_id]) + if(destination.platform_code == params["destination"]) + destination_platform = destination + break + + if(isnull(destination_platform)) + return FALSE + + SStransport.incoming_request(src, specific_transport_id, destination_platform.platform_code) + update_appearance() + +/obj/machinery/computer/tram_controls/proc/update_display(datum/source, datum/transport_controller/linear/tram/controller, controller_active, controller_status, travel_direction, obj/effect/landmark/transport/nav_beacon/tram/platform/destination_platform) + SIGNAL_HANDLER + + if(machine_stat & (NOPOWER|BROKEN)) + icon_screen = null + update_appearance() + return + + if(isnull(controller) || !controller.controller_operational) + icon_screen = "[base_icon_state]_broken" + update_appearance() + return + + if(isnull(destination_platform)) + icon_screen = "[specific_transport_id]" + update_appearance() + return + + if(controller.controller_status & EMERGENCY_STOP || controller.controller_status & SYSTEM_FAULT) + icon_screen = "[base_icon_state]_NIS" + update_appearance() + return + + if(controller_active) + icon_screen = "[base_icon_state]_0[travel_direction]" + update_appearance() + return + + icon_screen = "" + icon_screen += "[controller.specific_transport_id]" + icon_screen += "[destination_platform.platform_code]" + + update_appearance() + +/obj/machinery/computer/tram_controls/on_construction(mob/user) + . = ..() + var/obj/item/circuitboard/computer/tram_controls/my_circuit = circuit + split_mode = my_circuit.split_mode + if(split_mode) + switch(dir) + if(NORTH) + pixel_x = 8 + pixel_y = -32 + if(SOUTH) + pixel_x = -8 + pixel_y = 32 + if(EAST) + pixel_x = -32 + pixel_y = -8 + if(WEST) + pixel_x = 32 + pixel_y = 8 + else + switch(dir) + if(NORTH) + pixel_y = -32 + if(SOUTH) + pixel_y = 32 + if(EAST) + pixel_x = -32 + if(WEST) + pixel_x = 32 + +/obj/machinery/computer/tram_controls/update_overlays() + . = ..() + + if(isnull(icon_screen)) + return + + . += emissive_appearance(icon, icon_screen, src, alpha = src.alpha) + +/obj/machinery/computer/tram_controls/power_change() + ..() + var/datum/transport_controller/linear/tram/tram = transport_ref?.resolve() + if(isnull(tram)) + icon_screen = "[base_icon_state]_broken" + update_appearance() + return + + update_display(src, tram, tram.controller_active, tram.controller_status, tram.travel_direction, tram.destination_platform) + +/obj/machinery/computer/tram_controls/proc/call_response(controller, list/relevant, response_code, response_info) + SIGNAL_HANDLER + switch(response_code) + if(REQUEST_SUCCESS) + say("The next station is: [response_info]") + + if(REQUEST_FAIL) + if(!LAZYFIND(relevant, src)) + return + + switch(response_info) + if(NOT_IN_SERVICE) + say("The tram is not in service. Please contact the nearest engineer.") + if(INVALID_PLATFORM) + say("Configuration error. Please contact the nearest engineer.") + if(INTERNAL_ERROR) + say("Tram controller error. Please contact the nearest engineer or crew member with telecommunications access to reset the controller.") + else + return + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/computer/tram_controls, 32) diff --git a/code/modules/transport/tram/tram_displays.dm b/code/modules/transport/tram/tram_displays.dm new file mode 100644 index 0000000000000..47fe21f2ff19e --- /dev/null +++ b/code/modules/transport/tram/tram_displays.dm @@ -0,0 +1,163 @@ +/obj/machinery/transport/destination_sign + name = "destination sign" + desc = "A display to show you what direction the tram is travelling." + icon = 'icons/obj/tram/tram_display.dmi' + icon_state = "desto_blank" + base_icon_state = "desto" + use_power = NO_POWER_USE + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 1.2 + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.47 + anchored = TRUE + density = FALSE + layer = SIGN_LAYER + light_range = 0 + /// What sign face prefixes we have icons for + var/static/list/available_faces = list() + /// The sign face we're displaying + var/sign_face + var/sign_color = COLOR_DISPLAY_BLUE + +/obj/machinery/transport/destination_sign/split/north + pixel_x = -8 + +/obj/machinery/transport/destination_sign/split/south + pixel_x = 8 + +/obj/machinery/transport/destination_sign/indicator + icon = 'icons/obj/tram/tram_indicator.dmi' + icon_state = "indi_blank" + base_icon_state = "indi" + use_power = IDLE_POWER_USE + max_integrity = 50 + light_range = 2 + light_power = 0.7 + light_angle = 115 + flags_1 = NONE + +/obj/item/wallframe/indicator_display + name = "indicator display frame" + desc = "Used to build tram indicator displays, just secure to the wall." + icon_state = "indi_blank" + icon = 'icons/obj/tram/tram_indicator.dmi' + custom_materials = list(/datum/material/titanium = SHEET_MATERIAL_AMOUNT * 4, /datum/material/iron = SHEET_MATERIAL_AMOUNT * 2, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 2) + result_path = /obj/machinery/transport/destination_sign/indicator + pixel_shift = 32 + +/obj/machinery/transport/destination_sign/Initialize(mapload) + . = ..() + RegisterSignal(SStransport, COMSIG_TRANSPORT_ACTIVE, PROC_REF(update_sign)) + SStransport.displays += src + available_faces = list( + TRAMSTATION_LINE_1, + ) + set_light(l_dir = REVERSE_DIR(dir)) + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/transport/destination_sign/Destroy() + SStransport.displays -= src + . = ..() + +/obj/machinery/transport/destination_sign/indicator/setDir(newdir) + . = ..() + set_light(l_dir = REVERSE_DIR(dir)) + +/obj/machinery/transport/destination_sign/indicator/LateInitialize(mapload) + . = ..() + link_tram() + +/obj/machinery/transport/destination_sign/indicator/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(held_item?.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_RMB] = "unanchor" + if(held_item?.tool_behaviour == TOOL_WELDER) + context[SCREENTIP_CONTEXT_LMB] = "repair" + + return CONTEXTUAL_SCREENTIP_SET + + +/obj/machinery/transport/destination_sign/indicator/examine(mob/user) + . = ..() + + if(panel_open) + . += span_notice("It is secured to the tram wall with [EXAMINE_HINT("bolts.")]") + +/obj/machinery/transport/destination_sign/on_deconstruction(disassembled) + if(disassembled) + new /obj/item/wallframe/indicator_display(drop_location()) + else + new /obj/item/stack/sheet/mineral/titanium(drop_location(), 2) + new /obj/item/stack/sheet/iron(drop_location(), 1) + new /obj/item/shard(drop_location()) + new /obj/item/shard(drop_location()) + +/obj/machinery/transport/destination_sign/indicator/wrench_act_secondary(mob/living/user, obj/item/tool) + . = ..() + balloon_alert(user, "[anchored ? "un" : ""]securing...") + tool.play_tool_sound(src) + if(tool.use_tool(src, user, 6 SECONDS)) + playsound(loc, 'sound/items/deconstruct.ogg', 50, vary = TRUE) + balloon_alert(user, "[anchored ? "un" : ""]secured") + deconstruct() + return TRUE + +/obj/machinery/transport/destination_sign/proc/update_sign(datum/source, datum/transport_controller/linear/tram/controller, controller_active, controller_status, travel_direction, obj/effect/landmark/transport/nav_beacon/tram/platform/destination_platform) + SIGNAL_HANDLER + + if(machine_stat & (NOPOWER|BROKEN)) + sign_face = null + update_appearance() + return + + if(!controller || !controller.controller_operational || isnull(destination_platform)) + sign_face = "[base_icon_state]_NIS" + sign_color = COLOR_DISPLAY_RED + update_appearance() + return + + if(controller.controller_status & EMERGENCY_STOP || controller.controller_status & SYSTEM_FAULT) + sign_face = "[base_icon_state]_NIS" + sign_color = COLOR_DISPLAY_RED + update_appearance() + return + + sign_face = "" + sign_face += "[base_icon_state]_" + if(!LAZYFIND(available_faces, controller.specific_transport_id)) + sign_face += "[TRAMSTATION_LINE_1]" + else + sign_face += "[controller.specific_transport_id]" + + sign_face += "[controller_active]" + sign_face += "[destination_platform.platform_code]" + sign_face += "[travel_direction]" + sign_color = COLOR_DISPLAY_BLUE + + update_appearance() + +/obj/machinery/transport/destination_sign/update_icon_state() + . = ..() + if(isnull(sign_face)) + icon_state = "[base_icon_state]_blank" + return + else + icon_state = sign_face + +/obj/machinery/transport/destination_sign/update_overlays() + . = ..() + + if(isnull(sign_face)) + set_light(l_on = FALSE) + return + + set_light(l_on = TRUE, l_color = sign_color) + . += emissive_appearance(icon, "[sign_face]_e", src, alpha = src.alpha) + +/obj/machinery/transport/destination_sign/indicator/power_change() + ..() + var/datum/transport_controller/linear/tram/tram = transport_ref?.resolve() + if(!tram) + return + + update_sign(src, tram, tram.controller_active, tram.controller_status, tram.travel_direction, tram.destination_platform) + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/transport/destination_sign/indicator, 32) diff --git a/code/modules/transport/tram/tram_doors.dm b/code/modules/transport/tram/tram_doors.dm new file mode 100644 index 0000000000000..d836685acf11f --- /dev/null +++ b/code/modules/transport/tram/tram_doors.dm @@ -0,0 +1,230 @@ +#define TRAM_DOOR_WARNING_TIME (1.4 SECONDS) +#define TRAM_DOOR_CYCLE_TIME (0.4 SECONDS) +#define TRAM_DOOR_CRUSH_TIME (0.7 SECONDS) +#define TRAM_DOOR_RECYCLE_TIME (3 SECONDS) + +/obj/machinery/door/airlock/tram + name = "tram door" + icon = 'icons/obj/doors/airlocks/tram/tram.dmi' + overlays_file = 'icons/obj/doors/airlocks/tram/tram-overlays.dmi' + multi_tile = TRUE + opacity = FALSE + assemblytype = null + airlock_material = "glass" + air_tight = TRUE + req_access = list(ACCESS_TCOMMS) + transport_linked_id = TRAMSTATION_LINE_1 + doorOpen = 'sound/machines/tramopen.ogg' + doorClose = 'sound/machines/tramclose.ogg' + autoclose = FALSE + /// Weakref to the tram we're attached + var/datum/weakref/transport_ref + var/retry_counter + var/crushing_in_progress = FALSE + bound_width = 64 + +/obj/machinery/door/airlock/tram/Initialize(mapload) + . = ..() + if(!id_tag) + id_tag = assign_random_name() + +/obj/machinery/door/airlock/tram/open(forced = DEFAULT_DOOR_CHECKS) + if(operating || welded || locked || seal) + return FALSE + + if(!density) + return TRUE + + if(forced == DEFAULT_DOOR_CHECKS && (!hasPower() || wires.is_cut(WIRE_OPEN))) + return FALSE + + SEND_SIGNAL(src, COMSIG_AIRLOCK_OPEN, FALSE) + operating = TRUE + update_icon(ALL, AIRLOCK_OPENING, TRUE) + + if(forced >= BYPASS_DOOR_CHECKS) + playsound(src, 'sound/machines/airlockforced.ogg', vol = 40, vary = FALSE) + sleep(TRAM_DOOR_CYCLE_TIME) + else + playsound(src, doorOpen, vol = 40, vary = FALSE) + sleep(TRAM_DOOR_WARNING_TIME) + + set_density(FALSE) + if(!isnull(filler)) + filler.set_density(FALSE) + update_freelook_sight() + flags_1 &= ~PREVENT_CLICK_UNDER_1 + air_update_turf(TRUE, FALSE) + sleep(TRAM_DOOR_CYCLE_TIME) + layer = OPEN_DOOR_LAYER + update_icon(ALL, AIRLOCK_OPEN, TRUE) + operating = FALSE + + return TRUE + +/obj/machinery/door/airlock/tram/close(forced = DEFAULT_DOOR_CHECKS, force_crush = FALSE) + retry_counter++ + if(retry_counter >= 4 || force_crush || forced == BYPASS_DOOR_CHECKS) + try_to_close(forced = BYPASS_DOOR_CHECKS) + return + + if(retry_counter == 1) + playsound(src, 'sound/machines/chime.ogg', 40, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + + addtimer(CALLBACK(src, PROC_REF(verify_status)), TRAM_DOOR_RECYCLE_TIME) + try_to_close() + +/** + * Perform a close attempt and report TRUE/FALSE if it worked + * + * Arguments: + * * rapid - boolean: if TRUE will skip safety checks and crush whatever is in the way + */ +/obj/machinery/door/airlock/tram/proc/try_to_close(forced = DEFAULT_DOOR_CHECKS) + if(operating || welded || locked || seal) + return FALSE + if(density) + return TRUE + crushing_in_progress = TRUE + var/hungry_door = (forced == BYPASS_DOOR_CHECKS || !safe) + if((obj_flags & EMAGGED) || !safe) + do_sparks(3, TRUE, src) + playsound(src, SFX_SPARKS, vol = 75, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + use_power(50) + playsound(src, doorClose, vol = 40, vary = FALSE) + operating = TRUE + layer = CLOSED_DOOR_LAYER + update_icon(ALL, AIRLOCK_CLOSING, 1) + sleep(TRAM_DOOR_WARNING_TIME) + if(!hungry_door) + for(var/turf/checked_turf in locs) + for(var/atom/movable/blocker in checked_turf) + if(blocker.density && blocker != src) //something is blocking the door + say("Please stand clear of the doors!") + playsound(src, 'sound/machines/buzz-sigh.ogg', 60, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + layer = OPEN_DOOR_LAYER + update_icon(ALL, AIRLOCK_OPEN, 1) + operating = FALSE + return FALSE + SEND_SIGNAL(src, COMSIG_AIRLOCK_CLOSE) + sleep(TRAM_DOOR_CRUSH_TIME) + set_density(TRUE) + if(!isnull(filler)) + filler.set_density(TRUE) + update_freelook_sight() + flags_1 |= PREVENT_CLICK_UNDER_1 + air_update_turf(TRUE, TRUE) + crush() + crushing_in_progress = FALSE + sleep(TRAM_DOOR_CYCLE_TIME) + update_icon(ALL, AIRLOCK_CLOSED, 1) + operating = FALSE + retry_counter = 0 + return TRUE + +/** + * Crush the jerk holding up the tram from moving + * + * Tram doors need their own crush proc because the normal one + * leaves you stunned far too long, leading to the doors crushing + * you over and over, no escape! + * + * While funny to watch, not ideal for the player. + */ +/obj/machinery/door/airlock/tram/crush() + for(var/turf/checked_turf in locs) + for(var/mob/living/future_pancake in checked_turf) + future_pancake.visible_message(span_warning("[src] beeps angrily and closes on [future_pancake]!"), span_userdanger("[src] beeps angrily and closes on you!")) + SEND_SIGNAL(future_pancake, COMSIG_LIVING_DOORCRUSHED, src) + if(ishuman(future_pancake) || ismonkey(future_pancake)) + future_pancake.emote("scream") + future_pancake.adjustBruteLoss(DOOR_CRUSH_DAMAGE * 2) + future_pancake.Paralyze(2 SECONDS) + + else //for simple_animals & borgs + future_pancake.adjustBruteLoss(DOOR_CRUSH_DAMAGE * 2) + var/turf/location = get_turf(src) + //add_blood doesn't work for borgs/xenos, but add_blood_floor does. + future_pancake.add_splatter_floor(location) + + log_combat(src, future_pancake, "crushed") + + for(var/obj/vehicle/sealed/mecha/mech in checked_turf) // Your fancy metal won't save you here! + mech.take_damage(DOOR_CRUSH_DAMAGE) + log_combat(src, mech, "crushed") + +/** + * Checks if the door close action was successful. Retries if it failed + * + * If some jerk is blocking the doors, they've had enough warning by attempt 3, + * take a chunk of skin, people have places to be! + */ +/obj/machinery/door/airlock/tram/proc/verify_status() + if(airlock_state == AIRLOCK_CLOSED) + return + + if(retry_counter < 3) + close() + return + + playsound(src, 'sound/machines/buzz-two.ogg', 60, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + say("YOU'RE HOLDING UP THE TRAM, ASSHOLE!") + close(forced = BYPASS_DOOR_CHECKS) + +/** + * Set the weakref for the tram we're attached to + */ +/obj/machinery/door/airlock/tram/proc/find_tram() + for(var/datum/transport_controller/linear/tram/tram as anything in SStransport.transports_by_type[TRANSPORT_TYPE_TRAM]) + if(tram.specific_transport_id == transport_linked_id) + transport_ref = WEAKREF(tram) + +/obj/machinery/door/airlock/tram/Initialize(mapload, set_dir, unres_sides) + . = ..() + RemoveElement(/datum/element/atmos_sensitive, mapload) + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/door/airlock/tram/LateInitialize(mapload) + . = ..() + INVOKE_ASYNC(src, PROC_REF(open)) + SStransport.doors += src + find_tram() + +/obj/machinery/door/airlock/tram/Destroy() + SStransport.doors -= src + return ..() + +/** + * Tram doors can be opened with hands when unpowered + */ +/obj/machinery/door/airlock/tram/examine(mob/user) + . = ..() + . += span_notice("It has an emergency mechanism to open using [EXAMINE_HINT("just your hands")] in the event of an emergency.") + +/** + * Tram doors can be opened with hands when unpowered + */ +/obj/machinery/door/airlock/tram/try_safety_unlock(mob/user) + if(!hasPower() && density) + balloon_alert(user, "pulling emergency exit...") + if(do_after(user, 4 SECONDS, target = src)) + try_to_crowbar(null, user, TRUE) + return TRUE + +/** + * If you pry (bump) the doors open midtravel, open quickly so you can jump out and make a daring escape. + */ +/obj/machinery/door/airlock/tram/bumpopen(mob/user, forced = BYPASS_DOOR_CHECKS) + if(operating || !density) + return + var/datum/transport_controller/linear/tram/tram_part = transport_ref?.resolve() + add_fingerprint(user) + if((tram_part.travel_remaining < DEFAULT_TRAM_LENGTH || tram_part.travel_remaining > tram_part.travel_trip_length - DEFAULT_TRAM_LENGTH) && tram_part.controller_active) + return // we're already animating, don't reset that + open(forced = BYPASS_DOOR_CHECKS) + return + +#undef TRAM_DOOR_WARNING_TIME +#undef TRAM_DOOR_CYCLE_TIME +#undef TRAM_DOOR_CRUSH_TIME +#undef TRAM_DOOR_RECYCLE_TIME diff --git a/code/modules/transport/tram/tram_floors.dm b/code/modules/transport/tram/tram_floors.dm new file mode 100644 index 0000000000000..1e1fad836c3b2 --- /dev/null +++ b/code/modules/transport/tram/tram_floors.dm @@ -0,0 +1,325 @@ +/turf/open/floor/noslip/tram + name = "high-traction tram platform" + icon = 'icons/turf/tram.dmi' + icon_state = "noslip_tram" + base_icon_state = "noslip_tram" + floor_tile = /obj/item/stack/tile/noslip/tram + +/turf/open/floor/tram + name = "tram guideway" + icon = 'icons/turf/tram.dmi' + icon_state = "tram_platform" + base_icon_state = "tram_platform" + floor_tile = /obj/item/stack/tile/tram + footstep = FOOTSTEP_CATWALK + barefootstep = FOOTSTEP_HARD_BAREFOOT + clawfootstep = FOOTSTEP_HARD_CLAW + heavyfootstep = FOOTSTEP_GENERIC_HEAVY + tiled_dirt = FALSE + rcd_proof = TRUE + +/turf/open/floor/tram/examine(mob/user) + . += ..() + . += span_notice("The reinforcement bolts are [EXAMINE_HINT("wrenched")] firmly in place. Use a [EXAMINE_HINT("wrench")] to remove the plate.") + +/turf/open/floor/tram/attackby(obj/item/object, mob/living/user, params) + . = ..() + if(istype(object, /obj/item/stack/thermoplastic)) + build_with_transport_tiles(object, user) + else if(istype(object, /obj/item/stack/sheet/mineral/titanium)) + build_with_titanium(object, user) + +/turf/open/floor/tram/make_plating(force = FALSE) + if(force) + return ..() + return //unplateable + +/turf/open/floor/tram/try_replace_tile(obj/item/stack/tile/replacement_tile, mob/user, params) + return + +/turf/open/floor/tram/crowbar_act(mob/living/user, obj/item/item) + return + +/turf/open/floor/tram/wrench_act(mob/living/user, obj/item/item) + ..() + to_chat(user, span_notice("You begin removing the plate...")) + if(item.use_tool(src, user, 30, volume=80)) + if(!istype(src, /turf/open/floor/tram)) + return TRUE + if(floor_tile) + new floor_tile(src, 2) + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) + return TRUE + +/turf/open/floor/tram/ex_act(severity, target) + if(target == src) + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) + return TRUE + if(severity < EXPLODE_DEVASTATE && is_shielded()) + return FALSE + + switch(severity) + if(EXPLODE_DEVASTATE) + if(prob(80)) + if(!ispath(baseturf_at_depth(2), /turf/open/floor)) + attempt_lattice_replacement() + else + ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR) + else + break_tile() + if(EXPLODE_HEAVY) + if(prob(30)) + if(!ispath(baseturf_at_depth(2), /turf/open/floor)) + attempt_lattice_replacement() + else + ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR) + else + break_tile() + if(EXPLODE_LIGHT) + if(prob(50)) + break_tile() + + return TRUE + +/turf/open/floor/tram/broken_states() + return list("tram_platform-damaged1","tram_platform-damaged2") + +/turf/open/floor/tram/tram_platform/burnt_states() + return list("tram_platform-scorched1","tram_platform-scorched2") + +/turf/open/floor/tram/plate + name = "linear induction plate" + desc = "The linear induction plate that powers the tram." + icon = 'icons/turf/tram.dmi' + icon_state = "tram_plate" + base_icon_state = "tram_plate" + flags_1 = NONE + +/turf/open/floor/tram/plate/broken_states() + return list("tram_plate-damaged1","tram_plate-damaged2") + +/turf/open/floor/tram/plate/burnt_states() + return list("tram_plate-scorched1","tram_plate-scorched2") + +/turf/open/floor/tram/plate/energized + desc = "The linear induction plate that powers the tram. It is currently energized." + /// Inbound station + var/inbound + /// Outbound station + var/outbound + /// Transport ID of the tram + var/specific_transport_id = TRAMSTATION_LINE_1 + +/turf/open/floor/tram/plate/energized/Initialize(mapload) + . = ..() + AddComponent(/datum/component/energized, inbound, outbound, specific_transport_id) + +/turf/open/floor/tram/plate/energized/examine(mob/user) + . = ..() + if(broken || burnt) + . += span_danger("It looks damaged and the electrical components exposed!") + . += span_notice("The plate can be repaired using a [EXAMINE_HINT("titanium sheet")].") + +/turf/open/floor/tram/plate/energized/broken_states() + return list("energized_plate_damaged") + +/turf/open/floor/tram/plate/energized/burnt_states() + return list("energized_plate_damaged") + +/turf/open/floor/tram/plate/energized/attackby(obj/item/attacking_item, mob/living/user, params) + if((broken || burnt) && istype(attacking_item, /obj/item/stack/sheet/mineral/titanium)) + if(attacking_item.use(1)) + broken = FALSE + update_appearance() + balloon_alert(user, "plate replaced") + return + return ..() + +/turf/open/floor/tram/plate/energized/broken + broken = TRUE + +// Resetting the tram contents to its original state needs the turf to be there +/turf/open/indestructible/tram + name = "tram guideway" + icon = 'icons/turf/tram.dmi' + icon_state = "tram_platform" + base_icon_state = "tram_platform" + footstep = FOOTSTEP_CATWALK + barefootstep = FOOTSTEP_HARD_BAREFOOT + clawfootstep = FOOTSTEP_HARD_CLAW + heavyfootstep = FOOTSTEP_GENERIC_HEAVY + +/turf/open/indestructible/tram/attackby(obj/item/object, mob/living/user, params) + . = ..() + if(istype(object, /obj/item/stack/thermoplastic)) + build_with_transport_tiles(object, user) + else if(istype(object, /obj/item/stack/sheet/mineral/titanium)) + build_with_titanium(object, user) + +/turf/open/indestructible/tram/plate + name = "linear induction plate" + desc = "The linear induction plate that powers the tram." + icon_state = "tram_plate" + base_icon_state = "tram_plate" + flags_1 = NONE + +/turf/open/floor/glass/reinforced/tram/Initialize(mapload) + . = ..() + RemoveElement(/datum/element/atmos_sensitive, mapload) + +/turf/open/floor/glass/reinforced/tram + name = "tram bridge" + desc = "It shakes a bit when you step, but lets you cross between sides quickly!" + +/obj/structure/thermoplastic + name = "tram floor" + desc = "A lightweight thermoplastic flooring." + icon = 'icons/turf/tram.dmi' + icon_state = "tram_dark" + base_icon_state = "tram_dark" + density = FALSE + anchored = TRUE + max_integrity = 150 + integrity_failure = 0.75 + armor_type = /datum/armor/tram_floor + layer = TRAM_FLOOR_LAYER + plane = FLOOR_PLANE + obj_flags = BLOCK_Z_OUT_DOWN | BLOCK_Z_OUT_UP + appearance_flags = PIXEL_SCALE|KEEP_TOGETHER + var/secured = TRUE + var/floor_tile = /obj/item/stack/thermoplastic + var/mutable_appearance/damage_overlay + +/datum/armor/tram_floor + melee = 40 + bullet = 10 + laser = 10 + bomb = 45 + fire = 90 + acid = 100 + +/obj/structure/thermoplastic/light + icon_state = "tram_light" + base_icon_state = "tram_light" + floor_tile = /obj/item/stack/thermoplastic/light + +/obj/structure/thermoplastic/examine(mob/user) + . = ..() + + if(secured) + . += span_notice("It is secured with a set of [EXAMINE_HINT("screws.")] To remove tile use a [EXAMINE_HINT("screwdriver.")]") + else + . += span_notice("You can [EXAMINE_HINT("crowbar")] to remove the tile.") + . += span_notice("It can be re-secured using a [EXAMINE_HINT("screwdriver.")]") + +/obj/structure/thermoplastic/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0) + . = ..() + if(.) //received damage + update_appearance() + +/obj/structure/thermoplastic/update_icon_state() + . = ..() + var/ratio = atom_integrity / max_integrity + ratio = CEILING(ratio * 4, 1) * 25 + if(ratio > 75) + icon_state = base_icon_state + return + + icon_state = "[base_icon_state]_damage[ratio]" + +/obj/structure/thermoplastic/screwdriver_act_secondary(mob/living/user, obj/item/tool) + . = ..() + if(secured) + user.visible_message(span_notice("[user] begins to unscrew the tile..."), + span_notice("You begin to unscrew the tile...")) + if(tool.use_tool(src, user, 1 SECONDS, volume = 50)) + secured = FALSE + to_chat(user, span_notice("The screws come out, and a gap forms around the edge of the tile.")) + else + user.visible_message(span_notice("[user] begins to fasten the tile..."), + span_notice("You begin to fasten the tile...")) + if(tool.use_tool(src, user, 1 SECONDS, volume = 50)) + secured = TRUE + to_chat(user, span_notice("The tile is securely screwed in place.")) + + return ITEM_INTERACT_SUCCESS + +/obj/structure/thermoplastic/crowbar_act_secondary(mob/living/user, obj/item/tool) + . = ..() + if(secured) + to_chat(user, span_warning("The security screws need to be removed first!")) + return FALSE + + else + user.visible_message(span_notice("[user] wedges \the [tool] into the tile's gap in the edge and starts prying..."), + span_notice("You wedge \the [tool] into the tram panel's gap in the frame and start prying...")) + if(tool.use_tool(src, user, 1 SECONDS, volume = 50)) + to_chat(user, span_notice("The panel pops out of the frame.")) + var/obj/item/stack/thermoplastic/pulled_tile = new() + pulled_tile.update_integrity(atom_integrity) + user.put_in_hands(pulled_tile) + qdel(src) + + return ITEM_INTERACT_SUCCESS + +/obj/structure/thermoplastic/welder_act(mob/living/user, obj/item/tool) + if(atom_integrity >= max_integrity) + to_chat(user, span_warning("[src] is already in good condition!")) + return ITEM_INTERACT_SUCCESS + if(!tool.tool_start_check(user, amount = 0)) + return FALSE + to_chat(user, span_notice("You begin repairing [src]...")) + var/integrity_to_repair = max_integrity - atom_integrity + if(tool.use_tool(src, user, integrity_to_repair * 0.5, volume = 50)) + atom_integrity = max_integrity + to_chat(user, span_notice("You repair [src].")) + update_appearance() + return ITEM_INTERACT_SUCCESS + +/obj/item/stack/thermoplastic + name = "thermoplastic tram tile" + singular_name = "thermoplastic tram tile" + desc = "A high-traction floor tile. It sparkles in the light." + icon = 'icons/obj/tiles.dmi' + lefthand_file = 'icons/mob/inhands/items/tiles_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/tiles_righthand.dmi' + icon_state = "tile_textured_white_large" + inhand_icon_state = "tile-neon-glow" + color = COLOR_TRAM_BLUE + w_class = WEIGHT_CLASS_NORMAL + force = 1 + throwforce = 1 + throw_speed = 3 + throw_range = 7 + max_amount = 60 + novariants = TRUE + merge_type = /obj/item/stack/thermoplastic + var/tile_type = /obj/structure/thermoplastic + +/obj/item/stack/thermoplastic/light + color = COLOR_TRAM_LIGHT_BLUE + tile_type = /obj/structure/thermoplastic/light + +/obj/item/stack/thermoplastic/Initialize(mapload, new_amount, merge = TRUE, list/mat_override=null, mat_amt=1) + . = ..() + pixel_x = rand(-3, 3) + pixel_y = rand(-3, 3) //randomize a little + +/obj/item/stack/thermoplastic/examine(mob/user) + . = ..() + if(throwforce && !is_cyborg) //do not want to divide by zero or show the message to borgs who can't throw + var/damage_value + switch(CEILING(MAX_LIVING_HEALTH / throwforce, 1)) //throws to crit a human + if(1 to 3) + damage_value = "superb" + if(4 to 6) + damage_value = "great" + if(7 to 9) + damage_value = "good" + if(10 to 12) + damage_value = "fairly decent" + if(13 to 15) + damage_value = "mediocre" + if(!damage_value) + return + . += span_notice("Those could work as a [damage_value] throwing weapon.") diff --git a/code/modules/transport/tram/tram_machinery.dm b/code/modules/transport/tram/tram_machinery.dm new file mode 100644 index 0000000000000..7371447d08244 --- /dev/null +++ b/code/modules/transport/tram/tram_machinery.dm @@ -0,0 +1,106 @@ +/obj/item/assembly/control/transport + /// The ID of the tram we're linked to + var/specific_transport_id = TRAMSTATION_LINE_1 + /// Options to be passed with the requests to the transport subsystem + var/options = NONE + +/obj/item/assembly/control/transport/multitool_act(mob/living/user) + var/list/available_platforms = list() + for(var/obj/effect/landmark/transport/nav_beacon/tram/platform/platform as anything in SStransport.nav_beacons[specific_transport_id]) + LAZYADD(available_platforms, platform.name) + + var/selected_platform = tgui_input_list(user, "Set the platform ID", "Platform", available_platforms) + var/obj/effect/landmark/transport/nav_beacon/tram/platform/change_platform + for(var/obj/effect/landmark/transport/nav_beacon/tram/platform/destination as anything in SStransport.nav_beacons[specific_transport_id]) + if(destination.name == selected_platform) + change_platform = destination + break + + if(!change_platform || QDELETED(user) || QDELETED(src) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) + return + + if(get_dist(change_platform, src) > 15) + balloon_alert(user, "out of range!") + return + + id = change_platform.platform_code + balloon_alert(user, "platform changed") + to_chat(user, span_notice("You change the platform ID to [change_platform.name].")) + +/obj/item/assembly/control/transport/call_button + name = "tram call button" + desc = "A small device used to bring trams to you." + ///ID to link to allow us to link to one specific tram in the world + id = 0 + +/obj/item/assembly/control/transport/call_button/Initialize(mapload) + . = ..() + return INITIALIZE_HINT_LATELOAD + +/obj/item/assembly/control/transport/call_button/LateInitialize(mapload) + . = ..() + if(!id_tag) + id_tag = assign_random_name() + SStransport.hello(src, name, id_tag) + RegisterSignal(SStransport, COMSIG_TRANSPORT_RESPONSE, PROC_REF(call_response)) + +/obj/item/assembly/control/transport/proc/call_response(controller, list/relevant, response_code, response_info) + SIGNAL_HANDLER + if(!LAZYFIND(relevant, src)) + return + + switch(response_code) + if(REQUEST_SUCCESS) + say("The tram has been called to the platform.") + + if(REQUEST_FAIL) + switch(response_info) + if(BROKEN_BEYOND_REPAIR) + say("The tram has suffered a catastrophic failure. Please seek alternate modes of travel.") + if(NOT_IN_SERVICE) //tram has no power or other fault, but it's not broken forever + say("The tram is not in service due to loss of power or system problems. Please contact the nearest engineer to check power and controller.") + if(INVALID_PLATFORM) //engineer needs to fix button + say("Button configuration error. Please contact the nearest engineer.") + if(TRANSPORT_IN_USE) + say("The tram is tramversing the station, please wait.") + if(INTERNAL_ERROR) + say("Tram controller error. Please contact the nearest engineer or crew member with telecommunications access to reset the controller.") + if(NO_CALL_REQUIRED) //already here + say("The tram is already here. Please board the tram and select a destination.") + else + say("Tram controller error. Please contact the nearest engineer or crew member with telecommunications access to reset the controller.") + +/obj/item/assembly/control/transport/call_button/activate() + if(cooldown) + return + cooldown = TRUE + addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 2 SECONDS) + + // INVOKE_ASYNC(SStransport, TYPE_PROC_REF(/datum/controller/subsystem/processing/transport, call_request), src, specific_transport_id, id) + SEND_SIGNAL(src, COMSIG_TRANSPORT_REQUEST, specific_transport_id, id) + +/obj/machinery/button/transport/tram + name = "tram request" + desc = "A button for calling the tram. It has a speakerbox in it with some internals." + base_icon_state = "tram" + icon_state = "tram" + light_color = COLOR_DISPLAY_BLUE + can_alter_skin = FALSE + device_type = /obj/item/assembly/control/transport/call_button + req_access = list() + id = 0 + /// The ID of the tram we're linked to + var/specific_transport_id = TRAMSTATION_LINE_1 + +/obj/machinery/button/transport/tram/setup_device() + var/obj/item/assembly/control/transport/call_button/tram_device = device + tram_device.id = id + tram_device.specific_transport_id = specific_transport_id + return ..() + +/obj/machinery/button/transport/tram/examine(mob/user) + . = ..() + . += span_notice("There's a small inscription on the button...") + . += span_notice("THIS CALLS THE TRAM! IT DOES NOT OPERATE IT! The console on the tram tells it where to go!") + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/button/transport/tram, 32) diff --git a/code/modules/transport/tram/tram_power.dm b/code/modules/transport/tram/tram_power.dm new file mode 100644 index 0000000000000..ff0251e909052 --- /dev/null +++ b/code/modules/transport/tram/tram_power.dm @@ -0,0 +1,73 @@ +/obj/machinery/transport/power_rectifier + name = "tram power rectifier" + desc = "An electrical device that converts alternating current (AC) to direct current (DC) for powering the tram." + icon = 'icons/obj/tram/tram_controllers.dmi' + icon_state = "rectifier" + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 11.4 + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 114 + power_channel = AREA_USAGE_ENVIRON + anchored = TRUE + density = FALSE + armor_type = /datum/armor/transport_module + resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + max_integrity = 750 + pixel_y = 32 + /// The tram platform we're connected to and providing power + var/obj/effect/landmark/transport/nav_beacon/tram/platform/connected_platform + +/obj/machinery/transport/power_rectifier/Initialize(mapload) + . = ..() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/transport/power_rectifier/LateInitialize(mapload) + . = ..() + RegisterSignal(SStransport, COMSIG_TRANSPORT_ACTIVE, PROC_REF(power_tram)) + find_platform() + +/** + * The mapper should have placed the rectifier in the area containing the station, the object will search for a landmark within + * its control area and set its idle position. + */ +/obj/machinery/transport/power_rectifier/proc/find_platform() + var/area/my_area = get_area(src) + for(var/obj/effect/landmark/transport/nav_beacon/tram/platform/candidate_platform in SStransport.nav_beacons[configured_transport_id]) + if(get_area(candidate_platform) == my_area) + connected_platform = candidate_platform + RegisterSignal(connected_platform, COMSIG_QDELETING, PROC_REF(on_landmark_qdel)) + log_transport("[id_tag]: Power rectifier linked to landmark [connected_platform.name]") + return + +/obj/machinery/transport/power_rectifier/proc/power_tram(datum/source, datum/transport_controller/linear/tram/controller, controller_active, controller_status, travel_direction, obj/effect/landmark/transport/nav_beacon/tram/platform/destination_platform) + SIGNAL_HANDLER + + if(controller_active && destination_platform == connected_platform) + update_use_power(ACTIVE_POWER_USE) + else + update_use_power(IDLE_POWER_USE) + + update_appearance() + +/** + * Update the lights based on the rectifier status. + */ +/obj/machinery/transport/power_rectifier/update_overlays() + . = ..() + + if(machine_stat & NOPOWER) + . += mutable_appearance(icon, "rec-power-0") + . += emissive_appearance(icon, "rec-power-0", src, alpha = src.alpha) + return + + . += mutable_appearance(icon, "rec-power-1") + . += emissive_appearance(icon, "rec-power-1", src, alpha = src.alpha) + + var/is_active = use_power == ACTIVE_POWER_USE + . += mutable_appearance(icon, "rec-active-[is_active]") + . += emissive_appearance(icon, "rec-active-[is_active]", src, alpha = src.alpha) + +/** + * Clear reference to the connected landmark if it gets destroyed. + */ +/obj/machinery/transport/power_rectifier/proc/on_landmark_qdel() + log_transport("[id_tag]: Power rectifier received QDEL from landmark [connected_platform.name]") + connected_platform = null diff --git a/code/modules/transport/tram/tram_remote.dm b/code/modules/transport/tram/tram_remote.dm new file mode 100644 index 0000000000000..4176117d8b2f8 --- /dev/null +++ b/code/modules/transport/tram/tram_remote.dm @@ -0,0 +1,130 @@ +/obj/item/assembly/control/transport/remote + icon_state = "tramremote_nis" + inhand_icon_state = "electronic" + lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' + icon = 'icons/obj/devices/remote.dmi' + name = "tram remote" + desc = "A remote control that can be linked to a tram. This can only go well." + w_class = WEIGHT_CLASS_TINY + options = RAPID_MODE + ///desired tram destination + var/destination + COOLDOWN_DECLARE(tram_remote) + +/obj/item/assembly/control/transport/remote/Initialize(mapload) + . = ..() + if(!id_tag) + id_tag = assign_random_name() + SStransport.hello(src, name, id_tag) + register_context() + +/obj/item/assembly/control/transport/remote/add_context(atom/source, list/context, obj/item/held_item, mob/user) + if(!specific_transport_id) + context[SCREENTIP_CONTEXT_LMB] = "Link tram" + return CONTEXTUAL_SCREENTIP_SET + context[SCREENTIP_CONTEXT_LMB] = "Dispatch tram" + context[SCREENTIP_CONTEXT_RMB] = "Select destination" + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Toggle door safeties" + context[SCREENTIP_CONTEXT_ALT_LMB] = "Change tram" + return CONTEXTUAL_SCREENTIP_SET + +//set tram destination +/obj/item/assembly/control/transport/remote/attack_self_secondary(mob/user) + var/list/available_platforms = list() + for(var/obj/effect/landmark/transport/nav_beacon/tram/platform/platform as anything in SStransport.nav_beacons[specific_transport_id]) + LAZYADD(available_platforms, platform.name) + + var/selected_platform = tgui_input_list(user, "Available destinations", "Where to?", available_platforms) + for(var/obj/effect/landmark/transport/nav_beacon/tram/platform/potential_platform as anything in SStransport.nav_beacons[specific_transport_id]) + if(potential_platform.name == selected_platform) + destination = potential_platform.platform_code + break + + balloon_alert(user, "set [selected_platform]") + to_chat(user, span_notice("You change the platform ID on [src] to [selected_platform].")) + +///set safety bypass +/obj/item/assembly/control/transport/remote/CtrlClick(mob/user) + switch(options) + if(!RAPID_MODE) + options |= RAPID_MODE + if(RAPID_MODE) + options &= ~RAPID_MODE + update_appearance() + balloon_alert(user, "mode: [options ? "fast" : "safe"]") + +/obj/item/assembly/control/transport/remote/examine(mob/user) + . = ..() + if(!specific_transport_id) + . += "There is an X showing on the display." + . += "Left-click to link to a tram." + return + . += "The rapid mode light is [options ? "on" : "off"]." + if(cooldown) + . += "The number on the display shows [DisplayTimeText(cooldown, 1)]." + else + . += "The display indicates ready." + . += "Left-click to dispatch tram." + . += "Right-click to set destination." + . += "Ctrl-click to toggle safety bypass." + . += "Alt-click to change configured tram." + +/obj/item/assembly/control/transport/remote/update_icon_state() + . = ..() + + if(!specific_transport_id) + icon_state = "tramremote_nis" + return + + icon_state = "tramremote_ob" + +/obj/item/assembly/control/transport/remote/update_overlays() + . = ..() + if(options & RAPID_MODE) + . += mutable_appearance(icon, "tramremote_emag") + +/obj/item/assembly/control/transport/remote/attack_self(mob/user) + if(!specific_transport_id) + link_tram(user) + return + + if(cooldown) + balloon_alert(user, "cooldown: [DisplayTimeText(cooldown, 1)]") + return + + activate(user) + COOLDOWN_START(src, tram_remote, 2 MINUTES) + +///send our selected commands to the tram +/obj/item/assembly/control/transport/remote/activate(mob/user) + if(!specific_transport_id) + balloon_alert(user, "no tram linked!") + return + if(!destination) + balloon_alert(user, "no destination!") + return + + SEND_SIGNAL(src, COMSIG_TRANSPORT_REQUEST, specific_transport_id, destination, options) + +/obj/item/assembly/control/transport/remote/AltClick(mob/user) + . = ..() + if(!can_interact(user)) + return + + link_tram(user) + +/obj/item/assembly/control/transport/remote/proc/link_tram(mob/user) + specific_transport_id = null + var/list/transports_available + for(var/datum/transport_controller/linear/tram/tram as anything in SStransport.transports_by_type[TRANSPORT_TYPE_TRAM]) + LAZYADD(transports_available, tram.specific_transport_id) + + specific_transport_id = tgui_input_list(user, "Available transports", "Select a transport", transports_available) + + if(specific_transport_id) + balloon_alert(user, "tram linked") + else + balloon_alert(user, "link failed!") + + update_appearance() diff --git a/code/modules/transport/tram/tram_signals.dm b/code/modules/transport/tram/tram_signals.dm new file mode 100644 index 0000000000000..101ae1027a306 --- /dev/null +++ b/code/modules/transport/tram/tram_signals.dm @@ -0,0 +1,737 @@ +/// Pedestrian crossing signal for tram +/obj/machinery/transport/crossing_signal + name = "crossing signal" + desc = "Indicates to pedestrians if it's safe to cross the tracks. Connects to sensors down the track." + icon = 'icons/obj/tram/crossing_signal.dmi' + icon_state = "crossing-inbound" + base_icon_state = "crossing-inbound" + layer = TRAM_SIGNAL_LAYER + max_integrity = 250 + integrity_failure = 0.25 + light_range = 2 + light_power = 0.7 + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 3.6 + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.72 + anchored = TRUE + density = FALSE + interaction_flags_machine = INTERACT_MACHINE_OPEN + circuit = /obj/item/circuitboard/machine/crossing_signal + // pointless if it only takes 2 seconds to cross but updates every 2 seconds + subsystem_type = /datum/controller/subsystem/processing/fastprocess + light_color = LIGHT_COLOR_BABY_BLUE + /// green, amber, or red for tram, blue if it's emag, tram missing, etc. + var/signal_state = XING_STATE_MALF + /// the sensor we use + var/datum/weakref/sensor_ref + /// Inbound station + var/inbound + /// Outbound station + var/outbound + /// If us or anything else in the operation chain is broken + var/operating_status = TRANSPORT_SYSTEM_NORMAL + var/sign_dir = INBOUND + /** Proximity thresholds for crossing signal states + * + * The proc that checks the distance between the tram and crossing signal uses these vars to determine the distance between tram and signal to change + * colors. The numbers are specifically set for Tramstation. If we get another map with crossing signals we'll have to probably subtype it or something. + * If the value is set too high, it will cause the lights to turn red when the tram arrives at another station. You want to optimize the amount of + * warning without turning it red unnessecarily. + * + * Red: decent chance of getting hit, but if you're quick it's a decent gamble. + * Amber: slow people may be in danger. + */ + var/amber_distance_threshold = AMBER_THRESHOLD_NORMAL + var/red_distance_threshold = RED_THRESHOLD_NORMAL + +/** Crossing signal subtypes + * + * Each map will have a different amount of tiles between stations, so adjust the signals here based on the map. + * The distance is calculated from the bottom left corner of the tram, + * so signals on the east side have their distance reduced by the tram length, in this case 10 for Tramstation. +*/ +/obj/machinery/transport/crossing_signal/northwest + dir = NORTH + sign_dir = INBOUND + +/obj/machinery/transport/crossing_signal/northeast + dir = NORTH + sign_dir = OUTBOUND + +/obj/machinery/transport/crossing_signal/southwest + dir = SOUTH + sign_dir = INBOUND + pixel_y = 20 + +/obj/machinery/transport/crossing_signal/southeast + dir = SOUTH + sign_dir = OUTBOUND + pixel_y = 20 + +/obj/machinery/static_signal + name = "crossing signal" + desc = "Indicates to pedestrians if it's safe to cross the tracks." + icon = 'icons/obj/tram/crossing_signal.dmi' + icon_state = "crossing-inbound" + layer = TRAM_SIGNAL_LAYER + max_integrity = 250 + integrity_failure = 0.25 + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 3.6 + anchored = TRUE + density = FALSE + light_range = 1.5 + light_power = 3 + light_color = COLOR_VIBRANT_LIME + var/sign_dir = INBOUND + +/obj/machinery/static_signal/northwest + dir = NORTH + sign_dir = INBOUND + +/obj/machinery/static_signal/northeast + dir = NORTH + sign_dir = OUTBOUND + +/obj/machinery/static_signal/southwest + dir = SOUTH + sign_dir = INBOUND + pixel_y = 20 + +/obj/machinery/static_signal/southeast + dir = SOUTH + sign_dir = OUTBOUND + pixel_y = 20 + +/obj/machinery/transport/crossing_signal/Initialize(mapload) + . = ..() + RegisterSignal(SStransport, COMSIG_TRANSPORT_ACTIVE, PROC_REF(wake_up)) + RegisterSignal(SStransport, COMSIG_COMMS_STATUS, PROC_REF(comms_change)) + SStransport.crossing_signals += src + register_context() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/transport/crossing_signal/LateInitialize(mapload) + . = ..() + link_tram() + link_sensor() + find_uplink() + +/obj/machinery/transport/crossing_signal/Destroy() + SStransport.crossing_signals -= src + . = ..() + +/obj/machinery/transport/crossing_signal/attackby(obj/item/weapon, mob/living/user, params) + if(!user.combat_mode) + if(default_deconstruction_screwdriver(user, icon_state, icon_state, weapon)) + return + + if(default_deconstruction_crowbar(weapon)) + return + + return ..() + +/obj/machinery/transport/crossing_signal/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(panel_open) + if(held_item?.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_ALT_LMB] = "rotate signal" + context[SCREENTIP_CONTEXT_RMB] = "flip signal" + + if(istype(held_item, /obj/item/card/emag) && !(obj_flags & EMAGGED)) + context[SCREENTIP_CONTEXT_LMB] = "disable sensors" + + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/transport/crossing_signal/examine(mob/user) + . = ..() + . += span_notice("The maintenance panel is [panel_open ? "open" : "closed"].") + if(panel_open) + . += span_notice("It can be flipped or rotated with a [EXAMINE_HINT("wrench.")]") + switch(operating_status) + if(TRANSPORT_REMOTE_WARNING) + . += span_notice("The orange [EXAMINE_HINT("remote warning")] light is on.") + . += span_notice("The status display reads: Check track sensor.") + if(TRANSPORT_REMOTE_FAULT) + . += span_notice("The blue [EXAMINE_HINT("remote fault")] light is on.") + . += span_notice("The status display reads: Check tram controller.") + if(TRANSPORT_LOCAL_FAULT) + . += span_notice("The red [EXAMINE_HINT("local fault")] light is on.") + . += span_notice("The status display reads: Repair required.") + switch(dir) + if(NORTH, SOUTH) + . += span_notice("The tram configuration display shows EAST/WEST.") + if(EAST, WEST) + . += span_notice("The tram configuration display shows NORTH/SOUTH.") + +/obj/machinery/transport/crossing_signal/emag_act(mob/living/user) + if(obj_flags & EMAGGED) + return FALSE + balloon_alert(user, "disabled motion sensors") + operating_status = TRANSPORT_LOCAL_FAULT + obj_flags |= EMAGGED + return TRUE + +/obj/machinery/transport/crossing_signal/AltClick(mob/living/user) + . = ..() + if(!can_interact(user)) + return + + var/obj/item/tool = user.get_active_held_item() + if(!panel_open || tool?.tool_behaviour != TOOL_WRENCH) + return FALSE + + tool.play_tool_sound(src, 50) + setDir(turn(dir,-90)) + to_chat(user, span_notice("You rotate [src].")) + find_uplink() + return TRUE + +/obj/machinery/transport/crossing_signal/attackby_secondary(obj/item/weapon, mob/user, params) + . = ..() + + if(weapon.tool_behaviour == TOOL_WRENCH && panel_open) + switch(sign_dir) + if(INBOUND) + sign_dir = OUTBOUND + if(OUTBOUND) + sign_dir = INBOUND + + to_chat(user, span_notice("You flip directions on [src].")) + update_appearance() + + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/transport/crossing_signal/proc/link_sensor() + sensor_ref = WEAKREF(find_closest_valid_sensor()) + update_appearance() + +/obj/machinery/transport/crossing_signal/proc/unlink_sensor() + sensor_ref = null + if(operating_status < TRANSPORT_REMOTE_WARNING) + operating_status = TRANSPORT_REMOTE_WARNING + degraded_response() + update_appearance() + +/obj/machinery/transport/crossing_signal/proc/wake_sensor() + if(operating_status > TRANSPORT_REMOTE_WARNING) + degraded_response() + return + + var/obj/machinery/transport/guideway_sensor/linked_sensor = sensor_ref?.resolve() + if(isnull(linked_sensor)) + operating_status = TRANSPORT_REMOTE_WARNING + degraded_response() + + else if(linked_sensor.trigger_sensor()) + operating_status = TRANSPORT_SYSTEM_NORMAL + normal_response() + + else + operating_status = TRANSPORT_REMOTE_WARNING + degraded_response() + +/obj/machinery/transport/crossing_signal/proc/normal_response() + amber_distance_threshold = AMBER_THRESHOLD_NORMAL + red_distance_threshold = RED_THRESHOLD_NORMAL + +/obj/machinery/transport/crossing_signal/proc/degraded_response() + amber_distance_threshold = AMBER_THRESHOLD_DEGRADED + red_distance_threshold = RED_THRESHOLD_DEGRADED + +/obj/machinery/transport/crossing_signal/proc/clear_uplink() + inbound = null + outbound = null + update_appearance() + +/** + * Only process if the tram is actually moving + */ +/obj/machinery/transport/crossing_signal/proc/wake_up(datum/source, transport_controller, controller_active) + SIGNAL_HANDLER + + if(machine_stat & BROKEN || machine_stat & NOPOWER) + operating_status = TRANSPORT_LOCAL_FAULT + update_appearance() + return + + if(prob(TRANSPORT_BREAKDOWN_RATE)) + operating_status = TRANSPORT_LOCAL_FAULT + local_fault() + return + + var/datum/transport_controller/linear/tram/tram = transport_ref?.resolve() + var/obj/machinery/transport/guideway_sensor/linked_sensor = sensor_ref?.resolve() + + if(malfunctioning) + operating_status = TRANSPORT_LOCAL_FAULT + else if(isnull(tram) || tram.controller_status & COMM_ERROR) + operating_status = TRANSPORT_REMOTE_FAULT + else + operating_status = TRANSPORT_SYSTEM_NORMAL + + if(isnull(linked_sensor)) + link_sensor() + wake_sensor() + update_operating() + +/obj/machinery/transport/crossing_signal/on_set_machine_stat() + . = ..() + if(machine_stat & BROKEN || machine_stat & NOPOWER) + operating_status = TRANSPORT_LOCAL_FAULT + else + operating_status = TRANSPORT_SYSTEM_NORMAL + +/obj/machinery/transport/crossing_signal/on_set_is_operational() + . = ..() + if(!is_operational) + operating_status = TRANSPORT_LOCAL_FAULT + else + operating_status = TRANSPORT_SYSTEM_NORMAL + update_operating() + +/obj/machinery/transport/crossing_signal/proc/comms_change(source, controller, new_status) + SIGNAL_HANDLER + + var/datum/transport_controller/linear/tram/updated_controller = controller + + if(updated_controller.specific_transport_id != configured_transport_id) + return + + switch(new_status) + if(TRUE) + if(operating_status == TRANSPORT_REMOTE_FAULT) + operating_status = TRANSPORT_SYSTEM_NORMAL + if(FALSE) + if(operating_status == TRANSPORT_SYSTEM_NORMAL) + operating_status = TRANSPORT_REMOTE_FAULT + +/** + * Update processing state. + * + * Returns whether we are still processing. + */ +/obj/machinery/transport/crossing_signal/proc/update_operating() + update_appearance() + // Immediately process for snappy feedback + var/should_process = process() != PROCESS_KILL + if(should_process) + update_use_power(ACTIVE_POWER_USE) + begin_processing() + return + update_use_power(IDLE_POWER_USE) + end_processing() + +/obj/machinery/transport/crossing_signal/process() + + var/datum/transport_controller/linear/tram/tram = transport_ref?.resolve() + + // Check for stopped states. + if(!tram || !tram.controller_operational || !is_operational || !inbound || !outbound) + // Tram missing, we lost power, or something isn't right + // Throw the error message (blue) + set_signal_state(XING_STATE_MALF, force = !is_operational) + return PROCESS_KILL + + var/obj/structure/transport/linear/tram_part = tram.return_closest_platform_to(src) + + if(QDELETED(tram_part)) + set_signal_state(XING_STATE_MALF, force = !is_operational) + return PROCESS_KILL + + // Everything will be based on position and travel direction + var/signal_pos + var/tram_pos + var/tram_velocity_sign // 1 for positive axis movement, -1 for negative + // Try to be agnostic about N-S vs E-W movement + if(tram.travel_direction & (NORTH|SOUTH)) + signal_pos = y + tram_pos = tram_part.y + tram_velocity_sign = tram.travel_direction & NORTH ? 1 : -1 + else + signal_pos = x + tram_pos = tram_part.x + tram_velocity_sign = tram.travel_direction & EAST ? 1 : -1 + + // How far away are we? negative if already passed. + var/approach_distance = tram_velocity_sign * (signal_pos - (tram_pos + (DEFAULT_TRAM_LENGTH * 0.5))) + + // Check for stopped state. + // Will kill the process since tram starting up will restart process. + if(!tram.controller_active) + set_signal_state(XING_STATE_GREEN) + return PROCESS_KILL + + // Check if tram is driving away from us. + if(approach_distance < 0) + // driving away. Green. In fact, in order to reverse, it'll have to stop, so let's go ahead and kill. + set_signal_state(XING_STATE_GREEN) + return PROCESS_KILL + + // Check the tram's terminus station. + // INBOUND 1 < 2 < 3 + // OUTBOUND 1 > 2 > 3 + if(tram.travel_direction & WEST && inbound < tram.destination_platform.platform_code) + set_signal_state(XING_STATE_GREEN) + return PROCESS_KILL + if(tram.travel_direction & EAST && outbound > tram.destination_platform.platform_code) + set_signal_state(XING_STATE_GREEN) + return PROCESS_KILL + + // Finally the interesting part where it's ACTUALLY approaching + if(approach_distance <= red_distance_threshold) + if(operating_status != TRANSPORT_SYSTEM_NORMAL) + set_signal_state(XING_STATE_MALF) + else + set_signal_state(XING_STATE_RED) + return + if(approach_distance <= amber_distance_threshold) + set_signal_state(XING_STATE_AMBER) + return + set_signal_state(XING_STATE_GREEN) + +/** + * Set the signal state and update appearance. + * + * Arguments: + * new_state - the new state (XING_STATE_RED, etc) + * force_update - force appearance to update even if state didn't change. + */ +/obj/machinery/transport/crossing_signal/proc/set_signal_state(new_state, force = FALSE) + if(new_state == signal_state && !force) + return + + signal_state = new_state + flick_overlay() + update_appearance() + +/obj/machinery/transport/crossing_signal/update_icon_state() + switch(dir) + if(SOUTH, EAST) + pixel_y = 20 + if(NORTH, WEST) + pixel_y = 0 + + switch(sign_dir) + if(INBOUND) + icon_state = "crossing-inbound" + base_icon_state = "crossing-inbound" + if(OUTBOUND) + icon_state = "crossing-outbound" + base_icon_state = "crossing-outbound" + + return ..() + +/obj/machinery/static_signal/update_icon_state() + switch(dir) + if(SOUTH, EAST) + pixel_y = 20 + if(NORTH, WEST) + pixel_y = 0 + + switch(sign_dir) + if(INBOUND) + icon_state = "crossing-inbound" + base_icon_state = "crossing-inbound" + if(OUTBOUND) + icon_state = "crossing-outbound" + base_icon_state = "crossing-outbound" + + return ..() + +/obj/machinery/transport/crossing_signal/update_appearance(updates) + . = ..() + + if(machine_stat & NOPOWER) + set_light(l_on = FALSE) + return + + var/new_color + switch(signal_state) + if(XING_STATE_MALF) + new_color = LIGHT_COLOR_BABY_BLUE + if(XING_STATE_GREEN) + new_color = LIGHT_COLOR_VIVID_GREEN + if(XING_STATE_AMBER) + new_color = LIGHT_COLOR_BRIGHT_YELLOW + else + new_color = LIGHT_COLOR_FLARE + + set_light(l_on = TRUE, l_color = new_color) + +/obj/machinery/transport/crossing_signal/update_overlays() + . = ..() + + if(machine_stat & NOPOWER) + return + + if(machine_stat & BROKEN) + operating_status = TRANSPORT_LOCAL_FAULT + + var/lights_overlay = "[base_icon_state]-l[signal_state]" + var/status_overlay = "[base_icon_state]-s[operating_status]" + + . += mutable_appearance(icon, lights_overlay) + . += mutable_appearance(icon, status_overlay) + . += emissive_appearance(icon, lights_overlay, offset_spokesman = src, alpha = src.alpha) + . += emissive_appearance(icon, status_overlay, offset_spokesman = src, alpha = src.alpha) + +/obj/machinery/static_signal/power_change() + ..() + + if(!is_operational) + set_light(l_on = FALSE) + return + + set_light(l_on = TRUE) + +/obj/machinery/static_signal/update_overlays() + . = ..() + + if(!is_operational) + return + + . += mutable_appearance(icon, "[base_icon_state]-l0") + . += mutable_appearance(icon, "[base_icon_state]-s0") + . += emissive_appearance(icon, "[base_icon_state]-l0", offset_spokesman = src, alpha = src.alpha) + . += emissive_appearance(icon, "[base_icon_state]-s0", offset_spokesman = src, alpha = src.alpha) + +/obj/machinery/transport/guideway_sensor + name = "guideway sensor" + icon = 'icons/obj/tram/tram_sensor.dmi' + icon_state = "sensor-base" + desc = "Uses an infrared beam to detect passing trams. Works when paired with a sensor on the other side of the track." + layer = TRAM_RAIL_LAYER + use_power = NO_POWER_USE + circuit = /obj/item/circuitboard/machine/guideway_sensor + /// Sensors work in a married pair + var/datum/weakref/paired_sensor + /// If us or anything else in the operation chain is broken + var/operating_status = TRANSPORT_SYSTEM_NORMAL + +/obj/machinery/transport/guideway_sensor/Initialize(mapload) + . = ..() + SStransport.sensors += src + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/transport/guideway_sensor/LateInitialize(mapload) + . = ..() + pair_sensor() + RegisterSignal(SStransport, COMSIG_TRANSPORT_ACTIVE, PROC_REF(wake_up)) + +/obj/machinery/transport/guideway_sensor/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(panel_open) + if(held_item?.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_RMB] = "rotate sensor" + + if(istype(held_item, /obj/item/card/emag) && !(obj_flags & EMAGGED)) + context[SCREENTIP_CONTEXT_LMB] = "disable sensor" + + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/transport/guideway_sensor/examine(mob/user) + . = ..() + . += span_notice("The maintenance panel is [panel_open ? "open" : "closed"].") + if(panel_open) + . += span_notice("It can be rotated with a [EXAMINE_HINT("wrench.")]") + switch(operating_status) + if(TRANSPORT_REMOTE_WARNING) + . += span_notice("The orange [EXAMINE_HINT("remote warning")] light is on.") + . += span_notice("The status display reads: Check paired sensor.") + if(TRANSPORT_REMOTE_FAULT) + . += span_notice("The blue [EXAMINE_HINT("remote fault")] light is on.") + . += span_notice("The status display reads: Paired sensor not found.") + if(TRANSPORT_LOCAL_FAULT) + . += span_notice("The red [EXAMINE_HINT("local fault")] light is on.") + . += span_notice("The status display reads: Repair required.") + +/obj/machinery/transport/guideway_sensor/attackby(obj/item/weapon, mob/living/user, params) + if (!user.combat_mode) + if(default_deconstruction_screwdriver(user, icon_state, icon_state, weapon)) + return + + if(default_deconstruction_crowbar(weapon)) + return + + return ..() + +/obj/machinery/transport/guideway_sensor/proc/pair_sensor() + set_machine_stat(machine_stat | MAINT) + if(paired_sensor) + var/obj/machinery/transport/guideway_sensor/divorcee = paired_sensor?.resolve() + divorcee.set_machine_stat(machine_stat | MAINT) + divorcee.paired_sensor = null + divorcee.update_appearance() + paired_sensor = null + + for(var/obj/machinery/transport/guideway_sensor/potential_sensor in SStransport.sensors) + if(potential_sensor == src) + continue + switch(potential_sensor.dir) + if(NORTH, SOUTH) + if(potential_sensor.x == src.x) + paired_sensor = WEAKREF(potential_sensor) + set_machine_stat(machine_stat & ~MAINT) + break + if(EAST, WEST) + if(potential_sensor.y == src.y) + paired_sensor = WEAKREF(potential_sensor) + set_machine_stat(machine_stat & ~MAINT) + break + + update_appearance() + + var/obj/machinery/transport/guideway_sensor/new_partner = paired_sensor?.resolve() + if(isnull(new_partner)) + return + + new_partner.paired_sensor = WEAKREF(src) + new_partner.set_machine_stat(machine_stat & ~MAINT) + new_partner.update_appearance() + playsound(src, 'sound/machines/synth_yes.ogg', 75, vary = FALSE, use_reverb = TRUE) + +/obj/machinery/transport/guideway_sensor/Destroy() + SStransport.sensors -= src + if(paired_sensor) + var/obj/machinery/transport/guideway_sensor/divorcee = paired_sensor?.resolve() + divorcee.set_machine_stat(machine_stat & ~MAINT) + divorcee.paired_sensor = null + divorcee.update_appearance() + playsound(src, 'sound/machines/synth_no.ogg', 75, vary = FALSE, use_reverb = TRUE) + paired_sensor = null + . = ..() + +/obj/machinery/transport/guideway_sensor/wrench_act(mob/living/user, obj/item/tool) + . = ..() + + if(default_change_direction_wrench(user, tool)) + pair_sensor() + return TRUE + +/obj/machinery/transport/guideway_sensor/update_overlays() + . = ..() + + if(machine_stat & BROKEN || machine_stat & NOPOWER || malfunctioning) + operating_status = TRANSPORT_LOCAL_FAULT + . += mutable_appearance(icon, "sensor-[TRANSPORT_LOCAL_FAULT]") + . += emissive_appearance(icon, "sensor-[TRANSPORT_LOCAL_FAULT]", src, alpha = src.alpha) + return + + if(machine_stat & MAINT) + operating_status = TRANSPORT_REMOTE_FAULT + . += mutable_appearance(icon, "sensor-[TRANSPORT_REMOTE_FAULT]") + . += emissive_appearance(icon, "sensor-[TRANSPORT_REMOTE_FAULT]", src, alpha = src.alpha) + return + + var/obj/machinery/transport/guideway_sensor/buddy = paired_sensor?.resolve() + if(buddy) + if(!buddy.is_operational) + operating_status = TRANSPORT_REMOTE_WARNING + . += mutable_appearance(icon, "sensor-[TRANSPORT_REMOTE_WARNING]") + . += emissive_appearance(icon, "sensor-[TRANSPORT_REMOTE_WARNING]", src, alpha = src.alpha) + else + operating_status = TRANSPORT_SYSTEM_NORMAL + . += mutable_appearance(icon, "sensor-[TRANSPORT_SYSTEM_NORMAL]") + . += emissive_appearance(icon, "sensor-[TRANSPORT_SYSTEM_NORMAL]", src, alpha = src.alpha) + return + + else + operating_status = TRANSPORT_REMOTE_FAULT + . += mutable_appearance(icon, "sensor-[TRANSPORT_REMOTE_FAULT]") + . += emissive_appearance(icon, "sensor-[TRANSPORT_REMOTE_FAULT]", src, alpha = src.alpha) + +/obj/machinery/transport/guideway_sensor/proc/trigger_sensor() + var/obj/machinery/transport/guideway_sensor/buddy = paired_sensor?.resolve() + if(!buddy) + return FALSE + + if(!is_operational || !buddy.is_operational) + return FALSE + + return TRUE + +/obj/machinery/transport/guideway_sensor/proc/wake_up() + SIGNAL_HANDLER + + if(machine_stat & BROKEN) + update_appearance() + return + + if(prob(TRANSPORT_BREAKDOWN_RATE)) + operating_status = TRANSPORT_LOCAL_FAULT + local_fault() + + var/obj/machinery/transport/guideway_sensor/buddy = paired_sensor?.resolve() + + if(buddy) + set_machine_stat(machine_stat & ~MAINT) + + update_appearance() + +/obj/machinery/transport/guideway_sensor/on_set_is_operational() + . = ..() + + var/obj/machinery/transport/guideway_sensor/buddy = paired_sensor?.resolve() + if(buddy) + buddy.update_appearance() + + update_appearance() + +/obj/machinery/transport/crossing_signal/proc/find_closest_valid_sensor() + if(!istype(src) || !src.z) + return FALSE + + var/list/obj/machinery/transport/guideway_sensor/sensor_candidates = list() + + for(var/obj/machinery/transport/guideway_sensor/sensor in SStransport.sensors) + if(sensor.z == src.z) + if((sensor.x == src.x && sensor.dir & NORTH|SOUTH) || (sensor.y == src.y && sensor.dir & EAST|WEST)) + sensor_candidates += sensor + + var/obj/machinery/transport/guideway_sensor/selected_sensor = get_closest_atom(/obj/machinery/transport/guideway_sensor, sensor_candidates, src) + var/sensor_distance = get_dist(src, selected_sensor) + if(sensor_distance <= DEFAULT_TRAM_LENGTH) + return selected_sensor + + return FALSE + +/obj/machinery/transport/crossing_signal/proc/find_uplink() + if(!istype(src) || !src.z) + return FALSE + + var/list/obj/effect/landmark/transport/nav_beacon/tram/platform/inbound_candidates = list() + var/list/obj/effect/landmark/transport/nav_beacon/tram/platform/outbound_candidates = list() + + inbound = null + outbound = null + + for(var/obj/effect/landmark/transport/nav_beacon/tram/platform/beacon in SStransport.nav_beacons[configured_transport_id]) + if(beacon.z != src.z) + continue + + switch(src.dir) + if(NORTH, SOUTH) + if(abs((beacon.y - src.y)) <= DEFAULT_TRAM_LENGTH) + if(beacon.x < src.x) + inbound_candidates += beacon + else + outbound_candidates += beacon + if(EAST, WEST) + if(abs((beacon.x - src.x)) <= DEFAULT_TRAM_LENGTH) + if(beacon.y < src.y) + inbound_candidates += beacon + else + outbound_candidates += beacon + + var/obj/effect/landmark/transport/nav_beacon/tram/platform/selected_inbound = get_closest_atom(/obj/effect/landmark/transport/nav_beacon/tram/platform, inbound_candidates, src) + if(isnull(selected_inbound)) + return FALSE + + inbound = selected_inbound.platform_code + + var/obj/effect/landmark/transport/nav_beacon/tram/platform/selected_outbound = get_closest_atom(/obj/effect/landmark/transport/nav_beacon/tram/platform, outbound_candidates, src) + if(isnull(selected_outbound)) + return FALSE + + outbound = selected_outbound.platform_code + + update_appearance() diff --git a/code/modules/transport/tram/tram_structures.dm b/code/modules/transport/tram/tram_structures.dm new file mode 100644 index 0000000000000..ccf6a99d92967 --- /dev/null +++ b/code/modules/transport/tram/tram_structures.dm @@ -0,0 +1,635 @@ +/** + * the tram has a few objects mapped onto it at roundstart, by default many of those objects have unwanted properties + * for example grilles and windows have the atmos_sensitive element applied to them, which makes them register to + * themselves moving to re register signals onto the turf via connect_loc. this is bad and dumb since it makes the tram + * more expensive to move. + * + * if you map something on to the tram, make SURE if possible that it doesnt have anything reacting to its own movement + * it will make the tram more expensive to move and we dont want that because we dont want to return to the days where + * the tram took a third of the tick per movement when its just carrying its default mapped in objects + */ + +/obj/structure/grille/tram/Initialize(mapload) + . = ..() + RemoveElement(/datum/element/atmos_sensitive, mapload) + //atmos_sensitive applies connect_loc which 1. reacts to movement in order to 2. unregister and register signals to + //the old and new locs. we dont want that, pretend these grilles and windows are plastic or something idk + +/obj/structure/tram/Initialize(mapload, direct) + . = ..() + RemoveElement(/datum/element/atmos_sensitive, mapload) + +/obj/structure/tram + name = "tram wall" + desc = "A lightweight titanium composite structure with titanium silicate panels." + icon = 'icons/obj/tram/tram_structure.dmi' + icon_state = "tram-part-0" + base_icon_state = "tram-part" + max_integrity = 150 + layer = TRAM_WALL_LAYER + density = TRUE + opacity = FALSE + anchored = TRUE + flags_1 = PREVENT_CLICK_UNDER_1 + armor_type = /datum/armor/tram_structure + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_TRAM_STRUCTURE + canSmoothWith = SMOOTH_GROUP_TRAM_STRUCTURE + can_be_unanchored = FALSE + can_atmos_pass = ATMOS_PASS_DENSITY + explosion_block = 3 + receive_ricochet_chance_mod = 1.2 + rad_insulation = RAD_MEDIUM_INSULATION + /// What state of de/construction it's in + var/state = TRAM_SCREWED_TO_FRAME + /// Mineral to return when deconstructed + var/mineral = /obj/item/stack/sheet/titaniumglass + /// Amount of mineral to return when deconstructed + var/mineral_amount = 2 + /// Type of structure made out of girder + var/tram_wall_type = /obj/structure/tram + /// Type of girder made when deconstructed + var/girder_type = /obj/structure/girder/tram + var/mutable_appearance/damage_overlay + /// Sound when it breaks + var/break_sound = SFX_SHATTER + /// Sound when hit without combat mode + var/knock_sound = 'sound/effects/glassknock.ogg' + /// Sound when hit with combat mode + var/bash_sound = 'sound/effects/glassbash.ogg' + +/obj/structure/tram/split + base_icon_state = "tram-split" + +/datum/armor/tram_structure + melee = 40 + bullet = 10 + laser = 10 + bomb = 45 + fire = 90 + acid = 100 + +/obj/structure/tram/Initialize(mapload) + AddElement(/datum/element/blocks_explosives) + . = ..() + var/obj/item/stack/initialized_mineral = new mineral + set_custom_materials(initialized_mineral.mats_per_unit, mineral_amount) + qdel(initialized_mineral) + air_update_turf(TRUE, TRUE) + register_context() + +/obj/structure/tram/examine(mob/user) + . = ..() + switch(state) + if(TRAM_SCREWED_TO_FRAME) + . += span_notice("The panel is [EXAMINE_HINT("screwed")] to the frame. To dismantle use a [EXAMINE_HINT("screwdriver.")]") + if(TRAM_IN_FRAME) + . += span_notice("The panel is [EXAMINE_HINT("unscrewed,")] but [EXAMINE_HINT("pried")] into the frame. To dismantle use a [EXAMINE_HINT("crowbar.")]") + if(TRAM_OUT_OF_FRAME) + . += span_notice("The panel is [EXAMINE_HINT("pried")] out of the frame, but still[EXAMINE_HINT("wired.")] To dismantle use [EXAMINE_HINT("wirecutters.")]") + +/obj/structure/tram/add_context(atom/source, list/context, obj/item/held_item, mob/user) + if(held_item?.tool_behaviour == TOOL_WELDER && atom_integrity < max_integrity) + context[SCREENTIP_CONTEXT_LMB] = "repair" + if(held_item?.tool_behaviour == TOOL_SCREWDRIVER && state == TRAM_SCREWED_TO_FRAME) + context[SCREENTIP_CONTEXT_RMB] = "unscrew panel" + if(held_item?.tool_behaviour == TOOL_CROWBAR && state == TRAM_IN_FRAME) + context[SCREENTIP_CONTEXT_RMB] = "remove panel" + if(held_item?.tool_behaviour == TOOL_WIRECUTTER && state == TRAM_OUT_OF_FRAME) + context[SCREENTIP_CONTEXT_RMB] = "disconnect panel" + + return CONTEXTUAL_SCREENTIP_SET + +/obj/structure/tram/update_overlays(updates = ALL) + . = ..() + var/ratio = atom_integrity / max_integrity + ratio = CEILING(ratio * 4, 1) * 25 + cut_overlay(damage_overlay) + if(ratio > 75) + return + + damage_overlay = mutable_appearance('icons/obj/structures.dmi', "damage[ratio]", -(layer + 0.1)) + . += damage_overlay + +/obj/structure/tram/attack_hand(mob/living/user, list/modifiers) + . = ..() + + if(!user.combat_mode) + user.visible_message(span_notice("[user] knocks on [src]."), \ + span_notice("You knock on [src].")) + playsound(src, knock_sound, 50, TRUE) + else + user.visible_message(span_warning("[user] bashes [src]!"), \ + span_warning("You bash [src]!")) + playsound(src, bash_sound, 100, TRUE) + +/obj/structure/tram/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + switch(the_rcd.mode) + if(RCD_DECONSTRUCT) + return list("mode" = RCD_DECONSTRUCT, "delay" = 3 SECONDS, "cost" = 10) + return FALSE + +/obj/structure/tram/rcd_act(mob/user, obj/item/construction/rcd/the_rcd) + switch(the_rcd.mode) + if(RCD_DECONSTRUCT) + qdel(src) + return TRUE + return FALSE + +/obj/structure/tram/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0) + . = ..() + if(.) //received damage + update_appearance() + +/obj/structure/tram/narsie_act() + add_atom_colour(NARSIE_WINDOW_COLOUR, FIXED_COLOUR_PRIORITY) + +/obj/structure/tram/singularity_pull(singulo, current_size) + ..() + + if(current_size >= STAGE_FIVE) + deconstruct(disassembled = FALSE) + +/obj/structure/tram/welder_act(mob/living/user, obj/item/tool) + if(atom_integrity >= max_integrity) + to_chat(user, span_warning("[src] is already in good condition!")) + return ITEM_INTERACT_SUCCESS + if(!tool.tool_start_check(user, amount = 0)) + return FALSE + to_chat(user, span_notice("You begin repairing [src]...")) + if(tool.use_tool(src, user, 4 SECONDS, volume = 50)) + atom_integrity = max_integrity + to_chat(user, span_notice("You repair [src].")) + update_appearance() + return ITEM_INTERACT_SUCCESS + +/obj/structure/tram/attackby_secondary(obj/item/tool, mob/user, params) + switch(state) + if(TRAM_SCREWED_TO_FRAME) + if(tool.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message(span_notice("[user] begins to unscrew the tram panel from the frame..."), + span_notice("You begin to unscrew the tram panel from the frame...")) + if(tool.use_tool(src, user, 1 SECONDS, volume = 50)) + state = TRAM_IN_FRAME + to_chat(user, span_notice("The screws come out, and a gap forms around the edge of the pane.")) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + if(tool.tool_behaviour) + to_chat(user, span_warning("The security screws need to be removed first!")) + + if(TRAM_IN_FRAME) + if(tool.tool_behaviour == TOOL_CROWBAR) + user.visible_message(span_notice("[user] wedges \the [tool] into the tram panel's gap in the frame and starts prying..."), + span_notice("You wedge \the [tool] into the tram panel's gap in the frame and start prying...")) + if(tool.use_tool(src, user, 1 SECONDS, volume = 50)) + state = TRAM_OUT_OF_FRAME + to_chat(user, span_notice("The panel pops out of the frame, exposing some cabling that look like they can be cut.")) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + if(tool.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message(span_notice("[user] resecures the tram panel to the frame..."), + span_notice("You resecure the tram panel to the frame...")) + state = TRAM_SCREWED_TO_FRAME + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + if(TRAM_OUT_OF_FRAME) + if(tool.tool_behaviour == TOOL_WIRECUTTER) + user.visible_message(span_notice("[user] starts cutting the connective cabling on \the [src]..."), + span_notice("You start cutting the connective cabling on \the [src]")) + if(tool.use_tool(src, user, 1 SECONDS, volume = 50)) + to_chat(user, span_notice("The panels falls out of the way exposing the frame backing.")) + deconstruct(disassembled = TRUE) + + if(tool.tool_behaviour == TOOL_CROWBAR) + user.visible_message(span_notice("[user] snaps the tram panel into place."), + span_notice("You snap the tram panel into place...")) + state = TRAM_IN_FRAME + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + if(tool.tool_behaviour) + to_chat(user, span_warning("The cabling need to be cut first!")) + + return ..() + +/obj/structure/tram/deconstruct(disassembled = TRUE) + if(!(obj_flags & NO_DECONSTRUCTION)) + if(disassembled) + new girder_type(loc) + if(mineral_amount) + for(var/i in 1 to mineral_amount) + new mineral(loc) + qdel(src) + +/obj/structure/tram/attackby(obj/item/item, mob/user, params) + . = ..() + + if(istype(item, /obj/item/wallframe/tram)) + try_wallmount(item, user) + +/obj/structure/tram/proc/try_wallmount(obj/item/wallmount, mob/user) + if(!istype(wallmount, /obj/item/wallframe/tram)) + return + + var/obj/item/wallframe/frame = wallmount + if(frame.try_build(src, user)) + frame.attach(src, user) + + return + +/* + * Other misc tramwall types + */ + +/obj/structure/tram/alt + + +/obj/structure/tram/alt/titanium + name = "solid tram" + desc = "A lightweight titanium composite structure. There is further solid plating where the panels usually attach to the frame." + icon = 'icons/turf/walls/shuttle_wall.dmi' + icon_state = "shuttle_wall-0" + base_icon_state = "shuttle_wall" + mineral = /obj/item/stack/sheet/mineral/titanium + tram_wall_type = /obj/structure/tram/alt/titanium + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS + canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_TITANIUM_WALLS + +/obj/structure/tram/alt/plastitanium + name = "reinforced tram" + desc = "An evil tram of plasma and titanium." + icon = 'icons/turf/walls/plastitanium_wall.dmi' + icon_state = "plastitanium_wall-0" + base_icon_state = "plastitanium_wall" + mineral = /obj/item/stack/sheet/mineral/plastitanium + tram_wall_type = /obj/structure/tram/alt/plastitanium + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_WALLS + canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_PLASTITANIUM_WALLS + +/obj/structure/tram/alt/gold + name = "gold tram" + desc = "A solid gold tram. Swag!" + icon = 'icons/turf/walls/gold_wall.dmi' + icon_state = "gold_wall-0" + base_icon_state = "gold_wall" + mineral = /obj/item/stack/sheet/mineral/gold + tram_wall_type = /obj/structure/tram/alt/gold + explosion_block = 0 //gold is a soft metal you dingus. + smoothing_groups = SMOOTH_GROUP_GOLD_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_GOLD_WALLS + custom_materials = list(/datum/material/gold = SHEET_MATERIAL_AMOUNT * 2) + +/obj/structure/tram/alt/silver + name = "silver tram" + desc = "A solid silver tram. Shiny!" + icon = 'icons/turf/walls/silver_wall.dmi' + icon_state = "silver_wall-0" + base_icon_state = "silver_wall" + mineral = /obj/item/stack/sheet/mineral/silver + tram_wall_type = /obj/structure/tram/alt/silver + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_SILVER_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_SILVER_WALLS + custom_materials = list(/datum/material/silver = SHEET_MATERIAL_AMOUNT * 2) + +/obj/structure/tram/alt/diamond + name = "diamond tram" + desc = "A composite structure with diamond-plated panels. Looks awfully sharp..." + icon = 'icons/turf/walls/diamond_wall.dmi' + icon_state = "diamond_wall-0" + base_icon_state = "diamond_wall" + mineral = /obj/item/stack/sheet/mineral/diamond + tram_wall_type = /obj/structure/tram/alt/diamond //diamond wall takes twice as much time to slice + max_integrity = 800 + explosion_block = 3 + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_DIAMOND_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_DIAMOND_WALLS + custom_materials = list(/datum/material/diamond = SHEET_MATERIAL_AMOUNT * 2) + +/obj/structure/tram/alt/bananium + name = "bananium tram" + desc = "A composite structure with bananium plating. Honk!" + icon = 'icons/turf/walls/bananium_wall.dmi' + icon_state = "bananium_wall-0" + base_icon_state = "bananium_wall" + mineral = /obj/item/stack/sheet/mineral/bananium + tram_wall_type = /obj/structure/tram/alt/bananium + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_BANANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_BANANIUM_WALLS + custom_materials = list(/datum/material/bananium = SHEET_MATERIAL_AMOUNT*2) + +/obj/structure/tram/alt/sandstone + name = "sandstone tram" + desc = "A composite structure with sandstone plating. Rough." + icon = 'icons/turf/walls/sandstone_wall.dmi' + icon_state = "sandstone_wall-0" + base_icon_state = "sandstone_wall" + mineral = /obj/item/stack/sheet/mineral/sandstone + tram_wall_type = /obj/structure/tram/alt/sandstone + explosion_block = 0 + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_SANDSTONE_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_SANDSTONE_WALLS + custom_materials = list(/datum/material/sandstone = SHEET_MATERIAL_AMOUNT*2) + +/obj/structure/tram/alt/uranium + article = "a" + name = "uranium tram" + desc = "A composite structure with uranium plating. This is probably a bad idea." + icon = 'icons/turf/walls/uranium_wall.dmi' + icon_state = "uranium_wall-0" + base_icon_state = "uranium_wall" + mineral = /obj/item/stack/sheet/mineral/uranium + tram_wall_type = /obj/structure/tram/alt/uranium + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_URANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_URANIUM_WALLS + custom_materials = list(/datum/material/uranium = SHEET_MATERIAL_AMOUNT*2) + + /// Mutex to prevent infinite recursion when propagating radiation pulses + var/active = null + + /// The last time a radiation pulse was performed + var/last_event = 0 + +/obj/structure/tram/alt/uranium/attackby(obj/item/W, mob/user, params) + radiate() + return ..() + +/obj/structure/tram/alt/uranium/attack_hand(mob/user, list/modifiers) + radiate() + return ..() + +/obj/structure/tram/alt/uranium/proc/radiate() + SIGNAL_HANDLER + if(active) + return + if(world.time <= last_event + 1.5 SECONDS) + return + active = TRUE + radiation_pulse( + src, + max_range = 3, + threshold = RAD_LIGHT_INSULATION, + chance = URANIUM_IRRADIATION_CHANCE, + minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME, + ) + propagate_radiation_pulse() + last_event = world.time + active = FALSE + +/obj/structure/tram/alt/plasma + name = "plasma tram" + desc = "A composite structure with plasma plating. This is definitely a bad idea." + icon = 'icons/turf/walls/plasma_wall.dmi' + icon_state = "plasma_wall-0" + base_icon_state = "plasma_wall" + mineral = /obj/item/stack/sheet/mineral/plasma + tram_wall_type = /obj/structure/tram/alt/plasma + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_PLASMA_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_PLASMA_WALLS + custom_materials = list(/datum/material/plasma = SHEET_MATERIAL_AMOUNT*2) + +/obj/structure/tram/alt/wood + name = "wooden tram" + desc = "A tram with wooden framing. Flammable. There's a reason we use metal now." + icon = 'icons/turf/walls/wood_wall.dmi' + icon_state = "wood_wall-0" + base_icon_state = "wood_wall" + mineral = /obj/item/stack/sheet/mineral/wood + tram_wall_type = /obj/structure/tram/alt/wood + explosion_block = 0 + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_WOOD_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_WOOD_WALLS + custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT*2) + +/obj/structure/tram/alt/wood/attackby(obj/item/W, mob/user) + if(W.get_sharpness() && W.force) + var/duration = ((4.8 SECONDS) / W.force) * 2 //In seconds, for now. + if(istype(W, /obj/item/hatchet) || istype(W, /obj/item/fireaxe)) + duration /= 4 //Much better with hatchets and axes. + if(do_after(user, duration * (1 SECONDS), target=src)) //Into deciseconds. + deconstruct(disassembled = FALSE) + return + return ..() + +/obj/structure/tram/alt/bamboo + name = "bamboo tram" + desc = "A tram with a bamboo framing." + icon = 'icons/turf/walls/bamboo_wall.dmi' + icon_state = "bamboo_wall-0" + base_icon_state = "wall" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_BAMBOO_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_BAMBOO_WALLS + mineral = /obj/item/stack/sheet/mineral/bamboo + tram_wall_type = /obj/structure/tram/alt/bamboo + +/obj/structure/tram/alt/iron + name = "rough iron tram" + desc = "A composite structure with rough iron plating." + icon = 'icons/turf/walls/iron_wall.dmi' + icon_state = "iron_wall-0" + base_icon_state = "iron_wall" + mineral = /obj/item/stack/rods + mineral_amount = 5 + tram_wall_type = /obj/structure/tram/alt/iron + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_IRON_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_IRON_WALLS + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 2.5) + +/obj/structure/tram/alt/abductor + name = "alien tram" + desc = "A composite structure made of some kind of alien alloy." + icon = 'icons/turf/walls/abductor_wall.dmi' + icon_state = "abductor_wall-0" + base_icon_state = "abductor_wall" + mineral = /obj/item/stack/sheet/mineral/abductor + tram_wall_type = /obj/structure/tram/alt/abductor + explosion_block = 3 + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_ABDUCTOR_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_ABDUCTOR_WALLS + custom_materials = list(/datum/material/alloy/alien = SHEET_MATERIAL_AMOUNT*2) + +/obj/structure/tram/get_dumping_location() + return null + +/obj/structure/tram/spoiler + name = "tram spoiler" + icon = 'icons/obj/tram/tram_structure.dmi' + desc = "Nanotrasen bought the luxury package under the impression titanium spoilers make the tram go faster. They're just for looks, or potentially stabbing anybody who gets in the way." + icon_state = "tram-spoiler-retracted" + max_integrity = 400 + obj_flags = CAN_BE_HIT + mineral = /obj/item/stack/sheet/mineral/titanium + girder_type = /obj/structure/girder/tram/corner + smoothing_flags = NONE + smoothing_groups = null + canSmoothWith = null + /// Position of the spoiler + var/deployed = FALSE + /// Malfunctioning due to tampering or emag + var/malfunctioning = FALSE + /// Weakref to the tram piece we control + var/datum/weakref/tram_ref + /// The tram we're attached to + var/tram_id = TRAMSTATION_LINE_1 + +/obj/structure/tram/spoiler/Initialize(mapload) + . = ..() + return INITIALIZE_HINT_LATELOAD + +/obj/structure/tram/spoiler/LateInitialize() + . = ..() + RegisterSignal(SStransport, COMSIG_TRANSPORT_ACTIVE, PROC_REF(set_spoiler)) + +/obj/structure/tram/spoiler/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(held_item?.tool_behaviour == TOOL_MULTITOOL && (obj_flags & EMAGGED)) + context[SCREENTIP_CONTEXT_LMB] = "repair" + + if(held_item?.tool_behaviour == TOOL_WELDER && atom_integrity >= max_integrity) + context[SCREENTIP_CONTEXT_LMB] = "[malfunctioning ? "repair" : "lock"]" + + return CONTEXTUAL_SCREENTIP_SET + +/obj/structure/tram/spoiler/examine(mob/user) + . = ..() + if(obj_flags & EMAGGED) + . += span_warning("The electronics panel is sparking occasionally. It can be reset with a [EXAMINE_HINT("multitool.")]") + + if(malfunctioning) + . += span_warning("The spoiler is [EXAMINE_HINT("welded")] in place!") + else + . += span_notice("The spoiler can be locked in to place with a [EXAMINE_HINT("welder.")]") + +/obj/structure/tram/spoiler/proc/set_spoiler(source, controller, controller_active, controller_status, travel_direction) + SIGNAL_HANDLER + + var/spoiler_direction = travel_direction + if(obj_flags & EMAGGED && !malfunctioning) + malfunctioning = TRUE + + if(malfunctioning || controller_status & COMM_ERROR) + if(!deployed) + // Bring out the blades + if(malfunctioning) + visible_message(span_danger("\the [src] locks up due to its servo overheating!")) + do_sparks(3, cardinal_only = FALSE, source = src) + deploy_spoiler() + return + + if(!controller_active) + return + + switch(spoiler_direction) + if(SOUTH, EAST) + switch(dir) + if(NORTH, EAST) + retract_spoiler() + if(SOUTH, WEST) + deploy_spoiler() + + if(NORTH, WEST) + switch(dir) + if(NORTH, EAST) + deploy_spoiler() + if(SOUTH, WEST) + retract_spoiler() + return + +/obj/structure/tram/spoiler/proc/deploy_spoiler() + if(deployed) + return + flick("tram-spoiler-deploying", src) + icon_state = "tram-spoiler-deployed" + deployed = TRUE + update_appearance() + +/obj/structure/tram/spoiler/proc/retract_spoiler() + if(!deployed) + return + flick("tram-spoiler-retracting", src) + icon_state = "tram-spoiler-retracted" + deployed = FALSE + update_appearance() + +/obj/structure/tram/spoiler/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + to_chat(user, span_warning("You short-circuit the [src]'s servo to overheat!"), type = MESSAGE_TYPE_INFO) + playsound(src, SFX_SPARKS, 100, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + do_sparks(5, cardinal_only = FALSE, source = src) + obj_flags |= EMAGGED + +/obj/structure/tram/spoiler/multitool_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return FALSE + + if(obj_flags & EMAGGED) + balloon_alert(user, "electronics reset!") + obj_flags &= ~EMAGGED + return TRUE + + return FALSE + +/obj/structure/tram/spoiler/welder_act(mob/living/user, obj/item/tool) + if(!tool.tool_start_check(user, amount = 1)) + return FALSE + + if(atom_integrity >= max_integrity) + to_chat(user, span_warning("You begin to weld \the [src], [malfunctioning ? "repairing damage" : "preventing retraction"].")) + if(!tool.use_tool(src, user, 4 SECONDS, volume = 50)) + return + malfunctioning = !malfunctioning + user.visible_message(span_warning("[user] [malfunctioning ? "welds \the [src] in place" : "repairs \the [src]"] with [tool]."), \ + span_warning("You finish welding \the [src], [malfunctioning ? "locking it in place." : "it can move freely again!"]"), null, COMBAT_MESSAGE_RANGE) + + if(malfunctioning) + deploy_spoiler() + + update_appearance() + return ITEM_INTERACT_SUCCESS + + to_chat(user, span_notice("You begin repairing [src]...")) + if(!tool.use_tool(src, user, 4 SECONDS, volume = 50)) + return + atom_integrity = max_integrity + to_chat(user, span_notice("You repair [src].")) + update_appearance() + return ITEM_INTERACT_SUCCESS + +/obj/structure/tram/spoiler/update_overlays() + . = ..() + if(deployed && malfunctioning) + . += mutable_appearance(icon, "tram-spoiler-welded") + +/obj/structure/chair/sofa/bench/tram + name = "bench" + desc = "Perfectly designed to be comfortable to sit on, and hellish to sleep on." + icon_state = "bench_middle" + greyscale_config = /datum/greyscale_config/bench_middle + greyscale_colors = COLOR_TRAM_BLUE + +/obj/structure/chair/sofa/bench/tram/left + icon_state = "bench_left" + greyscale_config = /datum/greyscale_config/bench_left + +/obj/structure/chair/sofa/bench/tram/right + icon_state = "bench_right" + greyscale_config = /datum/greyscale_config/bench_right + +/obj/structure/chair/sofa/bench/tram/corner + icon_state = "bench_corner" + greyscale_config = /datum/greyscale_config/bench_corner + +/obj/structure/chair/sofa/bench/tram/solo + icon_state = "bench_solo" + greyscale_config = /datum/greyscale_config/bench_solo diff --git a/code/modules/transport/transport_module.dm b/code/modules/transport/transport_module.dm new file mode 100644 index 0000000000000..89a7333865e17 --- /dev/null +++ b/code/modules/transport/transport_module.dm @@ -0,0 +1,943 @@ +/** + * Base transport structure. A single tile that can form a modular set with neighbouring tiles + * This type holds elevators and trams + */ +/obj/structure/transport/linear + name = "linear transport module" + desc = "A lightweight lift platform. It moves." + icon = 'icons/obj/smooth_structures/catwalk.dmi' + icon_state = "catwalk-0" + base_icon_state = "catwalk" + density = FALSE + anchored = TRUE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + armor_type = /datum/armor/transport_module + max_integrity = 50 + layer = TRAM_FLOOR_LAYER + plane = FLOOR_PLANE + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_INDUSTRIAL_LIFT + canSmoothWith = SMOOTH_GROUP_INDUSTRIAL_LIFT + obj_flags = BLOCK_Z_OUT_DOWN + appearance_flags = PIXEL_SCALE|KEEP_TOGETHER //no TILE_BOUND since we're potentially multitile + // If we don't do this, we'll build our overlays early, and fuck up how we're rendered + blocks_emissive = EMISSIVE_BLOCK_NONE + + ///ID used to determine what transport types we can merge with + var/transport_id = TRANSPORT_TYPE_ELEVATOR + + ///if true, the elevator works through floors + var/pass_through_floors = FALSE + + ///what movables on our platform that we are moving + var/list/atom/movable/transport_contents = list() + ///weakrefs to the contents we have when we're first created. stored so that admins can clear the tram to its initial state + ///if someone put a bunch of stuff onto it. + var/list/datum/weakref/initial_contents = list() + + ///what glide_size we set our moving contents to. + var/glide_size_override = 8 + ///movables inside transport_contents who had their glide_size changed since our last movement. + ///used so that we dont have to change the glide_size of every object every movement, which scales to cost more than you'd think + var/list/atom/movable/changed_gliders = list() + + ///decisecond delay between horizontal movements. cannot make the tram move faster than 1 movement per world.tick_lag. only used to give to the transport_controller + var/speed_limiter = 0.5 + + ///master datum that controls our movement. in general /transport/linear subtypes control moving themselves, and + /// /datum/transport_controller instances control moving the entire tram and any behavior associated with that. + var/datum/transport_controller/linear/transport_controller_datum + ///what subtype of /datum/transport_controller to create for itself if no other platform on this tram has created one yet. + ///very important for some behaviors since + var/transport_controller_type = /datum/transport_controller/linear + + ///how many tiles this platform extends on the x axis + var/width = 1 + ///how many tiles this platform extends on the y axis (north-south not up-down, that would be the z axis) + var/height = 1 + + ///if TRUE, this platform will late initialize and then expand to become a multitile object across all other linked platforms on this z level + var/create_modular_set = FALSE + + /// Does our elevator warn people (with visual effects) when moving down? + var/warns_on_down_movement = FALSE + /// if TRUE, we will gib anyone we land on top of. if FALSE, we will just apply damage with a serious wound penalty. + var/violent_landing = TRUE + /// damage multiplier if a mob is hit by the lift while it is moving horizontally + var/collision_lethality = 1 + /// How long does it take for the elevator to move vertically? + var/elevator_vertical_speed = 2 SECONDS + + /// We use a radial to travel primarily, instead of a button / ui + var/radial_travel = TRUE + /// A lazylist of REFs to all mobs which have a radial open currently + var/list/current_operators + +/datum/armor/transport_module + melee = 80 + bullet = 90 + bomb = 70 + fire = 100 + acid = 100 + +/obj/structure/transport/linear/Initialize(mapload) + . = ..() + // Yes if it's VV'd it won't be accurate but it probably shouldn't ever be + if(radial_travel) + AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Send Transport") + + ADD_TRAIT(src, TRAIT_CHASM_STOPPER, INNATE_TRAIT) + set_movement_registrations() + + //since transport_controller datums find all connected platforms when a transport structure first creates it and then + //sets those platforms' transport_controller_datum to itself, this check will only evaluate to true once per tram platform + if(!transport_controller_datum && transport_controller_type) + transport_controller_datum = new transport_controller_type(src) + return INITIALIZE_HINT_LATELOAD + +/obj/structure/transport/linear/LateInitialize() + . = ..() + //after everything is initialized the transport controller can order everything + transport_controller_datum.order_platforms_by_z_level() + +/obj/structure/transport/linear/Destroy() + transport_controller_datum = null + return ..() + + +///set the movement registrations to our current turf(s) so contents moving out of our tile(s) are removed from our movement lists +/obj/structure/transport/linear/proc/set_movement_registrations(list/turfs_to_set) + for(var/turf/turf_loc as anything in turfs_to_set || locs) + RegisterSignal(turf_loc, COMSIG_ATOM_EXITED, PROC_REF(uncrossed_remove_item_from_transport)) + RegisterSignals(turf_loc, list(COMSIG_ATOM_ENTERED,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON), PROC_REF(add_item_on_transport)) + +///unset our movement registrations from turfs that no longer contain us (or every loc if turfs_to_unset is unspecified) +/obj/structure/transport/linear/proc/unset_movement_registrations(list/turfs_to_unset) + var/static/list/registrations = list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON) + for(var/turf/turf_loc as anything in turfs_to_unset || locs) + UnregisterSignal(turf_loc, registrations) + + +/obj/structure/transport/linear/proc/uncrossed_remove_item_from_transport(datum/source, atom/movable/gone, direction) + SIGNAL_HANDLER + if(!(gone.loc in locs)) + remove_item_from_transport(gone) + +/obj/structure/transport/linear/proc/remove_item_from_transport(atom/movable/potential_rider) + SIGNAL_HANDLER + if(!(potential_rider in transport_contents)) + return + if(isliving(potential_rider) && HAS_TRAIT(potential_rider, TRAIT_CANNOT_BE_UNBUCKLED)) + REMOVE_TRAIT(potential_rider, TRAIT_CANNOT_BE_UNBUCKLED, BUCKLED_TRAIT) + + transport_contents -= potential_rider + changed_gliders -= potential_rider + + UnregisterSignal(potential_rider, list(COMSIG_QDELETING, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE)) + +/obj/structure/transport/linear/proc/add_item_on_transport(datum/source, atom/movable/new_transport_contents) + SIGNAL_HANDLER + var/static/list/blacklisted_types = typecacheof(list(/obj/structure/fluff/tram_rail, /obj/effect/decal/cleanable, /obj/structure/transport/linear, /mob/camera)) + if(is_type_in_typecache(new_transport_contents, blacklisted_types) || new_transport_contents.invisibility == INVISIBILITY_ABSTRACT) //prevents the tram from stealing things like landmarks + return FALSE + if(new_transport_contents in transport_contents) + return FALSE + + if(isliving(new_transport_contents) && !HAS_TRAIT(new_transport_contents, TRAIT_CANNOT_BE_UNBUCKLED)) + ADD_TRAIT(new_transport_contents, TRAIT_CANNOT_BE_UNBUCKLED, BUCKLED_TRAIT) + + transport_contents += new_transport_contents + RegisterSignal(new_transport_contents, COMSIG_QDELETING, PROC_REF(remove_item_from_transport)) + + return TRUE + +///adds everything on our tile that can be added to our transport_contents and initial_contents lists when we're created +/obj/structure/transport/linear/proc/add_initial_contents() + for(var/turf/turf_loc in locs) + for(var/atom/movable/movable_contents as anything in turf_loc) + if(movable_contents == src) + continue + + if(add_item_on_transport(src, movable_contents)) + + var/datum/weakref/new_initial_contents = WEAKREF(movable_contents) + if(!new_initial_contents) + continue + + initial_contents += new_initial_contents + +///signal handler for COMSIG_MOVABLE_UPDATE_GLIDE_SIZE: when a movable in transport_contents changes its glide_size independently. +///adds that movable to a lazy list, movables in that list have their glide_size updated when the tram next moves +/obj/structure/transport/linear/proc/on_changed_glide_size(atom/movable/moving_contents, new_glide_size) + SIGNAL_HANDLER + if(new_glide_size != glide_size_override) + changed_gliders += moving_contents + + +///make this tram platform multitile, expanding to cover all the tram platforms adjacent to us and deleting them. makes movement more efficient. +///the platform becoming multitile should be in the lower left corner since thats assumed to be the loc of multitile objects +/obj/structure/transport/linear/proc/create_modular_set(min_x, min_y, max_x, max_y, z) + + if(!(min_x && min_y && max_x && max_y && z)) + for(var/obj/structure/transport/linear/other_transport as anything in transport_controller_datum.transport_modules) + if(other_transport.z != z) + continue + + min_x = min(min_x, other_transport.x) + max_x = max(max_x, other_transport.x) + + min_y = min(min_y, other_transport.y) + max_y = max(max_y, other_transport.y) + + var/turf/lower_left_corner = locate(min_x, min_y, z) + var/obj/structure/transport/linear/primary_module = locate() in lower_left_corner + + if(!primary_module) + stack_trace("no lift in the lower left corner of a lift level!") + return FALSE + + if(primary_module != src) + //the loc of a multitile object must always be the lower left corner + return primary_module.create_modular_set() + + width = (max_x - min_x) + 1 + height = (max_y - min_y) + 1 + + ///list of turfs we dont go over. if for whatever reason we encounter an already multitile lift platform + ///we add all of its locs to this list so we dont add that lift platform multiple times as we iterate through its locs + var/list/locs_to_skip = locs.Copy() + + bound_width = bound_width * width + bound_height = bound_height * height + + //multitile movement code assumes our loc is on the lower left corner of our bounding box + + var/first_x = 0 + var/first_y = 0 + + var/last_x = max(max_x - min_x, 0) + var/last_y = max(max_y - min_y, 0) + + for(var/y in first_y to last_y) + + var/y_pixel_offset = world.icon_size * y + + for(var/x in first_x to last_x) + + var/x_pixel_offset = world.icon_size * x + + var/turf/set_turf = locate(x + min_x, y + min_y, z) + + if(!set_turf) + continue + + if(set_turf in locs_to_skip) + continue + + var/obj/structure/transport/linear/other_transport = locate() in set_turf + + if(!other_transport) + continue + + locs_to_skip += other_transport.locs.Copy()//make sure we never go over multitile platforms multiple times + + other_transport.pixel_x = x_pixel_offset + other_transport.pixel_y = y_pixel_offset + + overlays += other_transport + + //now we vore all the other lifts connected to us on our z level + for(var/obj/structure/transport/linear/other_transport in transport_controller_datum.transport_modules) + if(other_transport == src || other_transport.z != z) + continue + + transport_controller_datum.transport_modules -= other_transport + if(other_transport.transport_contents) + transport_contents |= other_transport.transport_contents + if(other_transport.initial_contents) + initial_contents |= other_transport.initial_contents + + qdel(other_transport) + + transport_controller_datum.create_modular_set = TRUE + + var/turf/old_loc = loc + + forceMove(locate(min_x, min_y, z))//move to the lower left corner + set_movement_registrations(locs - old_loc) + blocks_emissive = EMISSIVE_BLOCK_GENERIC + update_appearance() + return TRUE + +///returns an unordered list of all lift platforms adjacent to us. used so our transport_controller_datum can control all connected platforms. +///includes platforms directly above or below us as well. only includes platforms with an identical transport_id to our own. +/obj/structure/transport/linear/proc/module_adjacency(datum/transport_controller/transport_controller_datum) + . = list() + for(var/direction in GLOB.cardinals_multiz) + var/obj/structure/transport/linear/neighbor = locate() in get_step_multiz(src, direction) + if(!neighbor || neighbor.transport_id != transport_id) + continue + . += neighbor + +///main proc for moving the lift in the direction [travel_direction]. handles horizontal and/or vertical movement for multi platformed lifts and multitile lifts. +/obj/structure/transport/linear/proc/travel(travel_direction) + var/list/things_to_move = transport_contents + var/turf/destination + if(!isturf(travel_direction)) + destination = get_step_multiz(src, travel_direction) + else + destination = travel_direction + travel_direction = get_dir_multiz(loc, travel_direction) + + var/x_offset = ROUND_UP(bound_width / 32) - 1 //how many tiles our horizontally farthest edge is from us + var/y_offset = ROUND_UP(bound_height / 32) - 1 //how many tiles our vertically farthest edge is from us + + //the x coordinate of the edge furthest from our future destination, which would be our right hand side + var/back_edge_x = destination.x + x_offset//if we arent multitile this should just be destination.x + var/upper_edge_y = destination.y + y_offset + + var/turf/upper_right_corner = locate(min(world.maxx, back_edge_x), min(world.maxy, upper_edge_y), destination.z) + + var/list/dest_locs = block( + destination, + upper_right_corner + ) + + var/list/entering_locs = dest_locs - locs + var/list/exited_locs = locs - dest_locs + + if(travel_direction == DOWN) + for(var/turf/dest_turf as anything in entering_locs) + SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move) + + if(iswallturf(dest_turf)) + var/turf/closed/wall/hit_wall = dest_turf + do_sparks(2, FALSE, hit_wall) + hit_wall.dismantle_wall(devastated = TRUE) + for(var/mob/nearby_witness in urange(8, src)) + shake_camera(nearby_witness, 2, 3) + playsound(hit_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE) + + for(var/mob/living/crushed in dest_turf.contents) + to_chat(crushed, span_userdanger("You are crushed by [src]!")) + if(violent_landing) + // Violent landing = gibbed. But the nicest kind of gibbing, keeping everything intact. + crushed.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS) + crushed.gib(DROP_ALL_REMAINS) + else + // Less violent landing simply crushes every bone in your body. + crushed.Paralyze(30 SECONDS, ignore_canstun = TRUE) + crushed.apply_damage(30, BRUTE, BODY_ZONE_CHEST, wound_bonus = 30) + crushed.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = 25) + crushed.apply_damage(15, BRUTE, BODY_ZONE_L_LEG, wound_bonus = 15) + crushed.apply_damage(15, BRUTE, BODY_ZONE_R_LEG, wound_bonus = 15) + crushed.apply_damage(15, BRUTE, BODY_ZONE_L_ARM, wound_bonus = 15) + crushed.apply_damage(15, BRUTE, BODY_ZONE_R_ARM, wound_bonus = 15) + + else if(travel_direction == UP) + for(var/turf/dest_turf as anything in entering_locs) + ///handles any special interactions objects could have with the lift/tram, handled on the item itself + SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move) + + if(iswallturf(dest_turf)) + var/turf/closed/wall/hit_wall = dest_turf + do_sparks(2, FALSE, hit_wall) + hit_wall.dismantle_wall(devastated = TRUE) + for(var/mob/client_mob in SSspatial_grid.orthogonal_range_search(src, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS, 8)) + shake_camera(client_mob, 2, 3) + playsound(hit_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE) + + else + ///potentially finds a spot to throw the victim at for daring to be hit by a tram. is null if we havent found anything to throw + var/atom/throw_target + + for(var/turf/dest_turf as anything in entering_locs) + ///handles any special interactions objects could have with the lift/tram, handled on the item itself + SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move) + + if(iswallturf(dest_turf)) + var/turf/closed/wall/collided_wall = dest_turf + do_sparks(2, FALSE, collided_wall) + collided_wall.dismantle_wall(devastated = TRUE) + for(var/mob/client_mob in SSspatial_grid.orthogonal_range_search(collided_wall, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS, 8)) + shake_camera(client_mob, duration = 2, strength = 3) + + playsound(collided_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE) + + if(ismineralturf(dest_turf)) + var/turf/closed/mineral/dest_mineral_turf = dest_turf + for(var/mob/client_mob in SSspatial_grid.orthogonal_range_search(dest_mineral_turf, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS, 8)) + shake_camera(client_mob, duration = 2, strength = 3) + dest_mineral_turf.gets_drilled(give_exp = FALSE) + + for(var/obj/structure/victim_structure in dest_turf.contents) + if(QDELING(victim_structure)) + continue + if(!is_type_in_typecache(victim_structure, transport_controller_datum.ignored_smashthroughs) && victim_structure.layer >= LOW_OBJ_LAYER) + + if(victim_structure.anchored && initial(victim_structure.anchored) == TRUE) + visible_message(span_danger("[src] smashes through [victim_structure]!")) + victim_structure.deconstruct(FALSE) + + else + if(!throw_target) + throw_target = get_edge_target_turf(src, turn(travel_direction, pick(45, -45))) + visible_message(span_danger("[src] violently rams [victim_structure] out of the way!")) + victim_structure.anchored = FALSE + victim_structure.take_damage(rand(20, 25) * collision_lethality) + victim_structure.throw_at(throw_target, 200 * collision_lethality, 4 * collision_lethality) + + for(var/obj/machinery/victim_machine in dest_turf.contents) + if(QDELING(victim_machine)) + continue + if(is_type_in_typecache(victim_machine, transport_controller_datum.ignored_smashthroughs)) + continue + if(istype(victim_machine, /obj/machinery/field)) //graceful break handles this scenario + continue + if(victim_machine.layer >= LOW_OBJ_LAYER) //avoids stuff that is probably flush with the ground + playsound(src, 'sound/effects/bang.ogg', 50, TRUE) + visible_message(span_danger("[src] smashes through [victim_machine]!")) + qdel(victim_machine) + + for(var/mob/living/victim_living in dest_turf.contents) + var/damage_multiplier = victim_living.maxHealth * 0.01 + var/extra_ouch = FALSE // if emagged you're gonna have a really bad time + if(speed_limiter == 0.5) // slow trams don't cause extra damage + for(var/obj/structure/tram/spoiler/my_spoiler in transport_contents) + if(get_dist(my_spoiler, victim_living) != 1) + continue + + if(my_spoiler.deployed) + extra_ouch = TRUE + break + + if(transport_controller_datum.ignored_smashthroughs[victim_living.type]) + continue + to_chat(victim_living, span_userdanger("[src] collides into you!")) + playsound(src, 'sound/effects/splat.ogg', 50, TRUE) + var/damage = 0 + switch(extra_ouch) + if(TRUE) + playsound(src, 'sound/effects/grillehit.ogg', 50, TRUE) + var/obj/item/bodypart/head/head = victim_living.get_bodypart("head") + if(head) + log_combat(src, victim_living, "beheaded") + head.dismember() + victim_living.regenerate_icons() + add_overlay(mutable_appearance(icon, "blood_overlay")) + register_collision(points = 3) + + if(FALSE) + log_combat(src, victim_living, "collided with") + if(prob(15)) //sorry buddy, luck wasn't on your side + damage = 29 * collision_lethality * damage_multiplier + else + damage = rand(7, 21) * collision_lethality * damage_multiplier + victim_living.apply_damage(2 * damage, BRUTE, BODY_ZONE_HEAD, wound_bonus = 7) + victim_living.apply_damage(3 * damage, BRUTE, BODY_ZONE_CHEST, wound_bonus = 21) + victim_living.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_LEG, wound_bonus = 14) + victim_living.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_LEG, wound_bonus = 14) + victim_living.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_ARM, wound_bonus = 14) + victim_living.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_ARM, wound_bonus = 14) + + if(QDELETED(victim_living)) //in case it was a mob that dels on death + continue + if(!throw_target) + throw_target = get_edge_target_turf(src, turn(travel_direction, pick(45, -45))) + + var/turf/turf_to_bloody = get_turf(victim_living) + turf_to_bloody.add_mob_blood(victim_living) + + victim_living.throw_at() + //if travel_direction EAST, will turn to the NORTHEAST or SOUTHEAST and throw the ran over guy away + var/datum/callback/land_slam = new(victim_living, TYPE_PROC_REF(/mob/living/, tram_slam_land)) + victim_living.throw_at(throw_target, 200 * collision_lethality, 4 * collision_lethality, callback = land_slam) + + //increment the hit counters + if(ismob(victim_living) && victim_living.client && istype(transport_controller_datum, /datum/transport_controller/linear/tram)) + register_collision(points = 1) + + unset_movement_registrations(exited_locs) + group_move(things_to_move, travel_direction) + set_movement_registrations(entering_locs) + +/obj/structure/transport/linear/proc/register_collision(points = 1) + SSpersistence.tram_hits_this_round += points + SSblackbox.record_feedback("amount", "tram_collision", points) + var/datum/transport_controller/linear/tram/tram_controller = transport_controller_datum + ASSERT(istype(tram_controller)) + tram_controller.register_collision(points) + +///move the movers list of movables on our tile to destination if we successfully move there first. +///this is like calling forceMove() on everything in movers and ourselves, except nothing in movers +///has destination.Entered() and origin.Exited() called on them, as only our movement can be perceived. +///none of the movers are able to react to the movement of any other mover, saving a lot of needless processing cost +///and is more sensible. without this, if you and a banana are on the same platform, when that platform moves you will slip +///on the banana even if youre not moving relative to it. +/obj/structure/transport/linear/proc/group_move(list/atom/movable/movers, movement_direction) + if(movement_direction == NONE) + stack_trace("a transport was told to move to somewhere it already is!") + return FALSE + + var/turf/our_dest = get_step(src, movement_direction) + + //vars updated per mover + var/turf/mover_old_loc + var/turf/mover_old_area + + var/turf/mover_new_loc + var/turf/mover_new_area + + if(glide_size != glide_size_override) + set_glide_size(glide_size_override) + + forceMove(our_dest) + if(loc != our_dest || QDELETED(src))//check if our movement succeeded, if it didnt then the movers cant be moved + return FALSE + + for(var/atom/movable/mover as anything in changed_gliders) + if(QDELETED(mover)) + movers -= mover + continue + + if(mover.glide_size != glide_size_override) + mover.set_glide_size(glide_size_override) + + changed_gliders.Cut() + + for(var/atom/movable/mover as anything in movers) + if(QDELETED(mover)) + movers -= mover + continue + + //another O(n) set of read operations, not ideal given datum var read times. + //ideally we would only need to do this check once per tile with contents (which is constant per tram, while contents can scale infinitely) + //and then the only O(n) process is calling these procs for each contents that actually changes areas + //but that approach is probably a lot buggier. itd be nice to have it figured out though + mover_old_loc = mover.loc + mover_old_area = mover_old_loc.loc + + mover.loc = (mover_new_loc = get_step(mover, movement_direction)) + mover_new_area = mover_new_loc.loc + + + if(mover_old_area != mover_new_area) + mover_old_area.Exited(mover, movement_direction) + mover_new_area.Entered(mover, mover_new_area) + + mover.Moved(mover_old_loc, movement_direction, TRUE, null, FALSE) + + + return TRUE + +/** + * reset the contents of this lift platform to its original state in case someone put too much shit on it. + * everything that is considered foreign is deleted, you can configure what is considered foreign. + * + * used by an admin via calling reset_lift_contents() on our transport_controller_datum. + * + * Arguments: + * * consider_anything_past - number. if > 0 this platform will only handle foreign contents that exceed this number on each of our locs + * * foreign_objects - bool. if true this platform will consider /atom/movable's that arent mobs as part of foreign contents + * * foreign_non_player_mobs - bool. if true we consider mobs that dont have a mind to be foreign + * * consider_player_mobs - bool. if true we consider player mobs to be foreign. only works if foreign_non_player_mobs is true as well + */ +/obj/structure/transport/linear/proc/reset_contents(consider_anything_past = 0, foreign_objects = TRUE, foreign_non_player_mobs = TRUE, consider_player_mobs = FALSE) + if(!foreign_objects && !foreign_non_player_mobs && !consider_player_mobs) + return FALSE + + consider_anything_past = isnum(consider_anything_past) ? max(consider_anything_past, 0) : 0 + //just in case someone fucks up the arguments + + if(consider_anything_past && length(transport_contents) <= consider_anything_past) + return FALSE + + ///list of resolve()'d initial_contents that are still in transport_contents + var/list/atom/movable/original_contents = list(src) + + ///list of objects we consider foreign according to the given arguments + var/list/atom/movable/foreign_contents = list() + + + for(var/datum/weakref/initial_contents_ref as anything in initial_contents) + if(!initial_contents_ref) + continue + + var/atom/movable/resolved_contents = initial_contents_ref.resolve() + + if(!resolved_contents) + continue + + if(!(resolved_contents in transport_contents)) + continue + + original_contents += resolved_contents + + for(var/turf/turf_loc as anything in locs) + var/list/atom/movable/foreign_contents_in_loc = list() + + for(var/atom/movable/foreign_movable as anything in (turf_loc.contents - original_contents)) + if(foreign_objects && ismovable(foreign_movable) && !ismob(foreign_movable) && !istype(foreign_movable, /obj/effect/landmark/transport/nav_beacon)) + foreign_contents_in_loc += foreign_movable + continue + + if(foreign_non_player_mobs && ismob(foreign_movable)) + var/mob/foreign_mob = foreign_movable + if(consider_player_mobs || !foreign_mob.mind) + foreign_contents_in_loc += foreign_mob + continue + + if(consider_anything_past) + foreign_contents_in_loc.len -= consider_anything_past + //hey cool this works, neat. this takes from the opposite side of the list that youd expect but its easy so idc + //also this means that if you use consider_anything_past then foreign mobs are less likely to be deleted than foreign objects + //because internally the contents list is 2 linked lists of obj contents - mob contents, thus mobs are always last in the order + //when you iterate it. + + foreign_contents += foreign_contents_in_loc + + for(var/atom/movable/contents_to_delete as anything in foreign_contents) + qdel(contents_to_delete) + + return TRUE + +/// Callback / general proc to check if the lift is usable by the passed mob. +/obj/structure/transport/linear/proc/can_open_lift_radial(mob/living/user, starting_position) + // Gotta be a living mob + if(!isliving(user)) + return FALSE + // Gotta be awake and aware + if(user.incapacitated()) + return FALSE + // Maintain the god given right to fight an elevator + if(user.combat_mode) + return FALSE + // Gotta be by the lift + if(!user.Adjacent(src)) + return FALSE + // If the lift moves while the radial is open close that shit + if(starting_position != loc) + return FALSE + + return TRUE + +/// Opens the radial for the lift, allowing the user to move it around. +/obj/structure/transport/linear/proc/open_lift_radial(mob/living/user) + var/starting_position = loc + if(!can_open_lift_radial(user, starting_position)) + return + // One radial per person + for(var/obj/structure/transport/linear/other_platform as anything in transport_controller_datum.transport_modules) + if(REF(user) in other_platform.current_operators) + return + + + var/list/possible_directions = list() + if(transport_controller_datum.Check_lift_move(UP)) + var/static/image/up_arrow + if(!up_arrow) + up_arrow = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH) + + possible_directions["Up"] = up_arrow + + if(transport_controller_datum.Check_lift_move(DOWN)) + var/static/image/down_arrow + if(!down_arrow) + down_arrow = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH) + + possible_directions["Down"] = down_arrow + + add_fingerprint(user) + if(!length(possible_directions)) + balloon_alert(user, "elevator out of service!") + return + + LAZYADD(current_operators, REF(user)) + var/result = show_radial_menu( + user = user, + anchor = src, + choices = possible_directions, + custom_check = CALLBACK(src, PROC_REF(can_open_lift_radial), user, starting_position), + require_near = TRUE, + tooltips = TRUE, + ) + + LAZYREMOVE(current_operators, REF(user)) + if(!can_open_lift_radial(user, starting_position)) + return //nice try + if(!isnull(result) && result != "Cancel" && transport_controller_datum.controller_status & CONTROLS_LOCKED) + // Only show this message if they actually wanted to move + balloon_alert(user, "elevator controls locked!") + return + switch(result) + if("Up") + // We have to make sure that they don't do illegal actions + // by not having their radial menu refresh from someone else moving the lift. + if(!transport_controller_datum.simple_move_wrapper(UP, elevator_vertical_speed, user)) + return + + show_fluff_message(UP, user) + open_lift_radial(user) + + if("Down") + if(!transport_controller_datum.simple_move_wrapper(DOWN, elevator_vertical_speed, user)) + return + + show_fluff_message(DOWN, user) + open_lift_radial(user) + + if("Cancel") + return + +/** + * Proc to ensure that the radial menu closes when it should. + * Arguments: + * * user - The person that opened the menu. + * * starting_loc - The location of the lift when the menu was opened, used to prevent the menu from being interacted with after the lift was moved by someone else. + * + * Returns: + * * boolean, FALSE if the menu should be closed, TRUE if the menu is clear to stay opened. + */ +/obj/structure/transport/linear/proc/check_menu(mob/user, starting_loc) + if(user.incapacitated() || !user.Adjacent(src) || starting_loc != src.loc) + return FALSE + return TRUE + +/obj/structure/transport/linear/attack_hand(mob/user, list/modifiers) + . = ..() + if(.) + return + if(!radial_travel) + return ..() + + return open_lift_radial(user) + +//ai probably shouldn't get to use lifts but they sure are great for admins to crush people with +/obj/structure/transport/linear/attack_ghost(mob/user) + . = ..() + if(.) + return + if(!radial_travel) + return + if(!isAdminGhostAI(user)) + return + + return open_lift_radial(user) + +/obj/structure/transport/linear/attack_paw(mob/user, list/modifiers) + if(!radial_travel) + return ..() + + return open_lift_radial(user) + +/obj/structure/transport/linear/attackby(obj/item/attacking_item, mob/user, params) + if(!radial_travel) + return ..() + + return open_lift_radial(user) + +/obj/structure/transport/linear/attack_robot(mob/living/user) + if(!radial_travel) + return ..() + + return open_lift_radial(user) + +/** + * Shows a message indicating that the lift has moved up or down. + * Arguments: + * * direction - What direction are we going + * * user - The mob that caused the lift to move, for the visible message. + */ +/obj/structure/transport/linear/proc/show_fluff_message(direction, mob/user) + if(direction == UP) + user.visible_message(span_notice("[user] moves the lift upwards."), span_notice("You move the lift upwards.")) + + if(direction == DOWN) + user.visible_message(span_notice("[user] moves the lift downwards."), span_notice("You move the lift downwards.")) + +// A subtype intended for "public use" +/obj/structure/transport/linear/public + icon = 'icons/turf/floors.dmi' + icon_state = "rockvault" + base_icon_state = null + smoothing_flags = NONE + smoothing_groups = null + canSmoothWith = null + warns_on_down_movement = TRUE + violent_landing = FALSE + elevator_vertical_speed = 3 SECONDS + radial_travel = FALSE + +/obj/structure/transport/linear/debug + name = "transport platform" + desc = "A lightweight platform. It moves in any direction, except up and down." + color = "#5286b9ff" + transport_id = TRANSPORT_TYPE_DEBUG + radial_travel = TRUE + +/obj/structure/transport/linear/debug/open_lift_radial(mob/living/user) + var/starting_position = loc + if (!can_open_lift_radial(user,starting_position)) + return +//NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST + var/static/list/tool_list = list( + "NORTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH), + "NORTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH), + "EAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST), + "SOUTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST), + "SOUTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH), + "SOUTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH), + "WEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST), + "NORTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST) + ) + + var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, PROC_REF(can_open_lift_radial), user, starting_position), require_near = TRUE, tooltips = FALSE) + if (!can_open_lift_radial(user,starting_position)) + return // nice try + if(!isnull(result) && result != "Cancel" && transport_controller_datum.controller_status & CONTROLS_LOCKED) + // Only show this message if they actually wanted to move + balloon_alert(user, "elevator controls locked!") + return + + switch(result) + if("NORTH") + transport_controller_datum.move_transport_horizontally(NORTH) + open_lift_radial(user) + if("NORTHEAST") + transport_controller_datum.move_transport_horizontally(NORTHEAST) + open_lift_radial(user) + if("EAST") + transport_controller_datum.move_transport_horizontally(EAST) + open_lift_radial(user) + if("SOUTHEAST") + transport_controller_datum.move_transport_horizontally(SOUTHEAST) + open_lift_radial(user) + if("SOUTH") + transport_controller_datum.move_transport_horizontally(SOUTH) + open_lift_radial(user) + if("SOUTHWEST") + transport_controller_datum.move_transport_horizontally(SOUTHWEST) + open_lift_radial(user) + if("WEST") + transport_controller_datum.move_transport_horizontally(WEST) + open_lift_radial(user) + if("NORTHWEST") + transport_controller_datum.move_transport_horizontally(NORTHWEST) + open_lift_radial(user) + if("Cancel") + return + + add_fingerprint(user) + +/obj/structure/transport/linear/tram + name = "tram subfloor" + desc = "The subfloor lattice of the tram. You can build a tram wall frame by using titanium sheets, or place down thermoplastic tram floor tiles." + icon = 'icons/obj/tram/tram_structure.dmi' + icon_state = "subfloor" + base_icon_state = null + density = FALSE + layer = TRAM_STRUCTURE_LAYER + smoothing_flags = NONE + smoothing_groups = null + canSmoothWith = null + //the modular structure is pain to work with, damage is done to the floor on top + transport_id = TRANSPORT_TYPE_TRAM + transport_controller_type = /datum/transport_controller/linear/tram + radial_travel = FALSE + obj_flags = NONE + /// Set by the tram control console in late initialize + var/travelling = FALSE + + /// Do we want this transport to link with nearby modules to make a multi-tile platform + create_modular_set = TRUE + +/obj/structure/transport/linear/tram/corner/northwest + icon_state = "subfloor-corner-nw" + +/obj/structure/transport/linear/tram/corner/southwest + icon_state = "subfloor-corner-sw" + +/obj/structure/transport/linear/tram/corner/northeast + icon_state = "subfloor-corner-ne" + +/obj/structure/transport/linear/tram/corner/southeast + icon_state = "subfloor-corner-se" + +/obj/structure/transport/linear/tram/add_item_on_transport(datum/source, atom/movable/item) + . = ..() + if(travelling) + on_changed_glide_size(item, item.glide_size) + +/obj/structure/transport/linear/tram/proc/set_travelling(travelling) + if (src.travelling == travelling) + return + + for(var/atom/movable/glider as anything in transport_contents) + if(travelling) + glider.set_glide_size(glide_size_override) + RegisterSignal(glider, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(on_changed_glide_size)) + else + changed_gliders -= glider + UnregisterSignal(glider, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE) + + src.travelling = travelling + SEND_SIGNAL(src, COMSIG_TRANSPORT_ACTIVE, travelling) + +/obj/structure/transport/linear/tram/set_currently_z_moving() + return FALSE //trams can never z fall and shouldnt waste any processing time trying to do so + +/** + * Handles unlocking the tram controls for use after moving + * + * More safety checks to make sure the tram has actually docked properly + * at a location before users are allowed to interact with the tram console again. + * Tram finds its location at this point before fully unlocking controls to the user. + */ +/obj/structure/transport/linear/tram/proc/unlock_controls() + for(var/obj/structure/transport/linear/tram/tram_part as anything in transport_controller_datum.transport_modules) //only thing everyone needs to know is the new location. + tram_part.set_travelling(FALSE) + transport_controller_datum.controls_lock(FALSE) + +///debug proc to highlight the locs of the tram platform +/obj/structure/transport/linear/tram/proc/find_dimensions(iterations = 100) + message_admins("num turfs: [length(locs)]") + + var/overlay = /obj/effect/overlay/ai_detect_hud + var/list/turfs = list() + + for(var/turf/our_turf as anything in locs) + new overlay(our_turf) + turfs += our_turf + + addtimer(CALLBACK(src, PROC_REF(clear_turfs), turfs, iterations), 1) + +/obj/structure/transport/linear/tram/proc/clear_turfs(list/turfs_to_clear, iterations) + for(var/turf/our_old_turf as anything in turfs_to_clear) + var/obj/effect/overlay/ai_detect_hud/hud = locate() in our_old_turf + if(hud) + qdel(hud) + + var/overlay = /obj/effect/overlay/ai_detect_hud + + for(var/turf/our_turf as anything in locs) + new overlay(our_turf) + + iterations-- + + var/list/turfs = list() + for(var/turf/our_turf as anything in locs) + turfs += our_turf + + if(iterations) + addtimer(CALLBACK(src, PROC_REF(clear_turfs), turfs, iterations), 1) + +/obj/structure/transport/linear/tram/proc/estop_throw(throw_direction) + for(var/mob/living/passenger in transport_contents) + to_chat(passenger, span_userdanger("The tram comes to a sudden, grinding stop!")) + var/throw_target = get_edge_target_turf(src, throw_direction) + var/datum/callback/land_slam = new(passenger, TYPE_PROC_REF(/mob/living/, tram_slam_land)) + passenger.throw_at(throw_target, 400, 4, force = MOVE_FORCE_OVERPOWERING, callback = land_slam) + +/obj/structure/transport/linear/tram/slow + transport_controller_type = /datum/transport_controller/linear/tram/slow + speed_limiter = /datum/transport_controller/linear/tram/slow::speed_limiter diff --git a/code/modules/transport/transport_navigation.dm b/code/modules/transport/transport_navigation.dm new file mode 100644 index 0000000000000..3b5c73b5de1a1 --- /dev/null +++ b/code/modules/transport/transport_navigation.dm @@ -0,0 +1,108 @@ +/** + * transport_controller landmarks. used to map specific destinations on the map. + */ +/obj/effect/landmark/transport/nav_beacon/tram + name = "tram destination" //the tram buttons will mention this. + icon_state = "tram" + + /// The ID of the tram we're linked to + var/specific_transport_id = TRAMSTATION_LINE_1 + /// The ID of that particular destination + var/platform_code = null + /// Icons for the tgui console to list out for what is at this location + var/list/tgui_icons = list() + +/obj/effect/landmark/transport/nav_beacon/tram/Initialize(mapload) + . = ..() + LAZYADDASSOCLIST(SStransport.nav_beacons, specific_transport_id, src) + +/obj/effect/landmark/transport/nav_beacon/tram/Destroy() + LAZYREMOVEASSOC(SStransport.nav_beacons, specific_transport_id, src) + return ..() + +/obj/effect/landmark/transport/nav_beacon/tram/nav + name = "tram nav beacon" + invisibility = INVISIBILITY_MAXIMUM // nav aids can't be abstract since they stay with the tram + +/** + * transport_controller landmarks. used to map in specific_transport_id to trams and elevators. when the transport_controller encounters one on a tile + * it sets its specific_transport_id to that landmark. allows you to have multiple trams and multiple objects linking to their specific tram + */ +/obj/effect/landmark/transport/transport_id + name = "transport init landmark" + icon_state = "lift_id" + ///what specific id we give to the tram we're placed on, should explicitely set this if its a subtype, or weird things might happen + var/specific_transport_id + +//tramstation + +/obj/effect/landmark/transport/transport_id/tramstation/line_1 + specific_transport_id = TRAMSTATION_LINE_1 + +/obj/effect/landmark/transport/nav_beacon/tram/nav/tramstation/main + name = TRAMSTATION_LINE_1 + specific_transport_id = TRAM_NAV_BEACONS + dir = WEST + +/obj/effect/landmark/transport/nav_beacon/tram/platform/tramstation/west + name = "West Wing" + platform_code = TRAMSTATION_WEST + tgui_icons = list("Arrivals" = "plane-arrival", "Command" = "bullhorn", "Security" = "gavel") + +/obj/effect/landmark/transport/nav_beacon/tram/platform/tramstation/central + name = "Central Wing" + platform_code = TRAMSTATION_CENTRAL + tgui_icons = list("Service" = "cocktail", "Medical" = "plus", "Engineering" = "wrench") + +/obj/effect/landmark/transport/nav_beacon/tram/platform/tramstation/east + name = "East Wing" + platform_code = TRAMSTATION_EAST + tgui_icons = list("Departures" = "plane-departure", "Cargo" = "box", "Science" = "flask") + +//birdshot + +/obj/effect/landmark/transport/transport_id/birdshot/line_1 + specific_transport_id = BIRDSHOT_LINE_1 + +/obj/effect/landmark/transport/transport_id/birdshot/line_2 + specific_transport_id = BIRDSHOT_LINE_2 + +/obj/effect/landmark/transport/nav_beacon/tram/nav/birdshot/prison + name = BIRDSHOT_LINE_1 + specific_transport_id = TRAM_NAV_BEACONS + dir = NORTH + +/obj/effect/landmark/transport/nav_beacon/tram/nav/birdshot/maint + name = BIRDSHOT_LINE_2 + specific_transport_id = TRAM_NAV_BEACONS + dir = WEST + +/obj/effect/landmark/transport/nav_beacon/tram/platform/birdshot/sec_wing + name = "Security Wing" + specific_transport_id = BIRDSHOT_LINE_1 + platform_code = BIRDSHOT_SECURITY_WING + tgui_icons = list("Security" = "gavel") + +/obj/effect/landmark/transport/nav_beacon/tram/platform/birdshot/prison_wing + name = "Prison Wing" + specific_transport_id = BIRDSHOT_LINE_1 + platform_code = BIRDSHOT_PRISON_WING + tgui_icons = list("Prison" = "box") + +/obj/effect/landmark/transport/nav_beacon/tram/platform/birdshot/maint_left + name = "Port Platform" + specific_transport_id = BIRDSHOT_LINE_2 + platform_code = BIRDSHOT_MAINTENANCE_LEFT + tgui_icons = list("Port Platform" = "plane-departure") + +/obj/effect/landmark/transport/nav_beacon/tram/platform/birdshot/maint_right + name = "Starboard Platform" + specific_transport_id = BIRDSHOT_LINE_2 + platform_code = BRIDSHOT_MAINTENANCE_RIGHT + tgui_icons = list("Starboard Platform" = "plane-arrival") + +//map-agnostic landmarks + +/obj/effect/landmark/transport/nav_beacon/tram/nav/immovable_rod + name = "DESTINATION/NOT/FOUND" + specific_transport_id = IMMOVABLE_ROD_DESTINATIONS diff --git a/code/modules/tutorials/_tutorial.dm b/code/modules/tutorials/_tutorial.dm index 3baa9ad148b7d..7819a9e2b85ac 100644 --- a/code/modules/tutorials/_tutorial.dm +++ b/code/modules/tutorials/_tutorial.dm @@ -16,7 +16,7 @@ RegisterSignal(user, COMSIG_QDELETING, PROC_REF(destroy_self)) RegisterSignal(user.client, COMSIG_QDELETING, PROC_REF(destroy_self)) -/datum/tutorial/Destroy(force, ...) +/datum/tutorial/Destroy(force) user.client?.screen -= instruction_screen QDEL_NULL(instruction_screen) @@ -163,7 +163,7 @@ ASSERT(ispath(tutorial_type, /datum/tutorial)) src.tutorial_type = tutorial_type -/datum/tutorial_manager/Destroy(force, ...) +/datum/tutorial_manager/Destroy(force) if (!force) stack_trace("Something is trying to destroy [type], which is a singleton") return QDEL_HINT_LETMELIVE diff --git a/code/modules/tutorials/tutorials/drop.dm b/code/modules/tutorials/tutorials/drop.dm index de692edab433d..06980b28848dd 100644 --- a/code/modules/tutorials/tutorials/drop.dm +++ b/code/modules/tutorials/tutorials/drop.dm @@ -12,7 +12,7 @@ var/atom/movable/screen/drop_preview var/obj/last_held_item -/datum/tutorial/drop/Destroy(force, ...) +/datum/tutorial/drop/Destroy(force) last_held_item = null user.client?.screen -= drop_preview QDEL_NULL(drop_preview) diff --git a/code/modules/tutorials/tutorials/switch_hands.dm b/code/modules/tutorials/tutorials/switch_hands.dm index bf27a9e9d83aa..f1bcbbb3b7117 100644 --- a/code/modules/tutorials/tutorials/switch_hands.dm +++ b/code/modules/tutorials/tutorials/switch_hands.dm @@ -19,7 +19,7 @@ hand_to_watch = (user.active_hand_index % user.held_items.len) + 1 -/datum/tutorial/switch_hands/Destroy(force, ...) +/datum/tutorial/switch_hands/Destroy(force) user.client?.screen -= hand_preview QDEL_NULL(hand_preview) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index fd68150e44c9a..61bc9ec6d4e6e 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -100,6 +100,7 @@ #include "baseturfs.dm" #include "bespoke_id.dm" #include "binary_insert.dm" +#include "bitrunning.dm" #include "blindness.dm" #include "bloody_footprints.dm" #include "breath.dm" @@ -108,6 +109,7 @@ #include "card_mismatch.dm" #include "cardboard_cutouts.dm" #include "chain_pull_through_space.dm" +#include "changeling.dm" #include "chat_filter.dm" #include "circuit_component_category.dm" #include "client_colours.dm" @@ -147,6 +149,7 @@ #include "heretic_rituals.dm" #include "high_five.dm" #include "holidays.dm" +#include "hulk.dm" #include "human_through_recycler.dm" #include "hunger_curse.dm" #include "hydroponics_extractor_storage.dm" @@ -161,10 +164,12 @@ #include "leash.dm" #include "lesserform.dm" #include "limbsanity.dm" +#include "ling_decap.dm" #include "liver.dm" #include "load_map_security.dm" #include "lungs.dm" #include "machine_disassembly.dm" +#include "mafia.dm" #include "map_landmarks.dm" #include "mapload_space_verification.dm" #include "mapping.dm" @@ -174,6 +179,8 @@ #include "metabolizing.dm" #include "mindbound_actions.dm" #include "missing_icons.dm" +#include "mob_chains.dm" +#include "mob_damage.dm" #include "mob_faction.dm" #include "mob_spawn.dm" #include "modify_fantasy_variable.dm" @@ -188,9 +195,11 @@ #include "objectives.dm" #include "operating_table.dm" #include "orderable_items.dm" +#include "organ_bodypart_shuffle.dm" #include "organ_set_bonus.dm" #include "organs.dm" #include "outfit_sanity.dm" +#include "oxyloss_suffocation.dm" #include "paintings.dm" #include "pills.dm" #include "plane_double_transform.dm" @@ -215,6 +224,7 @@ #include "screenshot_antag_icons.dm" #include "screenshot_basic.dm" #include "screenshot_dynamic_human_icons.dm" +#include "screenshot_high_luminosity_eyes.dm" #include "screenshot_humanoids.dm" #include "screenshot_husk.dm" #include "screenshot_saturnx.dm" @@ -248,6 +258,7 @@ #include "subsystem_init.dm" #include "suit_storage_icons.dm" #include "surgeries.dm" +#include "tail_wag.dm" #include "teleporters.dm" #include "tgui_create_message.dm" #include "timer_sanity.dm" diff --git a/code/modules/unit_tests/achievements.dm b/code/modules/unit_tests/achievements.dm index 44e1384c2e01a..decda52a2f5d7 100644 --- a/code/modules/unit_tests/achievements.dm +++ b/code/modules/unit_tests/achievements.dm @@ -2,13 +2,13 @@ /datum/unit_test/achievements /datum/unit_test/achievements/Run() - var/award_icons = icon_states(ACHIEVEMENTS_SET) for(var/datum/award/award as anything in subtypesof(/datum/award)) if(!initial(award.name)) //Skip abstract achievements types continue var/init_icon = initial(award.icon) - if(!init_icon || !(init_icon in award_icons)) - TEST_FAIL("Award [initial(award.name)] has an unexistent icon: \"[init_icon || "null"]\"") + var/init_icon_state = initial(award.icon_state) + if(!init_icon_state || !icon_exists(init_icon, init_icon_state)) + TEST_FAIL("Award [initial(award.name)] has a non-existent icon in [init_icon]: \"[init_icon_state || "null"]\"") if(length(initial(award.database_id)) > 32) //sql schema limit TEST_FAIL("Award [initial(award.name)] database id is too long") var/init_category = initial(award.category) diff --git a/code/modules/unit_tests/area_contents.dm b/code/modules/unit_tests/area_contents.dm index 8a48d644ee964..52394dd60ec2e 100644 --- a/code/modules/unit_tests/area_contents.dm +++ b/code/modules/unit_tests/area_contents.dm @@ -6,23 +6,28 @@ /datum/unit_test/area_contents/Run() // First, we check that there are no entries in more then one area // That or duplicate entries - for(var/area/space in GLOB.areas) - for(var/turf/position as anything in space.get_contained_turfs()) - if(!isturf(position)) - TEST_FAIL("Found a [position.type] in [space.type]'s turf listing") + for (var/area/area_to_test in GLOB.areas) + area_to_test.cannonize_contained_turfs() + for (var/i in 1 to area_to_test.turfs_by_zlevel.len) + if (!islist(area_to_test.turfs_by_zlevel[i])) + TEST_FAIL("zlevel index [i] in [area_to_test.type] is not a list.") - if(position.in_contents_of) - var/area/existing = position.in_contents_of - if(existing == space) - TEST_FAIL("Found a duplicate turf [position.type] inside [space.type]'s turf listing") - else - TEST_FAIL("Found a shared turf [position.type] between [space.type] and [existing.type]'s turf listings") + for (var/turf/turf_to_check as anything in area_to_test.turfs_by_zlevel[i]) + if (!isturf(turf_to_check)) + TEST_FAIL("Found a [turf_to_check.type] in [area_to_test.type]'s turf listing") - var/area/dream_spot = position.loc - if(dream_spot != space) - TEST_FAIL("Found a turf [position.type] which is IN [dream_spot.type], but is registered as being in [space.type]") + if (turf_to_check.in_contents_of) + var/area/existing = turf_to_check.in_contents_of + if (existing == turf_to_check) + TEST_FAIL("Found a duplicate turf [turf_to_check.type] inside [area_to_test.type]'s turf listing") + else + TEST_FAIL("Found a shared turf [turf_to_check.type] between [area_to_test.type] and [existing.type]'s turf listings") - position.in_contents_of = space + var/area/turfs_actual_area = turf_to_check.loc + if (turfs_actual_area != area_to_test) + TEST_FAIL("Found a turf [turf_to_check.type] which is IN [turfs_actual_area.type], but is registered as being in [area_to_test.type]") + + turf_to_check.in_contents_of = turf_to_check for(var/turf/position in ALL_TURFS()) if(!position.in_contents_of) diff --git a/code/modules/unit_tests/atmospherics_sanity.dm b/code/modules/unit_tests/atmospherics_sanity.dm index c4f024e9178b5..ff177ae4517b2 100644 --- a/code/modules/unit_tests/atmospherics_sanity.dm +++ b/code/modules/unit_tests/atmospherics_sanity.dm @@ -101,7 +101,7 @@ UNTIL(crawls == 0) for(var/area/missed as anything in remaining_areas) if(missed.has_contained_turfs()) - var/turf/first_turf = missed.get_contained_turfs()[1] + var/turf/first_turf = missed.get_zlevel_turf_lists()[1][1] TEST_FAIL("Disconnected Area '[missed]'([missed.type]) at ([first_turf.x], [first_turf.y], [first_turf.z])") else TEST_NOTICE(src, "Disconnected Area '[missed]'([missed.type]) with no turfs?") diff --git a/code/modules/unit_tests/barsigns.dm b/code/modules/unit_tests/barsigns.dm index 7058dd5346dc9..d1242da3d5769 100644 --- a/code/modules/unit_tests/barsigns.dm +++ b/code/modules/unit_tests/barsigns.dm @@ -12,7 +12,7 @@ for(var/sign_type in (subtypesof(/datum/barsign) - /datum/barsign/hiddensigns)) var/datum/barsign/sign = new sign_type() - if(!(sign.icon in barsign_icon_states)) + if(!(sign.icon_state in barsign_icon_states)) TEST_FAIL("Icon state for [sign_type] does not exist in [barsign_icon].") /** @@ -35,3 +35,24 @@ TEST_FAIL("[sign_type] does not have a unique name.") existing_names += sign.name + +/** + * Test that an emped barsign displays correctly + */ +/datum/unit_test/barsigns_emp + +/datum/unit_test/barsigns_emp/Run() + var/obj/machinery/barsign/testing_sign = allocate(/obj/machinery/barsign) + var/datum/barsign/hiddensigns/empbarsign/emp_bar_sign = /datum/barsign/hiddensigns/empbarsign + + testing_sign.emp_act(EMP_HEAVY) + + // make sure we get the correct chosen_sign set + if(!istype(testing_sign.chosen_sign, emp_bar_sign)) + TEST_FAIL("[testing_sign] got EMPed but did not get its chosen_sign set correctly.") + + // make sure the sign's icon_state actually got set + var/expected_icon_state = initial(emp_bar_sign.icon_state) + if(testing_sign.icon_state != expected_icon_state) + TEST_FAIL("[testing_sign]'s icon_state was [testing_sign.icon_state] when it should have been [expected_icon_state].") + diff --git a/code/modules/unit_tests/baseturfs.dm b/code/modules/unit_tests/baseturfs.dm index 2ac016f38b9a5..3150aac342f9c 100644 --- a/code/modules/unit_tests/baseturfs.dm +++ b/code/modules/unit_tests/baseturfs.dm @@ -34,7 +34,7 @@ // Do this instead of just ChangeTurf to guarantee that baseturfs is completely default on-init behavior RESET_TO_EXPECTED(run_loc_floor_bottom_left) - run_loc_floor_bottom_left.PlaceOnTop(/turf/closed/wall/rock) + run_loc_floor_bottom_left.place_on_top(/turf/closed/wall/rock) TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, /turf/closed/wall/rock, "Rock wall should've been placed on top") run_loc_floor_bottom_left.ScrapeAway() @@ -53,7 +53,7 @@ // Do this instead of just ChangeTurf to guarantee that baseturfs is completely default on-init behavior RESET_TO_EXPECTED(run_loc_floor_bottom_left) - run_loc_floor_bottom_left.PlaceOnBottom(fake_turf_type = /turf/closed/wall/rock) + run_loc_floor_bottom_left.place_on_bottom(/turf/closed/wall/rock) TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, EXPECTED_FLOOR_TYPE, "PlaceOnBottom shouldn't have changed turf") run_loc_floor_bottom_left.ScrapeAway() diff --git a/code/modules/unit_tests/bitrunning.dm b/code/modules/unit_tests/bitrunning.dm new file mode 100644 index 0000000000000..568eeeed8c133 --- /dev/null +++ b/code/modules/unit_tests/bitrunning.dm @@ -0,0 +1,15 @@ +/// Ensures settings on vdoms are correct +/datum/unit_test/bitrunner_vdom_settings + +/datum/unit_test/bitrunner_vdom_settings/Run() + var/obj/structure/closet/crate/secure/bitrunning/decrypted/cache = allocate(/obj/structure/closet/crate/secure/bitrunning/decrypted) + + for(var/path in subtypesof(/datum/lazy_template/virtual_domain)) + var/datum/lazy_template/virtual_domain/vdom = new path + TEST_ASSERT_NOTNULL(vdom.key, "[path] should have a key") + TEST_ASSERT_NOTNULL(vdom.map_name, "[path] should have a map name") + + if(!length(vdom.extra_loot)) + continue + + TEST_ASSERT_EQUAL(cache.spawn_loot(vdom.extra_loot), TRUE, "[path] didn't spawn loot. Extra loot should be an associative list") diff --git a/code/modules/unit_tests/cardboard_cutouts.dm b/code/modules/unit_tests/cardboard_cutouts.dm index f706f8c95b696..ce7066de1ca95 100644 --- a/code/modules/unit_tests/cardboard_cutouts.dm +++ b/code/modules/unit_tests/cardboard_cutouts.dm @@ -11,11 +11,9 @@ nukie_cutout.push_over() test_screenshot("nukie_cutout_pushed", getFlatIcon(nukie_cutout)) -#if DM_VERSION >= 515 // This is the only reason we're testing xenomorphs. // Making a custom subtype with direct_icon is hacky. ASSERT(!isnull(/datum/cardboard_cutout/xenomorph_maid::direct_icon)) -#endif var/obj/item/cardboard_cutout/xenomorph/xenomorph_cutout = new test_screenshot("xenomorph_cutout", getFlatIcon(xenomorph_cutout)) diff --git a/code/modules/unit_tests/changeling.dm b/code/modules/unit_tests/changeling.dm new file mode 100644 index 0000000000000..1393eee838988 --- /dev/null +++ b/code/modules/unit_tests/changeling.dm @@ -0,0 +1,99 @@ +/// Tests transformation sting goes back and forth correctly +/datum/unit_test/transformation_sting + var/ling_name = "Is-A-Changeling" + var/base_victim_name + var/last_frame = 1 + var/icon/final_icon + +/datum/unit_test/transformation_sting/Run() + var/mob/living/carbon/human/ling = setup_ling() + var/mob/living/carbon/human/victim = setup_victim() + var/datum/antagonist/changeling/ling_datum = IS_CHANGELING(ling) + + // Get the ability we're testing + ling_datum.purchase_power(/datum/action/changeling/sting/transformation) + var/datum/action/changeling/sting/transformation/sting_action = locate() in ling.actions + sting_action.selected_dna = ling_datum.current_profile + sting_action.sting_duration = 0.5 SECONDS // just makes sure everything settles. + + // Check that they look different before stinging + add_to_screenshot(ling, victim, both_species = TRUE) + + // Do the sting, make the transformation + sting_action.sting_action(ling, victim) + // Check their name and species align + TEST_ASSERT(victim.has_status_effect(/datum/status_effect/temporary_transformation), "Victim did not get temporary transformation status effect on being transformation stung.") + TEST_ASSERT_EQUAL(victim.real_name, ling_name, "Victim real name did not change on being transformation stung.") + TEST_ASSERT_EQUAL(victim.name, ling_name, "Victim name did not change on being transformation stung.") + TEST_ASSERT_EQUAL(victim.dna.species.type, ling.dna.species.type, "Victim species did not change on being transformation stung.") + TEST_ASSERT_EQUAL(victim.dna.features["mcolor"], ling.dna.features["mcolor"], "Victim mcolor did not change on being transformation stung.") + // Check they actually look the same + add_to_screenshot(ling, victim) + + // Make sure we give it enough time such that the status effect process ticks over and finishes + sleep(sting_action.sting_duration + 0.5 SECONDS) + + // Check their name and species reset correctly + TEST_ASSERT_EQUAL(victim.name, base_victim_name, "Victim name did not change back after transformation sting expired.") + TEST_ASSERT_EQUAL(victim.real_name, base_victim_name, "Victim real name did not change back after transformation sting expired.") + TEST_ASSERT_NOTEQUAL(victim.dna.species.type, ling.dna.species.type, "Victim species did not change back after transformation sting expired.") + TEST_ASSERT_NOTEQUAL(victim.dna.features["mcolor"], ling.dna.features["mcolor"], "Victim mcolor did not reset after transformation sting expired.") + // Check they actually look different again + add_to_screenshot(ling, victim, both_species = TRUE) + + test_screenshot("appearances", final_icon) + +/// Adds both mobs to the screenshot test, if both_species is TRUE, it also adds the victim in lizard form +/datum/unit_test/transformation_sting/proc/add_to_screenshot(mob/living/carbon/human/ling, mob/living/carbon/human/victim, both_species = FALSE) + if(isnull(final_icon)) + final_icon = icon('icons/effects/effects.dmi', "nothing") + + // If we have a lot of dna features with a lot of parts (icons) + // This'll eventually runtime into a bad icon operation + // So we're recaching the icons here to prevent it from failing + final_icon = icon(final_icon) + final_icon.Insert(getFlatIcon(ling, no_anim = TRUE), dir = SOUTH, frame = last_frame) + final_icon.Insert(getFlatIcon(victim, no_anim = TRUE), dir = NORTH, frame = last_frame) + + if(both_species) + var/prior_species = victim.dna.species.type + victim.set_species(/datum/species/lizard) + final_icon.Insert(getFlatIcon(victim, no_anim = TRUE), dir = EAST, frame = last_frame) + victim.set_species(prior_species) + + last_frame += 1 + +/datum/unit_test/transformation_sting/proc/setup_victim() + var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent) + base_victim_name = victim.real_name + victim.mind_initialize() + return victim + +/datum/unit_test/transformation_sting/proc/setup_ling() + var/mob/living/carbon/human/ling = allocate(/mob/living/carbon/human/consistent) + // Because we use two consistent humans, we need to change some of the features to know they're actually updating to new values. + // The more DNA features and random things we change, the more likely we are to catch something not updating correctly. + // Yeah guess who/what this is, I dare you. + ling.dna.features["mcolor"] = "#886600" + ling.dna.features["tail_lizard"] = "Smooth" + ling.dna.features["snout"] = "Sharp + Light" + ling.dna.features["horns"] = "Curled" + ling.dna.features["frills"] = "Short" + ling.dna.features["spines"] = "Long + Membrane" + ling.dna.features["body_markings"] = "Light Belly" + ling.dna.features["legs"] = DIGITIGRADE_LEGS + ling.eye_color_left = "#FFFFFF" + ling.eye_color_right = "#FFFFFF" + ling.dna.update_ui_block(DNA_EYE_COLOR_LEFT_BLOCK) + ling.dna.update_ui_block(DNA_EYE_COLOR_RIGHT_BLOCK) + ling.set_species(/datum/species/lizard) + + ling.real_name = ling_name + ling.dna.real_name = ling_name + ling.name = ling_name + ling.dna.initialize_dna(create_mutation_blocks = FALSE, randomize_features = FALSE) + + ling.mind_initialize() + ling.mind.add_antag_datum(/datum/antagonist/changeling) + + return ling diff --git a/code/modules/unit_tests/combat.dm b/code/modules/unit_tests/combat.dm index 3302eca9c74e1..d645df98cbfd9 100644 --- a/code/modules/unit_tests/combat.dm +++ b/code/modules/unit_tests/combat.dm @@ -99,3 +99,42 @@ TEST_ASSERT_EQUAL(victim.loc.x, run_loc_floor_bottom_left.x + 2, "Victim was moved after being pushed against a wall") TEST_ASSERT(victim.has_status_effect(/datum/status_effect/incapacitating/knockdown), "Victim was not knocked down after being pushed against a wall") TEST_ASSERT_EQUAL(victim.get_active_held_item(), null, "Victim didn't drop toolbox after being pushed against a wall") + +/// Tests you can punch yourself +/datum/unit_test/self_punch + +/datum/unit_test/self_punch/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + ADD_TRAIT(dummy, TRAIT_PERFECT_ATTACKER, TRAIT_SOURCE_UNIT_TESTS) + dummy.set_combat_mode(TRUE) + dummy.ClickOn(dummy) + TEST_ASSERT_NOTEQUAL(dummy.getBruteLoss(), 0, "Dummy took no brute damage after self-punching") + +/// Tests handcuffed (HANDS_BLOCKED) mobs cannot punch +/datum/unit_test/handcuff_punch + +/datum/unit_test/handcuff_punch/Run() + var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent) + ADD_TRAIT(attacker, TRAIT_PERFECT_ATTACKER, TRAIT_SOURCE_UNIT_TESTS) + ADD_TRAIT(attacker, TRAIT_HANDS_BLOCKED, TRAIT_SOURCE_UNIT_TESTS) + attacker.set_combat_mode(TRUE) + attacker.ClickOn(victim) + TEST_ASSERT_EQUAL(victim.getBruteLoss(), 0, "Victim took brute damage from being punched by a handcuffed attacker") + attacker.next_move = -1 + attacker.next_click = -1 + attacker.ClickOn(attacker) + TEST_ASSERT_EQUAL(attacker.getBruteLoss(), 0, "Attacker took brute damage from self-punching while handcuffed") + +/// Tests handcuffed (HANDS_BLOCKED) monkeys can still bite despite being cuffed +/datum/unit_test/handcuff_bite + +/datum/unit_test/handcuff_bite/Run() + var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent) + ADD_TRAIT(attacker, TRAIT_PERFECT_ATTACKER, TRAIT_SOURCE_UNIT_TESTS) + ADD_TRAIT(attacker, TRAIT_HANDS_BLOCKED, TRAIT_SOURCE_UNIT_TESTS) + attacker.set_combat_mode(TRUE) + attacker.set_species(/datum/species/monkey) + attacker.ClickOn(victim) + TEST_ASSERT_NOTEQUAL(victim.getBruteLoss(), 0, "Victim took no brute damage from being bit by a handcuffed monkey, which is incorrect, as it's a bite attack") diff --git a/code/modules/unit_tests/create_and_destroy.dm b/code/modules/unit_tests/create_and_destroy.dm index e0e763ebb8571..0c9498b19bf27 100644 --- a/code/modules/unit_tests/create_and_destroy.dm +++ b/code/modules/unit_tests/create_and_destroy.dm @@ -111,7 +111,7 @@ GLOBAL_VAR_INIT(running_create_and_destroy, FALSE) if(fails & BAD_INIT_NO_HINT) TEST_FAIL("[path] didn't return an Initialize hint") if(fails & BAD_INIT_QDEL_BEFORE) - TEST_FAIL("[path] qdel'd in New()") + TEST_FAIL("[path] qdel'd before we could call Initialize()") if(fails & BAD_INIT_SLEPT) TEST_FAIL("[path] slept during Initialize()") diff --git a/code/modules/unit_tests/dcs_check_list_arguments.dm b/code/modules/unit_tests/dcs_check_list_arguments.dm index 8089896ba3d46..67d7417062b27 100644 --- a/code/modules/unit_tests/dcs_check_list_arguments.dm +++ b/code/modules/unit_tests/dcs_check_list_arguments.dm @@ -1,18 +1,36 @@ /** - * list arguments for bespoke elements are treated just like any other datum: as a text ref in the ID. - * Using un-cached lists in AddElement() and RemoveElement() calls will just create new elements over - * and over. That's what this unit test is for. It's not a catch-all, but it does a decent job at it. + * list arguments for bespoke elements are treated as a text ref in the ID, like any other datum. + * Which means that, unless cached, using lists as arguments will lead to multiple instance of the same element + * being created over and over. + * + * Because of how it works, this unit test checks that these list datum args + * do not share similar contents (when rearranged in descending alpha-numerical order), to ensure that + * the least necessary amount of elements is created. So, using static lists may not be enough, + * for example, in the case of two different critters using the death_drops element to drop ectoplasm on death, since, + * despite being static lists, the two are different instances assigned to different mob types. + * + * Most of the time, you won't encounter two different static lists with similar contents used as element args, + * meaning using static lists is accepted. However, should that happen, it's advised to replace the instances + * with various string_x procs: lists, assoc_lists, assoc_nested_lists or numbers_list, depending on the type. + * + * In the case of an element where the position of the contents of each datum list argument is important, + * ELEMENT_DONT_SORT_LIST_ARGS should be added to its flags, to prevent such issues where the contents are similar + * when sorted, but the element instances are not. + * + * In the off-chance the element is not compatible with this unit test (such as for connect_loc et simila), + * you can also use ELEMENT_NO_LIST_UNIT_TEST so that they won't be processed by this unit test at all. */ /datum/unit_test/dcs_check_list_arguments /** - * This unit test requires every (tangible) atom to have been created at least once - * so its search is more accurate. That's why it's run after create_and_destroy. + * This unit test requires every (unless ignored) atom to have been created at least once + * for a more accurate search, which is why it's run after create_and_destroy is done running. */ priority = TEST_AFTER_CREATE_AND_DESTROY /datum/unit_test/dcs_check_list_arguments/Run() + var/we_failed = FALSE for(var/element_type in SSdcs.arguments_that_are_lists_by_element) - // Keeps tracks of the lists that shouldn't be compared with again. + // Keeps track of the lists that shouldn't be compared with again. var/list/to_ignore = list() var/list/superlist = SSdcs.arguments_that_are_lists_by_element[element_type] for(var/list/current as anything in superlist) @@ -27,7 +45,11 @@ bad_lists += list(compare) to_ignore[compare] = TRUE if(bad_lists) + we_failed = TRUE //Include the original, unsorted list in the report. It should be easier to find by the contributor. var/list/unsorted_list = superlist[current] - TEST_FAIL("found [length(bad_lists)] identical lists used as argument for element [element_type]. List: [json_encode(unsorted_list)].\n\ - Make sure it's a cached list, or use one of the string_list proc. Also, use the ELEMENT_DONT_SORT_LIST_ARGS flag if the key position of your lists matters.") + TEST_FAIL("Found [length(bad_lists)] datum list arguments with similar contents for [element_type]. Contents: [json_encode(unsorted_list)].") + ///Let's avoid sending the same instructions over and over, as it's just going to clutter the CI and confuse someone. + if(we_failed) + TEST_FAIL("Ensure that each list is static or cached. string_lists() (as well as similar procs) is your friend here.\n\ + Check the documentation from dcs_check_list_arguments.dm for more information!") diff --git a/code/modules/unit_tests/designs.dm b/code/modules/unit_tests/designs.dm index a9cda144649cb..0495ebdc7d9ae 100644 --- a/code/modules/unit_tests/designs.dm +++ b/code/modules/unit_tests/designs.dm @@ -18,6 +18,8 @@ TEST_FAIL("Design [current_design.type] requires materials but does not have have any build_path or make_reagent set") else if (!isnull(current_design.build_path) || !isnull(current_design.build_path)) // //Design requires no materials but creates stuff TEST_FAIL("Design [current_design.type] requires NO materials but has build_path or make_reagent set") + if (length(current_design.reagents_list) && !(current_design.build_type & LIMBGROWER)) + TEST_FAIL("Design [current_design.type] requires reagents but isn't a limb grower design. Reagent costs are only supported by limb grower designs") for(var/path in subtypesof(/datum/design/surgery)) var/datum/design/surgery/current_design = new path //Create an instance of each design diff --git a/code/modules/unit_tests/dragon_expiration.dm b/code/modules/unit_tests/dragon_expiration.dm index 7b36b5762911c..45262dc9d6090 100644 --- a/code/modules/unit_tests/dragon_expiration.dm +++ b/code/modules/unit_tests/dragon_expiration.dm @@ -2,9 +2,12 @@ /datum/unit_test/contents_barfer /datum/unit_test/contents_barfer/Run() - var/mob/living/simple_animal/hostile/space_dragon/dragon_time = allocate(/mob/living/simple_animal/hostile/space_dragon) + var/mob/living/basic/space_dragon/dragon_time = allocate(/mob/living/basic/space_dragon) var/mob/living/carbon/human/to_be_consumed = allocate(/mob/living/carbon/human/consistent) + to_be_consumed.adjust_fire_stacks(5) + to_be_consumed.ignite_mob() TEST_ASSERT(dragon_time.eat(to_be_consumed), "The space dragon failed to consume the dummy!") + TEST_ASSERT(!to_be_consumed.has_status_effect(/datum/status_effect/fire_handler/fire_stacks), "The space dragon failed to extinguish the dummy!") TEST_ASSERT_EQUAL(to_be_consumed.loc, dragon_time, "The dummy's location, after being successfuly consumed, was not within the space dragon's contents!") dragon_time.death() TEST_ASSERT(isturf(to_be_consumed.loc), "After dying, the space dragon did not eject the consumed dummy content barfer element.") @@ -13,7 +16,7 @@ /datum/unit_test/space_dragon_expiration /datum/unit_test/space_dragon_expiration/Run() - var/mob/living/simple_animal/hostile/space_dragon/dragon_time = allocate(/mob/living/simple_animal/hostile/space_dragon) + var/mob/living/basic/space_dragon/dragon_time = allocate(/mob/living/basic/space_dragon) var/mob/living/carbon/human/to_be_consumed = allocate(/mob/living/carbon/human/consistent) dragon_time.mind_initialize() diff --git a/code/modules/unit_tests/find_reference_sanity.dm b/code/modules/unit_tests/find_reference_sanity.dm index 8bd2a14dbf5f0..4bc6445f02434 100644 --- a/code/modules/unit_tests/find_reference_sanity.dm +++ b/code/modules/unit_tests/find_reference_sanity.dm @@ -15,6 +15,8 @@ return ..() /atom/movable/ref_test + // Gotta make sure we do a full check + references_to_clear = INFINITY var/atom/movable/ref_test/self_ref /atom/movable/ref_test/Destroy(force) @@ -27,12 +29,11 @@ SSgarbage.should_save_refs = TRUE //Sanity check - #if DM_VERSION >= 515 var/refcount = refcount(victim) TEST_ASSERT_EQUAL(refcount, 3, "Should be: test references: 0 + baseline references: 3 (victim var,loc,allocated list)") - #endif - victim.DoSearchVar(testbed, "Sanity Check", search_time = 1) //We increment search time to get around an optimization - TEST_ASSERT(!victim.found_refs.len, "The ref-tracking tool found a ref where none existed") + victim.DoSearchVar(testbed, "Sanity Check") //We increment search time to get around an optimization + + TEST_ASSERT(!LAZYLEN(victim.found_refs), "The ref-tracking tool found a ref where none existed") SSgarbage.should_save_refs = FALSE /datum/unit_test/find_reference_baseline/Run() @@ -45,15 +46,13 @@ testbed.test_list += victim testbed.test_assoc_list["baseline"] = victim - #if DM_VERSION >= 515 var/refcount = refcount(victim) TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)") - #endif - victim.DoSearchVar(testbed, "First Run", search_time = 2) + victim.DoSearchVar(testbed, "First Run") - TEST_ASSERT(victim.found_refs["test"], "The ref-tracking tool failed to find a regular value") - TEST_ASSERT(victim.found_refs[testbed.test_list], "The ref-tracking tool failed to find a list entry") - TEST_ASSERT(victim.found_refs[testbed.test_assoc_list], "The ref-tracking tool failed to find an assoc list value") + TEST_ASSERT(LAZYACCESS(victim.found_refs, "test"), "The ref-tracking tool failed to find a regular value") + TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_list), "The ref-tracking tool failed to find a list entry") + TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find an assoc list value") SSgarbage.should_save_refs = FALSE /datum/unit_test/find_reference_exotic/Run() @@ -66,16 +65,14 @@ testbed.vis_contents += victim testbed.test_assoc_list[victim] = TRUE - #if DM_VERSION >= 515 var/refcount = refcount(victim) TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)") - #endif - victim.DoSearchVar(testbed, "Second Run", search_time = 3) + victim.DoSearchVar(testbed, "Second Run") //This is another sanity check - TEST_ASSERT(!victim.found_refs[testbed.overlays], "The ref-tracking tool found an overlays entry? That shouldn't be possible") - TEST_ASSERT(victim.found_refs[testbed.vis_contents], "The ref-tracking tool failed to find a vis_contents entry") - TEST_ASSERT(victim.found_refs[testbed.test_assoc_list], "The ref-tracking tool failed to find an assoc list key") + TEST_ASSERT(!LAZYACCESS(victim.found_refs, testbed.overlays), "The ref-tracking tool found an overlays entry? That shouldn't be possible") + TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.vis_contents), "The ref-tracking tool failed to find a vis_contents entry") + TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find an assoc list key") SSgarbage.should_save_refs = FALSE /datum/unit_test/find_reference_esoteric/Run() @@ -90,15 +87,14 @@ var/list/to_find_assoc = list(victim) testbed.test_assoc_list["Nesting"] = to_find_assoc - #if DM_VERSION >= 515 var/refcount = refcount(victim) TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)") - #endif - victim.DoSearchVar(victim, "Third Run Self", search_time = 4) - victim.DoSearchVar(testbed, "Third Run Testbed", search_time = 4) - TEST_ASSERT(victim.found_refs["self_ref"], "The ref-tracking tool failed to find a self reference") - TEST_ASSERT(victim.found_refs[to_find], "The ref-tracking tool failed to find a nested list entry") - TEST_ASSERT(victim.found_refs[to_find_assoc], "The ref-tracking tool failed to find a nested assoc list entry") + victim.DoSearchVar(victim, "Third Run Self") + victim.DoSearchVar(testbed, "Third Run Testbed") + + TEST_ASSERT(LAZYACCESS(victim.found_refs, "self_ref"), "The ref-tracking tool failed to find a self reference") + TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find), "The ref-tracking tool failed to find a nested list entry") + TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_assoc), "The ref-tracking tool failed to find a nested assoc list entry") SSgarbage.should_save_refs = FALSE /datum/unit_test/find_reference_null_key_entry/Run() @@ -108,12 +104,11 @@ //Calm before the storm testbed.test_assoc_list = list(null = victim) - #if DM_VERSION >= 515 var/refcount = refcount(victim) TEST_ASSERT_EQUAL(refcount, 4, "Should be: test references: 1 + baseline references: 3 (victim var,loc,allocated list)") - #endif - victim.DoSearchVar(testbed, "Fourth Run", search_time = 5) - TEST_ASSERT(testbed.test_assoc_list, "The ref-tracking tool failed to find a null key'd assoc list entry") + victim.DoSearchVar(testbed, "Fourth Run") + + TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find a null key'd assoc list entry") /datum/unit_test/find_reference_assoc_investigation/Run() var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test) @@ -126,13 +121,12 @@ var/list/to_find_null_assoc_nested = list(victim) testbed.test_assoc_list[null] = to_find_null_assoc_nested - #if DM_VERSION >= 515 var/refcount = refcount(victim) TEST_ASSERT_EQUAL(refcount, 5, "Should be: test references: 2 + baseline references: 3 (victim var,loc,allocated list)") - #endif - victim.DoSearchVar(testbed, "Fifth Run", search_time = 6) - TEST_ASSERT(victim.found_refs[to_find_in_key], "The ref-tracking tool failed to find a nested assoc list key") - TEST_ASSERT(victim.found_refs[to_find_null_assoc_nested], "The ref-tracking tool failed to find a null key'd nested assoc list entry") + victim.DoSearchVar(testbed, "Fifth Run") + + TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_in_key), "The ref-tracking tool failed to find a nested assoc list key") + TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_null_assoc_nested), "The ref-tracking tool failed to find a null key'd nested assoc list entry") SSgarbage.should_save_refs = FALSE /datum/unit_test/find_reference_static_investigation/Run() @@ -150,11 +144,9 @@ for(var/key in global.vars) global_vars[key] = global.vars[key] - #if DM_VERSION >= 515 var/refcount = refcount(victim) TEST_ASSERT_EQUAL(refcount, 5, "Should be: test references: 2 + baseline references: 3 (victim var,loc,allocated list)") - #endif - victim.DoSearchVar(global_vars, "Sixth Run", search_time = 7) + victim.DoSearchVar(global_vars, "Sixth Run") - TEST_ASSERT(victim.found_refs[global_vars], "The ref-tracking tool failed to find a natively global variable") + TEST_ASSERT(LAZYACCESS(victim.found_refs, global_vars), "The ref-tracking tool failed to find a natively global variable") SSgarbage.should_save_refs = FALSE diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm index 1ef15f8d0f512..d0d39227f43b7 100644 --- a/code/modules/unit_tests/fish_unit_tests.dm +++ b/code/modules/unit_tests/fish_unit_tests.dm @@ -28,10 +28,10 @@ var/obj/structure/aquarium/traits/aquarium = allocate(/obj/structure/aquarium/traits) TEST_ASSERT(!aquarium.sterile.try_to_reproduce(), "The test aquarium's sterile fish managed to reproduce when it shouldn't have") var/obj/item/fish/crossbreeder_jr = aquarium.crossbreeder.try_to_reproduce() - TEST_ASSERT(crossbreeder_jr, "The test aquarium's crossbreeder fish didn't manage to reproduce when it should have.") + TEST_ASSERT(crossbreeder_jr, "The test aquarium's crossbreeder fish didn't manage to reproduce when it should have") TEST_ASSERT_EQUAL(crossbreeder_jr.type, aquarium.cloner.type, "The test aquarium's crossbreeder fish mated with the wrong type of fish") var/obj/item/fish/cloner_jr = aquarium.cloner.try_to_reproduce() - TEST_ASSERT(cloner_jr, "The test aquarium's cloner fish didn't manage to reproduce when it should have.") + TEST_ASSERT(cloner_jr, "The test aquarium's cloner fish didn't manage to reproduce when it should have") TEST_ASSERT_NOTEQUAL(cloner_jr.type, aquarium.sterile.type, "The test aquarium's cloner fish mated with the sterile fish") ///Checks that fish evolutions work correctly. @@ -41,11 +41,24 @@ var/obj/structure/aquarium/evolution/aquarium = allocate(/obj/structure/aquarium/evolution) var/obj/item/fish/evolve_jr = aquarium.evolve.try_to_reproduce() TEST_ASSERT(evolve_jr, "The test aquarium's evolution fish didn't manage to reproduce when it should have") - TEST_ASSERT_NOTEQUAL(evolve_jr.type, /obj/item/fish/goldfish, "The test aquarium's evolution fish managed to pass the conditions of an impossible evolution.") + TEST_ASSERT_NOTEQUAL(evolve_jr.type, /obj/item/fish/goldfish, "The test aquarium's evolution fish managed to pass the conditions of an impossible evolution") TEST_ASSERT_EQUAL(evolve_jr.type, /obj/item/fish/clownfish, "The test aquarium's evolution fish's offspring isn't of the expected type") TEST_ASSERT(!(/datum/fish_trait/dummy in evolve_jr.fish_traits), "The test aquarium's evolution fish's offspring still has the old trait that ought to be removed by the evolution datum") TEST_ASSERT(/datum/fish_trait/dummy/two in evolve_jr.fish_traits, "The test aquarium's evolution fish's offspring doesn't have the evolution trait") +/datum/unit_test/fish_scanning + +/datum/unit_test/fish_scanning/Run() + var/scannable_fishes = 0 + for(var/obj/item/fish/fish_prototype as anything in subtypesof(/obj/item/fish)) + if(initial(fish_prototype.experisci_scannable)) + scannable_fishes++ + for(var/datum/experiment/scanning/fish/fish_scan as anything in typesof(/datum/experiment/scanning/fish)) + fish_scan = new fish_scan + var/scan_key = fish_scan.required_atoms[1] + if(fish_scan.required_atoms[scan_key] > scannable_fishes) + TEST_FAIL("[fish_scan.type] has requirements higher than the number of scannable fish types in the game: [scannable_fishes]") + ///dummy fish item used for the tests, as well with related subtypes and datums. /obj/item/fish/testdummy grind_results = list() diff --git a/code/modules/unit_tests/full_heal.dm b/code/modules/unit_tests/full_heal.dm index 9289abba9543a..f5d247d7a10e6 100644 --- a/code/modules/unit_tests/full_heal.dm +++ b/code/modules/unit_tests/full_heal.dm @@ -43,7 +43,7 @@ /datum/unit_test/full_heal_damage_types/Run() var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) - dummy.apply_damages(brute = 10, burn = 10, tox = 10, oxy = 10, clone = 10, stamina = 10) + dummy.apply_damages(brute = 10, burn = 10, tox = 10, oxy = 10, stamina = 10) dummy.fully_heal(HEAL_DAMAGE) if(dummy.getBruteLoss()) @@ -54,7 +54,5 @@ TEST_FAIL("The dummy still had toxins damage after a fully heal!") if(dummy.getOxyLoss()) TEST_FAIL("The dummy still had oxy damage after a fully heal!") - if(dummy.getCloneLoss()) - TEST_FAIL("The dummy still had clone damage after a fully heal!") if(dummy.getStaminaLoss()) TEST_FAIL("The dummy still had stamina damage after a fully heal!") diff --git a/code/modules/unit_tests/greyscale_config.dm b/code/modules/unit_tests/greyscale_config.dm index 9c5106be5b0c5..d3d9ce9d4fdd4 100644 --- a/code/modules/unit_tests/greyscale_config.dm +++ b/code/modules/unit_tests/greyscale_config.dm @@ -38,4 +38,4 @@ continue var/number_of_colors = length(colors) - 1 if(config.expected_colors != number_of_colors) - TEST_FAIL("[thing] has the wrong amount of colors configured for [config.DebugName()]. Expected [config.expected_colors] but only found [number_of_colors].") + TEST_FAIL("[thing] has the wrong amount of colors configured for [config.DebugName()]. Expected [config.expected_colors] colors but found [number_of_colors].") diff --git a/code/modules/unit_tests/hallucination_icons.dm b/code/modules/unit_tests/hallucination_icons.dm index 6477e94714ca6..c9f9cf8237c04 100644 --- a/code/modules/unit_tests/hallucination_icons.dm +++ b/code/modules/unit_tests/hallucination_icons.dm @@ -19,7 +19,7 @@ // Test preset delusion hallucinations for invalid image setups for(var/datum/hallucination/delusion/preset/hallucination as anything in subtypesof(/datum/hallucination/delusion/preset)) - if(initial(hallucination.dynamic_icon)) + if(initial(hallucination.dynamic_delusion)) continue var/icon = initial(hallucination.delusion_icon_file) var/icon_state = initial(hallucination.delusion_icon_state) diff --git a/code/modules/unit_tests/heretic_rituals.dm b/code/modules/unit_tests/heretic_rituals.dm index 7298a16327490..b55136cfacebe 100644 --- a/code/modules/unit_tests/heretic_rituals.dm +++ b/code/modules/unit_tests/heretic_rituals.dm @@ -63,8 +63,13 @@ var/list/created_atoms = list() for(var/ritual_item_path in knowledge.required_atoms) var/amount_to_create = knowledge.required_atoms[ritual_item_path] + if(islist(ritual_item_path)) + ritual_item_path = pick(ritual_item_path) for(var/i in 1 to amount_to_create) - created_atoms += new ritual_item_path(get_turf(our_heretic)) + var/obj/item/item = new ritual_item_path(get_turf(our_heretic)) + if(isitem(item)) + item.item_flags &= ~ABSTRACT + created_atoms += item // Now, we can ACTUALLY run the ritual. Let's do it. // Attempt to run the knowledge via the sacrifice rune. @@ -104,6 +109,10 @@ for(var/atom/thing as anything in nearby_atoms) if(!ismovable(thing)) continue + if(isitem(thing)) + var/obj/item/item = thing + if(item.item_flags & ABSTRACT) //bodyparts and stuff will get registered otherwise + continue // There are atoms around the rune still, and there shouldn't be. // All component atoms were consumed, and all resulting atoms were cleaned up. diff --git a/code/modules/unit_tests/hulk.dm b/code/modules/unit_tests/hulk.dm new file mode 100644 index 0000000000000..52706e9ac73fd --- /dev/null +++ b/code/modules/unit_tests/hulk.dm @@ -0,0 +1,44 @@ +/// Tests hulk attacking over normal attacking +/datum/unit_test/hulk_attack + var/hulk_hits = 0 + var/hand_hits = 0 + +/datum/unit_test/hulk_attack/Run() + var/mob/living/carbon/human/hulk = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + + + RegisterSignal(dummy, COMSIG_ATOM_HULK_ATTACK, PROC_REF(hulk_sig_fire)) + RegisterSignal(dummy, COMSIG_ATOM_ATTACK_HAND, PROC_REF(hand_sig_fire)) + + hulk.dna.add_mutation(/datum/mutation/human/hulk) + hulk.set_combat_mode(TRUE) + hulk.ClickOn(dummy) + + TEST_ASSERT_EQUAL(hulk_hits, 1, "Hulk should have hit the dummy once.") + TEST_ASSERT_EQUAL(hand_hits, 0, "Hulk should not have hit the dummy with attack_hand.") + TEST_ASSERT(dummy.getBruteLoss(), "Dummy should have taken brute damage from being hulk punched.") + +/datum/unit_test/hulk_attack/proc/hulk_sig_fire() + SIGNAL_HANDLER + hulk_hits += 1 + +/datum/unit_test/hulk_attack/proc/hand_sig_fire() + SIGNAL_HANDLER + hand_hits += 1 + +/// Tests that hulks aren't given rapid attacks from rapid attack gloves +/datum/unit_test/hulk_north_star + +/datum/unit_test/hulk_north_star/Run() + var/mob/living/carbon/human/hulk = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + var/obj/item/clothing/gloves/rapid/fotns = allocate(/obj/item/clothing/gloves/rapid) + + hulk.equip_to_appropriate_slot(fotns) + hulk.dna.add_mutation(/datum/mutation/human/hulk) + hulk.set_combat_mode(TRUE) + hulk.ClickOn(dummy) + + TEST_ASSERT_NOTEQUAL(hulk.next_move, world.time + CLICK_CD_RAPID, "Hulk should not gain the effects of the Fists of the North Star.") + TEST_ASSERT_EQUAL(hulk.next_move, world.time + CLICK_CD_MELEE, "Hulk click cooldown was a value not expected.") diff --git a/code/modules/unit_tests/human_through_recycler.dm b/code/modules/unit_tests/human_through_recycler.dm index 7d554d72690b2..c51b9a0e30bed 100644 --- a/code/modules/unit_tests/human_through_recycler.dm +++ b/code/modules/unit_tests/human_through_recycler.dm @@ -17,8 +17,13 @@ TEST_ASSERT_EQUAL(damage_incurred, chewer.crush_damage, "Assistant did not take the expected amount of brute damage ([chewer.crush_damage]) from the emagged recycler! Took ([damage_incurred]) instead.") TEST_ASSERT(chewer.bloody, "The emagged recycler did not become bloody after crushing the assistant!") + var/list/bad_contents = assistant.contents + for(var/obj/item/item in assistant.contents) + if(item.item_flags & ABSTRACT) + bad_contents -= item + // Now, let's test to see if all of their clothing got properly deleted. - TEST_ASSERT_EQUAL(length(assistant.contents), 0, "Assistant still has items in its contents after being put through an emagged recycler!") + TEST_ASSERT_EQUAL(length(bad_contents), 0, "Assistant still has items in its contents after being put through an emagged recycler!") // Consistent Assistants will always have the following: ID, PDA, backpack, a uniform, a headset, and a pair of shoes. If any of these are still present, then the recycler did not properly delete the assistant's clothing. // However, let's check for EVERYTHING just in case, because we don't want to miss anything. // This is just what we expect to be deleted. diff --git a/code/modules/unit_tests/ling_decap.dm b/code/modules/unit_tests/ling_decap.dm new file mode 100644 index 0000000000000..4c8c7e4e03124 --- /dev/null +++ b/code/modules/unit_tests/ling_decap.dm @@ -0,0 +1,47 @@ +/// Test lings don't die when decapitated. +/datum/unit_test/ling_decap + +/datum/unit_test/ling_decap/Run() + var/mob/living/carbon/human/ling = allocate(/mob/living/carbon/human/consistent) + ling.mind_initialize() + ling.mind.add_antag_datum(/datum/antagonist/changeling) + + var/obj/item/bodypart/head/noggin = ling.get_bodypart(BODY_ZONE_HEAD) + noggin.dismember() + TEST_ASSERT_NULL(ling.get_bodypart(BODY_ZONE_HEAD), "Changeling failed to be decapitated.") + var/obj/item/organ/internal/brain/brain = locate(/obj/item/organ/internal/brain) in noggin + TEST_ASSERT_NULL(brain.brainmob.mind, "Changeling's mind was moved to their brain after decapitation, but it should have remained in their body.") + + var/obj/item/organ/internal/brain/oldbrain = locate(/obj/item/organ/internal/brain) in noggin + noggin.drop_organs() + TEST_ASSERT_NULL(locate(/obj/item/organ/internal/brain) in noggin, "Changeling's head failed to drop its brain.") + TEST_ASSERT_NULL(oldbrain.brainmob.mind, "Changeling's mind was moved to their brain after decapitation and organ dropping, but it should have remained in their body.") + + TEST_ASSERT_EQUAL(ling.stat, CONSCIOUS, "Changeling was not conscious after losing their head.") + + // Cleanup + qdel(noggin) + for(var/obj/item/organ/leftover in ling.loc) + qdel(leftover) + +/// Tests people get decapitated properly. +/datum/unit_test/normal_decap + +/datum/unit_test/normal_decap/Run() + var/mob/living/carbon/human/normal_guy = allocate(/mob/living/carbon/human/consistent) + normal_guy.mind_initialize() + var/my_guys_mind = normal_guy.mind + + var/obj/item/bodypart/head/noggin = normal_guy.get_bodypart(BODY_ZONE_HEAD) + noggin.dismember() + var/obj/item/organ/internal/brain/brain = locate(/obj/item/organ/internal/brain) in noggin + TEST_ASSERT_EQUAL(brain.brainmob.mind, my_guys_mind, "Dummy's mind was not moved to their brain after decapitation.") + + var/obj/item/organ/internal/brain/oldbrain = locate(/obj/item/organ/internal/brain) in noggin + noggin.drop_organs() + TEST_ASSERT_EQUAL(oldbrain.brainmob.mind, my_guys_mind, "Dummy's mind was not moved to their brain after being removed from their head.") + + // Cleanup + qdel(noggin) + for(var/obj/item/organ/leftover in normal_guy.loc) + qdel(leftover) diff --git a/code/modules/unit_tests/mafia.dm b/code/modules/unit_tests/mafia.dm new file mode 100644 index 0000000000000..85fa50842932b --- /dev/null +++ b/code/modules/unit_tests/mafia.dm @@ -0,0 +1,48 @@ +///Checks if a Mafia game with a Modular Computer and a Ghost will run with 'basic_setup', which is the default +///way the game is ran, without admin-intervention. +///The game should immediately end in a Town Victory due to lack of evils, but we can verify that both the PDA and the ghost +///successfully managed to get into the round. +/datum/unit_test/mafia + ///Boolean on whether the Mafia game started or not. Will Fail if it hasn't. + var/mafia_game_started = FALSE + +/datum/unit_test/mafia/Run() + RegisterSignal(SSdcs, COMSIG_MAFIA_GAME_START, PROC_REF(on_mafia_start)) + var/datum/mafia_controller/controller = GLOB.mafia_game || new() + + TEST_ASSERT(controller, "No Mafia game was found, nor was it able to be created properly.") + + //spawn human and give them a laptop. + var/mob/living/carbon/human/consistent/living_player = allocate(/mob/living/carbon/human/consistent) + var/obj/item/modular_computer/laptop/preset/mafia/modpc_player = allocate(/obj/item/modular_computer/laptop/preset/mafia) + living_player.put_in_active_hand(modpc_player, TRUE) + + //make the laptop run Mafia app. + var/datum/computer_file/program/mafia/mafia_program = locate() in modpc_player.stored_files + TEST_ASSERT(mafia_program, "Mafia program was unable to be found on [modpc_player].") + modpc_player.active_program = mafia_program + + //Spawn a ghost and make them eligible to use the Mafia UI (just to be safe). + var/mob/dead/observer/ghost_player = allocate(/mob/dead/observer) + var/datum/client_interface/mock_client = new() + ghost_player.mock_client = mock_client + mock_client.mob = ghost_player + ADD_TRAIT(ghost_player, TRAIT_PRESERVE_UI_WITHOUT_CLIENT, TRAIT_SOURCE_UNIT_TESTS) + + //First make the human sign up for Mafia, then the ghost, then we'll auto-start it. + controller.signup_mafia(living_player, modpc = modpc_player) + controller.signup_mafia(ghost_player, ghost_client = mock_client) + + controller.basic_setup() + + TEST_ASSERT(mafia_game_started, "Mafia game did not start despite basic_setup being called.") + TEST_ASSERT_NOTNULL(controller.player_role_lookup[modpc_player], "The Modular Computer was unable to join a game of Mafia.") + TEST_ASSERT_NOTNULL(controller.player_role_lookup[mock_client.ckey], "The Mock client wasn't put into a game of Mafia.") + + mock_client.mob = null + + qdel(controller) + +/datum/unit_test/mafia/proc/on_mafia_start(datum/controller/subsystem/processing/dcs/source, datum/mafia_controller/game) + SIGNAL_HANDLER + mafia_game_started = TRUE diff --git a/code/modules/unit_tests/mapload_space_verification.dm b/code/modules/unit_tests/mapload_space_verification.dm index 80772b8a633cd..da35af1d1761f 100644 --- a/code/modules/unit_tests/mapload_space_verification.dm +++ b/code/modules/unit_tests/mapload_space_verification.dm @@ -31,7 +31,7 @@ if(!isspaceturf(iterated_turf) || is_type_in_typecache(turf_area, excluded_area_typecache)) continue // Alright, so let's assume we have intended behavior. If something yorks, we'll get a bare `/area` (maploader?) or a mapper is doing something they shouldn't be doing. // We need turf_area.type for the error message because we have fifteen million ruin areas named "Unexplored Location" and it's completely unhelpful here. - TEST_FAIL("Space turf found in non-allowed area ([turf_area.type]) at [AREACOORD(iterated_turf)]! Please ensure that all space turfs are in an /area/space!") + TEST_FAIL("Space turf [iterated_turf.type] found in non-allowed area ([turf_area.type]) at [AREACOORD(iterated_turf)]! Please ensure that all space turfs are in an /area/space!") /// Verifies that there are ZERO space turfs on a valid planetary station. We NEVER want space turfs here, so we do not check for /area/space here since something completely undesirable is happening. diff --git a/code/modules/unit_tests/mecha_damage.dm b/code/modules/unit_tests/mecha_damage.dm index f0fb4c59bb247..dc5f4ecae8a1d 100644 --- a/code/modules/unit_tests/mecha_damage.dm +++ b/code/modules/unit_tests/mecha_damage.dm @@ -83,4 +83,4 @@ TEST_ASSERT(post_hit_health < pre_integrity, "[checking] was [hit_by_phrase], but didn't take any damage.") var/damage_taken = round(pre_integrity - post_hit_health, DAMAGE_PRECISION) - TEST_ASSERT_EQUAL(damage_taken, expected_damage, "[checking] didn't take the expected amount of damage when [hit_by_phrase]. (Expected damage: [expected_damage], recieved damage: [damage_taken])") + TEST_ASSERT_EQUAL(damage_taken, expected_damage, "[checking] didn't take the expected amount of damage when [hit_by_phrase]. (Expected damage: [expected_damage], received damage: [damage_taken])") diff --git a/code/modules/unit_tests/mob_chains.dm b/code/modules/unit_tests/mob_chains.dm new file mode 100644 index 0000000000000..2562019958e46 --- /dev/null +++ b/code/modules/unit_tests/mob_chains.dm @@ -0,0 +1,31 @@ +/// Checks if mobs who are linked together with the mob chain component react as expected +/datum/unit_test/mob_chains + +/datum/unit_test/mob_chains/Run() + var/mob/living/centipede_head = allocate(/mob/living/basic/pet/dog) + var/list/segments = list(centipede_head) + centipede_head.AddComponent(/datum/component/mob_chain) + var/mob/living/centipede_tail = centipede_head + for (var/i in 1 to 2) + var/mob/living/new_segment = allocate(/mob/living/basic/pet/dog) + new_segment.AddComponent(/datum/component/mob_chain, front = centipede_tail) + segments += new_segment + centipede_tail = new_segment + + var/test_damage = 15 + centipede_head.apply_damage(test_damage, BRUTE) + TEST_ASSERT_EQUAL(centipede_head.bruteloss, 0, "Centipede head took damage which should have been passed to its tail.") + TEST_ASSERT_EQUAL(centipede_tail.bruteloss, test_damage, "Centipede tail did not take damage which should have originated from its head.") + + var/expected_damage = 5 + for (var/mob/living/segment as anything in segments) + segment.combat_mode = TRUE + segment.melee_damage_lower = expected_damage + segment.melee_damage_upper = expected_damage + + var/mob/living/victim = allocate(/mob/living/basic/pet/dog) + centipede_head.ClickOn(victim) + TEST_ASSERT_EQUAL(victim.bruteloss, expected_damage * 3, "Centipede failed to do damage with all of its segments.") + + centipede_head.death() + TEST_ASSERT_EQUAL(centipede_tail.stat, DEAD, "Centipede tail failed to die with head.") diff --git a/code/modules/unit_tests/mob_damage.dm b/code/modules/unit_tests/mob_damage.dm new file mode 100644 index 0000000000000..c27bc31ffe0f2 --- /dev/null +++ b/code/modules/unit_tests/mob_damage.dm @@ -0,0 +1,582 @@ +/// Tests to make sure mob damage procs are working correctly +/datum/unit_test/mob_damage + priority = TEST_LONGER + +/datum/unit_test/mob_damage/Destroy() + SSmobs.ignite() + return ..() + +/datum/unit_test/mob_damage/Run() + SSmobs.pause() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + dummy.maxHealth = 200 // tank mode + + /* The sanity tests: here we make sure that: + 1) That damage procs are returning the expected values. They should be returning the actual amount of damage taken/healed. + (Negative values mean damage was taken, positive mean healing) + 2) Verifying that the damage has been accurately applied to the mob afterwards. */ + + test_sanity_simple(dummy) + test_sanity_complex(dummy) + + // Testing if biotypes are working as intended + test_biotypes(dummy) + + // Testing whether or not TRAIT_NOBREATH is working as intended + test_nobreath(dummy) + + // Testing whether or not TRAIT_TOXINLOVER and TRAIT_TOXIMMUNE are working as intended + test_toxintraits(dummy) + + // Testing the proc ordered_healing() + test_ordered_healing(dummy) + + // testing with godmode enabled + test_godmode(dummy) + +/** + * Test whether the adjust damage procs return the correct values and that the mob's health is the expected value afterwards. + * + * By default this calls apply_damage(amount) followed by verify_damage(amount_after) and returns TRUE if both succeeded. + * amount_after defaults to the mob's current stamina loss but can be overridden as needed. + * + * Arguments: + * * testing_mob - the mob to apply the damage to + * * amount - the amount of damage to apply to the mob + * * expected - what the expected return value of the damage proc is + * * amount_after - in case you want to specify what the damage amount on the mob should be afterwards + * * included_types - Bitflag of damage types to apply + * * biotypes - the biotypes of damage to apply + * * bodytypes - the bodytypes of damage to apply + * * forced - whether or not this is forced damage + */ +/datum/unit_test/mob_damage/proc/test_apply_damage(mob/living/testing_mob, amount, expected = -amount, amount_after, included_types, biotypes, bodytypes, forced) + if(isnull(amount_after)) + amount_after = testing_mob.getStaminaLoss() - expected // stamina loss applies to both carbon and basic mobs the same way, so that's why we're using it here + if(!apply_damage(testing_mob, amount, expected, included_types, biotypes, bodytypes, forced)) + return FALSE + if(!verify_damage(testing_mob, amount_after, included_types)) + return FALSE + return TRUE + +/** + * Test whether the set damage procs return the correct values and that the mob's health is the expected value afterwards. + * + * By default this calls set_damage(amount) followed by verify_damage(amount_after) and returns TRUE if both succeeded. + * amount_after defaults to the mob's current stamina loss but can be overridden as needed. + * + * Arguments: + * * testing_mob - the mob to apply the damage to + * * amount - the amount of damage to apply to the mob + * * expected - what the expected return value of the damage proc is + * * amount_after - in case you want to specify what the damage amount on the mob should be afterwards + * * included_types - Bitflag of damage types to apply + * * biotypes - the biotypes of damage to apply + * * bodytypes - the bodytypes of damage to apply + * * forced - whether or not this is forced damage + */ +/datum/unit_test/mob_damage/proc/test_set_damage(mob/living/testing_mob, amount, expected, amount_after, included_types, biotypes, bodytypes, forced) + if(isnull(amount_after)) + amount_after = testing_mob.getStaminaLoss() - expected + if(!set_damage(testing_mob, amount, expected, included_types, biotypes, bodytypes, forced)) + return FALSE + if(!verify_damage(testing_mob, amount_after, included_types)) + return FALSE + return TRUE + +/** + * Check that the mob has a specific amount of damage + * + * By default this checks that the mob has of every type of damage. + * Arguments: + * * testing_mob - the mob to check the damage of + * * amount - the amount of damage to verify that the mob has + * * included_types - Bitflag of damage types to check. + */ +/datum/unit_test/mob_damage/proc/verify_damage(mob/living/testing_mob, amount, included_types = ALL) + if(included_types & TOXLOSS) + TEST_ASSERT_EQUAL(testing_mob.getToxLoss(), amount, \ + "[testing_mob] should have [amount] toxin damage, instead they have [testing_mob.getToxLoss()]!") + if(included_types & BRUTELOSS) + TEST_ASSERT_EQUAL(round(testing_mob.getBruteLoss(), 1), amount, \ + "[testing_mob] should have [amount] brute damage, instead they have [testing_mob.getBruteLoss()]!") + if(included_types & FIRELOSS) + TEST_ASSERT_EQUAL(round(testing_mob.getFireLoss(), 1), amount, \ + "[testing_mob] should have [amount] burn damage, instead they have [testing_mob.getFireLoss()]!") + if(included_types & OXYLOSS) + TEST_ASSERT_EQUAL(testing_mob.getOxyLoss(), amount, \ + "[testing_mob] should have [amount] oxy damage, instead they have [testing_mob.getOxyLoss()]!") + if(included_types & STAMINALOSS) + TEST_ASSERT_EQUAL(testing_mob.getStaminaLoss(), amount, \ + "[testing_mob] should have [amount] stamina damage, instead they have [testing_mob.getStaminaLoss()]!") + return TRUE + +/** + * Apply a specific amount of damage to the mob using adjustBruteLoss(), adjustToxLoss(), etc. + * + * By default this applies damage of every type to the mob, and checks that the damage procs return the value + * Arguments: + * * testing_mob - the mob to apply the damage to + * * amount - the amount of damage to apply to the mob + * * expected - what the expected return value of the damage proc is + * * included_types - Bitflag of damage types to apply + * * biotypes - the biotypes of damage to apply + * * bodytypes - the bodytypes of damage to apply + * * forced - whether or not this is forced damage + */ +/datum/unit_test/mob_damage/proc/apply_damage(mob/living/testing_mob, amount, expected = -amount, included_types = ALL, biotypes = ALL, bodytypes = ALL, forced = FALSE) + var/damage_returned + if(included_types & TOXLOSS) + damage_returned = testing_mob.adjustToxLoss(amount, updating_health = FALSE, forced = forced, required_biotype = biotypes) + TEST_ASSERT_EQUAL(damage_returned, expected, \ + "adjustToxLoss() should have returned [expected], but returned [damage_returned] instead!") + if(included_types & BRUTELOSS) + damage_returned = round(testing_mob.adjustBruteLoss(amount, updating_health = FALSE, forced = forced, required_bodytype = bodytypes), 1) + TEST_ASSERT_EQUAL(damage_returned, expected, \ + "adjustBruteLoss() should have returned [expected], but returned [damage_returned] instead!") + if(included_types & FIRELOSS) + damage_returned = round(testing_mob.adjustFireLoss(amount, updating_health = FALSE, forced = forced, required_bodytype = bodytypes), 1) + TEST_ASSERT_EQUAL(damage_returned, expected, \ + "adjustFireLoss() should have returned [expected], but returned [damage_returned] instead!") + if(included_types & OXYLOSS) + damage_returned = testing_mob.adjustOxyLoss(amount, updating_health = FALSE, forced = forced, required_biotype = biotypes) + TEST_ASSERT_EQUAL(damage_returned, expected, \ + "adjustOxyLoss() should have returned [expected], but returned [damage_returned] instead!") + if(included_types & STAMINALOSS) + damage_returned = testing_mob.adjustStaminaLoss(amount, updating_stamina = FALSE, forced = forced, required_biotype = biotypes) + TEST_ASSERT_EQUAL(damage_returned, expected, \ + "adjustStaminaLoss() should have returned [expected], but returned [damage_returned] instead!") + return TRUE + +/** + * Set a specific amount of damage for the mob using setBruteLoss(), setToxLoss(), etc. + * + * By default this sets every type of damage to for the mob, and checks that the damage procs return the value + * Arguments: + * * testing_mob - the mob to apply the damage to + * * amount - the amount of damage to apply to the mob + * * expected - what the expected return value of the damage proc is + * * included_types - Bitflag of damage types to apply + * * biotypes - the biotypes of damage to apply + * * bodytypes - the bodytypes of damage to apply + * * forced - whether or not this is forced damage + */ +/datum/unit_test/mob_damage/proc/set_damage(mob/living/testing_mob, amount, expected = -amount, included_types = ALL, biotypes = ALL, bodytypes = ALL, forced = FALSE) + var/damage_returned + if(included_types & TOXLOSS) + damage_returned = testing_mob.setToxLoss(amount, updating_health = FALSE, forced = forced, required_biotype = biotypes) + TEST_ASSERT_EQUAL(damage_returned, expected, \ + "setToxLoss() should have returned [expected], but returned [damage_returned] instead!") + if(included_types & BRUTELOSS) + damage_returned = round(testing_mob.setBruteLoss(amount, updating_health = FALSE, forced = forced), 1) + TEST_ASSERT_EQUAL(damage_returned, expected, \ + "setBruteLoss() should have returned [expected], but returned [damage_returned] instead!") + if(included_types & FIRELOSS) + damage_returned = round(testing_mob.setFireLoss(amount, updating_health = FALSE, forced = forced), 1) + TEST_ASSERT_EQUAL(damage_returned, expected, \ + "setFireLoss() should have returned [expected], but returned [damage_returned] instead!") + if(included_types & OXYLOSS) + damage_returned = testing_mob.setOxyLoss(amount, updating_health = FALSE, forced = forced, required_biotype = biotypes) + TEST_ASSERT_EQUAL(damage_returned, expected, \ + "setOxyLoss() should have returned [expected], but returned [damage_returned] instead!") + if(included_types & STAMINALOSS) + damage_returned = testing_mob.setStaminaLoss(amount, updating_stamina = FALSE, forced = forced, required_biotype = biotypes) + TEST_ASSERT_EQUAL(damage_returned, expected, \ + "setStaminaLoss() should have returned [expected], but returned [damage_returned] instead!") + return TRUE + +/// Sanity tests damage and healing using adjustToxLoss, adjustBruteLoss, etc +/datum/unit_test/mob_damage/proc/test_sanity_simple(mob/living/carbon/human/consistent/dummy) + // Apply 5 damage and then heal it + if(!test_apply_damage(dummy, amount = 5)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly") + + if(!test_apply_damage(dummy, amount = -5)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! healing was not applied correctly") + + // Apply 15 damage and heal 3 + if(!test_apply_damage(dummy, amount = 15)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly") + + if(!test_apply_damage(dummy, amount = -3)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! underhealing was not applied correctly") + + // Now overheal by 666. It should heal for 12. + + if(!test_apply_damage(dummy, amount = -666, expected = 12)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! overhealing was not applied correctly") + + // Now test the damage setter procs + + // set all types of damage to 5 + if(!test_set_damage(dummy, amount = 5, expected = -5)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! failed to set damage to 5") + // now try healing 5 + if(!test_set_damage(dummy, amount = 0, expected = 5)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! failed to set damage to 0") + +/// Sanity tests damage and healing using the more complex procs like take_overall_damage(), heal_overall_damage(), etc +/datum/unit_test/mob_damage/proc/test_sanity_complex(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) + + var/damage_returned + // take 5 brute, 2 burn + damage_returned = round(dummy.take_bodypart_damage(5, 2, updating_health = FALSE), 1) + TEST_ASSERT_EQUAL(damage_returned, -7, \ + "take_bodypart_damage() should have returned -7, but returned [damage_returned] instead!") + + TEST_ASSERT_EQUAL(round(dummy.getBruteLoss(), 1), 5, \ + "Dummy should have 5 brute damage, instead they have [dummy.getBruteLoss()]!") + TEST_ASSERT_EQUAL(round(dummy.getFireLoss(), 1), 2, \ + "Dummy should have 2 burn damage, instead they have [dummy.getFireLoss()]!") + + // heal 4 brute, 1 burn + damage_returned = round(dummy.heal_bodypart_damage(4, 1, updating_health = FALSE), 1) + TEST_ASSERT_EQUAL(damage_returned, 5, \ + "heal_bodypart_damage() should have returned 5, but returned [damage_returned] instead!") + + if(!verify_damage(dummy, 1, included_types = BRUTELOSS|FIRELOSS)) + TEST_FAIL("heal_bodypart_damage did not apply its healing correctly on the mob!") + + // heal 1 brute, 1 burn + damage_returned = round(dummy.heal_overall_damage(1, 1, updating_health = FALSE), 1) + TEST_ASSERT_EQUAL(damage_returned, 2, \ + "heal_overall_damage() should have returned 2, but returned [damage_returned] instead!") + + if(!verify_damage(dummy, 0, included_types = BRUTELOSS|FIRELOSS)) + TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mob!") + + // take 50 brute, 50 burn + damage_returned = round(dummy.take_overall_damage(50, 50, updating_health = FALSE), 1) + TEST_ASSERT_EQUAL(damage_returned, -100, \ + "take_overall_damage() should have returned -100, but returned [damage_returned] instead!") + + if(!verify_damage(dummy, 50, included_types = BRUTELOSS|FIRELOSS)) + TEST_FAIL("take_overall_damage did not apply its damage correctly on the mob!") + + // testing negative damage amount args with the overall damage procs - the sign should be ignored for these procs + + damage_returned = round(dummy.take_bodypart_damage(-5, -5, updating_health = FALSE), 1) + TEST_ASSERT_EQUAL(damage_returned, -10, \ + "take_bodypart_damage() should have returned -10, but returned [damage_returned] instead!") + + damage_returned = round(dummy.heal_bodypart_damage(-5, -5, updating_health = FALSE), 1) + TEST_ASSERT_EQUAL(damage_returned, 10, \ + "heal_bodypart_damage() should have returned 10, but returned [damage_returned] instead!") + + damage_returned = round(dummy.take_overall_damage(-5, -5, updating_health = FALSE), 1) + TEST_ASSERT_EQUAL(damage_returned, -10, \ + "take_overall_damage() should have returned -10, but returned [damage_returned] instead!") + + damage_returned = round(dummy.heal_overall_damage(-5, -5, updating_health = FALSE), 1) + TEST_ASSERT_EQUAL(damage_returned, 10, \ + "heal_overall_damage() should have returned 10, but returned [damage_returned] instead!") + + if(!verify_damage(dummy, 50, included_types = BRUTELOSS|FIRELOSS)) + TEST_FAIL("heal_overall_damage did not apply its healingcorrectly on the mob!") + + // testing overhealing + + damage_returned = round(dummy.heal_overall_damage(75, 99, updating_health = FALSE), 1) + TEST_ASSERT_EQUAL(damage_returned, 100, \ + "heal_overall_damage() should have returned 100, but returned [damage_returned] instead!") + + if(!verify_damage(dummy, 0, included_types = BRUTELOSS|FIRELOSS)) + TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mob!") + +/// Tests damage procs with godmode on +/datum/unit_test/mob_damage/proc/test_godmode(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) + // flip godmode bit to 1 + dummy.status_flags ^= GODMODE + + // Apply 9 damage and then heal it + if(!test_apply_damage(dummy, amount = 9, expected = 0)) + TEST_FAIL("ABOVE FAILURE: failed test_godmode! mob took damage despite having godmode enabled.") + + if(!test_apply_damage(dummy, amount = -9, expected = 0)) + TEST_FAIL("ABOVE FAILURE: failed test_godmode! mob healed when they should've been at full health.") + + // Apply 11 damage and then heal it, this time with forced enabled. The damage should go through regardless of godmode. + if(!test_apply_damage(dummy, amount = 11, forced = TRUE)) + TEST_FAIL("ABOVE FAILURE: failed test_godmode! godmode did not respect forced = TRUE") + + if(!test_apply_damage(dummy, amount = -11, forced = TRUE)) + TEST_FAIL("ABOVE FAILURE: failed test_godmode! godmode did not respect forced = TRUE") + + // flip godmode bit back to 0 + dummy.status_flags ^= GODMODE + +/// Testing biotypes +/datum/unit_test/mob_damage/proc/test_biotypes(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) + // Testing biotypes using a plasmaman, who is MOB_MINERAL and MOB_HUMANOID + dummy.set_species(/datum/species/plasmaman) + + // argumentless default: should default to required_biotype = ALL. The damage should be applied in that case. + if(!test_apply_damage(dummy, 1, included_types = TOXLOSS|STAMINALOSS)) + TEST_FAIL("ABOVE FAILURE: plasmaman did not take damage with biotypes = ALL") + + // If we specify MOB_ORGANIC, the damage should not get applied because plasmamen lack that biotype. + if(!test_apply_damage(dummy, 1, expected = 0, included_types = TOXLOSS|STAMINALOSS, biotypes = MOB_ORGANIC)) + TEST_FAIL("ABOVE FAILURE: plasmaman took damage with biotypes = MOB_ORGANIC") + + // Now if we specify MOB_MINERAL the damage should get applied. + if(!test_apply_damage(dummy, 1, included_types = TOXLOSS|STAMINALOSS, biotypes = MOB_MINERAL)) + TEST_FAIL("ABOVE FAILURE: plasmaman did not take damage with biotypes = MOB_MINERAL") + + // Transform back to human + dummy.set_species(/datum/species/human) + + // We have 2 damage presently. + // Try to heal it; let's specify MOB_MINERAL, which should no longer work because we have changed back to a human. + if(!test_apply_damage(dummy, -2, expected = 0, included_types = TOXLOSS|STAMINALOSS, biotypes = MOB_MINERAL)) + TEST_FAIL("ABOVE FAILURE: human took damage with biotypes = MOB_MINERAL") + + // Force heal some of the damage. When forced = TRUE the damage/healing gets applied no matter what. + if(!test_apply_damage(dummy, -1, included_types = TOXLOSS|STAMINALOSS, biotypes = MOB_MINERAL, forced = TRUE)) + TEST_FAIL("ABOVE FAILURE: human did not get healed when biotypes = MOB_MINERAL and forced = TRUE") + + // Now heal the rest of it with the correct biotype. Make sure that this works. We should have 0 damage afterwards. + if(!test_apply_damage(dummy, -1, included_types = TOXLOSS|STAMINALOSS, biotypes = MOB_ORGANIC)) + TEST_FAIL("ABOVE FAILURE: human did not get healed with biotypes = MOB_ORGANIC") + +/// Testing oxyloss with the TRAIT_NOBREATH +/datum/unit_test/mob_damage/proc/test_nobreath(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) + + // TRAIT_NOBREATH is supposed to prevent oxyloss damage (but not healing). Let's make sure that's the case. + ADD_TRAIT(dummy, TRAIT_NOBREATH, TRAIT_SOURCE_UNIT_TESTS) + // force some oxyloss here + dummy.setOxyLoss(2, updating_health = FALSE, forced = TRUE) + + // Try to take more oxyloss damage with TRAIT_NOBREATH. It should not work. + if(!test_apply_damage(dummy, 2, expected = 0, amount_after = dummy.getOxyLoss(), included_types = OXYLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_nobreath! mob took oxyloss damage while having TRAIT_NOBREATH") + + // Make sure we are still be able to heal the oxyloss. This should work. + if(!test_apply_damage(dummy, -2, amount_after = dummy.getOxyLoss()-2, included_types = OXYLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_nobreath! mob could not heal oxyloss damage while having TRAIT_NOBREATH") + + REMOVE_TRAIT(dummy, TRAIT_NOBREATH, TRAIT_SOURCE_UNIT_TESTS) + +/// Testing toxloss with TRAIT_TOXINLOVER and TRAIT_TOXIMMUNE +/datum/unit_test/mob_damage/proc/test_toxintraits(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) + + // TRAIT_TOXINLOVER is supposed to invert toxin damage and healing. Things that would normally cause toxloss now heal it, and vice versa. + ADD_TRAIT(dummy, TRAIT_TOXINLOVER, TRAIT_SOURCE_UNIT_TESTS) + // force some toxloss here + dummy.setToxLoss(2, updating_health = FALSE, forced = TRUE) + + // Try to take more toxloss damage with TRAIT_TOXINLOVER. It should heal instead. + if(!test_apply_damage(dummy, 2, expected = 2, amount_after = dummy.getToxLoss()-2, included_types = TOXLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob did not heal from toxin damage with TRAIT_TOXINLOVER") + + // If we try to heal the toxloss we should take damage instead + if(!test_apply_damage(dummy, -2, expected = -2, amount_after = dummy.getToxLoss()+2, included_types = TOXLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob did not take damage from toxin healing with TRAIT_TOXINLOVER") + + // TOXIMMUNE trait should prevent the damage you get from being healed by toxins medicines while having TRAIT_TOXINLOVER + ADD_TRAIT(dummy, TRAIT_TOXIMMUNE, TRAIT_SOURCE_UNIT_TESTS) + + // need to force apply some toxin damage since the TOXIMUNNE trait sets toxloss to 0 upon being added + dummy.setToxLoss(2, updating_health = FALSE, forced = TRUE) + + // try to 'heal' again - this time it should just do nothing because we should be immune to any sort of toxin damage - including from inverted healing + if(!test_apply_damage(dummy, -2, expected = 0, amount_after = dummy.getToxLoss(), included_types = TOXLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob should not have taken any damage or healing with TRAIT_TOXINLOVER + TRAIT_TOXIMMUNE") + + // ok, let's try taking 'damage'. The inverted damage should still heal mobs with the TOXIMMUNE trait. + if(!test_apply_damage(dummy, 2, expected = 2, amount_after = dummy.getToxLoss()-2, included_types = TOXLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob did not heal from taking toxin damage with TRAIT_TOXINLOVER + TRAIT_TOXIMMUNE") + + REMOVE_TRAIT(dummy, TRAIT_TOXINLOVER, TRAIT_SOURCE_UNIT_TESTS) + REMOVE_TRAIT(dummy, TRAIT_TOXIMMUNE, TRAIT_SOURCE_UNIT_TESTS) + +/// Testing heal_ordered_damage() +/datum/unit_test/mob_damage/proc/test_ordered_healing(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) + var/damage_returned + + // We apply 20 brute, 20 burn, and 20 toxin damage. 60 damage total + apply_damage(dummy, 20, included_types = TOXLOSS|BRUTELOSS|FIRELOSS) + + // Heal 30 damage of that, starting from brute + damage_returned = round(dummy.heal_ordered_damage(30, list(BRUTE, BURN, TOX)), 1) + TEST_ASSERT_EQUAL(damage_returned, 30, \ + "heal_ordered_damage() should have returned 30, but returned [damage_returned] instead!") + + // Should have 10 burn damage and 20 toxins damage remaining, let's check + TEST_ASSERT_EQUAL(dummy.getBruteLoss(), 0, \ + "[src] should have 0 brute damage, but has [dummy.getBruteLoss()] instead!") + TEST_ASSERT_EQUAL(dummy.getFireLoss(), 10, \ + "[src] should have 10 burn damage, but has [dummy.getFireLoss()] instead!") + TEST_ASSERT_EQUAL(dummy.getToxLoss(), 20, \ + "[src] should have 20 toxin damage, but has [dummy.getToxLoss()] instead!") + + // Now heal the remaining 30, overhealing by 5. + damage_returned = round(dummy.heal_ordered_damage(35, list(BRUTE, BURN, TOX)), 1) + TEST_ASSERT_EQUAL(damage_returned, 30, \ + "heal_ordered_damage() should have returned 30, but returned [damage_returned] instead!") + + // Should have no damage remaining + TEST_ASSERT_EQUAL(dummy.getBruteLoss(), 0, \ + "[src] should have 0 brute damage, but has [dummy.getBruteLoss()] instead!") + TEST_ASSERT_EQUAL(dummy.getFireLoss(), 0, \ + "[src] should have 0 burn damage, but has [dummy.getFireLoss()] instead!") + TEST_ASSERT_EQUAL(dummy.getToxLoss(), 0, \ + "[src] should have 0 toxin damage, but has [dummy.getToxLoss()] instead!") + +/// Tests that mob damage procs are working as intended for basic mobs +/datum/unit_test/mob_damage/basic + +/datum/unit_test/mob_damage/basic/Run() + SSmobs.pause() + var/mob/living/basic/mouse/gray/gusgus = allocate(/mob/living/basic/mouse/gray) + // give gusgus a damage_coeff of 1 for this test + gusgus.damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 1, OXY = 1) + // tank mouse + gusgus.maxHealth = 200 + + test_sanity_simple(gusgus) + test_sanity_complex(gusgus) + +/** + * Check that the mob has a specific amount of damage. Note: basic mobs have all incoming damage types besides stam converted into brute damage. + * + * By default this checks that the mob has of every type of damage. + * Arguments: + * * testing_mob - the mob to check the damage of + * * amount - the amount of damage to verify that the mob has + * * expected - the expected return value of the damage procs, if it differs from the default of (amount * 4) + * * included_types - Bitflag of damage types to check. + */ +/datum/unit_test/mob_damage/basic/verify_damage(mob/living/testing_mob, amount, expected, included_types = ALL) + if(included_types & TOXLOSS) + TEST_ASSERT_EQUAL(testing_mob.getToxLoss(), 0, \ + "[testing_mob] should have [0] toxin damage, instead they have [testing_mob.getToxLoss()]!") + if(included_types & BRUTELOSS) + TEST_ASSERT_EQUAL(round(testing_mob.getBruteLoss(), 1), expected || amount * 4, \ + "[testing_mob] should have [expected || amount * 4] brute damage, instead they have [testing_mob.getBruteLoss()]!") + if(included_types & FIRELOSS) + TEST_ASSERT_EQUAL(round(testing_mob.getFireLoss(), 1), 0, \ + "[testing_mob] should have [0] burn damage, instead they have [testing_mob.getFireLoss()]!") + if(included_types & OXYLOSS) + TEST_ASSERT_EQUAL(testing_mob.getOxyLoss(), 0, \ + "[testing_mob] should have [0] oxy damage, instead they have [testing_mob.getOxyLoss()]!") + if(included_types & STAMINALOSS) + TEST_ASSERT_EQUAL(testing_mob.getStaminaLoss(), amount, \ + "[testing_mob] should have [amount] stamina damage, instead they have [testing_mob.getStaminaLoss()]!") + return TRUE + +/datum/unit_test/mob_damage/basic/test_sanity_simple(mob/living/basic/mouse/gray/gusgus) + // check to see if basic mob damage works + + // Simple damage and healing + // Take 1 damage, heal for 1 + if(!test_apply_damage(gusgus, amount = 1)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly") + + if(!test_apply_damage(gusgus, amount = -1)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! healing was not applied correctly") + + // Give 2 damage of every time (translates to 8 brute, 2 staminaloss) + if(!test_apply_damage(gusgus, amount = 2)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly") + + // underhealing: heal 1 damage of every type (translates to 4 brute, 1 staminaloss) + if(!test_apply_damage(gusgus, amount = -1)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! healing was not applied correctly") + + // overhealing + + // heal 11 points of toxloss (should take care of all 4 brute damage remaining) + if(!apply_damage(gusgus, -11, expected = 4, included_types = TOXLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! toxloss was not applied correctly") + // heal the remaining point of staminaloss + if(!apply_damage(gusgus, -11, expected = 1, included_types = STAMINALOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! failed to heal staminaloss correctly") + // heal 35 points of each type, we should already be at full health so nothing should happen + if(!test_apply_damage(gusgus, amount = -35, expected = 0)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! overhealing was not applied correctly") + +/datum/unit_test/mob_damage/basic/test_sanity_complex(mob/living/basic/mouse/gray/gusgus) + // Heal up, so that errors from the previous tests we won't cause this one to fail + gusgus.fully_heal(HEAL_DAMAGE) + var/damage_returned + // overall damage procs + + // take 5 brute, 2 burn + damage_returned = gusgus.take_bodypart_damage(5, 2, updating_health = FALSE) + TEST_ASSERT_EQUAL(damage_returned, -7, \ + "take_bodypart_damage() should have returned -7, but returned [damage_returned] instead!") + + TEST_ASSERT_EQUAL(gusgus.bruteloss, 7, \ + "Mouse should have 7 brute damage, instead they have [gusgus.bruteloss]!") + TEST_ASSERT_EQUAL(gusgus.fireloss, 0, \ + "Mouse should have 0 burn damage, instead they have [gusgus.fireloss]!") + + // heal 4 brute, 1 burn + damage_returned = gusgus.heal_bodypart_damage(4, 1, updating_health = FALSE) + TEST_ASSERT_EQUAL(damage_returned, 5, \ + "heal_bodypart_damage() should have returned 5, but returned [damage_returned] instead!") + + TEST_ASSERT_EQUAL(gusgus.bruteloss, 2, \ + "Mouse should have 2 brute damage, instead they have [gusgus.bruteloss]!") + TEST_ASSERT_EQUAL(gusgus.fireloss, 0, \ + "Mouse should have 0 burn damage, instead they have [gusgus.fireloss]!") + + // heal 1 brute, 1 burn + damage_returned = gusgus.heal_overall_damage(1, 1, updating_health = FALSE) + TEST_ASSERT_EQUAL(damage_returned, 2, \ + "heal_overall_damage() should have returned 2, but returned [damage_returned] instead!") + + TEST_ASSERT_EQUAL(gusgus.bruteloss, 0, \ + "Mouse should have 0 brute damage, instead they have [gusgus.bruteloss]!") + TEST_ASSERT_EQUAL(gusgus.fireloss, 0, \ + "Mouse should have 0 burn damage, instead they have [gusgus.fireloss]!") + + // take 50 brute, 50 burn + damage_returned = gusgus.take_overall_damage(3, 3, updating_health = FALSE) + TEST_ASSERT_EQUAL(damage_returned, -6, \ + "take_overall_damage() should have returned -6, but returned [damage_returned] instead!") + + if(!verify_damage(gusgus, 1, expected = 6, included_types = BRUTELOSS)) + TEST_FAIL("take_overall_damage did not apply its damage correctly on the mouse!") + + // testing negative args with the overall damage procs + + damage_returned = gusgus.take_bodypart_damage(-1, -1, updating_health = FALSE) + TEST_ASSERT_EQUAL(damage_returned, -2, \ + "take_bodypart_damage() should have returned -2, but returned [damage_returned] instead!") + + damage_returned = gusgus.heal_bodypart_damage(-1, -1, updating_health = FALSE) + TEST_ASSERT_EQUAL(damage_returned, 2, \ + "heal_bodypart_damage() should have returned 2, but returned [damage_returned] instead!") + + damage_returned = gusgus.take_overall_damage(-1, -1, updating_health = FALSE) + TEST_ASSERT_EQUAL(damage_returned, -2, \ + "take_overall_damage() should have returned -2, but returned [damage_returned] instead!") + + damage_returned = gusgus.heal_overall_damage(-1, -1, updating_health = FALSE) + TEST_ASSERT_EQUAL(damage_returned, 2, \ + "heal_overall_damage() should have returned 2, but returned [damage_returned] instead!") + + if(!verify_damage(gusgus, 1, expected = 6, included_types = BRUTELOSS)) + TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mouse!") + + // testing overhealing + + damage_returned = gusgus.heal_overall_damage(75, 99, updating_health = FALSE) + TEST_ASSERT_EQUAL(damage_returned, 6, \ + "heal_overall_damage() should have returned 6, but returned [damage_returned] instead!") + + if(!verify_damage(gusgus, 0, included_types = BRUTELOSS)) + TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mouse!") diff --git a/code/modules/unit_tests/monkey_business.dm b/code/modules/unit_tests/monkey_business.dm index 20bfffe6a4821..80044a0486d98 100644 --- a/code/modules/unit_tests/monkey_business.dm +++ b/code/modules/unit_tests/monkey_business.dm @@ -14,7 +14,8 @@ /datum/unit_test/monkey_business/Run() for(var/monkey_id in 1 to length(GLOB.the_station_areas)) - var/mob/living/carbon/human/monkey = allocate(/mob/living/carbon/human/consistent, get_first_open_turf_in_area(GLOB.the_station_areas[monkey_id])) + var/area/monkey_zone = GLOB.areas_by_type[GLOB.the_station_areas[monkey_id]] + var/mob/living/carbon/human/monkey = allocate(/mob/living/carbon/human/consistent, get_first_open_turf_in_area(monkey_zone)) monkey.set_species(/datum/species/monkey) monkey.set_name("Monkey [monkey_id]") if(monkey_id % monkey_angry_nth == 0) // BLOOD FOR THE BLOOD GODS diff --git a/code/modules/unit_tests/organ_bodypart_shuffle.dm b/code/modules/unit_tests/organ_bodypart_shuffle.dm new file mode 100644 index 0000000000000..842dd1c6c1344 --- /dev/null +++ b/code/modules/unit_tests/organ_bodypart_shuffle.dm @@ -0,0 +1,34 @@ +/// Moves organs in and out of bodyparts, and moves the bodyparts around to see if someone didn't fuck up their movement +/datum/unit_test/organ_bodypart_shuffle + +/datum/unit_test/organ_bodypart_shuffle/Run() + var/mob/living/carbon/human/hollow_boy = allocate(/mob/living/carbon/human/consistent) + + // Test if organs are all properly updating when forcefully removed + var/list/removed_organs = list() + + for(var/obj/item/organ/organ as anything in hollow_boy.organs) + organ.moveToNullspace() + removed_organs += organ + + for(var/obj/item/organ/organ as anything in removed_organs) + TEST_ASSERT(!(organ in hollow_boy.organs), "Organ '[organ.name] remained inside human after forceMove into nullspace.") + TEST_ASSERT(organ.loc == null, "Organ '[organ.name] did not move to nullspace after being forced to.") + TEST_ASSERT(!(organ.owner), "Organ '[organ.name] kept reference to human after forceMove into nullspace.") + TEST_ASSERT(!(organ.bodypart_owner), "Organ '[organ.name] kept reference to bodypart after forceMove into nullspace.") + + for(var/obj/item/bodypart/bodypart as anything in hollow_boy.bodyparts) + bodypart = new bodypart.type() //fresh, duplice bodypart with no insides + for(var/obj/item/organ/organ as anything in removed_organs) + if(bodypart.body_zone != deprecise_zone(organ.zone)) + continue + organ.bodypart_insert(bodypart) // Put all the old organs back in + bodypart.replace_limb(hollow_boy) //so stick new bodyparts on them with their old organs + // Check if, after we put the old organs in a new limb, and after we put that new limb on the mob, if the organs came with + for(var/obj/item/organ/organ as anything in removed_organs) //technically readded organ now + if(bodypart.body_zone != deprecise_zone(organ.zone)) + continue + TEST_ASSERT(organ in hollow_boy.organs, "Organ '[organ.name] was put in an empty bodypart that replaced a humans, but the organ did not come with.") + + // Test if bodyparts are all properly updating when forcefully removed + hollow_boy = allocate(/mob/living/carbon/human/consistent) //freshly filled with wet insides diff --git a/code/modules/unit_tests/organ_set_bonus.dm b/code/modules/unit_tests/organ_set_bonus.dm index 250433028c11a..a9e9a8805f9c4 100644 --- a/code/modules/unit_tests/organ_set_bonus.dm +++ b/code/modules/unit_tests/organ_set_bonus.dm @@ -30,7 +30,7 @@ // Attempt to insert entire list of mutant organs for the given infusion_entry. for(var/obj/item/organ/organ as anything in output_organs) organ = new organ() - TEST_ASSERT(organ.Insert(lab_rat, special = TRUE, drop_if_replaced = FALSE), "The organ `[organ.type]` for `[infuser_entry.type]` was not inserted in the mob when expected, Insert() returned falsy when TRUE was expected.") + TEST_ASSERT(organ.Insert(lab_rat, special = TRUE, movement_flags = DELETE_IF_REPLACED), "The organ `[organ.type]` for `[infuser_entry.type]` was not inserted in the mob when expected, Insert() returned falsy when TRUE was expected.") inserted_organs += organ // Search for added Status Effect. diff --git a/code/modules/unit_tests/organs.dm b/code/modules/unit_tests/organs.dm index 4c99bfaa339fd..1da3808ba3908 100644 --- a/code/modules/unit_tests/organs.dm +++ b/code/modules/unit_tests/organs.dm @@ -25,14 +25,10 @@ )) /datum/unit_test/organ_sanity/Run() - for(var/obj/item/organ/organ_type as anything in subtypesof(/obj/item/organ)) + for(var/obj/item/organ/organ_type as anything in subtypesof(/obj/item/organ) - test_organ_blacklist) organ_test_insert(organ_type) /datum/unit_test/organ_sanity/proc/organ_test_insert(obj/item/organ/organ_type) - // Skip prototypes. - if(test_organ_blacklist[organ_type]) - return - // Appropriate mob (Human) which will receive organ. var/mob/living/carbon/human/lab_rat = allocate(/mob/living/carbon/human/consistent) var/obj/item/organ/test_organ = new organ_type() @@ -41,8 +37,8 @@ var/mob/living/basic/pet/dog/lab_dog = allocate(/mob/living/basic/pet/dog/corgi) var/obj/item/organ/reject_organ = new organ_type() - TEST_ASSERT(test_organ.Insert(lab_rat, special = TRUE, drop_if_replaced = FALSE), TEST_ORGAN_INSERT_MESSAGE(test_organ, "should return TRUE to indicate success.")) - TEST_ASSERT(!reject_organ.Insert(lab_dog, special = TRUE, drop_if_replaced = FALSE), TEST_ORGAN_INSERT_MESSAGE(test_organ, "shouldn't return TRUE when inserting into a basic mob (Corgi).")) + TEST_ASSERT(test_organ.Insert(lab_rat, special = TRUE, movement_flags = DELETE_IF_REPLACED), TEST_ORGAN_INSERT_MESSAGE(test_organ, "should return TRUE to indicate success.")) + TEST_ASSERT(!reject_organ.Insert(lab_dog, special = TRUE, movement_flags = DELETE_IF_REPLACED), TEST_ORGAN_INSERT_MESSAGE(test_organ, "shouldn't return TRUE when inserting into a basic mob (Corgi).")) // Species change swaps out all the organs, making test_organ un-usable by this point. if(species_changing_organs[test_organ.type]) @@ -96,19 +92,22 @@ var/slot_to_use = test_organ.slot // Tests [mob/living/proc/adjustOrganLoss] - dummy.adjustOrganLoss(slot_to_use, test_organ.maxHealth * 10) + TEST_ASSERT_EQUAL(dummy.adjustOrganLoss(slot_to_use, test_organ.maxHealth * 10), -test_organ.maxHealth, \ + "Mob level \"apply organ damage\" returned the wrong value for [slot_to_use] organ with default arguments.") TEST_ASSERT_EQUAL(dummy.get_organ_loss(slot_to_use), test_organ.maxHealth, \ "Mob level \"apply organ damage\" can exceed the [slot_to_use] organ's damage cap with default arguments.") dummy.fully_heal(HEAL_ORGANS) // Tests [mob/living/proc/set_organ_damage] - dummy.setOrganLoss(slot_to_use, test_organ.maxHealth * 10) + TEST_ASSERT_EQUAL(dummy.setOrganLoss(slot_to_use, test_organ.maxHealth * 10), -test_organ.maxHealth, \ + "Mob level \"set organ damage\" returned the wrong value for [slot_to_use] organ with default arguments.") TEST_ASSERT_EQUAL(dummy.get_organ_loss(slot_to_use), test_organ.maxHealth, \ "Mob level \"set organ damage\" can exceed the [slot_to_use] organ's damage cap with default arguments.") dummy.fully_heal(HEAL_ORGANS) // Tests [mob/living/proc/adjustOrganLoss] with a large max supplied - dummy.adjustOrganLoss(slot_to_use, test_organ.maxHealth * 10, INFINITY) + TEST_ASSERT_EQUAL(dummy.adjustOrganLoss(slot_to_use, test_organ.maxHealth * 10, INFINITY), -test_organ.maxHealth, \ + "Mob level \"apply organ damage\" returned the wrong value for [slot_to_use] organ with a large maximum supplied.") TEST_ASSERT_EQUAL(dummy.get_organ_loss(slot_to_use), test_organ.maxHealth, \ "Mob level \"apply organ damage\" can exceed the [slot_to_use] organ's damage cap with a large maximum supplied.") dummy.fully_heal(HEAL_ORGANS) diff --git a/code/modules/unit_tests/oxyloss_suffocation.dm b/code/modules/unit_tests/oxyloss_suffocation.dm new file mode 100644 index 0000000000000..a44911ebbba76 --- /dev/null +++ b/code/modules/unit_tests/oxyloss_suffocation.dm @@ -0,0 +1,10 @@ +/// Test getting over a certain threshold of oxy damage results in KO +/datum/unit_test/oxyloss_suffocation + +/datum/unit_test/oxyloss_suffocation/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + + dummy.setOxyLoss(75) + TEST_ASSERT(HAS_TRAIT_FROM(dummy, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT), "Dummy should have been knocked out from taking oxy damage.") + dummy.setOxyLoss(0) + TEST_ASSERT(!HAS_TRAIT_FROM(dummy, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT), "Dummy should have woken up from KO when healing to 0 oxy damage.") diff --git a/code/modules/unit_tests/plantgrowth_tests.dm b/code/modules/unit_tests/plantgrowth_tests.dm index e3654e744d062..595c71d05a49e 100644 --- a/code/modules/unit_tests/plantgrowth_tests.dm +++ b/code/modules/unit_tests/plantgrowth_tests.dm @@ -3,7 +3,7 @@ // Maybe some day it would be used as unit test. // -------- IT IS NOW! /datum/unit_test/plantgrowth/Run() - var/list/paths = subtypesof(/obj/item/seeds) - /obj/item/seeds - typesof(/obj/item/seeds/sample) - /obj/item/seeds/lavaland + var/list/paths = subtypesof(/obj/item/seeds) - /obj/item/seeds - /obj/item/seeds/lavaland for(var/seedpath in paths) var/obj/item/seeds/seed = new seedpath diff --git a/code/modules/unit_tests/quirks.dm b/code/modules/unit_tests/quirks.dm index 7a2ce474d5e89..37765ce8ca0ee 100644 --- a/code/modules/unit_tests/quirks.dm +++ b/code/modules/unit_tests/quirks.dm @@ -19,3 +19,25 @@ continue used_icons[icon] = quirk_type + +// Make sure all quirks start with a description in medical records +/datum/unit_test/quirk_initial_medical_records + +/datum/unit_test/quirk_initial_medical_records/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent) + + for(var/datum/quirk/quirk_type as anything in subtypesof(/datum/quirk)) + if (initial(quirk_type.abstract_parent_type) == quirk_type) + continue + + if(!isnull(quirk_type.medical_record_text)) + continue + + //Add quirk to a patient - so we can pass quirks that add a medical record after being assigned someone + patient.add_quirk(quirk_type) + + var/datum/quirk/quirk = patient.get_quirk(quirk_type) + + TEST_ASSERT_NOTNULL(quirk.medical_record_text,"[quirk_type] has no medical record description!") + + patient.remove_quirk(quirk_type) diff --git a/code/modules/unit_tests/rcd.dm b/code/modules/unit_tests/rcd.dm index 27ab66fa0be3b..49e9f8461fd0a 100644 --- a/code/modules/unit_tests/rcd.dm +++ b/code/modules/unit_tests/rcd.dm @@ -15,7 +15,7 @@ engineer.put_in_hands(rcd, forced = TRUE) - rcd.mode = RCD_MACHINE + rcd.mode = RCD_STRUCTURE var/list/adjacent_turfs = get_adjacent_open_turfs(engineer) @@ -24,7 +24,7 @@ var/turf/adjacent_turf = adjacent_turfs[1] for(var/i in 1 to 10) - adjacent_turf.rcd_act(engineer, rcd, rcd.mode) + adjacent_turf.rcd_act(engineer, rcd, list("[RCD_DESIGN_MODE]" = rcd.mode, "[RCD_DESIGN_PATH]" = /obj/structure/frame/machine/secured)) var/frame_count = 0 for(var/obj/structure/frame/machine_frame in adjacent_turf.contents) diff --git a/code/modules/unit_tests/required_map_items.dm b/code/modules/unit_tests/required_map_items.dm index 39930afd822c2..5cbef64539109 100644 --- a/code/modules/unit_tests/required_map_items.dm +++ b/code/modules/unit_tests/required_map_items.dm @@ -15,10 +15,11 @@ /datum/unit_test/required_map_items/proc/setup_expected_types() expected_types += subtypesof(/obj/item/stamp/head) expected_types += subtypesof(/obj/machinery/computer/department_orders) - expected_types += /obj/machinery/computer/communications - expected_types += /mob/living/carbon/human/species/monkey/punpun + + expected_types += /mob/living/basic/parrot/poly expected_types += /mob/living/basic/pet/dog/corgi/ian - expected_types += /mob/living/simple_animal/parrot/poly + expected_types += /mob/living/carbon/human/species/monkey/punpun + expected_types += /obj/machinery/computer/communications expected_types += /obj/machinery/drone_dispenser /datum/unit_test/required_map_items/Run() diff --git a/code/modules/unit_tests/say.dm b/code/modules/unit_tests/say.dm index 3ae55a12e37e5..ec58dcedc8831 100644 --- a/code/modules/unit_tests/say.dm +++ b/code/modules/unit_tests/say.dm @@ -58,12 +58,15 @@ /// This runs some simple speech tests on a speaker and listener and determines if a person can hear whispering or speaking as they are moved a distance away /datum/unit_test/speech - var/list/handle_speech_result = null - var/list/handle_hearing_result = null var/mob/living/carbon/human/speaker var/mob/living/carbon/human/listener + var/list/handle_speech_result = null + var/list/handle_hearing_result = null + var/obj/item/radio/speaker_radio var/obj/item/radio/listener_radio + var/speaker_radio_heard_message = FALSE + var/listener_radio_received_message = FALSE /datum/unit_test/speech/proc/handle_speech(datum/source, list/speech_args) SIGNAL_HANDLER @@ -99,6 +102,16 @@ handle_hearing_result = list() handle_hearing_result += hearing_args +/datum/unit_test/speech/proc/handle_radio_hearing(datum/source, mob/living/user, message, channel) + SIGNAL_HANDLER + + speaker_radio_heard_message = TRUE + +/datum/unit_test/speech/proc/handle_radio_speech(datum/source, list/data) + SIGNAL_HANDLER + + listener_radio_received_message = TRUE + /datum/unit_test/speech/Run() speaker = allocate(/mob/living/carbon/human/consistent) // Name changes to make understanding breakpoints easier @@ -114,7 +127,10 @@ listener.mock_client = mock_client RegisterSignal(speaker, COMSIG_MOB_SAY, PROC_REF(handle_speech)) + RegisterSignal(speaker_radio, COMSIG_RADIO_NEW_MESSAGE, PROC_REF(handle_radio_hearing)) + RegisterSignal(listener, COMSIG_MOVABLE_HEAR, PROC_REF(handle_hearing)) + RegisterSignal(listener_radio, COMSIG_RADIO_RECEIVE_MESSAGE, PROC_REF(handle_radio_speech)) // speaking and whispering should be hearable conversation(distance = 1) @@ -169,6 +185,9 @@ handle_hearing_result = null /datum/unit_test/speech/proc/radio_test() + speaker_radio_heard_message = FALSE + listener_radio_received_message = FALSE + speaker.forceMove(run_loc_floor_bottom_left) listener.forceMove(locate((run_loc_floor_bottom_left.x + 10), run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) @@ -186,11 +205,15 @@ speaker.say(pangram_quote) TEST_ASSERT(handle_speech_result, "Handle speech signal was not fired (radio test)") - TEST_ASSERT(islist(handle_hearing_result), "Listener failed to hear radio message (radio test)") + TEST_ASSERT(speaker_radio_heard_message, "Speaker's radio did not hear them speak (radio test)") TEST_ASSERT_EQUAL(speaker_radio.get_frequency(), listener_radio.get_frequency(), "Radio frequencies were not equal (radio test)") + TEST_ASSERT(listener_radio_received_message, "Listener's radio did not receive the broadcast (radio test)") + TEST_ASSERT(islist(handle_hearing_result), "Listener failed to hear radio message (radio test)") handle_speech_result = null handle_hearing_result = null + speaker_radio_heard_message = FALSE + listener_radio_received_message = FALSE speaker_radio.set_frequency(FREQ_CTF_RED) speaker.say(pangram_quote) @@ -200,6 +223,8 @@ handle_speech_result = null handle_hearing_result = null + speaker_radio_heard_message = FALSE + listener_radio_received_message = FALSE speaker_radio.set_broadcasting(FALSE) #undef NORMAL_HEARING_RANGE diff --git a/code/modules/unit_tests/screenshot_high_luminosity_eyes.dm b/code/modules/unit_tests/screenshot_high_luminosity_eyes.dm new file mode 100644 index 0000000000000..4b0c3a986f122 --- /dev/null +++ b/code/modules/unit_tests/screenshot_high_luminosity_eyes.dm @@ -0,0 +1,64 @@ +#define UPDATE_EYES_LEFT 1 +#define UPDATE_EYES_RIGHT 2 + +/// Tests to make sure no punks have broken high luminosity eyes +/datum/unit_test/screenshot_high_luminosity_eyes + var/mob/living/carbon/human/test_subject + var/obj/item/organ/internal/eyes/robotic/glow/test_eyes + +/datum/unit_test/screenshot_high_luminosity_eyes/Run() + // Create a mob with red and blue eyes. This is to test that high luminosity eyes properly default to the old eye color. + test_subject = allocate(/mob/living/carbon/human/consistent) + test_subject.equipOutfit(/datum/outfit/job/assistant/consistent) + test_subject.eye_color_left = COLOR_RED + test_subject.eye_color_right = COLOR_BLUE + + // Create our eyes, and insert them into the mob + test_eyes = allocate(/obj/item/organ/internal/eyes/robotic/glow) + test_eyes.Insert(test_subject) + + // This should be 4, but just in case it ever changes in the future + var/default_light_range = test_eyes.eye.light_range + + // Test the normal light on appearance + test_eyes.toggle_active() + var/icon/flat_icon = create_icon() + test_screenshot("light_on", flat_icon) + + // Change the eye color to pink and green + test_eyes.set_beam_color(COLOR_SCIENCE_PINK, to_update = UPDATE_EYES_LEFT) + test_eyes.set_beam_color(COLOR_SLIME_GREEN, to_update = UPDATE_EYES_RIGHT) + + // Make sure the light overlay goes away (but not the emissive overlays) when we go to light range 0 while still turned on + test_eyes.set_beam_range(0) + TEST_ASSERT_EQUAL(test_eyes.eye.light_on, TRUE, "[src]'s 'eye.light_on' is FALSE after setting range to 0 while on. 'eye.light_on' should = TRUE!") + flat_icon = create_icon() + test_screenshot("light_emissive", flat_icon) + + // turn it on and off again, it should look the same afterwards + test_eyes.toggle_active() + TEST_ASSERT_EQUAL(test_eyes.eye.light_on, FALSE, "[src]'s 'eye.light_on' is TRUE after being toggled off at range 0. 'eye.light_on' should = FALSE!") + test_eyes.toggle_active() + TEST_ASSERT_EQUAL(test_eyes.eye.light_on, TRUE, "[src]'s 'eye.light_on' is FALSE after being toggled on at range 0. 'eye.light_on' should = TRUE!") + flat_icon = create_icon() + test_screenshot("light_emissive", flat_icon) + + // Make sure the light comes back on when we go from range 0 to 1 + // Change left/right eye color back to red/blue. It should match the original screenshot + test_eyes.set_beam_range(default_light_range) + test_eyes.set_beam_color(COLOR_RED, to_update = UPDATE_EYES_LEFT) + test_eyes.set_beam_color(COLOR_BLUE, to_update = UPDATE_EYES_RIGHT) + flat_icon = create_icon() + test_screenshot("light_on", flat_icon) + +/// Create the mob icon with light cone underlay +/datum/unit_test/screenshot_high_luminosity_eyes/proc/create_icon() + var/icon/final_icon = get_flat_icon_for_all_directions(test_subject, no_anim = FALSE) + for(var/mutable_appearance/light_underlay as anything in test_subject.underlays) + if(light_underlay.icon == 'icons/effects/light_overlays/light_cone.dmi') + // The light cone icon is 96x96, so we have to shift it over to have it match our sprites. x = 1, y = 1 is the lower left corner so we shift 32 pixels opposite to that. + final_icon.Blend(get_flat_icon_for_all_directions(light_underlay, no_anim = FALSE), ICON_UNDERLAY, -world.icon_size + 1, -world.icon_size + 1) + return final_icon + +#undef UPDATE_EYES_LEFT +#undef UPDATE_EYES_RIGHT diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_bloodbrother.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_bloodbrother.png index e17d8c14159d1..f34414bdc13d3 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_bloodbrother.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_bloodbrother.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_changeling.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_changeling.png index b1ec4226567d3..c63a02c837052 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_changeling.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_changeling.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_changelingmidround.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_changelingmidround.png index ac412207c236d..e43584b63a010 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_changelingmidround.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_changelingmidround.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_glitch.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_glitch.png new file mode 100644 index 0000000000000..ac8a6347bbd71 Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_glitch.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_nightmare.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_nightmare.png index 3b723129ac84d..853ef8453830e 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_nightmare.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_nightmare.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_stowawaychangeling.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_stowawaychangeling.png index b1ec4226567d3..c63a02c837052 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_stowawaychangeling.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_stowawaychangeling.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_wizard.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_wizard.png index 8681ec75728a6..69a856722fd67 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_wizard.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_wizard.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_wizardmidround.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_wizardmidround.png index 8681ec75728a6..69a856722fd67 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_wizardmidround.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_wizardmidround.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_high_luminosity_eyes_light_emissive.png b/code/modules/unit_tests/screenshots/screenshot_high_luminosity_eyes_light_emissive.png new file mode 100644 index 0000000000000..912f16e6839b2 Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_high_luminosity_eyes_light_emissive.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_high_luminosity_eyes_light_on.png b/code/modules/unit_tests/screenshots/screenshot_high_luminosity_eyes_light_on.png new file mode 100644 index 0000000000000..272f6f2570761 Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_high_luminosity_eyes_light_on.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_monkey_holodeck.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_monkey_holodeck.png new file mode 100644 index 0000000000000..e0d02f4302f43 Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_monkey_holodeck.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_mush.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_mush.png index 6755b4792e0fe..1985d06b1532e 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_mush.png and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_mush.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_shadow_nightmare.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_shadow_nightmare.png index 61656a8889214..5cdeedeffca7b 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_shadow_nightmare.png and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_shadow_nightmare.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_husk_body.png b/code/modules/unit_tests/screenshots/screenshot_husk_body.png index 2911277fcd974..d113b47384678 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_husk_body.png and b/code/modules/unit_tests/screenshots/screenshot_husk_body.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_husk_body_missing_limbs.png b/code/modules/unit_tests/screenshots/screenshot_husk_body_missing_limbs.png index 6c526af2ebb29..1a1db7dfd87fe 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_husk_body_missing_limbs.png and b/code/modules/unit_tests/screenshots/screenshot_husk_body_missing_limbs.png differ diff --git a/code/modules/unit_tests/screenshots/transformation_sting_appearances.png b/code/modules/unit_tests/screenshots/transformation_sting_appearances.png new file mode 100644 index 0000000000000..e20d19d45b680 Binary files /dev/null and b/code/modules/unit_tests/screenshots/transformation_sting_appearances.png differ diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index c97e034e0b368..a40723c2f6927 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -6,18 +6,8 @@ // If you are refactoring a simple_animal, REMOVE it from this list var/list/allowed_types = list( /mob/living/simple_animal/bot, - /mob/living/simple_animal/bot/cleanbot, - /mob/living/simple_animal/bot/cleanbot/autopatrol, - /mob/living/simple_animal/bot/cleanbot/medbay, /mob/living/simple_animal/bot/firebot, /mob/living/simple_animal/bot/floorbot, - /mob/living/simple_animal/bot/hygienebot, - /mob/living/simple_animal/bot/medbot, - /mob/living/simple_animal/bot/medbot/autopatrol, - /mob/living/simple_animal/bot/medbot/derelict, - /mob/living/simple_animal/bot/medbot/mysterious, - /mob/living/simple_animal/bot/medbot/nukie, - /mob/living/simple_animal/bot/medbot/stationary, /mob/living/simple_animal/bot/mulebot, /mob/living/simple_animal/bot/mulebot/paranormal, /mob/living/simple_animal/bot/secbot, @@ -33,15 +23,6 @@ /mob/living/simple_animal/bot/secbot/honkbot, /mob/living/simple_animal/bot/secbot/pingsky, /mob/living/simple_animal/bot/vibebot, - /mob/living/simple_animal/drone, - /mob/living/simple_animal/drone/classic, - /mob/living/simple_animal/drone/derelict, - /mob/living/simple_animal/drone/polymorphed, - /mob/living/simple_animal/drone/snowflake, - /mob/living/simple_animal/drone/snowflake/bardrone, - /mob/living/simple_animal/drone/syndrone, - /mob/living/simple_animal/drone/syndrone/badass, - /mob/living/simple_animal/holodeck_monkey, /mob/living/simple_animal/hostile, /mob/living/simple_animal/hostile/alien, /mob/living/simple_animal/hostile/alien/drone, @@ -51,7 +32,6 @@ /mob/living/simple_animal/hostile/alien/queen/large, /mob/living/simple_animal/hostile/alien/sentinel, /mob/living/simple_animal/hostile/asteroid, - /mob/living/simple_animal/hostile/asteroid/brimdemon, /mob/living/simple_animal/hostile/asteroid/curseblob, /mob/living/simple_animal/hostile/asteroid/elite, /mob/living/simple_animal/hostile/asteroid/elite/broodmother, @@ -61,82 +41,13 @@ /mob/living/simple_animal/hostile/asteroid/elite/legionnaire, /mob/living/simple_animal/hostile/asteroid/elite/legionnairehead, /mob/living/simple_animal/hostile/asteroid/elite/pandora, - /mob/living/simple_animal/hostile/asteroid/gutlunch, - /mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch, - /mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck, - /mob/living/simple_animal/hostile/asteroid/gutlunch/guthen, - /mob/living/simple_animal/hostile/asteroid/hivelord, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion/advanced, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow/portal, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril, - /mob/living/simple_animal/hostile/asteroid/hivelordbrood, - /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion, - /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/advanced, - /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/snow, - /mob/living/simple_animal/hostile/asteroid/ice_demon, /mob/living/simple_animal/hostile/asteroid/polarbear, /mob/living/simple_animal/hostile/asteroid/polarbear/lesser, /mob/living/simple_animal/hostile/asteroid/wolf, - /mob/living/simple_animal/hostile/big_legion, - /mob/living/simple_animal/hostile/blob, - /mob/living/simple_animal/hostile/blob/blobbernaut, - /mob/living/simple_animal/hostile/blob/blobbernaut/independent, - /mob/living/simple_animal/hostile/blob/blobspore, - /mob/living/simple_animal/hostile/blob/blobspore/independent, - /mob/living/simple_animal/hostile/blob/blobspore/weak, - /mob/living/simple_animal/hostile/construct, - /mob/living/simple_animal/hostile/construct/artificer, - /mob/living/simple_animal/hostile/construct/artificer/angelic, - /mob/living/simple_animal/hostile/construct/artificer/hostile, - /mob/living/simple_animal/hostile/construct/artificer/mystic, - /mob/living/simple_animal/hostile/construct/artificer/noncult, - /mob/living/simple_animal/hostile/construct/harvester, - /mob/living/simple_animal/hostile/construct/juggernaut, - /mob/living/simple_animal/hostile/construct/juggernaut/angelic, - /mob/living/simple_animal/hostile/construct/juggernaut/hostile, - /mob/living/simple_animal/hostile/construct/juggernaut/mystic, - /mob/living/simple_animal/hostile/construct/juggernaut/noncult, - /mob/living/simple_animal/hostile/construct/proteon, - /mob/living/simple_animal/hostile/construct/proteon/hostile, - /mob/living/simple_animal/hostile/construct/wraith, - /mob/living/simple_animal/hostile/construct/wraith/angelic, - /mob/living/simple_animal/hostile/construct/wraith/hostile, - /mob/living/simple_animal/hostile/construct/wraith/mystic, - /mob/living/simple_animal/hostile/construct/wraith/noncult, /mob/living/simple_animal/hostile/dark_wizard, - /mob/living/simple_animal/hostile/gorilla, - /mob/living/simple_animal/hostile/gorilla/lesser, - /mob/living/simple_animal/hostile/gorilla/cargo_domestic, - /mob/living/simple_animal/hostile/guardian, - /mob/living/simple_animal/hostile/guardian/assassin, - /mob/living/simple_animal/hostile/guardian/charger, - /mob/living/simple_animal/hostile/guardian/dextrous, - /mob/living/simple_animal/hostile/guardian/explosive, - /mob/living/simple_animal/hostile/guardian/gaseous, - /mob/living/simple_animal/hostile/guardian/gravitokinetic, - /mob/living/simple_animal/hostile/guardian/lightning, - /mob/living/simple_animal/hostile/guardian/protector, - /mob/living/simple_animal/hostile/guardian/ranged, - /mob/living/simple_animal/hostile/guardian/standard, - /mob/living/simple_animal/hostile/guardian/support, - /mob/living/simple_animal/hostile/heretic_summon, - /mob/living/simple_animal/hostile/heretic_summon/armsy, - /mob/living/simple_animal/hostile/heretic_summon/armsy/prime, - /mob/living/simple_animal/hostile/heretic_summon/ash_spirit, - /mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror, - /mob/living/simple_animal/hostile/heretic_summon/raw_prophet, - /mob/living/simple_animal/hostile/heretic_summon/rust_spirit, - /mob/living/simple_animal/hostile/heretic_summon/stalker, /mob/living/simple_animal/hostile/illusion, /mob/living/simple_animal/hostile/illusion/escape, /mob/living/simple_animal/hostile/illusion/mirage, - /mob/living/simple_animal/hostile/jungle, - /mob/living/simple_animal/hostile/jungle/leaper, - /mob/living/simple_animal/hostile/jungle/mook, - /mob/living/simple_animal/hostile/jungle/seedling, /mob/living/simple_animal/hostile/megafauna, /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner, /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/doom, @@ -163,82 +74,22 @@ /mob/living/simple_animal/hostile/mimic/copy/ranged, /mob/living/simple_animal/hostile/mimic/crate, /mob/living/simple_animal/hostile/mimic/xenobio, - /mob/living/simple_animal/hostile/nanotrasen, - /mob/living/simple_animal/hostile/nanotrasen/elite, - /mob/living/simple_animal/hostile/nanotrasen/ranged, - /mob/living/simple_animal/hostile/nanotrasen/ranged/assault, - /mob/living/simple_animal/hostile/nanotrasen/ranged/smg, - /mob/living/simple_animal/hostile/nanotrasen/screaming, /mob/living/simple_animal/hostile/ooze, /mob/living/simple_animal/hostile/ooze/gelatinous, /mob/living/simple_animal/hostile/ooze/grapes, - /mob/living/simple_animal/hostile/pirate, - /mob/living/simple_animal/hostile/pirate/melee, - /mob/living/simple_animal/hostile/pirate/melee/space, - /mob/living/simple_animal/hostile/pirate/ranged, - /mob/living/simple_animal/hostile/pirate/ranged/space, /mob/living/simple_animal/hostile/retaliate, - /mob/living/simple_animal/hostile/retaliate/clown, - /mob/living/simple_animal/hostile/retaliate/clown/banana, - /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, - /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, - /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/destroyer, - /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, - /mob/living/simple_animal/hostile/retaliate/clown/fleshclown, - /mob/living/simple_animal/hostile/retaliate/clown/honkling, - /mob/living/simple_animal/hostile/retaliate/clown/longface, - /mob/living/simple_animal/hostile/retaliate/clown/lube, - /mob/living/simple_animal/hostile/retaliate/clown/mutant, - /mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton, - /mob/living/simple_animal/hostile/retaliate/clown/mutant/slow, - /mob/living/simple_animal/hostile/retaliate/goat, /mob/living/simple_animal/hostile/retaliate/goose, /mob/living/simple_animal/hostile/retaliate/goose/vomit, - /mob/living/simple_animal/hostile/retaliate/nanotrasenpeace, - /mob/living/simple_animal/hostile/retaliate/nanotrasenpeace/ranged, - /mob/living/simple_animal/hostile/retaliate/snake, - /mob/living/simple_animal/hostile/retaliate/trader, - /mob/living/simple_animal/hostile/retaliate/trader/mrbones, - /mob/living/simple_animal/hostile/skeleton, - /mob/living/simple_animal/hostile/skeleton/eskimo, - /mob/living/simple_animal/hostile/skeleton/ice, - /mob/living/simple_animal/hostile/skeleton/plasmaminer, - /mob/living/simple_animal/hostile/skeleton/plasmaminer/jackhammer, - /mob/living/simple_animal/hostile/skeleton/templar, - /mob/living/simple_animal/hostile/smspider, - /mob/living/simple_animal/hostile/smspider/overcharged, - /mob/living/simple_animal/hostile/space_dragon, - /mob/living/simple_animal/hostile/space_dragon/spawn_with_antag, /mob/living/simple_animal/hostile/vatbeast, - /mob/living/simple_animal/hostile/venus_human_trap, - /mob/living/simple_animal/hostile/wizard, /mob/living/simple_animal/hostile/zombie, - /mob/living/simple_animal/parrot, - /mob/living/simple_animal/parrot/natural, - /mob/living/simple_animal/parrot/poly, - /mob/living/simple_animal/parrot/poly/ghost, /mob/living/simple_animal/pet, - /mob/living/simple_animal/pet/cat, - /mob/living/simple_animal/pet/cat/_proc, - /mob/living/simple_animal/pet/cat/breadcat, - /mob/living/simple_animal/pet/cat/cak, - /mob/living/simple_animal/pet/cat/jerry, - /mob/living/simple_animal/pet/cat/kitten, - /mob/living/simple_animal/pet/cat/original, - /mob/living/simple_animal/pet/cat/runtime, - /mob/living/simple_animal/pet/cat/space, /mob/living/simple_animal/pet/gondola, /mob/living/simple_animal/pet/gondola/gondolapod, - /mob/living/simple_animal/revenant, - /mob/living/simple_animal/robot_customer, - /mob/living/simple_animal/shade, + /mob/living/simple_animal/pet/gondola/virtual_domain, /mob/living/simple_animal/slime, /mob/living/simple_animal/slime/pet, /mob/living/simple_animal/slime/random, /mob/living/simple_animal/slime/transformed_slime, - /mob/living/simple_animal/sloth, - /mob/living/simple_animal/sloth/citrus, - /mob/living/simple_animal/sloth/paperwork, /mob/living/simple_animal/soulscythe, // DO NOT ADD NEW ENTRIES TO THIS LIST // READ THE COMMENT ABOVE diff --git a/code/modules/unit_tests/spawn_humans.dm b/code/modules/unit_tests/spawn_humans.dm index 0523f141d2568..e90473a7ba069 100644 --- a/code/modules/unit_tests/spawn_humans.dm +++ b/code/modules/unit_tests/spawn_humans.dm @@ -2,7 +2,7 @@ var/locs = block(run_loc_floor_bottom_left, run_loc_floor_top_right) for(var/I in 1 to 5) - new /mob/living/carbon/human/consistent(pick(locs)) + allocate(/mob/living/carbon/human/consistent, pick(locs)) sleep(5 SECONDS) diff --git a/code/modules/unit_tests/species_change_organs.dm b/code/modules/unit_tests/species_change_organs.dm index e0555aa89bcf9..41d55047f0346 100644 --- a/code/modules/unit_tests/species_change_organs.dm +++ b/code/modules/unit_tests/species_change_organs.dm @@ -12,7 +12,7 @@ dummy.gain_trauma(/datum/brain_trauma/severe/blindness) // Give a cyber heart var/obj/item/organ/internal/heart/cybernetic/cyber_heart = allocate(/obj/item/organ/internal/heart/cybernetic) - cyber_heart.Insert(dummy, special = TRUE, drop_if_replaced = FALSE) + cyber_heart.Insert(dummy, special = TRUE, movement_flags = DELETE_IF_REPLACED) // Give one of their organs a bit of damage var/obj/item/organ/internal/appendix/existing_appendix = dummy.get_organ_slot(ORGAN_SLOT_APPENDIX) existing_appendix.set_organ_damage(25) diff --git a/code/modules/unit_tests/spell_mindswap.dm b/code/modules/unit_tests/spell_mindswap.dm index 133f9662d9107..f598bcc72631e 100644 --- a/code/modules/unit_tests/spell_mindswap.dm +++ b/code/modules/unit_tests/spell_mindswap.dm @@ -11,6 +11,10 @@ var/mob/living/carbon/human/swapper = allocate(/mob/living/carbon/human/consistent) var/mob/living/carbon/human/to_swap = allocate(/mob/living/carbon/human/consistent) + swapper.real_name = "The Mindswapper" + swapper.name = swapper.real_name + to_swap.real_name = "The Guy Who Gets Mindswapped" + to_swap.name = to_swap.real_name swapper.forceMove(run_loc_floor_bottom_left) to_swap.forceMove(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) diff --git a/code/modules/unit_tests/spell_shapeshift.dm b/code/modules/unit_tests/spell_shapeshift.dm index 4e2ce6590037e..3b598e9994200 100644 --- a/code/modules/unit_tests/spell_shapeshift.dm +++ b/code/modules/unit_tests/spell_shapeshift.dm @@ -18,6 +18,8 @@ qdel(shift) +#define TRIGGER_RESET_COOLDOWN(spell) spell.next_use_time = 0; spell.Trigger(); + /** * Validates that shapeshift spells put the mob in another mob, as they should. */ @@ -25,7 +27,7 @@ /datum/unit_test/shapeshift_spell/Run() - var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent, run_loc_floor_bottom_left) dummy.mind_initialize() for(var/spell_type in subtypesof(/datum/action/cooldown/spell/shapeshift)) @@ -57,8 +59,7 @@ if(forced_shape) shift.shapeshift_type = forced_shape - shift.next_use_time = 0 - shift.Trigger() + TRIGGER_RESET_COOLDOWN(shift) var/mob/expected_shape = shift.shapeshift_type if(!istype(dummy.loc, expected_shape)) return TEST_FAIL("Shapeshift spell: [shift.name] failed to transform the dummy into the shape [initial(expected_shape.name)]. \ @@ -68,8 +69,7 @@ if(!(shift in shape.actions)) return TEST_FAIL("Shapeshift spell: [shift.name] failed to grant the spell to the dummy's shape.") - shift.next_use_time = 0 - shift.Trigger() + TRIGGER_RESET_COOLDOWN(shift) if(istype(dummy.loc, shift.shapeshift_type)) return TEST_FAIL("Shapeshift spell: [shift.name] failed to transform the dummy back into a human.") @@ -81,13 +81,13 @@ /datum/unit_test/shapeshift_holoparasites/Run() - var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent, run_loc_floor_bottom_left) var/datum/action/cooldown/spell/shapeshift/wizard/shift = new(dummy) shift.shapeshift_type = shift.possible_shapes[1] shift.Grant(dummy) - var/mob/living/simple_animal/hostile/guardian/test_stand = allocate(/mob/living/simple_animal/hostile/guardian) + var/mob/living/basic/guardian/test_stand = allocate(/mob/living/basic/guardian) test_stand.set_summoner(dummy) // The stand's summoner is dummy. @@ -99,9 +99,53 @@ TEST_ASSERT_EQUAL(test_stand.summoner, dummy.loc, "Shapeshift spell failed to transfer the holoparasite to the dummy's shape.") // Dummy casts shapeshfit back, the stand's summoner should become the dummy again. - shift.next_use_time = 0 - shift.Trigger() + TRIGGER_RESET_COOLDOWN(shift) TEST_ASSERT(!istype(dummy.loc, shift.shapeshift_type), "Shapeshift spell failed to transform the dummy back into human form.") TEST_ASSERT_EQUAL(test_stand.summoner, dummy, "Shapeshift spell failed to transfer the holoparasite back to the dummy's human form.") qdel(shift) + +#define EXPECTED_HEALTH_RATIO 0.5 + +/// Validates that shapeshifting carries health or death between forms properly, if it is supposed to +/datum/unit_test/shapeshift_health + +/datum/unit_test/shapeshift_health/Run() + for(var/spell_type in subtypesof(/datum/action/cooldown/spell/shapeshift)) + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent, run_loc_floor_bottom_left) + var/datum/action/cooldown/spell/shapeshift/shift_spell = new spell_type(dummy) + shift_spell.Grant(dummy) + shift_spell.shapeshift_type = shift_spell.possible_shapes[1] + + if (istype(shift_spell, /datum/action/cooldown/spell/shapeshift/polymorph_belt)) + var/datum/action/cooldown/spell/shapeshift/polymorph_belt/belt_spell = shift_spell + belt_spell.channel_time = 0 SECONDS // No do-afters + + if (shift_spell.convert_damage) + shift_spell.Trigger() + TEST_ASSERT(istype(dummy.loc, shift_spell.shapeshift_type), "Failed to transform into [shift_spell.shapeshift_type]using [shift_spell.name].") + var/mob/living/shifted_mob = dummy.loc + shifted_mob.apply_damage(shifted_mob.maxHealth * EXPECTED_HEALTH_RATIO, BRUTE, forced = TRUE) + TRIGGER_RESET_COOLDOWN(shift_spell) + TEST_ASSERT(!istype(dummy.loc, shift_spell.shapeshift_type), "Failed to unfransform from [shift_spell.shapeshift_type] using [shift_spell.name].") + TEST_ASSERT_EQUAL(dummy.get_total_damage(), dummy.maxHealth * EXPECTED_HEALTH_RATIO, "Failed to transfer damage from [shift_spell.shapeshift_type] to original form using [shift_spell.name].") + TRIGGER_RESET_COOLDOWN(shift_spell) + TEST_ASSERT(istype(dummy.loc, shift_spell.shapeshift_type), "Failed to transform into [shift_spell.shapeshift_type] after taking damage using [shift_spell.name].") + shifted_mob = dummy.loc + TEST_ASSERT_EQUAL(shifted_mob.get_total_damage(), shifted_mob.maxHealth * EXPECTED_HEALTH_RATIO, "Failed to transfer damage from original form to [shift_spell.shapeshift_type] using [shift_spell.name].") + TRIGGER_RESET_COOLDOWN(shift_spell) + + if (shift_spell.die_with_shapeshifted_form) + TRIGGER_RESET_COOLDOWN(shift_spell) + TEST_ASSERT(istype(dummy.loc, shift_spell.shapeshift_type), "Failed to transform into [shift_spell.shapeshift_type]") + var/mob/living/shifted_mob = dummy.loc + shifted_mob.health = 0 // Fucking megafauna + shifted_mob.death() + if (shift_spell.revert_on_death) + TEST_ASSERT(!istype(dummy.loc, shift_spell.shapeshift_type), "Failed to untransform after death using [shift_spell.name].") + TEST_ASSERT_EQUAL(dummy.stat, DEAD, "Failed to kill original mob when transformed mob died using [shift_spell.name].") + + qdel(shift_spell) + +#undef EXPECTED_HEALTH_RATIO +#undef TRIGGER_RESET_COOLDOWN diff --git a/code/modules/unit_tests/station_trait_tests.dm b/code/modules/unit_tests/station_trait_tests.dm index 430e83b9af7be..21173e152d757 100644 --- a/code/modules/unit_tests/station_trait_tests.dm +++ b/code/modules/unit_tests/station_trait_tests.dm @@ -6,5 +6,7 @@ for(var/datum/job/job as anything in subtypesof(/datum/job)) if(!(initial(job.job_flags) & JOB_CREW_MEMBER)) continue + if((initial(job.job_flags) & STATION_TRAIT_JOB_FLAGS) == STATION_TRAIT_JOB_FLAGS) + continue if(!(job in cyber_trait.job_to_cybernetic)) TEST_FAIL("Job [job] does not have an assigned cybernetic for [cyber_trait.type] station trait.") diff --git a/code/modules/unit_tests/strange_reagent.dm b/code/modules/unit_tests/strange_reagent.dm index 345be4befe977..6c3add3092d58 100644 --- a/code/modules/unit_tests/strange_reagent.dm +++ b/code/modules/unit_tests/strange_reagent.dm @@ -97,6 +97,8 @@ /datum/unit_test/strange_reagent/proc/test_death_no_damage(target_type) var/mob/living/target = allocate_new_target(target_type) target.death() + if(QDELETED(target)) + return update_amounts(target) strange_reagent.expose_mob(target, INGEST, amount_needed_to_revive) TEST_ASSERT_NOTEQUAL(target.stat, DEAD, "Strange Reagent did not revive a dead target type [target.type].") @@ -107,6 +109,8 @@ return target.death() + if(QDELETED(target)) + return update_amounts(target) strange_reagent.expose_mob(target, INGEST, amount_needed_to_revive) TEST_ASSERT_NOTEQUAL(target.stat, DEAD, "Strange Reagent did not revive a dead target type [target.type].") @@ -126,6 +130,8 @@ return target.death() + if(QDELETED(target)) + return update_amounts(target) strange_reagent.expose_mob(target, INGEST, amount_needed_to_full_heal) TEST_ASSERT_EQUAL(target_max_health, get_target_organic_health_manual(target), "Strange Reagent did not fully heal a dead target type [target.type] with the expected amount.") @@ -134,6 +140,8 @@ var/mob/living/target = allocate_new_target(target_type) if(!damage_target_to_percentage(target, strange_reagent.max_revive_damage_ratio * 0.9)) // 10% under the damage cap return + if(QDELETED(target)) + return update_amounts(target) strange_reagent.expose_mob(target, INGEST, amount_needed_to_revive) @@ -143,7 +151,9 @@ var/mob/living/target = allocate_new_target(target_type) if(!damage_target_to_percentage(target, strange_reagent.max_revive_damage_ratio * 1.1)) // 10% over the damage cap return - + if(QDELETED(target)) + return + update_amounts(target) strange_reagent.expose_mob(target, INGEST, amount_needed_to_revive) TEST_ASSERT_EQUAL(target.stat, DEAD, "Strange Reagent revived a target type [target.type] with more than double their max health in damage.") diff --git a/code/modules/unit_tests/tail_wag.dm b/code/modules/unit_tests/tail_wag.dm new file mode 100644 index 0000000000000..cd82dfdfd50c9 --- /dev/null +++ b/code/modules/unit_tests/tail_wag.dm @@ -0,0 +1,102 @@ +/// Tests to make sure tail wagging behaves as expected +/datum/unit_test/tail_wag + // used by the stop_after test + var/timer_finished = FALSE + +/datum/unit_test/tail_wag/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + var/obj/item/organ/external/tail/cat/dummy_tail = allocate(/obj/item/organ/external/tail/cat) + dummy_tail.Insert(dummy, special = TRUE, movement_flags = DELETE_IF_REPLACED) + + // SANITY TEST + + // start wagging + dummy.wag_tail() + if(!(dummy_tail.wag_flags & WAG_WAGGING)) + TEST_FAIL("Tail did not start wagging when it should have!") + + // stop wagging + dummy.unwag_tail() + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("Tail did not stop wagging when it should have!") + + // TESTING WAG EMOTE + + // start wagging + dummy.emote("wag") + if(!(dummy_tail.wag_flags & WAG_WAGGING)) + TEST_FAIL("Tail did not start wagging after using the *wag emote!") + + // stop wagging + dummy.emote("wag") + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("Tail did not stop wagging after using the *wag emote!") + + // TESTING WAG_ABLE FLAG + + // flip the wag flag to unwaggable + dummy_tail.wag_flags &= ~WAG_ABLE + + // try to wag it again + dummy.wag_tail() + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("Tail should not have the ability to wag, yet it did!") + + // flip the wag flag to waggable again + dummy_tail.wag_flags |= WAG_ABLE + + // start wagging again + dummy.wag_tail() + if(!(dummy_tail.wag_flags & WAG_WAGGING)) + TEST_FAIL("Tail did not start wagging when it should have!") + + // TESTING STOP_AFTER + + // stop wagging + dummy.unwag_tail() + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("Tail did not stop wagging when it should have!") + + // start wagging, stop after 0.1 seconds + dummy.wag_tail(0.1 SECONDS) + // because timers are a pain + addtimer(VARSET_CALLBACK(src, timer_finished, TRUE), 0.2 SECONDS) + if(!(dummy_tail.wag_flags & WAG_WAGGING)) + TEST_FAIL("Tail did not start wagging when it should have!") + + UNTIL(timer_finished) // wait a little bit + + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("Tail was supposed to stop wagging on its own after 0.1 seconds but it did not!") + + // TESTING TAIL REMOVAL + + // remove the tail + dummy_tail.Remove(dummy, special = TRUE) + + // check if tail is still wagging after being removed + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("Tail was still wagging after being removed!") + + // try to wag the removed tail + dummy.wag_tail() + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("A disembodied tail was able to start wagging!") + + // TESTING MOB DEATH + + // put it back and start wagging again + dummy_tail.Insert(dummy, special = TRUE, movement_flags = DELETE_IF_REPLACED) + dummy.wag_tail() + if(!(dummy_tail.wag_flags & WAG_WAGGING)) + TEST_FAIL("Tail did not start wagging when it should have!") + + // kill the mob, see if it stops wagging + dummy.adjustBruteLoss(9001) + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("A mob's tail was still wagging after being killed!") + + // check if we are still able to wag the tail after death + dummy.wag_tail() + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("A dead mob was able to wag their tail!") diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index 583a74dacc768..414c4189bb4a1 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -311,7 +311,7 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests()) //Needs a holodeck area linked to it which is not guarenteed to exist and technically is supposed to have a 1:1 relationship with computer anyway. returnable_list += typesof(/obj/machinery/computer/holodeck) //runtimes if not paired with a landmark - returnable_list += typesof(/obj/structure/industrial_lift) + returnable_list += typesof(/obj/structure/transport/linear) // Runtimes if the associated machinery does not exist, but not the base type returnable_list += subtypesof(/obj/machinery/airlock_controller) // Always ought to have an associated escape menu. Any references it could possibly hold would need one regardless. diff --git a/code/modules/uplink/uplink_devices.dm b/code/modules/uplink/uplink_devices.dm index c2409773ada6d..4d539be433de2 100644 --- a/code/modules/uplink/uplink_devices.dm +++ b/code/modules/uplink/uplink_devices.dm @@ -5,7 +5,7 @@ // simultaneously is an annoying distraction. /obj/item/uplink name = "station bounced radio" - icon = 'icons/obj/radio.dmi' + icon = 'icons/obj/devices/voice.dmi' icon_state = "radio" inhand_icon_state = "radio" worn_icon_state = "radio" @@ -14,7 +14,7 @@ righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' dog_fashion = /datum/dog_fashion/back - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BELT throw_speed = 3 throw_range = 7 diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm index 2c060b6ff98e2..65935f077e33d 100644 --- a/code/modules/uplink/uplink_items.dm +++ b/code/modules/uplink/uplink_items.dm @@ -1,4 +1,3 @@ - // TODO: Work into reworked uplinks. /// Selects a set number of unique items from the uplink, and deducts a percentage discount from them /proc/create_uplink_sales(num, datum/uplink_category/category, limited_stock, list/sale_items) @@ -8,7 +7,20 @@ var/datum/uplink_item/taken_item = pick_n_take(sale_items_copy) var/datum/uplink_item/uplink_item = new taken_item.type() var/discount = uplink_item.get_discount() - var/list/disclaimer = list("Void where prohibited.", "Not recommended for children.", "Contains small parts.", "Check local laws for legality in region.", "Do not taunt.", "Not responsible for direct, indirect, incidental or consequential damages resulting from any defect, error or failure to perform.", "Keep away from fire or flames.", "Product is provided \"as is\" without any implied or expressed warranties.", "As seen on TV.", "For recreational use only.", "Use only as directed.", "16% sales tax will be charged for orders originating within Space Nebraska.") + var/static/list/disclaimer = list( + "Void where prohibited.", + "Not recommended for children.", + "Contains small parts.", + "Check local laws for legality in region.", + "Do not taunt.", + "Not responsible for direct, indirect, incidental or consequential damages resulting from any defect, error or failure to perform.", + "Keep away from fire or flames.", + "Product is provided \"as is\" without any implied or expressed warranties.", + "As seen on TV.", + "For recreational use only.", + "Use only as directed.", + "16% sales tax will be charged for orders originating within Space Nebraska.", + ) uplink_item.limited_stock = limited_stock if(uplink_item.cost >= 20) //Tough love for nuke ops discount *= 0.5 @@ -111,10 +123,10 @@ /// Spawns an item and logs its purchase /datum/uplink_item/proc/purchase(mob/user, datum/uplink_handler/uplink_handler, atom/movable/source) - var/atom/A = spawn_item(item, user, uplink_handler, source) + var/atom/spawned_item = spawn_item(item, user, uplink_handler, source) log_uplink("[key_name(user)] purchased [src] for [cost] telecrystals from [source]'s uplink") if(purchase_log_vis && uplink_handler.purchase_log) - uplink_handler.purchase_log.LogPurchase(A, src, cost) + uplink_handler.purchase_log.LogPurchase(spawned_item, src, cost) if(lock_other_purchases) uplink_handler.shop_locked = TRUE @@ -122,20 +134,24 @@ /datum/uplink_item/proc/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source) if(!spawn_path) return - var/atom/A + var/atom/spawned_item if(ispath(spawn_path)) - A = new spawn_path(get_turf(user)) + spawned_item = new spawn_path(get_turf(user)) else - A = spawn_path + spawned_item = spawn_path if(refundable) - A.AddElement(/datum/element/uplink_reimburse, (refund_amount ? refund_amount : cost)) - if(ishuman(user) && isitem(A)) - var/mob/living/carbon/human/H = user - if(H.put_in_hands(A)) - to_chat(H, span_boldnotice("[A] materializes into your hands!")) - return A - to_chat(user, span_boldnotice("[A] materializes onto the floor!")) - return A + spawned_item.AddElement(/datum/element/uplink_reimburse, (refund_amount ? refund_amount : cost)) + var/mob/living/carbon/human/human_user = user + if(istype(human_user) && isitem(spawned_item) && human_user.put_in_hands(spawned_item)) + to_chat(human_user, span_boldnotice("[spawned_item] materializes into your hands!")) + else + to_chat(user, span_boldnotice("[spawned_item] materializes onto the floor!")) + SEND_SIGNAL(uplink_handler, COMSIG_ON_UPLINK_PURCHASE, spawned_item, user) + return spawned_item + +///For special overrides if an item can be bought or not. +/datum/uplink_item/proc/can_be_bought(datum/uplink_handler/source) + return TRUE /datum/uplink_category/discounts name = "Discounted Gear" diff --git a/code/modules/uplink/uplink_items/ammunition.dm b/code/modules/uplink/uplink_items/ammunition.dm index 292f87ffe13b3..e88727812528d 100644 --- a/code/modules/uplink/uplink_items/ammunition.dm +++ b/code/modules/uplink/uplink_items/ammunition.dm @@ -18,7 +18,6 @@ /datum/uplink_item/ammo/pistol name = "9mm Handgun Magazine" desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol." - progression_minimum = 10 MINUTES item = /obj/item/ammo_box/magazine/m9mm cost = 1 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -28,7 +27,6 @@ name = "9mm Armour Piercing Magazine" desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol. \ These rounds are less effective at injuring the target but penetrate protective gear." - progression_minimum = 30 MINUTES item = /obj/item/ammo_box/magazine/m9mm/ap cost = 2 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -37,7 +35,6 @@ name = "9mm Hollow Point Magazine" desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol. \ These rounds are more damaging but ineffective against armour." - progression_minimum = 30 MINUTES item = /obj/item/ammo_box/magazine/m9mm/hp cost = 3 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -46,7 +43,6 @@ name = "9mm Incendiary Magazine" desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol. \ Loaded with incendiary rounds which inflict little damage, but ignite the target." - progression_minimum = 30 MINUTES item = /obj/item/ammo_box/magazine/m9mm/fire cost = 2 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -55,7 +51,6 @@ name = ".357 Speed Loader" desc = "A speed loader that contains seven additional .357 Magnum rounds; usable with the Syndicate revolver. \ For when you really need a lot of things dead." - progression_minimum = 30 MINUTES item = /obj/item/ammo_box/a357 cost = 4 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //nukies get their own version diff --git a/code/modules/uplink/uplink_items/badass.dm b/code/modules/uplink/uplink_items/badass.dm index 93087be9dd00e..da7212ee8fc5b 100644 --- a/code/modules/uplink/uplink_items/badass.dm +++ b/code/modules/uplink/uplink_items/badass.dm @@ -22,7 +22,11 @@ if(!.) return - notify_ghosts("[user] has purchased a BADASS Syndicate Balloon!", source = src, action = NOTIFY_ORBIT, header = "What are they THINKING?") + notify_ghosts( + "[user] has purchased a BADASS Syndicate Balloon!", + source = ., + header = "What are they THINKING?", + ) /datum/uplink_item/badass/syndiecards name = "Syndicate Playing Cards" @@ -46,9 +50,8 @@ desc = "A secure briefcase containing 5000 space credits. Useful for bribing personnel, or purchasing goods \ and services at lucrative prices. The briefcase also feels a little heavier to hold; it has been \ manufactured to pack a little bit more of a punch if your client needs some convincing." - item = /obj/item/storage/secure/briefcase/syndie + item = /obj/item/storage/briefcase/secure/syndie cost = 3 - progression_minimum = 5 MINUTES restricted = TRUE illegal_tech = FALSE diff --git a/code/modules/uplink/uplink_items/bundle.dm b/code/modules/uplink/uplink_items/bundle.dm index 912c28e2bcb2b..f236aa4da253a 100644 --- a/code/modules/uplink/uplink_items/bundle.dm +++ b/code/modules/uplink/uplink_items/bundle.dm @@ -58,7 +58,6 @@ These items are collectively worth more than 25 telecrystals, but you do not know which specialization \ you will receive. May contain discontinued and/or exotic items. \ The Syndicate will only provide one Syndi-Kit per agent." - progression_minimum = 30 MINUTES item = /obj/item/storage/box/syndicate/bundle_a cost = 20 stock_key = UPLINK_SHARED_STOCK_KITS @@ -70,7 +69,6 @@ In Syndi-kit Special, you will receive items used by famous syndicate agents of the past. \ Collectively worth more than 25 telecrystals, the syndicate loves a good throwback. \ The Syndicate will only provide one Syndi-Kit per agent." - progression_minimum = 30 MINUTES item = /obj/item/storage/box/syndicate/bundle_b cost = 20 stock_key = UPLINK_SHARED_STOCK_KITS @@ -148,14 +146,13 @@ Rumored to contain a valuable assortment of items based on your current reputation, but you never know. Contents are sorted to always be worth 80 TC. \ The Syndicate will only provide one surplus item per agent." cost = 20 - item = /obj/structure/closet/crate/syndicrate - progression_minimum = 30 MINUTES + item = /obj/structure/closet/crate/secure/syndicrate stock_key = UPLINK_SHARED_STOCK_SURPLUS crate_tc_value = 80 - crate_type = /obj/structure/closet/crate/syndicrate + crate_type = /obj/structure/closet/crate/secure/syndicrate /// edited version of fill crate for super surplus to ensure it can only be unlocked with the syndicrate key -/datum/uplink_item/bundles_tc/surplus/united/fill_crate(obj/structure/closet/crate/syndicrate/surplus_crate, list/possible_items) +/datum/uplink_item/bundles_tc/surplus/united/fill_crate(obj/structure/closet/crate/secure/syndicrate/surplus_crate, list/possible_items) if(!istype(surplus_crate)) return var/tc_budget = crate_tc_value @@ -173,6 +170,5 @@ The Syndicate will only provide one surplus item per agent." cost = 20 item = /obj/item/syndicrate_key - progression_minimum = 30 MINUTES purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) stock_key = UPLINK_SHARED_STOCK_SURPLUS diff --git a/code/modules/uplink/uplink_items/clownops.dm b/code/modules/uplink/uplink_items/clownops.dm index b973869cf121b..852676dbcbb74 100644 --- a/code/modules/uplink/uplink_items/clownops.dm +++ b/code/modules/uplink/uplink_items/clownops.dm @@ -114,6 +114,26 @@ restricted = TRUE refundable = TRUE +/datum/uplink_item/reinforcement/monkey_agent + name = "Simian Agent Reinforcements" + desc = "Call in an extremely well trained monkey secret agent from our Syndicate Banana Department. \ + They've been trained to operate machinery and can read, but they can't speak Common." + item = /obj/item/antag_spawner/loadout/monkey_man + cost = 7 + purchasable_from = UPLINK_CLOWN_OPS + restricted = TRUE + refundable = TRUE + +/datum/uplink_item/reinforcement/monkey_supplies + name = "Simian Agent Supplies" + desc = "Sometimes you need a bit more firepower than a rabid monkey. Such as a rabid, armed monkey! \ + Monkeys can unpack this kit to receive a bag with a bargain-bin gun, ammunition, and some miscellaneous supplies." + item = /obj/item/storage/toolbox/guncase/monkeycase + cost = 4 + purchasable_from = UPLINK_CLOWN_OPS + restricted = TRUE + refundable = TRUE + /datum/uplink_item/mech/honker name = "Dark H.O.N.K." desc = "A clown combat mech equipped with bombanana peel and tearstache grenade launchers, as well as the ubiquitous HoNkER BlAsT 5000." diff --git a/code/modules/uplink/uplink_items/contractor.dm b/code/modules/uplink/uplink_items/contractor.dm new file mode 100644 index 0000000000000..6004caf97452e --- /dev/null +++ b/code/modules/uplink/uplink_items/contractor.dm @@ -0,0 +1,90 @@ +/datum/uplink_category/contractor + name = "Contractor" + weight = 10 + +/datum/uplink_item/bundles_tc/contract_kit + name = "Contract Kit" + desc = "The Syndicate have offered you the chance to become a contractor, take on kidnapping contracts for TC \ + and cash payouts. Upon purchase, you'll be granted your own contract uplink embedded within the supplied \ + tablet computer. Additionally, you'll be granted standard contractor gear to help with your mission - \ + comes supplied with the tablet, specialised space suit, chameleon jumpsuit and mask, agent card, \ + specialised contractor baton, and three randomly selected low cost items. \ + Can include otherwise unobtainable items." + item = /obj/item/storage/box/syndicate/contract_kit + category = /datum/uplink_category/contractor + cost = 20 + purchasable_from = ~(UPLINK_CLOWN_OPS | UPLINK_NUKE_OPS | UPLINK_TRAITORS) + +/datum/uplink_item/bundles_tc/contract_kit/purchase(mob/user, datum/uplink_handler/uplink_handler, atom/movable/source) + . = ..() + for(var/uplink_items in subtypesof(/datum/uplink_item/contractor)) + var/datum/uplink_item/uplink_item = new uplink_items + uplink_handler.extra_purchasable += uplink_item + +/datum/uplink_item/contractor + restricted = TRUE + category = /datum/uplink_category/contractor + purchasable_from = NONE //they will be added to extra_purchasable + +//prevents buying contractor stuff before you make an account. +/datum/uplink_item/contractor/can_be_bought(datum/uplink_handler/uplink_handler) + if(!uplink_handler.contractor_hub) + return FALSE + return ..() + +/datum/uplink_item/contractor/reroll + name = "Contract Reroll" + desc = "Request a reroll of your current contract list. Will generate a new target, \ + payment, and dropoff for the contracts you currently have available." + item = /obj/effect/gibspawner/generic + limited_stock = 2 + cost = 0 + +/datum/uplink_item/contractor/reroll/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source) + //We're not regenerating already completed/aborted/extracting contracts, but we don't want to repeat their targets. + var/list/new_target_list = list() + for(var/datum/syndicate_contract/contract_check in uplink_handler.contractor_hub.assigned_contracts) + if (contract_check.status != CONTRACT_STATUS_ACTIVE && contract_check.status != CONTRACT_STATUS_INACTIVE) + if (contract_check.contract.target) + new_target_list.Add(contract_check.contract.target) + continue + + //Reroll contracts without duplicates + for(var/datum/syndicate_contract/rerolling_contract in uplink_handler.contractor_hub.assigned_contracts) + if (rerolling_contract.status != CONTRACT_STATUS_ACTIVE && rerolling_contract.status != CONTRACT_STATUS_INACTIVE) + continue + + rerolling_contract.generate(new_target_list) + new_target_list.Add(rerolling_contract.contract.target) + + //Set our target list with the new set we've generated. + uplink_handler.contractor_hub.assigned_targets = new_target_list + return source //for log icon + +/datum/uplink_item/contractor/pinpointer + name = "Contractor Pinpointer" + desc = "A pinpointer that finds targets even without active suit sensors. \ + Due to taking advantage of an exploit within the system, it can't pinpoint \ + to the same accuracy as the traditional models. \ + Becomes permanently locked to the user that first activates it." + item = /obj/item/pinpointer/crew/contractor + limited_stock = 2 + cost = 1 + +/datum/uplink_item/contractor/extraction_kit + name = "Fulton Extraction Kit" + desc = "For getting your target across the station to those difficult dropoffs. \ + Place the beacon somewhere secure, and link the pack. \ + Activating the pack on your target will send them over to the beacon - \ + make sure they're not just going to run away though!" + item = /obj/item/storage/box/contractor/fulton_extraction + limited_stock = 1 + cost = 1 + +/datum/uplink_item/contractor/partner + name = "Contractor Reinforcement" + desc = "A reinforecment operative will be sent to aid you in your goals, \ + they are paid separately, and will not take a cut from your profits." + item = /obj/item/antag_spawner/loadout/contractor + limited_stock = 1 + cost = 2 diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm index 0cdcf3a7bb7f6..970741876bb7a 100644 --- a/code/modules/uplink/uplink_items/dangerous.dm +++ b/code/modules/uplink/uplink_items/dangerous.dm @@ -19,7 +19,6 @@ name = "Makarov Pistol" desc = "A small, easily concealable handgun that uses 9mm auto rounds in 8-round magazines and is compatible \ with suppressors." - progression_minimum = 10 MINUTES item = /obj/item/gun/ballistic/automatic/pistol cost = 7 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -28,7 +27,6 @@ name = "Box of Throwing Weapons" desc = "A box of shurikens and reinforced bolas from ancient Earth martial arts. They are highly effective \ throwing weapons. The bolas can knock a target down and the shurikens will embed into limbs." - progression_minimum = 10 MINUTES item = /obj/item/storage/box/syndie_kit/throwing_weapons cost = 3 illegal_tech = FALSE @@ -63,11 +61,11 @@ /datum/uplink_item/dangerous/doublesword name = "Double-Bladed Energy Sword" desc = "The double-bladed energy sword does slightly more damage than a standard energy sword and will deflect \ - all energy projectiles, but requires two hands to wield." + energy projectiles it blocks, but requires two hands to wield. It also struggles to protect you from tackles." progression_minimum = 30 MINUTES item = /obj/item/dualsaber - cost = 16 + cost = 13 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //nukies get their own version /datum/uplink_item/dangerous/doublesword/get_discount_value(discount_type) @@ -84,7 +82,7 @@ desc = "Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, they require an \ organic host as a home base and source of fuel. Holoparasites come in various types and share damage with their host." progression_minimum = 30 MINUTES - item = /obj/item/guardiancreator/tech/choose/traitor + item = /obj/item/guardian_creator/tech cost = 18 surplus = 0 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -95,7 +93,13 @@ name = "Syndicate Revolver" desc = "Waffle Co.'s modernized Syndicate revolver. Fires 7 brutal rounds of .357 Magnum." item = /obj/item/gun/ballistic/revolver/syndicate - progression_minimum = 30 MINUTES cost = 13 surplus = 50 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //nukies get their own version + +/datum/uplink_item/dangerous/cat + name = "Feral cat grenade" + desc = "This grenade is filled with 5 feral cats in stasis. Upon activation, the feral cats are awoken and unleashed unto unlucky bystanders. WARNING: The cats are not trained to discern friend from foe!" + cost = 5 + item = /obj/item/grenade/spawnergrenade/cat + surplus = 30 diff --git a/code/modules/uplink/uplink_items/device_tools.dm b/code/modules/uplink/uplink_items/device_tools.dm index 0fd5aa35b781b..66cb58c7b2899 100644 --- a/code/modules/uplink/uplink_items/device_tools.dm +++ b/code/modules/uplink/uplink_items/device_tools.dm @@ -46,7 +46,7 @@ desc = "When linked to a tram's on board computer systems, this device allows the user to manipulate the controls remotely. \ Includes direction toggle and a rapid mode to bypass door safety checks and crossing signals. \ Perfect for running someone over in the name of a tram malfunction!" - item = /obj/item/tram_remote + item = /obj/item/assembly/control/transport/remote cost = 2 /datum/uplink_item/device_tools/thermal diff --git a/code/modules/uplink/uplink_items/explosive.dm b/code/modules/uplink/uplink_items/explosive.dm index 27145ccf004df..72f40b00dfca3 100644 --- a/code/modules/uplink/uplink_items/explosive.dm +++ b/code/modules/uplink/uplink_items/explosive.dm @@ -16,7 +16,6 @@ desc = "C-4 is plastic explosive of the common variety Composition C. You can use it to breach walls, sabotage equipment, or connect \ an assembly to it in order to alter the way it detonates. It can be attached to almost all objects and has a modifiable timer with a \ minimum setting of 10 seconds." - progression_minimum = 5 MINUTES item = /obj/item/grenade/c4 cost = 1 @@ -24,7 +23,6 @@ name = "Bag of C-4 explosives" desc = "Because sometimes quantity is quality. Contains 10 C-4 plastic explosives." item = /obj/item/storage/backpack/duffelbag/syndie/c4 - progression_minimum = 10 MINUTES cost = 8 //20% discount! cant_discount = TRUE @@ -43,7 +41,6 @@ desc = "When inserted into a tablet, this cartridge gives you four opportunities to \ detonate tablets of crewmembers who have their message feature enabled. \ The concussive effect from the explosion will knock the recipient out for a short period, and deafen them for longer." - progression_minimum = 20 MINUTES item = /obj/item/computer_disk/virus/detomatix cost = 6 restricted = TRUE @@ -64,7 +61,7 @@ name = "Pizza Bomb" desc = "A pizza box with a bomb cunningly attached to the lid. The timer needs to be set by opening the box; afterwards, \ opening the box again will trigger the detonation after the timer has elapsed. Comes with free pizza, for you or your target!" - progression_minimum = 30 MINUTES + progression_minimum = 15 MINUTES item = /obj/item/pizzabox/bomb cost = 6 surplus = 8 @@ -82,7 +79,6 @@ /datum/uplink_item/explosives/syndicate_bomb/emp name = "Syndicate EMP Bomb" desc = "A variation of the syndicate bomb designed to produce a large EMP effect." - progression_minimum = 30 MINUTES item = /obj/item/sbeacondrop/emp cost = 7 diff --git a/code/modules/uplink/uplink_items/implant.dm b/code/modules/uplink/uplink_items/implant.dm index 34fc9eedb0ff1..87c9fd6c96c07 100644 --- a/code/modules/uplink/uplink_items/implant.dm +++ b/code/modules/uplink/uplink_items/implant.dm @@ -9,11 +9,14 @@ /datum/uplink_item/implants/freedom name = "Freedom Implant" - desc = "An implant injected into the body and later activated at the user's will. It will attempt to free the \ - user from common restraints such as handcuffs." + desc = "Can be activated to release common restraints such as handcuffs, legcuffs, and even bolas tethered around the legs." item = /obj/item/storage/box/syndie_kit/imp_freedom cost = 5 +/datum/uplink_item/implants/freedom/New() + . = ..() + desc += " Implant has enough energy for [FREEDOM_IMPLANT_CHARGES] uses before it becomes inert and harmlessly self-destructs." + /datum/uplink_item/implants/radio name = "Internal Syndicate Radio Implant" desc = "An implant injected into the body, allowing the use of an internal Syndicate radio. \ diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm index 991252a38263a..8793bc0fbe8e3 100644 --- a/code/modules/uplink/uplink_items/job.dm +++ b/code/modules/uplink/uplink_items/job.dm @@ -146,11 +146,17 @@ cost = 11 restricted_roles = list(JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER) +/datum/uplink_item/role_restricted/rebarxbowsyndie + name = "Syndicate Rebar Crossbow" + desc = "A much more proffessional version of the engineer's bootleg rebar crossbow. 3 shot mag, quicker loading, and better ammo. Owners manual included." + item = /obj/item/storage/box/syndie_kit/rebarxbowsyndie + cost = 10 + restricted_roles = list(JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER) + /datum/uplink_item/role_restricted/magillitis_serum name = "Magillitis Serum Autoinjector" desc = "A single-use autoinjector which contains an experimental serum that causes rapid muscular growth in Hominidae. \ Side-affects may include hypertrichosis, violent outbursts, and an unending affinity for bananas." - progression_minimum = 10 MINUTES item = /obj/item/reagent_containers/hypospray/medipen/magillitis cost = 15 restricted_roles = list(JOB_GENETICIST, JOB_RESEARCH_DIRECTOR) @@ -159,7 +165,6 @@ name = "Box of Gorilla Cubes" desc = "A box with three Waffle Co. brand gorilla cubes. Eat big to get big. \ Caution: Product may rehydrate when exposed to water." - progression_minimum = 15 MINUTES item = /obj/item/storage/box/gorillacubes cost = 6 restricted_roles = list(JOB_GENETICIST, JOB_RESEARCH_DIRECTOR) @@ -173,6 +178,16 @@ cost = 5 surplus = 50 +/datum/uplink_item/role_restricted/advanced_plastic_surgery + name = "Advanced Plastic Surgery Program" + desc = "A bootleg copy of an collector item, this disk contains the procedure to perform advanced plastic surgery, allowing you to model someone's face and voice based on a picture taken by a camera on your offhand. \ + All changes are superficial and does not change ones genetic makeup. \ + Insert into an Operating Console to enable the procedure." + item = /obj/item/disk/surgery/brainwashing + restricted_roles = list(JOB_MEDICAL_DOCTOR, JOB_CHIEF_MEDICAL_OFFICER, JOB_ROBOTICIST) + cost = 1 + surplus = 50 + /datum/uplink_item/role_restricted/springlock_module name = "Heavily Modified Springlock MODsuit Module" desc = "A module that spans the entire size of the MOD unit, sitting under the outer shell. \ @@ -199,7 +214,8 @@ name = "Kinetic Accelerator Pressure Mod" desc = "A modification kit which allows Kinetic Accelerators to do greatly increased damage while indoors. \ Occupies 35% mod capacity." - progression_minimum = 30 MINUTES + // While less deadly than a revolver it does have infinite ammo + progression_minimum = 15 MINUTES item = /obj/item/borg/upgrade/modkit/indoors cost = 5 //you need two for full damage, so total of 10 for maximum damage limited_stock = 2 //you can't use more than two! @@ -210,7 +226,6 @@ name = "Guide to Advanced Mimery Series" desc = "The classical two part series on how to further hone your mime skills. Upon studying the series, the user should be able to make 3x1 invisible walls, and shoot bullets out of their fingers. \ Obviously only works for Mimes." - progression_minimum = 20 MINUTES cost = 12 item = /obj/item/storage/box/syndie_kit/mimery restricted_roles = list(JOB_MIME) @@ -228,7 +243,7 @@ /datum/uplink_item/role_restricted/chemical_gun name = "Reagent Dartgun" desc = "A heavily modified syringe gun which is capable of synthesizing its own chemical darts using input reagents. Can hold 90u of reagents." - progression_minimum = 20 MINUTES + progression_minimum = 15 MINUTES item = /obj/item/gun/chem cost = 12 restricted_roles = list(JOB_CHEMIST, JOB_CHIEF_MEDICAL_OFFICER, JOB_BOTANIST) @@ -236,7 +251,6 @@ /datum/uplink_item/role_restricted/pie_cannon name = "Banana Cream Pie Cannon" desc = "A special pie cannon for a special clown, this gadget can hold up to 20 pies and automatically fabricates one every two seconds!" - progression_minimum = 10 MINUTES cost = 10 item = /obj/item/pneumatic_cannon/pie/selfcharge restricted_roles = list(JOB_CLOWN) @@ -266,9 +280,6 @@ someone saves them or they manage to crawl out. Be sure not to ram into any walls or vending machines, as the springloaded seats \ are very sensitive. Now with our included lube defense mechanism which will protect you against any angry shitcurity! \ Premium features can be unlocked with a cryptographic sequencer!" - // It has a low progression cost because it's the sort of item that only works well early in the round - // Plus, it costs all your TC, and it's not an instant kill tool. - progression_minimum = 5 MINUTES item = /obj/vehicle/sealed/car/clowncar cost = 20 restricted_roles = list(JOB_CLOWN) @@ -280,9 +291,6 @@ His Grace grants gradual regeneration and complete stun immunity to His wielder, but be wary: if He gets too hungry, He will become impossible to drop and eventually kill you if not fed. \ However, if left alone for long enough, He will fall back to slumber. \ To activate His Grace, simply unlatch Him." - // It has a low progression cost because it's the sort of item that only works well early in the round - // Plus, it costs all your TC and will lock your uplink. - progression_minimum = 5 MINUTES lock_other_purchases = TRUE cant_discount = TRUE item = /obj/item/his_grace @@ -325,3 +333,50 @@ cost = 14 //High cost because of the potential for extreme damage in the hands of a skilled scientist. restricted_roles = list(JOB_RESEARCH_DIRECTOR, JOB_SCIENTIST) surplus = 5 + +/datum/uplink_item/role_restricted/evil_seedling + name = "Evil Seedling" + desc = "A rare seed we have recovered that grows into a dangerous species that will aid you with your tasks!" + item = /obj/item/seeds/seedling/evil + cost = 8 + restricted_roles = list(JOB_BOTANIST) + +/datum/uplink_item/role_restricted/bee_smoker + name = "Bee Smoker" + desc = "A device that runs on cannabis, turning it into a gas that can hypnotize bees to follow our commands." + item = /obj/item/bee_smoker + cost = 4 + restricted_roles = list(JOB_BOTANIST) + +/datum/uplink_item/role_restricted/monkey_agent + name = "Simian Agent Reinforcements" + desc = "Call in an extremely well trained monkey secret agent from our Syndicate Banana Department. \ + They've been trained to operate machinery and can read, but they can't speak Common. \ + Please note that these are free-range monkeys that don't react with Mutadone." + item = /obj/item/antag_spawner/loadout/monkey_man + cost = 6 + restricted_roles = list(JOB_RESEARCH_DIRECTOR, JOB_SCIENTIST, JOB_GENETICIST, JOB_ASSISTANT, JOB_MIME, JOB_CLOWN) + restricted = TRUE + refundable = TRUE + +/datum/uplink_item/role_restricted/monkey_supplies + name = "Simian Agent Supplies" + desc = "Sometimes you need a bit more firepower than a rabid monkey. Such as a rabid, armed monkey! \ + Monkeys can unpack this kit to receive a bag with a bargain-bin gun, ammunition, and some miscellaneous supplies." + item = /obj/item/storage/toolbox/guncase/monkeycase + cost = 4 + limited_stock = 3 + restricted_roles = list(JOB_ASSISTANT, JOB_MIME, JOB_CLOWN) + restricted = TRUE + refundable = FALSE + + +/datum/uplink_item/role_restricted/reticence + name = "Reticence Cloaked Assasination exosuit" + desc = "A silent, fast, and nigh-invisible but exceptionally fragile miming exosuit! \ + fully equipped with a near-silenced pistol, and a RCD for your best assasination needs, Does not include tools, No refunds." + item = /obj/vehicle/sealed/mecha/reticence/loaded + cost = 20 + restricted_roles = list(JOB_MIME) + restricted = TRUE + refundable = FALSE diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm index be7c163bd861d..d8bead5da6781 100644 --- a/code/modules/uplink/uplink_items/nukeops.dm +++ b/code/modules/uplink/uplink_items/nukeops.dm @@ -42,6 +42,21 @@ // ~~ Weapon Categories ~~ +// Core Gear Box: This contains all the 'fundamental' equipment that most nuclear operatives probably should be buying. It isn't cheaper, but it is a quick and convenient method of acquiring all the gear necessary immediately. +// Only allows one purchase, and doesn't prevent the purchase of the contained items. Focused on newer players to help them understand what items they need to succeed, and to help older players quickly purchase the baseline gear they need. + +/datum/uplink_item/weapon_kits/core + name = "Core Equipment Box (Essential)" + desc = "This box contains an airlock authentification override card, a MODsuit energy shield module, a C-4 explosive charge, a freedom implant and a stimpack injector. \ + The most important support items for most operatives to succeed in their mission, bundled together. It is highly recommend you buy this kit. \ + Note: This bundle is not at a discount. You can purchase all of these items separately. You do not NEED these items, but most operatives fail WITHOUT at \ + least SOME of these items. More experienced operatives can do without." + item = /obj/item/storage/box/syndie_kit/core_gear + cost = 22 //freedom 5, doormag 3, c-4 1, stimpack 5, shield modsuit module 8 + limited_stock = 1 + cant_discount = TRUE + purchasable_from = UPLINK_NUKE_OPS + //Low-cost firearms: Around 8 TC each. Meant for easy squad weapon purchases /datum/uplink_item/weapon_kits/low_cost @@ -153,14 +168,21 @@ cost = 4 purchasable_from = UPLINK_NUKE_OPS -// ~~ Energy Sword and Shield ~~ +// ~~ Energy Sword and Shield & CQC ~~ /datum/uplink_item/weapon_kits/medium_cost/sword_and_board name = "Energy Shield and Sword Case (Very Hard)" - desc = "A case containing an energy sword and energy shield. The shield is capable of deflecting \ - energy and laser projectiles, and the sword most forms of attack. Perfect for the enterprising nuclear knight. " + desc = "A case containing an energy sword and energy shield. Paired together, it provides considerable defensive power without lethal potency. \ + Perfect for the enterprising nuclear knight. Comes with a medieval helmet for your MODsuit!" item = /obj/item/storage/toolbox/guncase/sword_and_board +/datum/uplink_item/weapon_kits/medium_cost/cqc + name = "CQC Equipment Case (Very Hard)" + desc = "Contains a manual that instructs you in the ways of CQC, or Close Quarters Combat. Comes with a stealth implant, a pack of smokes and a snazzy bandana (use it with the hat stabilizers in your MODsuit)." + item = /obj/item/storage/toolbox/guncase/cqc + purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS + surplus = 0 + // ~~ Syndicate Revolver ~~ // Nuclear operatives get a special deal on their revolver purchase compared to traitors. @@ -331,16 +353,9 @@ Blast your enemies with instant shots! Just watch out for the rebound..." item = /obj/item/ammo_box/magazine/sniper_rounds/marksman -/datum/uplink_item/weapon_kits/high_cost/cqc - name = "CQC Equipment Case (Very Hard)" - desc = "Contains a manual that instructs you in the ways of CQC, or Close Quarters Combat. Comes with a stealth implant and a snazzy bandana (and a hat stabilizer to go with it)." - item = /obj/item/storage/toolbox/guncase/cqc - purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS - surplus = 0 - /datum/uplink_item/weapon_kits/high_cost/doublesword - name = "Double-Energy Sword Case (Very Hard)" - desc = "A case containing a double-energy sword, anti-slip module, meth autoinjector, and a bar of soap. \ + name = "Double-Bladed Energy Sword Case (Very Hard)" + desc = "A case containing a double-bladed energy sword, anti-slip module, meth autoinjector, and a bar of soap. \ Some say the most infamous nuclear operatives utilized this combination of equipment to slaughter hundreds \ of Nanotrasen employees. However, some also say this is an embellishment from the Tiger Co-operative. \ The soap did most of the work. Comes with a prisoner uniform so you fit the part." @@ -498,6 +513,16 @@ cost = 10 purchasable_from = UPLINK_NUKE_OPS +/datum/uplink_item/bundles_tc/cowboy + name = "Syndicate Outlaw Kit" + desc = "There've been high tales of an outlaw 'round these parts. A fella so ruthless and efficient no ranger could ever capture 'em. \ + Now you can be just like 'em! \ + This kit contains armor-lined cowboy equipment, a custom revolver and holster, and a horse with a complimentary apple to tame. \ + A lighter is also included, though you must supply your own smokes." + item = /obj/item/storage/box/syndie_kit/cowboy + cost = 18 + purchasable_from = UPLINK_NUKE_OPS + // Mech related gear /datum/uplink_category/mech @@ -570,10 +595,10 @@ /datum/uplink_item/suits/energy_shield name = "MODsuit Energy Shield Module" - desc = "An energy shield module for a MODsuit. The shields can handle up to three impacts \ - within a short duration and will rapidly recharge while not under fire." + desc = "An energy shield module for a MODsuit. The shields can stop a single impact \ + before needing to recharge. Used wisely, this module will keep you alive for a lot longer." item = /obj/item/mod/module/energy_shield - cost = 15 + cost = 8 purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS /datum/uplink_item/suits/emp_shield @@ -679,7 +704,7 @@ /datum/uplink_item/implants/nuclear/deathrattle name = "Box of Deathrattle Implants" desc = "A collection of implants (and one reusable implanter) that should be injected into the team. When one of the team \ - dies, all other implant holders recieve a mental message informing them of their teammates' name \ + dies, all other implant holders receive a mental message informing them of their teammates' name \ and the location of their death. Unlike most implants, these are designed to be implanted \ in any creature, biological or mechanical." item = /obj/item/storage/box/syndie_kit/imp_deathrattle @@ -793,7 +818,7 @@ /datum/uplink_item/badass/hats name = "Hat Crate" - desc = "Hat crate! Contains hats, along with hat stabilizers to wear your hats while you're in your suit! HATS!!!" + desc = "Hat crate! Contains hats! HATS!!!" item = /obj/structure/closet/crate/large/hats cost = 5 purchasable_from = UPLINK_CLOWN_OPS | UPLINK_NUKE_OPS diff --git a/code/modules/uplink/uplink_items/stealthy.dm b/code/modules/uplink/uplink_items/stealthy.dm index 54c9bbe9adcf7..793120fe56f34 100644 --- a/code/modules/uplink/uplink_items/stealthy.dm +++ b/code/modules/uplink/uplink_items/stealthy.dm @@ -65,7 +65,6 @@ desc = "This box contains a guide on how to craft masterful works of origami, allowing you to transform normal pieces of paper into \ perfectly aerodynamic (and potentially lethal) paper airplanes." item = /obj/item/storage/box/syndie_kit/origami_bundle - progression_minimum = 10 MINUTES cost = 4 surplus = 0 purchasable_from = ~UPLINK_NUKE_OPS //clown ops intentionally left in, because that seems like some s-tier shenanigans. @@ -77,7 +76,7 @@ and gain the ability to swat bullets from the air, but you will also refuse to use dishonorable ranged weaponry." item = /obj/item/book/granter/martial/carp progression_minimum = 30 MINUTES - cost = 13 + cost = 17 surplus = 0 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -90,7 +89,17 @@ slur as if inebriated. It can produce an infinite number \ of bolts, but takes time to automatically recharge after each shot." item = /obj/item/gun/energy/recharge/ebow - progression_minimum = 30 MINUTES cost = 10 surplus = 50 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) + +/datum/uplink_item/stealthy_weapons/contrabaton + name = "Contractor Baton" + desc = "A compact, specialised baton assigned to Syndicate contractors. Applies light electrical shocks to targets. \ + These shocks are capable of affecting the inner circuitry of most robots as well, applying a short stun. \ + Has the added benefit of affecting the vocal cords of your victim, causing them to slur as if inebriated." + item = /obj/item/melee/baton/telescopic/contractor_baton + cost = 7 + surplus = 50 + limited_stock = 1 + purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_INFILTRATORS) diff --git a/code/modules/vehicles/_vehicle.dm b/code/modules/vehicles/_vehicle.dm index ca666292a3f52..4eca7cd4ebae1 100644 --- a/code/modules/vehicles/_vehicle.dm +++ b/code/modules/vehicles/_vehicle.dm @@ -6,7 +6,6 @@ max_integrity = 300 armor_type = /datum/armor/obj_vehicle layer = VEHICLE_LAYER - plane = GAME_PLANE_FOV_HIDDEN density = TRUE anchored = FALSE blocks_emissive = EMISSIVE_BLOCK_GENERIC diff --git a/code/modules/vehicles/atv.dm b/code/modules/vehicles/atv.dm index c4fd2bcaceb7e..aa9a963a1013c 100644 --- a/code/modules/vehicles/atv.dm +++ b/code/modules/vehicles/atv.dm @@ -59,22 +59,18 @@ turret.pixel_x = base_pixel_x turret.pixel_y = base_pixel_y + 4 turret.layer = ABOVE_MOB_LAYER - SET_PLANE(turret, GAME_PLANE_UPPER, our_turf) if(EAST) turret.pixel_x = base_pixel_x - 12 turret.pixel_y = base_pixel_y + 4 turret.layer = OBJ_LAYER - SET_PLANE(turret, GAME_PLANE, our_turf) if(SOUTH) turret.pixel_x = base_pixel_x turret.pixel_y = base_pixel_y + 4 turret.layer = OBJ_LAYER - SET_PLANE(turret, GAME_PLANE, our_turf) if(WEST) turret.pixel_x = base_pixel_x + 12 turret.pixel_y = base_pixel_y + 4 turret.layer = OBJ_LAYER - SET_PLANE(turret, GAME_PLANE, our_turf) /obj/vehicle/ridden/atv/welder_act(mob/living/user, obj/item/W) if(user.combat_mode) @@ -117,12 +113,11 @@ smoke.start() /obj/vehicle/ridden/atv/bullet_act(obj/projectile/P) - if(prob(50) || !buckled_mobs) + if(prob(50) || !LAZYLEN(buckled_mobs)) return ..() - for(var/m in buckled_mobs) - var/mob/buckled_mob = m + for(var/mob/buckled_mob as anything in buckled_mobs) buckled_mob.bullet_act(P) - return TRUE + return BULLET_ACT_HIT /obj/vehicle/ridden/atv/atom_destruction() explosion(src, devastation_range = -1, light_impact_range = 2, flame_range = 3, flash_range = 4) diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm index 1fd230bb47a82..032a054a77c9a 100644 --- a/code/modules/vehicles/cars/clowncar.dm +++ b/code/modules/vehicles/cars/clowncar.dm @@ -9,10 +9,11 @@ movedelay = 0.6 car_traits = CAN_KIDNAP key_type = /obj/item/bikehorn - light_system = MOVABLE_LIGHT_DIRECTIONAL + light_system = OVERLAY_LIGHT_DIRECTIONAL light_range = 8 light_power = 2 light_on = FALSE + access_provider_flags = VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_KIDNAPPED ///list of headlight colors we use to pick through when we have party mode due to emag var/headlight_colors = list(COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_LIME, COLOR_BRIGHT_BLUE, COLOR_CYAN, COLOR_PURPLE) ///Cooldown time inbetween [/obj/vehicle/sealed/car/clowncar/proc/roll_the_dice()] usages @@ -198,7 +199,7 @@ * * Fart and make everyone nearby laugh */ /obj/vehicle/sealed/car/clowncar/proc/roll_the_dice(mob/user) - if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_CLOWNCAR_RANDOMNESS)) + if(TIMER_COOLDOWN_RUNNING(src, COOLDOWN_CLOWNCAR_RANDOMNESS)) to_chat(user, span_notice("The button panel is currently recharging.")) return TIMER_COOLDOWN_START(src, COOLDOWN_CLOWNCAR_RANDOMNESS, dice_cooldown_time) diff --git a/code/modules/vehicles/cars/vim.dm b/code/modules/vehicles/cars/vim.dm index a37c6e18b8eee..53eee5105a97e 100644 --- a/code/modules/vehicles/cars/vim.dm +++ b/code/modules/vehicles/cars/vim.dm @@ -13,7 +13,7 @@ enter_delay = 20 movedelay = 0.6 engine_sound_length = 0.3 SECONDS - light_system = MOVABLE_LIGHT_DIRECTIONAL + light_system = OVERLAY_LIGHT_DIRECTIONAL light_range = 4 light_power = 2 light_on = FALSE diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index fdb53c47572cb..05b3470358f0d 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -10,7 +10,7 @@ * AI also has special checks becaus it gets in and out of the mech differently * Always call remove_occupant(mob) when leaving the mech so the mob is removed properly * - * For multi-crew, you need to set how the occupants recieve ability bitflags corresponding to their status on the vehicle(i.e: driver, gunner etc) + * For multi-crew, you need to set how the occupants receive ability bitflags corresponding to their status on the vehicle(i.e: driver, gunner etc) * Abilities can then be set to only apply for certain bitflags and are assigned as such automatically * * Clicks are wither translated into mech_melee_attack (see mech_melee_attack.dm) @@ -29,7 +29,7 @@ move_force = MOVE_FORCE_VERY_STRONG move_resist = MOVE_FORCE_EXTREMELY_STRONG COOLDOWN_DECLARE(mecha_bump_smash) - light_system = MOVABLE_LIGHT_DIRECTIONAL + light_system = OVERLAY_LIGHT_DIRECTIONAL light_on = FALSE light_range = 8 generic_canpass = FALSE @@ -54,7 +54,7 @@ /// Keeps track of the mech's servo motor var/obj/item/stock_parts/servo/servo ///Contains flags for the mecha - var/mecha_flags = CANSTRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE + var/mecha_flags = CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE ///Spark effects are handled by this datum var/datum/effect_system/spark_spread/spark_system @@ -70,8 +70,6 @@ var/bumpsmash = FALSE ///////////ATMOS - ///Whether the pilot is hidden from the outside viewers and whether the cabin can be sealed to be airtight - var/enclosed = TRUE ///Whether the cabin exchanges gases with the environment var/cabin_sealed = FALSE ///Internal air mix datum @@ -173,6 +171,10 @@ var/overclock_temp = 0 ///Temperature threshold at which actuators may start causing internal damage var/overclock_temp_danger = 15 + ///Whether the mech has an option to enable safe overclocking + var/overclock_safety_available = FALSE + ///Whether the overclocking turns off automatically when overheated + var/overclock_safety = FALSE //Bool for zoom on/off var/zoom_mode = FALSE @@ -263,6 +265,7 @@ equip_by_category[key] -= path AddElement(/datum/element/falling_hazard, damage = 80, wound_bonus = 10, hardhat_safety = FALSE, crushes = TRUE) + AddElement(/datum/element/hostile_machine) /obj/vehicle/sealed/mecha/Destroy() for(var/ejectee in occupants) @@ -323,7 +326,7 @@ if(!ai.linked_core) // we probably shouldnt gib AIs with a core unlucky_ai = occupant ai.investigate_log("has been gibbed by having their mech destroyed.", INVESTIGATE_DEATHS) - ai.gib() //No wreck, no AI to recover + ai.gib(DROP_ALL_REMAINS) //No wreck, no AI to recover else mob_exit(ai,silent = TRUE, forced = TRUE) // so we dont ghost the AI continue @@ -392,7 +395,7 @@ /obj/vehicle/sealed/mecha/generate_actions() initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_eject) - if(enclosed) + if(mecha_flags & IS_ENCLOSED) initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/mech_toggle_cabin_seal, VEHICLE_CONTROL_SETTINGS) if(can_use_overclock) initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_overclock) @@ -463,7 +466,7 @@ . += span_warning("It's missing a capacitor.") if(!scanmod) . += span_warning("It's missing a scanning module.") - if(enclosed) + if(mecha_flags & IS_ENCLOSED) return if(mecha_flags & SILICON_PILOT) . += span_notice("[src] appears to be piloting itself...") @@ -517,9 +520,13 @@ if(!overclock_mode && overclock_temp > 0) overclock_temp -= seconds_per_tick return - overclock_temp = min(overclock_temp + seconds_per_tick, overclock_temp_danger * 2) + var/temp_gain = seconds_per_tick * (1 + 1 / movedelay) + overclock_temp = min(overclock_temp + temp_gain, overclock_temp_danger * 2) if(overclock_temp < overclock_temp_danger) return + if(overclock_temp >= overclock_temp_danger && overclock_safety) + toggle_overclock(FALSE) + return var/damage_chance = 100 * ((overclock_temp - overclock_temp_danger) / (overclock_temp_danger * 2)) if(SPT_PROB(damage_chance, seconds_per_tick)) do_sparks(5, TRUE, src) @@ -565,10 +572,10 @@ /obj/vehicle/sealed/mecha/proc/process_occupants(seconds_per_tick) for(var/mob/living/occupant as anything in occupants) - if(!enclosed && occupant?.incapacitated()) //no sides mean it's easy to just sorta fall out if you're incapacitated. + if(!(mecha_flags & IS_ENCLOSED) && occupant?.incapacitated()) //no sides mean it's easy to just sorta fall out if you're incapacitated. mob_exit(occupant, randomstep = TRUE) //bye bye continue - if(cell) + if(cell && cell.maxcharge) var/cellcharge = cell.charge/cell.maxcharge switch(cellcharge) if(0.75 to INFINITY) @@ -581,6 +588,8 @@ occupant.throw_alert(ALERT_CHARGE, /atom/movable/screen/alert/lowcell/mech, 3) else occupant.throw_alert(ALERT_CHARGE, /atom/movable/screen/alert/emptycell/mech) + else + occupant.throw_alert(ALERT_CHARGE, /atom/movable/screen/alert/nocell) var/integrity = atom_integrity/max_integrity*100 switch(integrity) if(30 to 45) @@ -675,7 +684,7 @@ if(!(livinguser in return_controllers_with_flag(VEHICLE_CONTROL_MELEE))) to_chat(livinguser, span_warning("You're in the wrong seat to interact with your hands.")) return - var/on_cooldown = TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_MELEE_ATTACK) + var/on_cooldown = TIMER_COOLDOWN_RUNNING(src, COOLDOWN_MECHA_MELEE_ATTACK) var/adjacent = Adjacent(target) if(SEND_SIGNAL(src, COMSIG_MECHA_MELEE_CLICK, livinguser, target, on_cooldown, adjacent) & COMPONENT_CANCEL_MELEE_CLICK) return @@ -714,12 +723,12 @@ ///////////////////////////////////// /obj/vehicle/sealed/mecha/remove_air(amount) - if(enclosed && cabin_sealed) + if((mecha_flags & IS_ENCLOSED) && cabin_sealed) return cabin_air.remove(amount) return ..() /obj/vehicle/sealed/mecha/return_air() - if(enclosed && cabin_sealed) + if((mecha_flags & IS_ENCLOSED) && cabin_sealed) return cabin_air return ..() @@ -738,11 +747,11 @@ ///makes cabin unsealed, dumping cabin air outside or airtight filling the cabin with external air mix /obj/vehicle/sealed/mecha/proc/set_cabin_seal(mob/user, cabin_sealed) - if(!enclosed) + if(!(mecha_flags & IS_ENCLOSED)) balloon_alert(user, "cabin can't be sealed!") log_message("Tried to seal cabin. This mech can't be airtight.", LOG_MECHA) return - if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_CABIN_SEAL)) + if(TIMER_COOLDOWN_RUNNING(src, COOLDOWN_MECHA_CABIN_SEAL)) balloon_alert(user, "on cooldown!") return TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_CABIN_SEAL, 1 SECONDS) @@ -811,6 +820,15 @@ else overclock_mode = !overclock_mode log_message("Toggled overclocking.", LOG_MECHA) + + for(var/mob/occupant as anything in occupants) + var/datum/action/act = locate(/datum/action/vehicle/sealed/mecha/mech_overclock) in occupant.actions + if(!act) + continue + act.button_icon_state = "mech_overload_[overclock_mode ? "on" : "off"]" + balloon_alert(occupant, "overclock [overclock_mode ? "on":"off"]") + act.build_all_button_icons() + if(overclock_mode) movedelay = movedelay / overclock_coeff visible_message(span_notice("[src] starts heating up, making humming sounds.")) @@ -857,5 +875,5 @@ act.button_icon_state = "mech_lights_on" else act.button_icon_state = "mech_lights_off" - balloon_alert(occupant, "toggled lights [mecha_flags & LIGHTS_ON ? "on":"off"]") + balloon_alert(occupant, "lights [mecha_flags & LIGHTS_ON ? "on":"off"]") act.build_all_button_icons() diff --git a/code/modules/vehicles/mecha/combat/durand.dm b/code/modules/vehicles/mecha/combat/durand.dm index 4ac13b8dd64b7..35b53c30ccfc8 100644 --- a/code/modules/vehicles/mecha/combat/durand.dm +++ b/code/modules/vehicles/mecha/combat/durand.dm @@ -164,7 +164,7 @@ own integrity back to max. Shield is automatically dropped if we run out of powe pixel_y = 4 max_integrity = 10000 anchored = TRUE - light_system = MOVABLE_LIGHT + light_system = OVERLAY_LIGHT light_range = MINIMUM_USEFUL_LIGHT_RANGE light_power = 5 light_color = LIGHT_COLOR_ELECTRIC_CYAN @@ -233,7 +233,7 @@ own integrity back to max. Shield is automatically dropped if we run out of powe set_light_on(chassis.defense_mode) if(chassis.defense_mode) - invisibility = 0 + SetInvisibility(INVISIBILITY_NONE, id=type) flick("shield_raise", src) playsound(src, 'sound/mecha/mech_shield_raise.ogg', 50, FALSE) icon_state = "shield" @@ -256,7 +256,7 @@ own integrity back to max. Shield is automatically dropped if we run out of powe */ /obj/durand_shield/proc/make_invisible() if(!chassis.defense_mode) - invisibility = INVISIBILITY_MAXIMUM + RemoveInvisibility(type) /obj/durand_shield/proc/resetdir(datum/source, olddir, newdir) SIGNAL_HANDLER diff --git a/code/modules/vehicles/mecha/combat/gygax.dm b/code/modules/vehicles/mecha/combat/gygax.dm index 204739cd7fc0f..82fd77f22890d 100644 --- a/code/modules/vehicles/mecha/combat/gygax.dm +++ b/code/modules/vehicles/mecha/combat/gygax.dm @@ -22,6 +22,8 @@ ) step_energy_drain = 4 can_use_overclock = TRUE + overclock_safety_available = TRUE + overclock_safety = TRUE /datum/armor/mecha_gygax melee = 25 @@ -45,7 +47,7 @@ force = 30 accesses = list(ACCESS_SYNDICATE) wreckage = /obj/structure/mecha_wreckage/gygax/dark - mecha_flags = ID_LOCK_ON | CANSTRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE + mecha_flags = ID_LOCK_ON | CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE max_equip_by_category = list( MECHA_L_ARM = 1, MECHA_R_ARM = 1, diff --git a/code/modules/vehicles/mecha/combat/honker.dm b/code/modules/vehicles/mecha/combat/honker.dm index dddcb7e82252f..a7c9f018d2869 100644 --- a/code/modules/vehicles/mecha/combat/honker.dm +++ b/code/modules/vehicles/mecha/combat/honker.dm @@ -13,7 +13,7 @@ exit_delay = 40 accesses = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) wreckage = /obj/structure/mecha_wreckage/honker - mecha_flags = CANSTRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE + mecha_flags = CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE mech_type = EXOSUIT_MODULE_HONK max_equip_by_category = list( MECHA_L_ARM = 1, @@ -47,7 +47,7 @@ max_temperature = 35000 accesses = list(ACCESS_SYNDICATE) wreckage = /obj/structure/mecha_wreckage/honker/dark - mecha_flags = ID_LOCK_ON | CANSTRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE + mecha_flags = ID_LOCK_ON | CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE max_equip_by_category = list( MECHA_L_ARM = 1, MECHA_R_ARM = 1, diff --git a/code/modules/vehicles/mecha/combat/marauder.dm b/code/modules/vehicles/mecha/combat/marauder.dm index 79b0956899430..750223a85d7ad 100644 --- a/code/modules/vehicles/mecha/combat/marauder.dm +++ b/code/modules/vehicles/mecha/combat/marauder.dm @@ -12,7 +12,7 @@ resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF accesses = list(ACCESS_CENT_SPECOPS) wreckage = /obj/structure/mecha_wreckage/marauder - mecha_flags = CANSTRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE + mecha_flags = CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE mech_type = EXOSUIT_MODULE_MARAUDER force = 45 max_equip_by_category = list( @@ -61,7 +61,7 @@ /datum/action/vehicle/sealed/mecha/mech_smoke/Trigger(trigger_flags) if(!owner || !chassis || !(owner in chassis.occupants)) return - if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_SMOKE) && chassis.smoke_charges>0) + if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_SMOKE) && chassis.smoke_charges>0) chassis.smoke_system.start() chassis.smoke_charges-- TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_SMOKE, chassis.smoke_cooldown) @@ -117,7 +117,7 @@ base_icon_state = "mauler" accesses = list(ACCESS_SYNDICATE) wreckage = /obj/structure/mecha_wreckage/mauler - mecha_flags = ID_LOCK_ON | CANSTRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE + mecha_flags = ID_LOCK_ON | CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE max_equip_by_category = list( MECHA_L_ARM = 1, MECHA_R_ARM = 1, diff --git a/code/modules/vehicles/mecha/combat/reticence.dm b/code/modules/vehicles/mecha/combat/reticence.dm index bff5ecbd97bf4..5e400b12d9788 100644 --- a/code/modules/vehicles/mecha/combat/reticence.dm +++ b/code/modules/vehicles/mecha/combat/reticence.dm @@ -3,16 +3,16 @@ name = "\improper reticence" icon_state = "reticence" base_icon_state = "reticence" - movedelay = 2 - max_integrity = 100 + movedelay = 1 + max_integrity = 120 armor_type = /datum/armor/mecha_reticence max_temperature = 15000 force = 30 - destruction_sleep_duration = 40 + destruction_sleep_duration = 1 exit_delay = 40 wreckage = /obj/structure/mecha_wreckage/reticence accesses = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) - mecha_flags = CANSTRAFE | IS_ENCLOSED | HAS_LIGHTS | QUIET_STEPS | QUIET_TURNS | MMI_COMPATIBLE + mecha_flags = CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS | QUIET_STEPS | QUIET_TURNS | MMI_COMPATIBLE mech_type = EXOSUIT_MODULE_RETICENCE max_equip_by_category = list( MECHA_L_ARM = 1, @@ -25,10 +25,10 @@ color = "#87878715" /datum/armor/mecha_reticence - melee = 25 - bullet = 20 - laser = 30 - energy = 15 + melee = 40 + bullet = 40 + laser = 50 + energy = 20 fire = 100 acid = 100 @@ -36,13 +36,13 @@ equip_by_category = list( MECHA_L_ARM = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced, MECHA_R_ARM = /obj/item/mecha_parts/mecha_equipment/rcd, - MECHA_UTILITY = list(), - MECHA_POWER = list(), + MECHA_UTILITY = list(/obj/item/mecha_parts/mecha_equipment/radio, /obj/item/mecha_parts/mecha_equipment/air_tank/full, /obj/item/mecha_parts/mecha_equipment/thrusters/ion), + MECHA_POWER = /obj/item/mecha_parts/mecha_equipment/generator, MECHA_ARMOR = list(), ) /obj/vehicle/sealed/mecha/reticence/loaded/populate_parts() - cell = new /obj/item/stock_parts/cell/hyper(src) + cell = new /obj/item/stock_parts/cell/bluespace(src) scanmod = new /obj/item/stock_parts/scanning_module/phasic(src) capacitor = new /obj/item/stock_parts/capacitor/super(src) servo = new /obj/item/stock_parts/servo/pico(src) diff --git a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm index dae449d8388b4..237a0d971b0cb 100644 --- a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm +++ b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm @@ -19,7 +19,7 @@ base_icon_state = "savannah_ivanov" icon_state = "savannah_ivanov_0_0" //does not include mmi compatibility - mecha_flags = CANSTRAFE | IS_ENCLOSED | HAS_LIGHTS + mecha_flags = CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS mech_type = EXOSUIT_MODULE_SAVANNAH movedelay = 3 max_integrity = 450 //really tanky, like damn @@ -86,7 +86,7 @@ if(chassis.phasing) to_chat(owner, span_warning("You're already airborne!")) return - if(TIMER_COOLDOWN_CHECK(chassis, COOLDOWN_MECHA_SKYFALL)) + if(TIMER_COOLDOWN_RUNNING(chassis, COOLDOWN_MECHA_SKYFALL)) var/timeleft = S_TIMER_COOLDOWN_TIMELEFT(chassis, COOLDOWN_MECHA_SKYFALL) to_chat(owner, span_warning("You need to wait [DisplayTimeText(timeleft, 1)] before attempting to Skyfall.")) return @@ -147,7 +147,6 @@ chassis.movedelay = 1 chassis.density = FALSE chassis.layer = ABOVE_ALL_MOB_LAYER - SET_PLANE(chassis, GAME_PLANE_UPPER_FOV_HIDDEN, launch_turf) animate(chassis, alpha = 0, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) animate(chassis, pixel_z = 400, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) //Animate our rising mech (just like pods hehe) addtimer(CALLBACK(src, PROC_REF(begin_landing)), 2 SECONDS) @@ -212,7 +211,7 @@ to_chat(crushed_victim, span_userdanger("[chassis] crashes down on you from above!")) if(crushed_victim.stat != CONSCIOUS) crushed_victim.investigate_log("has been gibbed by a falling Savannah Ivanov mech.", INVESTIGATE_DEATHS) - crushed_victim.gib(FALSE, FALSE, FALSE) + crushed_victim.gib(DROP_ALL_REMAINS) continue crushed_victim.adjustBruteLoss(80) @@ -254,7 +253,7 @@ /datum/action/vehicle/sealed/mecha/ivanov_strike/Trigger(trigger_flags) if(!owner || !chassis || !(owner in chassis.occupants)) return - if(TIMER_COOLDOWN_CHECK(chassis, COOLDOWN_MECHA_MISSILE_STRIKE)) + if(TIMER_COOLDOWN_RUNNING(chassis, COOLDOWN_MECHA_MISSILE_STRIKE)) var/timeleft = S_TIMER_COOLDOWN_TIMELEFT(chassis, COOLDOWN_MECHA_MISSILE_STRIKE) to_chat(owner, span_warning("You need to wait [DisplayTimeText(timeleft, 1)] before firing another Ivanov Strike.")) return @@ -292,7 +291,7 @@ /** * ## end_missile_targeting * - * Called by the ivanov strike datum action or other actions that would end targetting + * Called by the ivanov strike datum action or other actions that would end targeting * Unhooks signals into clicking to call drop_missile plus other flavor like the overlay */ /datum/action/vehicle/sealed/mecha/ivanov_strike/proc/end_missile_targeting() diff --git a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm index f9ee84ba4b893..d650ce66f2ead 100644 --- a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm +++ b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm @@ -107,7 +107,7 @@ if(get_integrity() <= 1) to_chat(chassis.occupants, span_warning("Error -- Equipment critically damaged.")) return FALSE - if(TIMER_COOLDOWN_CHECK(chassis, COOLDOWN_MECHA_EQUIPMENT(type))) + if(TIMER_COOLDOWN_RUNNING(chassis, COOLDOWN_MECHA_EQUIPMENT(type))) return FALSE return TRUE @@ -121,7 +121,7 @@ * Cooldown proc variant for using do_afters between activations instead of timers * Example of usage is mech drills, rcds * arguments: - * * target: targetted atom for action activation + * * target: targeted atom for action activation * * user: occupant to display do after for * * interaction_key: interaction key to pass to [/proc/do_after] */ @@ -219,7 +219,7 @@ /obj/item/mecha_parts/mecha_equipment/proc/set_active(active) src.active = active -/obj/item/mecha_parts/mecha_equipment/log_message(message, message_type=LOG_GAME, color=null, log_globally) +/obj/item/mecha_parts/mecha_equipment/log_message(message, message_type=LOG_GAME, color=null, log_globally, list/data) if(chassis) return chassis.log_message("ATTACHMENT: [src] [message]", message_type, color) return ..() diff --git a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm index 3062d9923bc06..f00444ae598b0 100644 --- a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm +++ b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm @@ -107,8 +107,8 @@ "auto_pressurize_on_seal" = auto_pressurize_on_seal, "port_connected" = internal_tank?.connected_port ? TRUE : FALSE, "tank_release_pressure" = round(internal_tank.release_pressure), - "tank_release_pressure_min" = internal_tank.can_min_release_pressure, - "tank_release_pressure_max" = internal_tank.can_max_release_pressure, + "tank_release_pressure_min" = CAN_MIN_RELEASE_PRESSURE, + "tank_release_pressure_max" = CAN_MAX_RELEASE_PRESSURE, "tank_pump_active" = tank_pump_active, "tank_pump_direction" = tank_pump_direction, "tank_pump_pressure" = round(tank_pump_pressure), @@ -122,7 +122,7 @@ switch(action) if("set_cabin_pressure") var/new_pressure = text2num(params["new_pressure"]) - internal_tank.release_pressure = clamp(round(new_pressure), internal_tank.can_min_release_pressure, internal_tank.can_max_release_pressure) + internal_tank.release_pressure = clamp(round(new_pressure), CAN_MIN_RELEASE_PRESSURE, CAN_MAX_RELEASE_PRESSURE) return TRUE if("toggle_port") if(internal_tank.connected_port) diff --git a/code/modules/vehicles/mecha/equipment/tools/medical_tools.dm b/code/modules/vehicles/mecha/equipment/tools/medical_tools.dm index ca3b4f2f47945..fd138bbeaf8f9 100644 --- a/code/modules/vehicles/mecha/equipment/tools/medical_tools.dm +++ b/code/modules/vehicles/mecha/equipment/tools/medical_tools.dm @@ -167,7 +167,6 @@ Respiratory Damage: [patient.getOxyLoss()]%
    Toxin Content: [patient.getToxLoss()]%
    Burn Severity: [patient.getFireLoss()]%
    - [span_danger("[patient.getCloneLoss() ? "Subject appears to have cellular damage." : ""]")]
    [span_danger("[patient.get_organ_loss(ORGAN_SLOT_BRAIN) ? "Significant brain damage detected." : ""]")]
    [span_danger("[length(patient.get_traumas()) ? "Brain Traumas detected." : ""]")]
    "} @@ -198,7 +197,7 @@ log_message("Injecting [patient] with [to_inject] units of [R.name].", LOG_MECHA) for(var/driver in chassis.return_drivers()) log_combat(driver, patient, "injected", "[name] ([R] - [to_inject] units)") - SG.reagents.trans_id_to(patient,R.type,to_inject) + SG.reagents.trans_to(patient, to_inject, target_id = R.type) /obj/item/mecha_parts/mecha_equipment/medical/sleeper/container_resist_act(mob/living/user) go_out() diff --git a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm index 6aae4adaaeed7..c7c082a20e887 100644 --- a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm +++ b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm @@ -29,6 +29,8 @@ butcher_sound = null, \ disabled = TRUE, \ ) + ADD_TRAIT(src, TRAIT_INSTANTLY_PROCESSES_BOULDERS, INNATE_TRAIT) + ADD_TRAIT(src, TRAIT_BOULDER_BREAKER, INNATE_TRAIT) /obj/item/mecha_parts/mecha_equipment/drill/action(mob/source, atom/target, list/modifiers) // Check if we can even use the equipment to begin with. @@ -66,8 +68,12 @@ playsound(src,'sound/weapons/drill.ogg',40,TRUE) else if(isobj(target)) var/obj/O = target - O.take_damage(15, BRUTE, 0, FALSE, get_dir(chassis, target)) - playsound(src,'sound/weapons/drill.ogg',40,TRUE) + if(istype(O, /obj/item/boulder)) + var/obj/item/boulder/nu_boulder = O + nu_boulder.manual_process(src, source) + else + O.take_damage(15, BRUTE, 0, FALSE, get_dir(chassis, target)) + playsound(src,'sound/weapons/drill.ogg', 40, TRUE) // If we caused a qdel drilling the target, we can stop drilling them. // Prevents starting a do_after on a qdeleted target. @@ -122,7 +128,7 @@ SEND_SIGNAL(src, COMSIG_MECHA_DRILL_MOB, chassis, target) else target.investigate_log("has been gibbed by [src] (attached to [chassis]).", INVESTIGATE_DEATHS) - target.gib() + target.gib(DROP_ALL_REMAINS) else //drill makes a hole var/obj/item/bodypart/target_part = target.get_bodypart(target.get_random_valid_zone(BODY_ZONE_CHEST)) @@ -154,10 +160,11 @@ name = "exosuit mining scanner" desc = "Equipment for working exosuits. It will automatically check surrounding rock for useful minerals." icon_state = "mecha_analyzer" - equip_cooldown = 15 + equip_cooldown = 1.5 SECONDS equipment_slot = MECHA_UTILITY mech_flags = EXOSUIT_MODULE_WORKING var/scanning_time = 0 + COOLDOWN_DECLARE(area_scan_cooldown) /obj/item/mecha_parts/mecha_equipment/mining_scanner/Initialize(mapload) . = ..() @@ -174,7 +181,25 @@ if(!LAZYLEN(chassis.occupants)) return scanning_time = world.time + equip_cooldown - mineral_scan_pulse(get_turf(src)) + mineral_scan_pulse(get_turf(src), scanner = src) + +/obj/item/mecha_parts/mecha_equipment/mining_scanner/get_snowflake_data() + return list( + "snowflake_id" = MECHA_SNOWFLAKE_ID_ORE_SCANNER, + "cooldown" = COOLDOWN_TIMELEFT(src, area_scan_cooldown), + ) + +/obj/item/mecha_parts/mecha_equipment/mining_scanner/handle_ui_act(action, list/params) + switch(action) + if("area_scan") + if(!COOLDOWN_FINISHED(src, area_scan_cooldown)) + return FALSE + COOLDOWN_START(src, area_scan_cooldown, 15 SECONDS) + for(var/mob/living/carbon/human/driver in chassis.return_drivers()) + for(var/obj/structure/ore_vent/vent as anything in range(5, chassis)) + if(istype(vent, /obj/structure/ore_vent)) + vent.scan_and_confirm(driver, TRUE) + return TRUE #undef DRILL_BASIC #undef DRILL_HARDENED diff --git a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm index b2a69357a82c2..49b9dc466cd49 100644 --- a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm +++ b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm @@ -17,31 +17,30 @@ var/killer_clamp = FALSE ///How much base damage this clamp does var/clamp_damage = 20 - ///Var for the chassis we are attached to, needed to access ripley contents and such - var/obj/vehicle/sealed/mecha/ripley/cargo_holder ///Audio for using the hydraulic clamp var/clampsound = 'sound/mecha/hydraulic.ogg' + ///Chassis but typed for the cargo_hold var + var/obj/vehicle/sealed/mecha/ripley/workmech /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/attach(obj/vehicle/sealed/mecha/new_mecha) . = ..() - if(istype(chassis, /obj/vehicle/sealed/mecha/ripley)) - cargo_holder = chassis + workmech = chassis ADD_TRAIT(chassis, TRAIT_OREBOX_FUNCTIONAL, TRAIT_MECH_EQUIPMENT(type)) /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/detach(atom/moveto) REMOVE_TRAIT(chassis, TRAIT_OREBOX_FUNCTIONAL, TRAIT_MECH_EQUIPMENT(type)) - cargo_holder = null + workmech = null return ..() /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/action(mob/living/source, atom/target, list/modifiers) if(!action_checks(target)) return - if(!cargo_holder) - return + if(!workmech.cargo_hold) + CRASH("Mech [chassis] has a clamp device, but no internal storage. This should be impossible.") if(ismecha(target)) var/obj/vehicle/sealed/mecha/M = target var/have_ammo - for(var/obj/item/mecha_ammo/box in cargo_holder.cargo) + for(var/obj/item/mecha_ammo/box in workmech.cargo_hold.contents) if(istype(box, /obj/item/mecha_ammo) && box.rounds) have_ammo = TRUE if(M.ammo_resupply(box, source, TRUE)) @@ -66,7 +65,7 @@ if(clamptarget.anchored) to_chat(source, "[icon2html(src, source)][span_warning("[target] is firmly secured!")]") return - if(LAZYLEN(cargo_holder.cargo) >= cargo_holder.cargo_capacity) + if(workmech.cargo_hold.contents.len >= workmech.cargo_hold.cargo_capacity) to_chat(source, "[icon2html(src, source)][span_warning("Not enough room in cargo compartment!")]") return playsound(chassis, clampsound, 50, FALSE, -6) @@ -75,13 +74,12 @@ if(!do_after_cooldown(target, source)) clamptarget.set_anchored(initial(clamptarget.anchored)) return - LAZYADD(cargo_holder.cargo, clamptarget) - clamptarget.forceMove(chassis) + clamptarget.forceMove(workmech.cargo_hold) clamptarget.set_anchored(FALSE) - if(!cargo_holder.ore_box && istype(clamptarget, /obj/structure/ore_box)) - cargo_holder.ore_box = clamptarget + if(!chassis.ore_box && istype(clamptarget, /obj/structure/ore_box)) + chassis.ore_box = clamptarget to_chat(source, "[icon2html(src, source)][span_notice("[target] successfully loaded.")]") - log_message("Loaded [clamptarget]. Cargo compartment capacity: [cargo_holder.cargo_capacity - LAZYLEN(cargo_holder.cargo)]", LOG_MECHA) + log_message("Loaded [clamptarget]. Cargo compartment capacity: [workmech.cargo_hold.cargo_capacity - workmech.cargo_hold.contents.len]", LOG_MECHA) else if(isliving(target)) var/mob/living/M = target @@ -172,7 +170,7 @@ var/datum/reagents/water_reagents = new /datum/reagents(required_amount/8) //required_amount/8, because the water usage is split between eight sprays. As of this comment, required_amount/8 = 10u each. water.reagents = water_reagents water_reagents.my_atom = water - reagents.trans_to(water, required_amount/8) + reagents.trans_to(water, required_amount / 8) water.move_at(get_step(chassis, get_dir(targetturf, chassis)), 2, 4) //Target is the tile opposite of the mech as the starting turf. playsound(chassis, 'sound/effects/extinguish.ogg', 75, TRUE, -3) @@ -216,130 +214,103 @@ attempt_refill(usr) return TRUE -#define MODE_DECONSTRUCT 0 -#define MODE_WALL 1 -#define MODE_AIRLOCK 2 - /obj/item/mecha_parts/mecha_equipment/rcd name = "mounted RCD" desc = "An exosuit-mounted Rapid Construction Device." icon_state = "mecha_rcd" - equip_cooldown = 10 - energy_drain = 250 + equip_cooldown = 0 // internal RCD already handles it + energy_drain = 0 // internal RCD handles power consumption based on matter use range = MECHA_MELEE|MECHA_RANGED item_flags = NO_MAT_REDEMPTION - ///determines what we'll so when clicking on a turf - var/mode = MODE_DECONSTRUCT + ///Maximum range the RCD can construct at. + var/rcd_range = 3 + ///Whether or not to deconstruct instead. + var/deconstruct_active = FALSE + ///The type of internal RCD this equipment uses. + var/rcd_type = /obj/item/construction/rcd/exosuit + ///The internal RCD item used by this equipment. + var/obj/item/construction/rcd/internal_rcd /obj/item/mecha_parts/mecha_equipment/rcd/Initialize(mapload) . = ..() + internal_rcd = new rcd_type(src) GLOB.rcd_list += src /obj/item/mecha_parts/mecha_equipment/rcd/Destroy() GLOB.rcd_list -= src + qdel(internal_rcd) return ..() /obj/item/mecha_parts/mecha_equipment/rcd/get_snowflake_data() return list( - "snowflake_id" = MECHA_SNOWFLAKE_ID_MODE, - "mode" = get_mode_name(), - "mode_label" = "RCD control", + "snowflake_id" = MECHA_SNOWFLAKE_ID_RCD, + "scan_ready" = COOLDOWN_FINISHED(internal_rcd, destructive_scan_cooldown), + "deconstructing" = deconstruct_active, + "mode" = internal_rcd.design_title, ) -/// fetches the mode name to display in the UI -/obj/item/mecha_parts/mecha_equipment/rcd/proc/get_mode_name() - switch(mode) - if(MODE_DECONSTRUCT) - return "Deconstruct" - if(MODE_WALL) - return "Build wall" - if(MODE_AIRLOCK) - return "Build Airlock" - else - return "Someone didnt set this" +/// Set the RCD's owner when attaching and detaching it +/obj/item/mecha_parts/mecha_equipment/rcd/attach(obj/vehicle/sealed/mecha/new_mecha, attach_right) + internal_rcd.owner = new_mecha + return ..() + +/obj/item/mecha_parts/mecha_equipment/rcd/detach(atom/moveto) + internal_rcd.owner = null + return ..() /obj/item/mecha_parts/mecha_equipment/rcd/handle_ui_act(action, list/params) - if(action == "change_mode") - mode++ - if(mode > MODE_AIRLOCK) - mode = MODE_DECONSTRUCT - switch(mode) - if(MODE_DECONSTRUCT) - to_chat(chassis.occupants, "[icon2html(src, chassis.occupants)][span_notice("Switched RCD to Deconstruct.")]") - energy_drain = initial(energy_drain) - if(MODE_WALL) - to_chat(chassis.occupants, "[icon2html(src, chassis.occupants)][span_notice("Switched RCD to Construct Walls and Flooring.")]") - energy_drain = 2*initial(energy_drain) - if(MODE_AIRLOCK) - to_chat(chassis.occupants, "[icon2html(src, chassis.occupants)][span_notice("Switched RCD to Construct Airlock.")]") - energy_drain = 2*initial(energy_drain) - return TRUE + switch(action) + if("rcd_scan") + if(!COOLDOWN_FINISHED(internal_rcd, destructive_scan_cooldown)) + return FALSE + rcd_scan(internal_rcd) + COOLDOWN_START(internal_rcd, destructive_scan_cooldown, RCD_DESTRUCTIVE_SCAN_COOLDOWN) + return TRUE + if("toggle_deconstruct") + deconstruct_active = !deconstruct_active + return TRUE + if("change_mode") + for(var/mob/driver as anything in chassis.return_drivers()) + internal_rcd.ui_interact(driver) + return TRUE /obj/item/mecha_parts/mecha_equipment/rcd/action(mob/source, atom/target, list/modifiers) - if(!isturf(target) && !istype(target, /obj/machinery/door/airlock)) - target = get_turf(target) - if(!action_checks(target) || !(target in view(3, chassis)) || istype(target, /turf/open/space/transit)) + if(!action_checks(target)) return - playsound(chassis, 'sound/machines/click.ogg', 50, TRUE) - - switch(mode) - if(MODE_DECONSTRUCT) - to_chat(source, "[icon2html(src, source)][span_notice("Deconstructing [target]...")]") - if(iswallturf(target)) - var/turf/closed/wall/wall_turf = target - if(!do_after_cooldown(wall_turf, source)) - return - wall_turf.ScrapeAway() - else if(isfloorturf(target)) - var/turf/open/floor/floor_turf = target - if(!do_after_cooldown(floor_turf, source)) - return - floor_turf.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) - else if (istype(target, /obj/machinery/door/airlock)) - if(!do_after_cooldown(target, source)) - return - qdel(target) - if(MODE_WALL) - if(isfloorturf(target)) - var/turf/open/floor/floor_turf = target - to_chat(source, "[icon2html(src, source)][span_notice("Building Wall...")]") - if(!do_after_cooldown(floor_turf, source)) - return - floor_turf.PlaceOnTop(/turf/closed/wall) - else if(isopenturf(target)) - var/turf/open/open_turf = target - to_chat(source, "[icon2html(src, source)][span_notice("Building Floor...")]") - if(!do_after_cooldown(open_turf, source)) - return - open_turf.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) - if(MODE_AIRLOCK) - if(isfloorturf(target)) - to_chat(source, "[icon2html(src, source)][span_notice("Building Airlock...")]") - if(!do_after_cooldown(target, source)) - return - var/obj/machinery/door/airlock/airlock_door = new /obj/machinery/door/airlock(target) - airlock_door.autoclose = TRUE - playsound(target, 'sound/effects/sparks2.ogg', 50, TRUE) - chassis.spark_system.start() - playsound(target, 'sound/items/deconstruct.ogg', 50, TRUE) - return ..() + if(get_dist(chassis, target) > rcd_range) + balloon_alert(source, "out of range!") + return + if(!internal_rcd) // if it somehow went missing + internal_rcd = new rcd_type(src) + stack_trace("Exosuit-mounted RCD had no internal RCD!") + ..() // do this now because the do_after can take a while + var/construction_mode = internal_rcd.mode + if(deconstruct_active) // deconstruct isn't in the RCD menu so switch it to deconstruct mode and set it back when it's done + internal_rcd.mode = RCD_DECONSTRUCT + internal_rcd.rcd_create(target, source) + internal_rcd.mode = construction_mode + return TRUE -#undef MODE_DECONSTRUCT -#undef MODE_WALL -#undef MODE_AIRLOCK +/obj/item/mecha_parts/mecha_equipment/rcd/attackby(obj/item/attacking_item, mob/user, params) + if(istype(attacking_item, /obj/item/rcd_upgrade)) + internal_rcd.install_upgrade(attacking_item, user) + return + return ..() //Dunno where else to put this so shrug /obj/item/mecha_parts/mecha_equipment/ripleyupgrade name = "Ripley MK-II Conversion Kit" - desc = "A pressurized canopy attachment kit for an Autonomous Power Loader Unit \"Ripley\" MK-I mecha, to convert it to the slower, but space-worthy MK-II design. This kit cannot be removed, once applied." + desc = "A pressurized canopy attachment kit for an Autonomous Power Loader Unit \"Ripley\" MK-I exosuit, to convert it to the slower, but space-worthy MK-II design. This kit cannot be removed, once applied." icon_state = "ripleyupgrade" mech_flags = EXOSUIT_MODULE_RIPLEY + var/result = /obj/vehicle/sealed/mecha/ripley/mk2 /obj/item/mecha_parts/mecha_equipment/ripleyupgrade/can_attach(obj/vehicle/sealed/mecha/ripley/mecha, attach_right = FALSE, mob/user) if(mecha.type != /obj/vehicle/sealed/mecha/ripley) to_chat(user, span_warning("This conversion kit can only be applied to APLU MK-I models.")) return FALSE - if(LAZYLEN(mecha.cargo)) + var/obj/vehicle/sealed/mecha/ripley/workmech = mecha + if(LAZYLEN(workmech.cargo_hold)) to_chat(user, span_warning("[mecha]'s cargo hold must be empty before this conversion kit can be applied.")) return FALSE if(!(mecha.mecha_flags & PANEL_OPEN)) //non-removable upgrade, so lets make sure the pilot or owner has their say. @@ -354,43 +325,59 @@ return TRUE /obj/item/mecha_parts/mecha_equipment/ripleyupgrade/attach(obj/vehicle/sealed/mecha/markone, attach_right = FALSE) - var/obj/vehicle/sealed/mecha/ripley/mk2/marktwo = new (get_turf(markone),1) - if(!marktwo) + var/obj/vehicle/sealed/mecha/newmech = new result(get_turf(markone),1) + if(!newmech) return - QDEL_NULL(marktwo.cell) + QDEL_NULL(newmech.cell) if (markone.cell) - marktwo.cell = markone.cell - markone.cell.forceMove(marktwo) + newmech.cell = markone.cell + markone.cell.forceMove(newmech) markone.cell = null - QDEL_NULL(marktwo.scanmod) + QDEL_NULL(newmech.scanmod) if (markone.scanmod) - marktwo.scanmod = markone.scanmod - markone.scanmod.forceMove(marktwo) + newmech.scanmod = markone.scanmod + markone.scanmod.forceMove(newmech) markone.scanmod = null - QDEL_NULL(marktwo.capacitor) + QDEL_NULL(newmech.capacitor) if (markone.capacitor) - marktwo.capacitor = markone.capacitor - markone.capacitor.forceMove(marktwo) + newmech.capacitor = markone.capacitor + markone.capacitor.forceMove(newmech) markone.capacitor = null - QDEL_NULL(marktwo.servo) + QDEL_NULL(newmech.servo) if (markone.servo) - marktwo.servo = markone.servo - markone.servo.forceMove(marktwo) + newmech.servo = markone.servo + markone.servo.forceMove(newmech) markone.servo = null - marktwo.update_part_values() + newmech.update_part_values() for(var/obj/item/mecha_parts/mecha_equipment/equipment in markone.flat_equipment) //Move the equipment over... if(istype(equipment, /obj/item/mecha_parts/mecha_equipment/ejector)) - continue //the MK2 already has one. + continue //the new mech already has one. var/righthandgun = markone.equip_by_category[MECHA_R_ARM] == equipment - equipment.detach(marktwo) - equipment.attach(marktwo, righthandgun) - marktwo.dna_lock = markone.dna_lock - marktwo.mecha_flags = markone.mecha_flags - marktwo.strafe = markone.strafe + equipment.detach(newmech) + equipment.attach(newmech, righthandgun) + newmech.dna_lock = markone.dna_lock + newmech.mecha_flags |= markone.mecha_flags & ~initial(markone.mecha_flags) // transfer any non-inherent flags like PANEL_OPEN and LIGHTS_ON + newmech.set_light_on(newmech.mecha_flags & LIGHTS_ON) // in case the lights were on + newmech.strafe = markone.strafe //Integ set to the same percentage integ as the old mecha, rounded to be whole number - marktwo.update_integrity(round((markone.get_integrity() / markone.max_integrity) * marktwo.get_integrity())) + newmech.update_integrity(round((markone.get_integrity() / markone.max_integrity) * newmech.get_integrity())) if(markone.name != initial(markone.name)) - marktwo.name = markone.name + newmech.name = markone.name markone.wreckage = FALSE + if(HAS_TRAIT(markone, TRAIT_MECHA_CREATED_NORMALLY)) + ADD_TRAIT(newmech, TRAIT_MECHA_CREATED_NORMALLY, newmech) qdel(markone) - playsound(get_turf(marktwo),'sound/items/ratchet.ogg',50,TRUE) + playsound(get_turf(newmech),'sound/items/ratchet.ogg',50,TRUE) + +/obj/item/mecha_parts/mecha_equipment/ripleyupgrade/paddy + name = "Paddy Conversion Kit" + desc = "A hardpoint modification kit for an Autonomous Power Loader Unit \"Ripley\" MK-I exosuit, to convert it to the Paddy lightweight security design. This kit cannot be removed, once applied." + icon_state = "paddyupgrade" + mech_flags = EXOSUIT_MODULE_RIPLEY + result = /obj/vehicle/sealed/mecha/ripley/paddy + +/obj/item/mecha_parts/mecha_equipment/ripleyupgrade/paddy/can_attach(obj/vehicle/sealed/mecha/ripley/mecha, attach_right = FALSE, mob/user) + if(mecha.equip_by_category[MECHA_L_ARM] || mecha.equip_by_category[MECHA_R_ARM]) //Paddys can't use RIPLEY-type equipment + to_chat(user, span_warning("This kit cannot be applied with hardpoint equipment attached.")) + return FALSE + return ..() diff --git a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm index fe97c5f2b89ae..335e8bc5a3e12 100644 --- a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm +++ b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm @@ -78,7 +78,7 @@ //Base energy weapon type /obj/item/mecha_parts/mecha_equipment/weapon/energy name = "general energy weapon" - firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/red /obj/item/mecha_parts/mecha_equipment/weapon/energy/laser equip_cooldown = 8 @@ -100,6 +100,7 @@ variance = 25 projectiles_per_shot = 5 fire_sound = 'sound/weapons/taser2.ogg' + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/blue /obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy equip_cooldown = 15 @@ -161,7 +162,7 @@ icon_state = "mecha_kineticgun" energy_drain = 30 projectile = /obj/projectile/kinetic/mech - fire_sound = 'sound/weapons/kenetic_accel.ogg' + fire_sound = 'sound/weapons/kinetic_accel.ogg' harmful = TRUE mech_flags = EXOSUIT_MODULE_COMBAT | EXOSUIT_MODULE_WORKING @@ -173,6 +174,7 @@ equip_cooldown = 8 projectile = /obj/projectile/energy/electrode fire_sound = 'sound/weapons/taser.ogg' + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect /obj/item/mecha_parts/mecha_equipment/weapon/honker @@ -538,3 +540,76 @@ equip_cooldown = 60 det_time = 20 mech_flags = EXOSUIT_MODULE_HONK + +///long claw of the law +/obj/item/mecha_parts/mecha_equipment/weapon/paddy_claw + name = "hydraulic claw" + desc = "A modified hydraulic clamp, for use exclusively with the Paddy exosuit. Non-lethally apprehends suspects." + icon_state = "paddy_claw" + equip_cooldown = 15 + energy_drain = 10 + tool_behaviour = TOOL_RETRACTOR + range = MECHA_MELEE + toolspeed = 0.8 + mech_flags = EXOSUIT_MODULE_PADDY + ///Chassis but typed for the cargo_hold var + var/obj/vehicle/sealed/mecha/ripley/secmech + ///Audio for using the hydraulic clamp + var/clampsound = 'sound/mecha/hydraulic.ogg' + ///Var for the cuff type. Basically stole how cuffing works from secbots + var/cuff_type = /obj/item/restraints/handcuffs/cable/zipties/used + ///Var for autocuff, can be toggled in the mech interface. + var/autocuff = TRUE + + +/obj/item/mecha_parts/mecha_equipment/weapon/paddy_claw/attach(obj/vehicle/sealed/mecha/new_mecha) + . = ..() + secmech = chassis + +/obj/item/mecha_parts/mecha_equipment/weapon/paddy_claw/detach(atom/moveto) + secmech = null + return ..() + +/obj/item/mecha_parts/mecha_equipment/weapon/paddy_claw/action(mob/living/source, atom/target, list/modifiers) + if(!secmech.cargo_hold) //We did try + CRASH("Mech [chassis] has a claw device, but no internal storage. This should be impossible.") + if(ismob(target)) + var/mob/living/mobtarget = target + if(mobtarget.move_resist == MOVE_FORCE_OVERPOWERING) //No megafauna or bolted AIs, please. + to_chat(source, "[span_warning("[src] is unable to lift [mobtarget].")]") + return + if(secmech.cargo_hold.contents.len >= secmech.cargo_hold.cargo_capacity) + to_chat(source, "[icon2html(src, source)][span_warning("Not enough room in cargo compartment!")]") + return + + playsound(chassis, clampsound, 50, FALSE, -6) + mobtarget.visible_message(span_notice("[chassis] lifts [mobtarget] into its internal holding cell."),span_userdanger("[chassis] grips you with [src] and prepares to load you into [secmech.cargo_hold]!")) + if(!do_after_cooldown(mobtarget, source)) + return + mobtarget.forceMove(secmech.cargo_hold) + log_message("Loaded [mobtarget]. Cargo compartment capacity: [secmech.cargo_hold.cargo_capacity - secmech.cargo_hold.contents.len]", LOG_MECHA) + to_chat(source, "[icon2html(src, source)][span_notice("[mobtarget] successfully loaded.")]") + to_chat(mobtarget, "[span_warning("You have been moved into [secmech.cargo_hold]. You can attempt to resist out if you wish.")]") + if(autocuff && iscarbon(target)) + var/mob/living/carbon/carbontarget = target + carbontarget.set_handcuffed(new cuff_type(carbontarget)) + carbontarget.update_handcuffed() + return + + if(!istype(target, /obj/machinery/door)) + return + var/obj/machinery/door/target_door = target + playsound(chassis, clampsound, 50, FALSE, -6) + target_door.try_to_crowbar(src, source) + +/obj/item/mecha_parts/mecha_equipment/weapon/paddy_claw/get_snowflake_data() + return list( + "snowflake_id" = MECHA_SNOWFLAKE_ID_CLAW, + "autocuff" = autocuff, + ) + +/obj/item/mecha_parts/mecha_equipment/weapon/paddy_claw/handle_ui_act(action, list/params) + switch(action) + if("togglecuff") + autocuff = !autocuff + return TRUE diff --git a/code/modules/vehicles/mecha/mech_fabricator.dm b/code/modules/vehicles/mecha/mech_fabricator.dm index 56b3cd2138c5e..c4acded799446 100644 --- a/code/modules/vehicles/mecha/mech_fabricator.dm +++ b/code/modules/vehicles/mecha/mech_fabricator.dm @@ -50,11 +50,7 @@ var/list/datum/design/cached_designs /obj/machinery/mecha_part_fabricator/Initialize(mapload) - rmat = AddComponent( \ - /datum/component/remote_materials, \ - mapload && link_on_init, \ - mat_container_flags = BREAKDOWN_FLAGS_LATHE \ - ) + rmat = AddComponent(/datum/component/remote_materials, mapload && link_on_init) cached_designs = list() RefreshParts() //Recalculating local material sizes if the fab isn't linked return ..() @@ -403,9 +399,6 @@ . = TRUE - add_fingerprint(usr) - usr.set_machine(src) - switch(action) if("build") var/designs = params["designs"] @@ -499,15 +492,5 @@ return FALSE return default_deconstruction_crowbar(I) -/obj/machinery/mecha_part_fabricator/proc/is_insertion_ready(mob/user) - if(panel_open) - to_chat(user, span_warning("You can't load [src] while it's opened!")) - return FALSE - if(being_built) - to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion.")) - return FALSE - - return TRUE - /obj/machinery/mecha_part_fabricator/maint link_on_init = FALSE diff --git a/code/modules/vehicles/mecha/mecha_actions.dm b/code/modules/vehicles/mecha/mecha_actions.dm index f42e89aa1d55f..2b410bd60c775 100644 --- a/code/modules/vehicles/mecha/mecha_actions.dm +++ b/code/modules/vehicles/mecha/mecha_actions.dm @@ -101,7 +101,7 @@ toggle_strafe() /obj/vehicle/sealed/mecha/proc/toggle_strafe() - if(!(mecha_flags & CANSTRAFE)) + if(!(mecha_flags & CAN_STRAFE)) to_chat(occupants, "this mecha doesn't support strafing!") return @@ -152,6 +152,5 @@ if(!owner || !chassis || !(owner in chassis.occupants)) return chassis.toggle_overclock(forced_state) - chassis.balloon_alert(owner, chassis.overclock_mode ? "started overclocking" : "stopped overclocking") button_icon_state = "mech_overload_[chassis.overclock_mode ? "on" : "off"]" build_all_button_icons() diff --git a/code/modules/vehicles/mecha/mecha_ai_interaction.dm b/code/modules/vehicles/mecha/mecha_ai_interaction.dm index 22db07718095e..9ae35d8ff4ba4 100644 --- a/code/modules/vehicles/mecha/mecha_ai_interaction.dm +++ b/code/modules/vehicles/mecha/mecha_ai_interaction.dm @@ -72,7 +72,6 @@ to_chat(occupants, span_danger("You have been forcibly ejected!")) for(var/ejectee in occupants) mob_exit(ejectee, silent = TRUE, randomstep = TRUE, forced = TRUE) //IT IS MINE, NOW. SUCK IT, RD! - AI.can_shunt = FALSE //ONE AI ENTERS. NO AI LEAVES. if(AI_TRANS_FROM_CARD) //Using an AI card to upload to a mech. AI = card.AI diff --git a/code/modules/vehicles/mecha/mecha_construction_paths.dm b/code/modules/vehicles/mecha/mecha_construction_paths.dm index fb572f7d1282e..3287d66c7bf2c 100644 --- a/code/modules/vehicles/mecha/mecha_construction_paths.dm +++ b/code/modules/vehicles/mecha/mecha_construction_paths.dm @@ -33,6 +33,7 @@ var/obj/item/mecha_parts/chassis/parent_chassis = parent mech.CheckParts(parent_chassis.contents) SSblackbox.record_feedback("tally", "mechas_created", 1, mech.name) + ADD_TRAIT(mech, TRAIT_MECHA_CREATED_NORMALLY, mech) QDEL_NULL(parent) // Default proc to generate mech steps. @@ -503,7 +504,7 @@ list( "key" = /obj/item/circuitboard/mecha/honker/targeting, "action" = ITEM_DELETE, - "desc" = "Prank targetting board can be added!", + "desc" = "Prank targeting board can be added!", "forward_message" = "added prank" ), list( diff --git a/code/modules/vehicles/mecha/mecha_control_console.dm b/code/modules/vehicles/mecha/mecha_control_console.dm index 771ed97268bfd..635ec9425b1c8 100644 --- a/code/modules/vehicles/mecha/mecha_control_console.dm +++ b/code/modules/vehicles/mecha/mecha_control_console.dm @@ -29,16 +29,16 @@ name = M.name, integrity = round((M.get_integrity() / M.max_integrity) * 100), charge = M.cell ? round(M.cell.percent()) : null, - airtank = M.enclosed ? M.return_pressure() : null, + airtank = (M.mecha_flags & IS_ENCLOSED) ? M.return_pressure() : null, pilot = M.return_drivers(), location = get_area_name(M, TRUE), emp_recharging = MT.recharging, tracker_ref = REF(MT) ) if(istype(M, /obj/vehicle/sealed/mecha/ripley)) - var/obj/vehicle/sealed/mecha/ripley/RM = M + var/obj/vehicle/sealed/mecha/ripley/workmech = M mech_data += list( - cargo_space = round((LAZYLEN(RM.cargo) / RM.cargo_capacity) * 100) + cargo_space = round(workmech.cargo_hold.contents.len / workmech.cargo_hold.cargo_capacity * 100) ) data["mechs"] += list(mech_data) @@ -76,7 +76,7 @@ /obj/item/mecha_parts/mecha_tracking name = "exosuit tracking beacon" desc = "Device used to transmit exosuit data." - icon = 'icons/obj/device.dmi' + icon = 'icons/obj/devices/new_assemblies.dmi' icon_state = "motion2" w_class = WEIGHT_CLASS_SMALL /// If this beacon allows for AI control. Exists to avoid using istype() on checking @@ -97,12 +97,12 @@ var/answer = {"Name: [chassis.name]
    Integrity: [round((chassis.get_integrity()/chassis.max_integrity * 100), 0.01)]%
    Cell Charge: [isnull(cell_charge) ? "Not Found":"[chassis.cell.percent()]%"]
    - Cabin Pressure: [chassis.enclosed ? "[round(chassis.return_pressure(), 0.01)] kPa" : "Not Sealed"]
    + Cabin Pressure: [(chassis.mecha_flags & IS_ENCLOSED) ? "[round(chassis.return_pressure(), 0.01)] kPa" : "Not Sealed"]
    Pilot: [english_list(chassis.return_drivers(), nothing_text = "None")]
    Location: [get_area_name(chassis, TRUE) || "Unknown"]"} if(istype(chassis, /obj/vehicle/sealed/mecha/ripley)) - var/obj/vehicle/sealed/mecha/ripley/RM = chassis - answer += "
    Used Cargo Space: [round((LAZYLEN(RM.cargo) / RM.cargo_capacity * 100), 0.01)]%" + var/obj/item/mecha_parts/mecha_equipment/ejector/cargo_holder = locate(/obj/item/mecha_parts/mecha_equipment/ejector) in chassis.equip_by_category[MECHA_UTILITY] + answer += "
    Used Cargo Space: [round((cargo_holder.contents.len / cargo_holder.cargo_capacity * 100), 0.01)]%" return answer diff --git a/code/modules/vehicles/mecha/mecha_damage.dm b/code/modules/vehicles/mecha/mecha_damage.dm index 08f294f202f94..8a06aaf298fa5 100644 --- a/code/modules/vehicles/mecha/mecha_damage.dm +++ b/code/modules/vehicles/mecha/mecha_damage.dm @@ -9,7 +9,7 @@ * Pretty simple, adds armor, you can choose against what * ## Internal damage * When taking damage will force you to take some time to repair, encourages improvising in a fight - * Targetting different def zones will damage them to encurage a more strategic approach to fights + * Targeting different def zones will damage them to encurage a more strategic approach to fights * where they target the "dangerous" modules */ diff --git a/code/modules/vehicles/mecha/mecha_defense.dm b/code/modules/vehicles/mecha/mecha_defense.dm index 37fe73f4a2d11..c1207c2677f94 100644 --- a/code/modules/vehicles/mecha/mecha_defense.dm +++ b/code/modules/vehicles/mecha/mecha_defense.dm @@ -9,11 +9,11 @@ * Pretty simple, adds armor, you can choose against what * ## Internal damage * When taking damage will force you to take some time to repair, encourages improvising in a fight - * Targetting different def zones will damage them to encurage a more strategic approach to fights + * Targeting different def zones will damage them to encurage a more strategic approach to fights * where they target the "dangerous" modules */ -/// tries to damage mech equipment depending on damage and where is being targetted +/// tries to damage mech equipment depending on damage and where is being targeted /obj/vehicle/sealed/mecha/proc/try_damage_component(damage, def_zone) if(damage < component_damage_threshold) return @@ -41,6 +41,7 @@ if(damage_taken <= 0 || atom_integrity < 0) return damage_taken + diag_hud_set_mechhealth() spark_system?.start() try_deal_internal_damage(damage_taken) if(damage_taken >= 5 || prob(33)) @@ -114,10 +115,16 @@ return ..() /obj/vehicle/sealed/mecha/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit) //wrapper - if(!enclosed && LAZYLEN(occupants) && !(mecha_flags & SILICON_PILOT) && (hitting_projectile.def_zone == BODY_ZONE_HEAD || hitting_projectile.def_zone == BODY_ZONE_CHEST)) //allows bullets to hit the pilot of open-canopy mechs - for(var/mob/living/hitmob as anything in occupants) - hitmob.bullet_act(hitting_projectile, def_zone, piercing_hit) //If the sides are open, the occupant can be hit - return BULLET_ACT_HIT + //allows bullets to hit the pilot of open-canopy mechs + if(!(mecha_flags & IS_ENCLOSED) \ + && LAZYLEN(occupants) \ + && !(mecha_flags & SILICON_PILOT) \ + && (def_zone == BODY_ZONE_HEAD || def_zone == BODY_ZONE_CHEST)) + var/mob/living/hitmob = pick(occupants) + return hitmob.bullet_act(hitting_projectile, def_zone, piercing_hit) //If the sides are open, the occupant can be hit + + . = ..() + log_message("Hit by projectile. Type: [hitting_projectile]([hitting_projectile.damage_type]).", LOG_MECHA, color="red") // yes we *have* to run the armor calc proc here I love tg projectile code too try_damage_component(run_atom_armor( @@ -126,8 +133,8 @@ damage_flag = hitting_projectile.armor_flag, attack_dir = REVERSE_DIR(hitting_projectile.dir), armour_penetration = hitting_projectile.armour_penetration, - ), hitting_projectile.def_zone) - return ..() + ), def_zone) + /obj/vehicle/sealed/mecha/ex_act(severity, target) log_message("Affected by explosion of severity: [severity].", LOG_MECHA, color="red") @@ -190,7 +197,7 @@ /obj/vehicle/sealed/mecha/fire_act() //Check if we should ignite the pilot of an open-canopy mech . = ..() - if(enclosed || mecha_flags & SILICON_PILOT) + if(mecha_flags & IS_ENCLOSED || mecha_flags & SILICON_PILOT) return for(var/mob/living/cookedalive as anything in occupants) if(cookedalive.fire_stacks < 5) @@ -218,6 +225,10 @@ ammo_resupply(weapon, user) return + if(istype(weapon, /obj/item/rcd_upgrade)) + upgrade_rcd(weapon, user) + return + if(weapon.GetID()) if(!allowed(user)) if(mecha_flags & ID_LOCK_ON) @@ -324,6 +335,7 @@ . = ..() if(.) try_damage_component(., user.zone_selected) + diag_hud_set_mechhealth() /obj/vehicle/sealed/mecha/examine(mob/user) . = ..() @@ -336,21 +348,9 @@ ..() . = TRUE - if(!(mecha_flags & PANEL_OPEN) && LAZYLEN(occupants)) - for(var/mob/occupant as anything in occupants) - occupant.show_message( - span_userdanger("[user] is trying to open the maintenance panel of [src]!"), MSG_VISUAL, - span_userdanger("You hear someone trying to open the maintenance panel of [src]!"), MSG_AUDIBLE, - ) - visible_message(span_danger("[user] is trying to open the maintenance panel of [src]!")) - if(!do_after(user, 5 SECONDS, src)) - return - for(var/mob/occupant as anything in occupants) - occupant.show_message( - span_userdanger("[user] has opened the maintenance panel of [src]!"), MSG_VISUAL, - span_userdanger("You hear someone opening the maintenance panel of [src]!"), MSG_AUDIBLE, - ) - visible_message(span_danger("[user] has opened the maintenance panel of [src]!")) + if(LAZYLEN(occupants)) + balloon_alert(user, "panel blocked") + return mecha_flags ^= PANEL_OPEN balloon_alert(user, (mecha_flags & PANEL_OPEN) ? "panel open" : "panel closed") @@ -420,6 +420,7 @@ break if(did_the_thing) user.balloon_alert_to_viewers("[(atom_integrity >= max_integrity) ? "fully" : "partially"] repaired [src]") + diag_hud_set_mechhealth() else user.balloon_alert_to_viewers("stopped welding [src]", "interrupted the repair!") @@ -428,6 +429,7 @@ atom_integrity = max_integrity if(cell && charge_cell) cell.charge = cell.maxcharge + diag_hud_set_mechcell() if(internal_damage & MECHA_INT_FIRE) clear_internal_damage(MECHA_INT_FIRE) if(internal_damage & MECHA_INT_TEMP_CONTROL) @@ -438,6 +440,7 @@ clear_internal_damage(MECHA_CABIN_AIR_BREACH) if(internal_damage & MECHA_INT_CONTROL_LOST) clear_internal_damage(MECHA_INT_CONTROL_LOST) + diag_hud_set_mechhealth() /obj/vehicle/sealed/mecha/narsie_act() emp_act(EMP_HEAVY) @@ -511,3 +514,9 @@ else balloon_alert(user, "can't use this ammo!") return FALSE + +///Upgrades any attached RCD equipment. +/obj/vehicle/sealed/mecha/proc/upgrade_rcd(obj/item/rcd_upgrade/rcd_upgrade, mob/user) + for(var/obj/item/mecha_parts/mecha_equipment/rcd/rcd_equip in flat_equipment) + if(rcd_equip.internal_rcd.install_upgrade(rcd_upgrade, user)) + return diff --git a/code/modules/vehicles/mecha/mecha_helpers.dm b/code/modules/vehicles/mecha/mecha_helpers.dm index ad012386df392..c9de84747e915 100644 --- a/code/modules/vehicles/mecha/mecha_helpers.dm +++ b/code/modules/vehicles/mecha/mecha_helpers.dm @@ -8,7 +8,10 @@ return cell?.charge /obj/vehicle/sealed/mecha/proc/use_power(amount) - return (get_charge() && cell.use(amount)) + var/output = get_charge() && cell.use(amount) + if (output) + diag_hud_set_mechcell() + return output /obj/vehicle/sealed/mecha/proc/give_power(amount) if(!isnull(get_charge())) diff --git a/code/modules/vehicles/mecha/mecha_mob_interaction.dm b/code/modules/vehicles/mecha/mecha_mob_interaction.dm index c5f440ac97e25..e9d7f5a1e1fe8 100644 --- a/code/modules/vehicles/mecha/mecha_mob_interaction.dm +++ b/code/modules/vehicles/mecha/mecha_mob_interaction.dm @@ -35,13 +35,14 @@ return FALSE return ..() -///proc called when a new non-mmi/AI mob enters this mech +///proc called when a new non-mmi mob enters this mech /obj/vehicle/sealed/mecha/proc/moved_inside(mob/living/newoccupant) if(!(newoccupant?.client)) return FALSE if(ishuman(newoccupant) && !Adjacent(newoccupant)) return FALSE add_occupant(newoccupant) + mecha_flags &= ~PANEL_OPEN //Close panel if open newoccupant.forceMove(src) newoccupant.update_mouse_pointer() add_fingerprint(newoccupant) @@ -92,6 +93,7 @@ brain_obj.set_mecha(src) add_occupant(brain_mob)//Note this forcemoves the brain into the mech to allow relaymove + mecha_flags &= ~PANEL_OPEN //Close panel if open mecha_flags |= SILICON_PILOT brain_mob.reset_perspective(src) brain_mob.remote_control = src @@ -120,7 +122,7 @@ if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf. if(!AI.linked_core) //if the victim AI has no core AI.investigate_log("has been gibbed by being forced out of their mech by another AI.", INVESTIGATE_DEATHS) - AI.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. + AI.gib(DROP_ALL_REMAINS) //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. AI = null mecha_flags &= ~SILICON_PILOT return @@ -158,12 +160,16 @@ /obj/vehicle/sealed/mecha/add_occupant(mob/M, control_flags) RegisterSignal(M, COMSIG_MOB_CLICKON, PROC_REF(on_mouseclick), TRUE) RegisterSignal(M, COMSIG_MOB_SAY, PROC_REF(display_speech_bubble), TRUE) + RegisterSignal(M, COMSIG_MOVABLE_KEYBIND_FACE_DIR, PROC_REF(on_turn), TRUE) . = ..() update_appearance() /obj/vehicle/sealed/mecha/remove_occupant(mob/M) - UnregisterSignal(M, COMSIG_MOB_CLICKON) - UnregisterSignal(M, COMSIG_MOB_SAY) + UnregisterSignal(M, list( + COMSIG_MOB_CLICKON, + COMSIG_MOB_SAY, + COMSIG_MOVABLE_KEYBIND_FACE_DIR, + )) M.clear_alert(ALERT_CHARGE) M.clear_alert(ALERT_MECH_DAMAGE) if(M.client) diff --git a/code/modules/vehicles/mecha/mecha_movement.dm b/code/modules/vehicles/mecha/mecha_movement.dm index d6c8feed4b3b9..a77381fe97a87 100644 --- a/code/modules/vehicles/mecha/mecha_movement.dm +++ b/code/modules/vehicles/mecha/mecha_movement.dm @@ -19,6 +19,11 @@ //we can reach it and it's in front of us? grab it! if(ore.Adjacent(src) && ((get_dir(src, ore) & dir) || ore.loc == loc)) ore.forceMove(ore_box) + for(var/obj/item/boulder/boulder in range(1, src)) + //As above, but for boulders + if(boulder.Adjacent(src) && ((get_dir(src, boulder) & dir) || boulder.loc == loc)) + boulder.forceMove(ore_box) + ///Plays the mech step sound effect. Split from movement procs so that other mechs (HONK) can override this one specific part. /obj/vehicle/sealed/mecha/proc/play_stepsound() @@ -49,6 +54,11 @@ return TRUE return FALSE +///Called when the driver turns with the movement lock key +/obj/vehicle/sealed/mecha/proc/on_turn(mob/living/driver, direction) + SIGNAL_HANDLER + return COMSIG_IGNORE_MOVEMENT_LOCK + /obj/vehicle/sealed/mecha/relaymove(mob/living/user, direction) . = TRUE if(!canmove || !(user in return_drivers())) @@ -70,14 +80,14 @@ return loc_atom.relaymove(src, direction) var/obj/machinery/portable_atmospherics/canister/internal_tank = get_internal_tank() if(internal_tank?.connected_port) - if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_MESSAGE)) + if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) to_chat(occupants, "[icon2html(src, occupants)][span_warning("Unable to move while connected to the air system port!")]") TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) return FALSE if(!Process_Spacemove(direction)) return FALSE if(zoom_mode) - if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_MESSAGE)) + if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) to_chat(occupants, "[icon2html(src, occupants)][span_warning("Unable to move while in zoom mode!")]") TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) return FALSE @@ -89,17 +99,17 @@ if(isnull(servo)) missing_parts += "micro-servo" if(length(missing_parts)) - if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_MESSAGE)) + if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) to_chat(occupants, "[icon2html(src, occupants)][span_warning("Missing [english_list(missing_parts)].")]") TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) return FALSE if(!use_power(step_energy_drain)) - if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_MESSAGE)) + if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) to_chat(occupants, "[icon2html(src, occupants)][span_warning("Insufficient power to move!")]") TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) return FALSE if(lavaland_only && is_mining_level(z)) - if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_MESSAGE)) + if(TIMER_COOLDOWN_FINISHED(src, COOLDOWN_MECHA_MESSAGE)) to_chat(occupants, "[icon2html(src, occupants)][span_warning("Invalid Environment.")]") TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MESSAGE, 2 SECONDS) return FALSE @@ -121,11 +131,11 @@ break //if we're not facing the way we're going rotate us - if(dir != direction && !strafe || forcerotate || keyheld) + if(dir != direction && (!strafe || forcerotate || keyheld)) if(dir != direction && !(mecha_flags & QUIET_TURNS) && !step_silent) playsound(src,turnsound,40,TRUE) setDir(direction) - if(!pivot_step) //If we pivot step, we don't return here so we don't just come to a stop + if(keyheld || !pivot_step) //If we pivot step, we don't return here so we don't just come to a stop return TRUE set_glide_size(DELAY_TO_GLIDE_SIZE(movedelay)) diff --git a/code/modules/vehicles/mecha/mecha_parts.dm b/code/modules/vehicles/mecha/mecha_parts.dm index fe5b53caba78d..bec0fefcc6d85 100644 --- a/code/modules/vehicles/mecha/mecha_parts.dm +++ b/code/modules/vehicles/mecha/mecha_parts.dm @@ -7,7 +7,7 @@ icon = 'icons/mob/mech_construct.dmi' icon_state = "blank" w_class = WEIGHT_CLASS_GIGANTIC - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY /obj/item/mecha_parts/proc/try_attach_part(mob/user, obj/vehicle/sealed/mecha/M, attach_right = FALSE) //For attaching parts to a finished mech if(!user.transferItemToLoc(src, M)) @@ -336,12 +336,12 @@ /obj/item/circuitboard/mecha name = "exosuit circuit board" - icon = 'icons/obj/assemblies/module.dmi' + icon = 'icons/obj/devices/circuitry_n_data.dmi' icon_state = "std_mod" inhand_icon_state = "electronic" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - flags_1 = CONDUCT_1 + obj_flags = CONDUCTS_ELECTRICITY force = 5 w_class = WEIGHT_CLASS_SMALL throwforce = 0 diff --git a/code/modules/vehicles/mecha/mecha_ui.dm b/code/modules/vehicles/mecha/mecha_ui.dm index 815770875ea05..1bf5b8674a47d 100644 --- a/code/modules/vehicles/mecha/mecha_ui.dm +++ b/code/modules/vehicles/mecha/mecha_ui.dm @@ -82,6 +82,8 @@ data["internal_damage"] = internal_damage data["can_use_overclock"] = can_use_overclock + data["overclock_safety_available"] = overclock_safety_available + data["overclock_safety"] = overclock_safety data["overclock_mode"] = overclock_mode data["overclock_temp_percentage"] = overclock_temp / overclock_temp_danger @@ -95,7 +97,7 @@ data["capacitor_rating"] = capacitor?.rating data["weapons_safety"] = weapons_safety - data["enclosed"] = enclosed + data["enclosed"] = mecha_flags & IS_ENCLOSED data["cabin_sealed"] = cabin_sealed data["cabin_temp"] = round(cabin_air.temperature - T0C) data["cabin_pressure"] = round(cabin_air.return_pressure()) @@ -188,7 +190,7 @@ if(userinput == format_text(name)) //default mecha names may have improper span artefacts in their name, so we format the name to_chat(usr, span_notice("You rename [name] to... well, [userinput].")) return - name = userinput + name = "\proper [userinput]" chassis_camera?.update_c_tag(src) if("toggle_safety") set_safety(usr) @@ -210,9 +212,8 @@ toggle_lights(user = usr) if("toggle_overclock") toggle_overclock() - var/datum/action/act = locate(/datum/action/vehicle/sealed/mecha/mech_overclock) in usr.actions - act.button_icon_state = "mech_overload_[overclock_mode ? "on" : "off"]" - act.build_all_button_icons() + if("toggle_overclock_safety") + overclock_safety = !overclock_safety if("repair_int_damage") try_repair_int_damage(usr, params["flag"]) return FALSE diff --git a/code/modules/vehicles/mecha/mecha_wreckage.dm b/code/modules/vehicles/mecha/mecha_wreckage.dm index 3540891d336cd..8896b7268fe15 100644 --- a/code/modules/vehicles/mecha/mecha_wreckage.dm +++ b/code/modules/vehicles/mecha/mecha_wreckage.dm @@ -105,7 +105,7 @@ if(AI.client) //AI player is still in the dead AI and is connected to_chat(AI, span_notice("The remains of your file system have been recovered on a mobile storage device.")) else //Give the AI a heads-up that it is probably going to get fixed. - AI.notify_ghost_cloning("You have been recovered from the wreckage!", source = card) + AI.notify_revival("You have been recovered from the wreckage!", source = card) to_chat(user, "[span_boldnotice("Backup files recovered")]: [AI.name] ([rand(1000,9999)].exe) salvaged from [name] and stored within local memory.") AI = null @@ -165,6 +165,10 @@ name = "\improper Ripley MK-II wreckage" icon_state = "ripleymkii-broken" +/obj/structure/mecha_wreckage/ripley/paddy + name = "\improper Paddy wreckage" + icon_state = "paddy-broken" + /obj/structure/mecha_wreckage/clarke name = "\improper Clarke wreckage" icon_state = "clarke-broken" diff --git a/code/modules/vehicles/mecha/working/clarke.dm b/code/modules/vehicles/mecha/working/clarke.dm index e31690f65b929..2ec0b4a473648 100644 --- a/code/modules/vehicles/mecha/working/clarke.dm +++ b/code/modules/vehicles/mecha/working/clarke.dm @@ -7,6 +7,7 @@ max_temperature = 65000 max_integrity = 200 movedelay = 1.25 + overclock_coeff = 1.25 resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF lights_power = 7 step_energy_drain = 12 //slightly higher energy drain since you movin those wheels FAST diff --git a/code/modules/vehicles/mecha/working/ripley.dm b/code/modules/vehicles/mecha/working/ripley.dm index 9db12624125aa..711da429cca2f 100644 --- a/code/modules/vehicles/mecha/working/ripley.dm +++ b/code/modules/vehicles/mecha/working/ripley.dm @@ -5,6 +5,7 @@ base_icon_state = "ripley" silicon_icon_state = "ripley-empty" movedelay = 1.5 //Move speed, lower is faster. + overclock_coeff = 1.25 max_temperature = 20000 max_integrity = 200 lights_power = 7 @@ -16,11 +17,11 @@ MECHA_POWER = 1, MECHA_ARMOR = 1, ) + mecha_flags = CAN_STRAFE | HAS_LIGHTS | MMI_COMPATIBLE wreckage = /obj/structure/mecha_wreckage/ripley mech_type = EXOSUIT_MODULE_RIPLEY possible_int_damage = MECHA_INT_FIRE|MECHA_INT_CONTROL_LOST|MECHA_INT_SHORT_CIRCUIT accesses = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_MINING) - enclosed = FALSE //Normal ripley has an open cockpit design enter_delay = 10 //can enter in a quarter of the time of other mechs exit_delay = 10 /// Custom Ripley step and turning sounds (from TGMC) @@ -35,10 +36,8 @@ ) /// Amount of Goliath hides attached to the mech var/hides = 0 - /// List of all things in Ripley's Cargo Compartment - var/list/cargo - /// How much things Ripley can carry in their Cargo Compartment - var/cargo_capacity = 15 + /// Reference to the Cargo Hold equipment. + var/obj/item/mecha_parts/mecha_equipment/ejector/cargo_hold /// How fast the mech is in low pressure var/fast_pressure_step_in = 1.5 /// How fast the mech is in normal pressure @@ -66,13 +65,6 @@ bullet = 5 laser = 5 -/obj/vehicle/sealed/mecha/ripley/Destroy() - for(var/atom/movable/A in cargo) - A.forceMove(drop_location()) - step_rand(A) - QDEL_LIST(cargo) - return ..() - /obj/vehicle/sealed/mecha/ripley/mk2 desc = "Autonomous Power Loader Unit MK-II. This prototype Ripley is refitted with a pressurized cabin, trading its prior speed for atmospheric protection and armor." name = "\improper APLU MK-II \"Ripley\"" @@ -83,10 +75,10 @@ movedelay = 4 max_temperature = 30000 max_integrity = 250 + mecha_flags = CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE possible_int_damage = MECHA_INT_FIRE|MECHA_INT_TEMP_CONTROL|MECHA_CABIN_AIR_BREACH|MECHA_INT_CONTROL_LOST|MECHA_INT_SHORT_CIRCUIT armor_type = /datum/armor/mecha_ripley_mk2 wreckage = /obj/structure/mecha_wreckage/ripley/mk2 - enclosed = TRUE enter_delay = 40 silicon_icon_state = null @@ -99,6 +91,110 @@ fire = 100 acid = 100 +/obj/vehicle/sealed/mecha/ripley/paddy + desc = "Autonomous Power Loader Unit Subtype Paddy. A Modified MK-I Ripley design intended for light security use." + name = "\improper APLU \"Paddy\"" + icon_state = "paddy" + base_icon_state = "paddy" + max_temperature = 20000 + max_integrity = 250 + mech_type = EXOSUIT_MODULE_PADDY + possible_int_damage = MECHA_INT_FIRE|MECHA_INT_CONTROL_LOST|MECHA_INT_SHORT_CIRCUIT + accesses = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY) + armor_type = /datum/armor/mecha_paddy + wreckage = /obj/structure/mecha_wreckage/ripley/paddy + silicon_icon_state = "paddy-empty" + equip_by_category = list( + MECHA_L_ARM = null, + MECHA_R_ARM = null, + MECHA_UTILITY = list(/obj/item/mecha_parts/mecha_equipment/ejector/seccage), + MECHA_POWER = list(), + MECHA_ARMOR = list(), + ) + ///Siren Lights/Sound State + var/siren = FALSE + ///Overlay for Siren Lights + var/mutable_appearance/sirenlights + ///Looping sound datum for the Siren audio + var/datum/looping_sound/siren/weewooloop + +/datum/armor/mecha_paddy + melee = 40 + bullet = 20 + laser = 10 + energy = 20 + bomb = 40 + fire = 100 + acid = 100 + +/obj/vehicle/sealed/mecha/ripley/paddy/Initialize(mapload) + . = ..() + weewooloop = new(src, FALSE, FALSE) + weewooloop.volume = 100 + +/obj/vehicle/sealed/mecha/ripley/paddy/generate_actions() + . = ..() + initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/siren) + +/obj/vehicle/sealed/mecha/ripley/paddy/mob_exit(mob/M, silent = FALSE, randomstep = FALSE, forced = FALSE) + var/obj/item/mecha_parts/mecha_equipment/ejector/seccage/cargo_holder = locate(/obj/item/mecha_parts/mecha_equipment/ejector/seccage) in equip_by_category[MECHA_UTILITY] + for(var/mob/contained in cargo_holder) + cargo_holder.cheese_it(contained) + togglesiren(force_off = TRUE) + return ..() + +/obj/vehicle/sealed/mecha/ripley/paddy/proc/togglesiren(force_off = FALSE) + if(force_off || siren) + weewooloop.stop() + siren = FALSE + else + weewooloop.start() + siren = TRUE + for(var/mob/occupant as anything in occupants) + balloon_alert(occupant, "siren [siren ? "activated" : "disabled"]") + var/datum/action/act = locate(/datum/action/vehicle/sealed/mecha/siren) in occupant.actions + act.button_icon_state = "mech_siren_[siren ? "on" : "off"]" + act.build_all_button_icons() + update_appearance(UPDATE_OVERLAYS) + +/obj/vehicle/sealed/mecha/ripley/paddy/update_overlays() + . = ..() + if(!siren) + return + sirenlights = new() + sirenlights.icon = icon + sirenlights.icon_state = "paddy_sirens" + SET_PLANE_EXPLICIT(sirenlights, ABOVE_LIGHTING_PLANE, src) + . += sirenlights + +/obj/vehicle/sealed/mecha/ripley/paddy/Destroy() + QDEL_NULL(weewooloop) + return ..() + +/datum/action/vehicle/sealed/mecha/siren + name = "Toggle External Siren and Lights" + button_icon_state = "mech_siren_off" + +/datum/action/vehicle/sealed/mecha/siren/New() + . = ..() + var/obj/vehicle/sealed/mecha/ripley/paddy/secmech = chassis + button_icon_state = "mech_siren_[secmech?.siren ? "on" : "off"]" + +/datum/action/vehicle/sealed/mecha/siren/Trigger(trigger_flags, forced_state = FALSE) + var/obj/vehicle/sealed/mecha/ripley/paddy/secmech = chassis + secmech.togglesiren() + +/obj/vehicle/sealed/mecha/ripley/paddy/preset + accesses = list(ACCESS_SECURITY) + mecha_flags = CAN_STRAFE | HAS_LIGHTS | MMI_COMPATIBLE | ID_LOCK_ON + equip_by_category = list( + MECHA_L_ARM = /obj/item/mecha_parts/mecha_equipment/weapon/energy/disabler, + MECHA_R_ARM = /obj/item/mecha_parts/mecha_equipment/weapon/paddy_claw, + MECHA_UTILITY = list(/obj/item/mecha_parts/mecha_equipment/ejector/seccage), + MECHA_POWER = list(), + MECHA_ARMOR = list(), + ) + /obj/vehicle/sealed/mecha/ripley/deathripley desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" name = "\improper DEATH-RIPLEY" @@ -110,7 +206,7 @@ lights_power = 7 wreckage = /obj/structure/mecha_wreckage/ripley/deathripley step_energy_drain = 0 - enclosed = TRUE + mecha_flags = CAN_STRAFE | IS_ENCLOSED | HAS_LIGHTS | MMI_COMPATIBLE enter_delay = 40 silicon_icon_state = null equip_by_category = list( @@ -193,37 +289,54 @@ GLOBAL_DATUM(cargo_ripley, /obj/vehicle/sealed/mecha/ripley/cargo) servo = new /obj/item/stock_parts/servo(src) update_part_values() -/obj/vehicle/sealed/mecha/ripley/Exit(atom/movable/leaving, direction) - if(leaving in cargo) - return FALSE - return ..() - -/obj/vehicle/sealed/mecha/ripley/contents_explosion(severity, target) - for(var/i in cargo) - var/obj/cargoobj = i - if(prob(10 * severity)) - LAZYREMOVE(cargo, cargoobj) - cargoobj.forceMove(drop_location()) - return ..() - /obj/item/mecha_parts/mecha_equipment/ejector - name = "Cargo compartment" + name = "cargo compartment" desc = "Holds cargo loaded with a hydraulic clamp." icon_state = "mecha_bin" equipment_slot = MECHA_UTILITY detachable = FALSE + ///Number of atoms we can store + var/cargo_capacity = 15 + +/obj/item/mecha_parts/mecha_equipment/ejector/attach() + . = ..() + var/obj/vehicle/sealed/mecha/ripley/workmech = chassis + workmech.cargo_hold = src + + +/obj/item/mecha_parts/mecha_equipment/ejector/Destroy() + for(var/atom/stored in contents) + forceMove(stored, drop_location()) + step_rand(stored) + return ..() + +/obj/item/mecha_parts/mecha_equipment/ejector/contents_explosion(severity, target) + for(var/obj/stored in contents) + if(prob(10 * severity)) + stored.forceMove(drop_location()) + return ..() + +/obj/item/mecha_parts/mecha_equipment/ejector/relay_container_resist_act(mob/living/user, obj/container) + to_chat(user, span_notice("You lean on the back of [container] and start pushing so it falls out of [src].")) + if(do_after(user, 300, target = container)) + if(!user || user.stat != CONSCIOUS || user.loc != src || container.loc != src ) + return + to_chat(user, span_notice("You successfully pushed [container] out of [src]!")) + container.forceMove(drop_location()) + else + if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. + to_chat(user, span_warning("You fail to push [container] out of [src]!")) /obj/item/mecha_parts/mecha_equipment/ejector/get_snowflake_data() - var/obj/vehicle/sealed/mecha/ripley/miner = chassis var/list/data = list( "snowflake_id" = MECHA_SNOWFLAKE_ID_EJECTOR, - "cargo_capacity" = miner.cargo_capacity, + "cargo_capacity" = cargo_capacity, "cargo" = list() ) - for(var/obj/crate in miner.cargo) + for(var/atom/entry in contents) data["cargo"] += list(list( - "name" = crate.name, - "ref" = REF(crate), + "name" = entry.name, + "ref" = REF(entry), )) return data @@ -232,30 +345,89 @@ GLOBAL_DATUM(cargo_ripley, /obj/vehicle/sealed/mecha/ripley/cargo) if(.) return TRUE if(action == "eject") - var/obj/vehicle/sealed/mecha/ripley/miner = chassis - var/obj/crate = locate(params["cargoref"]) in miner.cargo + var/obj/crate = locate(params["cargoref"]) in contents if(!crate) return FALSE - to_chat(miner.occupants, "[icon2html(src, miner.occupants)][span_notice("You unload [crate].")]") + to_chat(chassis.occupants, "[icon2html(src, chassis.occupants)][span_notice("You unload [crate].")]") crate.forceMove(drop_location()) - LAZYREMOVE(miner.cargo, crate) - if(crate == miner.ore_box) - miner.ore_box = null + if(crate == chassis.ore_box) + chassis.ore_box = null playsound(chassis, 'sound/weapons/tap.ogg', 50, TRUE) - log_message("Unloaded [crate]. Cargo compartment capacity: [miner.cargo_capacity - LAZYLEN(miner.cargo)]", LOG_MECHA) + log_message("Unloaded [crate]. Cargo compartment capacity: [cargo_capacity - contents.len]", LOG_MECHA) return TRUE -/obj/vehicle/sealed/mecha/ripley/relay_container_resist_act(mob/living/user, obj/O) - to_chat(user, span_notice("You lean on the back of [O] and start pushing so it falls out of [src].")) - if(do_after(user, 300, target = O)) - if(!user || user.stat != CONSCIOUS || user.loc != src || O.loc != src ) - return - to_chat(user, span_notice("You successfully pushed [O] out of [src]!")) - O.forceMove(drop_location()) - LAZYREMOVE(cargo, O) - else - if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. - to_chat(user, span_warning("You fail to push [O] out of [src]!")) +/obj/item/mecha_parts/mecha_equipment/ejector/seccage + name = "holding cell" + desc = "Holds suspects loaded with a hydraulic claw." + cargo_capacity = 4 + +/obj/item/mecha_parts/mecha_equipment/ejector/seccage/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_MOB_REMOVING_CUFFS, PROC_REF(stop_cuff_removal)) + +/obj/item/mecha_parts/mecha_equipment/ejector/seccage/Destroy() + UnregisterSignal(src, COMSIG_MOB_REMOVING_CUFFS) + for(var/mob/freebird in contents) //Let's not qdel people iside the mech kthx + cheese_it(freebird) + return ..() + +/obj/item/mecha_parts/mecha_equipment/ejector/seccage/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + RegisterSignal(arrived, COMSIG_MOB_REMOVING_CUFFS, PROC_REF(stop_cuff_removal)) + return ..() + +/obj/item/mecha_parts/mecha_equipment/ejector/seccage/Exited(atom/movable/gone, direction) + UnregisterSignal(gone, COMSIG_MOB_REMOVING_CUFFS) + return ..() + +/obj/item/mecha_parts/mecha_equipment/ejector/seccage/proc/stop_cuff_removal(datum/source, obj/item/cuffs) + SIGNAL_HANDLER + to_chat(source, span_warning("You don't have the room to remove [cuffs]!")) + return COMSIG_MOB_BLOCK_CUFF_REMOVAL + +/obj/item/mecha_parts/mecha_equipment/ejector/seccage/ui_act(action, list/params) + if(action == "eject") + var/mob/passenger = locate(params["cargoref"]) in contents + if(!passenger) + return FALSE + to_chat(chassis.occupants, "[icon2html(src, chassis.occupants)][span_notice("You unload [passenger].")]") + passenger.forceMove(drop_location()) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_step), passenger, chassis.dir), 1) //That's right, one tick. Just enough to cause the tile move animation. + playsound(chassis, 'sound/weapons/tap.ogg', 50, TRUE) + log_message("Unloaded [passenger]. Cargo compartment capacity: [cargo_capacity - contents.len]", LOG_MECHA) + return TRUE + return ..() + +/obj/item/mecha_parts/mecha_equipment/ejector/seccage/container_resist_act(mob/living/user) + to_chat(user, span_notice("You begin attempting a breakout. (This will take around 45 seconds and [chassis] need to remain stationary.)")) + if(!do_after(user, 1 MINUTES, target = chassis)) + return + to_chat(user, span_notice("You break out of the [src].")) + playsound(chassis, 'sound/items/crowbar.ogg', 100, TRUE) + cheese_it(user) + for(var/mob/freebird in contents) + if(user != freebird) + to_chat(freebird, span_warning("[user] has managed to open the hatch, and you fall out with him. You're free!")) + cheese_it(freebird) + +/obj/item/mecha_parts/mecha_equipment/ejector/seccage/proc/cheese_it(mob/living/escapee) + var/range = rand(1, 3) + var/variance = rand(-45, 45) + var/angle = 180 + var/turf/current_turf = get_turf(src) + switch (chassis?.dir) + if(NORTH) + angle = 270 + if(EAST) + angle = 180 + if(SOUTH) + angle = 90 + if(WEST) + angle = 0 + var/target_x = round(range * cos(angle + variance), 1) + current_turf.x + var/target_y = round(range * sin(angle + variance), 1) + current_turf.y + escapee.Knockdown(1) //Otherwise everyone hits eachother while being thrown + escapee.forceMove(drop_location()) + escapee.throw_at(locate(target_x, target_y, current_turf.z), range, 1) /** * Makes the mecha go faster and halves the mecha drill cooldown if in Lavaland pressure. @@ -266,11 +438,11 @@ GLOBAL_DATUM(cargo_ripley, /obj/vehicle/sealed/mecha/ripley/cargo) var/turf/T = get_turf(loc) if(lavaland_equipment_pressure_check(T)) - movedelay = fast_pressure_step_in + movedelay = !overclock_mode ? fast_pressure_step_in : fast_pressure_step_in / overclock_coeff for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in flat_equipment) drill.equip_cooldown = initial(drill.equip_cooldown) * 0.5 else - movedelay = slow_pressure_step_in + movedelay = !overclock_mode ? slow_pressure_step_in : slow_pressure_step_in / overclock_coeff for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in flat_equipment) drill.equip_cooldown = initial(drill.equip_cooldown) diff --git a/code/modules/vehicles/motorized_wheelchair.dm b/code/modules/vehicles/motorized_wheelchair.dm index 5b0f02c7ae8d1..7d3b70baf95ee 100644 --- a/code/modules/vehicles/motorized_wheelchair.dm +++ b/code/modules/vehicles/motorized_wheelchair.dm @@ -24,12 +24,30 @@ ///stock parts for this chair var/list/component_parts = list() +/obj/vehicle/ridden/wheelchair/motorized/Initialize(mapload) + . = ..() + // Add tier 1 stock parts so that non-crafted wheelchairs aren't empty + component_parts += GLOB.stock_part_datums[/datum/stock_part/capacitor] + component_parts += GLOB.stock_part_datums[/datum/stock_part/servo] + component_parts += GLOB.stock_part_datums[/datum/stock_part/servo] + power_cell = new /obj/item/stock_parts/cell(src) + refresh_parts() + /obj/vehicle/ridden/wheelchair/motorized/make_ridable() AddElement(/datum/element/ridable, /datum/component/riding/vehicle/wheelchair/motorized) /obj/vehicle/ridden/wheelchair/motorized/CheckParts(list/parts_list) + // This wheelchair was crafted, so clean out default parts + qdel(power_cell) + component_parts = list() + for(var/obj/item/stock_parts/part in parts_list) - // find macthing datum/stock_part for this part and add to component list + if(istype(part, /obj/item/stock_parts/cell)) // power cell, physically moves into the wheelchair + power_cell = part + part.forceMove(src) + continue + + // find matching datum/stock_part for this part and add to component list var/datum/stock_part/newstockpart = GLOB.stock_part_datums_per_object[part.type] if(isnull(newstockpart)) CRASH("No corresponding datum/stock_part for [part.type]") @@ -58,6 +76,9 @@ /obj/vehicle/ridden/wheelchair/motorized/atom_destruction(damage_flag) for(var/datum/stock_part/part in component_parts) new part.physical_object_type(drop_location()) + if(!isnull(power_cell)) + power_cell.forceMove(drop_location()) + power_cell = null return ..() /obj/vehicle/ridden/wheelchair/motorized/relaymove(mob/living/user, direction) @@ -81,28 +102,23 @@ user.put_in_hands(power_cell) power_cell = null -/obj/vehicle/ridden/wheelchair/motorized/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - I.play_tool_sound(src) - panel_open = !panel_open - user.visible_message(span_notice("[user] [panel_open ? "opens" : "closes"] the maintenance panel on [src]."), span_notice("You [panel_open ? "open" : "close"] the maintenance panel.")) - return +/obj/vehicle/ridden/wheelchair/motorized/attackby(obj/item/attacking_item, mob/user, params) if(!panel_open) return ..() - if(istype(I, /obj/item/stock_parts/cell)) + if(istype(attacking_item, /obj/item/stock_parts/cell)) if(power_cell) to_chat(user, span_warning("There is a power cell already installed.")) else - I.forceMove(src) - power_cell = I - to_chat(user, span_notice("You install the [I].")) + attacking_item.forceMove(src) + power_cell = attacking_item + to_chat(user, span_notice("You install the [attacking_item].")) refresh_parts() return - if(!istype(I, /obj/item/stock_parts)) + if(!istype(attacking_item, /obj/item/stock_parts)) return ..() - var/datum/stock_part/newstockpart = GLOB.stock_part_datums_per_object[I.type] + var/datum/stock_part/newstockpart = GLOB.stock_part_datums_per_object[attacking_item.type] if(isnull(newstockpart)) CRASH("No corresponding datum/stock_part for [newstockpart.type]") for(var/datum/stock_part/oldstockpart in component_parts) @@ -114,8 +130,7 @@ if(istype(newstockpart, type_to_check) && istype(oldstockpart, type_to_check)) if(newstockpart.tier > oldstockpart.tier) // delete the part in the users hand and add the datum part to the component_list - I.moveToNullspace() - qdel(I) + qdel(attacking_item) component_parts += newstockpart // create an new instance of the old datum stock part physical type & put it in the users hand var/obj/item/stock_parts/part = new oldstockpart.physical_object_type @@ -126,17 +141,26 @@ break refresh_parts() -/obj/vehicle/ridden/wheelchair/motorized/wrench_act(mob/living/user, obj/item/I) - to_chat(user, span_notice("You begin to detach the wheels...")) - if(!I.use_tool(src, user, 40, volume=50)) - return TRUE +/obj/vehicle/ridden/wheelchair/motorized/wrench_act(mob/living/user, obj/item/tool) + balloon_alert(user, "disassembling") + if(!tool.use_tool(src, user, 4 SECONDS, volume=50)) + return ITEM_INTERACT_SUCCESS to_chat(user, span_notice("You detach the wheels and deconstruct the chair.")) new /obj/item/stack/rods(drop_location(), 8) new /obj/item/stack/sheet/iron(drop_location(), 10) for(var/datum/stock_part/part in component_parts) new part.physical_object_type(drop_location()) + if(!isnull(power_cell)) + power_cell.forceMove(drop_location()) + power_cell = null qdel(src) - return TRUE + return ITEM_INTERACT_SUCCESS + +/obj/vehicle/ridden/wheelchair/motorized/screwdriver_act(mob/living/user, obj/item/tool) + tool.play_tool_sound(src) + panel_open = !panel_open + user.visible_message(span_notice("[user] [panel_open ? "opens" : "closes"] the maintenance panel on [src]."), span_notice("You [panel_open ? "open" : "close"] the maintenance panel.")) + return ITEM_INTERACT_SUCCESS /obj/vehicle/ridden/wheelchair/motorized/examine(mob/user) . = ..() @@ -159,30 +183,30 @@ if(!(guy in buckled_mobs)) Bump(guy) -/obj/vehicle/ridden/wheelchair/motorized/Bump(atom/A) +/obj/vehicle/ridden/wheelchair/motorized/Bump(atom/bumped_atom) . = ..() // Here is the shitty emag functionality. - if(obj_flags & EMAGGED && (isclosedturf(A) || isliving(A))) + if(obj_flags & EMAGGED && (isclosedturf(bumped_atom) || isliving(bumped_atom))) explosion(src, devastation_range = -1, heavy_impact_range = 1, light_impact_range = 3, flash_range = 2, adminlog = FALSE) visible_message(span_boldwarning("[src] explodes!!")) return // If the speed is higher than delay_multiplier throw the person on the wheelchair away - if(A.density && speed > delay_multiplier && has_buckled_mobs()) + if(bumped_atom.density && speed > delay_multiplier && has_buckled_mobs()) var/mob/living/disabled = buckled_mobs[1] var/atom/throw_target = get_edge_target_turf(disabled, pick(GLOB.cardinals)) unbuckle_mob(disabled) disabled.throw_at(throw_target, 2, 3) - disabled.Knockdown(100) + disabled.Knockdown(10 SECONDS) disabled.adjustStaminaLoss(40) - if(isliving(A)) - var/mob/living/ramtarget = A + if(isliving(bumped_atom)) + var/mob/living/ramtarget = bumped_atom throw_target = get_edge_target_turf(ramtarget, pick(GLOB.cardinals)) ramtarget.throw_at(throw_target, 2, 3) - ramtarget.Knockdown(80) + ramtarget.Knockdown(8 SECONDS) ramtarget.adjustStaminaLoss(35) visible_message(span_danger("[src] crashes into [ramtarget], sending [disabled] and [ramtarget] flying!")) else - visible_message(span_danger("[src] crashes into [A], sending [disabled] flying!")) + visible_message(span_danger("[src] crashes into [bumped_atom], sending [disabled] flying!")) playsound(src, 'sound/effects/bang.ogg', 50, 1) /obj/vehicle/ridden/wheelchair/motorized/emag_act(mob/user, obj/item/card/emag/emag_card) diff --git a/code/modules/vehicles/pimpin_ride.dm b/code/modules/vehicles/pimpin_ride.dm index 39b78b99f62a6..7ef79cb89cf04 100644 --- a/code/modules/vehicles/pimpin_ride.dm +++ b/code/modules/vehicles/pimpin_ride.dm @@ -14,6 +14,7 @@ /obj/vehicle/ridden/janicart/Initialize(mapload) . = ..() + register_context() update_appearance() AddElement(/datum/element/ridable, /datum/component/riding/vehicle/janicart) GLOB.janitor_devices += src @@ -85,6 +86,44 @@ if (!.) try_remove_bag(user) +/obj/vehicle/ridden/janicart/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + + if(!held_item) + if(occupant_amount() > 0) + context[SCREENTIP_CONTEXT_LMB] = "Dismount" + context[SCREENTIP_CONTEXT_RMB] = "Dismount" + if(trash_bag) + context[SCREENTIP_CONTEXT_RMB] = "Remove trash bag" + if(is_key(inserted_key) && occupants.Find(user)) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Remove key" + return CONTEXTUAL_SCREENTIP_SET + else if(trash_bag) + context[SCREENTIP_CONTEXT_LMB] = "Remove trash bag" + context[SCREENTIP_CONTEXT_RMB] = "Remove trash bag" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/storage/bag/trash) && !trash_bag) + context[SCREENTIP_CONTEXT_LMB] = "Add trash bag" + context[SCREENTIP_CONTEXT_RMB] = "Add trash bag" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/janicart_upgrade) && !installed_upgrade) + context[SCREENTIP_CONTEXT_LMB] = "Install upgrade" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/screwdriver) && installed_upgrade) + context[SCREENTIP_CONTEXT_LMB] = "Remove upgrade" + return CONTEXTUAL_SCREENTIP_SET + + if(is_key(held_item) && !is_key(inserted_key)) + context[SCREENTIP_CONTEXT_LMB] = "Insert key" + context[SCREENTIP_CONTEXT_RMB] = "Insert key" + return CONTEXTUAL_SCREENTIP_SET + else if (trash_bag) + context[SCREENTIP_CONTEXT_LMB] = "Insert into trash bag" + context[SCREENTIP_CONTEXT_RMB] = "Insert into trash bag" + return CONTEXTUAL_SCREENTIP_SET /** * Called if the attached bag is being qdeleted, ensures appearance is maintained properly diff --git a/code/modules/vehicles/sealed.dm b/code/modules/vehicles/sealed.dm index dc49f6b5dbe55..de1b5ac93f052 100644 --- a/code/modules/vehicles/sealed.dm +++ b/code/modules/vehicles/sealed.dm @@ -3,6 +3,8 @@ var/enter_delay = 2 SECONDS var/mouse_pointer var/headlights_toggle = FALSE + ///Determines which occupants provide access when bumping into doors + var/access_provider_flags = VEHICLE_CONTROL_DRIVE /obj/vehicle/sealed/generate_actions() . = ..() @@ -31,7 +33,7 @@ . = ..() if(istype(A, /obj/machinery/door)) var/obj/machinery/door/conditionalwall = A - for(var/mob/occupant as anything in return_drivers()) + for(var/mob/occupant as anything in return_controllers_with_flag(access_provider_flags)) if(conditionalwall.try_safety_unlock(occupant)) return conditionalwall.bumpopen(occupant) diff --git a/code/modules/vehicles/vehicle_actions.dm b/code/modules/vehicles/vehicle_actions.dm index c1b48f0477151..ed9884a9ea3cb 100644 --- a/code/modules/vehicles/vehicle_actions.dm +++ b/code/modules/vehicles/vehicle_actions.dm @@ -232,7 +232,7 @@ var/hornsound = 'sound/items/carhorn.ogg' /datum/action/vehicle/sealed/horn/Trigger(trigger_flags) - if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_CAR_HONK)) + if(TIMER_COOLDOWN_RUNNING(src, COOLDOWN_CAR_HONK)) return TIMER_COOLDOWN_START(src, COOLDOWN_CAR_HONK, 2 SECONDS) vehicle_entered_target.visible_message(span_danger("[vehicle_entered_target] loudly honks!")) @@ -301,9 +301,11 @@ return COOLDOWN_START(src, thank_time_cooldown, 6 SECONDS) var/obj/vehicle/sealed/car/clowncar/clown_car = vehicle_entered_target - var/mob/living/carbon/human/clown = pick(clown_car.return_drivers()) - if(!clown) + var/list/mob/drivers = clown_car.return_drivers() + if(!length(drivers)) + to_chat(owner, span_danger("You prepare to thank the driver, only to realize that they don't exist.")) return + var/mob/clown = pick(drivers) owner.say("Thank you for the fun ride, [clown.name]!") clown_car.increment_thanks_counter() @@ -316,7 +318,7 @@ var/bell_cooldown /datum/action/vehicle/ridden/wheelchair/bell/Trigger(trigger_flags) - if(TIMER_COOLDOWN_CHECK(src, bell_cooldown)) + if(TIMER_COOLDOWN_RUNNING(src, bell_cooldown)) return TIMER_COOLDOWN_START(src, bell_cooldown, 0.5 SECONDS) playsound(vehicle_ridden_target, 'sound/machines/microwave/microwave-end.ogg', 70) @@ -359,10 +361,10 @@ animate(vehicle, pixel_y = -6, time = 3) playsound(vehicle, 'sound/vehicles/skateboard_ollie.ogg', 50, TRUE) passtable_on(rider, VEHICLE_TRAIT) - vehicle.pass_flags |= PASSTABLE + passtable_on(vehicle, VEHICLE_TRAIT) rider.Move(landing_turf, vehicle_target.dir) passtable_off(rider, VEHICLE_TRAIT) - vehicle.pass_flags &= ~PASSTABLE + passtable_off(vehicle, VEHICLE_TRAIT) /datum/action/vehicle/ridden/scooter/skateboard/kickflip name = "Kickflip" diff --git a/code/modules/vehicles/vehicle_key.dm b/code/modules/vehicles/vehicle_key.dm index e1b45d55f0a80..f6e5f7c4e2882 100644 --- a/code/modules/vehicles/vehicle_key.dm +++ b/code/modules/vehicles/vehicle_key.dm @@ -41,7 +41,7 @@ switch(user.mind?.get_skill_level(/datum/skill/cleaning)) if(SKILL_LEVEL_NONE to SKILL_LEVEL_NOVICE) //Their mind is too weak to ascend as a janny user.visible_message(span_suicide("[user] is putting \the [src] in [user.p_their()] mouth and is trying to become one with the janicart, but has no idea where to start! It looks like [user.p_theyre()] trying to commit suicide!")) - user.gib() + user.gib(DROP_ALL_REMAINS) return MANUAL_SUICIDE if(SKILL_LEVEL_APPRENTICE to SKILL_LEVEL_JOURNEYMAN) //At least they tried user.visible_message(span_suicide("[user] is putting \the [src] in [user.p_their()] mouth and has inefficiently become one with the janicart! It looks like [user.p_theyre()] trying to commit suicide!")) diff --git a/code/modules/vehicles/wheelchair.dm b/code/modules/vehicles/wheelchair.dm index 6dbc3c93a59c2..e7b3d9b3a56af 100644 --- a/code/modules/vehicles/wheelchair.dm +++ b/code/modules/vehicles/wheelchair.dm @@ -52,15 +52,16 @@ . = ..() update_appearance() -/obj/vehicle/ridden/wheelchair/wrench_act(mob/living/user, obj/item/I) //Attackby should stop it attacking the wheelchair after moving away during decon +/obj/vehicle/ridden/wheelchair/wrench_act(mob/living/user, obj/item/tool) //Attackby should stop it attacking the wheelchair after moving away during decon ..() - to_chat(user, span_notice("You begin to detach the wheels...")) - if(I.use_tool(src, user, 40, volume=50)) - to_chat(user, span_notice("You detach the wheels and deconstruct the chair.")) - new /obj/item/stack/rods(drop_location(), 6) - new /obj/item/stack/sheet/iron(drop_location(), 4) - qdel(src) - return TRUE + balloon_alert(user, "disassembling") + if(!tool.use_tool(src, user, 4 SECONDS, volume=50)) + return ITEM_INTERACT_SUCCESS + to_chat(user, span_notice("You detach the wheels and deconstruct the chair.")) + new /obj/item/stack/rods(drop_location(), 6) + new /obj/item/stack/sheet/iron(drop_location(), 4) + qdel(src) + return ITEM_INTERACT_SUCCESS /obj/vehicle/ridden/wheelchair/AltClick(mob/user) return ..() // This hotkey is BLACKLISTED since it's used by /datum/component/simple_rotation diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index abf07b4b603ba..47bb966555686 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -298,12 +298,10 @@ for(var/obj/item/vending_refill/installed_refill in component_parts) restock(installed_refill) -/obj/machinery/vending/deconstruct(disassembled = TRUE) +/obj/machinery/vending/on_deconstruction(disassembled) if(refill_canister) return ..() - if(!(flags_1 & NODECONSTRUCT_1)) //the non constructable vendors drop metal instead of a machine frame. - new /obj/item/stack/sheet/iron(loc, 3) - qdel(src) + new /obj/item/stack/sheet/iron(loc, 3) /obj/machinery/vending/update_appearance(updates=ALL) . = ..() @@ -620,7 +618,7 @@ return FALSE if(default_unfasten_wrench(user, tool, time = 6 SECONDS)) unbuckle_all_mobs(TRUE) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS return FALSE /obj/machinery/vending/screwdriver_act(mob/living/user, obj/item/attack_item) @@ -746,7 +744,6 @@ tilted = TRUE tilted_rotation = picked_rotation layer = ABOVE_MOB_LAYER - SET_PLANE_IMPLICIT(src, GAME_PLANE_UPPER) if(get_turf(fatty) != get_turf(src)) throw_at(get_turf(fatty), 1, 1, spin = FALSE, quickstart = FALSE) @@ -1016,7 +1013,6 @@ tilted = FALSE layer = initial(layer) - SET_PLANE_IMPLICIT(src, initial(plane)) var/matrix/to_turn = turn(transform, -tilted_rotation) animate(src, transform = to_turn, 0.2 SECONDS) @@ -1041,10 +1037,10 @@ LAZYADD(product_datum.returned_products, inserted_item) return - if(vending_machine_input[format_text(inserted_item.name)]) - vending_machine_input[format_text(inserted_item.name)]++ + if(vending_machine_input[inserted_item.type]) + vending_machine_input[inserted_item.type]++ else - vending_machine_input[format_text(inserted_item.name)] = 1 + vending_machine_input[inserted_item.type] = 1 loaded_items++ /obj/machinery/vending/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE) @@ -1068,7 +1064,7 @@ /obj/machinery/vending/exchange_parts(mob/user, obj/item/storage/part_replacer/replacer) if(!istype(replacer)) return FALSE - if((flags_1 & NODECONSTRUCT_1) && !replacer.works_from_distance) + if((obj_flags & NO_DECONSTRUCTION) && !replacer.works_from_distance) return FALSE if(!component_parts || !refill_canister) return FALSE @@ -1087,7 +1083,7 @@ replacer.play_rped_sound() return TRUE -/obj/machinery/vending/on_deconstruction() +/obj/machinery/vending/on_deconstruction(disassembled) update_canister() . = ..() @@ -1208,11 +1204,12 @@ for (var/datum/data/vending_product/product_record as anything in product_records + coin_records + hidden_records) var/list/product_data = list( name = product_record.name, + path = replacetext(replacetext("[product_record.product_path]", "/obj/item/", ""), "/", "-"), amount = product_record.amount, colorable = product_record.colorable, ) - .["stock"][product_record.name] = product_data + .["stock"][product_data["path"]] = product_data .["extended_inventory"] = extended_inventory @@ -1591,7 +1588,7 @@ if(loaded_item.custom_price) return TRUE -/obj/machinery/vending/custom/ui_interact(mob/user) +/obj/machinery/vending/custom/ui_interact(mob/user, datum/tgui/ui) if(!linked_account) balloon_alert(user, "no registered owner!") return FALSE @@ -1601,12 +1598,12 @@ . = ..() .["access"] = compartmentLoadAccessCheck(user) .["vending_machine_input"] = list() - for (var/stocked_item in vending_machine_input) + for (var/obj/item/stocked_item as anything in vending_machine_input) if(vending_machine_input[stocked_item] > 0) var/base64 var/price = 0 for(var/obj/item/stored_item in contents) - if(format_text(stored_item.name) == stocked_item) + if(stored_item.type == stocked_item) price = stored_item.custom_price if(!base64) //generate an icon of the item to use in UI if(base64_cache[stored_item.type]) @@ -1616,7 +1613,8 @@ base64_cache[stored_item.type] = base64 break var/list/data = list( - name = stocked_item, + path = stocked_item, + name = initial(stocked_item.name), price = price, img = base64, amount = vending_machine_input[stocked_item], @@ -1631,8 +1629,8 @@ switch(action) if("dispense") if(isliving(usr)) - vend_act(usr, params["item"]) - vend_ready = TRUE + vend_act(usr, params) + vend_ready = TRUE return TRUE /obj/machinery/vending/custom/attackby(obj/item/attack_item, mob/user, params) @@ -1656,23 +1654,23 @@ /obj/machinery/vending/custom/crowbar_act(mob/living/user, obj/item/attack_item) return FALSE -/obj/machinery/vending/custom/deconstruct(disassembled) +/obj/machinery/vending/custom/on_deconstruction(disassembled) unbuckle_all_mobs(TRUE) var/turf/current_turf = get_turf(src) if(current_turf) for(var/obj/item/stored_item in contents) stored_item.forceMove(current_turf) explosion(src, devastation_range = -1, light_impact_range = 3) - return ..() /** * Vends an item to the user. Handles all the logic: * Updating stock, account transactions, alerting users. * @return -- TRUE if a valid condition was met, FALSE otherwise. */ -/obj/machinery/vending/custom/proc/vend_act(mob/living/user, choice) +/obj/machinery/vending/custom/proc/vend_act(mob/living/user, list/params) if(!vend_ready) return + var/obj/item/choice = text2path(params["item"]) // typepath is a string coming from javascript, we need to convert it back var/obj/item/dispensed_item var/obj/item/card/id/id_card = user.get_idcard(TRUE) vend_ready = FALSE @@ -1681,8 +1679,8 @@ flick(icon_deny, src) return TRUE var/datum/bank_account/payee = id_card.registered_account - for(var/obj/stock in contents) - if(format_text(stock.name) == choice) + for(var/obj/item/stock in contents) + if(istype(stock, choice)) dispensed_item = stock break if(!dispensed_item) diff --git a/code/modules/vending/autodrobe.dm b/code/modules/vending/autodrobe.dm index 55e1915252822..59bd06135bb83 100644 --- a/code/modules/vending/autodrobe.dm +++ b/code/modules/vending/autodrobe.dm @@ -58,6 +58,8 @@ /obj/item/clothing/glasses/eyepatch = 1, /obj/item/clothing/glasses/eyepatch/medical = 1, /obj/item/clothing/under/costume/gi = 1, + /obj/item/clothing/head/soft/propeller_hat = 1, + /obj/item/clothing/neck/bowtie/rainbow = 1, ), ), list( @@ -66,6 +68,8 @@ "products" = list( /obj/item/clothing/suit/costume/imperium_monk = 1, /obj/item/clothing/suit/chaplainsuit/holidaypriest = 1, + /obj/item/clothing/suit/chaplainsuit/habit = 1, + /obj/item/clothing/head/chaplain/habit_veil = 1, /obj/item/clothing/suit/chaplainsuit/whiterobe = 1, /obj/item/clothing/head/wizard/marisa/fake = 1, /obj/item/clothing/suit/wizrobe/marisa/fake = 1, diff --git a/code/modules/vending/cartridge.dm b/code/modules/vending/cartridge.dm index dabf334c1f97e..7c4a2c9578d70 100644 --- a/code/modules/vending/cartridge.dm +++ b/code/modules/vending/cartridge.dm @@ -13,7 +13,7 @@ /obj/item/computer_disk/ordnance = 10, /obj/item/computer_disk/quartermaster = 10, /obj/item/computer_disk/command/captain = 3, - /obj/item/modular_computer/pda/heads = 10, + /obj/item/modular_computer/pda = 10, ) refill_canister = /obj/item/vending_refill/cart default_price = PAYCHECK_COMMAND diff --git a/code/modules/vending/clothesmate.dm b/code/modules/vending/clothesmate.dm index eeca2422e0c6c..47276acb25914 100644 --- a/code/modules/vending/clothesmate.dm +++ b/code/modules/vending/clothesmate.dm @@ -18,6 +18,7 @@ /obj/item/clothing/mask/bandana = 3, /obj/item/clothing/mask/bandana/striped = 3, /obj/item/clothing/mask/bandana/skull = 3, + /obj/item/clothing/mask/facescarf = 3, /obj/item/clothing/neck/scarf = 6, /obj/item/clothing/neck/large_scarf = 6, /obj/item/clothing/neck/large_scarf/red = 6, @@ -161,6 +162,8 @@ /obj/item/clothing/suit/hooded/ethereal_raincoat = 3, /obj/item/clothing/under/ethereal_tunic = 3, + /obj/item/clothing/mask/kitsune = 3, + /obj/item/clothing/mask/rebellion = 6, /obj/item/clothing/suit/costume/wellworn_shirt/graphic/ian = 1, /obj/item/clothing/suit/costume/irs = 20, /obj/item/clothing/head/costume/irs = 20, diff --git a/code/modules/vending/drinnerware.dm b/code/modules/vending/drinnerware.dm index 2e00d9d2a01a4..c37750a2d3d8e 100644 --- a/code/modules/vending/drinnerware.dm +++ b/code/modules/vending/drinnerware.dm @@ -14,6 +14,7 @@ /obj/item/kitchen/spoon/soup_ladle = 3, /obj/item/clothing/suit/apron/chef = 2, /obj/item/kitchen/rollingpin = 2, + /obj/item/kitchen/tongs = 2, /obj/item/knife/kitchen = 2, ), ), diff --git a/code/modules/vending/games.dm b/code/modules/vending/games.dm index 80fb135084198..33fefd08d2e79 100644 --- a/code/modules/vending/games.dm +++ b/code/modules/vending/games.dm @@ -52,7 +52,9 @@ /obj/item/skillchip/appraiser = 2, /obj/item/skillchip/basketweaving = 2, /obj/item/skillchip/bonsai = 2, + /obj/item/skillchip/intj = 2, /obj/item/skillchip/light_remover = 2, + /obj/item/skillchip/master_angler = 2, /obj/item/skillchip/sabrage = 2, /obj/item/skillchip/useless_adapter = 5, /obj/item/skillchip/wine_taster = 2, @@ -79,6 +81,7 @@ /obj/item/dice/fudge = 9, /obj/item/clothing/shoes/wheelys/skishoes = 4, /obj/item/instrument/musicalmoth = 1, + /obj/item/gun/ballistic/revolver/russian = 1, //the most dangerous game ) premium = list( /obj/item/disk/holodisk = 5, diff --git a/code/modules/vending/medical.dm b/code/modules/vending/medical.dm index 576cbb0b8b284..ad1c63e7e796f 100644 --- a/code/modules/vending/medical.dm +++ b/code/modules/vending/medical.dm @@ -19,6 +19,7 @@ /obj/item/stack/medical/bone_gel = 4, /obj/item/cane/white = 2, /obj/item/clothing/glasses/eyepatch/medical = 2, + /obj/item/storage/box/bandages = 2, ) contraband = list( /obj/item/storage/box/gum/happiness = 3, diff --git a/code/modules/vending/medical_wall.dm b/code/modules/vending/medical_wall.dm index 4fd120bdc4875..66badd4adf270 100644 --- a/code/modules/vending/medical_wall.dm +++ b/code/modules/vending/medical_wall.dm @@ -15,6 +15,7 @@ /obj/item/reagent_containers/medigel/sterilizine = 1, /obj/item/healthanalyzer/simple = 2, /obj/item/stack/medical/bone_gel = 2, + /obj/item/storage/box/bandages = 1, ) contraband = list( /obj/item/reagent_containers/pill/tox = 2, diff --git a/code/modules/vending/megaseed.dm b/code/modules/vending/megaseed.dm index 8bdc7e637e25a..130a0921a43a4 100644 --- a/code/modules/vending/megaseed.dm +++ b/code/modules/vending/megaseed.dm @@ -20,6 +20,7 @@ /obj/item/seeds/cocoapod = 3, /obj/item/seeds/eggplant = 3, /obj/item/seeds/grape = 3, + /obj/item/seeds/lanternfruit = 3, /obj/item/seeds/lemon = 3, /obj/item/seeds/lime = 3, /obj/item/seeds/olive = 3, diff --git a/code/modules/vending/security.dm b/code/modules/vending/security.dm index 6a7edd8e85413..ebecb03e1302c 100644 --- a/code/modules/vending/security.dm +++ b/code/modules/vending/security.dm @@ -16,6 +16,7 @@ /obj/item/storage/box/evidence = 6, /obj/item/flashlight/seclite = 4, /obj/item/restraints/legcuffs/bola/energy = 7, + /obj/item/clothing/gloves/tackler = 5, ) contraband = list( /obj/item/clothing/glasses/sunglasses = 2, @@ -26,9 +27,9 @@ /obj/item/coin/antagtoken = 1, /obj/item/clothing/head/helmet/blueshirt = 1, /obj/item/clothing/suit/armor/vest/blueshirt = 1, - /obj/item/clothing/gloves/tackler = 5, /obj/item/grenade/stingbang = 1, /obj/item/watertank/pepperspray = 2, + /obj/item/storage/belt/holster/energy = 4, ) refill_canister = /obj/item/vending_refill/security default_price = PAYCHECK_CREW @@ -41,8 +42,9 @@ G.arm_grenade() else if(istype(I, /obj/item/flashlight)) var/obj/item/flashlight/F = I - F.on = TRUE + F.set_light_on(TRUE) F.update_brightness() /obj/item/vending_refill/security + machine_name = "SecTech" icon_state = "refill_sec" diff --git a/code/modules/vending/snack.dm b/code/modules/vending/snack.dm index 23b1bcf2c71e3..1e81679135b3f 100644 --- a/code/modules/vending/snack.dm +++ b/code/modules/vending/snack.dm @@ -38,6 +38,7 @@ premium = list( /obj/item/food/spacers_sidekick = 3, /obj/item/food/pistachios = 3, + /obj/item/food/swirl_lollipop = 3, ) refill_canister = /obj/item/vending_refill/snack req_access = list(ACCESS_KITCHEN) diff --git a/code/modules/vending/sustenance.dm b/code/modules/vending/sustenance.dm index 1a2589fcdf152..d822912149087 100644 --- a/code/modules/vending/sustenance.dm +++ b/code/modules/vending/sustenance.dm @@ -44,11 +44,11 @@ if(isliving(user)) var/mob/living/living_user = user if(!(machine_stat & NOPOWER) && !istype(living_user.get_idcard(TRUE), /obj/item/card/id/advanced/prisoner)) - speak("No valid labor points account found. Vending is not permitted.") + speak("No valid prisoner account found. Vending is not permitted.") return return ..() -/obj/machinery/vending/sustenance/proceed_payment(obj/item/card/id/paying_id_card, datum/data/vending_product/product_to_vend, price_to_use) +/obj/machinery/vending/sustenance/labor_camp/proceed_payment(obj/item/card/id/paying_id_card, datum/data/vending_product/product_to_vend, price_to_use) if(!istype(paying_id_card, /obj/item/card/id/advanced/prisoner)) speak("I don't take bribes! Pay with labor points!") return FALSE @@ -66,7 +66,7 @@ paying_scum_id.points -= price_to_use return TRUE -/obj/machinery/vending/sustenance/fetch_balance_to_use(obj/item/card/id/passed_id) +/obj/machinery/vending/sustenance/labor_camp/fetch_balance_to_use(obj/item/card/id/passed_id) if(!istype(passed_id, /obj/item/card/id/advanced/prisoner)) return null //no points balance - no balance at all var/obj/item/card/id/advanced/prisoner/paying_scum_id = passed_id diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm index eebc8d63c06ea..7e41f93125eff 100644 --- a/code/modules/vending/wardrobes.dm +++ b/code/modules/vending/wardrobes.dm @@ -23,6 +23,7 @@ /obj/item/clothing/under/rank/security/officer/grey = 3, /obj/item/clothing/under/pants/slacks = 3, /obj/item/clothing/under/rank/security/officer/blueshirt = 3, + /obj/item/clothing/suit/armor/vest/secjacket = 3, /obj/item/clothing/suit/hooded/wintercoat/security = 3, /obj/item/clothing/suit/armor/vest = 3, /obj/item/clothing/gloves/color/black = 3, @@ -59,6 +60,7 @@ /obj/item/clothing/head/utility/surgerycap/green = 4, /obj/item/clothing/head/beret/medical/paramedic = 4, /obj/item/clothing/head/soft/paramedic = 4, + /obj/item/clothing/head/utility/head_mirror = 4, /obj/item/clothing/mask/bandana/striped/medical = 4, /obj/item/clothing/mask/surgical = 4, /obj/item/clothing/under/rank/medical/doctor = 4, @@ -169,6 +171,9 @@ /obj/item/storage/backpack/messenger = 3, /obj/item/storage/bag/mail = 3, /obj/item/radio/headset/headset_cargo = 3, + /obj/item/clothing/accessory/pocketprotector = 3, + /obj/item/clothing/head/utility/hardhat/orange = 3, + /obj/item/clothing/suit/hazardvest = 3, ) premium = list( /obj/item/clothing/head/costume/mailman = 1, @@ -521,6 +526,8 @@ /obj/item/storage/backpack/cultpack = 1, /obj/item/storage/fancy/candle_box = 2, /obj/item/radio/headset/headset_srv = 2, + /obj/item/clothing/suit/chaplainsuit/habit = 1, + /obj/item/clothing/head/chaplain/habit_veil = 1, ) contraband = list( /obj/item/toy/plush/ratplush = 1, @@ -633,6 +640,7 @@ /obj/item/clothing/under/rank/security/detective = 2, /obj/item/clothing/under/rank/security/detective/skirt = 2, /obj/item/clothing/suit/jacket/det_suit = 2, + /obj/item/clothing/suit/jacket/det_suit/brown = 2, /obj/item/clothing/shoes/sneakers/brown = 2, /obj/item/clothing/gloves/latex = 2, /obj/item/clothing/gloves/color/black = 2, diff --git a/code/modules/wiremod/components/abstract/equpiment_action.dm b/code/modules/wiremod/components/abstract/equpiment_action.dm deleted file mode 100644 index 17f931ae5e308..0000000000000 --- a/code/modules/wiremod/components/abstract/equpiment_action.dm +++ /dev/null @@ -1,65 +0,0 @@ -/obj/item/circuit_component/equipment_action - display_name = "Abstract Equipment Action" - desc = "You shouldn't be seeing this." - - /// The icon of the button - var/datum/port/input/option/icon_options - - /// The name to use for the button - var/datum/port/input/button_name - - /// Called when the user presses the button - var/datum/port/output/signal - -/obj/item/circuit_component/equipment_action/Initialize(mapload, default_icon) - . = ..() - - if (!isnull(default_icon)) - icon_options.set_input(default_icon) - - button_name = add_input_port("Name", PORT_TYPE_STRING) - - signal = add_output_port("Signal", PORT_TYPE_SIGNAL) - -/obj/item/circuit_component/equipment_action/populate_options() - var/static/action_options = list( - "Blank", - - "One", - "Two", - "Three", - "Four", - "Five", - - "Blood", - "Bomb", - "Brain", - "Brain Damage", - "Cross", - "Electricity", - "Exclamation", - "Heart", - "Id", - "Info", - "Injection", - "Magnetism", - "Minus", - "Network", - "Plus", - "Power", - "Question", - "Radioactive", - "Reaction", - "Repair", - "Say", - "Scan", - "Shield", - "Skull", - "Sleep", - "Wireless", - ) - - icon_options = add_option_port("Icon", action_options) - -/obj/item/circuit_component/equipment_action/proc/update_action() - return diff --git a/code/modules/wiremod/components/action/equpiment_action.dm b/code/modules/wiremod/components/action/equpiment_action.dm new file mode 100644 index 0000000000000..54150ca44d60b --- /dev/null +++ b/code/modules/wiremod/components/action/equpiment_action.dm @@ -0,0 +1,92 @@ +/obj/item/circuit_component/equipment_action + display_name = "Equipment Action" + desc = "Represents an action the user can take when using supported shells." + required_shells = list(/obj/item/organ/internal/cyberimp/bci, /obj/item/mod/module/circuit) + + /// The icon of the button + var/datum/port/input/option/icon_options + + /// The name to use for the button + var/datum/port/input/button_name + + /// The mob who activated their granted action + var/datum/port/output/user + + /// Called when the user presses the button + var/datum/port/output/signal + + /// An assoc list of datum REF()s, linked to the actions granted. + var/list/granted_to = list() + +/obj/item/circuit_component/equipment_action/Initialize(mapload, default_icon) + . = ..() + + if (!isnull(default_icon)) + icon_options.set_input(default_icon) + + button_name = add_input_port("Name", PORT_TYPE_STRING) + + user = add_output_port("User", PORT_TYPE_USER) + signal = add_output_port("Signal", PORT_TYPE_SIGNAL) + +/obj/item/circuit_component/equipment_action/Destroy() + QDEL_LIST_ASSOC_VAL(granted_to) + return ..() + +/obj/item/circuit_component/equipment_action/populate_options() + var/static/action_options = list( + "Blank", + + "One", + "Two", + "Three", + "Four", + "Five", + + "Blood", + "Bomb", + "Brain", + "Brain Damage", + "Cross", + "Electricity", + "Exclamation", + "Heart", + "Id", + "Info", + "Injection", + "Magnetism", + "Minus", + "Network", + "Plus", + "Power", + "Question", + "Radioactive", + "Reaction", + "Repair", + "Say", + "Scan", + "Shield", + "Skull", + "Sleep", + "Wireless", + ) + + icon_options = add_option_port("Icon", action_options) + +/obj/item/circuit_component/equipment_action/register_shell(atom/movable/shell) + . = ..() + SEND_SIGNAL(shell, COMSIG_CIRCUIT_ACTION_COMPONENT_REGISTERED, src) + +/obj/item/circuit_component/equipment_action/unregister_shell(atom/movable/shell) + . = ..() + SEND_SIGNAL(shell, COMSIG_CIRCUIT_ACTION_COMPONENT_UNREGISTERED, src) + +/obj/item/circuit_component/equipment_action/input_received(datum/port/input/port) + if (length(granted_to)) + update_actions() + +/obj/item/circuit_component/equipment_action/proc/update_actions() + for(var/ref in granted_to) + var/datum/action/granted_action = granted_to[ref] + granted_action.name = button_name.value || "Action" + granted_action.button_icon_state = "bci_[replacetextEx(lowertext(icon_options.value), " ", "_")]" diff --git a/code/modules/wiremod/components/action/pathfind.dm b/code/modules/wiremod/components/action/pathfind.dm index e7dcb16020745..0de6d346db17f 100644 --- a/code/modules/wiremod/components/action/pathfind.dm +++ b/code/modules/wiremod/components/action/pathfind.dm @@ -56,9 +56,11 @@ if(isnull(target_Y)) return - var/atom/path_id = id_card.value - if(path_id && !isidcard(path_id)) - path_id = null + var/list/access = list() + if(isidcard(id_card.value)) + var/obj/item/card/id/id = id_card.value + access = id.GetAccess() + else if (id_card.value) failed.set_output(COMPONENT_SIGNAL) reason_failed.set_output("Object marked is not an ID! Using no ID instead.") @@ -75,7 +77,7 @@ return // If we're going to the same place and the cooldown hasn't subsided, we're probably on the same path as before - if (destination == old_dest && TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME)) + if (destination == old_dest && TIMER_COOLDOWN_RUNNING(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME)) // Check if the current turf is the same as the current turf we're supposed to be in. If so, then we set the next step as the next turf on the list if(current_turf == next_turf) @@ -90,7 +92,7 @@ else // Either we're not going to the same place or the cooldown is over. Either way, we need a new path - if(destination != old_dest && TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_PATHFIND_DIF)) + if(destination != old_dest && TIMER_COOLDOWN_RUNNING(parent, COOLDOWN_CIRCUIT_PATHFIND_DIF)) failed.set_output(COMPONENT_SIGNAL) reason_failed.set_output("Cooldown still active!") return @@ -98,7 +100,7 @@ TIMER_COOLDOWN_END(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME) old_dest = destination - path = get_path_to(src, destination, max_range, id=path_id) + path = get_path_to(src, destination, max_range, access=access) if(length(path) == 0 || !path)// Check if we can even path there next_turf = null failed.set_output(COMPONENT_SIGNAL) diff --git a/code/modules/wiremod/components/action/radio.dm b/code/modules/wiremod/components/action/radio.dm index cd13e0e1e4515..3940059453ead 100644 --- a/code/modules/wiremod/components/action/radio.dm +++ b/code/modules/wiremod/components/action/radio.dm @@ -28,8 +28,17 @@ /// The ckey of the user who used the shell we were placed in, important for signalling logs. var/owner_ckey = null + /// The radio connection we are using to receive signals. var/datum/radio_frequency/radio_connection + /// How long of a cooldown we have before we can send another signal. + var/signal_cooldown_time = 1 SECONDS + +/obj/item/circuit_component/radio/Initialize(mapload) + . = ..() + if(signal_cooldown_time > 0) + desc = "[desc] It has a [signal_cooldown_time * 0.1] second cooldown between sending signals." + /obj/item/circuit_component/radio/register_shell(atom/movable/shell) parent_shell = shell var/potential_fingerprints = shell.fingerprintslast @@ -65,8 +74,10 @@ INVOKE_ASYNC(src, PROC_REF(handle_radio_input), port) /obj/item/circuit_component/radio/proc/handle_radio_input(datum/port/input/port) - var/frequency = freq.value + if(TIMER_COOLDOWN_RUNNING(parent, COOLDOWN_SIGNALLER_SEND)) + return + var/frequency = freq.value if(frequency != current_freq) SSradio.remove_object(src, current_freq) radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER) @@ -84,7 +95,8 @@ loggable_strings += ": The last fingerprints on the containing shell was [parent_shell.fingerprintslast]." var/loggable_string = loggable_strings.Join(" ") - GLOB.lastsignalers.Add(loggable_string) + add_to_signaler_investigate_log(loggable_string) + TIMER_COOLDOWN_START(parent, COOLDOWN_SIGNALLER_SEND, signal_cooldown_time) var/datum/signal/signal = new(list("code" = signal_code, "key" = parent?.owner_id), logging_data = loggable_string) radio_connection.post_signal(src, signal) diff --git a/code/modules/wiremod/components/action/soundemitter.dm b/code/modules/wiremod/components/action/soundemitter.dm index 6ee9b273fae39..44b9cbae8ab05 100644 --- a/code/modules/wiremod/components/action/soundemitter.dm +++ b/code/modules/wiremod/components/action/soundemitter.dm @@ -87,7 +87,7 @@ if(!parent.shell) return - if(TIMER_COOLDOWN_CHECK(parent.shell, COOLDOWN_CIRCUIT_SOUNDEMITTER)) + if(TIMER_COOLDOWN_RUNNING(parent.shell, COOLDOWN_CIRCUIT_SOUNDEMITTER)) return var/sound_to_play = options_map[sound_file.value] diff --git a/code/modules/wiremod/components/action/speech.dm b/code/modules/wiremod/components/action/speech.dm index 809c473c14a50..0e2936bcfbfdb 100644 --- a/code/modules/wiremod/components/action/speech.dm +++ b/code/modules/wiremod/components/action/speech.dm @@ -26,10 +26,10 @@ if(!parent.shell) return - if(TIMER_COOLDOWN_CHECK(parent.shell, COOLDOWN_CIRCUIT_SPEECH)) + if(TIMER_COOLDOWN_RUNNING(parent.shell, COOLDOWN_CIRCUIT_SPEECH)) return if(message.value) var/atom/movable/shell = parent.shell - shell.say(message.value, forced = "circuit speech | [key_name(parent.get_creator())]") + shell.say(message.value, forced = "circuit speech | [parent.get_creator()]") TIMER_COOLDOWN_START(shell, COOLDOWN_CIRCUIT_SPEECH, speech_cooldown) diff --git a/code/modules/wiremod/components/atom/matscanner.dm b/code/modules/wiremod/components/atom/matscanner.dm index bbc36bb978971..eb23efddb3aca 100644 --- a/code/modules/wiremod/components/atom/matscanner.dm +++ b/code/modules/wiremod/components/atom/matscanner.dm @@ -34,10 +34,7 @@ if(!istype(entity) || !IN_GIVEN_RANGE(location, entity, max_range)) result.set_output(null) return - var/breakdown_flags = BREAKDOWN_INCLUDE_ALCHEMY - if(break_down_alloys.value) - breakdown_flags |= BREAKDOWN_ALLOYS - var/list/composition = entity.get_material_composition(breakdown_flags) + var/list/composition = entity.get_material_composition() var/list/composition_but_with_string_keys = list() for(var/datum/material/material as anything in composition) composition_but_with_string_keys[material.name] = composition[material] diff --git a/code/modules/wiremod/components/bci/hud/bar_overlay.dm b/code/modules/wiremod/components/bci/hud/bar_overlay.dm index 5a1a46f5716ea..07d13a4ccfb9e 100644 --- a/code/modules/wiremod/components/bci/hud/bar_overlay.dm +++ b/code/modules/wiremod/components/bci/hud/bar_overlay.dm @@ -58,6 +58,7 @@ /datum/atom_hud/alternate_appearance/basic/one_person, "bar_overlay_[REF(src)]", cool_overlay, + null, owner, ) alt_appearance.show_to(owner) diff --git a/code/modules/wiremod/components/bci/hud/counter_overlay.dm b/code/modules/wiremod/components/bci/hud/counter_overlay.dm index d46c34ab047bf..a0f83b6f72eff 100644 --- a/code/modules/wiremod/components/bci/hud/counter_overlay.dm +++ b/code/modules/wiremod/components/bci/hud/counter_overlay.dm @@ -76,6 +76,7 @@ /datum/atom_hud/alternate_appearance/basic/one_person, "counter_overlay_[REF(src)]", counter, + null, owner, ) alt_appearance.show_to(owner) @@ -101,6 +102,7 @@ /datum/atom_hud/alternate_appearance/basic/one_person, "counter_overlay_[REF(src)]_[i]", number, + null, owner, ) number_alt_appearance.show_to(owner) diff --git a/code/modules/wiremod/components/bci/hud/object_overlay.dm b/code/modules/wiremod/components/bci/hud/object_overlay.dm index ad32a5408cd87..07e0f76761e55 100644 --- a/code/modules/wiremod/components/bci/hud/object_overlay.dm +++ b/code/modules/wiremod/components/bci/hud/object_overlay.dm @@ -27,8 +27,12 @@ var/datum/port/input/signal_on var/datum/port/input/signal_off + /// Reference to the BCI we're implanted inside var/obj/item/organ/internal/cyberimp/bci/bci + + /// Assoc list of REF to the target atom to the overlay alt appearance it is using var/list/active_overlays = list() + var/list/options_map /obj/item/circuit_component/object_overlay/populate_ports() @@ -42,8 +46,7 @@ image_rotation = add_input_port("Overlay Rotation", PORT_TYPE_NUMBER) /obj/item/circuit_component/object_overlay/Destroy() - for(var/active_overlay in active_overlays) - QDEL_NULL(active_overlay) + QDEL_LIST_ASSOC_VAL(active_overlays) return ..() /obj/item/circuit_component/object_overlay/populate_options() @@ -78,26 +81,24 @@ var/mob/living/owner = bci.owner var/atom/target_atom = target.value - if(!owner || !istype(owner) || !owner.client || !target_atom) + if(!istype(owner) || !owner.client || isnull(target_atom)) return if(COMPONENT_TRIGGERED_BY(signal_on, port)) show_to_owner(target_atom, owner) - if(COMPONENT_TRIGGERED_BY(signal_off, port) && (target_atom in active_overlays)) - var/datum/weakref/overlay_ref = active_overlays[target_atom] - var/datum/atom_hud/overlay = overlay_ref?.resolve() - QDEL_NULL(overlay) - active_overlays.Remove(target_atom) + var/datum/atom_hud/existing_overlay = active_overlays[REF(target_atom)] + if(COMPONENT_TRIGGERED_BY(signal_off, port) && !isnull(existing_overlay)) + qdel(existing_overlay) + active_overlays -= REF(target_atom) /obj/item/circuit_component/object_overlay/proc/show_to_owner(atom/target_atom, mob/living/owner) - if(LAZYLEN(active_overlays) >= OBJECT_OVERLAY_LIMIT) + if(length(active_overlays) >= OBJECT_OVERLAY_LIMIT) return - if(active_overlays[target_atom]) - var/datum/weakref/overlay_ref = active_overlays[target_atom] - var/datum/atom_hud/overlay = overlay_ref?.resolve() - QDEL_NULL(overlay) + var/datum/atom_hud/existing_overlay = active_overlays[REF(target_atom)] + if(!isnull(existing_overlay)) + qdel(existing_overlay) var/image/cool_overlay = image(icon = 'icons/hud/screen_bci.dmi', loc = target_atom, icon_state = options_map[object_overlay_options.value], layer = RIPPLE_LAYER) SET_PLANE_EXPLICIT(cool_overlay, ABOVE_LIGHTING_PLANE, target_atom) @@ -117,19 +118,16 @@ /datum/atom_hud/alternate_appearance/basic/one_person, "object_overlay_[REF(src)]", cool_overlay, + null, owner, ) alt_appearance.show_to(owner) - active_overlays[target_atom] = WEAKREF(alt_appearance) + active_overlays[REF(target_atom)] = alt_appearance /obj/item/circuit_component/object_overlay/proc/on_organ_removed(datum/source, mob/living/carbon/owner) SIGNAL_HANDLER - for(var/atom/target_atom in active_overlays) - var/datum/weakref/overlay_ref = active_overlays[target_atom] - var/datum/atom_hud/overlay = overlay_ref?.resolve() - QDEL_NULL(overlay) - active_overlays.Remove(target_atom) + QDEL_LIST_ASSOC_VAL(active_overlays) #undef OBJECT_OVERLAY_LIMIT diff --git a/code/modules/wiremod/components/bci/hud/target_intercept.dm b/code/modules/wiremod/components/bci/hud/target_intercept.dm index 8352b0aead991..bfdaec13122a0 100644 --- a/code/modules/wiremod/components/bci/hud/target_intercept.dm +++ b/code/modules/wiremod/components/bci/hud/target_intercept.dm @@ -42,7 +42,7 @@ if(!owner || !istype(owner) || !owner.client) return - if(TIMER_COOLDOWN_CHECK(parent.shell, COOLDOWN_CIRCUIT_TARGET_INTERCEPT)) + if(TIMER_COOLDOWN_RUNNING(parent.shell, COOLDOWN_CIRCUIT_TARGET_INTERCEPT)) return to_chat(owner, "Left-click to trigger target interceptor!") diff --git a/code/modules/wiremod/components/bci/thought_listener.dm b/code/modules/wiremod/components/bci/thought_listener.dm index 53d50f9c54b24..ae6889e2da904 100644 --- a/code/modules/wiremod/components/bci/thought_listener.dm +++ b/code/modules/wiremod/components/bci/thought_listener.dm @@ -26,7 +26,7 @@ /obj/item/circuit_component/thought_listener/populate_ports() input_name = add_input_port("Input Name", PORT_TYPE_STRING) input_desc = add_input_port("Input Description", PORT_TYPE_STRING) - output = add_output_port("Recieved Thought", PORT_TYPE_STRING) + output = add_output_port("Received Thought", PORT_TYPE_STRING) trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL) failure = add_output_port("On Failure", PORT_TYPE_SIGNAL) diff --git a/code/modules/wiremod/components/list/assoc_list_pick.dm b/code/modules/wiremod/components/list/assoc_list_pick.dm index 9c8e94c074a63..64dbe6a0a0b65 100644 --- a/code/modules/wiremod/components/list/assoc_list_pick.dm +++ b/code/modules/wiremod/components/list/assoc_list_pick.dm @@ -15,7 +15,6 @@ /obj/item/circuit_component/list_pick/assoc/make_list_port() input_list = add_input_port("List", PORT_TYPE_ASSOC_LIST(PORT_TYPE_STRING, PORT_TYPE_ANY)) - /obj/item/circuit_component/list_pick/assoc/pre_input_received(datum/port/input/port) if(port == list_options) var/new_type = list_options.value diff --git a/code/modules/wiremod/components/list/assoc_literal.dm b/code/modules/wiremod/components/list/assoc_literal.dm index a3f97cae74550..f829ad08f25ec 100644 --- a/code/modules/wiremod/components/list/assoc_literal.dm +++ b/code/modules/wiremod/components/list/assoc_literal.dm @@ -39,20 +39,20 @@ /obj/item/circuit_component/assoc_literal/populate_ports() AddComponent(/datum/component/circuit_component_add_port, \ - port_list = entry_ports, \ + port_list = key_ports, \ add_action = "add", \ remove_action = "remove", \ - port_type = PORT_TYPE_ANY, \ - prefix = "Index", \ + port_type = PORT_TYPE_STRING, \ + prefix = "Key", \ minimum_amount = 1, \ maximum_amount = 20 \ ) AddComponent(/datum/component/circuit_component_add_port, \ - port_list = key_ports, \ + port_list = entry_ports, \ add_action = "add", \ remove_action = "remove", \ - port_type = PORT_TYPE_STRING, \ - prefix = "Key", \ + port_type = PORT_TYPE_ANY, \ + prefix = "Value", \ minimum_amount = 1, \ maximum_amount = 20 \ ) diff --git a/code/modules/wiremod/components/list/list_pick.dm b/code/modules/wiremod/components/list/list_pick.dm index bce3e82688f88..a1e8141f3c34a 100644 --- a/code/modules/wiremod/components/list/list_pick.dm +++ b/code/modules/wiremod/components/list/list_pick.dm @@ -27,39 +27,24 @@ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL - var/index_type = PORT_TYPE_NUMBER - -/obj/item/circuit_component/list_pick/populate_options() - list_options = add_option_port("List Type", GLOB.wiremod_basic_types) - /obj/item/circuit_component/list_pick/proc/make_list_port() - input_list = add_input_port("List", PORT_TYPE_LIST(PORT_TYPE_ANY)) + input_list = add_input_port("List", PORT_TYPE_LIST(PORT_TYPE_STRING)) /obj/item/circuit_component/list_pick/populate_ports() input_name = add_input_port("Input Name", PORT_TYPE_STRING) - user = add_input_port("User", PORT_TYPE_ATOM) + user = add_input_port("User", PORT_TYPE_USER) make_list_port() - output = add_output_port("Picked Item", PORT_TYPE_NUMBER) - trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL) + output = add_output_port("Picked Item", PORT_TYPE_STRING) failure = add_output_port("On Failure", PORT_TYPE_SIGNAL) - success = add_output_port("On Succes", PORT_TYPE_SIGNAL) - - -/obj/item/circuit_component/list_pick/pre_input_received(datum/port/input/port) - if(port == list_options) - var/new_type = list_options.value - input_list.set_datatype(PORT_TYPE_LIST(new_type)) - output.set_datatype(new_type) - + success = add_output_port("On Success", PORT_TYPE_SIGNAL) /obj/item/circuit_component/list_pick/input_received(datum/port/input/port) - if(parent.Adjacent(user.value)) + var/mob/mob_user = user.value + if(!ismob(mob_user) || HAS_TRAIT_FROM(parent, TRAIT_CIRCUIT_UI_OPEN, REF(mob_user))) + failure.set_output(COMPONENT_SIGNAL) return - - if(ismob(user.value)) - trigger_output.set_output(COMPONENT_SIGNAL) - INVOKE_ASYNC(src, PROC_REF(show_list), user.value, input_name.value, input_list.value) + INVOKE_ASYNC(src, PROC_REF(show_list), mob_user, input_name.value, input_list.value) /// Show a list of options to the user using standed TGUI input list /obj/item/circuit_component/list_pick/proc/show_list(mob/user, message, list/showed_list) @@ -68,10 +53,17 @@ return if(!message) message = "circuit input" - if(!(user.can_perform_action(src, FORBID_TELEKINESIS_REACH))) + if(!(user.can_perform_action(parent.shell, FORBID_TELEKINESIS_REACH|ALLOW_SILICON_REACH|ALLOW_RESTING))) + failure.set_output(COMPONENT_SIGNAL) return + var/user_ref = REF(user) + ADD_TRAIT(parent, TRAIT_CIRCUIT_UI_OPEN, user_ref) var/picked = tgui_input_list(user, message = message, items = showed_list) - if(!(user.can_perform_action(src, FORBID_TELEKINESIS_REACH))) + REMOVE_TRAIT(parent, TRAIT_CIRCUIT_UI_OPEN, user_ref) + if(QDELETED(src)) + return + if(!(user.can_perform_action(parent.shell, FORBID_TELEKINESIS_REACH|ALLOW_SILICON_REACH|ALLOW_RESTING))) + failure.set_output(COMPONENT_SIGNAL) return choose_item(picked, showed_list) diff --git a/code/modules/wiremod/components/math/arctan2.dm b/code/modules/wiremod/components/math/arctan2.dm new file mode 100644 index 0000000000000..cce03df1d836f --- /dev/null +++ b/code/modules/wiremod/components/math/arctan2.dm @@ -0,0 +1,26 @@ +/** + * # Arctangent 2 function + * A variant of arctan. When given a deltaX and deltaY, returns the angle. I will blow you out of the sky + */ +/obj/item/circuit_component/arctan2 + display_name = "Arctangent 2 Component" + desc = "A two parameter arctan2 component, for calculating any angle you want." + category = "Math" + + /// The input port for the x-offset + var/datum/port/input/input_port_x + /// The input port for the y-offset + var/datum/port/input/input_port_y + + /// The result from the output + var/datum/port/output/output + + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + +/obj/item/circuit_component/arctan2/populate_ports() + input_port_x = add_input_port("Delta X", PORT_TYPE_NUMBER) + input_port_y = add_input_port("Delta Y", PORT_TYPE_NUMBER) + output = add_output_port("Angle", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/arctan2/input_received(datum/port/input/port) + output.set_output(arctan(input_port_x.value, input_port_y.value)) diff --git a/code/modules/wiremod/components/sensors/view_sensor.dm b/code/modules/wiremod/components/sensors/view_sensor.dm index f65853986e315..1725044c03597 100644 --- a/code/modules/wiremod/components/sensors/view_sensor.dm +++ b/code/modules/wiremod/components/sensors/view_sensor.dm @@ -37,7 +37,7 @@ if(!parent.shell) return - if(TIMER_COOLDOWN_CHECK(parent.shell, COOLDOWN_CIRCUIT_VIEW_SENSOR)) + if(TIMER_COOLDOWN_RUNNING(parent.shell, COOLDOWN_CIRCUIT_VIEW_SENSOR)) result.set_output(null) cooldown.set_output(COMPONENT_SIGNAL) return diff --git a/code/modules/wiremod/core/component.dm b/code/modules/wiremod/core/component.dm index 55fb258f0fbb3..39f3380907344 100644 --- a/code/modules/wiremod/core/component.dm +++ b/code/modules/wiremod/core/component.dm @@ -9,7 +9,7 @@ */ /obj/item/circuit_component name = COMPONENT_DEFAULT_NAME - icon = 'icons/obj/assemblies/module.dmi' + icon = 'icons/obj/devices/circuitry_n_data.dmi' icon_state = "component" inhand_icon_state = "electronic" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' @@ -174,6 +174,7 @@ qdel(input_port) if(parent) SStgui.update_uis(parent) + return null //explicitly set the port to null if used like this: `port = remove_input_port(port)` /** * Adds an output port and returns it @@ -203,6 +204,7 @@ qdel(output_port) if(parent) SStgui.update_uis(parent) + return null //explicitly set the port to null if used like this: `port = remove_output_port(port)` /** @@ -340,6 +342,7 @@ if(length(input_ports)) . += create_ui_notice("Power Usage Per Input: [power_usage_per_input]", "orange", "bolt") + /** * Called when a special button is pressed on this component in the UI. * diff --git a/code/modules/wiremod/core/component_printer.dm b/code/modules/wiremod/core/component_printer.dm index 25c684c5a28a0..7c691e3a4c47b 100644 --- a/code/modules/wiremod/core/component_printer.dm +++ b/code/modules/wiremod/core/component_printer.dm @@ -22,11 +22,7 @@ /obj/machinery/component_printer/Initialize(mapload) . = ..() - materials = AddComponent( \ - /datum/component/remote_materials, \ - mapload, \ - mat_container_flags = BREAKDOWN_FLAGS_LATHE, \ - ) + materials = AddComponent(/datum/component/remote_materials, mapload) /obj/machinery/component_printer/LateInitialize() . = ..() @@ -188,8 +184,7 @@ "cost" = cost, "id" = researched_design_id, "categories" = design.category, - "icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]", - "constructionTime" = -1 + "icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]" ) data["designs"] = designs @@ -323,11 +318,7 @@ /obj/machinery/module_duplicator/Initialize(mapload) . = ..() - materials = AddComponent( \ - /datum/component/remote_materials, \ - mapload, \ - mat_container_flags = BREAKDOWN_FLAGS_LATHE, \ - ) + materials = AddComponent(/datum/component/remote_materials, mapload) /obj/machinery/module_duplicator/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) diff --git a/code/modules/wiremod/core/integrated_circuit.dm b/code/modules/wiremod/core/integrated_circuit.dm index 4a04da3480efe..0499a56dcd474 100644 --- a/code/modules/wiremod/core/integrated_circuit.dm +++ b/code/modules/wiremod/core/integrated_circuit.dm @@ -11,7 +11,7 @@ GLOBAL_LIST_EMPTY_TYPED(integrated_circuits, /obj/item/integrated_circuit) /obj/item/integrated_circuit name = "integrated circuit" desc = "By inserting components and a cell into this, wiring them up, and putting them into a shell, anyone can pretend to be a programmer." - icon = 'icons/obj/assemblies/module.dmi' + icon = 'icons/obj/devices/circuitry_n_data.dmi' icon_state = "integrated_circuit" inhand_icon_state = "electronic" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' diff --git a/code/modules/wiremod/datatypes/datum.dm b/code/modules/wiremod/datatypes/datum.dm index f3faa8f5d8e1c..4045ee3793e66 100644 --- a/code/modules/wiremod/datatypes/datum.dm +++ b/code/modules/wiremod/datatypes/datum.dm @@ -4,6 +4,7 @@ datatype_flags = DATATYPE_FLAG_ALLOW_MANUAL_INPUT|DATATYPE_FLAG_ALLOW_ATOM_INPUT can_receive_from = list( PORT_TYPE_ATOM, + PORT_TYPE_USER ) /datum/circuit_datatype/datum/convert_value(datum/port/port, value_to_convert) diff --git a/code/modules/wiremod/datatypes/entity.dm b/code/modules/wiremod/datatypes/entity.dm index e3a76892f97d9..437b3d7adb49c 100644 --- a/code/modules/wiremod/datatypes/entity.dm +++ b/code/modules/wiremod/datatypes/entity.dm @@ -2,6 +2,9 @@ datatype = PORT_TYPE_ATOM color = "purple" datatype_flags = DATATYPE_FLAG_ALLOW_MANUAL_INPUT|DATATYPE_FLAG_ALLOW_ATOM_INPUT + can_receive_from = list( + PORT_TYPE_USER, + ) /datum/circuit_datatype/entity/convert_value(datum/port/port, value_to_convert) var/atom/object = value_to_convert diff --git a/code/modules/wiremod/datatypes/user.dm b/code/modules/wiremod/datatypes/user.dm new file mode 100644 index 0000000000000..693941dcd7409 --- /dev/null +++ b/code/modules/wiremod/datatypes/user.dm @@ -0,0 +1,9 @@ +/datum/circuit_datatype/user + datatype = PORT_TYPE_USER + color = "label" + +/datum/circuit_datatype/user/convert_value(datum/port/port, value_to_convert) + var/datum/object = value_to_convert + if(QDELETED(object)) + return null + return object diff --git a/code/modules/wiremod/shell/bot.dm b/code/modules/wiremod/shell/bot.dm index 36fd6c5b36993..533c654e787fa 100644 --- a/code/modules/wiremod/shell/bot.dm +++ b/code/modules/wiremod/shell/bot.dm @@ -9,7 +9,7 @@ icon_state = "setup_medium_box" density = FALSE - light_system = MOVABLE_LIGHT + light_system = OVERLAY_LIGHT light_on = FALSE /obj/structure/bot/Initialize(mapload) @@ -31,7 +31,7 @@ var/datum/port/output/entity /obj/item/circuit_component/bot/populate_ports() - entity = add_output_port("User", PORT_TYPE_ATOM) + entity = add_output_port("User", PORT_TYPE_USER) signal = add_output_port("Signal", PORT_TYPE_SIGNAL) /obj/item/circuit_component/bot/register_shell(atom/movable/shell) @@ -46,4 +46,3 @@ playsound(source, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) entity.set_output(user) signal.set_output(COMPONENT_SIGNAL) - diff --git a/code/modules/wiremod/shell/brain_computer_interface.dm b/code/modules/wiremod/shell/brain_computer_interface.dm index 89aac209c83e4..57bb2ed45cbb3 100644 --- a/code/modules/wiremod/shell/brain_computer_interface.dm +++ b/code/modules/wiremod/shell/brain_computer_interface.dm @@ -10,18 +10,16 @@ /obj/item/organ/internal/cyberimp/bci/Initialize(mapload) . = ..() + RegisterSignal(src, COMSIG_CIRCUIT_ACTION_COMPONENT_REGISTERED, PROC_REF(action_comp_registered)) + RegisterSignal(src, COMSIG_CIRCUIT_ACTION_COMPONENT_UNREGISTERED, PROC_REF(action_comp_unregistered)) + var/obj/item/integrated_circuit/circuit = new(src) - circuit.add_component(new /obj/item/circuit_component/equipment_action/bci(null, "One")) + circuit.add_component(new /obj/item/circuit_component/equipment_action(null, "One")) AddComponent(/datum/component/shell, list( new /obj/item/circuit_component/bci_core, ), SHELL_CAPACITY_SMALL, starting_circuit = circuit) -/obj/item/organ/internal/cyberimp/bci/on_insert(mob/living/carbon/receiver) - . = ..() - // Organs are put in nullspace, but this breaks circuit interactions - forceMove(receiver) - /obj/item/organ/internal/cyberimp/bci/say(message, bubble_type, list/spans, sanitize, datum/language/language, ignore_spam, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) if (owner) // Otherwise say_dead will be called. @@ -33,41 +31,17 @@ else return ..() -/obj/item/circuit_component/equipment_action/bci - display_name = "BCI Action" - desc = "Represents an action the user can take when implanted with the brain-computer interface." - required_shells = list(/obj/item/organ/internal/cyberimp/bci) - - /// A reference to the action button itself - var/datum/action/innate/bci_action/bci_action - -/obj/item/circuit_component/equipment_action/bci/Destroy() - QDEL_NULL(bci_action) - return ..() - -/obj/item/circuit_component/equipment_action/bci/register_shell(atom/movable/shell) - . = ..() - var/obj/item/organ/internal/cyberimp/bci/bci = shell - if(istype(bci)) - bci_action = new(src) - update_action() - - bci.actions += list(bci_action) - -/obj/item/circuit_component/equipment_action/bci/unregister_shell(atom/movable/shell) - var/obj/item/organ/internal/cyberimp/bci/bci = shell - if(istype(bci)) - bci.actions -= bci_action - QDEL_NULL(bci_action) - return ..() - -/obj/item/circuit_component/equipment_action/bci/input_received(datum/port/input/port) - if (!isnull(bci_action)) - update_action() +/obj/item/organ/internal/cyberimp/bci/proc/action_comp_registered(datum/source, obj/item/circuit_component/equipment_action/action_comp) + SIGNAL_HANDLER + LAZYADD(actions, new/datum/action/innate/bci_action(src, action_comp)) -/obj/item/circuit_component/equipment_action/bci/update_action() - bci_action.name = button_name.value - bci_action.button_icon_state = "bci_[replacetextEx(lowertext(icon_options.value), " ", "_")]" +/obj/item/organ/internal/cyberimp/bci/proc/action_comp_unregistered(datum/source, obj/item/circuit_component/equipment_action/action_comp) + SIGNAL_HANDLER + var/datum/action/innate/bci_action/action = action_comp.granted_to[REF(src)] + if(!istype(action)) + return + LAZYREMOVE(actions, action) + QDEL_LIST_ASSOC_VAL(action_comp.granted_to) /datum/action/innate/bci_action name = "Action" @@ -75,20 +49,23 @@ check_flags = AB_CHECK_CONSCIOUS button_icon_state = "bci_power" - var/obj/item/circuit_component/equipment_action/bci/circuit_component + var/obj/item/organ/internal/cyberimp/bci/bci + var/obj/item/circuit_component/equipment_action/circuit_component -/datum/action/innate/bci_action/New(obj/item/circuit_component/equipment_action/bci/circuit_component) +/datum/action/innate/bci_action/New(obj/item/organ/internal/cyberimp/bci/_bci, obj/item/circuit_component/equipment_action/circuit_component) ..() - + bci = _bci + circuit_component.granted_to[REF(_bci)] = src src.circuit_component = circuit_component /datum/action/innate/bci_action/Destroy() - circuit_component.bci_action = null + circuit_component.granted_to -= REF(bci) circuit_component = null return ..() /datum/action/innate/bci_action/Activate() + circuit_component.user.set_output(owner) circuit_component.signal.set_output(COMPONENT_SIGNAL) /obj/item/circuit_component/bci_core @@ -114,7 +91,7 @@ send_message_signal = add_input_port("Send Message", PORT_TYPE_SIGNAL) show_charge_meter = add_input_port("Show Charge Meter", PORT_TYPE_NUMBER, trigger = PROC_REF(update_charge_action)) - user_port = add_output_port("User", PORT_TYPE_ATOM) + user_port = add_output_port("User", PORT_TYPE_USER) /obj/item/circuit_component/bci_core/Destroy() QDEL_NULL(charge_action) @@ -306,7 +283,7 @@ . = ..() occupant_typecache = typecacheof(/mob/living/carbon) -/obj/machinery/bci_implanter/on_deconstruction() +/obj/machinery/bci_implanter/on_deconstruction(disassembled) drop_stored_bci() /obj/machinery/bci_implanter/Destroy() diff --git a/code/modules/wiremod/shell/compact_remote.dm b/code/modules/wiremod/shell/compact_remote.dm index 38fdfccf9515d..3336be06ddbf8 100644 --- a/code/modules/wiremod/shell/compact_remote.dm +++ b/code/modules/wiremod/shell/compact_remote.dm @@ -11,7 +11,7 @@ worn_icon_state = "electronic" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - light_system = MOVABLE_LIGHT_DIRECTIONAL + light_system = OVERLAY_LIGHT_DIRECTIONAL light_on = FALSE /obj/item/compact_remote/Initialize(mapload) diff --git a/code/modules/wiremod/shell/controller.dm b/code/modules/wiremod/shell/controller.dm index 7040e554a0a4a..ad03867b89be4 100644 --- a/code/modules/wiremod/shell/controller.dm +++ b/code/modules/wiremod/shell/controller.dm @@ -12,7 +12,7 @@ worn_icon_state = "electronic" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - light_system = MOVABLE_LIGHT_DIRECTIONAL + light_system = OVERLAY_LIGHT_DIRECTIONAL light_on = FALSE /obj/item/controller/Initialize(mapload) @@ -34,7 +34,7 @@ var/datum/port/output/entity /obj/item/circuit_component/controller/populate_ports() - entity = add_output_port("User", PORT_TYPE_ATOM) + entity = add_output_port("User", PORT_TYPE_USER) signal = add_output_port("Signal", PORT_TYPE_SIGNAL) alt = add_output_port("Alternate Signal", PORT_TYPE_SIGNAL) right = add_output_port("Extra Signal", PORT_TYPE_SIGNAL) @@ -51,6 +51,12 @@ COMSIG_CLICK_ALT, )) +/obj/item/circuit_component/controller/proc/handle_trigger(atom/source, user, port_name, datum/port/output/port_signal) + source.balloon_alert(user, "clicked [port_name] button") + playsound(source, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) + entity.set_output(user) + port_signal.set_output(COMPONENT_SIGNAL) + /** * Called when the shell item is used in hand */ @@ -58,10 +64,7 @@ SIGNAL_HANDLER if(!user.Adjacent(source)) return - source.balloon_alert(user, "clicked primary button") - playsound(source, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) - entity.set_output(user) - signal.set_output(COMPONENT_SIGNAL) + handle_trigger(source, user, "primary", signal) /** * Called when the shell item is alt-clicked @@ -70,10 +73,8 @@ SIGNAL_HANDLER if(!user.Adjacent(source)) return - source.balloon_alert(user, "clicked alternate button") - playsound(source, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) - entity.set_output(user) - alt.set_output(COMPONENT_SIGNAL) + handle_trigger(source, user, "alternate", alt) + /** * Called when the shell item is right-clicked in active hand @@ -82,7 +83,4 @@ SIGNAL_HANDLER if(!user.Adjacent(source)) return - source.balloon_alert(user, "clicked extra button") - playsound(source, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) - entity.set_output(user) - right.set_output(COMPONENT_SIGNAL) + handle_trigger(source, user, "extra", right) diff --git a/code/modules/wiremod/shell/dispenser.dm b/code/modules/wiremod/shell/dispenser.dm index 5fa2d2b1a8e0c..4ea2d03c9d706 100644 --- a/code/modules/wiremod/shell/dispenser.dm +++ b/code/modules/wiremod/shell/dispenser.dm @@ -9,7 +9,7 @@ icon_state = "setup_drone_arms" density = FALSE - light_system = MOVABLE_LIGHT + light_system = OVERLAY_LIGHT light_on = FALSE var/max_weight = WEIGHT_CLASS_NORMAL diff --git a/code/modules/wiremod/shell/drone.dm b/code/modules/wiremod/shell/drone.dm index dce5dca46adfe..96fe3b2fa0a7d 100644 --- a/code/modules/wiremod/shell/drone.dm +++ b/code/modules/wiremod/shell/drone.dm @@ -10,7 +10,7 @@ maxHealth = 300 health = 300 living_flags = 0 - light_system = MOVABLE_LIGHT_DIRECTIONAL + light_system = OVERLAY_LIGHT_DIRECTIONAL light_on = FALSE /mob/living/circuit_drone/Initialize(mapload) @@ -32,7 +32,7 @@ /mob/living/circuit_drone/updatehealth() . = ..() if(health < 0) - gib(no_brain = TRUE, no_organs = TRUE, no_bodyparts = TRUE) + gib() /mob/living/circuit_drone/welder_act(mob/living/user, obj/item/tool) . = ..() diff --git a/code/modules/wiremod/shell/gun.dm b/code/modules/wiremod/shell/gun.dm index e9176d8c6caa4..3a37501e8e17c 100644 --- a/code/modules/wiremod/shell/gun.dm +++ b/code/modules/wiremod/shell/gun.dm @@ -11,7 +11,7 @@ ammo_type = list(/obj/item/ammo_casing/energy/wiremod_gun) cell_type = /obj/item/stock_parts/cell/emproof/wiremod_gun item_flags = NONE - light_system = MOVABLE_LIGHT_DIRECTIONAL + light_system = OVERLAY_LIGHT_DIRECTIONAL light_on = FALSE automatic_charge_overlays = FALSE trigger_guard = TRIGGER_GUARD_ALLOW_ALL diff --git a/code/modules/wiremod/shell/keyboard.dm b/code/modules/wiremod/shell/keyboard.dm index 4231a6dc814d4..0b28959aa9c69 100644 --- a/code/modules/wiremod/shell/keyboard.dm +++ b/code/modules/wiremod/shell/keyboard.dm @@ -6,7 +6,7 @@ worn_icon_state = "electronic" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - light_system = MOVABLE_LIGHT_DIRECTIONAL + light_system = OVERLAY_LIGHT_DIRECTIONAL light_on = FALSE /obj/item/keyboard_shell/Initialize(mapload) @@ -27,7 +27,7 @@ var/datum/port/output/output /obj/item/circuit_component/keyboard_shell/populate_ports() - entity = add_output_port("User", PORT_TYPE_ATOM) + entity = add_output_port("User", PORT_TYPE_USER) output = add_output_port("Message", PORT_TYPE_STRING) signal = add_output_port("Signal", PORT_TYPE_SIGNAL) @@ -53,4 +53,3 @@ output.set_output(message) signal.set_output(COMPONENT_SIGNAL) - diff --git a/code/modules/wiremod/shell/module.dm b/code/modules/wiremod/shell/module.dm index bffff02e5fbca..6f9a3dda93b7e 100644 --- a/code/modules/wiremod/shell/module.dm +++ b/code/modules/wiremod/shell/module.dm @@ -10,8 +10,15 @@ /// A reference to the shell component, used to access the shell and its attached circuit var/datum/component/shell/shell + /// List of installed action components + var/list/obj/item/circuit_component/equipment_action/action_comps = list() + /obj/item/mod/module/circuit/Initialize(mapload) . = ..() + + RegisterSignal(src, COMSIG_CIRCUIT_ACTION_COMPONENT_REGISTERED, PROC_REF(action_comp_registered)) + RegisterSignal(src, COMSIG_CIRCUIT_ACTION_COMPONENT_UNREGISTERED, PROC_REF(action_comp_unregistered)) + shell = AddComponent(/datum/component/shell, \ list(new /obj/item/circuit_component/mod_adapter_core()), \ capacity = SHELL_CAPACITY_LARGE, \ @@ -22,6 +29,17 @@ if(drain_power(amount)) . = COMPONENT_OVERRIDE_POWER_USAGE +/obj/item/mod/module/circuit/proc/action_comp_registered(datum/source, obj/item/circuit_component/equipment_action/action_comp) + SIGNAL_HANDLER + action_comps += action_comp + +/obj/item/mod/module/circuit/proc/action_comp_unregistered(datum/source, obj/item/circuit_component/equipment_action/action_comp) + SIGNAL_HANDLER + action_comps -= action_comp + for(var/ref in action_comp.granted_to) + unpin_action(action_comp, locate(ref)) + QDEL_LIST_ASSOC_VAL(action_comp.granted_to) + /obj/item/mod/module/circuit/on_install() if(!shell?.attached_circuit) return @@ -30,6 +48,9 @@ /obj/item/mod/module/circuit/on_uninstall(deleting = FALSE) if(!shell?.attached_circuit) return + for(var/obj/item/circuit_component/equipment_action/action_comp in action_comps) + for(var/ref in action_comp.granted_to) + unpin_action(action_comp, locate(ref)) UnregisterSignal(shell?.attached_circuit, COMSIG_CIRCUIT_PRE_POWER_USAGE) /obj/item/mod/module/circuit/on_use() @@ -38,38 +59,79 @@ return if(!shell.attached_circuit) return - var/list/action_components = shell.attached_circuit.get_all_contents_type(/obj/item/circuit_component/equipment_action/mod) - if(!action_components.len) - shell.attached_circuit.interact(mod.wearer) + shell.attached_circuit?.interact(mod.wearer) + +/obj/item/mod/module/circuit/get_configuration(mob/user) + . = ..() + var/unnamed_action_index = 1 + for(var/obj/item/circuit_component/equipment_action/action_comp in action_comps) + .[REF(action_comp)] = add_ui_configuration(action_comp.button_name.value || "Unnamed Action [unnamed_action_index++]", "pin", !!action_comp.granted_to[REF(user)]) + +/obj/item/mod/module/circuit/configure_edit(key, value) + . = ..() + var/obj/item/circuit_component/equipment_action/action_comp = locate(key) in action_comps + if(!istype(action_comp)) + return + if(text2num(value)) + pin_action(action_comp, usr) + else + unpin_action(action_comp, usr) + +/obj/item/mod/module/circuit/proc/pin_action(obj/item/circuit_component/equipment_action/action_comp, mob/user) + if(!istype(user)) return - var/list/repeat_name_counts = list("Access Circuit" = 1) - var/list/display_names = list() - var/list/radial_options = list() - for(var/obj/item/circuit_component/equipment_action/mod/action_component in action_components) - var/action_name = action_component.button_name.value - if(!repeat_name_counts[action_name]) - repeat_name_counts[action_name] = 0 - repeat_name_counts[action_name]++ - if(repeat_name_counts[action_name] > 1) - action_name += " ([repeat_name_counts[action_name]])" - display_names[action_name] = REF(action_component) - var/option_icon_state = "bci_[replacetextEx(lowertext(action_component.icon_options.value), " ", "_")]" - radial_options += list("[action_name]" = image('icons/mob/actions/actions_items.dmi', option_icon_state)) - radial_options += list("Access Circuit" = image(shell.attached_circuit)) - var/selected_option = show_radial_menu(mod.wearer, src, radial_options, custom_check = FALSE, require_near = TRUE) - if(!selected_option) + if(action_comp.granted_to[REF(user)]) // Sanity check - don't pin an action for a mob that has already pinned it return - if(!mod || !mod.wearer || !mod.active || mod.activating) + mod.add_item_action(new/datum/action/item_action/mod/pinnable/circuit(mod, user, src, action_comp)) + +/obj/item/mod/module/circuit/proc/unpin_action(obj/item/circuit_component/equipment_action/action_comp, mob/user) + var/datum/action/item_action/mod/pinnable/circuit/action = action_comp.granted_to[REF(user)] + if(!istype(action)) return - if(selected_option == "Access Circuit") - shell.attached_circuit?.interact(mod.wearer) - else - var/component_reference = display_names[selected_option] - var/obj/item/circuit_component/equipment_action/mod/selected_component = locate(component_reference) in shell.attached_circuit.contents - if(!istype(selected_component)) - return - selected_component.signal.set_output(COMPONENT_SIGNAL) + qdel(action) +/datum/action/item_action/mod/pinnable/circuit + button_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "bci_blank" + + /// A reference to the module containing this action's component + var/obj/item/mod/module/circuit/module + + /// A reference to the component this action triggers. + var/obj/item/circuit_component/equipment_action/circuit_component + +/datum/action/item_action/mod/pinnable/circuit/New(Target, mob/user, obj/item/mod/module/circuit/linked_module, obj/item/circuit_component/equipment_action/action_comp) + . = ..() + module = linked_module + action_comp.granted_to[REF(user)] = src + circuit_component = action_comp + name = action_comp.button_name.value + button_icon_state = "bci_[replacetextEx(lowertext(action_comp.icon_options.value), " ", "_")]" + +/datum/action/item_action/mod/pinnable/circuit/Destroy() + circuit_component.granted_to -= REF(pinner) + circuit_component = null + + return ..() + +/datum/action/item_action/mod/pinnable/circuit/Trigger(trigger_flags) + . = ..() + if(!.) + return + var/obj/item/mod/control/mod = module.mod + if(!istype(mod)) + return + if(!mod.active || mod.activating) + if(mod.wearer) + module.balloon_alert(mod.wearer, "not active!") + return + circuit_component.user.set_output(owner) + circuit_component.signal.set_output(COMPONENT_SIGNAL) + +/// If the guy whose UI we are pinned to got deleted +/datum/action/item_action/mod/pinnable/circuit/pinner_deleted() + module?.action_comps[circuit_component] -= REF(pinner) + . = ..() /obj/item/circuit_component/mod_adapter_core display_name = "MOD circuit adapter core" @@ -123,7 +185,7 @@ toggle_suit = add_input_port("Toggle Suit", PORT_TYPE_SIGNAL) select_module = add_input_port("Select Module", PORT_TYPE_SIGNAL) // States - wearer = add_output_port("Wearer", PORT_TYPE_ATOM) + wearer = add_output_port("Wearer", PORT_TYPE_USER) deployed = add_output_port("Deployed", PORT_TYPE_NUMBER) activated = add_output_port("Activated", PORT_TYPE_NUMBER) selected_module = add_output_port("Selected Module", PORT_TYPE_STRING) @@ -237,8 +299,3 @@ if(!attached_module.mod?.wearer) return wearer.set_output(attached_module.mod.wearer) - -/obj/item/circuit_component/equipment_action/mod - display_name = "MOD action" - desc = "Represents an action the user can take when wearing the MODsuit." - required_shells = list(/obj/item/mod/module/circuit) diff --git a/code/modules/wiremod/shell/moneybot.dm b/code/modules/wiremod/shell/moneybot.dm index 46a834e2d6054..cacb457149dc7 100644 --- a/code/modules/wiremod/shell/moneybot.dm +++ b/code/modules/wiremod/shell/moneybot.dm @@ -9,7 +9,7 @@ icon_state = "setup_large" density = FALSE - light_system = MOVABLE_LIGHT + light_system = OVERLAY_LIGHT light_on = FALSE var/stored_money = 0 @@ -95,7 +95,7 @@ /obj/item/circuit_component/money_bot/populate_ports() total_money = add_output_port("Total Money", PORT_TYPE_NUMBER) money_input = add_output_port("Last Input Money", PORT_TYPE_NUMBER) - entity = add_output_port("User", PORT_TYPE_ATOM) + entity = add_output_port("User", PORT_TYPE_USER) money_trigger = add_output_port("Money Input", PORT_TYPE_SIGNAL) /obj/item/circuit_component/money_bot/register_shell(atom/movable/shell) diff --git a/code/modules/wiremod/shell/scanner.dm b/code/modules/wiremod/shell/scanner.dm index 3012fe65e490c..c88d7b1fb5f97 100644 --- a/code/modules/wiremod/shell/scanner.dm +++ b/code/modules/wiremod/shell/scanner.dm @@ -11,7 +11,7 @@ worn_icon_state = "electronic" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - light_system = MOVABLE_LIGHT_DIRECTIONAL + light_system = OVERLAY_LIGHT_DIRECTIONAL light_on = FALSE /obj/item/wiremod_scanner/Initialize(mapload) diff --git a/code/modules/wiremod/shell/server.dm b/code/modules/wiremod/shell/server.dm index fc71bfaa265b8..24501797197c7 100644 --- a/code/modules/wiremod/shell/server.dm +++ b/code/modules/wiremod/shell/server.dm @@ -10,7 +10,7 @@ icon_state = "setup_stationary" density = TRUE - light_system = MOVABLE_LIGHT + light_system = OVERLAY_LIGHT light_on = FALSE /obj/structure/server/Initialize(mapload) diff --git a/code/modules/zombie/items.dm b/code/modules/zombie/items.dm index 9d3a298812ea4..1d5caf8e9093b 100644 --- a/code/modules/zombie/items.dm +++ b/code/modules/zombie/items.dm @@ -11,20 +11,24 @@ bare_wound_bonus = 15 sharpness = SHARP_EDGED -/obj/item/mutant_hand/zombie/afterattack(atom/target, mob/user, proximity_flag) +/obj/item/mutant_hand/zombie/afterattack(atom/target, mob/living/user, proximity_flag) . = ..() if(!proximity_flag) return else if(isliving(target)) if(ishuman(target)) - try_to_zombie_infect(target) + try_to_zombie_infect(target, user, user.zone_selected) else . |= AFTERATTACK_PROCESSED_ITEM check_feast(target, user) -/proc/try_to_zombie_infect(mob/living/carbon/human/target) +/proc/try_to_zombie_infect(mob/living/carbon/human/target, mob/living/user, def_zone = BODY_ZONE_CHEST) CHECK_DNA_AND_SPECIES(target) + // Can't zombify with no head + if(!target.get_bodypart(BODY_ZONE_HEAD)) + return + if(HAS_TRAIT(target, TRAIT_NO_ZOMBIFY)) // cannot infect any TRAIT_NO_ZOMBIFY human return @@ -33,11 +37,31 @@ if(HAS_TRAIT(target, TRAIT_VIRUS_RESISTANCE) && prob(75)) return + var/obj/item/bodypart/actual_limb = target.get_bodypart(def_zone) + + // What you hitting bro? + if(!actual_limb) + return + + var/limb_damage = actual_limb.get_damage() + var/limb_armor = max(0, target.getarmor(actual_limb, BIO) - 25) + + // This is a pretty jank way to do this, but in short: + // if they have thick material on that bodypart it will always need at least 25 previous limb damage to trigger an infection. + // and if their bio armor isn't thick it's a bit weaker. + for(var/obj/item/clothing/iter_clothing in target.get_clothing_on_part(actual_limb)) + if(iter_clothing.clothing_flags & THICKMATERIAL) + limb_armor += 25 + + if(limb_armor > limb_damage) + return + var/obj/item/organ/internal/zombie_infection/infection infection = target.get_organ_slot(ORGAN_SLOT_ZOMBIE) if(!infection) infection = new() infection.Insert(target) + to_chat(user, span_alien("You see [target] twitch for a moment as [target.p_their()] head is covered in \a [infection] - [target.p_Theyve()] been infected.")) /obj/item/mutant_hand/zombie/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is ripping [user.p_their()] brains out! It looks like [user.p_theyre()] trying to commit suicide!")) @@ -50,12 +74,12 @@ if(target.stat == DEAD) var/hp_gained = target.maxHealth target.investigate_log("has been devoured by a zombie.", INVESTIGATE_DEATHS) - target.gib() - // zero as argument for no instant health update - user.adjustBruteLoss(-hp_gained, 0) - user.adjustToxLoss(-hp_gained, 0) - user.adjustFireLoss(-hp_gained, 0) - user.adjustCloneLoss(-hp_gained, 0) - user.updatehealth() - user.adjustOrganLoss(ORGAN_SLOT_BRAIN, -hp_gained) // Zom Bee gibbers "BRAAAAISNSs!1!" + target.gib(DROP_ALL_REMAINS) + var/need_mob_update + need_mob_update = user.adjustBruteLoss(-hp_gained, updating_health = FALSE) + need_mob_update += user.adjustToxLoss(-hp_gained, updating_health = FALSE) + need_mob_update += user.adjustFireLoss(-hp_gained, updating_health = FALSE) + need_mob_update += user.adjustOrganLoss(ORGAN_SLOT_BRAIN, -hp_gained) // Zom Bee gibbers "BRAAAAISNSs!1!" user.set_nutrition(min(user.nutrition + hp_gained, NUTRITION_LEVEL_FULL)) + if(need_mob_update) + user.updatehealth() diff --git a/code/modules/zombie/organs.dm b/code/modules/zombie/organs.dm index 3530146f1a2aa..2ed2bf541d88c 100644 --- a/code/modules/zombie/organs.dm +++ b/code/modules/zombie/organs.dm @@ -23,24 +23,36 @@ GLOB.zombie_infection_list -= src . = ..() -/obj/item/organ/internal/zombie_infection/Insert(mob/living/carbon/M, special = FALSE, drop_if_replaced = TRUE) +/obj/item/organ/internal/zombie_infection/on_mob_insert(mob/living/carbon/M, special = FALSE, movement_flags) . = ..() - if(!.) - return . + START_PROCESSING(SSobj, src) -/obj/item/organ/internal/zombie_infection/Remove(mob/living/carbon/M, special = FALSE) +/obj/item/organ/internal/zombie_infection/on_mob_remove(mob/living/carbon/M, special = FALSE) . = ..() STOP_PROCESSING(SSobj, src) - if(iszombie(M) && old_species && !special && !QDELETED(src)) + if(iszombie(M) && old_species && !special) M.set_species(old_species) if(timer_id) deltimer(timer_id) +/obj/item/organ/internal/zombie_infection/on_mob_insert(mob/living/carbon/organ_owner, special) + . = ..() + RegisterSignal(organ_owner, COMSIG_LIVING_DEATH, PROC_REF(organ_owner_died)) + +/obj/item/organ/internal/zombie_infection/on_mob_remove(mob/living/carbon/organ_owner, special) + . = ..() + UnregisterSignal(organ_owner, COMSIG_LIVING_DEATH) + +/obj/item/organ/internal/zombie_infection/proc/organ_owner_died(mob/living/carbon/source, gibbed) + SIGNAL_HANDLER + if(iszombie(source)) + qdel(src) // Congrats you somehow died so hard you stopped being a zombie + /obj/item/organ/internal/zombie_infection/on_find(mob/living/finder) - to_chat(finder, "Inside the head is a disgusting black \ + to_chat(finder, span_warning("Inside the head is a disgusting black \ web of pus and viscera, bound tightly around the brain like some \ - biological harness.") + biological harness.")) /obj/item/organ/internal/zombie_infection/process(seconds_per_tick, times_fired) if(!owner) diff --git a/config/awaymissionconfig.txt b/config/awaymissionconfig.txt index 5338ae36cc6f1..15d6b0ac71871 100644 --- a/config/awaymissionconfig.txt +++ b/config/awaymissionconfig.txt @@ -7,12 +7,11 @@ #Do NOT tick the maps during compile -- the game uses this list to decide which map to load. Ticking the maps will result in them ALL being loaded at once. #DO tick the associated code file for the away mission you are enabling. Otherwise, the map will be trying to reference objects which do not exist, which will cause runtime errors! -#_maps/RandomZLevels/blackmarketpackers.dmm #_maps/RandomZLevels/TheBeach.dmm -#_maps/RandomZLevels/centcomAway.dmm #_maps/RandomZLevels/moonoutpost19.dmm #_maps/RandomZLevels/undergroundoutpost45.dmm #_maps/RandomZLevels/caves.dmm #_maps/RandomZLevels/snowdin.dmm #_maps/RandomZLevels/research.dmm #_maps/RandomZLevels/SnowCabin.dmm +#_maps/RandomZLevels/museum.dmm diff --git a/config/blanks.json b/config/blanks.json index 299fa67a594b3..f3b38d67bdb4b 100644 --- a/config/blanks.json +++ b/config/blanks.json @@ -545,7 +545,7 @@ "
    ", "
    By writing and signing this form, you consent to the processing of your personal data by Nanotrasen Corporation.

    ", "
    ", - "

    Name of offical to take action:

    ", + "

    Name of official to take action:

    ", "

    [___________________________________]

    ", "

    Official Decision:

    ", "

    [___________________________________]

    ", diff --git a/config/config.txt b/config/config.txt index 5a903ce557f23..1c87fc110ae9a 100644 --- a/config/config.txt +++ b/config/config.txt @@ -126,11 +126,14 @@ VOTE_PERIOD 600 ## players' votes default to "No vote" (otherwise, default to "No change") # DEFAULT_NO_VOTE -## disable abandon mob -NORESPAWN +## Determines if players can respawn after death +## 0 = Cannot respawn (default) +## 1 = Can respawn +## 2 = Can respawn if choosing a different character +ALLOW_RESPAWN 0 -## Respawn delay (deciseconds), which doesn't allow to return to lobby (default 10 minutes) -#RESPAWN_DELAY 6000 +## Respawn delay (deciseconds), which doesn't allow to return to lobby +RESPAWN_DELAY 0 ## set a hosted by name for unix platforms HOSTEDBY Yournamehere @@ -407,8 +410,8 @@ MINUTE_CLICK_LIMIT 400 ## Game Chat Message Options ## Various messages to be sent to connected chat channels. -## Uncommenting these will enable them, by default they will be broadcast to Game chat channels on TGS3 or non-admin channels on TGS4. -## If using TGS4, the string option can be set as one of more chat channel tags (separated by ','s) to limit the message to channels with that tag name (case-sensitive). This will have no effect on TGS3. +## Uncommenting these will enable them, by default they will be broadcast to Game chat channels on TGS3 or non-admin channels on TGS>=4. +## If using TGS>=4, the string option can be set as one of more chat channel tags (separated by ','s) to limit the message to channels with that tag name (case-sensitive). This will have no effect on TGS3. ## i.e. CHANNEL_ANNOUNCE_NEW_GAME chat_channel_tag ## Which channel will have a message about a new game starting, message includes the station name. @@ -529,9 +532,9 @@ URGENT_AHELP_COOLDOWN 300 MOTD motd.txt #MOTD motd_extra.txt -## Assets can opt-in to caching their results into `tmp`. +## Assets can opt-in to caching their results into `cache/`. ## This is important, as preferences assets take upwards of 30 seconds (without sleeps) to collect. -## The cache is assumed to be cleared by TGS recompiling, which deletes `tmp`. +## The cache is assumed to be cleared by TGS recompiling, which deletes `cache/`. ## This should be disabled (through `CACHE_ASSETS 0`) on development, ## but enabled on production (the default). CACHE_ASSETS 0 diff --git a/config/game_options.txt b/config/game_options.txt index ae90dcb56b91e..e756865032c1f 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -529,3 +529,32 @@ DISALLOW_TITLE_MUSIC ## This is primarily useful for developing tutorials. If you have a proper DB setup, you ## don't need (or want) this. #GIVE_TUTORIALS_WITHOUT_DB + +## Configuration for station traits of each type. +## The first value (key) is the budget, or the space available to use for station traits of that type. Some take more space than others. +## The second value (assoc) is the weight associated with said budget compared to the rest for that type. +POSITIVE_STATION_TRAITS 0 8 +POSITIVE_STATION_TRAITS 1 4 +POSITIVE_STATION_TRAITS 2 2 +POSITIVE_STATION_TRAITS 3 1 + +NEUTRAL_STATION_TRAITS 0 10 +NEUTRAL_STATION_TRAITS 1 10 +NEUTRAL_STATION_TRAITS 2 3 +NEUTRAL_STATION_TRAITS 2.5 1 + +NEGATIVE_STATION_TRAITS 0 8 +NEGATIVE_STATION_TRAITS 1 4 +NEGATIVE_STATION_TRAITS 2 2 +NEGATIVE_STATION_TRAITS 3 1 + +# Uncomment to disable Quirk point balancing for the server and clients. +# If enabled, players will be able to select positive quirks without first selecting negative quirks. +# If enabled, randomized Quirks will still use points internally, in order to maintain balance. +#DISABLE_QUIRK_POINTS + +# The maximum amount of positive quirks one character can have at roundstart. +# If set to -1, then players will be able to select any quantity of positive quirks. +# If set to 0, then players won't be able to select any positive quirks. +# If commented-out or undefined, the maximum default is 6. +MAX_POSITIVE_QUIRKS 6 diff --git a/config/iceruinblacklist.txt b/config/iceruinblacklist.txt index 6ce22cdba3616..420a9d79a28fc 100644 --- a/config/iceruinblacklist.txt +++ b/config/iceruinblacklist.txt @@ -13,6 +13,7 @@ #_maps/RandomRuins/IceRuins/icemoon_underground_lavaland.dmm ##MISC +#_maps/RandomRuins/IceRuins/icemoon_surface_gas.dmm #_maps/RandomRuins/IceRuins/icemoon_surface_lust.dmm #_maps/RandomRuins/IceRuins/icemoon_surface_asteroid.dmm #_maps/RandomRuins/IceRuins/icemoon_surface_engioutpost.dmm diff --git a/config/jobconfig.toml b/config/jobconfig.toml index f1a9b65d594b9..c8a5c5c3dfa5d 100644 --- a/config/jobconfig.toml +++ b/config/jobconfig.toml @@ -46,6 +46,13 @@ "# Spawn Positions" = 1 "# Total Positions" = 1 +[BITRUNNER] +"# Playtime Requirements" = 0 +"# Required Account Age" = 0 +"# Required Character Age" = 0 +"# Spawn Positions" = 3 +"# Total Positions" = 3 + [BOTANIST] "# Playtime Requirements" = 0 "# Required Account Age" = 0 diff --git a/config/lavaruinblacklist.txt b/config/lavaruinblacklist.txt index 6f90b1f254e1b..d40c4386d31de 100644 --- a/config/lavaruinblacklist.txt +++ b/config/lavaruinblacklist.txt @@ -23,20 +23,21 @@ #_maps/RandomRuins/LavaRuins/lavaland_surface_sloth.dmm ##MISC +#_maps/RandomRuins/AnywhereRuins/fountain_hall.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_automated_trade_outpost.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_ww_vault.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_automated_trade_outpost.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_wwiioutpost.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_tomb.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_gaia.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_elephant_graveyard.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_gaia.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm -#_maps/RandomRuins/AnywhereRuins/fountain_hall.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_phonebooth.dmm \ No newline at end of file +#_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_phonebooth.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_tomb.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_watcher_grave.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_ww_vault.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_wwiioutpost.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm diff --git a/config/spaceruinblacklist.txt b/config/spaceruinblacklist.txt index f0851b1474ce1..dcff6527bae80 100644 --- a/config/spaceruinblacklist.txt +++ b/config/spaceruinblacklist.txt @@ -41,6 +41,10 @@ #_maps/RandomRuins/SpaceRuins/fasttravel.dmm #_maps/RandomRuins/SpaceRuins/forgottenship.dmm #_maps/RandomRuins/SpaceRuins/forgottenship.dmm +#_maps/RandomRuins/SpaceRuins/garbagetruck1.dmm +#_maps/RandomRuins/SpaceRuins/garbagetruck2.dmm +#_maps/RandomRuins/SpaceRuins/garbagetruck3.dmm +#_maps/RandomRuins/SpaceRuins/garbagetruck4.dmm #_maps/RandomRuins/SpaceRuins/gondolaasteroid.dmm #_maps/RandomRuins/SpaceRuins/hellfactory.dmm #_maps/RandomRuins/SpaceRuins/hellfactory.dmm diff --git a/cutter_templates/bitmask/cardinal_32x32.toml b/cutter_templates/bitmask/cardinal_32x32.toml new file mode 100644 index 0000000000000..9d3d4097e78ce --- /dev/null +++ b/cutter_templates/bitmask/cardinal_32x32.toml @@ -0,0 +1,36 @@ +mode = "BitmaskSlice" + +# Don't try and put directions in our icon states +produce_dirs = false +# We smooth only with our cardinal neighbors, not the ones on the diagonal +smooth_diagonally = false + +# Take as input a set of 32x32 blocks +[icon_size] +x = 32 +y = 32 + +# Output our stuff at the same level as it's input +[output_icon_pos] +x = 0 +y = 0 + +# And at the same width/height too +[output_icon_size] +x = 32 +y = 32 + +# This defines where in our list of blocks we draw each "direction" from +# no connections, east/west, north/south, and north/south/east/west +# the 0-3 is the block to read from, starting at 0 +[positions] +convex = 0 +horizontal = 1 +vertical = 2 +concave = 3 + +# When we cut up our blockls, we're cutting "around" a central point +# We typically want to cut around the center, so let's do that here +[cut_pos] +x = 16 +y = 16 diff --git a/cutter_templates/bitmask/diagonal_32x32.toml b/cutter_templates/bitmask/diagonal_32x32.toml new file mode 100644 index 0000000000000..1e80e3627e920 --- /dev/null +++ b/cutter_templates/bitmask/diagonal_32x32.toml @@ -0,0 +1,12 @@ +template = "bitmask/cardinal_32x32" + +# We're diagonal +smooth_diagonally = true + +# And because of that we need a state for all directions +[positions] +convex = 0 +vertical = 1 +horizontal = 2 +concave = 3 +flat = 4 diff --git a/dependencies.sh b/dependencies.sh index 6162b349b5141..04e1fb6240433 100644 --- a/dependencies.sh +++ b/dependencies.sh @@ -4,18 +4,18 @@ #Final authority on what's required to fully build the project # byond version -export BYOND_MAJOR=514 -export BYOND_MINOR=1588 +export BYOND_MAJOR=515 +export BYOND_MINOR=1630 #rust_g git tag -export RUST_G_VERSION=3.0.0 +export RUST_G_VERSION=3.1.0 #node version export NODE_VERSION=14 export NODE_VERSION_PRECISE=14.16.1 # SpacemanDMM git tag -export SPACEMAN_DMM_VERSION=suite-1.7.3 +export SPACEMAN_DMM_VERSION=suite-1.8 # Python version for mapmerge and other tools export PYTHON_VERSION=3.9.0 @@ -25,3 +25,9 @@ export AUXLUA_REPO=tgstation/auxlua #auxlua git tag export AUXLUA_VERSION=1.4.1 + +#hypnagogic repo +export CUTTER_REPO=actioninja/hypnagogic + +#hypnagogic git tag +export CUTTER_VERSION=v3.0.1 diff --git a/html/changelogs/AutoChangeLog-pr-78426.yml b/html/changelogs/AutoChangeLog-pr-78426.yml deleted file mode 100644 index d5afd71a5479c..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78426.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Shadow-Quill" -delete-after: True -changes: - - rscadd: "The Message Monitor console's board can now be obtained via the telecoms research node." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-78478.yml b/html/changelogs/AutoChangeLog-pr-78478.yml deleted file mode 100644 index e3eedb82a8e55..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-78478.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "ArcaneMusic" -delete-after: True -changes: - - bugfix: "Conveyor belts now properly show their new screentips on mouseover with tools." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81435.yml b/html/changelogs/AutoChangeLog-pr-81435.yml new file mode 100644 index 0000000000000..0ef4705974e09 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81435.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - rscadd: "Added three new 'special' bedsheets. One of them is quite rare and made from gondola hide." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81482.yml b/html/changelogs/AutoChangeLog-pr-81482.yml new file mode 100644 index 0000000000000..7306b7c28b4de --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81482.yml @@ -0,0 +1,4 @@ +author: "00-Steven" +delete-after: True +changes: + - bugfix: "Space cats CAN into space. (They're back to surviving being in unsuitable atmos.)" \ No newline at end of file diff --git a/html/changelogs/archive/2023-09.yml b/html/changelogs/archive/2023-09.yml index 1714eb30c8f60..2759082984006 100644 --- a/html/changelogs/archive/2023-09.yml +++ b/html/changelogs/archive/2023-09.yml @@ -825,3 +825,427 @@ - balance: Peacekeeper cyborg's emagged hug is no longer a hardstun. intercepti0n: - image: resprites t-ray scanner. +2023-09-23: + ArcaneMusic: + - bugfix: Conveyor belts now properly show their new screentips on mouseover with + tools. + Ben10Omintrix: + - refactor: seedlings have been refactored into basic mobs please report any bugs + - rscadd: seedlings now can have different colored petals and can look after botanys + plants + - rscadd: seedlings are re-added to the game! they grow out of seedling seeds obtainable + from exotic seed crates or traitor uplink + GPeckman: + - admin: Boneless smite should work properly again. + LT3: + - rscadd: Poly now causes a power surge when dusted by the supermatter crystal + - bugfix: Ghosts and godmode mobs will no longer create resonance when touching + the supermatter crystal + Shadow-Quill: + - rscadd: The Message Monitor console's board can now be obtained via the telecoms + research node. + distributivgesetz, CoiledLamb: + - rscadd: 'Added two new awards specifically for engineering and medical: The "Emergency + Services Award" and the "Atmospheric Mastery Award". CEs get 3 Emergency Services + Awards and 1 Atmospheric Mastery Award and CMOs get 3 Emergency Services Awards.' + lizardqueenlexi: + - bugfix: Foods that have special conditions for liking/disliking them (such as + donuts for sec officers) have these conditions again. + - bugfix: Characters with ageusia properly ignore non-toxic food types that they + eat. + - bugfix: If you examine toxic food, it can no longer appear to you as edible. + mc-oofert: + - bugfix: clown bomb payload is no longer named badmin payload and no longer disperses + clowns in cardinal directions only +2023-09-24: + GPeckman: + - bugfix: Epinephrine will now update health properly. + Jacquerel: + - rscadd: Brimdemon corpses release an explosion shortly after death, just to keep + you on your toes. + - refactor: Brimdemons now use the basic mob framework which (should) improve their + pathfinding somewhat. Please bug report any unusual behaviour. + - admin: The brimdemon's beam ability can be given to any mob, for your Binding + of Isaac event + - admin: Admins can now reset or modify the chaplain's sect from a UI panel + LT3: + - bugfix: Fixed supermatter surges always being the lowest severity + Melbert: + - bugfix: Maybe fixes some weird occurrences where you lose the ability to pass + over tables when you shouldn't, and visa versa + jlsnow301: + - rscadd: Adds Bitrunning to supply department- a semi-offstation role that rewards + teamwork. + - rscadd: Adds new machines to complement the job- net pod, quantum server, quantum + consoles, and the nexacache vendor. + - rscadd: Adds several new maps which can be loaded and unloaded at will. + - rscadd: Some flair for the new bitrunning vendor. + - rscadd: Adds a new antagonist for the virtual domain only. Short lived ghost role + that fights bitrunners. + - rscdel: Removes the BEPIS machine, moves its tech into the Bitrunning vendor. + mc-oofert: + - refactor: clowns are basicmobs now +2023-09-25: + Ghommie: + - bugfix: Fixed beams rendering below mobs by default. + - bugfix: The fishing line beam is no longer emissive (it doesn't glow in the dark). + LemonInTheDark: + - admin: First time user connections are now logged + Melbert: + - rscadd: Changelings can now speak through their decoy brain if it is placed in + an MMI, to maintain the illusion they are actually dead and have been debrained. + Pickle-Coding: + - qol: NT CIMs shows how much power the supermatter is releasing. + - qol: NT CIMs internal energy will adjust its prefix. + - qol: Energy displays (such as multitooling grid) will use the full range of SI + prefixes available, up to the peta prefix if you somehow managed to reach that. + - rscdel: Removes the per cubic centimeter part of internal energy. + - bugfix: Fix unnecessary delta time scaling on inactive supermatters. + - bugfix: Fix high energy zaps not scaling with delta time. + - bugfix: Fixes grounding rods lying about potential power you can generate. + - code_imp: Convert supermatter_zap() and tesla_zap() zap_str argument unit to be + in joules, and scales everything that uses that argument. + Singul0: + - bugfix: Syndicate Modsuit AI's now downloads the current codespeak book upon being + downloaded. + Vekter: + - bugfix: Fixes Birdshot's recycler being turned the wrong direction. + Watermelon914: + - bugfix: Fixed the overflow role having less slots than it actually should. + - bugfix: Fixed players being able to roll antagonist without ever being eligible + to play any role. Players who have their preferences set up so that they're + likely to return to lobby when the round starts have a lowered chance of becoming + antagonist. + carlarctg: + - rscadd: Added the Hippocrates bust to medbay heirlooms. Paramedics don't get one. + - rscadd: You can now swear the Hippocratic oath with these busts! It'll give you + pacifism but nothing else. The process is reversible. + - rscadd: There's a very small chance that the Hippocrates bust was once wielded + by a certain German doctor. This chance is increased for coroner heirlooms. + distributivgesetz: + - bugfix: Clamping/closing a wound should now heal the bodypart that was damaged + instead of a random one. + nikothedude: + - code_imp: New flags/args to electrocute_act() + starrm4nn: + - balance: Makes it so Ephedrine spasms have a 10 * (1.5 - purity)% chance per second + to happen, Adding a downside to pure Ephedrine + timothymtorres: + - bugfix: Fix secret documents steal objective failing while inside folder. +2023-09-26: + Ben10Omintrix: + - rscadd: added ranged attack friendly fire checks for basic mobs. minebots and + hivebots will now try to avoid shooting their friends + GPeckman: + - bugfix: Stun immune people should no longer have issues with gripper gloves and + other tackle gloves. + - bugfix: Intellicards in computers are no longer deleted when the computer is destroyed. + - bugfix: Modular consoles can now be deconstructed by right clicking with a wrench. + Higgin: + - bugfix: cigarettes no longer smoke themselves from inside your pockets or on your + hands. + Jacquerel: + - bugfix: Selecting "Monkey" on a magic mirror will now once again turn you into + a Monkey rather than a disgusting freak of nature. + - bugfix: Tall Boys have once again been barred from joining the Wizard Federation. + LT3: + - admin: Successful restart votes will now restart on the current map + - code_imp: End round and persistence data will be saved before executing successful + restart vote + - spellcheck: Improved wording in greyscale JSON error message + Rhials: + - bugfix: '"Spooky" meteors will now properly spawn during halloween.' + Sealed101: + - bugfix: you can no longer push watchers (and any other lavaland basic mob) around + by running into them + Singul0: + - rscadd: Adds an advanced plastic surgery procedure, allowing you to imitate people + in pictures. Simply hold a picture in your offhand of the person you wish to + imitate as while conducting the surgery! Remember, it's not foolproof, it only + changes your name and voice! + - rscadd: You can obtain the disk containing the afromentioned surgery. as a role-restricted + item to doctors and roboticists for 1TC, as a rare maint loot and BEPIS technode + reward + Thunder12345: + - bugfix: Metalgen can no longer be used to transmute indestructible turfs. + Timberpoes: + - bugfix: Centcom now rejects contraband that somehow makes it way onto the cargo + shuttle mid-transit and returns it. + Vincent983: + - image: the security records suspected status is now teal instead of orange + carlarctg: + - refactor: Heavily refactored mirrors to be less ass cancer 1998 code. Player facing + changes are that mirrors now use a radial menu, women can get beards in magic + mirrors, made the magic mirror 'change sex' option Woke (it supports the 4 official + genders and physique as well) + - bugfix: Fixed Pride Mirror teleporting you into the space on the first use. Now + it waits until you officially cancel and say 'I am Done' so you can customize + yourself to your liking. + jlsnow301: + - rscadd: Netpods and quantum servers now have more examination info + - bugfix: You no longer lose antag status if you receive it in the vdom. + - bugfix: Beach bar shouldn't have visible atmos piping anymore. + - bugfix: Adds more lighting to the vaporwave vdom level. + - balance: Buffed vdom megafauna health to compensate for new ability disks. + mc-oofert: + - bugfix: posibrains can be inserted again + san7890: + - bugfix: The custom error message for when there is only one map to vote for should + pop up in all cases rather than just a select few. + tattle: + - qol: Basic animals now make sounds for audible emotes + - sound: Added new sound effects for chicks, chickens, crabs, and insects + xPokee: + - image: adds a frog holoform for pAIs +2023-09-27: + Helg2: + - bugfix: '[Tramstation] Mass Driver in chapel now has tiny fan so you don''t space + yourself.' + Jacquerel: + - bugfix: Blob spores will respond to rallies more reliably (it won't runtime every + time they try and pathfind). + - bugfix: Blobbernaut pain animation overlays should align with the direction the + mob is facing instead of always facing South + - refactor: Blob spores, zombies, and blobbernauts now all use the basic mob framework. + They should work the same, but please report any issues. + Sealed101: + - bugfix: fixed Strong Stone ruin generation + Vekter: + - rscadd: Added a holodeck to Birdshot Station. It can be reached via the Crew Facilities + hallway. + Watermelon914: + - bugfix: Fixed job configs not being loaded properly. + admeeer: + - rscadd: Added a candle box crate for all your candle needs! +2023-09-28: + CoiledLamb: + - rscdel: removes surgical duffelbags + - bugfix: the surgery supply order now comes with a surgery tray + GPeckman: + - bugfix: The flight potion wings will no longer fail to work on lavaland/icemoon + on rare occasions. + Iamgoofball: + - qol: Gas masks now muffle your voice with TTS. + - qol: Security Hailer masks now disguise your voice to protect your right to brutalize + greytiders. + - qol: Lizards, Ethereals, and Xenomorphs now have a vocal effect. + - qol: Security Records now show someone's voice name. + Jacquerel: + - bugfix: Throwing things at cyborgs will now slow them down, as intended + - balance: Adjusted the calculation of throwforce -> slowdown for cyborgs such that + it is simply a flat duration for anything above a certain damage threshold (the + value of throwing iron rods) + - refactor: Hivelords and Legions now use the basic mob framework. Please report + any unusual behaviour. + - rscadd: Hivelords shed more spawn when they are attacked. + - rscadd: Legions have learned how to fling their skulls across long distances. + - rscadd: Legions can heal other lavaland mobs with their skulls. + - rscadd: Legions are better at preserving corpses they consume, and sometimes make + use of their radios. + - rscadd: Legions may leave behind an unpleasant surprise after you are rescued + from them. + - balance: The crew monitoring console will now display you as dead if you are dead, + an critically injured if in crit, rather than setting those icons purely based + on your current health. + - qol: You won't continue burning to a husk if consumed by a snow legion after being + set on fire by an ice drake. + Melbert: + - rscadd: Doctors can now get head mirrors from their clothes vendor, to complete + the doctor outfit + - config: Adds a config option for player respawning that enables respawns, but + forces you pick a new character. + - config: '"NORESPAWN" has been replaced with "ALLOW_RESPAWN 0". Unlimited respawns + is "ALLOW_RESPAWN 1" and character limited respawns is "ALLOW_RESPAWN 2".' + MidoriWroth: + - bugfix: Added complexity factors to foods that were missing them. + Profakos: + - rscadd: Added the BEPIS' minor rewards as purchasable products to the bitrunning + order console. + - rscdel: Removed the base BEPIS disk from the bitrunner console + Sealed101: + - qol: you can undeploy fulton beacons by clicking them with an empty hand + - qol: you can rename fulton beacons with a pen + - bugfix: fixed geysers spawning on turfs with plants + Tattle: + - qol: drones now have individual names, instead of just "drone" + Toastgoats: + - balance: Gave the bluespace geode pirates 4 more teleporter bolt turrets. + - bugfix: The bluespace geode pirates no longer have a bluespace portal to the bottomless + pit dimension. + - rscadd: Station-safe dirt tiles for all your mapping needs, but surely no station + maps use the chasm baseturf ones, right? Right? + Watermelon914: + - spellcheck: Tweaks the message that players get when not being able to qualify + for roundstart antag to be more accurate as to what's happening. + Xander3359: + - rscadd: Contractor baton in traitor uplink for 12 TC + - balance: Ebow no longer has a reputation requirement. + carlarctg: + - bugfix: Fixed zombies being able to infect headless corpses (Including former + zombies) + - bugfix: 'Fixed bio armor being totally useless against zombies. Now it checks + how hurt your limb is: If it''s more than the bio armor value, you get infected. + THICKMATERIAL clothing guarantees at least 25 damage required to infect you, + non-thick clothing reduces effective defence by 25. In practice this means people + with MODsuits, biosuits will resist infection unless they''re pummeled into + crit, and wearing a firesuit will save you from the first few slashes.' + - bugfix: Fixed the bomb hood armor not having the same bio armor value as bomb + armor. + - qol: Added a message to the zed when they succesfully infect someone. + - code_imp: Turned some proc names into snake_case rather than, uh, nospacecase. + - bugfix: Fixes full advanced surgery trays spawning with 'nothing' + - refactor: Turned slapcrafting into a component! You can examine compatible items + to see what recipes they can be used in, and what the ingredients for them are. + For example, spears and the head-on-spear crafting recipe. + - bugfix: Valentines and ERTs will no longer get mood boosts from traitor moodener + items + lizardqueenlexi: + - bugfix: Left-clicking an empty surgery tray will now tell you exactly why it does + nothing. + mc-oofert: + - bugfix: you can now deconstruct exodrone scanner arrays + - bugfix: the tree in space exodrone adventure no longer softlocks you + - qol: the exodrone launchers now tell you on examine how to remove their fuel canister + if you somehow needed to do that + - balance: exodrone wide scans are now capped at 10 minutes + - balance: exodrone travel times are 18% faster + - balance: you can now upgrade scanner arrays for faster wide scan + - balance: exodrone point scan and deep scan are faster + - spellcheck: fixes several typos related to exodrones and gives scanner control + console a description + nikothedude: + - bugfix: You can no longer break the game by AI rolling in a card or APC + - qol: AI Roll now doesnt require you to click the exact turf to move you + - qol: AI roll cooldown and roll time is now a variable, making it possible for + AIs to become terrifying catamari damacy balls + timothymtorres: + - bugfix: Fix altars not allowing items to be sacrificed + vinylspiders: + - bugfix: Seeds will no longer be removed from existence after receiving the "You + can't seem to add [seed type] to the seed extractor" message + - bugfix: Some seeds that were previously not able to be added to the seed extractor + may now be added (starthistle for example) + - bugfix: fixes replica pod seeds spawning humans in nullspace +2023-09-29: + A.C.M.O.: + - bugfix: Fixes the death sandwich, making it safe to examine. + BurgerBB: + - bugfix: Scrubbers and Vents will no longer reset their settings on map load. + GPeckman: + - admin: There is now a tool to apply a DNA Infuser entry to any human. + Ghilker: + - code_imp: adds a gas connector component that allows connection to the atmos piping + system without the need of repathing + - refactor: changes the cryo machine to use this new system + Ghommie: + - bugfix: Fixed crabs not correctly (kinda) walking sideway. + Higgin: + - balance: Diabetics rejoice! Nerfed sugar OD/hyperglycaemic shock to be an immediate + KO followed by drowsiness afterwards until the OD is gone. + Jacquerel: + - bugfix: The Nuke Op MODsuit AI downloader only works once per purchase, as intended. + - bugfix: Blob Zombies and Blobbernauts have had their attack speed restored to + its original value + JohnFulpWillard: + - code_imp: Your bodytype now decides what gendered sounds you make. + - balance: You can now remove and replace power cells from PDAs (with screwdriver). + - balance: PDAs now drain their power cells harder, and also take into account active + programs & their flashlight being on. + - balance: PDAs running out of charge now turn their flashlights off. + Melbert: + - qol: Examine blocked out roundstart / latejoin job information. + - qol: Captain gets a little bit more information about how their radio works roundstart. + - bugfix: Fixed roundstart players not getting radio information. + - balance: Transformation sting now lasts 8 minutes, down from permanent. However, + the effect is paused for dead and stasis mobs, making it permanent SO LONG AS + they stay dead or in stasis. The effect is also permanent if used on a monkey. + - balance: Transformation sting now costs 33 chemicals, down from 50. + - balance: Transformation sting now costs 2 dna points, down from 3. + - bugfix: Transformation sting works on monkeys again. + - refactor: Refactored a bit of human randomization. + Rhials: + - bugfix: The Polymorph Belt should now update its sprite when active. + - qol: The freedom implant has received minor feedback and other minor usage improvements. + Sealed101: + - bugfix: fixed lobstrosities becoming unmovable when killed during their charge + windup + SyncIt21: + - bugfix: removes incorrect stack traces when using some admin secrets + Vekter: + - bugfix: Fixes the missing grinder in Birdshot's Virology department + Vincent983: + - bugfix: the parole status and discharged status are now green and blue respectively + in the security record interface + Watermelon914: + - balance: Head revolutionaries and heads of staff are no longer immediately considered + disqualified when going AFK or disconnecting and are given a 2 minute grace + period. + - admin: Admins now get a log when a head revolutionary or head of staff disconnects + or goes AFK during a revolution. They also get the same log 1 minute after to + give them a chance to act on the information. + necromanceranne: + - bugfix: Splattercasting resets your blood to normal values when you transsform + into a vampire. + - bugfix: Gaining a new species will set your blood volume down to the normal volume + levels if higher than normal. + san7890: + - code_imp: Robot Customers have recently been touched codewise, please report any + bugs or unexpected behavior as there really should not be any. + - refactor: Snakes have been refactored into basic mobs. This means that they are + a bit more intelligent than previous snakes, making them more docile and averse + to harming people (unless otherwise provoked). They do chomp all sorts of mice + though. You can feed them a dead mouse to make them your friend if you'd want + that. + - sound: If you listen closely to snakes, you might be able to hear a small hissing + sound... + timothymtorres: + - bugfix: Fix water puddle runtime when washing items + - rscadd: Add drinking water causes drunk mobs to become sober + - rscadd: Add candle design to biogenerator + yorii: + - bugfix: dead bodies now cool down to room temperature over time + - qol: allowed names to start with a number if AI/Borg +2023-09-30: + DrDiasyl aka DrTuxedo: + - balance: Holsters can now be clipped to any suit, and house Captain antique gun + and HoS gun. You now can buy holsters from the SecTech premium section. + Helg2: + - rscadd: SM crystal can now dust someone or something if it falls on it. + Jacquerel: + - bugfix: If two cosmic heretics ascend in the same round, their star gazer survival + will be linked to each individual heretic and not shared by just one of them. + - bugfix: You can't click the Knock heretic portal to join as a mob while already + signed up to become a mob. + - balance: Cosmic heretics can't order the Star Gazer around while jaunting. + - balance: The Knock Heretic portal cannot summon Flesh Worms, but can summon Fire + Sharks. + - balance: The Knock Heretic portal will disperse if its creator is killed. + - admin: Mob abilities can be granted to arbitrary mobs via the VV menu in a similar + way to spells. + - bugfix: Lavaland syndicate operatives can no longer trivially use the jetpack + on their modsuit to fly over the lava. + JohnFulpWillard: + - bugfix: PDA flashlights wont cause the cell to constantly drain faster and faster. + Singul0: + - bugfix: Fixes missing baseturfs and clowns in mining planet VDOM.. + SyncIt21: + - code_imp: removed some redundant code for airlocks + Timberpoes: + - balance: There are now 3 roundstart cyborg job slots open by default. + admeeer: + - bugfix: You can now spray paint the SM without getting dusted + jlsnow301: + - rscadd: Quantum servers now talk over supply channel when they're done cooling + off. Go outside! + - bugfix: You can no longer use dragon swoop to bypass cordons. + - bugfix: Netpod brain damage is now properly reduced upon server upgrades. + - bugfix: Fixed an bug where swapping bodies in vdom prevented you from disconnecting. + - bugfix: Fixed a bug where a quantum server could get locked out of loading new + domains. + - bugfix: Changed quantum console UI to display "no bandwidth" rather than "none" + lizardqueenlexi: + - bugfix: The reverse revolver now looks like a normal Syndicate revolver on inspection. + nikothedude: + - code_imp: Gauze removal is now handled by the gauze's destroy instead of seep_gauze + xPokee: + - bugfix: fixed the stamp in the metastation CMO office always spawning on the floor diff --git a/html/changelogs/archive/2023-10.yml b/html/changelogs/archive/2023-10.yml new file mode 100644 index 0000000000000..1a0eb987d0c24 --- /dev/null +++ b/html/changelogs/archive/2023-10.yml @@ -0,0 +1,1179 @@ +2023-10-01: + ArcaneMusic: + - rscadd: A new export has arrived in the imports section, the Galactic Materials + Market! You can use this to buy and sell minerals for profit or cost, as well + as stock your station when you don't have any miners. + - rscadd: Insert sheets of minerals into the Galactic Materials Market to convert + them into a stock block, allowing you to lock in your price for 5 minutes. Wait + too long and it'll be subject to market value again! + - rscadd: Minerals can be bought on the market either using the station's cargo + budget by cargo crew, or privately by everyone else. + - rscdel: Any material stacks that can be bought and sold on the market before have + been removed from the cargo catalog. + GPeckman: + - bugfix: Security officers can now download the crew manifest PDA app that they + start with. + Ghommie: + - bugfix: Actually fixed the hooked item exploit. + Higgin: + - bugfix: Added warden to list of default required enemies for rulesets. + NPC1314: + - image: new chaplain outfit + SyncIt21: + - bugfix: Aloe and other baked foods that don't have reagents can be baked again + without turning to ash + carlarctg: + - rscadd: Heretic Rebalance + - balance: Researching the Main Knowledge paths that unlock Side Paths will grant + one Side Point that can be used only on those side paths. You can still spend + normal knowledge points on them if you wish. + - balance: Rune drawing time has been reduced from 30->20 seconds. Codex drawing + time has been reduced from 15->8. + - balance: 'Codex Cicatrix is now a roundstart knowledge, works as an amber focus + when held in-hand and opened, and has had its recipe changed to: 1 of any non-standard + pen (literally anything that isn''t the base pen), any book, and either animal + hide OR a corpse, any kind.' + - code_imp: Added support for using a list inside ritual requirements and a special + 'snowflake check' rituals can utilize. + - balance: The first non-path knowledge, the Mansus Hand Mark, has had its cost + reduced from 2->1 points. + distributivgesetz: + - qol: Font settings in the chat panel applies to all text now. + nikothedude: + - balance: Sci now has access to the materials & canisters section in their departmental + order console + ninjanomnom: + - rscdel: An easter egg plushie that was spawning where it shouldn't has been brought + back home. + - rscadd: The secure closet can now spawn live gibtonite, enjoy your free bomb. + san7890: + - refactor: Supermatter Spiders have been refactored into basic mobs, on the extremely + off chance you spot one and also notice any weird bugs regarding it, please + report it. +2023-10-02: + Ghommie: + - rscadd: Expanded the fishing portal generator. It now comes with several portal + options that can be unlocked by performing fish scanning experiments, which + also award a modest amount of techweb points. + - balance: The fishing portal generator is now buildable and no longer orderable. + The board can be printed from cargo, service and science lathes. + - balance: Advanced fishing tech is no longer a BEPIS design. It now requires the + base fish scanning experiment and 2000 points to be unlocked. + - rscadd: The advanced fishing rod now comes with an incorporated experiscanner + specific for fish scanning. + - rscadd: Added a new skillchip that may change the icon of the "fish" shown in + the minigame UI to less generic ones. Reaching master level in fishing also + does that. + - qol: The experiment handler UI no longer shows unselectable experiments. + Jacquerel: + - rscadd: Wizards who complete the grand ritual can now gift everyone with eternal + life + distributivgesetz: + - bugfix: Font scaling in TGUI chat has been reverted to its original implementation. +2023-10-03: + ArcaneMusic: + - image: Railings have had a visual update. + CoiledLamb: + - rscadd: adds boxes of bandages, a quick healing item + Fazzie: + - rscadd: Added a budget solar crate to the derelict teleporter room + - rscadd: Added a solar panel control to the north derelict solar + - qol: The derelict's AI coridoor is now shorter and prettier + Ical92: + - bugfix: fixed misplaced door on syndicate listening post + Jacquerel: + - bugfix: Spiders, Morphs, Fire Sharks, and Regal Rats no longer have a reduced + click speed. + - balance: Kudzu will now be destroyed by adverse weather. + - balance: Kudzu will no longer spread over holes. + timothymtorres: + - bugfix: Fix organs having no DNA and become bloody when violently removed. +2023-10-04: + Coded by Jacquerel, Sprited by Dalmationer: + - rscadd: Added tongs to the kitchen, which you can use to manipulate food from + further away + Cruix: + - bugfix: Some icons for selecting character preferences are no longer scaled incorrectly. + FIoppie: + - sound: '*flap now makes a fluttering noise for moth wings' + - sound: Moths now have a death sound + - qol: '*tremble emote now is just "trembles!" instead of "trembles in fear!"' + Fazzie: + - rscadd: A lot of new content has been added to the beach away mission + - qol: It also looks substantially better, too! + Jacquerel: + - refactor: Raw Prophets now use the basic mob framework. Please report any unusual + behaviour. + - admin: Admins can turn off dynamic rulesets (or force them on despite not meeting + the qualification criteria) on a per-round basis. + Melbert: + - qol: Moved a lot of maintenance spawnpoints out of non-maintenance rooms. Some + antags (paradox clone, fugitives, nightmares, spiders) are now less likely to + spawn in obvious places like the morgue, tech storage, or dorms rooms. + TheBoondock: + - rscadd: Added blackout, happens when you drink...ALOT + Wallem: + - rscadd: Adds The Hand of Midas, an ancient Egyptian matchlock pistol. + ZephyrTFA: + - bugfix: The Syndicate have fired their previous construction company after poor + results in recent outposts. + lizardqueenlexi: + - bugfix: Your heart will no longer be deleted if an admin heals you while you have + corazargh in your system. + - refactor: The cursed heart has been streamlined a bit, and now gives you a visual + cooldown for when you can beat your heart again. + - bugfix: Cutting open a hand-pressed paper bundle no longer deletes all of the + paper. + nikothedude: + - qol: Departmental order consoles now alert their department via radio when their + cooldown expires + ninjanomnom: + - admin: Appearance vars in VV now display instead of being left blank + timothymtorres: + - bugfix: Fix butchered monkeys to transfer reagents and diseases to meat + unit0016: + - rscadd: The funds the syndicate have been saving by restricting galley access + has been suddenly funneled into a singular mosaic pattern in the experiments + wing. + vinylspiders: + - refactor: fixed many instances of updatehealth() either being called needlessly + or not at all within on_mob_life() and in various other parts of the code + - refactor: damage procs now return useful information--the actual net change in + damage on the mob. added a unit test for this +2023-10-05: + lizardqueenlexi: + - bugfix: The Galactic Materials Market now offers things for sale as it should. + mc-oofert: + - code_imp: exploration drone adventures are now file-based and not database-based + timothymtorres: + - balance: CQC legsweeps now cause knockdown instead of paralysis. + - balance: CQC kicks now knockout a target on the floor for ten seconds if they + reach stam crit. Helmet protection shortens the knockout length. +2023-10-06: + EricZilla: + - image: We have received a new shipment of IDs, as the old ones were found out + to be haunted. + - image: Laser tag red team ID has received a massive nerf + - image: Station budget cards have gotten a facelift + - image: Emags and Doorjacks + - bugfix: Numbered prisoner IDs will now be legible + SyncIt21: + - bugfix: plumbing reaction chamber now balances the ph of it's solution correctly + to the best of it's ability so no guarantees + - code_imp: converted plumbing reaction chamber & mixing chamber UI files to Typescript + - refactor: plumbing mixing chamber now also accepts an TGUI input list to input + it's chemicals + Wallem: + - rscadd: Buffs the Active Sonar module with a radial scan, and makes the power + costs more in-line with other modules. + mc-oofert: + - bugfix: you are now made a ghost faster if you get gibbed + neocloudy: + - bugfix: MetaStation disposal pipes from Cargo to Disposals/the rest of the station + are working again. +2023-10-07: + CoiledLamb: + - qol: allows janitor keys to be stored in janitor wintercoats and janibets + - qol: gives empty fireaxe and mech removal crowbars cabinets directional helpers + GPeckman: + - bugfix: Borgs will no longer become permanently upside-down if tipped over by + multiple people at the same time. + - bugfix: Adminheal will now properly clear negative mutations as intended. + - bugfix: The AI can no longer turn you off if you shapeshift into a robot. + - rscadd: The laser carbine, a weak but fully automatic sidegrade to the normal + laser gun, can now be ordered from cargo. + - bugfix: Engineering borgs can no longer grab and drop their own iron/glass sheet + module. + - bugfix: Ice whelps can now use spells given to them by admins, and people who + have polymorphed into ice whelps can now polymorph back to normal. + Ghommie: + - bugfix: Fixed silent catwalks. + - rscadd: Fish analyzers can now be used to perform fish scanning experiments. + - balance: They can now be singularly bought as a goodie pack for 125 cr each, instead + of a crate of three for 500 cr. + Isratosh: + - spellcheck: '"offical" has been officially corrected to "official" in several + official locations.' + Jacquerel: + - refactor: Rust Walkers, Ash Spirits, Flesh Stalkers, and The Maid in the Mirror + now use the basic mob framework. Please report any unusual behaviour. + Kapu1178: + - bugfix: Blood once again appears as small drops instead of splatters during minor + bleeding. + Likteer: + - rscadd: Fake moustaches are now poorly slapped on top of what you're wearing + Melbert: + - refactor: Refactors how ethereals update their color when damaged. + - qol: AI, cyborg, and PAI camera (photo taking) behavior now uses balloon alerts + and has sound effects associated + - refactor: Refactored AI, cyborg, and PAI camera (photo taking) code + - bugfix: fixed being unable to print photos as a cyborg when below 50% toner, even + though photos only take 5% + ReezeBL: + - bugfix: fixed a PDA's messenger TGUI issue with handling of destroyed recipients. + Sealed101: + - bugfix: fixed bad food not having bad food reagents + necromanceranne: + - balance: Despite earlier reports suggesting that the famous lethality of the Regal + Condor was largely a myth, there has been rumors that the gun has once again + started to display its true killing potential on any station that it 'manifests'. + oranges: + - rscadd: Dogs now react to centrist grillers more realistically + san7890: + - refactor: Refactored goats into basic mobs! Not much should have changed beyond + their endless desire to retaliate should you attack them, they're still just + as good as chomping away plant life as ever. + timothymtorres: + - refactor: Refactor gib code to be more robust. + - qol: Gibbing a mob will result in all items being dropped instead of getting deleted. + There are a few exceptions (like admin gib self) where this will not take place. + unit0016: + - bugfix: It is no longer possible to chasm yourself on the geode. Again. +2023-10-08: + Comxy: + - bugfix: Spider types get properly checked again. + Ghommie: + - bugfix: People who are irremediably bald can still grow a beard with barber aid. + Hatterhat: + - qol: Miners can now tag monster spawners (necropolis tendrils, animal dens, demonic + portals, and netherworld links) by using their mining scanner on it, which updates + their GPS tag (and/or gives them one) to give it a numerical designation and + a short identifier for what it's spawning. + Jacquerel: + - bugfix: Flesh Worms will move smoothly more consistently. + LT3: + - image: Text alignment on ID cards slightly adjusted + Melbert: + - bugfix: Fixed an error from reading an ID card closely when you can't read + YehnBeep: + - qol: '"prison" intercoms have been renamed to "receive-only" intercoms to make + it clearer they cannot transmit.' + carlarctg: + - qol: Added slapcrafting to unloaded tech shells, click on them with ingredients + to quickly craft your shell. + san7890: + - refactor: Sloths are now basic mobs, however their overall sluggish behavior shouldn't + have changed much- let us know if anything is broken. + timothymtorres: + - bugfix: Fix bodies now lose fire stacks while husked. +2023-10-09: + Ben10Omintrix: + - refactor: ice demons have been refactored into basic mbos. please report any bugs + - rscadd: ice demons now have a unique trophy + IndieanaJones: + - bugfix: Slaughter/Laughter demon melee cooldowns have been fixed and now attack + at the regular player character attack speed + Jacquerel: + - balance: The chemical gun and PKA pressure mod traitor items are now purchasable + within 15 minutes of the round starting rather than 20/30. + - balance: All preset bundle kits, the cash briefcase, the makarov, the revolver, + the throwing weapon kit, c4, the detomatix cartridge, the large EMP bomb, gorillas, + advanced mimery tome, pie cannon, clown car, His Grace, and the origami kit + are now all purchasable at the start of a round. + distributivgesetz: + - qol: Supermatter shards can now be fastened with right click too. Now, just don't + forget to use a wrench. +2023-10-10: + BlueMemesauce: + - bugfix: fixed gibbing from having too much blood not working in some cases + Fazzie: + - qol: NT's logo on Centcom's landing pad looks better + - qol: Centcom's Cargo and other rooms had their items rearanged to look marginally + better. Like you're every gonna see them! + - bugfix: The Thunderdome on Centcom now has up-to-date cooking machinery + FlufflesTheDog: + - bugfix: Virtual domain gondola meat will no longer have a small chance to turn + you into a weaker gondola variant + GPeckman: + - bugfix: Warm donk-pockets should now have omnizine in them again. + HWSensum: + - balance: Reviver Implant now able to revive dead people. + Hatterhat: + - bugfix: Necropolis tendrils and other mining mob spawners can be hit in melee + again. + Jacquerel: + - bugfix: Cowardly mobs will consistently run away from you instead of getting tired + and just sort of standing there after an initial burst of movement. + Melber: + - bugfix: Wearing bread (or roses, or other non-mask things) no longer prevents + you from TTS speaking. + Melbert: + - bugfix: Robotic bodyparts not attached to people are now properly affected by + EMPs. + - bugfix: Virtual Drink Glasses now look correct. + SyncIt21: + - code_imp: moved some global procs and vars related to reagents to its own dedicated + file. removed some unused procs and macros + - code_imp: heavy auto docs for a lot of procs + - refactor: adds reagent sanity and bound check code + - refactor: multiple reagents are more uniformly distributed when transferring them + between beakers or dropper & in every other reagent dependent operation + Wallem: + - rscadd: Nuclear Operatives now have ready access to ancient cowboy technology + in the form of the Outlaw Bundle. Now you too can roll into town on your horse. + YehnBeep: + - qol: The autopsy tray (and surgery trays) can now hold the autopsy scanner + admeeer: + - code_imp: made an eensy teensie weensie change to some supermatter boilerplate + jlsnow301: + - bugfix: Added extra checks to bitrunning domain cleanup so avatars are deleted + properly. + - rscadd: Quantum servers now look for a new machine called a byteforge to spawn + loot on- no longer on an invisible landmark. This should make the rooms rebuildable + after disasters. + - rscadd: '*Most* bitrunning machinery is now researchable and buildable via circuits + in the engineering protolathe.' + lizardqueenlexi: + - bugfix: Metastation disposals will no longer infinitely loop garbage around the + station. + mc-oofert: + - code_imp: COMSIG_GLOB_LIGHT_MECHANISM_COMPLETED is now COMSIG_GLOB_PUZZLE_COMPLETED + ninjanomnom: + - bugfix: Images are once more displayed as images in vv instead of as an appearance + - rscadd: Pipes now have a colored visual display that shows their contents at a + glance. + san7890: + - refactor: Revenants, the mob that's split between planes of Life and Death, have + been refactored into a basic mob. While this alone shouldn't touch behavior, + a lot of the backend code has been gutted and refactored to try and furnish + a better antagonist experience. This might mean that some weird stuff can come + up and around, report something if it's utterly broken. + - code_imp: In order to better facilitate some code, you do not ghost outside of + a revenant on death, you simply get transferred into the ectoplasm. You should + still be able to speak with your ghost friends on how hard you got wrecked or + if you'll be able to resurrect though. + - code_imp: The timing on revenant stuff such as being revealed, stunned, and inhibited + (by holy weapons) should be tweaked a bit to allow better management. This should + mean that getting unstunned and such should be a bit more precise now. + - qol: Revenant instructions are now relayed in a neat little examine block. +2023-10-11: + GPeckman: + - bugfix: Borg modules can no longer be sold by pirates. + Iamgoofball: + - bugfix: Fixes a few runtimes with TTS and skips some code if TTS isn't enabled. + Jacquerel: + - balance: The Changeling Space Suit has been replaced by a new ability which makes + you passively spaceproof without replacing your clothing. + - admin: Editing the atmos sensitivity variables on a basic mob during the game + will now actually do something. + - qol: You can now see what drones and gorillas are holding by examining them. + - admin: It's now easier to give handless mobs hands by applying the "dextrous" + element. + - balance: Spiders and Bears can now climb railings (you know if... they'd rather + do that than destroy them). + - bugfix: Heretic mobs will not be summoned with AI enabled, and won't turn into + small animals instead of summoning a flesh stalker. + JohnFulpWillard: + - bugfix: Antiglow now probably has negative glow power. + Melbert: + - bugfix: Miner's Salve, Sterilizine, and Space Cleaner now all properly affect + burn wounds + Sealed101: + - bugfix: you can no longer polymorph belt into a holoparasite + ViktorKoL: + - bugfix: fixed some faulty research connections in between heretic's blade and + rust paths. + carlarctg: + - rscadd: Adds practice carbines to all firing ranges. They don't deal damage. + - qol: Adds a base physical description proc to gameplay species, displays it on + magic mirrors. It will give a description of not the lore of the species but + in what way they differ from base species. + - bugfix: Fixes a bad subtype on magical mirrors. + - bugfix: Magical mirrors now give the user ADVANCEDTOOLUSER and LITERACY if they + lack either of them, so monkey wizards aren't softlocked. + jlsnow301: + - bugfix: Fixed some issues in the security camera UI - pressing next or back will + now loop through the cameras + - bugfix: Fixed some style issues in the camera console where selected cams weren't + showing as selected + - bugfix: Camera console search works again + lizardqueenlexi: + - refactor: Harvester constructs have been updated to the basic mob framework. This + should have very little impact on their behavior, but please report any issues. + mc-oofert: + - bugfix: count station food verb now counts food only onstation + necromanceranne: + - balance: Unholy water acts as a coagulant for cultists. + nikothedude: + - bugfix: Wound promotion and demotion no longer removes gauze from the limb + timothymtorres: + - sound: Add burning sound loop to bonfires and fireplaces + - code_imp: Improved fireplaces to only process when lit + vinylspiders: + - bugfix: fixes a runtime in organ on_death() + - bugfix: using a magic mirror to change gender or skintone will now update your + icon properly to match your selection + - bugfix: bitrunners will no longer be lumped in with assistants on the crew monitor + console's display + - bugfix: moved a garbage spawner on Tramstation that was causing random runtimes + due to sometimes spawning in space depending on which module got loaded +2023-10-12: + Ghommie: + - bugfix: Examining twice experiment handlers with an active fish-related experiment + now gives a comprehensible, correctly spaced list of scanned species rather + than something like "pufferfishguppyslimefishchasmchrab". + - bugfix: No more "line snapped" balloon messages everytime the fishing minigame + is over + - bugfix: Getting to the Master level of the fishing skill now correctly gives you + that slight helping hand to identify yet-to-be-caught fishes. + Isratosh: + - admin: Gondola supplypods are functional again. + Jacquerel: + - refactor: Gorillas now use the basic mob framework. Please report any unusual + side effects. + - rscadd: Adds a new lavaland ruin where you can find a unique egg. + - balance: Flesh Spiders heal automatically over time if they go a short time without + taking damage, instead of healing large chunks by clicking themselves and waiting + two seconds. + - qol: Spider egg clusters which only hatch into one kind of spider don't ask you + to select that one type from a radial menu with one option on it. + - qol: As a Flesh Spider, the game now tells you how you can heal yourself. + LT3: + - rscadd: Introducing Nanotrasen Wave! A Nanotrasen exclusive, Waveallows your PDA + to be charged wirelessly through microwave frequencies. You can Wave-charge + your device by placing it inside a compatible microwave and selecting the charge + mode. + - rscadd: Microwaves can be upgraded to add wireless charging + - rscadd: Cell-swappable microwave for the engineer on-the-go + - rscadd: Microwave now has a wire to swap charge/cook modes + - rscadd: Furnishings RCD upgrade now includes wireless microwave + - rscadd: Tramstation and Birdshot engineering break rooms now have microwave and + donk pockets. Some microwaves come pre-equipped with wireless charging and an + upgraded cell. + - bugfix: The microwave in the snowdin ruin is now real, not a fluff prop + - bugfix: After the untimely loss of too many novice HoPs, the Icebox "New IDs and + You" instructions have been moved from the icemoon wastes to the HoP's office, + ending this rite of passage + - bugfix: Added some missing firelocks in the pharmacy area. Icebox pharmacy now + has a shower + SyncIt21: + - code_imp: removed round robin method of transferring reagents which would result + in some missing reagents after transferring. + - code_imp: added some more rounding for reagent operations. + - code_imp: cleaned up some plumbing & reaction chamber code + - code_imp: improves the performance of `update_total()` , `clear_reagents()` & + `del_reagent()` procs + - bugfix: plumbing setups will no longer output 0 or more than maximum available + volume of reagents. + - bugfix: removing, copying, transfering reagents is now done proportionally and + not equally again. + - refactor: examining individual reagents up close will display their volumes up + to 4 decimal places for accuracy. + - qol: droppers & beakers round the amount of reagents transferred before displaying + them to chat for easy readibility + - bugfix: You cannot order with cargo budget if you don't have cargo access in the + Galactic Market + - bugfix: Private & Cargo orders no longer get mixed together in the same crate + if you order them interchangeably so no more embezzlement in the Galactic Market + - bugfix: Orders made with cargo budget come in a regular cargo crate thus allowing + you to open them without QM cargo budget card in the Galactic Market + - qol: Orders made in the Galactic Market will deduct money from your account/cargo + budget only after the order has been confirmed in the cargo request console + & after the shuttle arrives with your order. This way you drain the budget only + after your orders were successfully delivered and not before hand itself + ViktorKoL: + - sound: the blood cult's rise to power is now accompanied by several new sound + effects + mc-oofert: + - refactor: venus human traps are basicmobs now + - balance: venus human traps have 100 health + - balance: venus human traps take damage out of range of kudzu, heal near kudzu, + are slightly slower, attack slower, and their damage output is slightly more + random + - balance: also venus human trap tangle ability now needs you to actually move backwards + to pull victims + vinylspiders: + - image: you can now change the style of lipstick to be higher or lower on the face + by alt-clicking the lipstick tube +2023-10-13: + EuSouAFazer: + - qol: The rollerdome is now better - the dance floor works now, and the bar is + groovier. + Jacquerel: + - bugfix: Dullahans can read, strip people, and utilise tools. + - bugfix: Dullahan brains and eyes will not decay while inside their living severed + head. + LT3: + - qol: Canisters can now be built in one step, no upgrading required + Paxilmaniac: + - image: Inhands for the Sakhno and related rifles will no longer be way too high + or big + Rhials: + - rscadd: Two new psyker-oriented virtual domains -- Crate Chaos and Infected Domain. + - rscadd: Map helper for cyber-police corpse spawn. + - rscadd: Map helper for swapping the encrypted crate in an area with a random crate + from that same area. + jlsnow301: + - bugfix: Fixed the errant bluescreen in the camera console. + mc-oofert: + - code_imp: basicmobs that delete on death, ghost before dying + san7890: + - bugfix: The Holy Hand Grenade's effect on revealing a revenant had its duration + accidentally nerfed, it is now back to 10 seconds. + - bugfix: Revenant midrounds should now properly run. + - bugfix: Revenant harvesting should now let you actually pass the final do_after + so you can harvest that sweet essence. +2023-10-14: + BlueMemesauce: + - bugfix: Modsuits can no longer be deepfried + DrDiasyl: + - sound: laser2.ogg sound has been changed. Now laser carbine uses it. + - image: Laser carbine and orange laser sprite have been improved. + IndieanaJones: + - bugfix: Space Dragon can break walls, eat corpses and destroy mechs more efficiently + again + - bugfix: Player-controlled lavaland elites can once again return to their tumor + after winning their fight + Jacquerel: + - bugfix: '"Mirror Walk" is once more the domain of the Maid in the Mirror rather + than "every heretic summon"' + - bugfix: Heretic mobs can once again survive space + - bugfix: Pete's anger management training has worn off, and he will once again + sometimes pick a fight with you for absolutely no reason. + - qol: Attacking a goat will not spam messages so frequently. + LT3: + - image: Nitrogen canisters are now yellow, antinob are grey/yellow, empty are grey, + hydrogen are red/white + MTandi: + - bugfix: The crew is instructed to place fax machines properly in the center of + a table without hanging. + Melbert: + - bugfix: Fixes Mauna Loa, Monover, Silibinin, Granibitaluri not exiting your system + on metabolism + - bugfix: Fixes holy water exiting your system at double the rate on metabolism + - bugfix: Holy Water no longer spams cultists with big text every time, it's much + more tame now + Rhials: + - qol: You can now return to your old body after being summoned by a manifest rune. + - qol: You can now return to your old body after dying in CTF. + - qol: You can now return to your old body after dying in the Medisim Shuttle battle + area. + - qol: You can no longer suicide in CTF areas, for integrity purposes. + bun235: + - rscadd: targetting someone's arm with *slap now has a unique message + dragomagol: + - qol: apples can now be sliced + mc-oofert: + - bugfix: sqdl2 query readout displays location of turfs properly + ninjanomnom: + - admin: VV can now display the contents of special byond lists like filters, or + client.images + - admin: VV on images now displays the image in the header + - admin: VV can now display filters and includes their type + san7890: + - bugfix: Space Dragons can now, once again, tear down walls and eat corpses. They + also have regained their special damage modifier when attacking mechs. +2023-10-15: + GPeckman: + - bugfix: Having all augmented limbs will make you properly spaceproof once again. + - bugfix: Androids are immune to crit damage again. + - bugfix: Surgery on robotic limbs can be canceled. + Iamgoofball: + - balance: Allows spacemen to use age-appropriate drugs by making it so you can + now huff N2O to get high. + Jacquerel: + - refactor: Space Dragons are now basic mobs, please report any unexpected behaviour. + - balance: You can now see that a space dragon is destroying a wall with a visual + indicator of the wall being damaged. + - balance: Space Dragons can pry open airlocks. + Melbert: + - qol: Leaning now has a small animation associated. + - qol: Cyborgs can now lean against walls. + - bugfix: Fixed some runtimes associated with leaning. + - bugfix: Fixed being able to lean while dead or in otherwise invalid states. + ZephyrTFA: + - rscadd: Vent Pumps can now be overclocked, do some light reading in the air alarm + to figure out what this means. + - balance: Vent Pumps now have fan integrity, the damaging of which reduces their + ability to move air. + - sound: Overclocking sounds, including spool, stop, and loop + carlarctg: + - qol: Bladists can now use silver *or* titanium while creating their blades + - bugfix: Fixes Monkey's Delight recipe + starrm4nn: + - bugfix: makes the riot helmet hide hair like other sec helmets +2023-10-16: + BlueMemesauce: + - spellcheck: Broken canisters now have a description + Cruix: + - rscadd: Added Afro (Huge) hairstyle + Melbert: + - code_imp: Removed species death and species hitby, replaced any uses with signals. + SyncIt21: + - bugfix: plumbing setups should(hopefully) no longer grind to a halt nor will overflow + with excess volume of reagents. + - code_imp: created defines for min & max ph. Improved some reaction_reagent code. + Made synthesizer dispensable reagent list values static to save memory. + - refactor: ph balancing mechanism for reaction chamber is significantly improved. + Optimized it's code overall + - refactor: examining each individual reagent will display their results back to + 2 decimal places again and not 4 for easy readability. + Wallem: + - bugfix: The active sonar module won't attempt to create 6000000 new pings per + process cycle anymore +2023-10-17: + DaydreamIQ: + - qol: Icebox Visitation now has a door connected to brig + Fazzie: + - rscadd: The free golem ship has been swapped for a much better one, with fishing + equipment. + - rscadd: The wizard's den now has a book with the guide to wizard. Hope that helps + their winrate! + - qol: The wizard's den no longer looks like a flying cucumber and has received + a major overhaul. Hooray! + - bugfix: Fixed the doors on the Beach away mission + - bugfix: Fixed the railings on the Beach away mission + GoldenAlpharex: + - code_imp: Got rid of a few more single-letter variables. Only over six thousand + left to go, woo! + - code_imp: Documented a huge part of telecommunications machinery and signal code, + and did some minor code improvements to said code. + - bugfix: Hands of cards will now properly display the last card added to the hand + all the time, even when there's more than five cards in that hand. + Higgin: + - bugfix: Fixes respiration-transmission advanced viruses to no longer have an always-guaranteed + infection chance per tick. + Jacquerel: + - bugfix: Megafauna, lavaland elites, and abstract mobs now correctly cannot be + spawned by a toolbox of ash drake summoning + LT3: + - bugfix: The remaining survival pod bed on Icebox is now a medical bed + - bugfix: Fixed font scaling for announcements + - bugfix: Microwave will no longer get stuck turned on if a PDA has no cell + - bugfix: Silicons can no longer silently change the microwave between cook and + charge + Melbert: + - rscdel: Deleted a mapped in wrestling belt + - refactor: Refactored unarmed attacking mechanisms, this means dis-coordinated + humans will now bite people like monkeys (like how coordinated monkeys punch + people like humans?) + - refactor: Dis-coordinated humans smashing up machines now use their hands, rather + than their paws + NamelessFairy: + - bugfix: TGC Mana and Health bars are correctly offset on the holodeck. + - bugfix: Players without bodies to return to can play CTF again. + RedBaronFlyer: + - sound: added sounds for scanning valued items with an export scanner + Rhials: + - balance: Random event frequency has been adjusted to fire events more often. + - code_imp: The event subsystem has been prettied up with comments and longer variable + names. + SyncIt21: + - code_imp: removed unnecessary calls to `update_total()` + Thunder12345: + - bugfix: Delta's cargo bay has been connected to the atmos pipe networks + - bugfix: Fuel tanks are explosive again + jlsnow301: + - bugfix: TGUI Say should no longer flash during initialization + mc-oofert: + - rscadd: added a new hallucination, your mother + vinylspiders: + - bugfix: ghost sheets will now have the correct flags for digi sprites + - bugfix: basic mobs will no longer runtime when trying to check the faction of + a porta turret + - refactor: faction checking is now done at the atom/movable level + - bugfix: Fixes a bug with the plasma flower core MODsuit that would cause a butterfly + murder scene wherever there were turrets +2023-10-18: + Ghommie: + - bugfix: Fish analyzers can now be actually used for experiments. + LT3: + - refactor: Tram process/industrial lift refactored into transport subsystem + - refactor: Nanotrasen has traded in last year's tram for a new 2563 model! + - rscadd: Tram spoilers can be emagged to cause extra damage on collision + - rscadd: Tram can now be manually driven from the controller cabinet using TGUI + - rscadd: Tram floor tiles can be created using plastic sheets + - rscadd: Tram walls/floor tiles can be removed, repaired, and replaced + - rscadd: Tram frames (girder) can be created/placed using titanium sheets + - rscadd: Broken tram machinery causes crossing signals to malfunction + - rscadd: Ninja drain on tram controls will cause minor malfunction on other tram + components + - rscadd: Engineers can stop/start and disable the tram + - qol: Tram machinery now has context hints and examine text to indicate status + - qol: Tram remote no longer requires physically touching the tram before using + - code_imp: You can now construct furniture and other decorations on the tram + - code_imp: Tram electrocution is now a component, not a turf + - code_imp: Tram stats are now persistent between rounds and logged to feedback + - code_imp: Engineers can now end Tram Malfunction event early by repairing the + tram controller + - code_imp: Replacement tram machinery is researchable and buildable + - code_imp: Most tram machinery now performs auto configuration instead of hardcoded + map values + - code_imp: Tram remote can now set specific destinations + - code_imp: Tram actions/errors now create log entries + - balance: Tram base floor is no longer indestructible + - balance: Damaged energized tram plates have a chance to electrocute + - bugfix: Tram destination signs should no longer display incorrect emissive overlays + - bugfix: Tram crossing signals no longer turn amber before the tram starts moving + - image: Improved sprites for most tram components + - image: Removed all unused/duplicate tram icons from .dmis + - image: Colors are now consistent between status, tram, incident displays + - admin: Reset tram commands available to admins in the debug panel + MTandi: + - bugfix: Distilled drink quality is fixed - can't give a mood debuff anymore + Melbert: + - rscadd: Revenants can now use Ouija Boards + - rscadd: Ouija Boards now have more options to select from by default (and admins + can VV it to even more options) + - bugfix: Blind people are now worse at using Ouija Boards + - bugfix: You can punch yourself again + ninjanomnom: + - admin: Invisimin can now be used on mobs that are already invisible, whether through + temporary or permanent effects. + - bugfix: Monkeyize/Humanize mob transformations no longer permanently reveal invisible + mobs if they had effects making them invisible otherwise. + - bugfix: Objects with the undertile element that have been made invisible through + other means are no longer revealed by being uncovered. + vinylspiders: + - bugfix: fixed runtime caused by simple mobs AttackingTarget() missing an arg + - bugfix: being killed or ghosting while being scoped will no longer cause the cursor + offset to persist in a bugged state +2023-10-19: + LT3: + - spellcheck: More announcement CSS fixes, now including light mode + Rhials: + - qol: As an observer, clicking on a bitrunning pod will let you orbit it's bitrunning + avatar. Cool! + carlarctg: + - qol: 'Added slapcraft recipes for: Pillow suits, pillow helmets, bone and sinew + tailoring/weaponry, pipeguns, ghetto jetpacks, and pneumatic cannons.' + - code_imp: The base type of cowboy hats no longer looks and is named like a bounty + hunter hat, clarifying the recipe for the heroic laser musket. They need cowboy + hats not bounty hats. + - bugfix: Fixed an issue where if a slapcraft recipe required more than one instance + of its 'primary' slapcrafting item, it wouldn't show the additional instance + when examining its recipes. + lizardqueenlexi: + - qol: Birdshot ordnance is now equipped with a second RPD and two holofan projectors. + - qol: Ordnance mishaps on Birdshot are significantly less likely to slam you into + an electrified window until you die. + vinylspiders: + - bugfix: fixes a tgui bluescreen bug with the bank account console that can occur + when there is bad bank account data + - bugfix: '"line snapped" and "rod dropped" balloon alerts will now display when + they are supposed to while fishing' +2023-10-20: + GPeckman: + - bugfix: B.O.R.I.S. modules can once again be properly applied to the unformatted + borg created when you reset an AI shell. + Higgin: + - bugfix: automatic breathers rejoice. oxyloss now knocks people out again. + LT3: + - bugfix: Maploaded medical beds now have correct brake lights + LemonInTheDark: + - rscadd: Screen is now more grungy for halloween + Melbert: + - bugfix: Silicons don't spark when shot by disablers + - bugfix: Changelings who fail to catch something with a tencacle will have throw + mode disabled automatically + - bugfix: Fixes occasions where you can reflect with Sleeping Carp when you shouldn't + be able to + - bugfix: Fixes some projectiles causing like 20x less eye blur than they should + be + - refactor: Refactored bullet-mob interactions + - refactor: Nightmare "shadow dodge" projectile ability is now sourced from their + brain + - bugfix: Magic Mirrors can change your race again (?) + - bugfix: Magic Mirrors properly prevent you from being soft locked + - bugfix: Robo customers are as robust as before + - bugfix: Spasms won't trigger in stasis, incapacitated, if your hands are blocked, + or you are immobilized. + Rhials: + - qol: Ghosts will now be prompted to orbit when someone loses control due to being + blackout drunk. + - qol: Ghosts will now be prompted to orbit when a cultist begins inscribing a Nar'Sie + rune. + Yttriums: + - balance: reduces cellulose fibers required for advanced regenerative mesh creation + from 20u to 10u + carlarctg: + - code_imp: Adds 'Bloody Spreader' component that bloodies everything it touches! + - code_imp: For example inserting an item into it if it is a storage item. Or entering + it if it's a turf, or bumping onto it, or... you get the point, hopefully. + - rscadd: Added this component to the MEAT backpack, meat slabs, bouncy castles, + meateor fluff, meateor heart, and the heretic sanguine blade. + - rscadd: Gave most of these the blood walk component as well, which spreads blood + if it's dragged around. + - rscadd: Meat slabs contain a limited amount of both components, eventually they + will 'dry out'. + - code_imp: Added a signal for when an item is entered into storage. + lizardqueenlexi: + - bugfix: Heretic summons should now display the correct name when polling ghosts + to play as them. + san7890: + - bugfix: People should be crawling into welded vents a lot less now. + timothymtorres: + - bugfix: Airtank suicides will now drop items and organs again. + - bugfix: Fix husks fire decay rate to be slower. Pyre chaplains can now use husked + bodies (only caused by burns) to complete their burning sacrifice rite. + vinylspiders: + - bugfix: Fixes a bug where your mother would delete your species after calling + you a disappointment, rendering you a broken husk of a mob +2023-10-21: + Ben10Omintrix: + - rscadd: added a new syndicate item - the bee smoker + GPeckman: + - qol: Nonhuman autopsy, Tier Two Lasers, and several other experiments can now + be completed earlier. + - balance: Advanced robotics techweb node no longer requires neural programming + node. + - rscdel: Protolathe/circuit imprinter/techfab designs costing reagents is now totally + deprecated. + Ical92: + - bugfix: connected Meta's Cytology equipment properly + Jacquerel: + - bugfix: Blobbernauts will once again take damage when not on blob tiles. + LT3, san7890: + - rscadd: Announcements have gotten a fresh coat of paint! They should be popping + with splendid new colors and should have a lot less ugly linebreaks, while still + managing to keep your attention at the screen. + OrionTheFox: + - bugfix: '[Tramstation] fixed an unlinked Disposals Bin in the Pod Bay' + Paxilmaniac: + - code_imp: Bitrunner domains can now have spells or items from disks disabled if + the domain maker wants such a thing + Pickle-Coding: + - bugfix: Fixed tesla coil zaps cutting off too early. + SyncIt21: + - bugfix: plumbing pill press & bottler won't stop when processing 50 unit bottles + - code_imp: 'made a lot of variables defines and lists static to save memory for + plumbing pill press. Moved global lists to it''s rightful + + place' + - code_imp: copied over chem master pill & patch designs over to plumbing pill press + and removed the old designs. resized UI + - bugfix: RCD & RTD ui updates when switching between root categories + Watermelon914: + - admin: Added SS13.get_runner_ckey() and SS13.get_runner_client() which stores + the ckey and returns the client of the user who ran the lua script. Can be unreliable + if accessed after sleeping. + - admin: Added timer loop helpers to the SS13.lua module, check the docs + - admin: The SS13.lua module can now be made local without causing any errors. + lizardqueenlexi: + - refactor: Artificer constructs have been converted to the basic mob framework. + This should change very little about them, but please report any bugs. NPC artificers + are now smarter, and will focus on healing nearby wounded constructs - if you + see them, take them out first! + mc-oofert: + - bugfix: making assembly activated bombs works again + nikothedude: + - bugfix: Scream for me, the spell, now works + - bugfix: Non-random puncture wounds can now be applied + ninjanomnom: + - balance: It damages your eyes to look at the supermatter singularity + san7890: + - refactor: Holodeck monkeys have been moved to the same system as old monkeys, + and should retain the similar "ephermeal" behavior, while being a whole lot + smarter by leveraging new AI. Please report anything that is completely wack + about this. + - balance: Slimes can't eat holodeck monkeys anymore, because apparently they could + and that is wack. + timothymtorres: + - rscadd: Add new fitness skill and mechanics for weight machines and punching bags. Working + out with a proper diet and good sleep will result in massive fitness gains. As + your fitness increases, so does your mass. +2023-10-22: + Ben10Omintrix: + - bugfix: fixes not being able to walk over or pull mook corpses + BlueMemesauce: + - balance: Export scanner no longer shows value of shipping manifests, now you actually + have to read them. + - balance: Shipping manifest penalty is now only half crate cost as well as capped + to 500 credits. + - balance: Shipping manifests for private orders or locked crates can no longer + have the incorrect contents error. Shipping manifests for departmental orders + can n longer have any error. + FlufflesTheDog: + - bugfix: Kisses and emitters no longer make the SM crystal scream so much. + Ghommie: + - bugfix: Cooked meat no longer spreads blood around as if it weren't cooked. + Ical92: + - rscadd: Demonic-Frost Miner's ruin gets an aesthetic refresh + Jacquerel: + - bugfix: Meatwheat Clumps, Bungo Pits, and Eggplant Eggs should once again inherit + reagent purity from the grown item which produces them. + - bugfix: You should be revived properly when entering the mansus realm following + a heretic sacrifice + - bugfix: The buff which is supposed to heal you in the mansus realm will now do + that instead of unavoidably damaging you + - balance: The mansus realm's healing buff heals for 25% as much as it did before + it was broken + JohnFulpWillard, sprites by CoiledLamb: + - rscadd: You can now play Mafia on your PDA. + - balance: Mafia changelings can now only talk to eachother during the night. + - bugfix: Mafia abilities can't be repeatedly used on people. + Jolly: + - image: Colored labcoats have been GAGSed! Please report any weird oddities on + Github. + - bugfix: The coroners lab coat is no longer offset by one pixel. + LT3: + - bugfix: Fixed tram cabinet LMB/RMB actions being reversed + - bugfix: Tram cabinet can now read IDs inside PDAs and wallets + - bugfix: Crossing signals now correctly indicate broken/no power + - bugfix: Trying to repair tram (weld) without welding fuel fails + - bugfix: You can actually unbolt the tram controller from the wall + - qol: Tram spoilers now have visual and examine hints about being malfunctioning/emagged + - qol: Improved some tram error messages + - image: Added Halloween themed floor/tram tiles + LemonInTheDark: + - rscadd: Starlight should be a bit more intense, and flow better onto non space + tiles + MTandi: + - qol: changed wording of a popup in the admin dressing menu + - rscadd: Gygax type mechs now have an option to disable overclock when overheated. + - bugfix: Fixed overclocking having no effect on Ripley. + Melbert: + - rscadd: Adds Food Allergies as a -2 quirk. You can select which food you're allergic + to or rock a random option. + SyncIt21: + - bugfix: cryo and stuff that transfers reagents with a multiplier should transfer + correct volumes as expected. + Treach: + - bugfix: Skeleton Keys now fit in the Explorer's Webbing. + ZephyrTFA: + - qol: signalers now tell you their cooldown and also use balloon alerts + jlsnow301: + - rscadd: Adds a subtle ghost poll. This pings in dead chat and gives a screen alert, + but no TGUI popup. Orbit the point of interest to be selected for the role. + - refactor: A number of ghost spawns now feature this alert. Write an issue report + if anything breaks. + lizardqueenlexi: + - refactor: Maintenance Drones now use the basic mob framework. This shouldn't come + with any noticeable gameplay changes, but please report any bugs. + - bugfix: Drones can now interact normally with electrified doors. + - bugfix: Drones' built-in tools can no longer be placed in storage objects and/or + thrown on the floor. + - bugfix: Drones can now perform right-click interactions correctly, such as deconstructing + reinforced windows. + - bugfix: Drones can now reboot or cannibalize other drones without being in combat + mode. + ninjanomnom: + - rscadd: New dream that plays sound at you + timothymtorres: + - bugfix: Fix holodeck items from being eaten, crafted, recycled, juiced, or grinded. + - bugfix: Fix cqc kicks to only cause staminaloss when target is on the floor + vinylspiders: + - bugfix: admin triggering the Revenant event now works again + xXPawnStarrXx: + - rscadd: Most pies can now be sliced rather than being consumed whole. + - rscadd: Rootdough can be crafted using soy milk in place of eggs. + - rscadd: Two new lizard-safe rootbread sandwiches can be crafted. +2023-10-23: + SyncIt21: + - bugfix: items that require reagent containers & the reagents inside it for crafting(e.g. + molotov) now crafts properly. + - bugfix: most crafting recipes should work now + jlsnow301: + - rscadd: Just in time for Halloween- ghost notifications have been upgraded to + their own announcements. Boo! + san7890: + - qol: Adminwho messages are now in an examine block for heightened clarity. +2023-10-24: + DrDiasyl: + - qol: The Command intercom now has a High-Volume setting like command headsets + - qol: A memo telling frequencies of station radio channels are now present near + the Command intercom and T-Comms room + - image: Station, Command, and Prison intercoms have received new sprites + - spellcheck: Station and Command intercom descriptions have been changed to tell + about their functionality + Fikou: + - bugfix: surgical trays no longer animate when opened + Ghommie: + - bugfix: The polymorphic belt shouldn't work on animated objects. Logically wouldn't + have DNA. + Jacquerel: + - bugfix: Food created by mixing chemicals once again has a reagent purity based + on the component chemicals. + SyncIt21: + - bugfix: plastic sheet produces 4 tiles via the tiles option without using the + crafting menu + cnleth: + - bugfix: Replaced error spaghetti in thunderdome kitchen with regular cooked spaghetti + exdal: + - bugfix: hallucination announcements use new announcement style + jlsnow301: + - bugfix: Entering a virtual domain should no longer give you a message that it + doesn't forbid items +2023-10-25: + CoiledLamb: + - image: resprites air alarms + DrDiasyl: + - rscadd: New automatic weapon for the crew - Disabler SMG. Capable of rapidly firing + weak disabler beams. + - image: Muzzle flashes got a new sprite, each direction included! + - image: Temperature Gun "BAKE" beams are now lava colored + Hatterhat: + - qol: Universal scanners are now capable of recognizing the account owners and + assigned profit splits on barcodes. Cargo technicians are asked to do their + due diligence when matters call for it. + - rscadd: Anomaly-locked MODsuit modules can now be varedited to have unremovable + cores, or can be spawned with this functionality by using the /prebuilt/locked + subtype. + Jacquerel: + - bugfix: You will no longer be asked to construct meteor shields on stations which + cannot be hit by meteors. + Melbert: + - refactor: Refactored zombies to use the regenerator component. Now they'll have + a slight glow/animation when the regeneration actually kicks in. + - qol: Monkey cubes have a slight animation associated now. + - bugfix: Cooking Deserts 101 grants all intended recipes + OrionTheFox: + - qol: Railings now have Examine hints for how to deconstruct them + - bugfix: '[Tramstation] fixed a missing Scrubber in the Civilian Radiation Shelter' + Rhials: + - bugfix: The Infected Domain should no longer fill up with smelly, poisonous gas + over time. + TheBoondock: + - qol: improves blackout drunk character gameplay + - bugfix: fixed improper prob() placement that caused blackout character to be forced + sleep + - sound: added hiccup sound + jlsnow301: + - bugfix: Ghost alerts have been tuned down a bit. + lizardqueenlexi: + - refactor: Hostile skeleton NPCs now use the basic mob framework. They're a little + smarter, and they also have a slightly improved set of attack effects and sounds. + They love to drink milk, but will be harmed greatly if any heartless spaceman + tricks them into drinking soymilk instead. Please report any bugs. + - refactor: Hostile Nanotrasen mobs now use the basic mob framework. This should + make them a little smarter and more dangerous. Please report any bugs. + - bugfix: Russian mobs will now actually use those knives they're holding. + - refactor: Juggernaut constructs now use the basic mob framework. Please report + any bugs. + mc-oofert: + - bugfix: you may not enter knock path caretakers last refuge with the nuke disk + - bugfix: you can no longer cuff knock heretics in refuge + necromanceranne: + - bugfix: The nanites inside of thermal pistols are once again angry, and aggressively + want to burn/puncture people. +2023-10-26: + Cruix: + - bugfix: fixed hair gradients not applying correctly to huge afros + - bugfix: fixed hair gradients not applying properly on dismembered heads. + Xackii: + - bugfix: Fixed gorilla attack cooldown. Now attacking speed to mobs is the same + as attacking speed to objects. + carlarctg: + - refactor: Adds charges to omens and omen smiting rather than only being permanent + or one-use. Mirrors now grant seven bad luckers. + - qol: Reduces omen bad luck if nobody's nearby to witness the funny. (Ghosts are + included in the check!) + - bugfix: Fixed an issue where a monkey check in doorcrushing was never actually + able to pass. Also they screech now. + timothymtorres: + - qol: Add smoke particles to burning fireplace +2023-10-27: + FlufflesTheDog: + - bugfix: Wigs now properly follow your head when you're any non-standard height + Hatterhat: + - qol: Examining an ammo box (incl. magazines) now tells you the top loaded round, + so if you have different ammo types in different magazines, you can at least + try to figure out which one is which. + - spellcheck: Ammo boxes (incl. magazines) can now be set to use different phrasing + for their ammunition (e.g. cartridges, shells, etc. instead of just mixing "rounds" + and "shells"). + Jacquerel: + - bugfix: Dying when using (most) shapeshift spells will now kill you rather than + having you pop out of the corpse of your previous form. + - bugfix: Damage will now be accurately carried between forms rather than being + slightly reduced upon each transformation. + Watermelon914: + - rscadd: Reworked the colour schemes for the minor and major announcements as well + as their layout + - rscdel: Rolled back changes to deadchat notifications + - admin: Admins can now select the colour of their announcements as well as the + subheader when creating a command report. + mc-oofert: + - bugfix: venus human traps no longer die when on weeds +2023-10-28: + GPeckman: + - bugfix: The health bar on the mech diagnostic hud display should update consistently + now. + Jacquerel: + - bugfix: If a mob you are shapeshifted into attempts to grow up into a different + kind of mob then you will stop being shapeshifted + Melbert: + - bugfix: Disablers and Lasers now show their on-impact effects on hit mobs again. + - bugfix: People held at gunpoint can now flinch when being hit. + - bugfix: Regenerating mobs no longer stop regenerating no matter hit with what. + - bugfix: Pressure damage is now properly modified by a mob's brute damage modifier. + - bugfix: Fixes some occasions which some effects (glass jaw, explodable worn items) + won't respond to hits. + - refactor: Refactored core code related to mobs sustaining damage. + Xackii: + - balance: Sutures now heal a percentage of basic/animal max health instead of a + flat amount. + lizardqueenlexi: + - bugfix: Mobs without the "advanced tool user" trait - such as monkeys - are no + longer able to interact with camera consoles. + - bugfix: Monkeys can now properly attack parrots. + - bugfix: The Demonic Frost-Miner will no longer run around destroying the corpses + in its arena the moment the round begins. + unit0016: + - bugfix: Every misaligned railing ending has been corrected by the Nanotrasen Hall + Monitor's Lunchclub. +2023-10-29: + DrDiasyl: + - sound: Dying with a SecHailer on your face will make a unique death sound + Ghommie: + - rscadd: Added a few fish related bounties. + - rscadd: Fish cases to store and preserve life fish within can be now printed from + the service techfab and the autolathe. + GoldenAlpharex: + - code_imp: Added support to the wet_floor component to make it so the wet overlay + could not be applied to certain turfs if desired. + - bugfix: Ice turfs no longer look tiled, and instead look smooth when placed next + to one-another. + Melbert: + - bugfix: Fixes being unable to open airlocks with telekinesis + Paxilmaniac: + - code_imp: the deployable component has been tweaked and improved with some new + options to it + Profakos: + - bugfix: Gnomes no longer runtime if they explode while sinking into the ground + necromanceranne: + - bugfix: Every person on the station now no longer has the Tranquility Evades the + Shield Pinky Finger Shovegrab unarmed combat technique, an ancient and forbidden + strike that allows anyone (literally anyone) to bypass all forms of blocking + defense by simply not being in combat mode when they shove or grab their target. + As a direct result, the chakra energy of the Spinward Sector has become severely + misaligned. Oh well. +2023-10-30: + GPeckman: + - bugfix: Light-Eaten objects can no longer emit light after being turned off and + then back on. + - code_imp: Flashlights now use light_on instead of defining their own variable. + Please report buggy behavior. + - bugfix: Removed rare hard delete involving telecomms machines. +2023-10-31: + CRITAWAKETS: + - rscadd: Corn oil is now produced from corn instead of regular vegetable oil. + - balance: Glycerol now uses corn oil instead of vegetable oil in it's recipe. + - bugfix: Grinding corn now produces oil when it previously only made cornmeal despite + having oil in it's reagents. + Mothblocks: + - spellcheck: Changed candy description to read better + Toastgoats: + - rscadd: The Ethereal Vintner's Union has been "convinced" to trade their signature + Lanternfruit with Nanotrasen! + - image: Sprites for the aforementioned fruit. + jlsnow301: + - bugfix: Fixed an invisibility exploit on large mobs. Probably better this way + lizardqueenlexi: + - refactor: Wraith constructs have been converted to the basic mob framework. NPC + wraiths are now extra cruel and will attack the lowest-health mob they can see + at any given time. Make sure this isn't you! Please report any bugs. + - bugfix: Artificers and juggernauts no longer attack significantly more slowly + than intended. + - refactor: Pirate NPCs now use the basic mob framework. They'll be a little smarter + in combat, and if you're wearing your ID they'll siphon your bank account with + every melee attack! Beware! Please report any bugs. + mc-oofert: + - rscadd: living floor, living flesh, and other stuff for the bioresearch outpost + ruin + - rscadd: bioresearch outpost ruin + - bugfix: you may not defeat indestructible grilles and windows with mere tools + san7890: + - bugfix: Bitrunners can no longer get mass-mindswapped out of their avatar when + the wizard does the event. Something about machinery and magic not going well + together. diff --git a/html/changelogs/archive/2023-11.yml b/html/changelogs/archive/2023-11.yml new file mode 100644 index 0000000000000..9240318c4d340 --- /dev/null +++ b/html/changelogs/archive/2023-11.yml @@ -0,0 +1,1151 @@ +2023-11-01: + Data_: + - bugfix: The detective's curtains can be closed again in Birdshot. + EEASAS: + - rscadd: Added a new ruin to Ice Box Station, Lizard Gas Station + GPeckman: + - refactor: The wings you get from a flight potion (if any) are now determined by + your chest bodypart, not your species. + - qol: Functional wings can now be ground up to get the flight potion back, if you + want to get a different wing variant. + - bugfix: Practice laser carbines can no longer be used to rapidly fire regular + laser guns. + - bugfix: The fire visual on mobs should no longer persist after the fire has been + extinguished. + Iajret: + - code_imp: mod reskins now properly shows their icon when skins loaded from different + .dmi + Jackraxxus: + - bugfix: Obsessed's moodlets (Both positive and negative) go away when the trauma + is cured or the antag status is removed. + Jacquerel: + - bugfix: Non-human mobs can hallucinate their mothers without causing a runtime + error + LT3: + - code_imp: Changing security levels will only trigger the nightshift subsystem + if lighting changes are required + Melbert: + - qol: Clicking on an adjacent dense object (or mob) with Spray Cleaner will now + spritz it rather than doing nothing. This means you can use Spray Cleaner to + clean bloodied windows, as the janitor gods intended. It also means you can + fill a spray bottle with Napalm, I guess. + - refactor: Any cleaning object can now clean a microwave. + Pickle-Coding: + - bugfix: Fixes tesla zaps being weird. + - admin: Logs explosions from explosive zaps. + Profakos: + - refactor: Traders are basic mobs now. Please alert us of any strange behaviours! + - code_imp: If there is only one option, radial lists will autopick it. This behaviour + can be turned off via a new argument. + SethLafuente: + - rscadd: Added Abnormal Eggs + - rscadd: Added Two new spiders + - rscdel: Some Tarantula abilities + - balance: Spiders speed are now connected to health + - balance: Spiders now take more brute damage + - balance: All egg laying now has a cooldown + SyncIt21: + - bugfix: Plumbing IV Drip has full control over its injection/draining rate. No + longer displays the message controlled by plumbing system + - bugfix: Plumbing IV Drip can be plunged by plunger again + - bugfix: Cargo will remove/cancel orders from its cart if that order exceeds the + available budget (both private or cargo) and the player cannot cancel this order + manually. All order costs are rounded up to integer values + - bugfix: Galactic material market will deny appending stacks to your existing order + if it exceeds the available (private or cargo depending on the mode of ordering) + budget & if it exceeds the available materials on the market. Galactic material + market UI is overall improved. + Xackii: + - bugfix: chameleon projector now can copy icon for storage items(backpacks, box, + holsters etc) using right-click on it. + ZephyrTFA: + - bugfix: signals in circuits now actually function + deathrobotpunch1: + - rscdel: Removed the biometric scanning toggle from the PENLITE holobarrier + jlsnow301: + - bugfix: Paraplegics can now enter netpods. + - bugfix: Fixes an exploit caused by teleporting out of a netpod. + - bugfix: Outfit selection at netpods shouldn't give armor bonuses any longer. + - bugfix: Bluespace launchpads no longer work on shuttles + mc-oofert: + - rscadd: ctrlclicking the knock path eldritch id card will toggle whether it creates + inverted portals or not + moocowswag: + - bugfix: Modular shield generator modules no longer lose linkage when riding a + shuttle + - bugfix: Modular shield generators now gracefully turn off when being moved by + a shuttle rather than leaving their projections behind, the generator's description + was updated to advertise this behavior. + necromanceranne: + - bugfix: Lethal ballistic pellet-based shotgun shells no longer instantly delete. + - balance: Operatives can once again read about the basics of CQC at a reasonable + price of 14 TC. + - qol: All Syndicate MODsuits come with the potent ability to wear hats on their + helmets FOR FREE. No longer does any operative need be shamed by their bald + helmet's unhatted state when they spot the captain, in their MODsuit, wearing + a hat on their helmet. The embarrassment has resulted in more than a few operatives + prematurely detonating their implants! BUT NO LONGER! FASHION IS YOURS! + - qol: There is now a Core Gear Box, containing a few essential pieces of gear for + success as an operative. This is right at the top of the uplink, you can't miss + it! Great for those operatives just starting out, or operatives who need all + their baseline equipment NOW. + - qol: To avoid poor magazine discipline, most combat-ready personnel have instructed + _not_ to put magazines into the gun loops on their armor vests. + rageguy505: + - rscdel: Removed extra consoles from birdshot's bitrunners + starrm4nn: + - bugfix: The Syndicate Revolver now has a Syndicate Firing Pin on the Nuke Ops + uplink. + timothymtorres: + - rscdel: Remove duplicate pipe on pirate ship + - rscdel: Remove duplicate power computer on oldstation ruin + - rscdel: Remove duplicate light in battlecruiser starfury + - rscdel: Remove duplicate space heater from snowcabin ruin + zxaber: + - balance: Malf Ability "Robotics Factory" can now be purchased multiple times. +2023-11-02: + ArcaneMusic: + - qol: Steam vents in maintenance now have tooltips. + DrDiasyl: + - qol: There is now a more convenient way to prompt surrender to emote - the Security + Pen. Each officer is equipped with one in their PDA. Simply click someone with + it to prompt a 30-second surrender. They are printable at Security Techfab as + well. + - qol: Penlights now are printable at Medical Techfab. + - image: Penlights got a new cleaner sprite to replace its ancient one + - code_imp: The code for Penlight's holographic sign has been improved. + OrionTheFox: + - qol: The RPD now accepts upgrade disks inserted by hand, as well as their original + method of hitting the disk with the RPD + SyncIt21: + - bugfix: plumbing reaction chamber will attempt to balance the ph of the solution + a maximum of 5 times before giving up and thus preventing infinite loops, high + tick usage + - bugfix: plumbing bottler now only deals with bottles, beakers similar stuff but + not pills, patches, syringes or anything than can hold reagents. That & slime + extracts, slime industrial extract & shotgun shell + kawoppi: + - spellcheck: basic mobs getting shoved by humans now display the mob's disarm response + lizardqueenlexi: + - refactor: Proteon constructs now use the basic mob framework. The ones encountered + in ruins are a bit flightier now, and will briefly flee combat if attacked - + only so that they can return and menace you again soon after. Please report + any bugs. + timothymtorres: + - rscdel: Removed duplicate northstar message monitor computer + - rscdel: Remove duplicate syndi turret on waystation ruin + - rscdel: Remove duplicate HoP shutter from birdshot + - rscdel: Remove duplicate atmos fire alarm, cargo air alarm, and chemistry shutter + from icebox. + - rscdel: Remove duplicate machinery from russian derelict +2023-11-03: + Melbert: + - balance: Changelings gutted by Megafauna now take 8x as long to finalize revival + stasis (~5 minutes). + Mickyan: + - spellcheck: The Mothic Rations Chart poster description now mentions Sparkweed + Cigarettes rather than Windgrass + Profakos: + - code_imp: creatures in cowboy boots will retaliate properly + Xackii: + - bugfix: Gorilla and dexterity holoparasite can now take things out of the backpack + while holding it in his hand. + jlsnow301: + - bugfix: Bubblegum should no longer teleport out of the simulation when threatened + - rscdel: Chamber of Echoes map removed as it conflicts with the actual Legion + - rscadd: Added some clarity to the range of netpods (4 tiles) in their exam text. + - bugfix: Heretics won't lose their living heart while bitrunning anymore. + - bugfix: You can no longer eavesdrop on nearby borgs' radio comms if they're using + encryption keys + - bugfix: Possessed blades can attempt to channel spirits again + larentoun: + - rscadd: Emote Panel TGUI added in IC category. + lizardqueenlexi: + - bugfix: Basic mobs will no longer randomly walk into terrain that harms or kills + them. + mc-oofert: + - bugfix: venus human traps heal in kudzu again + moocowswag: + - bugfix: Space carp that arrive via rifts are no longer stricken with rift travel + related sickness that causes them to become weaker. + ninjanomnom: + - bugfix: Fixes turrets being invisible when they shouldn't be + timothymtorres: + - bugfix: Fix fireplace smoke particles to work properly with all directions +2023-11-04: + Jacquerel: + - balance: Gorillas, Seedlings, Gold Grubs, Mooks, Constructs, Ascended Knock Heretics, + Fugu and mobs subject to a Fugu Gland now rip up walls in a slightly slower + but more cinematic way. + Melbert: + - admin: Admins without `R_POLL` no longer have access to "Server Poll Management", + not that they could have used it anyways. + Tattle: + - bugfix: you should now be able to scrub through the library without lagging the + server + Zergspower: + - bugfix: Bounties - Virus bounties work once more + jlsnow301: + - bugfix: Added feedback for both extractor and extractee while using fulton extraction + packs. + - qol: Extraction packs now have better exam text. + - bugfix: Fixes midround selection for observers + - rscadd: Added a new fishing map to bitrunning. + - rscadd: You are no longer limited to pina coladas on the beach bar domain. Cheers! + lizardqueenlexi: + - refactor: Shades now use the basic mob framework. Please report any bugs. + nikothedude: + - qol: Character preferences now have descriptions as tooltips - hover over their + names to see them + rageguy505: + - rscdel: Removed a extra coffee pot in birdshot's security breakroom + san7890: + - refactor: The way mobs get specialized actions (like revenants shocking lights + or regal rats summoning rats to their side when you slap them) have been modified, + please report any bugs. + timothymtorres: + - rscdel: 'Remove duplicate machinery from Metastation: morgue disposal bin, medical + break room air alarm, and IV drip in permabrig medroom' + vinylspiders: + - bugfix: Softspoken quirk will no longer be applied to sign language +2023-11-05: + D4C-420: + - bugfix: lab coats no longer have directional sprites when thrown + Jacquerel: + - bugfix: Fugu can correctly destroy walls when they get big. + Profakos: + - bugfix: Basic mobs using JPS can move again + Tattle: + - bugfix: paintings can once again be filtered + lessthnthree: + - admin: Holobarrier creation now adds player fingerprint data + san7890: + - bugfix: The HFR will not print out a piece of paper every time you multitool it, + saving any desired energy to use for more useful processes. + timothymtorres: + - rscdel: Remove duplicate lighting from beach bar domain + timothymtorres, Rahlzel: + - sound: Port salute emote sound from Colonial Marines SS13 attributed to Rahlzel +2023-11-06: + JohnFulpWillard: + - bugfix: Mafia games can now start properly. + LT3: + - qol: Holobarriers will match the spray paint colour of their projector + - code_imp: Emergency shuttle announcements no longer use hardcoded values + - code_imp: Central Command announcements now correctly use its new name when changed + - spellcheck: Consistency pass on event announcements + Rhials: + - code_imp: The notify_ghosts proc has been cleaned up. Please report any abnormal + changes in deadchat notification behavior. + - qol: The on-screen deadchat popups now contain the notification blurb when hovered + with your mouse again. + SyncIt21: + - bugfix: reagent volumes should be consistent & non breaking across plumbing & + chemistry as a whole + - bugfix: plumbing reaction chambers are more proactive. Will attempt to take in + reagents more frequently +2023-11-07: + ArcaneMusic: + - qol: Light switches have tooltips, and may now be deconstructed with right click + using a screwdriver. + Deadgebert: + - bugfix: walls built next to firelocks no longer hold onto their alarms + FlufflesTheDog: + - balance: paraplegic is no longer exclusive with spacer or settler or spacer. Broken + legs don't discriminate! + Ghommie: + - image: Several holidays now have themed floor and tram tiling. + Higgin: + - bugfix: fixes synthflesh not dealing and in fact healing toxin damage. + Jacquerel: + - bugfix: The plasma river is about as deadly for animals as it is for humans. + - bugfix: Golems can now wade in the plasma river unscathed. + - bugfix: Undismemberable limbs will no longer be dismembered by the plasma river. + - balance: Golems and plasmamen cannot become husked. + - image: Robotic and Skeletal parts will remain distinct while the rest of the body + is husked. + LT3: + - bugfix: Maximum smoothing turf groups now includes cliffs + - bugfix: Tramstation floor tiles will correctly get custom station colors when + they exist + Melbert: + - rscadd: Fishers can now try their luck at fishing out of hydroponics basins. + MoffNyan: + - rscadd: Maltroach bar sign! + NeonNik2245: + - qol: Make notepad available for everyone, who has only laptop or console. + Watermelon914: + - rscadd: Added a user type to integrated circuits + cnleth: + - bugfix: Reagent lookup in chem dispensers now shows correct reagent metabolization + rates + lizardqueenlexi: + - bugfix: You will now only become blackout drunk if you've actually been drinking. + - bugfix: Observers should stop being notified that a nameless entity is blacking + out. + san7890: + - bugfix: Both magic mirrors and regular mirrors are far better at respecting the + choice of the beard you wish to wear (within reason, of course). + spockye: + - bugfix: fixed a couple missing and misplaced disposals pipes on metastation + timothymtorres: + - qol: Improve the emote help verb to be more user friendly + - bugfix: Fix light eater affecting lava, space, openspace, and transparent turfs + vinylspiders: + - bugfix: tails will no longer keep wagging even in death + - code_imp: added some trailing commas in lists that were missing them, fixed a + typo in comments + vvvv-vvvv: + - bugfix: Fix refresh button in log viewer +2023-11-08: + Ben10Omintrix: + - balance: sets the leaper move and pull forces to strong + Bumtickley00: + - spellcheck: You no longer hear weaponelding when deconstructing a closet. + D4C-420: + - rscadd: Being sufficiently drunk now has a chance to cause working out to fail + and harm you + Isratosh: + - bugfix: Nuclear operative induction implants now work correctly on antagonists + and fail on non-antagonists + jlsnow301: + - bugfix: The screen alert should no longer break ghost UI when it's huge + lizardqueenlexi: + - config: The bitrunner job now has a default config for server owners. + necromanceranne: + - balance: Harnessing Shoreline Quay (bluespace energy, probably), a mystical energy + (total bullshit) that permeates the Astral Waterways (bluespace quantum dimensions, + probably), Sleeping Carp users can now once against deflect projectiles with + their bare hands when focused in on battle (in combat mode). + - balance: The Keelhaul technique is now nonlethal (a philosophical acknowledgement + of the familial bond of sleep and death), but causes the target to become temporarily + blind and dizzy along with its previous effects. + - balance: Sleeping carp users, while in combat mode, deal Stamina damage with their + grabs and shoves. If the target of their grab has enough Stamina damage (80), + they are knocked unconscious from a well placed nerve pinch. + - balance: Sleeping carp users find it very hard to wake up once they fall asleep.... + san7890: + - bugfix: Slimes now need to be on an open turf to reproduce and split into more + slimy slimes, instead of getting away with using phasing powers in pipes. + - bugfix: All vehicles (such as VIMs operated by a mouse or a lizard) will no longer + be able to phase through containment fields. +2023-11-09: + GPeckman: + - balance: Flightpotion wings will no longer make health analyzers list you as nonhuman. + KingkumaArt: + - rscadd: Enginenering rebar crossbows + tot kit + - rscadd: Added a bunch of ammos and crafting junk to make the ammo exist + - image: added icond for all the above + Melbert: + - rscadd: Anomalies, portals, and bluespace rifts will now wibble a bit. + SyncIt21: + - bugfix: Material market buy buttons greys out correctly and thus prevent you from + placing orders that exceeds the available budget. + - qol: machines/devices that ask you to pick a reagent name from an input list have + their names sorted alphabetically & preserves white space between words making + them more readable. + - qol: improves performance of plumbing reaction chamber furthur + - code_imp: cleaned up code for portable chem mixer & chem dispenser. converted + their ui to typescript + Toastgoats: + - balance: Venus human traps now take 12.5 damage per second instead of 20 while + off kudzu. + necromanceranne: + - rscadd: With the flood of Chi within the Spinward Sector receding, various masters + of The Tunnel Arts, colloquially known as 'Maint-fu Masters', have started to + refine the basics of their martial techniques. New forms have started to develop + within Spacestation 13's hidden maintenance dojos. + - rscadd: Someone shoved off-balance makes them vulnerable to more guaranteed unarmed + strikes, knockdowns from a successful punch, and more difficult to escape grabs. + - rscadd: Grabbing someone (as well as kicking them while they're on the floor) + makes them more vulnerable to taking unarmed attack damage, even if they have + armor. + - balance: Unarmed strikes made with human-equivalent limbs have higher damage floors, + meaning you overall do more damage on average while not increasing the overall + damage potential. It's more consistent! + - refactor: Significantly changed how punching accuracy and knockdowns are calculated. + - balance: Golem and mushroom limbs are a lot more effective at punching as a result + of these various changes. As they should be. + xXPawnStarrXx: + - rscadd: Three new plants; Peppercorn, Saltcane and Butterbeans. + - rscadd: Soysauce fermentation to soybeans. + - rscadd: Saltcane can be dried into a replacement seaweedsheet for sushi. + zergspower: + - balance: NPC Syndicate Shotgunners range requirement returned +2023-11-10: + Jacquerel: + - qol: Adds the capability for some player-controlled mobs with ranged attacks to + repeatedly fire their natural weapons by holding down the mouse button. + Profakos: + - spellcheck: Renamed Knock to Locks, and changed most of the flavor text of knowledge + gain, and renamed some items and knowledges from the path. + SyncIt21: + - bugfix: Examining circuit boards now displays their detailed names(tier included) + correctly if required. + - bugfix: plumbing factories should not rarely/randomly brick at volumes like 0.9999(when + in fact it should have been 1) + - code_imp: removed order history, import & export value from cargo & economy subsystems. + Allow supply packs to be properly deleted. In general memory savings + Thunder12345: + - rscadd: New Oranges' Juicery bar sign for the OJ connoisseurs. + TwistedSilicon: + - bugfix: Cryo cells now use their direction to orient their initial connection + instead of defaulting to South. + Zxaber, DrDiasyl, Maurukas: + - rscadd: A new security-focused combat mech, the Paddy, has been added, intended + to be particularly helpful for lone sec officers. You will find one in the Security + main office, and a replacement can be built with late-game mech research. + - bugfix: Ripley MK-I and MK-II mechs no longer qdel their stored items when destroyed. + san7890: + - bugfix: Nanotrasen has clarified an issue with their manual publishers, and these + guides should now contain actual user-pertinent content. + starrm4nn: + - qol: Bandolier can quick gather items now. + - balance: Bandolier capacity increased to 24 and can carry .357 ammo now. + timothymtorres: + - bugfix: Fix holodeck items from being juiced or grinded with a biogenerator or + pestle and mortar + - rscdel: Removed duplicate shutter from HoP office in birdboat, air alarm in northstar + maint, and portable air pump in northstar maint. +2023-11-11: + DaCoolBoss: + - rscadd: 4 new space ruins + Jacquerel: + - image: Adds yet another bar sign, this one mining themed + KingkumaArt: + - bugfix: fixed engi crossbow being able to be used onehanded + ability to craft + with sci inducers + MTandi: + - qol: Ordnance burn, freezing and supermatter chamber air alarms now show the air + contents on the tile of the connected sensor inside the chamber. + nikothedude: + - balance: Snails no longer receive blunt wounds, meaning sharp weapons can dismember + them more easily + - balance: Slimes can no longer receive dislocations + timothymtorres: + - bugfix: Fix mapping linter not identifying duplicate blacklisted objects on a + turf + - code_imp: Add a mapping linter to check for stacked machinery + - rscdel: Remove duplicate windows from birdshot, delta, and anomaly research ruin. +2023-11-12: + DaCoolBoss: + - spellcheck: Fixed typos in the examine text for the lead pipe & lead-acid battery. + Jacquerel: + - refactor: Guardians/Powerminers/Holoparasites now use the basic mob framework. + Please report any unexpected changes or behaviour. + - qol: The verbs used to communicate with, recall, or banish your Guardian are now + action buttons. + - balance: If (as a Guardian) your host moves slightly out of range you will now + be dragged back into range if possible, rather than being instantly teleported + to them. + - balance: Protectors now have a shorter leash range rather than a longer one, in + order to more easily take advantage of their ability to drag their charge out + of danger. + - balance: Ranged Guardians can now hold down the mouse button to fire automatically. + - balance: People riding vehicles or other mobs now inherit all of their movement + traits, so riding a flying mob (or vehicle, if we have any of those) will allow + you to cross chasms and lava safely. + Melbert: + - bugfix: Split persons can talk to their host once again + - bugfix: AI controlled mobs which are immobilized are now properly immobilized + - bugfix: People with copied memories can modify their bank account ID as normal. + - rscdel: The Stethoscope no longer tells you if the target is missing a heart or + lungs. Now, it will simply say the target is lacking a pulse or not breathing. + vinylspiders: + - code_imp: gets rid of the rest of the instances of 'targetted' typo from code +2023-11-13: + DATA_: + - spellcheck: The examine message for a carbon with an empty golem stomach now properly + matches said carbon's gender. + Jacquerel: + - bugfix: Goats will now calm down after getting grumpy without causing a runtime + error. + JohnFulpWillard: + - bugfix: '[Mafia] Obsessed now knows who their target is in their role description, + and people playing on PDA are told in their chat.' + Melbert: + - bugfix: Fix being unable to resist out of ice cubes + Momo8289: + - spellcheck: Made the remembrance day greeting message more tasteful. + SethLafuente: + - bugfix: fixes holodeck missing medical tools + Vishenka0704: + - bugfix: Now you can refuse the pirates request. + YesterdaysPromise: + - bugfix: Replaces the jetpack in Interdyne pirates' suit storage with air tanks. + They need to breath, and already got the suit for speed. + necromanceranne: + - bugfix: The Dread Disciples of Maint Khan, notorious Tunnel Arts practitioner + and maintenance warlord, have been driven from Nanotrasen stations within the + Spinward Sector. The average punch accuracy has been increased as a direct result, + with the most exhausted puncher now having a max potential inaccuracy of 80%, + rather than the absurd 20% of the Disciples. + nikothedude: + - code_imp: Quirks are now customizable on the quirks page instead of on the character + prefs page + vinylspiders: + - bugfix: emped bar signs will now display the correct sprite + - image: added a more detailed lightmask for the emp bar sign sprite + zxaber: + - bugfix: Ashwalker Tendril Revives no longer ghost the player being revived. +2023-11-14: + 1393F: + - bugfix: The Sleeping Carp scroll no longer says deflect using throw mode. + Ben10Omintrix: + - refactor: gutlunches have been refactored into basic mobs. please report any bugs + - rscadd: ashwalkers have a small ranch they can manage + - bugfix: wall tearer compnent wont runtime when interacting with mineral walls + BlueMemesauce: + - bugfix: Fixed tcomms relays being weird, they should be sabotagable again + Danny Boy: + - bugfix: Fixed Signer eyebrow raising/lowering indicators and emotes + - bugfix: Fixed Signer RuneChat punctuation + - bugfix: Signers no longer sign with their species' tongue + GPeckman: + - qol: Fixed/improved feedback when failing to apply a direct law change to a cyborg. + - bugfix: Borgs who are unsynced from a malf AI now lose the zeroth law as intended. + Hatterhat: + - bugfix: Kronkaine's action speed buff now stops when metabolized out. + - bugfix: Drug-related moodlets should now time out properly. They still linger + after metabolization ends, but they no longer last forever. + Likteer: + - rscadd: Green Beer has an overdose effect now. It will permeate your skin. + MTandi: + - balance: Mech overclock coefficient is down to 1.25 from the default 1.5 for Ripley + and Clarke. + - balance: Mech overclock heating now scales with movespeed, higher speed - faster + overheat. + - bugfix: fixed pipe painter not applying pipe color properly + - qol: made spraycans work also as pipe painters + - qol: spraycans now have basic color presets for quick selection + - image: Crates got new sprites + - image: Added more crate styles + Majkl-J: + - bugfix: You can now eject blank IDs from modular computers + Melbert: + - balance: Body temperature from being lit on fire will soft cap at 1,200 K. It + will still increase beyond this, but with diminishing returns. For example, + at 5,000 K, fire will heat 67x weaker. + - bugfix: Fixes some potential exploits and issues involving shielded equipment. + OrionTheFox: + - image: Resprites the Reactive Anomaly Armor + Rhials: + - bugfix: The full mining lockers in the Lavaland Mafia map have been replaced with + (empty) mining carts. + SethLafuente: + - bugfix: fixes half-covering glassware protecting eyes from chemicals + SyncIt21: + - bugfix: suite storage units can be locked again. Remember to install a card reader + or set the access levels in the airlock electronics inside its stock parts & + finally swipe your ID to properly enforce access control. + distributivgesetz: + - bugfix: Removes some roundstart active turfs. + - bugfix: The PDA painter and the emergency shield generator can now be destroyed. + - code_imp: Atoms no longer break again after they are hit when broken, making them + hopefully more stable in the future. + larentoun: + - qol: 'Gunpoint: Examining the target will show who is holding them at gunpoint' + - qol: 'Gunpoint: Examining the shooter will show who they are holding at gunpoint' + - balance: 'Gunpoint: If the target tries to grab, they will trigger the shot' + - balance: 'Gunpoint: If the target or the shooter are shoved, it will cancel the + gunpoint' + - balance: 'Gunpoint: If the target is pulled, it will cancel the gunpoint' + - balance: Both the target and the shooter can't be bumped anymore to avoid cheesing + charged shot or removing the gunpoint by just moving around + - bugfix: Clicking the alert button of the shooter will now correctly remove gunpoint + mc-oofert: + - bugfix: tramstation cargo disposals outlet has been repositioned to not softlock + whoever cannot lay down + san7890: + - bugfix: Gorillas and Regal Rats will no longer show up in the ghost-control menu + if they died without anyone ever taking control of them. + timothymtorres: + - rscadd: Add bamboo seeds to ash walker den. This lets them craft blowguns, crude + syringes, bamboo spears, punji stick traps, and more! +2023-11-15: + Ben10Omintrix: + - bugfix: bileworms will now attack + D4C-420: + - spellcheck: hopefully changed all instances of the word 'mjolnir' to 'mjollnir' + Ghommie: + - bugfix: Fixed a small issue with disposal outlets leaving contents about to be + ejected stuck inside the pipe beneath it if deleted. + Jacquerel: + - balance: Sapient brimdemons can't hurt themselves with their own beams + JohnFulpWillard: + - refactor: Destructive Analyzers now have a TGUI menu. + - bugfix: PDAs now log that they've been emagged, but will no longer log any further + programs they open beyond that. This means Nukies don't sell themselves out + by opening their disk tracking app. + LT3: + - bugfix: Bad luck omen again raises your chance of getting shocked by the tram + plate + - bugfix: Tram plate checks and energizes when the tram is moving + - code_imp: Omen component now applies the cursed trait + Melbert: + - code_imp: General heart code cleanup. + - bugfix: Heartbeat sound effects are no longer sourced to the exact tile you fell + into crit at + - bugfix: Abductors glands are less likely to become invisible or look wrong + - bugfix: Ethereal hearts are less likely to become invisible or look wrong, and + now properly spawn with their shine overlay + - image: Adds heartbeat animation to beating Ethereal Hearts + - bugfix: Fixes hallucination and encrypted announcements printing to the Newscaster. + SyncIt21: + - bugfix: drying rack now shows correct examines & screen tips. + - code_imp: tone of code organization for smart fridges overall. changed ui to typescript. + - qol: added more detailed examines & screen tips for smart fridges. drying racks + can be dismantled with a crowbar and not simply pried open with it. + TwistedSilicon: + - bugfix: invisimin verb now makes you invisible to all HUDs too! No more floating + healthbars or job identifiers giving you away while you sneak around. + jjpark-kb: + - qol: looms will now attempt to loop through stackable items (cotton as an example) + larentoun: + - rscadd: Emote Panel TGUI added in IC category. + mc-oofert: + - bugfix: The plaguebearer can no longer depower virology on Tramstation + san7890: + - bugfix: Safeties in the code have been added to prevent things in disposals going + into nullspace whenever they get ejected from a pipe - you will just magically + spawn at the turf that you were meant to be flung towards. + - qol: You will no longer be added to the list for ghost-orbit role polls if you + have opted out of getting antag ghost roles in your preferences. + - qol: You will get a tgui_alert to accept the ghost role if you were selected via + the orbit poll, instead of it just throwing you intot he role. + san7890, Ghommie: + - bugfix: The Blessing of Insanity now grants no damage slowdown and free hyperspace + movement correctly. +2023-11-16: + ArcaneMusic: + - bugfix: The Galactic Material Market now respects quantity of materials purchased, + removing them from the market when bought and preventing you from ordering more + than are available at a given time. + Fikou: + - qol: Dead human examines count as "soul departed" when the client is disconnected + or the human doesn't have a brain anymore. + Shroopy: + - bugfix: Molotovs now splash before burning, not after + deathrobotpunch: + - qol: the donksoft vendor refill cartridge is now available at the service lathe + falconignite: + - bugfix: Lizard tail wagging graphics + san7890: + - balance: Male Goats should no longer spawn with an udder, instead of it just being + Pete. +2023-11-17: + CRITAWAKETS: + - qol: The supermatter filters have been flipped on BirdshotStation to work like + the supermatters on every round, meaning the filtered gas goes in, and the non-filtered + gas comes out. + Dalm: + - image: A tea room sign for the bar + Fikou: + - bugfix: grabs no longer trigger krav maga + GPeckman: + - rscadd: Androids now have robotic brains instead of organic brains. + Ghommie: + - bugfix: Fixed catwalks over open space not making a sound when walked over. + - rscadd: The mother hallucination has more possible one-liners now. + Melbert: + - balance: Stop, drop, and roll no longer instantly clears 5 fire stacks off of + you - Instead, it will clear 1 fire stack off of you every time you roll, with + a roll every 0.8 seconds. + - balance: Stop, drop, and roll no longer stuns you for 6 seconds. Instead, it will + knock you to the floor while you are rolling. Moving around or getting up will + cancel the roll, and you cannot use items while rolling around. + - balance: Stop, drop, and roll will now repeat until the fire is put out or you + get up. + RedBaronFlyer: + - image: light tube inhand sprites are now grey, as are the icons for the light + bulb/tube/mixed boxes + Shadow-Quill: + - bugfix: 'The CRAB-17 will now only take whole credits, as fractional credits were + found to be worth less. + + :cl:' + SyncIt21: + - code_imp: removed redundant procs `get_master_reagent_id()` & `get_master_reagent_name()` + - code_imp: merged `remove_all_type()` proc with `remove_reagent()` now this proc + can perform both functions. `remove_reagent()` now returns the total volume + of reagents removed rather than a simple TRUE/FALSE. + - code_imp: merged `trans_id_to()` proc with `trans_to()` now this proc can perform + both functions + - refactor: plumbing reaction chamber will now use only a single tick to balance + ph of a solution making it less efficient but more faster. Just make the reaction + chamber wait for longer periods of time to accurately balance ph + - refactor: reagent holder code has been condensed. Report any bugs on GitHub + Vekter (on behalf of Constellado): + - image: 'Added a new bar sign as one of the winners of our Bar Sign Contest: "The + Assembly Line".' + YesterdaysPromise: + - rscadd: Adds a chance that, when sharpened, a sufficiently potent carrot will + turn into a sword instead of a shiv. + Zergspower: + - bugfix: Space Ruin - All American Diner - Soda Machine now is scooted out of the + way + jlsnow301: + - rscadd: Bitrunning Patch 1 features a host of changes! + - rscadd: Added randomized mobs to virtual domains, which will be indicated with + a unique icon. + - rscadd: New emag interaction with the quantum server. Antags will spawn more frequently, + and they can hack themselves onto the station. You have been warned. + - rscadd: Both living and dead players can now see which mob is going to spawn an + antagonist in the vdom. + - rscadd: 'Two new vdom antagonists: Cyber Tac and the NetGuardian. These unlock + at specific thresholds.' + - balance: You can no longer stack copies of the same ability with bitrunning disks. + - balance: Some of the disk items have been replaced with stronger versions. + - bugfix: You can no longer spy on crew using the advanced camera console on syndicate + assault. + - bugfix: Fixed the spawning mechanism of virtual domain antagonists. You should + now have a chance of playing as one. This chance increases as more domains are + completed. + - bugfix: Vdom antagonists shouldn't spawn at the end of the run any longer. + - bugfix: The preference for vdom antagonists has been changed to factor in the + new types. Check your preferences! + - bugfix: The quantum server will now show its balloon alerts to all observers. + - bugfix: Random domains should be fully random again. + mc-oofert: + - qol: if you die in a mech you automatically eject + mogeoko: + - bugfix: Turbine parts will now use an amount of materials no greater than needed + for the upgrade + san7890: + - bugfix: Bar Bots (and several other mobs) will no longer aggro on you if you click + on them with a "forceful" item from halfway across the room. + - balance: After a string of unfortunate incidents, persons with telekinesis have + been strongly warned against playing Russian Roulette, as they tend to hyperfixate + on the gun a bit too much and end up firing it directly at their head. +2023-11-18: + Ben10Omintrix: + - bugfix: gutlunches will stop having too many children + - balance: gutlunches are no longer in the mining faction + JohnFulpWillard: + - refactor: Secure briefcases are now actual briefcases. + - refactor: Wall safes are now structures, rather than items that can't be picked + up. + - refactor: Lockable items (Wall safes & Secure Briefcases) now use TGUI. + ZephyrTFA: + - rscadd: Chat Reliability Layer + - code_imp: TGUI chat messages now track their sequence and will be resent if the + client notices a discrepenency + jlsnow301: + - bugfix: After correcting a slight miscalculation, Bit Avatars now have hands again. + nikothedude: + - code_imp: TGUI preference lists can now have sorting prefixes to allow for specific + placement of items +2023-11-19: + DaCoolBoss: + - spellcheck: Nukie and ERT defibrillators now reference combat mode instead of + intents. + Ghommie: + - rscadd: Abductors have bigger brains. + JohnFulpWillard: + - rscadd: Infiltrators (Latejoin/Midround traitors) can now buy and use Contract + kits again. + - rscdel: Contractor baton can now only be purchased once. + LT3: + - bugfix: Tram will no longer electrocute innocent, law abiding crew trying to use + the crosswalk when there's no tram in sight + Melbert: + - refactor: Refactored another large chuck of attack code, primarily involving melee + item attacks and non-human mob attacks. Report if you see anything weird + - bugfix: Pacifists clicking on simple robots or silicons no longer causes sparks + - bugfix: Blocked thrown batons are now properly... blocked + - bugfix: Plates now respect the weight class of items on top. + - bugfix: Fried items now respect existing volume cap. + - bugfix: Smartfridges now don't accept bulky food items, good thing we have none + of those right guys? + Rhials: + - spellcheck: The Grey ID Cargo Crate is now spelled properly. + SapphicOverload: + - bugfix: You can once again use alt to turn while strafing in a mech + Shroopy: + - qol: Implanted HUDs can now be toggled on and off with an action. + SyncIt21: + - bugfix: selling large amount of mats in cargo should not give you infinite credits + - bugfix: runtime when adjusting material market after buying + WarlockD: + - bugfix: ' Attaching a circuit to the air alarm now reads from the correct turf.' + YakumoChen: + - balance: Paint cans hold 20x more paint than before, painters rejoice! (Janitors + cry more) + distributivgesetz: + - bugfix: Fixes a runtime when the radioactive nebula trait runs with a map that + has no virology area. + mc-oofert: + - bugfix: you can climb over more stuff with a climbing hook + san7890: + - bugfix: '"Old Chat" (AKA: The old-styled non-TGUI raw-HTMLesque chat that you + might see when it prods you with the "Failed to load fancy chat!" issue) should + now get all text messages as expected.' +2023-11-20: + EuSouAFazer: + - qol: Slightly moved the universal enzyme on meta's kitchen to a prettier spot. + - bugfix: The universal enzyme on meta's kitchen is no longer unecessarely varedited. + Ghommie: + - bugfix: Fixed the infinite growth serum exploit. + - bugfix: Fixed generic nutriment processing even when dead. + - bugfix: Fixed an issue with un-hidden (alien, syndie etc.) nodes not being researchable. + - bugfix: The aquarium auto-feeding feature now works correctly. + JohnFulpWillard: + - qol: NTNet Downloader now has a search bar, and programs are now better sorted. + KingkumaArt: + - bugfix: Stopped a DS crash when shooting a rebar crossbow in specific circumstances. + LT3: + - bugfix: Delam counter will correctly show 0 the shift after a delam + MTandi: + - bugfix: Hydrotrays consume nutrients according to their proportion in the mix, + instead of randomly picking reagents to consume every cycle. + Melbert: + - admin: Adds a button to check-antagonists that allows admins to send Nuke Op reinforcements + with a single button + - admin: Nuke Ops check antagonists now show you full war status (declared / not + declared) + - bugfix: Fixes multiple nuke teams (or an admin) being able to declare war at once + - rscdel: Heretic side path points are gone + Rhials: + - rscadd: Nuclear Operatives, in an attempt to appeal to the more "tacticool" members + of their cause, have begun using callsigns to designate themselves. Check your + preferences to set your Operative Alias! + - qol: At the request of the more vain members of the cause, hair dye has been added + to the Operative Firebase dorms. + SyncIt21: + - bugfix: debug chem synthesizer works again. cleaned up chem dispenser, portable + chem dispenser & debug chem synthesizer ui code + - qol: ui for displaying beaker reagents for debug chem synthesizer has been improved. + Now displays input list for adding reagents + - code_imp: Removes & merges `get_multiple_reagent_amounts()` proc with `get_reagent_amount()` + inside reagent holder + - code_imp: Removes & merges `get_reagent()` proc with `has_reagent()` inside reagent + holder + - code_imp: Removes & merges `has_chemical_flag()` proc with `has_reagent()` inside + reagent holder + - refactor: Reagent holder code has been further compressed. Report bugs on github + Tattle: + - bugfix: admin painting manager works again + jlsnow301: + - qol: Nearly every ghost alert should now feature a "VIEW" button, even those with + click interaction. + - rscdel: Ghost alerts no longer show the entire message in the tooltip, instead + have been replaced with titles. + - bugfix: Chat shouldn't bluescreen at the start of the round + - bugfix: Admins can spawn bitrunning events (again!) + lizardqueenlexi: + - bugfix: Hardcore Random will no longer assign incompatible quirks. + - spellcheck: Some roundstart tips have been made clearer regarding "suits" vs. + "exosuits". + san7890 and Ben10Omintrix/Kobsamobsa: + - refactor: Parrots (including Poly) have undergone a massive refactor, please report + any bugs or unexpected behavior that you may encounter. + - qol: Left-clicking a parrot with a cracker will tame it, right-clicking a parrot + with a cracker will now feed it the cracker. + vinylspiders: + - bugfix: fixed a race condition with mutations + - bugfix: fixes bug that was preventing high luminosity eyes' light from turning + on + - bugfix: fixes eyes being on the wrong side of the head when facing east/west + yooriss: + - refactor: Icemoon wolves now use the basic mob framework and should act more intelligently, + defending their pack. + - rscadd: Icemoon wolves can be tamed with slabs of meat and can be ridden as mounts + once friendly. Being rather large dogs, they also have access to most of the + pet commands you'd expect, such as fetching things, and violently mauling people + their owners point at. +2023-11-21: + 00-Steven: + - balance: signers no longer suffer from social anxiety's speech changes when they + go non-verbal. Other effects are maintained. + DaCoolBoss: + - bugfix: Removed a duplicate grille from the abandoned mime outpost space ruin, + and replaced the 'energy weapon lenses' with spent revolver rounds. + EuSouAFazer: + - bugfix: Meta's recycler works again + Hatterhat: + - balance: The SC/FISHER disruptor pistol is now more likely to show up in black + market uplinks. + - balance: The SC/FISHER now has more range (21 tiles up from 14), and is usable + by pacifists. + Melbert: + - bugfix: Atrocinating mobs will now behave more as you'd expect. Meaning they don't + slip on wet patches, can't trigger bear traps / landmines / mouse traps, ignore + conveyors, and can walk over tables and railings. + - bugfix: Floating mobs are unaffected by conveyor belts, acid (on the ground), + glass tables + - bugfix: Floating mobs won't squish stuff like roaches anymore + - bugfix: Fixes bear traps triggering on floating / flying mobs + OrionTheFox: + - qol: Allergy Dogtags (and any other dogtags, really) are now Tiny items and can + fit into wallets. + Thlumyn: + - bugfix: healing viruses can no longer have floor virus side effects + YesterdaysPromise: + - image: 'Following now have unique item sprites: syndicate war declaration radio, + curator and chief beacon''s, chaplain beacon.' + - image: 'Following now have unique inhand sprites: radio, export scanner, walkie-talkie, + syndicate war declaration radio, curator and chief beacon''s, chaplain beacon.' + dieamond13: + - bugfix: adds back one way exits to Tramstation science's entrance + necromanceranne: + - bugfix: When you successfully block a body collision, it does something rather + than nothing at all. + - rscadd: The battle against Maint Khan's forces rages on in the periphery stations + of the Spinward Sector. And with it, a new breed of unarmed warrior has emerged; + the cybernetic martial artist. Nanotrasen, rather than quell the minor maintenance + civil war brewing in their sector, have chosen to exploit this conflict to push + their weapons and cybernetics research to new heights! + - rscadd: Advanced cybernetic arms can be printed at the Robotics exofabricator + once researched. They are unlocked by researching the Advanced Robotics Research + node. + - rscadd: Advanced cybernetic arms are more durable than standard limbs, and also + have higher unarmed potential. + - balance: Strongarm implants now utilize the attacking limb's unarmed potential + to determine damage and potential armor penetration. It also does additional + stamina damage (1.5x punch damage) + - balance: Surplus prosthetic limbs contribute more of their carried damage to overall + health (AKA they make you actively more vulnerable to damage), and deal less + damage with unarmed attacks. Take Quadruple Amputee at your own risk. +2023-11-22: + Bumtickley00: + - balance: The CMO's hypospray now holds 60u, and can be set to inject smaller amounts + of reagents + Ghommie: + - spellcheck: Examining a human mob as an observer displays "Quirks", not "Traits" + Jane: + - rscadd: Bargonia Bar Sign for Cargo Bar Enjoyers + JohnFulpWillard: + - bugfix: '[Icebox] Atmos techs have access to the Engineering front desk windoor.' + - qol: '[Icebox] Security''s lower floor is not as easily cut off from the powernet + anymore.' + LT3: + - bugfix: Cursed/bad luck omen will now stick with the player for more than 1 incident + MTandi: + - bugfix: Emag overlay on lockers fixed + - image: New locker sprites + - image: Added new lockers + Xackii: + - bugfix: Androids cannot have overdose effect by any chems. + dieamond13: + - bugfix: removes a duplicate bookcase in icebox permabrig library + lizardqueenlexi: + - bugfix: Skillsoft's skillchip stations are now ADA-compliant (Astronauts with + Disabilities Act). Paraplegic characters can now implant themselves with skillchips, + the same as anyone else. + - bugfix: Heads impaled on spears now render in the correct place on the tip, instead + of halfway down the shaft. + - bugfix: Blind personnel are no longer able to magically see heads impaled on spears + from a distance. +2023-11-23: + OrionTheFox: + - qol: Allergy Dogtags (and any other dogtags, really) are now actually whitelisted + to fit into wallets. + SyncIt21: + - bugfix: autolathe does not diminish materials from custom material items like + toolboxes when printing them in bulk. Also does not gray out that item in the + UI + - bugfix: autloathe correctly updates UI after inserting items into it + Y0SH1M4S73R: + - rscadd: Certain types of pens now function like you expect they would when inserted + into a foam dart + - qol: Examining a foam dart closely will show you how to modify it, or what it + is modified with + aaaa1023: + - qol: adds pixel perfect 4x, 4.5x, and 5x + necromanceranne: + - balance: Judo Joe, archnemesis of Maint Khan, has begun re-airing his midnight + infomercials shilling his extremely expensive Tackle Supreme Judo Karate Training + video tapes. Unable to pass up a 'bargain', Nanotrasen has purchased these tapes + en masse. Tackling techniques have started to improve, as well as Nanotrasen's + tackling instructional algorithms within tackle gloves. + - balance: The outcomes for tackling are more equalized. It isn't as feast or famine, + and should be somewhat more controllable without becoming too severe. + - rscadd: Blocking successfully against a tackle will force the tackle to be a neutral + outcome. + - rscadd: Unarmed effectiveness from arms now contributes to attacking with and + defending from tackles. + - rscadd: Those who refuse to use firearms (like Sleeping Carp users and insane + unholy berzerkers) are better at tackling others. + - rscadd: Riot specialized armor, and not just riot armor, now contributes meaningfully + to tackling effectiveness. + - balance: MK.1 Swat Suits, the ones that come in SWAT crates, now functions similarly + to riot armor. + - rscadd: Settlers from the outer rims have noticed they aren't very good at protecting + themselves against Judo Joe's clearly discriminatory tackling techniques. + - rscadd: Security lockers come with gripper gloves, security vendors now sell them + as standard items, and the HoS' garment bag now has a pair of gorilla gloves. + Gripper gloves have a positive skill bonus to tackling. + - rscadd: Being insane also makes you INSANELY good at tackling but also INSANELY + likely to eat shit on a whiff. DO OR DIE, BITCH. + - refactor: Shoving slowdown and all its implementations now use a status effect, + Staggered. + vinylspiders: + - bugfix: fullupgrade chem dispensers will now spawn with all their chems +2023-11-24: + Autisem: + - rscadd: Cyborg inducer for engineering borgs + - balance: The borg RPED can hold as many part as the BSRPED now + - balance: Cyborg chargers now draw from the power net as cell chargers do + Jacquerel: + - rscadd: A new skill chip can be found in maintenance or purchased from the vendor, + allowing you to experience food in new and exciting ways. + - rscadd: Abductors also have access to this incredible power, simply using their + genius level brains. + JohnFulpWillard: + - qol: Mafia panel no longer shows vote buttons if you're on stand, shows the roles + of revealed players, and list out who is on the stand, if any. + SyncIt21: + - bugfix: RCD can build directional windows on top of existing grills & without + them. + - code_imp: removes & merges `expose_multiple()` proc into `expose()` proc inside + reagent holder + - code_imp: removes `conditonal_update()` proc & `on_update()` proc inside reagent + holder and reagent + - refactor: Reagent code has been trimmed and split into multiple files. report + bugs on github + TwistedSilicon: + - bugfix: Window damage overlays have been fixed. + Xander3359: + - admin: Remove "Make AI" from VV dropdown + dieamond13: + - qol: gives roundstart prisoners a key memory of what their crime is + jjpark-kb: + - bugfix: gutlunches now produce miner salve instead of milk, as well as the other + reagents if fed the correct ore +2023-11-25: + MTandi: + - image: SM shard sprite updated + Rhials: + - qol: Gives Cargo areas its own wire layout, instead of having it use the same + wires as Service areas. + Time-Green: + - rscadd: Adds an arctangent2 component to circuitry! +2023-11-26: + DaydreamIQ: + - qol: Mothuchi's pizzeria has been improved slightly! Order yourself a fresh mothic + pizza today! + Majkl-J: + - code_imp: HUDs no longer use their own trait variable + Momo8289: + - bugfix: MOD quick carry modules now give the correct carry speed bonus + Profakos: + - refactor: Slimes's colour, core type and mutation list is now held in a slime + type datum + - code_imp: Slime's variables have been documented, and renamed a bit to add clarity. + Please report bugs that might stem from renaming. + - bugfix: Slimes are not longer prevented from attacking pacifist humans. + - qol: Slime scans now display the actual amount of genetic instability, instead + of hiding it if a slime doesn't mutate further, or tweaking it if it might mutate + back into itself. This will make it easier to parse which slime to breed further + to get a rainbow slime. + Rhials: + - code_imp: Bitrunning/Bitrunning Den areas are now cargo area subtypes, rather + than station area subtypes. + - qol: The area preceding Metastation Cargo's front door is now denoted as being + the "cargo lobby". + SuperNovaa41: + - bugfix: Fixes cyborged heretics seeing influences. + Time-Green: + - bugfix: Fixes nebula killing everyone when forced by an admin on icebox + Y0SH1M4S73R: + - bugfix: Rebar crossbow bolts are now reuseable again, without risking crashing + clients when fired at point-blank range. + - qol: Bitrunning glitches will not show up in the roundend report unless they escape + the virtual domain. + vinylspiders: + - bugfix: fixes a runtime in footstep code that would prevent the fov effect from + playing to nearby mobs +2023-11-27: + DaCoolBoss: + - qol: Oculine now tastes bitter, and not like toxin. + LemonInTheDark: + - server: Minimum compile version has been bumped to 515. clients still support + 514 but we're gonna start using 515 restricted features for serverside now. + Profakos: + - bugfix: You can once again repaint robotic limbs to use alternate skins + SyncIt21: + - code_imp: removes unused proc `generate_possible_reactions()` from reagent holder + san7890: + - bugfix: Poly should now remember phrases between shifts. + wesoda25: + - bugfix: the dismemberment moodlet will now properly clear for ethereals who regrew + a limb in their resurrection crystals +2023-11-28: + FeudeyTF: + - code_imp: Removed currency value for free products + Kylerace: + - code_imp: verb callbacks will no longer execute if the original client disconnected + Rhials: + - qol: Bar signs can now be purchased from cargo. Neat! + - qol: Bar signs can now be deconstructed with a screwdriver and wrench. + - qol: Bar signs can now be smashed to pieces, rather than just disabled. + Y0SH1M4S73R: + - bugfix: People exposed to romerol while alive will once again revive as zombies + on death. + san7890: + - bugfix: Ranged Guardians (Holoparasites/Power Miners/etc.) can no longer use ranged + attacks in scouting (incorporeal) mode. + vinylspiders: + - bugfix: fixes an overlay lighting hard del + - bugfix: flashlights placed inside of backpacks and other storage items that were + on the floor will no longer allow light to shine through + - bugfix: fixed a null client runtime in advanced camera console + - bugfix: fixed a runtime in handle_dead_metabolism() +2023-11-29: + Ghommie: + - rscadd: Standing on structures such as crates, tables and bed will now look like + it. + Higgin: + - code_imp: Made ARDS death check respect maxHealth. + Iajret: + - bugfix: You can now destructively analyze syndicate and abductor items. It works + this time, I promise! + LT3: + - bugfix: Getting beheaded by the tram increments the scoreboard + - bugfix: 'Disease outbreak: classic spawned from the admin secrets panel no longer + fails to start' + - bugfix: Disease outbreak provides a message about why it fails to start + MGO: + - rscadd: Introduces new inverse reagents for Salicylic Acid, Oxandrolone, Salbutamol, + Pentetic Acid, Atropine, Ammoniated Mercury and Rezadone! + MTandi: + - bugfix: fixed chemical closet door not rendering when open + Momo8289: + - bugfix: The quick carry module should now correctly apply the appropriate traits + Mothblocks: + - image: Premade cleanbots are now always blue. + Rhials: + - bugfix: Renames a bar door from "Kitchen" to "Bar" on Metastation. + SyncIt21: + - bugfix: you no longer get an empty crate when ordering bluespace crystals from + the galactic material market. + Thlumyn: + - bugfix: bitrunning den shows up on the camera console now. + WarlockD: + - bugfix: All drones now can craft again + - rscadd: Non-Shy drones can now strip people of their things + - bugfix: Centered the "pull" button properly over the drop button + itseasytosee: + - bugfix: staggered targets now have the correct chance for escaping grapples. + - spellcheck: changed attack verb for punching a grappled target + san7890: + - bugfix: During the "Cursed Items" wizard event, you should only have smoke spawn + on you if you actually had a cursed item equipped to you. + vinylspiders: + - bugfix: fixed remaining footstep runtimes +2023-11-30: + Ben10Omintrix: + - refactor: cats are now basic pets. please report any bugs. + - rscadd: the cake cat and bread cat can now help the chef around in the kitchen + GoldenAlpharex: + - bugfix: The Radioactive Nebula station trait will now respect its upper intensity + cap set at one hour and forty minutes, no longer scaling past that, as was initially + intended. + Mothblocks: + - rscadd: Instead of teaming up random people together, blood brothers will now + start out with one player and let them convert a single other person over to + blood brother using a flash. + SuperNovaa41: + - bugfix: Cyber cops are now equipped with the correct outfit. + timothymtorres: + - bugfix: Fix bitrunning triggering claustrophobia diff --git a/html/changelogs/archive/2023-12.yml b/html/changelogs/archive/2023-12.yml new file mode 100644 index 0000000000000..6d8dfc6f781fb --- /dev/null +++ b/html/changelogs/archive/2023-12.yml @@ -0,0 +1,971 @@ +2023-12-01: + Fikou: + - rscadd: turns triple ai mode into a station trait + Jacquerel: + - rscadd: Agent IDs once more trick Beepsky into treating you more leniently. + - rscadd: Prisoner IDs make Beepsky treat you somewhat more suspiciously, as do + Syndicate IDs. Wearing a Centcomm ID means that Beepsky is aware that you are + above the law. + - rscadd: Player-controlled security bots can view someone's assessed threat level + by examining them. + MidoriWroth: + - bugfix: Icebox chemistry lab shutters are controlled by the button in the chemistry + lab and not in the pharmacy again. + Pickle-Coding: + - code_imp: The singularity processing is a bit more important than the other subsystems. + TJatPBnJ: + - bugfix: Changelings will no longer get an objective to impersonate crew without + absorbable DNA. + - bugfix: Changelings will no longer start without an escape objective. + Thlumyn: + - rscadd: Slimepeople can now get wings from flight potions. + Vermidia: + - spellcheck: fixed typo in one of spacer's moodlets + - rscadd: Heterochromatic, Signer, Spacer, and Voracious quirks are now properly + accounted for in medical records. + jlsnow301: + - rscadd: Added a new modular bitrunning domain - Starfront Saloon. + - balance: Psyker shuffle domain was made slightly easier and has been given more + rewards. + mc-oofert: + - bugfix: shield wall gens actually use power now + - qol: shield wall gens may now be rebuilt and use some balloon alerts, and have + wiring + san7890: + - balance: The Meat Hook will now "ground" you whenever you fire it out at someone. + You get a very small immobilization every time you fire, which "upgrades" to + a full immobilization whenever you actually hit an atom and start to drag it + in. + - bugfix: A chain should now show up as you drag in something with the meat hooks. + - bugfix: Meat hooks should no longer play the "magical gun" suicide if you were + to use it, but instead do their own unique thing. +2023-12-02: + 00-Steven: + - bugfix: Sign Language action properly toggles between an active/inactive background + again. + Autisem: + - admin: A new debug verb to turn yourself into an MMI(almost the funniest thing) + - bugfix: MMI's inside mechs can now properly open doors like there posibrain counterparts + Ben10Omintrix: + - bugfix: fix parrots not appearing dead sometimes + Fikou: + - rscadd: Instead of punished sect healing people like the normal bibble- you take + their burdens on instead! + Ghommie: + - bugfix: Fixed an issue with the offsets of ridden vehicles on tables, and another + when buckled to a bed. + IsaacExists: + - sound: Added human laughter to felinids + Vekter: + - rscadd: Added a new hostile variant of cats, "feral cats". + - rscadd: Added a new traitor item, "feral cat grenades". For 5 TC, you too can + throw a grenade at someone and make five cats maul them to death. + - rscdel: Replaced the "monkey cube" in Birdshot's tool storage with a different + "monkey cube". + - rscadd: Added a fun surprise item to Birdshot's tool storage to compensate for + the above change. + jlsnow301: + - bugfix: Mod links are now disabled in the virtual realm. + san7890: + - admin: The Player Panel should now contain the unique mob tag associated to a + certain mob that a player might inhabit at one time, which is stored on their + player details datum on their client (which is guaranteed to always exist). + - admin: The "Old Names" details of a player is now visible in their own personal + per-player player panel. +2023-12-03: + Ben10Omintrix: + - refactor: medbots are now basic bots. please report any bugs + - rscadd: medbots can wear hats! + Fikou: + - qol: psyker echolocation cooldown time has been reduced from 2 to 1.8 seconds + - bugfix: psyker heads no longer render an overlay of not having eyes + Jackraxxus: + - bugfix: Digi legs work with the QM's jumpskirt + MrMelbert: + - bugfix: smartfridges no longer show false overlays + Rhials: + - spellcheck: Fixes a typo in the Factory Quartermaster outfit name. + SyncIt21: + - bugfix: crafting food or any other items that require reagents will not leave + behind blank reagents. That and properly updates the holder those reagents are + stored in + Vekter: + - bugfix: Fixed damage ranging on feral cats + - bugfix: Fixes hole in Birdshot hallway + distributivgesetz: + - spellcheck: Occurrences of "recieve" has been changed to "receive". + san7890: + - bugfix: Some mapped-in gifts that were supposed to guarantee a certain gift weren't + spawning that exact gift type, this has been patched to reflect the mapper's + intent. + tralezab: + - balance: Honorbound no longer cares about innocence when it comes to lesser creatures. + They can still be considered unready in some cases. + - balance: Attacking a cultist with a halo or a nuclear operative first instantly + makes THEM guilty, allowing further attacks. + - balance: More favor for converting someone to the honorbound rules +2023-12-04: + Ghommie: + - rscadd: Added the daily (roundstart) message server key to the Chief Engineer's + memories. + - bugfix: Fixed some oopsie whoopsie with elevation, trams and beds causing people + to visually ascend or descend to heaven or hell. + Hatterhat: + - bugfix: SecTech restocking units are now actually named SecTech restocking units, + and not Generic restocking units. + KittyNoodle: + - bugfix: Heretics can no longer cast all of their spells while in jaunt + Melbert: + - rscadd: Falling down a z-level while standing now damages your legs rather than + your entire body. This also means falling down multiple z-s may rarely break + your legs. + - rscadd: Felinids now land on their feet if they fall down a z-level while standing. + This replaces the knockdown with a short slowdown, and also has the trade-off + of causing more leg damage. + - rscadd: Cats can now fall z-levels without sustaining damage. + Profakos: + - rscdel: Removes the slime's reagent holder. This will make them not slow down + from somehow imbibing morphine or frostoil. + Rhials: + - qol: Bar signs ordered from cargo will no longer be access-restricted. + SyncIt21: + - bugfix: soups and other reactions where large quantities of reagents are required/present + should not have a random chance of reacting forever. + - bugfix: reactions which are overheated should diminish all the way to 0 + - code_imp: cleaned up code in `datam/equilibrium` in general. Slightly optimized + soup reaction code + Timberpoes: + - admin: Removed the "Turn Target into MMI" right click context menu verb entirely, + and instead added the same command as a VV dropdown on human mobs. + distributivgesetz: + - balance: Clone damage dealt by the cosmic blade has been replaced with organ damage + and increased burn damage. Clone damage dealt by the cosmic beam has been removed. + The star gazer now deals burn damage instead of clone damage. + - bugfix: The health of mobs combo'd by a cosmic blade will now update correctly. + necromanceranne: + - bugfix: Blocking a tackler no longer causes things to go haywire and stun the + tackler/the tackle victim. + tralezab: + - rscadd: 'Festival Sect has 3 new rites: Cogitandi Fidis, Portable Song Tuning, + and Illuminating Solo.' + - balance: lowers threshold for triggering a final effect. Consult your Cogitandi + Fidis for more information + xXPawnStarrXx: + - rscadd: Added new roast dinners, able to be cut into smaller portions hopefully + in time for the Christmas season. + - rscadd: Added sprites for bone spears to match the other bone items. +2023-12-05: + 13spacemen: + - balance: Deleting and reprogramming pipes/devices with RPD is now INSTANT! + Autisem: + - bugfix: Rebirthing from headslug properly reapplys void adaptation + distributivgesetz: + - rscdel: Removed clone damage. + - rscdel: Removed the decloner gun. + - rscdel: "Removed clonexadone.\n/\U0001F191\n\n" + san7890: + - bugfix: 'Janitors rejoice (or lament): Floors should now be dirty shift-start. + + / \:cl:' +2023-12-06: + Ghommie: + - bugfix: Fixed the AI painting manager showing invalid choices. + - bugfix: "Fixed the painting manager search function.\n/\U0001F191" + Mothblocks: + - qol: Converting someone will now give a chat message. + - bugfix: Blood brothers can no longer get other antagonists, I hope. + dieamond13: + - rscadd: adds propeller hats, rainbow bowties, and swirl lollipops + norsvenska: + - rscadd: The NTSS Independence (Cruise Evac Shuttle) has received a makeover! Engineering + crews are still working on it, but it is operable and available for those who + have the money to rent it. +2023-12-07: + Chimpston, atlasle and aBlimpfox: + - image: "added Cult Cove, Neon Flamingo and Slowdive bar signs\n/\U0001F191" + Higgin: + - rscdel: Stagger animation no longer fires longer than it should on dead bodies + or on dead bodies period. + Tattle: + - balance: you can no longer teleport into chasms + Vermidia: + - bugfix: Medibots made from advanced medkits works again + - bugfix: Medibots made from brute medkits have their bonus healing again + - bugfix: Medibots can use robotic emotes again + san7890: + - admin: Spawning in Nar'Sie will no longer automatically trigger the round-ender, + you need to specifically start this chain of events through the new VV Dropdown + Option "Begin Nar'Sie Roundender". +2023-12-08: + Autisem: + - bugfix: Being revived inside a legion now sets you free + - bugfix: "in some rare cases gib() did infact not spawn gib\n/\U0001F191" + Ben10Omintrix: + - bugfix: medbots now drop hats when tipped and drop their items when they explode + GoldenAlpharex: + - bugfix: The typing indicator has overcome its shyness and is now back to its usual + form. + Higgin: + - balance: most diseases can now be slowed, mitigated, and eventually cured through + being well-fed, resting, and using spaceacillin. Curing diseases through this + way will give you immunity if you experience them at their peak/maximum and + aren't starving/malnourished when they cure. + - balance: disease symptoms can be forestalled for up to 100 cycles with a declining + chance of avoiding them over time using rest or spaceacillin. + - balance: This does not apply to things like fungal TB; it does apply to healing + viruses if you don't take care of yourself by staying fed and avoiding spaceacillin. + - balance: disease can be cured through direct injection or ingestion of cured blood. + However, curing disease in this way does not provide lasting immunity. You need + to naturally beat the virus or get a vaccine for that. + - balance: Wearing internals or using protective equipment while infected can limit + the spread of respiratory illnesses from yourself to others. Contact transmission + is still possible however. + - balance: Medical Doctors now have roundstart virology access. Paramedics and coroners + now get virology access on skeleton shift access. Virologists now have roundstart + pharmacy access. + - balance: Sentient Diseases now resist being overridden by other advanced diseases + and can always override other advanced diseases; they also have an extra bonus + on their stealth stat to help make up for early outing without a bit more testing. + - balance: biohazard lockers now also contain a syringe of spaceacillin (in line + with the orderable kit from cargo.) + - balance: Virus severity is now also a function of the number of symptoms out of + max your virus has. Experiment with different combinations using less than six + symptoms to make viruses that are deceptively less-obvious and less quick to + self-cure at the tradeoff of stats. + LemonInTheDark: + - refactor: (Almost) all smoothed icons can now be edited in their pre cut forms + Melbert: + - qol: Replaces unused xeno weed extract item in abandoned crates with a random + assortment of cannabis. + OrionTheFox: + - image: resprited CTF ID Cards + ZeWaka: + - rscadd: TGUI Stack elements can now be zebra-styled (alternating colors for contrast) + timothymtorres: + - bugfix: Fix cult halo and eyes affecting deconverted cultists + vinylspiders: + - bugfix: fixed mutations holding onto refs after removal + - bugfix: fixes timed dna injectors +2023-12-09: + ATHATH: + - bugfix: Spacemen can no longer use curses to cheat at Russian roulette by selectively + blocking attempts to shoot themselves. + - rscadd: A Russian revolver has been added to the contraband section of each Good + Clean Fun vendor. + Ben10Omintrix: + - refactor: cleanbots are refactored into basic bots. please report all bugs + - bugfix: fixes cleanbots getting stuck sometimes while patrolling + - rscadd: janitors get a new skillchip which allow them to communicate with cleanbots + DaCoolBoss: + - bugfix: Minor bug fixes and improvements to the garbage truck space ruins. + - image: Claw hammer icon has been neatened up a little. + Fikou: + - bugfix: fixes punished sect giving you burden for stuff like changing species + Ghommie: + - bugfix: False anomaly alarms now work. + IsaacExists: + - rscadd: Added new modular Assault-Type domain "Abductor Ship" + - rscadd: Added new simple mob abductor agents team + KittyNoodle: + - balance: heretic robes now have wound armor + - balance: heretic blades now have knife-level wound bonuses + SyncIt21: + - bugfix: Cryostylane and oxygen reaction now cools down temps. Other reactions + with the `REACTION_HEAT_ARBITARY` flag also cools down temps correctly + - bugfix: coffin cookies are no longer invisible during the holiday seasons + - bugfix: Eggs don't leave behind their shells when cracked into a soup pot. Cups + end their attack chain early when dealing with specific items + - bugfix: canisters don't disappear when their colours are changed + - code_imp: changed some vars into defines to save memory, removed unused/useless + vars & added auto docs + - code_imp: converted UI to typescript. moved global canister list to its appropriate + folder + - refactor: removed prototype canisters and optimized canisters as a whole. + TJatPBnJ: + - bugfix: Bolt action rifles no longer open their bolt when firing their last bullet. + itseasytosee: + - rscadd: Sleeping carp/cqc users can now snap peoples necks by punching them in + the head while they are in a kill grab. + jlsnow301: + - refactor: 'TGUI V5: The UI has had its entire engine replaced with React v18.2. + This might cause obvious or laughably broken UIs in places you wouldn''t expect. + Please report any issues you find to the repo!' + vinylspiders: + - bugfix: false walls icons will now display again + whataboutism-alos: + - rscadd: Sprited and implemented a short lizard tail +2023-12-10: + Higgin: + - bugfix: Healing viruses now no longer self-cure for reasons they're not supposed + to and do for those that they are. + JohnFulpWillard: + - rscadd: PDAs with a dead power cell are now limited to using their Messenger app. + - bugfix: Microwaves now stop charging PDAs if the cell was removed mid-charge. + - bugfix: Microwaves can now charge laptops. + - bugfix: PDA Flashlights can't be turned on while the PDA is dead. + - bugfix: You can now hold a laptop up to a camera (if it has a notekeeper app installed) + like PDAs already could. + Melbert: + - refactor: Refactored some methods of items interacting with other objects or mobs, + such as surgery and health analzyers. Report if anything seems wrong + Rhials, MrMelbert: + - rscadd: The Tracker implant has had its teleport beacon functionality migrated + to the new (cargo accessible) Beacon implant. + - rscadd: Teleport Blocker security implant, that prevents the implantee from teleporting + by any means. Purchasable from cargo. + - rscadd: Security implants may now be harmlessly self-destructed at the Prisoner + Management Console. + - balance: The Tracker implant tracking radius has increased from 20 to 35 tiles. + The Prisoner Management Console will track and display the area the implantee + is in as well. + - balance: The exile implant now prevents implantees from operating shuttle controls. + - code_imp: Various code improvements and removal of unused vars in the Prisoner + Management Console + - code_imp: The HUD slots for chem/tracking implants have been converted to display + any implant with the IMPLANT_TYPE_SECURITY flag and an associated sprite. + - spellcheck: Modifies various implant pad readouts, removing false information + and rewriting some sections. + SyncIt21: + - bugfix: Pyrosium oxygen reaction now heats the holder and causes reactions inside + it. Also correctly sets the holder temperature to 20 kelvin & causes reactions + when first made + WarlockD: + - bugfix: 'The air_alarm_circuit to gets the environment from the proper turf. + + :cl:' + san7890: + - admin: Object Possession has been reworked, please report any potential bugs. + - qol: Object Possession should now throw a screen alert for you to unpossess the + object instead of you having to search the stat-panel for the "release obj" + verb. You can still use the verb but it's a lot nicer now. Aghosting will also + work now. + timothymtorres: + - qol: Add RMB hotkey and screentip UI to tracking beacons to toggle them on/off. +2023-12-11: + MTandi: + - image: New cleanbot sprites + - image: New bucket sprites + - rscdel: All buckets and cleanbots are back to being blue + xXPawnStarrXx: + - bugfix: fixed big rice pans getting worse with cooking. +2023-12-12: + Ghommie: + - bugfix: Goldgrubs no longer block death bolts, even while alive. + Melbert: + - bugfix: When using an item on a mob, you will always attempt to fix wounds AFTER + surgery, BUT BEFORE the item's own interactions. As an example, this means using + a mesh on a mob will attempt to progress surgery first, then attempt to fix + any burn wounds, then heal burn damage. + OrionTheFox: + - image: The Head of Security's "Sturdy Shako" hat now uses the same gold color + as his other hat + Rhials: + - qol: Blackout drunkard personalities will now recieve a "you are about to sober + up" warning at the 60/40/20 second mark, instead of repeatedly at the 50 second + mark. + - bugfix: Sunset Saloon virtual domain should no longer sometimes spawn with holes + in the floor. + SyncIt21: + - bugfix: no runtime when unwrenching Atmos components + - spellcheck: fixed typo in attack chain define comment + jlsnow301: + - bugfix: Painting should be working again. + - bugfix: 'DNA Sequencer UI: You should be able to cycle in reverse with RMB again.' + - bugfix: Dropdowns should show what you've selected again. + vinylspiders: + - bugfix: fixes some AI runtimes that were caused by the pawn becoming null + vorpal-void: + - bugfix: fixed outdated item description +2023-12-13: + Ben10Omintrix: + - bugfix: AIs can summon bots again + - bugfix: cleanbots can clean floors directly underneath them and prioritize floors + nearest to them + - bugfix: medbots drop empty medkits now + Buyrcsp2: + - bugfix: Bird Phobia now triggers on geese + Ghommie: + - bugfix: climbing or being shoved into a glass table won't cause elevation issues. + Jacquerel: + - rscadd: Added subversive pins to the black market uplink which make security hate + you + - rscadd: The detective's spy cam can now be conveniently pinned onto people in + the same manner as medals + Jacquerel and Fikou: + - qol: If the station rolls the "Cargo Gorilla" station trait. you will be able + to sign up for the role from the game lobby. + - qol: If nobody signs up to be the Cargo Gorilla then you can select it from the + Late Join menu and arrive on the arrival shuttle. + - bugfix: The Cargo Gorilla will actually spawn. + LT3, Majkl-J: + - image: SM now has holiday lights + - image: You can now put a santa hat on the SM + Rex9001: + - admin: Updates the admin smite "Ocky icky phobia" with some new words + Ryll/Shaps: + - bugfix: You can no longer stack more than one applications of a burn wound at + once + Time-Green: + - bugfix: Highlander wont gib every person anymore + Watermelon914: + - balance: Organs can now be preserved by putting them in freezing temperatures. + - balance: Morgue trays and freezers will now cool down the contents placed inside + of them. + - balance: Morgue trays will now properly display if someone stored within them + is revivable and make periodic beeps every minute. + jlsnow301: + - bugfix: You should be able to edit your character using the feature buttons again. + - bugfix: You should be able to move stamps on paper again. +2023-12-14: + Iamgoofball: + - balance: Light Footed now makes stepping on glass Knockdown instead of Paralyze + - code_imp: Fixes some single letter variable usage in caltrop.dm + LemonInTheDark, Donglesplonge: + - image: Redoes fov "mask" sprites. They're clean, have a very pleasant dithering + effect, and look real fuckin good! + - rscdel: Changed FOV, it no longer hides mobs, instead it blurs the hidden area, + and makes it a bit darker/oversaturated + Profakos: + - bugfix: Crabs will properly only target Tiny creatures + Y0SH1M4S73R: + - qol: The asteroid on Tramstation can now have areas expanded into or created within. + jlsnow301: + - bugfix: Cult chat, PDAs, succumbing, etc shouldn't blue screen anymore. + san7890: + - server: The TGS -> Discord Relay Warning that detects if a cliented mob has a + null loc should now be plaintext instead of being fully screwed up with useless + codestuffs. + - qol: For admins, in the "Spawn Atom" window where you pick atoms to spawn-by-type, + anything that is a subtype of `/mob/living/basic` should now be replaced with + the `BASIC` tag instead (like with carbons, reagent containers, turfs, etc.). + So, instead of having to scroll through and try and figure out what that weird + subtype of Poly is, it should now be easier to read in that smaller screen. +2023-12-15: + ATHATH: + - rscadd: If your bitrunning avatar somehow acquires and consumes a red pill, they + will be disconnected from the Matrix. + Higgin: + - bugfix: Chemicals are now no longer less effective at curing advanced viruses + than they used to be. + IndieanaJones: + - bugfix: The clown car once again obtains the accesses of kidnapped crew within + the car. Supermatter crystals beware! + Profakos: + - bugfix: Away lathes can now print robot navigational beacons + Singul0: + - bugfix: Advanced Plastic Surgery is now unavailable shiftstart + SyncIt21: + - bugfix: mobs that have the `TRAIT_PERMANENTLY_ONFIRE` trait cannot be extinguished + by anything. + - bugfix: stuff thrown into lava should not runtime. it currently does not but if + there is a slight chance it does not happen + - bugfix: items that are rejected by the mat container will display the chat message + saying that. + - bugfix: ORM will generate points regardless of how the ore enters it. + - bugfix: Machines like autolathe, techfab etc can now be hit with iron sheets (or + any other material item type those machines accept) when in combat mode rather + than inserting them because it makes sense. + - bugfix: Mat container won't display chats fully if the `MATCONTAINER_SILENT` flag + is passed. + - refactor: Machines like autolathe, techfab etc now display summed up material + inserts to chats rather than each item individually. Also, will skip items & + its contents if it cannot be processed thus saving time + Time-Green: + - bugfix: fixes zombie tumor not reviving + jlsnow301: + - bugfix: Admin interview panel should feel snappier. + - bugfix: Newscaster shouldn't bluescreen anymore. + - bugfix: Tram controls should work again. + timothymtorres: + - balance: Fitness level decreases the time it takes to firemany carry someone. + Fitness level determines how much of a positive mood the workout grants. Working + out is now more difficult and requires more nutrition. + - balance: Exercise no longer triggers double metabolism. + vinylspiders: + - bugfix: fixes a bug which was causing certain mutations to only get partially + removed + vvvv-vvvv: + - bugfix: Fix double-clicking in various UIs + zxaber: + - balance: Emagged and Hacked APCs now occasionally flicker a blue effect, and the + effect is not visible with cameras. + - rscadd: Adds a new discount experiment for unlocking the combat exosuit nodes + - complete it by scanning two exosuits with equipment in the left and right + hand slots. This replaces the prior discount experiment about destroying exosuits. +2023-12-16: + Derpguy3: + - spellcheck: Fixed a minor grammar mistake in the RD's job description and the + encrypted cache crate. + Ghommie: + - balance: '"Scarves" and "Wallets!" are now neutral traits.' + LT3: + - bugfix: Internal action buttons on computer consoles should work again + TheWolfbringer: + - bugfix: 'Bitrunners can now access the Northstar ORM + + :cl:' + distributivgesetz: + - balance: Delamination variants no longer change once the explosion point has been + reached. + jlsnow301: + - bugfix: Keybindings in prefs are able to be set again. + - bugfix: Fixed the weird spacing on buttons. + - bugfix: Confirmation buttons should be usable again. Bitrunning domains, command + reports, etc. + tralezab: + - qol: Anesthetics tank description now mentions a rare quirk of anesthetic. +2023-12-17: + DATA-xPUNGED: + - balance: Exosuit Materials 1 now only requires one mech. + - qol: "Exosuit Materials 1 is much more lenient on the percentage it requires.\n\ + /\U0001F191" + Diamond-74: + - bugfix: character setup screen no longer runtimes with all_nighter quirk + Exester509: + - bugfix: ED-209s can no longer be crafted with most instances of helmet, you need + security ones just like Beepsky. + FlufflesTheDog: + - bugfix: Fixed a rare false positive with morgue tray alarms. + Ical92: + - bugfix: coffee cartridge racks start with a coffee cartridge in them + IndieanaJones: + - bugfix: Fixed runtime regarding thanking non-existent clown car drivers + Jacquerel: + - balance: Last Resort can now be used while unconscious or dead. + - balance: Last Resort stuns bystanders for slightly longer. + - rscadd: If you throw a bee at someone it will hit them sting-first and inject + that bee's reagent + - balance: Thick clothing can now protect you from the venom of bees, snakes, frogs, + and (small) spiders + - rscadd: Adds a new shuttle event, where space shuttles can experience minor turbulance. + Keep your belt on while the appropriate cabin light is lit. + lizardqueenlexi: + - bugfix: Motorized wheelchairs will no longer spawn in a bugged state where they + have no parts and can't be upgraded. + - bugfix: Motorized wheelchairs will drop their power cell when destroyed or deconstructed. + - qol: Power cells are now inserted into motorized wheelchairs as part of the crafting + recipe, instead of as an extra step afterwards. + tralezab: + - qol: Added a confirmation prompt to breaking your vow of silence. + - balance: Mimes can no longer repair the vow of silence once broken. + - balance: The negative moodlet from breaking your vow of silence is now lighter, + and no longer permanent. + - balance: Mimes are back to being able to write while under a vow of silence. + xXPawnStarrXx: + - bugfix: fixed the oversight with martian food complexity. +2023-12-18: + DarkHunter7756: + - bugfix: Aivime, Lentslurri, Eigenswap and Libitoil can now be cooked again. + - balance: Aivime now can stack blur effects for the soapiest game ever. + - spellcheck: Lenturri description become more actual + Diamond-74: + - bugfix: Reviver no longer attempts to revive impossible to defib mobs. + - refactor: Cleaned up unnecessary variables and re-arraigned code to have it perform + altogether in one tick. Additionally added a proper cooldown to revivers. + DrDiasyl: + - image: 'New Security clothing: Security High-Vis jacket!' + Fikou: + - rscadd: Bridge Assistant job accessible from a station trait. + Ical92: + - bugfix: fixed ghost role descriptions on NTTS Independence + Jacquerel: + - balance: Roundstart AIs are now made of positronic cubes, rather than brains inside + MMIs + LemonInTheDark: + - bugfix: Fire alarms no longer cause pitch blackness, instead creating a dark but + not black red light. + Melbert: + - bugfix: Fixes PAI health scan software + - bugfix: Fixes Laughter demons deleting the bodies of their friends. + Tattle: + - admin: obsession targets are logged + Time-Green: + - bugfix: Fixes losing HARS nullspacing your brain + - bugfix: Fixes AIize and borgize gibbing you + jlsnow301: + - bugfix: 'Interview UI should now be more obvious how it works: You must press + "enter" or save the answer.' + kawaiinick: + - code_imp: All nighters can now drink energy drinks to cope with their lack of + sleep. + uaioy: + - rscadd: Added 2 pocket quick equip keybinds + vvvv-vvvv: + - bugfix: Fix search categories in log viewer +2023-12-19: + Diamond-74: + - bugfix: Fixes runtime from augments not unregistering a signal. + - bugfix: Observers observing themselves no longer floods admins' logs. + Exester509: + - bugfix: Made Syndicate Stormtrooper and Nuclear Operative TGC card holograms visible + again + Fikou: + - bugfix: bridge assistant no longer passes some head of staff checks + - qol: if you steal a command member's liver, you can now sabre better! + - bugfix: qm's intern id is now "quartermaster-in-training" + - image: qm's id and hud icons now use the cargo tech icons (but blue) to be consistent + with the other heads of staff + Ghommie: + - rscadd: The Spectre-Meter modular computer app. A little, amatuerishly coded app + that, as the name implies, scan an area for spectral presence. It can be found + amongst other apps in maintenance computer disks. + - rscadd: An easier, lazier version of the Arcade app, also found in maintenance. + - rscadd: Black market computer disks, which contains programs not readily available + to the average assistant. + Mothblocks: + - bugfix: Fixed pressing Esc not unbinding keys in preferences. + OrionTheFox: + - image: the SpacePol bounty hunters have finally had a new run of Uniforms made, + bringing them back to the forefront of enforcement fashion! Wardens will find + their Police Jackets/Hats have been updated match SpacePol's new look as well. + - bugfix: fixed the Mafia Warden having a hat that didn't match his jacket, and + fixed SpacePol masks not covering the nose + Profakos: + - bugfix: The bitrunner domain completion screen alert is once again properly clickable + RedBaronFlyer: + - rscadd: Added orange hardhats, hazard vests, and pocket protectors to the cargo + drobe + Seven: + - bugfix: Ashwalkers can respawn fellow ashwalkers by bringing them back to their + tendril again. + - bugfix: Ashwalker tendrils no longer break hooded suits and modsuits. + - bugfix: Ashwalkers can sacrifice silicons, it wont give anything though. + SyncIt21: + - bugfix: crafting now transfers reagents from ingredients to final product making + previously inedible foods (toasted seeds, kasei dango & snow cones) edible. + Other crafted food products/items now differ in reagents based on the ingredients + required. + Timberpoes: + - admin: Delete painting button is once again visible. + Time-Green: + - qol: Assistants with <10h of playtime are now "Interns" + - bugfix: Monkeys will no longer eat your organs while they're still inside of you + ZephyrTFA: + - rscadd: Map Votes are now weighted random. + - rscadd: Custom Votes can now take advantage of Weighted Random winner selection + - rscdel: Removed Herobrine from the game + jlsnow301: + - bugfix: Emojipedia shouldn't bluescreen anymore. + vinylspiders: + - bugfix: monkeys will no longer cause other monkeys to get angry at the mobs they + just poofed by attacking +2023-12-20: + LT3: + - admin: Server wide admin announcements now use an alert box like other announcements + Majkl-J: + - balance: Disallows siphoning credits outside of station + MelokGleb: + - image: cyborg tools became animated + MrMelbert, Ghommie: + - bugfix: Alien nests, and some other stuff, can be physically attacked again. + - balance: x-mas trees (the ones with presents), are indestructibles. Truly protected + by a yuletide spirit. + Rhials: + - bugfix: Icebox escape pods will now land randomly on the surface, instead of only + in certain ruins. + jlsnow301: + - bugfix: Fixed a bluescreen while inputting a global variable in the circuit UI. + - bugfix: Emojipedia should copy the text on click properly, now +2023-12-21: + Rhials: + - bugfix: Disease Outbreak events will only select players on the station/lavaland + as patient zero. + ZephyrTFA: + - bugfix: Examine text on disconnected players is no longer accidentally subtle + vinylspiders: + - bugfix: fixes some dropdowns not displaying the right text after selecting something +2023-12-22: + LT3: + - qol: Roundstart intercept report and security level announcements are combined + into a single announcement + Melbert: + - bugfix: Crewmembers immune to viruses won't be picked by the fake virus event. + - qol: When you place a beaker in a chem-master, you no longer thwack it. + Profakos: + - qol: The bitrunning equipment vending machine now has a unique description for + each of the bitrunning disks + SSensum: + - rscadd: 'Added new quirk: Cyborg Lover!' + jlsnow301: + - bugfix: Name input in character setup should work properly now. + - bugfix: Many inputs should feel more responsive. + lizardqueenlexi: + - bugfix: Feature manipulation surgery will now properly update the patient's appearance. + - refactor: The tail portion of lizard spines will no longer draw on people who + do not have a tail. +2023-12-23: + 13spacemen: + - balance: Doing experiments AFTER their tech is researched now gives full 100% + points instead of 66%. + - refactor: Ghost roles now offer ghosts a clickable poll button. Ghosts can select + a role, deselect it, alt-click it for "Never For This Round", can cancel "Never", + can see the countdown, and can see how many other people are signed up for the + role poll. + Iamgoofball: + - bugfix: Fixes Holy Water giving you more blood and making you less drunk than + water normally does. + Jacquerel: + - rscadd: Nightmares retain their ability to dodge projectiles for a brief period + after leaving a dark area, meaning it is now possible to dodge lasers. + - qol: Nightmares and Shadow People now receive a screen alert when they are in + a sufficiently dark location that their abilities are active. + Melbert: + - bugfix: Gibbing a bot will no longer spawn bloody gibs. + Rhials: + - qol: Wizard apprentices now spawn on the same tile as the contract that summoned + them. + SyncIt21: + - bugfix: Reactions whose temps/ph values fall way below their optimal values no + longer restart & prevents infinite loops. Made reaction code slightly faster + xXPawnStarrXx: + - rscadd: Added new GaGs santahats. + zeroisthebiggay: + - bugfix: '''A whole bunch of spiders in a SWAT suit'' to ''A whole bunch of spiders + in a MODsuit''' +2023-12-24: + BurgerBB: + - balance: Miasma is now filtered through gas mask filters, at medium strength. + Deadgebert: + - qol: Head of Security beret added to their garmet bag + - qol: Head of Security bowman added to their locker + Diamond-74: + - bugfix: Injecting yourself twice with a luxury medipen in pressure doesn't runtime + and mechs with a cell with no max charge won't runtime. + - refactor: Gives hyposprays a var to check if they're fully used up rather than + setting total_volume to 0. + KannaLisvern: + - qol: Removed Bluespace Crystals and Diamonds from medical emergency bed's recipe. + Rex9001: + - balance: Ai's using the mech domination ability can now eject from the mech + Rhials: + - qol: Obsessed crewmembers are now displayed in the orbit panel. + - qol: The Paradox Clone orbit menu tab is now white. Neat! + SSensum: + - rscadd: Added weapon recharger boards to designs available to print on sec techfab. + Y0SH1M4S73R: + - qol: MOD wearers and internal AIs can pin the individual actions in a MOD circuit + module in a similar way to how they can pin modules. Circuit module actions + can be pinned from the configuration menu of the circuit + - refactor: The MOD action and BCI action components have been merged into one component + - the Equipment Action component. + jlsnow301: + - bugfix: Camera console search should update automatically + - bugfix: Autolathe search should now properly show designs + vinylspiders: + - bugfix: the boozeomat ui will stop duplicating space beer bottles + - refactor: refactored vending machine backend to have unique keys for their data + structures. should fix bugs related to items that happen to have the same name. +2023-12-25: + 00-Steven: + - bugfix: Camera circuits actually print pictures again! + - qol: Camera circuits can now take pictures while on a carbon, whether held, hung + around the neck, or sneakily hidden in either pocket. When held they try to + deposit their pictures into the holder's hands if possible. + - bugfix: Camera circuits should now actually use the camera's x axis picture size, + rather than using the camera's y axis picture size setting for both axis. + A.C.M.O.: + - bugfix: Fixed Personal AI cards, allowing them to wake up from sleep. + Iamgoofball: + - rscadd: Adds a mint condition clear PDA to the Goodies menu for 100,000 credits. + LT3: + - qol: TGUI will now wait longer trying to reconnect to a new round + Mothblocks: + - bugfix: Cut down a significant amount of time that caused the start of rounds + to lag. + - bugfix: Fixed a bug that would give you the chat message "/datum/element/damage_threshold + looks unharmed!" + SyncIt21: + - code_imp: removed excess calls to `update_total()` making reaction code slightly + faster. + - code_imp: removed excessive use of chemical constants (quantisation & rounding + constants) in places where they were not needed i.e. plumbing buffer, reaction + chamber, pyrotechnics & the liver. + Timberpoes: + - bugfix: Fixed an issue with polling ghost roles to control multiple mobs that + prevented Sentience Fun Balloons from working as intended. + Time-Green: + - bugfix: polymorphing into drone no longer gibs + Vermidia: + - rscadd: You can now look up and down through the IC menu + Watermelon914: + - balance: Cyborg lover has been replaced with Transhumanist. Transhumanists start + with a robotic limb and get mood buffs by being near to silicon-based lifeforms. + However, they get mood debuffs by being near organics, so there is a tradeoff + to taking this quirk. The cost for this quirk has been reduced from 2 to 0. + - bugfix: Fixed integrated circuit UI breaking. + jlsnow301: + - bugfix: AI voice changer UI should show defaults properly + - bugfix: NTOS Software Hub should focus on the input immediately now + - bugfix: Ore container UI should autofocus on the search bar now + - bugfix: Vend-a-tray registration text should display as intended +2023-12-26: + 13spacemen: + - rscadd: Canisters now have wires! You can pulse wires to do various canister functions + like opening/closing the valve. Make sure it has a cell though. + - rscadd: You can rig assembly combos (igniter-timer, prox-signaler, etc.) onto + canisters + - qol: Canisters now have screentips + - qol: You can now build multiple pipe layers with the RPD, with just 1 click! + Fikou: + - spellcheck: changes mutiline text to multiline text in vv + Ghommie: + - bugfix: Regal rats (and others), won't be punished by the automute system for + repeating the same command several times. + - rscadd: Added the 'Coupon Master' program for the PDA. Install it to receive periodical, + redeemable coupons for several cargo packs. Requires NTnet connection and the + messenger enabled to work. + - rscadd: Coupons are no longer only limited to goodies, but may also apply discount + to some other packs as well. + - bugfix: fixing damaged sandy dirt lacking an icon + Ical92: + - qol: Wendigo Cave ruin gets an aesthetic refresh + - bugfix: Wendigos (Wendigi?) no longer attack corpses + MelokGleb and KREKS: + - image: modified wizard weapon textures + ReinaCoder: + - rscadd: Adds a brown suit jacket to the detective's vendor + - image: 'Adds a new sprite for the a brown detective suit jacket + + :cl:' + SyncIt21: + - bugfix: debug chem heater now withdraws more than 100 units from its inserted + beaker. + - bugfix: chem heater has correct overlay when its panel is closed with a screwdriver + while a beaker is inside. + - qol: added more examines and tooltips for the chem heater. + - code_imp: converted chem heater UI to typescript. removed unused procs, vars, + ui tracking code. Added auto doc for everything. + - refactor: removed chem heater tutorial help button & its related reward, chem + heater code has been optimized as a whole. + - bugfix: plumbing rcd ui no longer has any graphical glitches + - bugfix: mat container displays correct number of sheets inserted for alloy materials. + - bugfix: remote materials now properly respect the `MATCONTAINER_NO_INSERT` flag. + - code_imp: removes material breakdown flags and related traits. + - code_imp: adds helper proc to insert materials via the remote material component + with proper context. + jlsnow301: + - bugfix: LUA editor should be usable again + vinylspiders: + - bugfix: fixes a potential mob hard del with cardboard boxes + - bugfix: fixes a hard del with thrown items +2023-12-27: + Melbert: + - bugfix: Fixed some occasions in which heartbeat SFX will continue on revival for + longer than expected + Mothblocks: + - bugfix: Fixed the surgery menu spamming chat messages when on the eyes section + of a player with no eyes. + Ryll/Shaps: + - balance: Yawns are less likely to propagate + jlsnow301: + - bugfix: NTOS Messenger should search as you type now + timothymtorres: + - qol: Add UI screentips to spraycans +2023-12-28: + Ben10Omintrix: + - rscadd: basic bots can now display their paths on huds + - bugfix: medbots can research healing again + Ghommie: + - rscadd: Added a "postal workers strike" negative station trait. In the case of + holidays and sunday though, it'll be a "postal system overtime" instead. + LemonInTheDark: + - rscdel: Removes the wires from canisters. It's a cool idea, but cheap controlled + maxcaps are bad actually + SyncIt21: + - bugfix: mercury & lithium will no longer make you randomly move outside of cryotubes + or in ground less turfs (space, water, lava etc) + necromanceranne: + - spellcheck: Fixes a typo in chat message when starting a Death Knell with the + Vorpal Scythe + - admin: Adds logging for the death knell, both when starts and when it is completed + successfully. +2023-12-29: + Diamond-74: + - bugfix: ai can now tell if it is in a do_after for resisting and will not interrupt + it. monkeys also now don't freeze up when aggressively grabbed and will resist + out of those and cuffs. + Ghommie, 13spacemen: + - bugfix: Signing or removing your candidature from ghost roles now properly updates + the screen button for it. + LemonInTheDark: + - bugfix: Dear mappers, the light debugger tool no longer deletes dragged wall lights + Mothblocks: + - bugfix: Fixed a lot of missing/broken images, such as emojis, language icons, + commendation hearts, icons in R&D menu, etc. + - refactor: Photo albums and photo frames are now more resilient to data loss, especially + when a server crashes. + - bugfix: Fixed "was shocked by /datum/component/energized" message. + SyncIt21: + - bugfix: reaction chamber open its UI when inserting/removing beakers from it + - bugfix: reaction chamber triggers new reactions & updates UI more often during + heating + - bugfix: instant and normal reactions now get triggered more often so for e.g. + more plastic sheets from polymer reactions or more reactions occur when there + are multiple reagents present + - bugfix: Off station ORM's can redeem points again. + jlsnow301: + - bugfix: NTOS Messenger should clear on enter now + timothymtorres: + - rscadd: Add 50% graffiti speed boost to tagger quirk + - bugfix: Fix time duration of large graffiti not applying properly + vinylspiders: + - bugfix: reduces lag in the tgui textarea input boxes + - spellcheck: changes some let's to lets +2023-12-30: + 13spacemen: + - bugfix: Poll alert buttons candidate number should be more responsive + Melbert: + - bugfix: Lizards now show a proper description in the magic mirror + ReturnToZender: + - bugfix: JSX files, when edited, cause TGUI to recompile on build + Tristrian: + - bugfix: Bot launchpads can deploy cleanbots and medibots again. + - spellcheck: Clarified the message when failling to recall a bot. + mc-oofert: + - rscadd: 69(roughly?) new vox phrases + mogeoko: + - bugfix: Atmospherics components will now move air into connected pipeline on deconstruction + if possible. Otherwise, air will be released to the outside from open nodes. + - bugfix: Unsafe pressure release on atmos components will now work the same way + it does in the normal pipes if there is an empty node with air. + - bugfix: The HFR user interface would close when the machine is shut down. + - bugfix: Atmospherics machinery will now share air from nodes after being rotated + and reconnected to pipenet. +2023-12-31: + 13spacemen: + - bugfix: Dimensional anomlies converting airlocks preserves the old name + Echomori: + - bugfix: trying to move up or down while controlling an advanced camera console + now properly moves the camera up or down, not your body + Ghommie: + - bugfix: The transhumanist quirk now should work as intended. + GoldenAlpharex: + - bugfix: Vending machines now display the proper color customization options and + item quantities again! + PapaMichael: + - sound: added *gaspshock emote sound effect to felinids + Rhials: + - code_imp: Unholy water no longer calls parent twice in on_mob_life(). + SyncIt21: + - bugfix: chem heater now shows the new value of its buffer dispense volume immediately + when it gets changed. + - bugfix: debug chem synthesizer now shows the new values of amount & purity immediately + when it gets changed. + Time-Green: + - bugfix: Fixes borg polymorph + - bugfix: Holodeck monkeys are properly cleaned up now + - bugfix: Holodeck monkeys have organs now + vinylspiders: + - qol: makes modal list uis autofocus + - bugfix: fishing up a spawner will now give you the spawned item instead of a broken, + undeletable spawner object + - code_imp: adds a warning to the stack trace when something tries to forceMove() + a qdeleted spawner + - bugfix: fixes textarea input boxes not updating on prop change (such as changing + tabs) diff --git a/html/changelogs/archive/2024-01.yml b/html/changelogs/archive/2024-01.yml new file mode 100644 index 0000000000000..6e71473c59858 --- /dev/null +++ b/html/changelogs/archive/2024-01.yml @@ -0,0 +1,790 @@ +2024-01-01: + 13spacemen: + - rscadd: Atmos Holofan projectors can be right-clicked inhand to make their holograms + more transparent + Ben10Omintrix: + - refactor: hygeinebots are now basic bots. please report all the bugs + - bugfix: fixes hygenebots not being able to patrol + - rscadd: hygeinebots can now be controlled by Players + Melbert: + - refactor: Refactored the area transformation colossus crystal effect to use the + same system dimensional anomalies use. That means the colossus crystal now has + access to some dimensional themes (bamboo, plasma, glass) and the dimensional + anomaly now has access to some colossus themes (jungle, alien). + Time-Green: + - rscadd: Adds a geared assistant station trait! Spawn with a skateboard, toolbelt + or in your favorite bee suit! + - code_imp: Moves assistant code around + jlsnow301: + - bugfix: Dropdowns and pop-up menus have been rewritten. This should fix an issue + where dropdown text was accidentally scrolling if hovered. Please report any + issues on the repo + kawoppi: + - qol: added screentips to the janicart (pimpin' ride) + uaioy: + - bugfix: The sustenance vendor in perma actually serves food now + - bugfix: Cyborgs do not deathgasp twice when dying anymore + vinylspiders: + - bugfix: fixed fire extinguisher cabinets not appearing opened after removing the + fire extinguisher from them +2024-01-02: + 00-Steven: + - bugfix: slamming through a glass table while previously on a table no longer gives + you a negative offset. + 13spacemen: + - qol: Gas analyzer can scan adjacent turfs. No more roasting yourself just to scan + your burn mix. + - qol: The "Explosive Planted" alert for C4 actually shows the C4 + Majkl-J: + - bugfix: Having an inorganic chest/legs no longer makes you drop your ID, belt + or pocketed stuff upon losing your jumpsuit + Mothblocks: + - bugfix: Fixed footstep sounds. + OrionTheFox: + - image: resprited the Pirate/Sailor costumes, the pirate jacket, and the pirate + spacesuit + Rex9001: + - rscadd: A new heretic path opens up! Gaze up at the great sky for the path of + the moon opens and the lie shall be slain in pursuit of ultimate truth! + - bugfix: Fixes the syndicate delusion not working + SyncIt21: + - bugfix: you can set the chemical reaction chamber temps to 0k again + - bugfix: used higher rounding value for reactions thus you get full volumes especially + for endothermic reactions(no more 99.99 but 100 units). + - bugfix: chem heater now applies heat per reaction step and sends updates to UI + more frequently + YakumoChen: + - qol: Toggling an armour booster module on a MODsuit now gives a balloon alert + making the tradeoffs more clear. + jlsnow301: + - bugfix: Protolathe input should feel more responsive. + xXPawnStarrXx: + - rscadd: Added new lizard variants of existing foods for equality of edibility. + - qol: made pickle jars reusable and vinegar craftable. +2024-01-03: + 0-ERRORNAME-0: + - image: Improved sprites for sailor school uniform + Ghommie: + - rscadd: Reworked Binoculars to function like a scope from long-range rifles. They + also can be used while moving, albeit that'll slow you down significantly. + - rscadd: Authentic mothic softcaps can be found in the "Mothic Pioneer" curator + kit, which googles can also used to look into the distance. The date on the + boxed kit has been updated to match "muh lore". + - qol: Using a scope (and now binocs or moth cap) won't hide your mouse pointer + away now, so that you won't slip out of the game screen and close the game tab + by accident. + Profakos: + - refactor: attack_slime is completely gone + - refactor: slime lifestate changes now have less boilerplate code + - balance: baby slimes do a bit of a less damage on the low end, but all slime damage + is much more consistent + - balance: instead of taking half brute damage, cyborgs are now immune to slime's + melee attacks. Charged slimes attacking cyborgs now lose a bit of charge. They + still get massive damage from charged slimes, and every slime attack flashes + them. + - bugfix: slimes can now properly drain basic mobs and simple animals that they + can damage + - bugfix: slimes can drift in zero gravity + SyncIt21: + - bugfix: turbine now shuts itself off when the room apc loses power or if it gets + damaged. Also uses a small amount of power for operation of internal electronics, + the green light & other stuff + - bugfix: No more runtime in turbine computer when parts are not fully connected + - qol: adds screentips & examines for turbine + - code_imp: removed unused vars, auto doc procs and cleans up some code in turbine + Tristrian: + - image: Throwing things has now a sprite animation forward your target. + - sound: Throwing things also has a sound accompagnying it. + - spellcheck: Dwarves don't throw things hard like hulks. Instead their throws are + described as "flimsily". + Vishenka0704: + - admin: The ability to export a part(or z-level) of the map has been added. + Watermelon914: + - bugfix: Fixed integrated circuit speech logging +2024-01-04: + Ghommie: + - bugfix: food made with drying racks now counts as "chef-made" (ergo, can provide + a mild buff). + vinylspiders: + - bugfix: fixes some runtimes in pathfinding code, as well as one in the give direct + control admin verb +2024-01-05: + Ben10Omintrix: + - bugfix: stationary medbots will no longer ignore u for a while if u move out of + their way while healing + Ghommie: + - refactor: Introduced a simple budget system to station traits, so that smaller + things only count as half a trait, for example. + - balance: Increased the odds and maximum number of station traits that can be rolled + each shift. + Hatterhat: + - bugfix: Universal scanners are no longer invisible when set to the export or sales + tagger modes. + JohnFulpWillard: + - spellcheck: Fixed punctuation in Luini Amaretto's description. + - bugfix: Guardian host's ability buttons now works while the host is sleeping/unconscious. + LT3: + - bugfix: Alternate carpet colors will no longer reskin to normal carpet + - bugfix: OOC announcements will now be in shown in OOC colors + Melbert: + - bugfix: Some things which affect everything in an area are less laggy, the "all + lights are broken" station trait especially + - bugfix: Fixed runtime from blobbernauts attacking non-living things + - bugfix: Fixed Fugu Gland applying to mobs incorrectly + Rex9001: + - qol: Path of moon and lock now actually fit in the heretic tree + - balance: Certain path of moon rituals that needed brains now use easier to obtain + organs + - bugfix: the moon heretic ascension now produces lunatics again + - admin: Amputation Shear amputation is now logged + Thlumyn: + - bugfix: closets now have a working welder deconstruct screentip + Thunder12345: + - bugfix: 'Birdshot: Released gulag prisoners can now get off the gulag shuttle.' + - bugfix: 'Birdshot: The gulag shuttle airlocks will now cycle like other airlocks.' + - bugfix: 'Icebox: Added a fire alarm to the upstairs fore primary hallway.' + - bugfix: Gibtonite ore on ice planet gulags no longer spawns as lavaland rock. + jlsnow301: + - bugfix: 'TGUI: Sections should be scrollable on mouse hover' + - bugfix: Fixed redundant "Integrity:" in air alarms + mc-oofert: + - bugfix: you no longer fall down cliffs if you dont have gravity + vinylspiders: + - bugfix: fixed an /image hard del in ghost code + - bugfix: fixed a runtime in datum/thrownthing + - code_imp: progressbars no longer qdel their /image +2024-01-06: + 13spacemen: + - qol: The hub entry shows the next map and shuttle time, and no longer shows the + alert + - qol: Ghosts can now view Wanted Status and Sec Records by examining people + Melbert: + - bugfix: Strong arm implant users can shove more correctly. + Sightld2: + - bugfix: Cyborgs no longer think they're hitting themselves when stunned with a + stun baton + SomeRandomOwl: + - bugfix: Build Mode Export's options menu now knows when you want to cancel and + not change the options + WarlockD: + - bugfix: Queen bee's made with cytology now work + intercepti0n: + - image: resprited onmob cone icon. + jlsnow301: + - bugfix: Having a netpod destroyed will no longer grant you permanent healing. + - bugfix: Search bars for smaller lists should return to their former responsiveness. + - bugfix: Randomization button in prefs should look normal again. + - bugfix: Quirk customization shouldn't close immediately. + mc-oofert: + - bugfix: conveyor belts no longer maintain movement if whatever is on them suddenly + leaves their z level + - bugfix: After a raid on the local drug mafia in the sector by TerraGov, the Russian + Mobsters no longer have access to easy drugs and as a result are no longer 24/7 + running faster than the average spaceman. + - bugfix: the eZ-13 MK2 heavy pulse rifle does damage again + - bugfix: you can use your hand to make minebots go into combat mode again + necromanceranne: + - spellcheck: Corrects every misspelled 'kenetic' in the codebase. + vinylspiders: + - bugfix: fixes an image hard del + - code_imp: adds context to image hard dels to make them easier to track + - bugfix: fixes an /image harddel in station blueprints + - code_imp: cleaned up some more /image qdels +2024-01-07: + 13spacemen: + - bugfix: Hub shuttle time works correctly + LT3: + - bugfix: Morgue trays and freezing temperatures will no longer husk bodies + - bugfix: Organs outside bodies will properly receive cold damage + Mothblocks: + - balance: Instead of too much damage to the head beheading someone, it will now + split their skull in half. While their skull is open, you can rip out their + eyes with your hands. and they will spill their brain out of their head if they + slip. + - balance: The Path of Blades ascension will accept either a beheaded person, or + someone with their skull split open. + - rscdel: Removed the beheading objectives from traitor. + exdal: + - bugfix: Papers no longer crash tgui + - bugfix: Biogenerator no longer crash tgui +2024-01-08: + 13spacemen: + - bugfix: Jumping to C4 via ghost notification works again + DrDiasyl: + - qol: It is now possible to craft more Security bio suits + - qol: Bio-emergency crate from Cargo now contains a box of latex gloves and sterile + masks + Ghommie: + - bugfix: fixed coupon codes with expiration times. + - bugfix: Fixed H.A.R.S. fucking up the brains a little. + Majkl-J: + - bugfix: spasm_animation now correctly resets the transform, as it should + - bugfix: Spamming the weld on a robotic part no longer drains the fuel + Thunder12345: + - bugfix: The clown planet biodome and virtual domain can now be completed without + slipping directly into the exit. + fIoppie: + - qol: Cargo techs can now access the windoor in the delivery office on Delta. + jlsnow301: + - bugfix: Comms console should now update as you type. + mc-oofert: + - bugfix: vents that are turned off dont replay their animation if you go out of + sight and back in + - bugfix: adds missing navigation landmarks to tramstation + scriptis: + - refactor: some very large tgui lists (air alarms, all recipes techfab view, techweb + view) are loaded on-demand as you scroll, making them not lag so hard +2024-01-09: + Melbert: + - bugfix: '"The sister and He Who Wept" Heretic painting will no longer cause big + lag' +2024-01-10: + 13spacemen: + - qol: If you can't heal a body part, you won't get a healing time delay. No more + spending 5 seconds healing a body part only to get a "can't heal that" message. + Absolucy: + - bugfix: Fixed the Codex Cicatrix always requiring a dead body, despite the description + saying leather or a hide would work too. + Chubbygummibear: + - bugfix: popup screen locs will work on clients >1614. Security cameras and Spyglass + will work + FlufflesTheDog: + - bugfix: Certain areas are now properly protected against grid check. Namely the + supermatter should consistently be protected. + Ghommie: + - bugfix: Fixed misfiring for firearms like tinkered detective revolvers. + IndieanaJones: + - bugfix: Dynamic midround rolls will properly consider roundstart players as part + of the living population again, allowing antagonists with a minimum population + value to spawn when they should be able to. + - bugfix: Players who observe before roundstart can be considered for midround ghost + roles again. + - bugfix: Some antagonists which had elements that scale with living station population + now function properly again. + JohnFulpWillard: + - bugfix: Refried beans and Spanish rice now lets you take the bowl back after eating + it. + - bugfix: Eldritch reagent (the one that heals heretics) now heal heretic monsters + rather than kill them. + - bugfix: The HoP's cartridge vending machine now sells regular PDAs instead of + base command ones. + LT3: + - bugfix: Unencoded server admin announcements will now actually broadcast + Melbert: + - admin: Several types are now more accessible via the "spawn" verb (and friends), + meaning you can type 'Spawn" Modsuit' or 'Spawn "Jumpsuit' in the command bar + and it'll just give you a list of modsuits or jumpsuits to pick from. + - bugfix: Lionhunter Rifle is now available at the same point as Mawed Crucible, + making it available to all paths again. + - bugfix: Lock knowledge now goes the correct order. Mark -> Ritual -> Unique item, + rather than backwards. + - balance: Blood cultists can now convert a Null Rod into a cult weapon on an offer + rune. The strength of the weapon it is converted into depends on how many cultists + the Chaplain crit / killed with it. At five or more, it will turn into a Bastard + Sword. Note, sacrificing someone holding a Null Rod will automatically convert + it after they are gibbed. + - balance: Heretics no longer produce a Bastard Sword upon cult conversion. They + are still immune to cult stun and cannot be converted by blood cultists. + - bugfix: Carps now migrate slightly better, probably. + - bugfix: And Poly now talks better, probably. + OrionTheFox: + - image: Resprited a majority of undershirts + SyncIt21: + - bugfix: ejecting cells from microwaves via ctrl click now requires player proximity. + - code_imp: smart fridge content overlay now uses `base_icon_state` instead of a + separate var + jlsnow301: + - bugfix: Medical records console should be useable again + - admin: Fixed the bluescreen in the centcom pod launcher. + zxaber: + - bugfix: Ripley MK-Is and Paddys now correctly make incoming projectiles hit the + pilot again. + - bugfix: The various borg apparatuses can no longer pick up other internal borg + tools. +2024-01-11: + Thunder12345: + - bugfix: Pipeguns no longer have floating bayonets + vinylspiders: + - bugfix: if your voice changes (e.g. through a voice changer or changeling mimicry) + your runechat will now appear as the mob you are speaking in the voice of + - bugfix: when doing emotes with your face obscured, your runechat color will now + appear as either that of Unknown or the mob you are wearing the id of (if you + are wearing a mask with someone else's id) +2024-01-12: + 13spacemen: + - bugfix: Healing simplemobs with sutures and other stacks works again + - spellcheck: Healing mobs with medical stacks like suture and mesh give better + user feedback messages + - image: New liquid plasma sprites from SS14! + ArcaneMusic: + - admin: Added a new admin verb that ends all active weather within the weather + subsystem. + IndieanaJones: + - balance: Nightmare's Light Eater can now stun targets under certain conditions. + LT3: + - bugfix: Icebox kitchen has its light switch again + - bugfix: Icebox medbay hallway now has a fire alarm + Melbert: + - bugfix: Fix Ringleader's Rise not causing as many hallucinations as expected + - bugfix: Fixes some cult spells being unable to heal constructs (blood rites particularly) + - bugfix: Chameleon Suits will now automatically set your helmet into a hood if + using a hooded suit again + - bugfix: Blood Drunk Miner (Hunter version) should dash a bit more. + - bugfix: Ghost alert for buying a badass balloon should have a proper icon + TheVekter: + - admin: Made logging for BSA targeting and firing easier to find for admins. + Watermelon914: + - bugfix: Fixed being able to download the contractor program on the syndie store. + vinylspiders: + - code_imp: removed a dupe trait entry in the traits_by_type list +2024-01-13: + ArcaneMusic: + - qol: ID cards now set their accounts with Alt-Right click. + - bugfix: ID cards now once again have contextual screen tips showing what buttons + do what actions. + LT3: + - bugfix: Tramstation east APC will no longer be destroyed when the tram crashes + - qol: Tramstation central power and disposals moved out of the wall + Majkl-J: + - code_imp: The check that prevents your stuff from dropping when you have robotic + parts is now more robust + Melbert: + - bugfix: Lunar Parade has the potential to break less. + Paxilmaniac: + - qol: The heat-proof catwalk made by heat-proof rods will go away when a tile is + placed on it, rather than sticking around and needing to be removed manually + - bugfix: The lighting will not irreparably break on tiles where plating was placed + on lava-proof catwalks + - code_imp: Some single letter variables and the structure of the code around placing + tiles on lava-proof catwalks has been improved + - bugfix: The deployable component will now actually stop rotating things when the + variable to not do that is set + - bugfix: The "required_container_accepts_subtypes" variable on chemical reactions + now actually works again + Rhials: + - bugfix: Specialty drinks crafted in the crafting menu will now create the intended + reagents, instead of containing the reagents used to craft it. + jlsnow301: + - bugfix: Dropdowns should be more performant +2024-01-14: + IndieanaJones: + - bugfix: Changeling eggs laid by headslugs work again + - bugfix: Changeling egg incubation times should feel much more consistent, hatching + after 4 minutes + LemonInTheDark: + - admin: Confirming that you have read an admin message now uh, works. it's been + 2 years bros + zxaber: + - bugfix: Borg tools that hold and use specific items now work correctly again. +2024-01-15: + Ghommie: + - bugfix: Animals enlargened by the fugu gland are now visually aligned with the + turf they're standing on. + IndieanaJones: + - qol: The combat mode toggle button is now present on the HUD for simple and basic + mobs. + - bugfix: Rat King's abilities no longer require the user to click twice in order + to activate them. + LT3: + - bugfix: Fixed missing power cable outside Tramstation medbay + Melbert: + - bugfix: Anomalies spawned by grand ritual runes sound act more like anomalies + RikuTheKiller: + - bugfix: Fixed round event controller pirate spawns. + SyncIt21: + - bugfix: chem heater with empty buffers can be refilled again +2024-01-16: + 00-Steven: + - bugfix: Signers no longer use the wrong verb when speaking directly into a radio + for the first message after toggling sign language. + - refactor: Moved the updating of verb variables into a new method which is called + earlier in living's say, which should avoid this happening for other things + which updated their verbs the same way. + 13spacemen: + - code_imp: Hub status shows if server is restarting, starting, time to start. The + "Time" is more accurate and based on roundstart now + Ben10Omintrix: + - bugfix: fix bileworm ai going insane after eating someone + Hatterhat: + - bugfix: Moodlets with parameters/effects e.g. limb reattachment moodlets should + probably disappear more appropriately. + JohnFulpWillard: + - qol: Using a Lawyer badge in your hand now shows a thought bubble with the badge, + rather than giving a lousy message in your chat. + Melbert: + - bugfix: Fixes some eldritch painting messages having no chat span + - bugfix: Fixes eldritch painting messages triggering before the examine, making + it hard to see + - bugfix: Fix beauty eldritch painting doing nothing to heretics who examine it + Time-Green: + - bugfix: Radiation shelters (wherever they are) will protect against nebula storms + Voudez: + - qol: Players are now able to see in chat when they are being hit by obj/machinery, + got crushed into dense turf or get hit by thrown non carbon mob. + - admin: Scenarios like mob hits dense turf, obj/machinery hits mob, item without + "living thrower" hitting mob, mob gets hit by thrown non carbon mob now appear + in logs. + ZephyrTFA: + - bugfix: Autolathes no longer allow you to duplicate materials at higher levels + of stock parts + - qol: Autolathes now show name instead of typepath when selecting a custom material + - qol: Autolathe now print out items one by one instead of waiting for all of them + to print at once + jlsnow301: + - bugfix: Sections will be more polite by not stealing focus from Input boxes in + TGUI + - bugfix: Poppers (like the prefs menu options) shouldn't be hidden beneath the + character preview anymore. +2024-01-17: + SyncIt21: + - qol: adds more examines, screentips & balloon alerts for cryo actions, cryo can + be pried open with a crowbar when there is no power to free someone trapped + inside + - qol: cryo auto turns on immediately without a 2 second delay & without needing + auto eject mode on + - bugfix: ejecting beaker from cryotube will put it in the players hand & not drop + it on the floor + - bugfix: mobs & other stuff now render on top of cryo tubes and not bottom of it + - bugfix: cryo checks for if the mob is on the same turf as cryo is now fixed, i.e. + you can no longer close the machine on yourself + - code_imp: removed unused vars from cryo, autodocs vars, removed unused `transfer_amounts` + from some chem machinery ui + - refactor: removed unused icon states for cryo tube, cryo no longer processes round + start and it's UI is now typescript + Time-Green: + - code_imp: The mushroom cap is now an external organ (jungle station will never + happen) + - bugfix: Mushpeople caps are no longer solid black + mc-oofert: + - image: the plumbing chemical splitter no longer has a wrongly rotated direction + the-orange-cow: + - bugfix: Lock heretics may once again access 'the relentless heartbeat' after purchasing + 'burglars fineness'. + vinylspiders: + - code_imp: removed the timeout_mod arg from add_mood_event, which was only used + for one thing and causes more issues than it's worth +2024-01-18: + 00-Steven: + - bugfix: Immobile shells no longer work regardless of anchor state if you put the + circuit in while it's unanchored. + - bugfix: Immobile shells properly propagate their on/off state after wrenching + to inner modules. + DATA-xPUNGED: + - bugfix: Bystanders will no longer think they've pulled out a victim's eyes after + seeing someone else do it. + - qol: They will also be able to tell when someone starts pulling out a victim's + eyes. + Ghommie: + - rscadd: Most fishing rods come with a hook and line preinstalled. Fishing toolboxes + come with separate reel and lines as usual. + - balance: Fishing hooks are now required to fish. + - balance: Without a reel line, the range of fishing rods is reduced by two tiles. + Conversely, having one installed gives a mild buff to the minigame completion + speed. + - balance: The craftable sinewy reel line can now be used to fish on lava or liquid + plasma, but it's a bit harder to use. + - balance: The rare-to-find-in-maintenance master fishing rod now comes with a flexible + line and weighted hook preinstalled, and has better range than other rods. + - balance: Fishing reel lines are now small enough to fit pockets. + - rscadd: The rescue and jawed hook can now snag and reel in mobs, not only items. + The jawed hook also slows down when applied, a la beartrap. + - qol: Fish bounties now accept filled (stasis) fish cases. + - qol: Several balloon alerts for fishing rod interactions. + - bugfix: Reeling in items (and mobs) now respects movement resistance and anchorage. + - bugfix: Fixed the fishing rod equipment UI being too small to fit its components. + - sound: Reeling in something now plays a sound. + - image: Resprited several fishes, and the aquarium. + - rscadd: Shields (and pillows) can be used to shove people around the same way + barehanded right-clicking does. Xenos and borgs can actually be moved this way. + - rscadd: Added a new MODsuit module, the bulwark module, which prevents knockdown + and staggering from shoving, and getting pushed away by thrown objects. Inbuilt + for the safeguard MODsuit, but one might also it in the black market. + - refactor: Disarming has been refactored. You can now shove simple critters onto + tables and into bins and closets + - balance: Shields now take their own armor values and the armor penetration of + the attack they blocked when damaged. This means shields are a bit sturdier + now. + - balance: Riot shields can tank a lot more damage against melee weapons, but less + against bullets. + - qol: strobe shields can now be used to bash people while combat mode is on. + JohnFulpWillard: + - refactor: Implant pads now use TGUI + - refactor: Ice cream vats now use a radial menu instead of an HTML one. + intercepti0n: + - refactor: Refactored Ore Silo Ui. + mc-oofert: + - code_imp: slightly cleans up dance machine code + - bugfix: disco dances are less comparable to having a seizure +2024-01-19: + 13spacemen: + - bugfix: Hub time should be correct again + Ben10Omintrix: + - bugfix: fix a runtime when loading ghosts to a mulebot +2024-01-20: + Diamond-74: + - bugfix: Monkeys don't get stuck on obstacles as often. + Ghommie: + - image: The "One Lean, Mean, Cleaning Machine" achievement now has its own icon. + Momo8289, Pepsilawn, Sinsinins, JohnFulpWillard: + - bugfix: Clown ops now get a code set for their nuke. +2024-01-21: + Ben10Omintrix: + - bugfix: fixes megafauna AI getting stuck attacking some corpses + Ghommie: + - rscadd: Modular Computers now support integrated circuits. What can be done with + them depends on the programs installed and whether they're running (open or + background). + - rscadd: Modular Consoles (the machinery) now have a small backup cell they draw + power from if the power goes out. + - image: The honkbot now looks up to date. + - bugfix: Fixes how the aquarium looks. + JohnFulpWillard: + - bugfix: Prototype emitters now work. + - bugfix: Prototype emitters don't go invisible if screwdriver'd open. + - bugfix: Emitters no longer show up as their panel being closed when it is open. + LemonInTheDark: + - bugfix: The map saving tool will no longer lock up and prevent all further action + at random + - bugfix: Map saving now takes on the order of seconds, not minutes + - bugfix: Fixes an issue with lists that caused strongdmm to report saved maps as + broken + Majkl-J: + - bugfix: Borgs now use the hug module to substitute for hands, allowing them to + finish previously unfinishable surgeries + Melbert: + - bugfix: Items and mobs no longer hide behind big runes (heretic, cult, wizard) + Rex9001: + - bugfix: Lunatics spawned from moon ascension now actually have an objective to + assist their ringleader + - bugfix: Lunatics now get a moonlight amulette on the ground if they have full + hands + Rhials: + - spellcheck: Modifies some existing Emergency Shuttle prerequisite messages, and + adds some missing ones. + YehnBeep: + - bugfix: Icebox perma's cytology lab is now useable without outside repairs. + Zergspower: + - bugfix: fixes whispering formatting + lizardqueenlexi: + - bugfix: Filled trash bags show up properly when worn. + mc-oofert: + - bugfix: cyborg inducer respects cell changes +2024-01-22: + Absolucy: + - bugfix: Tool arm implant hotkeys will properly work even after you change your + arm/species now. + Ghommie: + - bugfix: fixed some visual artifacts from achievement icons. + carshalash: + - bugfix: Nanotrasen is now stocking proper french Cognac instead of discount Irish + Cognac. It will now taste Smooth and French + lizardqueenlexi: + - bugfix: Wagging tail spines now sync up properly with the tails they're attached + to. +2024-01-23: + 00-Steven: + - sound: APCs actually play the tool sound when exposing their wires. + A.C.M.O.: + - config: Added two new config flags for quirks, DISABLE_QUIRK_POINTS and MAX_POSITIVE_QUIRKS. + Ghommie: + - bugfix: Goldgrubs should no longer spit out things that aren't ore (e.g. stasised + mobs from the polymorph belt). + scriptis: + - spellcheck: iron sheets's what? +2024-01-24: + JohnFulpWillard: + - bugfix: pAIs downloaded while in a PDA now gets the action button to control said + PDA. + - bugfix: pAI cards can now be ejected from a PDA when there is no pAI inhabiting + it. + - bugfix: Moved the curator's treasure hunter fedora up by a single pixel. + - bugfix: Contractor support units now don't have flavortext telling them they work + for someone else but their agent. + - bugfix: Contractor support units now comes in an antag spawner (like syndicate + monkey, nukie borgs/reinforcements). + - bugfix: Syndicate monkeys now get their monkey antag datum. + LT3: + - bugfix: Server announcements again no longer escape HTML by default + Melbert: + - bugfix: Replacing a limb fully claimed by an infested burn wound now properly + grants you control of the limb back + - bugfix: Losing control of a limb now sounds less weird in chat + SyncIt21: + - bugfix: reactions that create multiple reagents now terminate without looping + endlessly. + - bugfix: oatmeal reactions now terminate & produce the right quantity of results + but without milk. + vinylspiders: + - bugfix: fixes features not updating when changing character slots + - bugfix: fixes a spurious CI failure from do_charge() signal overrides +2024-01-25: + ArcaneMusic: + - refactor: Stock market events are now their own objects, and are handled by the + stock market individually. + - bugfix: The smelter and refinery now properly hold mining points, only taking + a small amount out of net gained points. + FernandoJ8: + - bugfix: the blur effects for hallucinogenic withdrawal and psychic projection + are now properly centered on the screen + LT3: + - bugfix: You can no longer interact with medical beds while incapacitated + SyncIt21: + - bugfix: fixes runtime when mineral scanning, passes right argument for scanner + in golem eyes +2024-01-26: + 13spacemen: + - bugfix: The hub time should be accurate for servers with different timezones + Ben10Omintrix: + - refactor: airlock controllers now use tgui + Ghommie: + - bugfix: Fixed the messenger circuit not sending messages. + - bugfix: Added several ports to modpc circuits that were missing or needing them. + - bugfix: Fixes ever-expanding ports whenever circuits are re-inserted in a modular + computer. + Isratosh: + - bugfix: Kidnapping traitor objective now properly returns the victim to the station + with their items + - bugfix: Missing fire alarms added to several rooms on Birdshot + JohnFulpWillard: + - bugfix: The library console's category search box now displays the category being + searched. + - refactor: Newspapers now use TGUI. + - bugfix: Fixed the newscaster's wanted section showing a non-existent photo. + JohnFulpWillard, Unit2E teaching me the TEG: + - bugfix: The TEG now works again (still unobtainable by regular means though). + - bugfix: the TEG and its circulators can now be rotated counterclockwise again. + - refactor: The TEG now uses a TGUI interface rather than the old HTML one. + LT3: + - bugfix: Fixed alignment of RPG titles + LemonInTheDark: + - bugfix: Icebox will no longer spawn a fuck ton of plasma after gibonite blows. + YW besties + Melbert: + - bugfix: Click CD applies to looking up and down correctly. + - bugfix: Fixed AIs who shunt to APCs causing their laws to be deleted. + - bugfix: You can no longer neck snap anyone with martial arts assuming you've got + someone in a tight grip. + SyncIt21: + - bugfix: items that contain recursive contents inside them (like foam dart boxes + from autolathes) now have their custom materials set to match with its design + cost rather than being nullified, meaning they are now recyclable. + - code_imp: all custom materials are now integer values. Improved code for how materials + are used in techfab & auto lathe for printing + - qol: added more screen tips & examines for ore silo, made UI wider, attach location + name to each machine & grey out paused machines to make it more noticeable. + - code_imp: auto docs proc & vars for silo log entry. Fixed return values of all + tool acts + - bugfix: ore silo UI now functions correctly after removing an entry from the UI + - bugfix: no runtimes when connecting a machine to silo that was previously disconnected + via the ore silo UI + - refactor: chemical reaction tester in runtime station has been remastered from + ground up. + intercepti0n: + - refactor: Refactored Ore Processing Unit UI. + - bugfix: Ore Processing Unit UI no longer lags client. +2024-01-27: + 00-Steven: + - bugfix: Cats can be swabbed for feliform cells again. + - sound: Cats have had their mastery of silent walking revoked, and have their pitter-pattering + footsteps back again. + Melbert: + - bugfix: Rush Gland now triggers correctly on being grabbed by a Goliath + Whoneedspacee: + - refactor: Legions abilities have been changed into actions that can be added to + any mob. + vinylspiders: + - bugfix: fixes a hard del with decals +2024-01-28: + 00-Steven: + - spellcheck: You no longer fail to find "a anything" when swabbing something for + cytology that doesn't have swabbing results. + - bugfix: Removed kitten omniscience. (They stop pointing at you now.) + Ghommie: + - image: Added an icon to the "My Watchlist Status is Not Important" achievement. + LT3: + - bugfix: Emergency shuttle console now only works while on the emergency shuttle + SyncIt21: + - spellcheck: shortened description for the PCM and capitalized some text for its + examines. + - code_imp: adds sanity checks and removed deprecated `content` tag from PCM UI. + Stops hologram items from being inserted. + VladinXXV: + - bugfix: Oppenheimer, the nukie medbot, has been reprogrammed to use Airplane Mode + as a factory default. The station AI is no longer immediately aware of his presence! + intercepti0n: + - refactor: 'Refactored Telecommunications Monitoring Console UI: added search bar, + made UI more compact.' +2024-01-29: + 00-Steven: + - bugfix: Blobs sitting on APCs no longer break them when already broken, and so + no longer spam the power down noise. + - bugfix: Jerry Tramstation can get laid again! (Fixed cat breeding.) + 13spacemen: + - refactor: Husk icons are now dynamically generated. See if you can identify what + species it was! + Ben10Omintrix: + - bugfix: fixes a runtime that sometimes happens in ai controllers + DaCoolBoss: + - spellcheck: typos fixed in the fireproof clothing religious rite + Krypandenej: + - bugfix: pesto pizza (cooked) is no longer raw, and cilbir is meat instead of fruit + LT3: + - bugfix: Tramstation external atmos ports are now properly connected + Melbert: + - bugfix: Wizarditis Timestop now has the desired effect. + - admin: Admins can now VV Timestop to make the caster not immune to their own Timestop. + If they really wanted. + SyncIt21: + - bugfix: sets minimum volume of reagent allowed to exist inside anything to 0.01 + therefore allowing plumbing iv drip small transfer rates to occur without reagents + disappearing. + - bugfix: auto lathes don't hog local apc supply when printing items + rageguy505: + - bugfix: ' The buttons for flashers on birdshot/tramstation now work' +2024-01-30: + 00-Steven: + - bugfix: Rootbread soup now uses poached egg (egg reagents in soup pot) instead + of raw unpeeled egg (which would break into reagents upon coming into contact + with the soup pot) and is thus craftable again. + ArcaneMusic: + - rscdel: The lathe tax on printing items has been removed from the game for both + humans and silicons. + Ghommie: + - image: Resprited the goldfish and lanternfish as well. + Inari-Whitebear: + - bugfix: Bone daggers and shivs are now longer electrically conductive + Melbert: + - bugfix: You can build on some niche tables again, such as the Wabbajack Altar. + Profakos: + - bugfix: The construction console drone becomes visible again while its in use + Rhials: + - bugfix: The encrypted bitrunner cache is now impervious to most conventional means + of destruction. + distributivgesetz: + - bugfix: Ambient loops will now refresh when entering a mob. + jjpark-kb: + - code_imp: added the code that allows the image overlays to work for the circulator + (teg) + - image: changed circulator's (teg) fast/slow turbine sprite + - image: added an anchor, panel, and flow overlay for the circulator (teg) +2024-01-31: + Jacquerel: + - bugfix: Cult pylons will no longer cover up "open space" (or water) with cult + floors + LT3: + - bugfix: ID card examine text shows how to set your linked bank account + - bugfix: ID card withdrawal from an unassigned account will ask if you want to + link an account + - bugfix: Tram power consumption will no longer will randomly drain APCs + - bugfix: Tram power moved to area based rectifiers + Melbert: + - bugfix: Fixed ore vent descriptions looking weird sometimes + - bugfix: Fixed being able to scan an ore vent multiple times at once + - bugfix: Fixed gaining scan points from scannning an ore vent without finishing + the scan + SapphicOverload: + - bugfix: Mk-II Ripley exosuits are spaceproof again. + - bugfix: Converted mechs now have their lights on if they did before the conversion. + distributivgesetz: + - bugfix: All Within Theoretical Limits should properly unlock now when the crystal + comes back from the countdown. diff --git a/html/changelogs/archive/2024-02.yml b/html/changelogs/archive/2024-02.yml new file mode 100644 index 0000000000000..6afd8b08c3696 --- /dev/null +++ b/html/changelogs/archive/2024-02.yml @@ -0,0 +1,448 @@ +2024-02-01: + Donglesplonge: + - bugfix: the top and top righthand doors of northstars cargo mail sorting room + now use any supply general, mining, and bitden access, and the top righthand + door no longer says its mining decontamination and uses the proper mail sorting + room airlock instead. + JohnFulpWillard: + - balance: Pirate suits can now hold the laser musket and smoothbore disabler. + - bugfix: Cultists can now vote for a Cult leader again. + - bugfix: Slimes using Feed while buckled now stops feeding. + - bugfix: Slimes are no longer prompted to feed off of dead people. + - bugfix: Slimes that can only feed onto one person now immediately feeds off of + them. + K4rlox: + - bugfix: makes the experiment chamber in syndicate lavaland base no longer explode + when testing grenades + Melbert: + - refactor: Jukebox has been refactored. Jukebox music now updates as the player + moves, mutes when the player is deafened, and overall sounds wayyy better. You + can also now toggle song repeat on jukeboxes. + ZephyrTFA: + - bugfix: Meteor Satellites no longer erroneously count every piece of paper as + a protected turf. + - bugfix: As a result the station goal is slightly more difficult + intercepti0n: + - bugfix: fixed ore vent's well being drawn over ash storm. + sylvia-from-fulp-station: + - rscadd: '[IceBox] Botany now has a service accessible bio generator' + - qol: '[IceBox] Kitchen and service hall rearranged' +2024-02-02: + 00-Steven: + - bugfix: Robotic voicebox actually lets you speak any language again (as long as + you know it). + Melbert: + - bugfix: Fixed examining modular PCs + - refactor: Big martial arts refactor, they should now overall act a ton more consistent. + Also technically any mob can do martial arts. Let me know if something is funky. + - bugfix: Robocontrol app maybe works better now. + Momo8289: + - rscadd: There's now a small chance to be a smartass when affected by Voice of + God + SapphicOverload: + - rscadd: Exosuit-mounted RCDs now have the same functionality as the handheld version. + SkeletalElite: + - rscadd: Wearing certain gloves (such as black gloves and combat oriented gloves) + allow you to cuff people faster + - balance: base handcuffing time is now 4 seconds + - balance: latex gloves now hide your fingerprints + ZephyrTFA: + - bugfix: lathes now respect always-powered areas + - balance: lathes now use power as they print instead of all at once + distributivgesetz: + - bugfix: Teleportations will no longer exceed reservation bounds. + intercepti0n: + - refactor: refactored experimentor UI to TGUI. + zxaber: + - balance: Mech wire panels are now blocked when the mech is occupied. I guess it + was moved to be behind the seat. +2024-02-03: + Justavidya: + - image: modified the cherry cupcake and blue-cherry cupcake sprites + Rhials: + - qol: Spider eggs will now close their spawn menu when you move away from them. + Whoneedspacee: + - bugfix: Ash drake's fire breath attack has proper cooldowns again +2024-02-04: + FernandoJ8: + - sound: The Guide to Advanced Mimery book series now only make a very faint noise + when turning their pages, in order to match their description + LT3: + - bugfix: Fixed alt-click validation for tram interactions + SapphicOverload: + - code_imp: Removes /obj/item/onetankbomb, assembly bombs are now handled by the + tank itself. + Swiftfeather: + - qol: Increased threshold of trace n2o required to make euphoria and giggles happen. + Xander3359: + - balance: Contractor baton costs 7 TC (down from 12 TC) + - balance: Midroll/Latejoin traitors can no longer buy the baton, they must buy + the whole kit + - bugfix: Midroll/Latejoin traitors no longer have access to roundstart traitor + exclusive items +2024-02-05: + Xander3359: + - bugfix: burning a bible no longer gives you INFINITY curses +2024-02-06: + Higgin: + - bugfix: fixes contractor abduction RRing people in the offmap hideout without + any feedback. You might not like how it turns out, but your body will get back + to the station now. + - qol: portable air pumps, scrubbers, heaters, canisters, liquid tanks, and racks + are now climbable. + Jacquerel: + - rscadd: Reading a newspaper will conceal your identity. + - bugfix: Making a mob sentient no longer gives you all of their factions. + - rscadd: Mothic Fleet Rations now no longer get dirty or decompose if left on the + floor, due to their wrappers. + Melbert: + - bugfix: When placing an item into storage (such as backpacks), all nearby mobs + now get a message, rather than just the first mob. + - bugfix: TGC decks of cards should act a bit less odd when looking inside. + - refactor: Refactored a bit of storage, cleaned up a fair bit of its code. Let + me know if you notice anything funky about storage (like backpacks). + - bugfix: Tinacusiate should break less, and break less things + - bugfix: Speaking to a Sign Languager with Tinacusiate in your system doesn't mess + with their text, because they're not speaking. + mc-oofert: + - bugfix: you may not toggle health assemblies from any range, even while crit + - bugfix: you may no longer altclick tape recorders at range to play them + - bugfix: you may no longer rip pillow tags at range without telekinesis or crit + or any other time you shouldnt be capable of it + - bugfix: you may no longer altclick ethereal disco balls while unconscious to anchor/unanchor + vinylspiders: + - bugfix: fixes a runtime in AI search_tactic +2024-02-07: + Higgin: + - bugfix: fixes ghetto surgery by gently adjusting time sensitivity cap and making + the cleaver not unintuitively bad at bone-sawing. + Momo8289: + - bugfix: Automated IV drips will now be on the layer set by the plumbing constructor + when created. + Rhials: + - bugfix: You can no longer convert assassination targets to your blood brother + team. + SyncIt21: + - bugfix: deleting objects with local material storage(autolathe, drone dispenser + etc) no longer drops sheets, they only drop materials when deconstructed +2024-02-08: + ArcaneMusic: + - bugfix: Icebox should have it's ore distribution and it's ore vents fixed, so + that vents should now produce ore. + - spellcheck: Boulder processing machines now don't mention things they don't do. + Higgin: + - bugfix: removed ability to buckle megafauna, constructs, blob minions, dragons, + and slimes. + IndieanaJones: + - bugfix: Regenerative Materia blobs can no longer drug non-carbons. + - bugfix: Alien Hunter's pounce ability no longer has any weird offset issues once + the pounce is completed. + - bugfix: Xenomorphs now have legcuffs applied to them properly. + Jacquerel: + - bugfix: Being consumed by the flesh of the necropolis while inside a cryo tube + will no longer convert the tube into a legion spawn point until the tube is + destroyed + JohnFulpWillard: + - rscadd: Paradox clones now have a bluespace stream instead of a syndicate poster + as their ghost poll icon. + - qol: Service personnel now show up in green in the crew monitor console. + - spellcheck: Mechs that have been renamed now are proper names, so are not described + as 'the' mech. + - bugfix: Science Xenos no longer turn the entire roundend report into bold letters. + Justice12354: + - qol: You no longer need to manually set the CC Commander's Headset to high-volume. + LemonInTheDark: + - rscadd: Ghost hair looks better now. Insert nerd shit about RGB vs HSL color space + here, go watch a youtube video or whatever. + Melbert: + - bugfix: The object overlay circuit component will no longer take things with it. + OrionTheFox: + - image: fixed some minor icon inconsistencies on the White Suit and Tan Suit + PapaMichael: + - bugfix: tramstation isolation cells now properly open on their timer. + Rhials: + - balance: Wallmounts now respect cameranets, and cannot be accessed from a view + of the adjacent wall. + SyncIt21: + - bugfix: reactions now compute purity of reagents based on their volume, meaning + larger amounts of reagents created will have more significant effects on the + final purity of the solution + - bugfix: Instant reactions yield reagent results again + ViktorKoL: + - bugfix: smoothed out a few asymmetries in the heretic research tree + - bugfix: ash mark now properly lowers the cooldown of mansus grasp when triggered + Zargoar: + - rscadd: 'Adds in the Red Mons, The Wizard, The Rune and MOTHS-MOTHS-MOTHS bar + signs from the bar sign contest. + + Congratulations to the winners!' + - image: Adds in the Red Mons, The Wizard, The Rune and MOTHS-MOTHS-MOTHS bar signs + from the bar sign contest. + cnleth: + - balance: RD and QM coats can hold telebatons now + - qol: Quartermaster's coat can hold items that a normal jacket can hold, such as + small oxygen tanks + dragomagol: + - bugfix: blood brothers should now properly succeed in converting non-targets + mc-oofert: + - bugfix: you may no longer reset autolathe drop direction at times when you shouldnt + be able to + - bugfix: you may no longer altclick unanchored toiletbongs from any range in any + condition to rotate them + optimumtact, whomst didn't write this CL entry.: + - bugfix: Fixed the colorful lights of the ethereal disco ball. +2024-02-09: + EEASAS: + - bugfix: Fixed some things in Ice Box's gas station ruin + - rscadd: Adds some things in Ice Box's gas station ruin + Ghommie: + - bugfix: Fixed text effects for runechat messages (the stuff enclosed in +, | and + _ characters). + - spellcheck: Improved the tip for say/text effects. + JohnFulpWillard: + - qol: The barcode scanner is now part of computer tech and can be printed at the + service techfab. + - bugfix: Species that can eat mice don't get disgusted from seeing one in the toilet + bong. + - rscadd: Grabbing an unwrenched statue/mannequin/skeleton model will now move its + direction as you move yours, and you can talk through it. + LT3: + - bugfix: SM cascade delam messages no longer display to clients not in game + Melbert: + - bugfix: Fixed Puncture Repair surgery not having surgical sounds + - bugfix: Fixed Surgery Initiator potentially showing invalid surgeries + - bugfix: Krav Maga users can shove again + Swiftfeather: + - qol: Added a new head-only target hotkey, unbound by default. + SyncIt21: + - qol: adds screentips & examines for screwdriver & crowbar acts & alt click. + - qol: techfabs can now use the mouse drop functionality to set drop target. + - qol: lathe printing animation plays on loop while printing rather than flicking + once for more visual feedback + - bugfix: lathe sheet insertion animations are now linked & work again for all material + types inserted via remote silo/local storage + - bugfix: printing custom materials items from autolathe works again. + - bugfix: printing multiple items from lathes will actually print that correct quantity + of items requested. + - bugfix: printing items the 2nd time around from lathes won't cause the UI to reload + each time. + - code_imp: autodoc for some vars & procs, merges procs. + - refactor: Optimized code for autolathe & techfabs in general. Report bugs on github + ViktorKoL: + - bugfix: fix heretic's rust mark failing to damage any items if the victim has + any container on them with another item inside, and maybe other bugs of similar + nature + Wallem: + - rscadd: Adds a new suicide_act() to the smite spell + Zergspower: + - bugfix: icebox ore generation underground is normal again +2024-02-10: + 00-Steven: + - bugfix: As they should've for a while, screwdrivers (cocktail) actually work as + screwdrivers (tool). + - sound: The screwdriver cocktail also actually plays the screwdriver sound when + used. + 1393F: + - bugfix: The HFR now provides the max temperature for recipes + - bugfix: the crystallizer now provides the dangerous object created instead of + itself in admin logging + Ben10Omintrix: + - rscadd: settlers can rename their pets + FernandoJ8: + - code_imp: added an IS_CHANGELING() helper and used it where applicable + Holoo-1: + - admin: remade everyone is traitor into everyone is antag in secrets panel + IndieanaJones: + - bugfix: Bolas in your pockets no longer slow you down. + JohnFulpWillard: + - rscadd: Changeling's reviving stasis ability now puts you in stasis. + LT3: + - qol: Health analyzer will now display blood alcohol content + LemonInTheDark: + - rscdel: Removes halloween screen tint, we're taking him to retire by the seaside + (he was alone and unloved) + Melbert: + - bugfix: Fixed some situations in which you couldn't interact with heretic runes + - bugfix: Fixed Finger Guns giving a misleading chat message + - bugfix: You know that one bug that makes the cryo cells on Deltastation unusuable? + Well it's not fixed but at least those cryo cells are usuable again, maybe at + the cost of another station's cryo cells. Who knows! + larentoun: + - bugfix: Now falsewalls visually don't close when they shouldn't. +2024-02-11: + Absolucy: + - bugfix: Fix succumb last words being double-encoded (i.e `i'm` becoming `i'lm`) + Higgin: + - bugfix: deathtrap recycler can now be moved. + - refactor: moved check for NO_DECONSTRUCTION flag to be inside can_be_unfasten_wrench, + allowing us to set specific machines to be movable but not deconstructable. + JohnFulpWillard: + - bugfix: Space Dragon's carp allies no longer turn the entire roundend report into + bold. + - bugfix: Uplinks now update their UI when you add telecrystals in them, so you + don't need to close and reopen it. + - bugfix: Bar bots asking for Cucumber Lemonade now gives you money for completing + it. + Melbert: + - balance: Rats are now 5x less likely to decide to eat a cable when idling. (1%, + down from 5%) + - refactor: Fire effects get added to mobs in a different way now. Maybe it will + get stuck less. Report any oddities. + - rscadd: Freedom Implants and Biodegrade can you free you of the shackles of knotted + shoes. + - rscadd: Shaft Miner's Bluespace Survival Capsules will now throw people away from + it when it deploys. Be sure to heed the warning to "Stand back". + Momo8289: + - rscadd: Fire ants can now be scooped up and used as a chem like normal space ants + Rhials: + - bugfix: You can now refine ectoplasmic raw cores at the implosion machine thing. + - balance: Beacon and Teleport Blocker implants have been moved from cargo to the + security lathe. + - balance: A new research node has been created, requiring Subdermal Implants and + Miniature Bluespace, to unlock Beacon/Teleport Blocker implants. + - balance: Exile implants can now be printed from the security lathe after researching + basic cybernetic implants. + - bugfix: The Energy Cake slice now does its on-eat effect AFTER being eaten, instead + of before. + - bugfix: The beach bar virtual domain's bar closets no longer have default locker + doors. + SyncIt21: + - bugfix: lathes now use moderate power for printing operations + necromanceranne: + - bugfix: The shove blocker module parent type now has the correct typepath. +2024-02-12: + Atlasle, JohnFulpWillard: + - rscadd: Adds the Touchy quirk, you need to be next to something to examine it, + for 2 extra quirk points. + Ed640: + - bugfix: Sculpted statues stop including light layers. + LT3: + - bugfix: MULEbot will correctly display its loaded cargo on BotKeeper + - bugfix: MULEbot home beacon can be set from control panel + - code_imp: MULEbot home location is automatically set on init + Melbert: + - qol: Glasses colors should be a lot less harsh, and being blind no longer also + blinds your hud. + - bugfix: Lavaland Beech Bartender's clothing storage is named the right thing now + - bugfix: Lipoplasty is An Option again + Momo8289: + - rscadd: Shakers can now pour drinks with custom names and descriptions! Alt-click + the shaker to enable this. + NeonNik2245: + - rscadd: 3 new masks for your characters (thanks to Kovac for the scarf sprites) + Rhials: + - bugfix: You can now click things as an alien larva again. + SyncIt21: + - bugfix: machines that should not drop contents when deleted no longer do. + - refactor: refactors how machines are deconstructed. report bugs on github. + Thunder12345: + - bugfix: The wizard's pets Jimmy and Jommy no longer fight to the death. + Xander3359: + - balance: Contractor kit no longer gets RNG items, it's a specific pool now + mc-oofert: + - rscadd: nanotrasen museum gateway map +2024-02-13: + 00-Steven: + - bugfix: Fixed stamps not accounting for scroll offset. You can actually stamp + paperwork properly without using accursed knowledge again. + - bugfix: Fixed PDA messenger not scrolling to the bottom when a new message gets + sent. + - bugfix: Paperwork should actually use the writing utensil's font, colour, and + boldness outside of input fields again. + LemonInTheDark: + - bugfix: Fixes fancy lights not updating their source location when picked up and + moved + Melbert: + - bugfix: You can once again see love on Valentines Day + MelokGleb and KREKS, McRamon, Ghommie: + - image: New sprites for changeling powers and items. Woooh. + Rex9001: + - balance: Smile of the moon's effects now last for longer and have a longer minimum + active time + - balance: Mark of moon detonations now do sanity damage + - qol: Ascended moon heretics and lunatics are now able to see who is and isn't + a lunatic. + - qol: People inside the ascended moon heretics sanity draining aura now gain an + effect under their tile. + Rhials: + - qol: Venus Human Traps are now visible in the orbit menu. Cool! + - bugfix: Spawning as a Venus Human Trap now properly gives you an antag datum/objective. + - bugfix: Venus human trap flowers no longer have a second, identical flower under + themselves. + - qol: Map hazard anomalies are no longer treated as points-of-interest in the orbit + menu. + Singul0: + - balance: Brainwashed individuals can no longer be debrainwashed through a mindshield + implant, they still protect you from brainwashing though! You just need to get + medical to fix them up. + - bugfix: You can now use bluespace launchpads from shuttles (except cargo and ferry + shuttles) + - bugfix: Fixes backwards engines in a few of centcom's ferries! + intercepti0n: + - bugfix: Separated logs list into pages in ore silo UI, thus fixing lag when logs + list grows too long. + siliconOpossum: + - bugfix: Getting blood on your hand when you only have one no longer makes your + hands visually permanently bloody + - bugfix: Holding bloodied gloves no longer makes your hands look bloody, bloodied + gloves now look bloody when worn, and damaged gloves now look damaged when worn + - bugfix: Gaining or losing an arm now correctly updates your hand overlays +2024-02-14: + Absolucy: + - rscadd: Painkillers (i.e morphine, miner's salve) now actually induce analgesic + effects, preventing various pain-related effects, such as screaming due to pain, + and also provides a speed bonus during surgery. + - rscadd: The tenacity trauma (traumatic neuropathy) also applies analgesic effects. + - refactor: Simplified code related to reagents adding traits. + Aylong: + - rscadd: Added `Mute` button into `Chat Tabs` settings, it disables tab unread + counter + - rscadd: Added `Clear chat` button into `General` settings, you can clear your + dirty chat like you did it before TGchat + - bugfix: Case-sensitive highlighting now works properly + JohnFulpWillard: + - bugfix: Revenants (and other flying mobs) will not make noise when walking into + pools of gibs, + Rhials: + - spellcheck: Some space ruin area names have been made more distinct. + - bugfix: Ghost role polls should spam you less when multiple of the same roll occur + in succession. + ViktorKoL: + - bugfix: heretics no longer lose their spells when returning from a shapeshift + - bugfix: knit flesh now heals organs as intended, and does not cause its victims + to be red forever if interrupted + - spellcheck: knit flesh chat messages are no longer gramatically incorrect + vinylspiders: + - bugfix: '*wag emote is now functional again' +2024-02-15: + CandleJaxx and Iamgoofball: + - bugfix: Fixes Krav Maga allowing pacifism bypasses. + IndieanaJones: + - balance: Nightmare's Light Eater takes less time in jaunt to gain a critical strike, + being reduced to 7 seconds from 15 seconds. + K4rlox: + - balance: Maintenance drones now can use RPED, RCD, Holosign, and Spray bottles + LemonInTheDark: + - rscadd: AI's acceleration now smoothly decays, instead of just falling back down + to 0 after 0.5 seconds + - bugfix: AI's standard movement (non accelerated) is smooth now, instead of constantly + jumping around + - bugfix: AIs will now follow their targets more closely, shouldn't have any issues + with them lagging behind anymore + Melbert: + - bugfix: Valentines no longer see themselves covered in hearts. They only see their + Valentine covered in hearts. + - balance: Scientists have discovered Nuka Cola is not good for short term health. +2024-02-16: + 00-Steven: + - bugfix: Fixed the lizardperson spine preference dropdown not showing up in the + character menu. + Kylerace: + - admin: admins/maintainers can now make the profiler focus on specific subsystems + by setting the subsystem var profile_focused to TRUE + LemonInTheDark: + - refactor: Fucks with how movement keys are handled. Please report any bugs + WinterDarkraven: + - rscadd: In an attempt to stop the greytide, NanoTrasen has increased security's + baton energy output. This has, through testing, done nothing but make the device + spark more than it used to. diff --git a/icons/Cutter.md b/icons/Cutter.md new file mode 100644 index 0000000000000..0d04c622feb0f --- /dev/null +++ b/icons/Cutter.md @@ -0,0 +1,76 @@ +## Guide to the icon cutter + +### What are cut icons? + +There are some icons in ss13 that are essentially stitched together from a smaller set of icon states. + +Smoothing is a prime example of this, though anything that takes a base image and operates on it fits the system nicely. + +### How does the cutter work? + +The cutter has a bunch of different modes, different ways to operate on images. They all take some sort of input, alongside a (.toml) config file that tells us what to DO with the input. + +The .toml file will know the cutter mode to use, alongside any config settings. Smoothing configs can use templates instead of copying out a bunch of information, templates are stored in the cutter_templates folder. + +The toml file will be named like this. `{name}.{input_extension}.toml`. So if I have a config mode that uses pngs as input (almost all of them) it'll look like `{name}.png.toml` + +It'll then use the `{name}.png` file to make `{name}.dmi` (or whatever the cutter mode outputs) + +You should NEVER modify the cutter's output, it'll be erased. You only want to modify the inputs (configs, pngs, etc). + +As I mentioned our cutter has several different modes that do different things with different inputs. + +Most cutter stuff in our repo uses the BitmaskSlice mode, you can find info about it [here](https://github.com/actioninja/hypnagogic/blob/master/examples/bitmask-slice.toml) + +## Bitmask Smoothing (BitmaskSlice) + +We use bitmask smoothing to make things in the world merge with each other, "smoothing" them together. + +This is done by checking around these objects for things that we want to smooth into, and then encoding that as a set of directions. +Now, we need icon states for every possible combination of smoothing directions, but it would be impossible to make those manually. + +So instead we take a base set of directions, typically no connections, north/south, east/west, north/south/east/west, and all connections, and then slice them up and stitch them together. + +Looks something like this + +>Example: [Bamboo](turf/floors/bamboo_mat.png.toml) +> +> png of 32x32 blocks, representing connections. +> +> [None, North + South, East + West, North + South + East + West, All] +> +>[Bamboo Template](turf/floors/bamboo_mat.png) +> +> And its output dmi +> +>[Bamboo Output](turf/floors/bamboo_mat.dmi) + +### How do I modify a smoothed icon? + +Modify the png, then recompile the game/run build.bat, it will automatically generate the dmi output. + +### How do I make a smoothed icon? + +Make a png file called `{dmi_name}.png`. It should be 5 times as wide as the dmi's width, and as tall as the dmi's height + +Create a config file called `{dmi_name}.png.toml`, set its [template](../cutter_templates/bitmask) to the style you want. Don't forget to set the output_name var to the base icon state you're using. + +Once you're done, just run build.bat or recompile, and it'll generate your cut dmi files for you. + +If you want to make something with nonstandard bounds you'll need to set the relevant variables, you can read the examples found [here](https://github.com/actioninja/hypnagogic/tree/master/examples) to understand different mode's configs. + +> Example: [Grass (50x50)](turf/floors/grass.png.toml) +> +>[Grass Template (50x50)](turf/floors/grass.png) + +If you want to give a particular smoothing junction a unique icon state use the prefabs var, add a new "state" to the png, and modify the config so it knows how to use it. + +> Example: [Donk Carpets (Big Pocket)](turf/floors/carpet_donk.png.toml) +> +>[Grass Template (50x50)](turf/floors/carpet_donk.png) + +If you want to make the smoothed icon animated, add another row of states below your first one. Each new row is a new frame, you define delays inside the config file as deciseconds. + +> Example: [Lava (Animated, 4 Frames)](turf/floors/lava.png.toml) +> +>[Lava (Animated)](turf/floors/lava.png) diff --git a/icons/area/areas_station.dmi b/icons/area/areas_station.dmi index b07ea38a1592b..66098018f7598 100644 Binary files a/icons/area/areas_station.dmi and b/icons/area/areas_station.dmi differ diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi index 38d1d44a000d1..31f26c3e6e11e 100644 Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi index ae695c3227f3d..12e3ce9f7d594 100644 Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ diff --git a/icons/effects/bitrunning.dmi b/icons/effects/bitrunning.dmi new file mode 100644 index 0000000000000..8efa429389c3a Binary files /dev/null and b/icons/effects/bitrunning.dmi differ diff --git a/icons/effects/bitrunning_48.dmi b/icons/effects/bitrunning_48.dmi new file mode 100644 index 0000000000000..e7c5bf37d8e89 Binary files /dev/null and b/icons/effects/bitrunning_48.dmi differ diff --git a/icons/effects/bitrunning_64.dmi b/icons/effects/bitrunning_64.dmi new file mode 100644 index 0000000000000..397b4709c83b3 Binary files /dev/null and b/icons/effects/bitrunning_64.dmi differ diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi index 93f836f4c84bd..fb31da10ee983 100644 Binary files a/icons/effects/blood.dmi and b/icons/effects/blood.dmi differ diff --git a/icons/effects/cranial_fissure.dmi b/icons/effects/cranial_fissure.dmi new file mode 100644 index 0000000000000..9f8f5b4602137 Binary files /dev/null and b/icons/effects/cranial_fissure.dmi differ diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 29e59dc6c7148..0b01e4dd6a8cb 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/effects/eldritch.dmi b/icons/effects/eldritch.dmi index 8b7738f3b46a0..91fa9ff4f538b 100644 Binary files a/icons/effects/eldritch.dmi and b/icons/effects/eldritch.dmi differ diff --git a/icons/effects/fov/field_of_view.dmi b/icons/effects/fov/field_of_view.dmi index 8086773d1407f..8a15ae18a926b 100644 Binary files a/icons/effects/fov/field_of_view.dmi and b/icons/effects/fov/field_of_view.dmi differ diff --git a/icons/effects/mapping_helpers.dmi b/icons/effects/mapping_helpers.dmi index d28d2770ec4fb..351bb5f00c4aa 100644 Binary files a/icons/effects/mapping_helpers.dmi and b/icons/effects/mapping_helpers.dmi differ diff --git a/icons/effects/mouse_pointers/moon_target.dmi b/icons/effects/mouse_pointers/moon_target.dmi new file mode 100644 index 0000000000000..d89cab42aa124 Binary files /dev/null and b/icons/effects/mouse_pointers/moon_target.dmi differ diff --git a/icons/effects/mouse_pointers/scope_hide.dmi b/icons/effects/mouse_pointers/scope_hide.dmi deleted file mode 100644 index a6182e8fc5f31..0000000000000 Binary files a/icons/effects/mouse_pointers/scope_hide.dmi and /dev/null differ diff --git a/icons/effects/ore_visuals.dmi b/icons/effects/ore_visuals.dmi index 5189711bc2566..87c31c8c7df6c 100644 Binary files a/icons/effects/ore_visuals.dmi and b/icons/effects/ore_visuals.dmi differ diff --git a/icons/effects/particles/notes/note_light.dmi b/icons/effects/particles/notes/note_light.dmi new file mode 100644 index 0000000000000..3474edebc233c Binary files /dev/null and b/icons/effects/particles/notes/note_light.dmi differ diff --git a/icons/effects/random_spawners.dmi b/icons/effects/random_spawners.dmi index f96676da097eb..08df14c0ffc99 100644 Binary files a/icons/effects/random_spawners.dmi and b/icons/effects/random_spawners.dmi differ diff --git a/icons/effects/vent_overlays.dmi b/icons/effects/vent_overlays.dmi new file mode 100644 index 0000000000000..54ac3d7a819bb Binary files /dev/null and b/icons/effects/vent_overlays.dmi differ diff --git a/icons/hud/fishing_hud.dmi b/icons/hud/fishing_hud.dmi index b68acee09b76a..58c478d071064 100644 Binary files a/icons/hud/fishing_hud.dmi and b/icons/hud/fishing_hud.dmi differ diff --git a/icons/hud/lobby/signup_button.dmi b/icons/hud/lobby/signup_button.dmi new file mode 100644 index 0000000000000..a67cc5584424e Binary files /dev/null and b/icons/hud/lobby/signup_button.dmi differ diff --git a/icons/hud/radial.dmi b/icons/hud/radial.dmi index 897cb3a872e29..42d5c451018ae 100644 Binary files a/icons/hud/radial.dmi and b/icons/hud/radial.dmi differ diff --git a/icons/hud/radial_fishing.dmi b/icons/hud/radial_fishing.dmi new file mode 100644 index 0000000000000..65fd55176b7c8 Binary files /dev/null and b/icons/hud/radial_fishing.dmi differ diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi index a1fc01434e456..289d02da46ada 100644 Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ diff --git a/icons/hud/screen_changeling.dmi b/icons/hud/screen_changeling.dmi index 61d3513cbfa3d..c3bab062ac497 100644 Binary files a/icons/hud/screen_changeling.dmi and b/icons/hud/screen_changeling.dmi differ diff --git a/icons/hud/screen_full.dmi b/icons/hud/screen_full.dmi index 54931085b74bb..ac33631e1a0ec 100644 Binary files a/icons/hud/screen_full.dmi and b/icons/hud/screen_full.dmi differ diff --git a/icons/misc/buildmode.dmi b/icons/misc/buildmode.dmi index dd794c02aaa30..7d56918846b20 100644 Binary files a/icons/misc/buildmode.dmi and b/icons/misc/buildmode.dmi differ diff --git a/icons/mob/actions/actions_animal.dmi b/icons/mob/actions/actions_animal.dmi index f3f9a667f2204..64b1c700f414c 100644 Binary files a/icons/mob/actions/actions_animal.dmi and b/icons/mob/actions/actions_animal.dmi differ diff --git a/icons/mob/actions/actions_changeling.dmi b/icons/mob/actions/actions_changeling.dmi index 0969b03725e19..bb3634a1dde11 100644 Binary files a/icons/mob/actions/actions_changeling.dmi and b/icons/mob/actions/actions_changeling.dmi differ diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi index 747b57949be82..ac7575d279b9e 100644 Binary files a/icons/mob/actions/actions_ecult.dmi and b/icons/mob/actions/actions_ecult.dmi differ diff --git a/icons/mob/actions/actions_mecha.dmi b/icons/mob/actions/actions_mecha.dmi index 5ae25522434ef..7c659ca3b573c 100644 Binary files a/icons/mob/actions/actions_mecha.dmi and b/icons/mob/actions/actions_mecha.dmi differ diff --git a/icons/mob/actions/actions_trader.dmi b/icons/mob/actions/actions_trader.dmi new file mode 100644 index 0000000000000..59330b4aac832 Binary files /dev/null and b/icons/mob/actions/actions_trader.dmi differ diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi index 558045a0656b0..c9aa153453880 100644 Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ diff --git a/icons/mob/augmentation/advanced_augments.dmi b/icons/mob/augmentation/advanced_augments.dmi new file mode 100644 index 0000000000000..aeb9ca1ca9b44 Binary files /dev/null and b/icons/mob/augmentation/advanced_augments.dmi differ diff --git a/icons/mob/clothing/accessories.dmi b/icons/mob/clothing/accessories.dmi index 389cffe7cf2c9..57670005b9240 100644 Binary files a/icons/mob/clothing/accessories.dmi and b/icons/mob/clothing/accessories.dmi differ diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi index dc519380cfd7a..49e46149a670f 100644 Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ diff --git a/icons/mob/clothing/belt.dmi b/icons/mob/clothing/belt.dmi index cb7d3afa50894..fbe6871c92e2d 100644 Binary files a/icons/mob/clothing/belt.dmi and b/icons/mob/clothing/belt.dmi differ diff --git a/icons/mob/clothing/belt_mirror.dmi b/icons/mob/clothing/belt_mirror.dmi index 0c1b8457be7c3..eb06288d800bc 100644 Binary files a/icons/mob/clothing/belt_mirror.dmi and b/icons/mob/clothing/belt_mirror.dmi differ diff --git a/icons/mob/clothing/head/chaplain.dmi b/icons/mob/clothing/head/chaplain.dmi index efb6ec3c9e908..100b7ee922fb9 100644 Binary files a/icons/mob/clothing/head/chaplain.dmi and b/icons/mob/clothing/head/chaplain.dmi differ diff --git a/icons/mob/clothing/head/costume.dmi b/icons/mob/clothing/head/costume.dmi index 3c5ea17b19d15..06de36df19aac 100644 Binary files a/icons/mob/clothing/head/costume.dmi and b/icons/mob/clothing/head/costume.dmi differ diff --git a/icons/mob/clothing/head/hats.dmi b/icons/mob/clothing/head/hats.dmi index abf2f9f18bd40..0fd0de8e4250e 100644 Binary files a/icons/mob/clothing/head/hats.dmi and b/icons/mob/clothing/head/hats.dmi differ diff --git a/icons/mob/clothing/head/pai_head.dmi b/icons/mob/clothing/head/pai_head.dmi index 0a04e7e8ab29e..e5dd4965d8b8d 100644 Binary files a/icons/mob/clothing/head/pai_head.dmi and b/icons/mob/clothing/head/pai_head.dmi differ diff --git a/icons/mob/clothing/head/plasmaman_head.dmi b/icons/mob/clothing/head/plasmaman_head.dmi index 9846cf0220011..1917ae7bcf538 100644 Binary files a/icons/mob/clothing/head/plasmaman_head.dmi and b/icons/mob/clothing/head/plasmaman_head.dmi differ diff --git a/icons/mob/clothing/head/utility.dmi b/icons/mob/clothing/head/utility.dmi index 3f3a668181cb7..51fd3191f1b10 100644 Binary files a/icons/mob/clothing/head/utility.dmi and b/icons/mob/clothing/head/utility.dmi differ diff --git a/icons/mob/clothing/mask.dmi b/icons/mob/clothing/mask.dmi index cbe6bc751c180..4c05979ff2bad 100644 Binary files a/icons/mob/clothing/mask.dmi and b/icons/mob/clothing/mask.dmi differ diff --git a/icons/mob/clothing/modsuit/mod_clothing.dmi b/icons/mob/clothing/modsuit/mod_clothing.dmi index 537b1ffd3ebdd..793c14ce115ff 100644 Binary files a/icons/mob/clothing/modsuit/mod_clothing.dmi and b/icons/mob/clothing/modsuit/mod_clothing.dmi differ diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi index 03cf6a861c140..5440bf9d99dae 100644 Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ diff --git a/icons/mob/clothing/suits/armor.dmi b/icons/mob/clothing/suits/armor.dmi index d10ca4add997b..be2ba8f3730c1 100644 Binary files a/icons/mob/clothing/suits/armor.dmi and b/icons/mob/clothing/suits/armor.dmi differ diff --git a/icons/mob/clothing/suits/chaplain.dmi b/icons/mob/clothing/suits/chaplain.dmi index 4b6368fb291d0..8806bf5f679b4 100644 Binary files a/icons/mob/clothing/suits/chaplain.dmi and b/icons/mob/clothing/suits/chaplain.dmi differ diff --git a/icons/mob/clothing/suits/costume.dmi b/icons/mob/clothing/suits/costume.dmi index bf4bbe310d2d7..3cd5770fb9aee 100644 Binary files a/icons/mob/clothing/suits/costume.dmi and b/icons/mob/clothing/suits/costume.dmi differ diff --git a/icons/mob/clothing/suits/jacket.dmi b/icons/mob/clothing/suits/jacket.dmi index cd924e847eb3e..fa93a85c7c6f1 100644 Binary files a/icons/mob/clothing/suits/jacket.dmi and b/icons/mob/clothing/suits/jacket.dmi differ diff --git a/icons/mob/clothing/suits/labcoat.dmi b/icons/mob/clothing/suits/labcoat.dmi index f499203c95e95..37cb0e8696bdb 100644 Binary files a/icons/mob/clothing/suits/labcoat.dmi and b/icons/mob/clothing/suits/labcoat.dmi differ diff --git a/icons/mob/clothing/suits/spacesuit.dmi b/icons/mob/clothing/suits/spacesuit.dmi index 4bc2d8cd7f9e1..3c381ecd56f94 100644 Binary files a/icons/mob/clothing/suits/spacesuit.dmi and b/icons/mob/clothing/suits/spacesuit.dmi differ diff --git a/icons/mob/clothing/under/cargo.dmi b/icons/mob/clothing/under/cargo.dmi index 4bf30a67a2d7b..180f0e4ec876f 100644 Binary files a/icons/mob/clothing/under/cargo.dmi and b/icons/mob/clothing/under/cargo.dmi differ diff --git a/icons/mob/clothing/under/costume.dmi b/icons/mob/clothing/under/costume.dmi index c8fd87f52726d..26e8eec9f2051 100644 Binary files a/icons/mob/clothing/under/costume.dmi and b/icons/mob/clothing/under/costume.dmi differ diff --git a/icons/mob/clothing/under/plasmaman.dmi b/icons/mob/clothing/under/plasmaman.dmi index 41cbfb4482b48..fcc8f008cd7aa 100644 Binary files a/icons/mob/clothing/under/plasmaman.dmi and b/icons/mob/clothing/under/plasmaman.dmi differ diff --git a/icons/mob/clothing/under/security.dmi b/icons/mob/clothing/under/security.dmi index 58761b3a38e71..b78ffa57f7e7d 100644 Binary files a/icons/mob/clothing/under/security.dmi and b/icons/mob/clothing/under/security.dmi differ diff --git a/icons/mob/clothing/under/suits.dmi b/icons/mob/clothing/under/suits.dmi index e3800e2bd04b8..e8fd6a820d393 100644 Binary files a/icons/mob/clothing/under/suits.dmi and b/icons/mob/clothing/under/suits.dmi differ diff --git a/icons/mob/clothing/under/syndicate.dmi b/icons/mob/clothing/under/syndicate.dmi index 49e866c22104d..57e0cf14a3a76 100644 Binary files a/icons/mob/clothing/under/syndicate.dmi and b/icons/mob/clothing/under/syndicate.dmi differ diff --git a/icons/mob/clothing/underwear.dmi b/icons/mob/clothing/underwear.dmi index d231b955bab40..cd5564fe211f1 100644 Binary files a/icons/mob/clothing/underwear.dmi and b/icons/mob/clothing/underwear.dmi differ diff --git a/icons/mob/effects/debuff_overlays.dmi b/icons/mob/effects/debuff_overlays.dmi new file mode 100644 index 0000000000000..383ce22aabec7 Binary files /dev/null and b/icons/mob/effects/debuff_overlays.dmi differ diff --git a/icons/mob/huds/antag_hud.dmi b/icons/mob/huds/antag_hud.dmi index d8d6dd1ac40d3..aa96f2338b250 100644 Binary files a/icons/mob/huds/antag_hud.dmi and b/icons/mob/huds/antag_hud.dmi differ diff --git a/icons/mob/huds/hud.dmi b/icons/mob/huds/hud.dmi index ec9be118f5792..3dff7642a9613 100644 Binary files a/icons/mob/huds/hud.dmi and b/icons/mob/huds/hud.dmi differ diff --git a/icons/mob/human/bodyparts.dmi b/icons/mob/human/bodyparts.dmi index 7e804e894d213..d6e4472973a32 100644 Binary files a/icons/mob/human/bodyparts.dmi and b/icons/mob/human/bodyparts.dmi differ diff --git a/icons/mob/human/human_face.dmi b/icons/mob/human/human_face.dmi index 6985cf07eee49..d96800c3a8bb8 100644 Binary files a/icons/mob/human/human_face.dmi and b/icons/mob/human/human_face.dmi differ diff --git a/icons/mob/human/species/lizard/lizard_spines.dmi b/icons/mob/human/species/lizard/lizard_spines.dmi index eaadb820ebc1f..5112c8a0a6eca 100644 Binary files a/icons/mob/human/species/lizard/lizard_spines.dmi and b/icons/mob/human/species/lizard/lizard_spines.dmi differ diff --git a/icons/mob/human/species/lizard/lizard_tails.dmi b/icons/mob/human/species/lizard/lizard_tails.dmi index c388e31fd9b89..f27c1dcb3908b 100644 Binary files a/icons/mob/human/species/lizard/lizard_tails.dmi and b/icons/mob/human/species/lizard/lizard_tails.dmi differ diff --git a/icons/mob/human/species/misc/bodypart_overlay_simple.dmi b/icons/mob/human/species/misc/bodypart_overlay_simple.dmi index 55fb5c40a842a..8df76eb63147b 100644 Binary files a/icons/mob/human/species/misc/bodypart_overlay_simple.dmi and b/icons/mob/human/species/misc/bodypart_overlay_simple.dmi differ diff --git a/icons/mob/human/species/monkey/bodyparts.dmi b/icons/mob/human/species/monkey/bodyparts.dmi index 689ef5e63d7e7..59a3a10c26cf2 100644 Binary files a/icons/mob/human/species/monkey/bodyparts.dmi and b/icons/mob/human/species/monkey/bodyparts.dmi differ diff --git a/icons/mob/human/species/monkey/uniform.dmi b/icons/mob/human/species/monkey/uniform.dmi index 21d70e5694f2c..a835629528ae5 100644 Binary files a/icons/mob/human/species/monkey/uniform.dmi and b/icons/mob/human/species/monkey/uniform.dmi differ diff --git a/icons/mob/human/species/wings.dmi b/icons/mob/human/species/wings.dmi index 26e8b011fce2a..45c438efd230d 100644 Binary files a/icons/mob/human/species/wings.dmi and b/icons/mob/human/species/wings.dmi differ diff --git a/icons/mob/inhands/64x64_lefthand.dmi b/icons/mob/inhands/64x64_lefthand.dmi index 8a6bab6977667..5d4d7c9e7689f 100644 Binary files a/icons/mob/inhands/64x64_lefthand.dmi and b/icons/mob/inhands/64x64_lefthand.dmi differ diff --git a/icons/mob/inhands/64x64_righthand.dmi b/icons/mob/inhands/64x64_righthand.dmi index 251458590bdd4..6cd0f2acebc6a 100644 Binary files a/icons/mob/inhands/64x64_righthand.dmi and b/icons/mob/inhands/64x64_righthand.dmi differ diff --git a/icons/mob/inhands/antag/changeling_lefthand.dmi b/icons/mob/inhands/antag/changeling_lefthand.dmi index daf0e2fb3f624..a3723f8dab012 100644 Binary files a/icons/mob/inhands/antag/changeling_lefthand.dmi and b/icons/mob/inhands/antag/changeling_lefthand.dmi differ diff --git a/icons/mob/inhands/antag/changeling_righthand.dmi b/icons/mob/inhands/antag/changeling_righthand.dmi index aa2144c5962b6..d3f320a38a9f1 100644 Binary files a/icons/mob/inhands/antag/changeling_righthand.dmi and b/icons/mob/inhands/antag/changeling_righthand.dmi differ diff --git a/icons/mob/inhands/clothing/hats_lefthand.dmi b/icons/mob/inhands/clothing/hats_lefthand.dmi index 7111ee4d7480b..191c85cf4825c 100644 Binary files a/icons/mob/inhands/clothing/hats_lefthand.dmi and b/icons/mob/inhands/clothing/hats_lefthand.dmi differ diff --git a/icons/mob/inhands/clothing/hats_righthand.dmi b/icons/mob/inhands/clothing/hats_righthand.dmi index 96756fc44db9c..8038e7474ee87 100644 Binary files a/icons/mob/inhands/clothing/hats_righthand.dmi and b/icons/mob/inhands/clothing/hats_righthand.dmi differ diff --git a/icons/mob/inhands/clothing/suits_lefthand.dmi b/icons/mob/inhands/clothing/suits_lefthand.dmi index 757fb8b8593c7..8b9fa5256a932 100644 Binary files a/icons/mob/inhands/clothing/suits_lefthand.dmi and b/icons/mob/inhands/clothing/suits_lefthand.dmi differ diff --git a/icons/mob/inhands/clothing/suits_righthand.dmi b/icons/mob/inhands/clothing/suits_righthand.dmi index c749a2ed98a8e..c88f4d224444f 100644 Binary files a/icons/mob/inhands/clothing/suits_righthand.dmi and b/icons/mob/inhands/clothing/suits_righthand.dmi differ diff --git a/icons/mob/inhands/equipment/custodial_lefthand.dmi b/icons/mob/inhands/equipment/custodial_lefthand.dmi index 8af03cfdf640a..2350998aa9535 100644 Binary files a/icons/mob/inhands/equipment/custodial_lefthand.dmi and b/icons/mob/inhands/equipment/custodial_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/custodial_righthand.dmi b/icons/mob/inhands/equipment/custodial_righthand.dmi index b3eecf85e70f3..df4738e3fd298 100644 Binary files a/icons/mob/inhands/equipment/custodial_righthand.dmi and b/icons/mob/inhands/equipment/custodial_righthand.dmi differ diff --git a/icons/mob/inhands/equipment/hydroponics_lefthand.dmi b/icons/mob/inhands/equipment/hydroponics_lefthand.dmi index 031909dbf8240..5f771404f2636 100644 Binary files a/icons/mob/inhands/equipment/hydroponics_lefthand.dmi and b/icons/mob/inhands/equipment/hydroponics_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/hydroponics_righthand.dmi b/icons/mob/inhands/equipment/hydroponics_righthand.dmi index a2cac9c47f307..11b9195203272 100644 Binary files a/icons/mob/inhands/equipment/hydroponics_righthand.dmi and b/icons/mob/inhands/equipment/hydroponics_righthand.dmi differ diff --git a/icons/mob/inhands/equipment/instruments_lefthand.dmi b/icons/mob/inhands/equipment/instruments_lefthand.dmi index edd78927aa681..2fb076293d529 100644 Binary files a/icons/mob/inhands/equipment/instruments_lefthand.dmi and b/icons/mob/inhands/equipment/instruments_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/instruments_righthand.dmi b/icons/mob/inhands/equipment/instruments_righthand.dmi index 7cc8568b908d4..f989cec435d98 100644 Binary files a/icons/mob/inhands/equipment/instruments_righthand.dmi and b/icons/mob/inhands/equipment/instruments_righthand.dmi differ diff --git a/icons/mob/inhands/equipment/medical_lefthand.dmi b/icons/mob/inhands/equipment/medical_lefthand.dmi index 7ce3674c86aad..feaed1690786e 100644 Binary files a/icons/mob/inhands/equipment/medical_lefthand.dmi and b/icons/mob/inhands/equipment/medical_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/medical_righthand.dmi b/icons/mob/inhands/equipment/medical_righthand.dmi index 6ceb5efe4d83c..15ccf5c090e59 100644 Binary files a/icons/mob/inhands/equipment/medical_righthand.dmi and b/icons/mob/inhands/equipment/medical_righthand.dmi differ diff --git a/icons/mob/inhands/equipment/shields_lefthand.dmi b/icons/mob/inhands/equipment/shields_lefthand.dmi index 4075fb26a965e..d31dbb3f830b1 100644 Binary files a/icons/mob/inhands/equipment/shields_lefthand.dmi and b/icons/mob/inhands/equipment/shields_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/shields_righthand.dmi b/icons/mob/inhands/equipment/shields_righthand.dmi index 73127fb026b83..dfd72809be71f 100644 Binary files a/icons/mob/inhands/equipment/shields_righthand.dmi and b/icons/mob/inhands/equipment/shields_righthand.dmi differ diff --git a/icons/mob/inhands/items/bedsheet_lefthand.dmi b/icons/mob/inhands/items/bedsheet_lefthand.dmi index 1f2d7df00753d..2795277a183ca 100644 Binary files a/icons/mob/inhands/items/bedsheet_lefthand.dmi and b/icons/mob/inhands/items/bedsheet_lefthand.dmi differ diff --git a/icons/mob/inhands/items/bedsheet_righthand.dmi b/icons/mob/inhands/items/bedsheet_righthand.dmi index 5c831140c9eab..4fe73af823a4c 100644 Binary files a/icons/mob/inhands/items/bedsheet_righthand.dmi and b/icons/mob/inhands/items/bedsheet_righthand.dmi differ diff --git a/icons/mob/inhands/items/devices_lefthand.dmi b/icons/mob/inhands/items/devices_lefthand.dmi index bf9c3154c62b1..7b558c20e6e55 100644 Binary files a/icons/mob/inhands/items/devices_lefthand.dmi and b/icons/mob/inhands/items/devices_lefthand.dmi differ diff --git a/icons/mob/inhands/items/devices_righthand.dmi b/icons/mob/inhands/items/devices_righthand.dmi index 93a5d92961043..042ecb745c485 100644 Binary files a/icons/mob/inhands/items/devices_righthand.dmi and b/icons/mob/inhands/items/devices_righthand.dmi differ diff --git a/icons/mob/inhands/items/food_lefthand.dmi b/icons/mob/inhands/items/food_lefthand.dmi index 934c2fcd04583..8e2b19c4dd42a 100644 Binary files a/icons/mob/inhands/items/food_lefthand.dmi and b/icons/mob/inhands/items/food_lefthand.dmi differ diff --git a/icons/mob/inhands/items/food_righthand.dmi b/icons/mob/inhands/items/food_righthand.dmi index f49835a3a7bac..12063ad38a8f9 100644 Binary files a/icons/mob/inhands/items/food_righthand.dmi and b/icons/mob/inhands/items/food_righthand.dmi differ diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi index f544eeb753a81..a2347dc667abc 100644 Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi index 85c6accc80c36..bb2a425194bd2 100644 Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ diff --git a/icons/mob/inhands/weapons/guns_lefthand.dmi b/icons/mob/inhands/weapons/guns_lefthand.dmi index 013e6e4745854..7e8297c848d3d 100644 Binary files a/icons/mob/inhands/weapons/guns_lefthand.dmi and b/icons/mob/inhands/weapons/guns_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/guns_righthand.dmi b/icons/mob/inhands/weapons/guns_righthand.dmi index 14bf9e762c753..366f285789a0a 100644 Binary files a/icons/mob/inhands/weapons/guns_righthand.dmi and b/icons/mob/inhands/weapons/guns_righthand.dmi differ diff --git a/icons/mob/inhands/weapons/hammers_lefthand.dmi b/icons/mob/inhands/weapons/hammers_lefthand.dmi index 090bcb2ab9e0a..7f6985e1de5ec 100644 Binary files a/icons/mob/inhands/weapons/hammers_lefthand.dmi and b/icons/mob/inhands/weapons/hammers_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/hammers_righthand.dmi b/icons/mob/inhands/weapons/hammers_righthand.dmi index 3b0af0743f8d1..2dd4f4b64c5cf 100644 Binary files a/icons/mob/inhands/weapons/hammers_righthand.dmi and b/icons/mob/inhands/weapons/hammers_righthand.dmi differ diff --git a/icons/mob/inhands/weapons/polearms_lefthand.dmi b/icons/mob/inhands/weapons/polearms_lefthand.dmi index ff98ba9572153..f9b43790df73f 100644 Binary files a/icons/mob/inhands/weapons/polearms_lefthand.dmi and b/icons/mob/inhands/weapons/polearms_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/polearms_righthand.dmi b/icons/mob/inhands/weapons/polearms_righthand.dmi index db760ec717535..9f7e623989588 100644 Binary files a/icons/mob/inhands/weapons/polearms_righthand.dmi and b/icons/mob/inhands/weapons/polearms_righthand.dmi differ diff --git a/icons/mob/inhands/weapons/staves_lefthand.dmi b/icons/mob/inhands/weapons/staves_lefthand.dmi index 6e34949c9d561..83504696b61a3 100644 Binary files a/icons/mob/inhands/weapons/staves_lefthand.dmi and b/icons/mob/inhands/weapons/staves_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/staves_righthand.dmi b/icons/mob/inhands/weapons/staves_righthand.dmi index df52d4d419876..c83e0eed3abee 100644 Binary files a/icons/mob/inhands/weapons/staves_righthand.dmi and b/icons/mob/inhands/weapons/staves_righthand.dmi differ diff --git a/icons/mob/inhands/weapons/swords_lefthand.dmi b/icons/mob/inhands/weapons/swords_lefthand.dmi index 98a037e5c8ea0..e0a33fbcee351 100644 Binary files a/icons/mob/inhands/weapons/swords_lefthand.dmi and b/icons/mob/inhands/weapons/swords_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/swords_righthand.dmi b/icons/mob/inhands/weapons/swords_righthand.dmi index b60f65194d57a..fcdac64bdca49 100644 Binary files a/icons/mob/inhands/weapons/swords_righthand.dmi and b/icons/mob/inhands/weapons/swords_righthand.dmi differ diff --git a/icons/mob/landmarks.dmi b/icons/mob/landmarks.dmi index f3ba54fe9b045..4f2402389df9f 100644 Binary files a/icons/mob/landmarks.dmi and b/icons/mob/landmarks.dmi differ diff --git a/icons/mob/large-worn-icons/64x64/head.dmi b/icons/mob/large-worn-icons/64x64/head.dmi index ac6163ddbed8a..86cf60afef3cb 100644 Binary files a/icons/mob/large-worn-icons/64x64/head.dmi and b/icons/mob/large-worn-icons/64x64/head.dmi differ diff --git a/icons/mob/mecha.dmi b/icons/mob/mecha.dmi index 845b1cdf140da..f6dcbdec2b7ee 100644 Binary files a/icons/mob/mecha.dmi and b/icons/mob/mecha.dmi differ diff --git a/icons/mob/mecha_equipment.dmi b/icons/mob/mecha_equipment.dmi index 18fe707cafe6e..90f0ce8c736cf 100644 Binary files a/icons/mob/mecha_equipment.dmi and b/icons/mob/mecha_equipment.dmi differ diff --git a/icons/mob/nonhuman-player/alien.dmi b/icons/mob/nonhuman-player/alien.dmi index 290df2e263041..db04cdf8674b4 100644 Binary files a/icons/mob/nonhuman-player/alien.dmi and b/icons/mob/nonhuman-player/alien.dmi differ diff --git a/icons/mob/nonhuman-player/alienleap.dmi b/icons/mob/nonhuman-player/alienleap.dmi index 7e10b8a5c5bfa..551b664c1b251 100644 Binary files a/icons/mob/nonhuman-player/alienleap.dmi and b/icons/mob/nonhuman-player/alienleap.dmi differ diff --git a/icons/mob/nonhuman-player/eldritch_mobs.dmi b/icons/mob/nonhuman-player/eldritch_mobs.dmi index 4e640694b2a38..18e50d727aee5 100644 Binary files a/icons/mob/nonhuman-player/eldritch_mobs.dmi and b/icons/mob/nonhuman-player/eldritch_mobs.dmi differ diff --git a/icons/mob/nonhuman-player/netguardian.dmi b/icons/mob/nonhuman-player/netguardian.dmi new file mode 100644 index 0000000000000..057e7a066c2be Binary files /dev/null and b/icons/mob/nonhuman-player/netguardian.dmi differ diff --git a/icons/mob/pets.dmi b/icons/mob/pets.dmi index 7c6800d602de3..8ddeaa0c3f40c 100644 Binary files a/icons/mob/pets.dmi and b/icons/mob/pets.dmi differ diff --git a/icons/mob/silicon/aibots.dmi b/icons/mob/silicon/aibots.dmi index c87a5916c025e..8949caa0299bc 100644 Binary files a/icons/mob/silicon/aibots.dmi and b/icons/mob/silicon/aibots.dmi differ diff --git a/icons/mob/silicon/pai.dmi b/icons/mob/silicon/pai.dmi index 624ed66951948..2be986d411dbe 100644 Binary files a/icons/mob/silicon/pai.dmi and b/icons/mob/silicon/pai.dmi differ diff --git a/icons/mob/simple/animal.dmi b/icons/mob/simple/animal.dmi index 8e3affff4a90a..7fcf0e9d65e79 100644 Binary files a/icons/mob/simple/animal.dmi and b/icons/mob/simple/animal.dmi differ diff --git a/icons/mob/simple/arachnoid.dmi b/icons/mob/simple/arachnoid.dmi index fca53195d4c06..d17297f2ccf57 100644 Binary files a/icons/mob/simple/arachnoid.dmi and b/icons/mob/simple/arachnoid.dmi differ diff --git a/icons/mob/simple/jungle/leaper.dmi b/icons/mob/simple/jungle/leaper.dmi index 39f807a191c2e..2150c7b1ac2cf 100644 Binary files a/icons/mob/simple/jungle/leaper.dmi and b/icons/mob/simple/jungle/leaper.dmi differ diff --git a/icons/mob/simple/jungle/mook.dmi b/icons/mob/simple/jungle/mook.dmi index c9265b22a0ad2..fbc38d29d99de 100644 Binary files a/icons/mob/simple/jungle/mook.dmi and b/icons/mob/simple/jungle/mook.dmi differ diff --git a/icons/mob/simple/jungle/seedling.dmi b/icons/mob/simple/jungle/seedling.dmi index 01e91c6c292c2..c4a76ebb2d1c3 100644 Binary files a/icons/mob/simple/jungle/seedling.dmi and b/icons/mob/simple/jungle/seedling.dmi differ diff --git a/icons/mob/simple/lavaland/lavaland_monsters.dmi b/icons/mob/simple/lavaland/lavaland_monsters.dmi index 13c37dca594f0..ffcfb04cbeb5d 100644 Binary files a/icons/mob/simple/lavaland/lavaland_monsters.dmi and b/icons/mob/simple/lavaland/lavaland_monsters.dmi differ diff --git a/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi b/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi index 2be68ef4c6696..808fdc59d9bae 100644 Binary files a/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi and b/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi differ diff --git a/icons/mob/simple/pets.dmi b/icons/mob/simple/pets.dmi index 78212b93c769e..9bd7d69c06bc5 100644 Binary files a/icons/mob/simple/pets.dmi and b/icons/mob/simple/pets.dmi differ diff --git a/icons/mob/simple/traders.dmi b/icons/mob/simple/traders.dmi deleted file mode 100644 index 2f2b828bed911..0000000000000 Binary files a/icons/mob/simple/traders.dmi and /dev/null differ diff --git a/icons/mob/telegraphing/telegraph.dmi b/icons/mob/telegraphing/telegraph.dmi index d5e03419cd898..de525ead4ee9a 100644 Binary files a/icons/mob/telegraphing/telegraph.dmi and b/icons/mob/telegraphing/telegraph.dmi differ diff --git a/icons/obj/antags/contractor_tablet.dmi b/icons/obj/antags/contractor_tablet.dmi deleted file mode 100644 index ae3cb579e2b1a..0000000000000 Binary files a/icons/obj/antags/contractor_tablet.dmi and /dev/null differ diff --git a/icons/obj/antags/eldritch.dmi b/icons/obj/antags/eldritch.dmi index d59bf3dbdeb18..7f6af6bfe2e65 100644 Binary files a/icons/obj/antags/eldritch.dmi and b/icons/obj/antags/eldritch.dmi differ diff --git a/icons/obj/aquarium.dmi b/icons/obj/aquarium.dmi index 3a27c83c906a8..aa37c035791e9 100644 Binary files a/icons/obj/aquarium.dmi and b/icons/obj/aquarium.dmi differ diff --git a/icons/obj/art/musician.dmi b/icons/obj/art/musician.dmi index 6f98eb0d7b0a6..df48e54e6c61e 100644 Binary files a/icons/obj/art/musician.dmi and b/icons/obj/art/musician.dmi differ diff --git a/icons/obj/assemblies/module.dmi b/icons/obj/assemblies/module.dmi deleted file mode 100644 index 2656c8db43279..0000000000000 Binary files a/icons/obj/assemblies/module.dmi and /dev/null differ diff --git a/icons/obj/assemblies/new_assemblies.dmi b/icons/obj/assemblies/new_assemblies.dmi deleted file mode 100644 index c3635aeb6d9f7..0000000000000 Binary files a/icons/obj/assemblies/new_assemblies.dmi and /dev/null differ diff --git a/icons/obj/assemblies/stock_parts.dmi b/icons/obj/assemblies/stock_parts.dmi deleted file mode 100644 index 6b2353b80ddb5..0000000000000 Binary files a/icons/obj/assemblies/stock_parts.dmi and /dev/null differ diff --git a/icons/obj/bedsheets.dmi b/icons/obj/bedsheets.dmi index 8db48b45fc684..daa0c3cdd7904 100644 Binary files a/icons/obj/bedsheets.dmi and b/icons/obj/bedsheets.dmi differ diff --git a/icons/obj/canisters.dmi b/icons/obj/canisters.dmi index e57c6ad9b963f..277833976adbb 100644 Binary files a/icons/obj/canisters.dmi and b/icons/obj/canisters.dmi differ diff --git a/icons/obj/card.dmi b/icons/obj/card.dmi index a5c4e8283010b..6397cf6fb5b5a 100644 Binary files a/icons/obj/card.dmi and b/icons/obj/card.dmi differ diff --git a/icons/obj/clothing/accessories.dmi b/icons/obj/clothing/accessories.dmi index 2711333118ab6..0253485cec327 100644 Binary files a/icons/obj/clothing/accessories.dmi and b/icons/obj/clothing/accessories.dmi differ diff --git a/icons/obj/clothing/belt_overlays.dmi b/icons/obj/clothing/belt_overlays.dmi index 740a832a023ee..7a215dcb9b1cc 100644 Binary files a/icons/obj/clothing/belt_overlays.dmi and b/icons/obj/clothing/belt_overlays.dmi differ diff --git a/icons/obj/clothing/belts.dmi b/icons/obj/clothing/belts.dmi index 0a2bd33c4e42a..5ccdf2c186f89 100644 Binary files a/icons/obj/clothing/belts.dmi and b/icons/obj/clothing/belts.dmi differ diff --git a/icons/obj/clothing/glasses.dmi b/icons/obj/clothing/glasses.dmi index 20f24dd22402f..fd898d3105fd8 100644 Binary files a/icons/obj/clothing/glasses.dmi and b/icons/obj/clothing/glasses.dmi differ diff --git a/icons/obj/clothing/head/chaplain.dmi b/icons/obj/clothing/head/chaplain.dmi index d95436fdd2d7a..ed6f6248b317c 100644 Binary files a/icons/obj/clothing/head/chaplain.dmi and b/icons/obj/clothing/head/chaplain.dmi differ diff --git a/icons/obj/clothing/head/costume.dmi b/icons/obj/clothing/head/costume.dmi index f676b1c6973d5..3cfbd3d21ef4f 100644 Binary files a/icons/obj/clothing/head/costume.dmi and b/icons/obj/clothing/head/costume.dmi differ diff --git a/icons/obj/clothing/head/cowboy.dmi b/icons/obj/clothing/head/cowboy.dmi index 66c0767294101..1a330eba0f801 100644 Binary files a/icons/obj/clothing/head/cowboy.dmi and b/icons/obj/clothing/head/cowboy.dmi differ diff --git a/icons/obj/clothing/head/hats.dmi b/icons/obj/clothing/head/hats.dmi index 15117fa69a835..6cca3da61f41a 100644 Binary files a/icons/obj/clothing/head/hats.dmi and b/icons/obj/clothing/head/hats.dmi differ diff --git a/icons/obj/clothing/head/plasmaman_hats.dmi b/icons/obj/clothing/head/plasmaman_hats.dmi index adcf9129c4583..f593a08b88c30 100644 Binary files a/icons/obj/clothing/head/plasmaman_hats.dmi and b/icons/obj/clothing/head/plasmaman_hats.dmi differ diff --git a/icons/obj/clothing/head/utility.dmi b/icons/obj/clothing/head/utility.dmi index 9571b2add7865..17040f5bb8b53 100644 Binary files a/icons/obj/clothing/head/utility.dmi and b/icons/obj/clothing/head/utility.dmi differ diff --git a/icons/obj/clothing/headsets.dmi b/icons/obj/clothing/headsets.dmi new file mode 100644 index 0000000000000..b977487e2c6ce Binary files /dev/null and b/icons/obj/clothing/headsets.dmi differ diff --git a/icons/obj/clothing/masks.dmi b/icons/obj/clothing/masks.dmi index 20951b3264f08..1dd2ac7d71c7e 100644 Binary files a/icons/obj/clothing/masks.dmi and b/icons/obj/clothing/masks.dmi differ diff --git a/icons/obj/clothing/modsuit/mod_clothing.dmi b/icons/obj/clothing/modsuit/mod_clothing.dmi index 7eb92f1c3519c..5070dbb145c9c 100644 Binary files a/icons/obj/clothing/modsuit/mod_clothing.dmi and b/icons/obj/clothing/modsuit/mod_clothing.dmi differ diff --git a/icons/obj/clothing/modsuit/mod_modules.dmi b/icons/obj/clothing/modsuit/mod_modules.dmi index f1d19c29da119..eb4b0c536ce3c 100644 Binary files a/icons/obj/clothing/modsuit/mod_modules.dmi and b/icons/obj/clothing/modsuit/mod_modules.dmi differ diff --git a/icons/obj/clothing/neck.dmi b/icons/obj/clothing/neck.dmi index ff7e012324a0a..6027b0022c45d 100644 Binary files a/icons/obj/clothing/neck.dmi and b/icons/obj/clothing/neck.dmi differ diff --git a/icons/obj/clothing/suits/armor.dmi b/icons/obj/clothing/suits/armor.dmi index 48fe5c92c339d..5ff4e25df8636 100644 Binary files a/icons/obj/clothing/suits/armor.dmi and b/icons/obj/clothing/suits/armor.dmi differ diff --git a/icons/obj/clothing/suits/chaplain.dmi b/icons/obj/clothing/suits/chaplain.dmi index 64474a04d3182..730e47cd6fa94 100644 Binary files a/icons/obj/clothing/suits/chaplain.dmi and b/icons/obj/clothing/suits/chaplain.dmi differ diff --git a/icons/obj/clothing/suits/costume.dmi b/icons/obj/clothing/suits/costume.dmi index 0c08b97605db3..ad68aea553f7f 100644 Binary files a/icons/obj/clothing/suits/costume.dmi and b/icons/obj/clothing/suits/costume.dmi differ diff --git a/icons/obj/clothing/suits/jacket.dmi b/icons/obj/clothing/suits/jacket.dmi index c63f262f10494..30e1a99d9eed4 100644 Binary files a/icons/obj/clothing/suits/jacket.dmi and b/icons/obj/clothing/suits/jacket.dmi differ diff --git a/icons/obj/clothing/suits/labcoat.dmi b/icons/obj/clothing/suits/labcoat.dmi index fa404813e40dc..430d11d5f96ab 100644 Binary files a/icons/obj/clothing/suits/labcoat.dmi and b/icons/obj/clothing/suits/labcoat.dmi differ diff --git a/icons/obj/clothing/suits/spacesuit.dmi b/icons/obj/clothing/suits/spacesuit.dmi index de9c1242767c9..84f84ac978e85 100644 Binary files a/icons/obj/clothing/suits/spacesuit.dmi and b/icons/obj/clothing/suits/spacesuit.dmi differ diff --git a/icons/obj/clothing/under/cargo.dmi b/icons/obj/clothing/under/cargo.dmi index fc04a897d5ea8..63e40538899f7 100644 Binary files a/icons/obj/clothing/under/cargo.dmi and b/icons/obj/clothing/under/cargo.dmi differ diff --git a/icons/obj/clothing/under/costume.dmi b/icons/obj/clothing/under/costume.dmi index bf60bb0a4452d..b58f96b82d9b2 100644 Binary files a/icons/obj/clothing/under/costume.dmi and b/icons/obj/clothing/under/costume.dmi differ diff --git a/icons/obj/clothing/under/plasmaman.dmi b/icons/obj/clothing/under/plasmaman.dmi index 4277c43d54b40..4d416d5b05f1f 100644 Binary files a/icons/obj/clothing/under/plasmaman.dmi and b/icons/obj/clothing/under/plasmaman.dmi differ diff --git a/icons/obj/clothing/under/security.dmi b/icons/obj/clothing/under/security.dmi index 075e1b5d087d0..35245bec6fa0a 100644 Binary files a/icons/obj/clothing/under/security.dmi and b/icons/obj/clothing/under/security.dmi differ diff --git a/icons/obj/debris.dmi b/icons/obj/debris.dmi index d256d1ddd6ed7..10b73560cbb17 100644 Binary files a/icons/obj/debris.dmi and b/icons/obj/debris.dmi differ diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi deleted file mode 100644 index d89ee6e5d6408..0000000000000 Binary files a/icons/obj/device.dmi and /dev/null differ diff --git a/icons/obj/assemblies/assemblies.dmi b/icons/obj/devices/assemblies.dmi similarity index 100% rename from icons/obj/assemblies/assemblies.dmi rename to icons/obj/devices/assemblies.dmi diff --git a/icons/obj/devices/circuitry_n_data.dmi b/icons/obj/devices/circuitry_n_data.dmi new file mode 100644 index 0000000000000..6a12910283efa Binary files /dev/null and b/icons/obj/devices/circuitry_n_data.dmi differ diff --git a/icons/obj/devices/flash.dmi b/icons/obj/devices/flash.dmi new file mode 100644 index 0000000000000..27500944aed0a Binary files /dev/null and b/icons/obj/devices/flash.dmi differ diff --git a/icons/obj/devices/gunmod.dmi b/icons/obj/devices/gunmod.dmi new file mode 100644 index 0000000000000..593facf46acf2 Binary files /dev/null and b/icons/obj/devices/gunmod.dmi differ diff --git a/icons/obj/devices/new_assemblies.dmi b/icons/obj/devices/new_assemblies.dmi new file mode 100644 index 0000000000000..411ad8b61df64 Binary files /dev/null and b/icons/obj/devices/new_assemblies.dmi differ diff --git a/icons/obj/devices/remote.dmi b/icons/obj/devices/remote.dmi new file mode 100644 index 0000000000000..7b1e07462ff45 Binary files /dev/null and b/icons/obj/devices/remote.dmi differ diff --git a/icons/obj/devices/scanner.dmi b/icons/obj/devices/scanner.dmi new file mode 100644 index 0000000000000..0b4a5ef4684e1 Binary files /dev/null and b/icons/obj/devices/scanner.dmi differ diff --git a/icons/obj/devices/stock_parts.dmi b/icons/obj/devices/stock_parts.dmi new file mode 100644 index 0000000000000..d249b0ab23471 Binary files /dev/null and b/icons/obj/devices/stock_parts.dmi differ diff --git a/icons/obj/devices/syndie_gadget.dmi b/icons/obj/devices/syndie_gadget.dmi new file mode 100644 index 0000000000000..4b8670ca7777a Binary files /dev/null and b/icons/obj/devices/syndie_gadget.dmi differ diff --git a/icons/obj/devices/tool.dmi b/icons/obj/devices/tool.dmi new file mode 100644 index 0000000000000..0d89f296761be Binary files /dev/null and b/icons/obj/devices/tool.dmi differ diff --git a/icons/obj/devices/tracker.dmi b/icons/obj/devices/tracker.dmi new file mode 100644 index 0000000000000..e9b3574bd4f76 Binary files /dev/null and b/icons/obj/devices/tracker.dmi differ diff --git a/icons/obj/devices/voice.dmi b/icons/obj/devices/voice.dmi new file mode 100644 index 0000000000000..1e875d9323038 Binary files /dev/null and b/icons/obj/devices/voice.dmi differ diff --git a/icons/obj/doors/airlocks/tram/tram-overlays.dmi b/icons/obj/doors/airlocks/tram/tram-overlays.dmi new file mode 100644 index 0000000000000..879c920686d5e Binary files /dev/null and b/icons/obj/doors/airlocks/tram/tram-overlays.dmi differ diff --git a/icons/obj/doors/airlocks/tram/tram.dmi b/icons/obj/doors/airlocks/tram/tram.dmi new file mode 100644 index 0000000000000..43d30b58a6f45 Binary files /dev/null and b/icons/obj/doors/airlocks/tram/tram.dmi differ diff --git a/icons/obj/doors/puzzledoor/danger.dmi b/icons/obj/doors/puzzledoor/danger.dmi new file mode 100644 index 0000000000000..89d19131cc189 Binary files /dev/null and b/icons/obj/doors/puzzledoor/danger.dmi differ diff --git a/icons/obj/doors/puzzledoor/default.dmi b/icons/obj/doors/puzzledoor/default.dmi index ec4fbed514bfa..49a9206139580 100644 Binary files a/icons/obj/doors/puzzledoor/default.dmi and b/icons/obj/doors/puzzledoor/default.dmi differ diff --git a/icons/obj/doors/tramdoor.dmi b/icons/obj/doors/tramdoor.dmi deleted file mode 100644 index 8ca8c9aee499e..0000000000000 Binary files a/icons/obj/doors/tramdoor.dmi and /dev/null differ diff --git a/icons/obj/economy.dmi b/icons/obj/economy.dmi index dc90265b6e901..04abc41cae175 100644 Binary files a/icons/obj/economy.dmi and b/icons/obj/economy.dmi differ diff --git a/icons/obj/exploration.dmi b/icons/obj/exploration.dmi index 2f9d004bee2c3..b7224d2df84d9 100644 Binary files a/icons/obj/exploration.dmi and b/icons/obj/exploration.dmi differ diff --git a/icons/obj/fishing.dmi b/icons/obj/fishing.dmi index 39bcc85344200..8e8be783fb7a9 100644 Binary files a/icons/obj/fishing.dmi and b/icons/obj/fishing.dmi differ diff --git a/icons/obj/fluff/containers.dmi b/icons/obj/fluff/containers.dmi index af9abe7073e4e..8aed1ffbb9a99 100644 Binary files a/icons/obj/fluff/containers.dmi and b/icons/obj/fluff/containers.dmi differ diff --git a/icons/obj/fluff/general.dmi b/icons/obj/fluff/general.dmi index 3f990111c3c74..1aa7ae5c89857 100644 Binary files a/icons/obj/fluff/general.dmi and b/icons/obj/fluff/general.dmi differ diff --git a/icons/obj/fluff/tram_rails.dmi b/icons/obj/fluff/tram_rails.dmi deleted file mode 100644 index 359fc5f783800..0000000000000 Binary files a/icons/obj/fluff/tram_rails.dmi and /dev/null differ diff --git a/icons/obj/food/egg.dmi b/icons/obj/food/egg.dmi index c7661fca918f0..58908d8247913 100644 Binary files a/icons/obj/food/egg.dmi and b/icons/obj/food/egg.dmi differ diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi index 97ded07df214b..c4d93c23b0b5d 100644 Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ diff --git a/icons/obj/food/frozen_treats.dmi b/icons/obj/food/frozen_treats.dmi index b5b91520e8ca6..5c27454bd3f51 100644 Binary files a/icons/obj/food/frozen_treats.dmi and b/icons/obj/food/frozen_treats.dmi differ diff --git a/icons/obj/food/lizard.dmi b/icons/obj/food/lizard.dmi index f404218aa7a60..15bed265e2f41 100644 Binary files a/icons/obj/food/lizard.dmi and b/icons/obj/food/lizard.dmi differ diff --git a/icons/obj/food/meat.dmi b/icons/obj/food/meat.dmi index dec295bcbf0b0..66761ee20298d 100644 Binary files a/icons/obj/food/meat.dmi and b/icons/obj/food/meat.dmi differ diff --git a/icons/obj/food/moth.dmi b/icons/obj/food/moth.dmi index fd6db22c30603..e3df78e65e613 100644 Binary files a/icons/obj/food/moth.dmi and b/icons/obj/food/moth.dmi differ diff --git a/icons/obj/food/piecake.dmi b/icons/obj/food/piecake.dmi index 9a122e00ef7df..8474ba29fe9f8 100644 Binary files a/icons/obj/food/piecake.dmi and b/icons/obj/food/piecake.dmi differ diff --git a/icons/obj/items_cyborg.dmi b/icons/obj/items_cyborg.dmi index aefea9fddceb7..f43d96153138c 100644 Binary files a/icons/obj/items_cyborg.dmi and b/icons/obj/items_cyborg.dmi differ diff --git a/icons/obj/lighting.dmi b/icons/obj/lighting.dmi index 061099defd4cd..2bda7341e518f 100644 Binary files a/icons/obj/lighting.dmi and b/icons/obj/lighting.dmi differ diff --git a/icons/obj/machines/atmospherics/binary_devices.dmi b/icons/obj/machines/atmospherics/binary_devices.dmi index 407e9fc94f444..96528d420c2ed 100644 Binary files a/icons/obj/machines/atmospherics/binary_devices.dmi and b/icons/obj/machines/atmospherics/binary_devices.dmi differ diff --git a/icons/obj/machines/atmospherics/unary_devices.dmi b/icons/obj/machines/atmospherics/unary_devices.dmi index 3350500fde950..47acf3c29c9c4 100644 Binary files a/icons/obj/machines/atmospherics/unary_devices.dmi and b/icons/obj/machines/atmospherics/unary_devices.dmi differ diff --git a/icons/obj/machines/barsigns.dmi b/icons/obj/machines/barsigns.dmi index c9450a009a152..fa8a3e13252f4 100644 Binary files a/icons/obj/machines/barsigns.dmi and b/icons/obj/machines/barsigns.dmi differ diff --git a/icons/obj/machines/beacon.dmi b/icons/obj/machines/beacon.dmi new file mode 100644 index 0000000000000..8cdf250731966 Binary files /dev/null and b/icons/obj/machines/beacon.dmi differ diff --git a/icons/obj/machines/bepis.dmi b/icons/obj/machines/bepis.dmi deleted file mode 100644 index f348c2e1b0559..0000000000000 Binary files a/icons/obj/machines/bepis.dmi and /dev/null differ diff --git a/icons/obj/machines/bitrunning.dmi b/icons/obj/machines/bitrunning.dmi new file mode 100644 index 0000000000000..d61e910d195be Binary files /dev/null and b/icons/obj/machines/bitrunning.dmi differ diff --git a/icons/obj/machines/computer.dmi b/icons/obj/machines/computer.dmi index 10974f97bac68..aae3a83a45562 100644 Binary files a/icons/obj/machines/computer.dmi and b/icons/obj/machines/computer.dmi differ diff --git a/icons/obj/machines/crossing_signal.dmi b/icons/obj/machines/crossing_signal.dmi deleted file mode 100644 index 4e31966f7e43d..0000000000000 Binary files a/icons/obj/machines/crossing_signal.dmi and /dev/null differ diff --git a/icons/obj/machines/engine/supermatter.dmi b/icons/obj/machines/engine/supermatter.dmi index 874773ef0859b..12b6aff39f638 100644 Binary files a/icons/obj/machines/engine/supermatter.dmi and b/icons/obj/machines/engine/supermatter.dmi differ diff --git a/icons/obj/machines/floor.dmi b/icons/obj/machines/floor.dmi index 6f858465dcdcb..b4b0b8881b20b 100644 Binary files a/icons/obj/machines/floor.dmi and b/icons/obj/machines/floor.dmi differ diff --git a/icons/obj/machines/lift_indicator.dmi b/icons/obj/machines/lift_indicator.dmi index 878606ae48962..24983cf5dae9c 100644 Binary files a/icons/obj/machines/lift_indicator.dmi and b/icons/obj/machines/lift_indicator.dmi differ diff --git a/icons/obj/machines/microwave.dmi b/icons/obj/machines/microwave.dmi index 427a5d9daaee6..7a72ba5d3dca3 100644 Binary files a/icons/obj/machines/microwave.dmi and b/icons/obj/machines/microwave.dmi differ diff --git a/icons/obj/machines/mining_machines.dmi b/icons/obj/machines/mining_machines.dmi index d6fc1afd71688..91f7f434ad34d 100644 Binary files a/icons/obj/machines/mining_machines.dmi and b/icons/obj/machines/mining_machines.dmi differ diff --git a/icons/obj/machines/modular_console.dmi b/icons/obj/machines/modular_console.dmi index 86e3d7139579d..2677dbb71220a 100644 Binary files a/icons/obj/machines/modular_console.dmi and b/icons/obj/machines/modular_console.dmi differ diff --git a/icons/obj/machines/research.dmi b/icons/obj/machines/research.dmi index 02d848eb4e9da..544054279e97a 100644 Binary files a/icons/obj/machines/research.dmi and b/icons/obj/machines/research.dmi differ diff --git a/icons/obj/machines/shield_generator.dmi b/icons/obj/machines/shield_generator.dmi index 51ef5676c6b9e..3a68da9b58259 100644 Binary files a/icons/obj/machines/shield_generator.dmi and b/icons/obj/machines/shield_generator.dmi differ diff --git a/icons/obj/machines/tram_sign.dmi b/icons/obj/machines/tram_sign.dmi deleted file mode 100644 index 1043153c4e3b1..0000000000000 Binary files a/icons/obj/machines/tram_sign.dmi and /dev/null differ diff --git a/icons/obj/machines/wallmounts.dmi b/icons/obj/machines/wallmounts.dmi index 39e4cc2476b58..12a9c8e418f08 100644 Binary files a/icons/obj/machines/wallmounts.dmi and b/icons/obj/machines/wallmounts.dmi differ diff --git a/icons/obj/medical/cryogenics.dmi b/icons/obj/medical/cryogenics.dmi index bd82d4bbacee0..4dd33af8ce7b4 100644 Binary files a/icons/obj/medical/cryogenics.dmi and b/icons/obj/medical/cryogenics.dmi differ diff --git a/icons/obj/medical/organs/mining_organs.dmi b/icons/obj/medical/organs/mining_organs.dmi index f3fc298284ba6..172f94001ffaf 100644 Binary files a/icons/obj/medical/organs/mining_organs.dmi and b/icons/obj/medical/organs/mining_organs.dmi differ diff --git a/icons/obj/medical/organs/organs.dmi b/icons/obj/medical/organs/organs.dmi index 94ba46568c929..0d04f7fae3ce4 100644 Binary files a/icons/obj/medical/organs/organs.dmi and b/icons/obj/medical/organs/organs.dmi differ diff --git a/icons/obj/medical/reagent_fillings.dmi b/icons/obj/medical/reagent_fillings.dmi index 524b52419a697..0d535c6cac2b3 100644 Binary files a/icons/obj/medical/reagent_fillings.dmi and b/icons/obj/medical/reagent_fillings.dmi differ diff --git a/icons/obj/medical/stack_medical.dmi b/icons/obj/medical/stack_medical.dmi index d12949da595f1..c4ec905786c69 100644 Binary files a/icons/obj/medical/stack_medical.dmi and b/icons/obj/medical/stack_medical.dmi differ diff --git a/icons/obj/mining.dmi b/icons/obj/mining.dmi index 54b19553b8402..cdc886d85acd1 100644 Binary files a/icons/obj/mining.dmi and b/icons/obj/mining.dmi differ diff --git a/icons/obj/mining_zones/artefacts.dmi b/icons/obj/mining_zones/artefacts.dmi index f3f7d00e4eef8..968bbe5b621d3 100644 Binary files a/icons/obj/mining_zones/artefacts.dmi and b/icons/obj/mining_zones/artefacts.dmi differ diff --git a/icons/obj/mining_zones/terrain.dmi b/icons/obj/mining_zones/terrain.dmi index 4db51145eeef1..fd930fe709dce 100644 Binary files a/icons/obj/mining_zones/terrain.dmi and b/icons/obj/mining_zones/terrain.dmi differ diff --git a/icons/obj/modular_laptop.dmi b/icons/obj/modular_laptop.dmi index 22432826e9287..c8ad438d1a38f 100644 Binary files a/icons/obj/modular_laptop.dmi and b/icons/obj/modular_laptop.dmi differ diff --git a/icons/obj/modular_pda.dmi b/icons/obj/modular_pda.dmi index 18df3249e62db..75553403ee2f8 100644 Binary files a/icons/obj/modular_pda.dmi and b/icons/obj/modular_pda.dmi differ diff --git a/icons/obj/ore.dmi b/icons/obj/ore.dmi index 23fcb8c89ead3..7cc3b8820577c 100644 Binary files a/icons/obj/ore.dmi and b/icons/obj/ore.dmi differ diff --git a/icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi b/icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi new file mode 100644 index 0000000000000..0262adcaeb241 Binary files /dev/null and b/icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi differ diff --git a/icons/obj/pipes_n_cables/!pipes_bitmask.dmi b/icons/obj/pipes_n_cables/!pipes_bitmask.dmi new file mode 100644 index 0000000000000..97643036fbe3b Binary files /dev/null and b/icons/obj/pipes_n_cables/!pipes_bitmask.dmi differ diff --git a/icons/obj/pipes_n_cables/atmos.dmi b/icons/obj/pipes_n_cables/atmos.dmi index 65cbdc672db74..91badbf3ccf9b 100644 Binary files a/icons/obj/pipes_n_cables/atmos.dmi and b/icons/obj/pipes_n_cables/atmos.dmi differ diff --git a/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi b/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi index 19775b6eff8a2..555b6c6328b02 100644 Binary files a/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi and b/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi differ diff --git a/icons/obj/pipes_n_cables/pipe_template_pieces.dmi b/icons/obj/pipes_n_cables/pipe_template_pieces.dmi new file mode 100644 index 0000000000000..d0d2f7ff7bb80 Binary files /dev/null and b/icons/obj/pipes_n_cables/pipe_template_pieces.dmi differ diff --git a/icons/obj/pipes_n_cables/pipes_bitmask.dmi b/icons/obj/pipes_n_cables/pipes_bitmask.dmi deleted file mode 100644 index 7a382fb55c5e4..0000000000000 Binary files a/icons/obj/pipes_n_cables/pipes_bitmask.dmi and /dev/null differ diff --git a/icons/obj/pipes_n_cables/prototype_canister.dmi b/icons/obj/pipes_n_cables/prototype_canister.dmi deleted file mode 100644 index fb73aa2ed6d25..0000000000000 Binary files a/icons/obj/pipes_n_cables/prototype_canister.dmi and /dev/null differ diff --git a/icons/obj/radio.dmi b/icons/obj/radio.dmi deleted file mode 100644 index e30e7d49f6f29..0000000000000 Binary files a/icons/obj/radio.dmi and /dev/null differ diff --git a/icons/obj/railings.dmi b/icons/obj/railings.dmi index 28332e2132455..6518908d544c2 100644 Binary files a/icons/obj/railings.dmi and b/icons/obj/railings.dmi differ diff --git a/icons/obj/service/bureaucracy.dmi b/icons/obj/service/bureaucracy.dmi index 8cccb7f591032..f28eb169cf0b6 100644 Binary files a/icons/obj/service/bureaucracy.dmi and b/icons/obj/service/bureaucracy.dmi differ diff --git a/icons/obj/service/hydroponics/equipment.dmi b/icons/obj/service/hydroponics/equipment.dmi index afcc4de523512..ed339a8a4209d 100644 Binary files a/icons/obj/service/hydroponics/equipment.dmi and b/icons/obj/service/hydroponics/equipment.dmi differ diff --git a/icons/obj/service/hydroponics/growing.dmi b/icons/obj/service/hydroponics/growing.dmi index fcf738998636a..aee567daa5445 100644 Binary files a/icons/obj/service/hydroponics/growing.dmi and b/icons/obj/service/hydroponics/growing.dmi differ diff --git a/icons/obj/service/hydroponics/growing_fruits.dmi b/icons/obj/service/hydroponics/growing_fruits.dmi index 7e535cdaf9b31..c0f547322c7cc 100644 Binary files a/icons/obj/service/hydroponics/growing_fruits.dmi and b/icons/obj/service/hydroponics/growing_fruits.dmi differ diff --git a/icons/obj/service/hydroponics/growing_vegetables.dmi b/icons/obj/service/hydroponics/growing_vegetables.dmi index bf49c72ba79b5..30f02e862e037 100644 Binary files a/icons/obj/service/hydroponics/growing_vegetables.dmi and b/icons/obj/service/hydroponics/growing_vegetables.dmi differ diff --git a/icons/obj/service/hydroponics/harvest.dmi b/icons/obj/service/hydroponics/harvest.dmi index 28a43776a50a0..b15a34105dec2 100644 Binary files a/icons/obj/service/hydroponics/harvest.dmi and b/icons/obj/service/hydroponics/harvest.dmi differ diff --git a/icons/obj/service/hydroponics/seeds.dmi b/icons/obj/service/hydroponics/seeds.dmi index b31590560d898..4de1a757e1fbc 100644 Binary files a/icons/obj/service/hydroponics/seeds.dmi and b/icons/obj/service/hydroponics/seeds.dmi differ diff --git a/icons/obj/service/janitor.dmi b/icons/obj/service/janitor.dmi index 0e814ca454589..0e30180832345 100644 Binary files a/icons/obj/service/janitor.dmi and b/icons/obj/service/janitor.dmi differ diff --git a/icons/obj/service/kitchen.dmi b/icons/obj/service/kitchen.dmi index cb47ddf35a2a6..aeafe2591e9bd 100644 Binary files a/icons/obj/service/kitchen.dmi and b/icons/obj/service/kitchen.dmi differ diff --git a/icons/obj/service/library.dmi b/icons/obj/service/library.dmi index 8229c1fc3947e..79a06dd4b8f91 100644 Binary files a/icons/obj/service/library.dmi and b/icons/obj/service/library.dmi differ diff --git a/icons/obj/signs.dmi b/icons/obj/signs.dmi index 20531b0f8ea27..78cc96fabbc13 100644 Binary files a/icons/obj/signs.dmi and b/icons/obj/signs.dmi differ diff --git a/icons/obj/smooth_structures/alien/nest.dmi b/icons/obj/smooth_structures/alien/nest.dmi index 6de377886f9cd..5f2c2503ba209 100644 Binary files a/icons/obj/smooth_structures/alien/nest.dmi and b/icons/obj/smooth_structures/alien/nest.dmi differ diff --git a/icons/obj/smooth_structures/alien/nest.png b/icons/obj/smooth_structures/alien/nest.png new file mode 100644 index 0000000000000..65d06047e34ec Binary files /dev/null and b/icons/obj/smooth_structures/alien/nest.png differ diff --git a/icons/obj/smooth_structures/alien/nest.png.toml b/icons/obj/smooth_structures/alien/nest.png.toml new file mode 100644 index 0000000000000..0d2dedabe815a --- /dev/null +++ b/icons/obj/smooth_structures/alien/nest.png.toml @@ -0,0 +1,2 @@ +output_name = "nest" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/alien/resin_membrane.dmi b/icons/obj/smooth_structures/alien/resin_membrane.dmi index 1ea306eaa669d..f91125dcecafd 100644 Binary files a/icons/obj/smooth_structures/alien/resin_membrane.dmi and b/icons/obj/smooth_structures/alien/resin_membrane.dmi differ diff --git a/icons/obj/smooth_structures/alien/resin_membrane.png b/icons/obj/smooth_structures/alien/resin_membrane.png new file mode 100644 index 0000000000000..3ea4dc33eccef Binary files /dev/null and b/icons/obj/smooth_structures/alien/resin_membrane.png differ diff --git a/icons/obj/smooth_structures/alien/resin_membrane.png.toml b/icons/obj/smooth_structures/alien/resin_membrane.png.toml new file mode 100644 index 0000000000000..be1ef95dde423 --- /dev/null +++ b/icons/obj/smooth_structures/alien/resin_membrane.png.toml @@ -0,0 +1,2 @@ +output_name = "resin_membrane" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/alien/resin_wall.dmi b/icons/obj/smooth_structures/alien/resin_wall.dmi index 7d837172b8ce0..1c2be2e907635 100644 Binary files a/icons/obj/smooth_structures/alien/resin_wall.dmi and b/icons/obj/smooth_structures/alien/resin_wall.dmi differ diff --git a/icons/obj/smooth_structures/alien/resin_wall.png b/icons/obj/smooth_structures/alien/resin_wall.png new file mode 100644 index 0000000000000..d42f25a43fd89 Binary files /dev/null and b/icons/obj/smooth_structures/alien/resin_wall.png differ diff --git a/icons/obj/smooth_structures/alien/resin_wall.png.toml b/icons/obj/smooth_structures/alien/resin_wall.png.toml new file mode 100644 index 0000000000000..d0ef4fffbdeac --- /dev/null +++ b/icons/obj/smooth_structures/alien/resin_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "resin_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/alien/weednode.dmi b/icons/obj/smooth_structures/alien/weednode.dmi index e8a0e000e1bb6..926730d899120 100644 Binary files a/icons/obj/smooth_structures/alien/weednode.dmi and b/icons/obj/smooth_structures/alien/weednode.dmi differ diff --git a/icons/obj/smooth_structures/alien/weednode.png b/icons/obj/smooth_structures/alien/weednode.png new file mode 100644 index 0000000000000..0a6992d4e8b86 Binary files /dev/null and b/icons/obj/smooth_structures/alien/weednode.png differ diff --git a/icons/obj/smooth_structures/alien/weednode.png.toml b/icons/obj/smooth_structures/alien/weednode.png.toml new file mode 100644 index 0000000000000..591ca723785ba --- /dev/null +++ b/icons/obj/smooth_structures/alien/weednode.png.toml @@ -0,0 +1,14 @@ +output_name = "weednode" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/obj/smooth_structures/alien/weeds1.dmi b/icons/obj/smooth_structures/alien/weeds1.dmi index 0809848b55ff0..4ba1bafcfe525 100644 Binary files a/icons/obj/smooth_structures/alien/weeds1.dmi and b/icons/obj/smooth_structures/alien/weeds1.dmi differ diff --git a/icons/obj/smooth_structures/alien/weeds1.png b/icons/obj/smooth_structures/alien/weeds1.png new file mode 100644 index 0000000000000..1275858dba889 Binary files /dev/null and b/icons/obj/smooth_structures/alien/weeds1.png differ diff --git a/icons/obj/smooth_structures/alien/weeds1.png.toml b/icons/obj/smooth_structures/alien/weeds1.png.toml new file mode 100644 index 0000000000000..8dc62bea3ccbe --- /dev/null +++ b/icons/obj/smooth_structures/alien/weeds1.png.toml @@ -0,0 +1,14 @@ +output_name = "weeds1" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/obj/smooth_structures/alien/weeds2.dmi b/icons/obj/smooth_structures/alien/weeds2.dmi index 567e9bfb7151c..12c3b37b0ad7b 100644 Binary files a/icons/obj/smooth_structures/alien/weeds2.dmi and b/icons/obj/smooth_structures/alien/weeds2.dmi differ diff --git a/icons/obj/smooth_structures/alien/weeds2.png b/icons/obj/smooth_structures/alien/weeds2.png new file mode 100644 index 0000000000000..a3e0002774b2b Binary files /dev/null and b/icons/obj/smooth_structures/alien/weeds2.png differ diff --git a/icons/obj/smooth_structures/alien/weeds2.png.toml b/icons/obj/smooth_structures/alien/weeds2.png.toml new file mode 100644 index 0000000000000..a24f8c01759ce --- /dev/null +++ b/icons/obj/smooth_structures/alien/weeds2.png.toml @@ -0,0 +1,14 @@ +output_name = "weeds2" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/obj/smooth_structures/alien/weeds3.dmi b/icons/obj/smooth_structures/alien/weeds3.dmi index 1903262a37195..45665d8969ede 100644 Binary files a/icons/obj/smooth_structures/alien/weeds3.dmi and b/icons/obj/smooth_structures/alien/weeds3.dmi differ diff --git a/icons/obj/smooth_structures/alien/weeds3.png b/icons/obj/smooth_structures/alien/weeds3.png new file mode 100644 index 0000000000000..b9c0d4440156b Binary files /dev/null and b/icons/obj/smooth_structures/alien/weeds3.png differ diff --git a/icons/obj/smooth_structures/alien/weeds3.png.toml b/icons/obj/smooth_structures/alien/weeds3.png.toml new file mode 100644 index 0000000000000..19eb01420b4e2 --- /dev/null +++ b/icons/obj/smooth_structures/alien/weeds3.png.toml @@ -0,0 +1,14 @@ +output_name = "weeds3" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/obj/smooth_structures/alien_table.dmi b/icons/obj/smooth_structures/alien_table.dmi index 63492d241ba34..865b7d3572f16 100644 Binary files a/icons/obj/smooth_structures/alien_table.dmi and b/icons/obj/smooth_structures/alien_table.dmi differ diff --git a/icons/obj/smooth_structures/alien_table.png b/icons/obj/smooth_structures/alien_table.png new file mode 100644 index 0000000000000..d9a5c3b73e41e Binary files /dev/null and b/icons/obj/smooth_structures/alien_table.png differ diff --git a/icons/obj/smooth_structures/alien_table.png.toml b/icons/obj/smooth_structures/alien_table.png.toml new file mode 100644 index 0000000000000..8ae63217ae571 --- /dev/null +++ b/icons/obj/smooth_structures/alien_table.png.toml @@ -0,0 +1,2 @@ +output_name = "alien_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/brass_table.dmi b/icons/obj/smooth_structures/brass_table.dmi index c48f99062e2c6..74417b815ab38 100644 Binary files a/icons/obj/smooth_structures/brass_table.dmi and b/icons/obj/smooth_structures/brass_table.dmi differ diff --git a/icons/obj/smooth_structures/brass_table.png b/icons/obj/smooth_structures/brass_table.png new file mode 100644 index 0000000000000..2552eb2f92b0e Binary files /dev/null and b/icons/obj/smooth_structures/brass_table.png differ diff --git a/icons/obj/smooth_structures/brass_table.png.toml b/icons/obj/smooth_structures/brass_table.png.toml new file mode 100644 index 0000000000000..d633747042d55 --- /dev/null +++ b/icons/obj/smooth_structures/brass_table.png.toml @@ -0,0 +1,2 @@ +output_name = "brass_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/catwalk.dmi b/icons/obj/smooth_structures/catwalk.dmi index 0920cc571f1e5..568afb15fea9d 100644 Binary files a/icons/obj/smooth_structures/catwalk.dmi and b/icons/obj/smooth_structures/catwalk.dmi differ diff --git a/icons/obj/smooth_structures/catwalk.png b/icons/obj/smooth_structures/catwalk.png new file mode 100644 index 0000000000000..5d9cc94056255 Binary files /dev/null and b/icons/obj/smooth_structures/catwalk.png differ diff --git a/icons/obj/smooth_structures/catwalk.png.toml b/icons/obj/smooth_structures/catwalk.png.toml new file mode 100644 index 0000000000000..42610e2043b1c --- /dev/null +++ b/icons/obj/smooth_structures/catwalk.png.toml @@ -0,0 +1,2 @@ +output_name = "catwalk" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/clockwork_window.dmi b/icons/obj/smooth_structures/clockwork_window.dmi index 674a16d43e052..6676095e9fbf3 100644 Binary files a/icons/obj/smooth_structures/clockwork_window.dmi and b/icons/obj/smooth_structures/clockwork_window.dmi differ diff --git a/icons/obj/smooth_structures/clockwork_window.png b/icons/obj/smooth_structures/clockwork_window.png new file mode 100644 index 0000000000000..a94b706447b63 Binary files /dev/null and b/icons/obj/smooth_structures/clockwork_window.png differ diff --git a/icons/obj/smooth_structures/clockwork_window.png.toml b/icons/obj/smooth_structures/clockwork_window.png.toml new file mode 100644 index 0000000000000..72375e7754102 --- /dev/null +++ b/icons/obj/smooth_structures/clockwork_window.png.toml @@ -0,0 +1,2 @@ +output_name = "clockwork_window" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/fancy_table.dmi b/icons/obj/smooth_structures/fancy_table.dmi index 475ca392ba0b7..c72e06d6798a6 100644 Binary files a/icons/obj/smooth_structures/fancy_table.dmi and b/icons/obj/smooth_structures/fancy_table.dmi differ diff --git a/icons/obj/smooth_structures/fancy_table.png b/icons/obj/smooth_structures/fancy_table.png new file mode 100644 index 0000000000000..3ca279bbd72ba Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table.png differ diff --git a/icons/obj/smooth_structures/fancy_table.png.toml b/icons/obj/smooth_structures/fancy_table.png.toml new file mode 100644 index 0000000000000..df9ca0016d76d --- /dev/null +++ b/icons/obj/smooth_structures/fancy_table.png.toml @@ -0,0 +1,14 @@ +output_name = "fancy_table" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 32 +y = 34 + +[output_icon_size] +x = 32 +y = 34 + +[cut_pos] +x = 16 +y = 17 \ No newline at end of file diff --git a/icons/obj/smooth_structures/fancy_table_black.dmi b/icons/obj/smooth_structures/fancy_table_black.dmi index 86d4410533fbc..3be7232af5ee7 100644 Binary files a/icons/obj/smooth_structures/fancy_table_black.dmi and b/icons/obj/smooth_structures/fancy_table_black.dmi differ diff --git a/icons/obj/smooth_structures/fancy_table_black.png b/icons/obj/smooth_structures/fancy_table_black.png new file mode 100644 index 0000000000000..f7e2bfa810bce Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_black.png differ diff --git a/icons/obj/smooth_structures/fancy_table_black.png.toml b/icons/obj/smooth_structures/fancy_table_black.png.toml new file mode 100644 index 0000000000000..acf7a6b821377 --- /dev/null +++ b/icons/obj/smooth_structures/fancy_table_black.png.toml @@ -0,0 +1,14 @@ +output_name = "fancy_table_black" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 32 +y = 34 + +[output_icon_size] +x = 32 +y = 34 + +[cut_pos] +x = 16 +y = 17 \ No newline at end of file diff --git a/icons/obj/smooth_structures/fancy_table_blue.dmi b/icons/obj/smooth_structures/fancy_table_blue.dmi index cb0fe3c8fafee..26263e514911f 100644 Binary files a/icons/obj/smooth_structures/fancy_table_blue.dmi and b/icons/obj/smooth_structures/fancy_table_blue.dmi differ diff --git a/icons/obj/smooth_structures/fancy_table_blue.png b/icons/obj/smooth_structures/fancy_table_blue.png new file mode 100644 index 0000000000000..ad2d4192c7f06 Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_blue.png differ diff --git a/icons/obj/smooth_structures/fancy_table_blue.png.toml b/icons/obj/smooth_structures/fancy_table_blue.png.toml new file mode 100644 index 0000000000000..0a71213ab340b --- /dev/null +++ b/icons/obj/smooth_structures/fancy_table_blue.png.toml @@ -0,0 +1,14 @@ +output_name = "fancy_table_blue" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 32 +y = 34 + +[output_icon_size] +x = 32 +y = 34 + +[cut_pos] +x = 16 +y = 17 \ No newline at end of file diff --git a/icons/obj/smooth_structures/fancy_table_cyan.dmi b/icons/obj/smooth_structures/fancy_table_cyan.dmi index 8343ae6761a6f..6da44a863cd5a 100644 Binary files a/icons/obj/smooth_structures/fancy_table_cyan.dmi and b/icons/obj/smooth_structures/fancy_table_cyan.dmi differ diff --git a/icons/obj/smooth_structures/fancy_table_cyan.png b/icons/obj/smooth_structures/fancy_table_cyan.png new file mode 100644 index 0000000000000..96a68b9361735 Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_cyan.png differ diff --git a/icons/obj/smooth_structures/fancy_table_cyan.png.toml b/icons/obj/smooth_structures/fancy_table_cyan.png.toml new file mode 100644 index 0000000000000..6991b7ea5248a --- /dev/null +++ b/icons/obj/smooth_structures/fancy_table_cyan.png.toml @@ -0,0 +1,14 @@ +output_name = "fancy_table_cyan" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 32 +y = 34 + +[output_icon_size] +x = 32 +y = 34 + +[cut_pos] +x = 16 +y = 17 \ No newline at end of file diff --git a/icons/obj/smooth_structures/fancy_table_green.dmi b/icons/obj/smooth_structures/fancy_table_green.dmi index 86c1746a7dfa5..dc6c7e4703c83 100644 Binary files a/icons/obj/smooth_structures/fancy_table_green.dmi and b/icons/obj/smooth_structures/fancy_table_green.dmi differ diff --git a/icons/obj/smooth_structures/fancy_table_green.png b/icons/obj/smooth_structures/fancy_table_green.png new file mode 100644 index 0000000000000..616eeeaa6eca0 Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_green.png differ diff --git a/icons/obj/smooth_structures/fancy_table_green.png.toml b/icons/obj/smooth_structures/fancy_table_green.png.toml new file mode 100644 index 0000000000000..a4486179c6aa8 --- /dev/null +++ b/icons/obj/smooth_structures/fancy_table_green.png.toml @@ -0,0 +1,14 @@ +output_name = "fancy_table_green" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 32 +y = 34 + +[output_icon_size] +x = 32 +y = 34 + +[cut_pos] +x = 16 +y = 17 \ No newline at end of file diff --git a/icons/obj/smooth_structures/fancy_table_orange.dmi b/icons/obj/smooth_structures/fancy_table_orange.dmi index f55ef38ac5576..91428a7525318 100644 Binary files a/icons/obj/smooth_structures/fancy_table_orange.dmi and b/icons/obj/smooth_structures/fancy_table_orange.dmi differ diff --git a/icons/obj/smooth_structures/fancy_table_orange.png b/icons/obj/smooth_structures/fancy_table_orange.png new file mode 100644 index 0000000000000..d2aff0ff4c177 Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_orange.png differ diff --git a/icons/obj/smooth_structures/fancy_table_orange.png.toml b/icons/obj/smooth_structures/fancy_table_orange.png.toml new file mode 100644 index 0000000000000..fcca03afb5aaf --- /dev/null +++ b/icons/obj/smooth_structures/fancy_table_orange.png.toml @@ -0,0 +1,14 @@ +output_name = "fancy_table_orange" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 32 +y = 34 + +[output_icon_size] +x = 32 +y = 34 + +[cut_pos] +x = 16 +y = 17 \ No newline at end of file diff --git a/icons/obj/smooth_structures/fancy_table_purple.dmi b/icons/obj/smooth_structures/fancy_table_purple.dmi index 52bdb02980c25..930b0556520f8 100644 Binary files a/icons/obj/smooth_structures/fancy_table_purple.dmi and b/icons/obj/smooth_structures/fancy_table_purple.dmi differ diff --git a/icons/obj/smooth_structures/fancy_table_purple.png b/icons/obj/smooth_structures/fancy_table_purple.png new file mode 100644 index 0000000000000..fad1b80c9229a Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_purple.png differ diff --git a/icons/obj/smooth_structures/fancy_table_purple.png.toml b/icons/obj/smooth_structures/fancy_table_purple.png.toml new file mode 100644 index 0000000000000..2da707716ff04 --- /dev/null +++ b/icons/obj/smooth_structures/fancy_table_purple.png.toml @@ -0,0 +1,14 @@ +output_name = "fancy_table_purple" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 32 +y = 34 + +[output_icon_size] +x = 32 +y = 34 + +[cut_pos] +x = 16 +y = 17 \ No newline at end of file diff --git a/icons/obj/smooth_structures/fancy_table_red.dmi b/icons/obj/smooth_structures/fancy_table_red.dmi index eb84340873bf7..ceca04aeb0609 100644 Binary files a/icons/obj/smooth_structures/fancy_table_red.dmi and b/icons/obj/smooth_structures/fancy_table_red.dmi differ diff --git a/icons/obj/smooth_structures/fancy_table_red.png b/icons/obj/smooth_structures/fancy_table_red.png new file mode 100644 index 0000000000000..f94307cb7d42a Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_red.png differ diff --git a/icons/obj/smooth_structures/fancy_table_red.png.toml b/icons/obj/smooth_structures/fancy_table_red.png.toml new file mode 100644 index 0000000000000..d3b99e80746ec --- /dev/null +++ b/icons/obj/smooth_structures/fancy_table_red.png.toml @@ -0,0 +1,14 @@ +output_name = "fancy_table_red" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 32 +y = 34 + +[output_icon_size] +x = 32 +y = 34 + +[cut_pos] +x = 16 +y = 17 \ No newline at end of file diff --git a/icons/obj/smooth_structures/fancy_table_royalblack.dmi b/icons/obj/smooth_structures/fancy_table_royalblack.dmi index 3fafd6b23c299..b7f6f6e284db7 100644 Binary files a/icons/obj/smooth_structures/fancy_table_royalblack.dmi and b/icons/obj/smooth_structures/fancy_table_royalblack.dmi differ diff --git a/icons/obj/smooth_structures/fancy_table_royalblack.png b/icons/obj/smooth_structures/fancy_table_royalblack.png new file mode 100644 index 0000000000000..82913622d1ee6 Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_royalblack.png differ diff --git a/icons/obj/smooth_structures/fancy_table_royalblack.png.toml b/icons/obj/smooth_structures/fancy_table_royalblack.png.toml new file mode 100644 index 0000000000000..76a975938f87f --- /dev/null +++ b/icons/obj/smooth_structures/fancy_table_royalblack.png.toml @@ -0,0 +1,14 @@ +output_name = "fancy_table_royalblack" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 32 +y = 34 + +[output_icon_size] +x = 32 +y = 34 + +[cut_pos] +x = 16 +y = 17 \ No newline at end of file diff --git a/icons/obj/smooth_structures/fancy_table_royalblue.dmi b/icons/obj/smooth_structures/fancy_table_royalblue.dmi index bd4400bfb0a1a..8f7a03ba5adc2 100644 Binary files a/icons/obj/smooth_structures/fancy_table_royalblue.dmi and b/icons/obj/smooth_structures/fancy_table_royalblue.dmi differ diff --git a/icons/obj/smooth_structures/fancy_table_royalblue.png b/icons/obj/smooth_structures/fancy_table_royalblue.png new file mode 100644 index 0000000000000..6b1a2b2a81348 Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_royalblue.png differ diff --git a/icons/obj/smooth_structures/fancy_table_royalblue.png.toml b/icons/obj/smooth_structures/fancy_table_royalblue.png.toml new file mode 100644 index 0000000000000..59987f0feb8b1 --- /dev/null +++ b/icons/obj/smooth_structures/fancy_table_royalblue.png.toml @@ -0,0 +1,14 @@ +output_name = "fancy_table_royalblue" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 32 +y = 34 + +[output_icon_size] +x = 32 +y = 34 + +[cut_pos] +x = 16 +y = 17 \ No newline at end of file diff --git a/icons/obj/smooth_structures/glass_table.dmi b/icons/obj/smooth_structures/glass_table.dmi index 09ecbc1bb44ae..56c43801832b4 100644 Binary files a/icons/obj/smooth_structures/glass_table.dmi and b/icons/obj/smooth_structures/glass_table.dmi differ diff --git a/icons/obj/smooth_structures/glass_table.png b/icons/obj/smooth_structures/glass_table.png new file mode 100644 index 0000000000000..f61de10c5077d Binary files /dev/null and b/icons/obj/smooth_structures/glass_table.png differ diff --git a/icons/obj/smooth_structures/glass_table.png.toml b/icons/obj/smooth_structures/glass_table.png.toml new file mode 100644 index 0000000000000..91177545f2c07 --- /dev/null +++ b/icons/obj/smooth_structures/glass_table.png.toml @@ -0,0 +1,2 @@ +output_name = "glass_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/hedge.dmi b/icons/obj/smooth_structures/hedge.dmi index 9c38bc844f6d4..dea538dd752bd 100644 Binary files a/icons/obj/smooth_structures/hedge.dmi and b/icons/obj/smooth_structures/hedge.dmi differ diff --git a/icons/obj/smooth_structures/hedge.png b/icons/obj/smooth_structures/hedge.png new file mode 100644 index 0000000000000..923c425cb3258 Binary files /dev/null and b/icons/obj/smooth_structures/hedge.png differ diff --git a/icons/obj/smooth_structures/hedge.png.toml b/icons/obj/smooth_structures/hedge.png.toml new file mode 100644 index 0000000000000..a03553bb3260b --- /dev/null +++ b/icons/obj/smooth_structures/hedge.png.toml @@ -0,0 +1,2 @@ +output_name = "hedge" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/lattice.dmi b/icons/obj/smooth_structures/lattice.dmi index d9223ded775fb..3ae4964a11f7a 100644 Binary files a/icons/obj/smooth_structures/lattice.dmi and b/icons/obj/smooth_structures/lattice.dmi differ diff --git a/icons/obj/smooth_structures/lattice.png b/icons/obj/smooth_structures/lattice.png new file mode 100644 index 0000000000000..9d00f8dfe20e8 Binary files /dev/null and b/icons/obj/smooth_structures/lattice.png differ diff --git a/icons/obj/smooth_structures/lattice.png.toml b/icons/obj/smooth_structures/lattice.png.toml new file mode 100644 index 0000000000000..63e711fce3132 --- /dev/null +++ b/icons/obj/smooth_structures/lattice.png.toml @@ -0,0 +1,2 @@ +output_name = "lattice" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/paperframes.dmi b/icons/obj/smooth_structures/paperframes.dmi index 9982db639cba6..0adb2646b1c03 100644 Binary files a/icons/obj/smooth_structures/paperframes.dmi and b/icons/obj/smooth_structures/paperframes.dmi differ diff --git a/icons/obj/smooth_structures/paperframes.png b/icons/obj/smooth_structures/paperframes.png new file mode 100644 index 0000000000000..b6d10892eccb9 Binary files /dev/null and b/icons/obj/smooth_structures/paperframes.png differ diff --git a/icons/obj/smooth_structures/paperframes.png.toml b/icons/obj/smooth_structures/paperframes.png.toml new file mode 100644 index 0000000000000..5d3bbe124429d --- /dev/null +++ b/icons/obj/smooth_structures/paperframes.png.toml @@ -0,0 +1,2 @@ +output_name = "paperframes" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/plasma_window.dmi b/icons/obj/smooth_structures/plasma_window.dmi index eaed6f219e087..bbfe759177736 100644 Binary files a/icons/obj/smooth_structures/plasma_window.dmi and b/icons/obj/smooth_structures/plasma_window.dmi differ diff --git a/icons/obj/smooth_structures/plasma_window.png b/icons/obj/smooth_structures/plasma_window.png new file mode 100644 index 0000000000000..d38c888be9af9 Binary files /dev/null and b/icons/obj/smooth_structures/plasma_window.png differ diff --git a/icons/obj/smooth_structures/plasma_window.png.toml b/icons/obj/smooth_structures/plasma_window.png.toml new file mode 100644 index 0000000000000..2ed25c1b89ccc --- /dev/null +++ b/icons/obj/smooth_structures/plasma_window.png.toml @@ -0,0 +1,2 @@ +output_name = "plasma_window" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/plasmaglass_table.dmi b/icons/obj/smooth_structures/plasmaglass_table.dmi index 5dfe68d0673b9..7c90a489e82de 100644 Binary files a/icons/obj/smooth_structures/plasmaglass_table.dmi and b/icons/obj/smooth_structures/plasmaglass_table.dmi differ diff --git a/icons/obj/smooth_structures/plasmaglass_table.png b/icons/obj/smooth_structures/plasmaglass_table.png new file mode 100644 index 0000000000000..f3b062d6fdb96 Binary files /dev/null and b/icons/obj/smooth_structures/plasmaglass_table.png differ diff --git a/icons/obj/smooth_structures/plasmaglass_table.png.toml b/icons/obj/smooth_structures/plasmaglass_table.png.toml new file mode 100644 index 0000000000000..744e082acf62f --- /dev/null +++ b/icons/obj/smooth_structures/plasmaglass_table.png.toml @@ -0,0 +1,2 @@ +output_name = "plasmaglass_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/plastitanium_window.dmi b/icons/obj/smooth_structures/plastitanium_window.dmi index 14ec56a59c40e..85eae01e8cb83 100644 Binary files a/icons/obj/smooth_structures/plastitanium_window.dmi and b/icons/obj/smooth_structures/plastitanium_window.dmi differ diff --git a/icons/obj/smooth_structures/plastitanium_window.png b/icons/obj/smooth_structures/plastitanium_window.png new file mode 100644 index 0000000000000..114e5d7e0219d Binary files /dev/null and b/icons/obj/smooth_structures/plastitanium_window.png differ diff --git a/icons/obj/smooth_structures/plastitanium_window.png.toml b/icons/obj/smooth_structures/plastitanium_window.png.toml new file mode 100644 index 0000000000000..fe2fcaada1dee --- /dev/null +++ b/icons/obj/smooth_structures/plastitanium_window.png.toml @@ -0,0 +1,2 @@ +output_name = "plastitanium_window" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/plastitaniumglass_table.dmi b/icons/obj/smooth_structures/plastitaniumglass_table.dmi index 4c4a6438690cc..78b6af93ba2a8 100644 Binary files a/icons/obj/smooth_structures/plastitaniumglass_table.dmi and b/icons/obj/smooth_structures/plastitaniumglass_table.dmi differ diff --git a/icons/obj/smooth_structures/plastitaniumglass_table.png b/icons/obj/smooth_structures/plastitaniumglass_table.png new file mode 100644 index 0000000000000..b9619645bdfa6 Binary files /dev/null and b/icons/obj/smooth_structures/plastitaniumglass_table.png differ diff --git a/icons/obj/smooth_structures/plastitaniumglass_table.png.toml b/icons/obj/smooth_structures/plastitaniumglass_table.png.toml new file mode 100644 index 0000000000000..b1db1da55fc06 --- /dev/null +++ b/icons/obj/smooth_structures/plastitaniumglass_table.png.toml @@ -0,0 +1,2 @@ +output_name = "plastitaniumglass_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/pod_window.dmi b/icons/obj/smooth_structures/pod_window.dmi index 46d4208b4b5de..f179cea280f9b 100644 Binary files a/icons/obj/smooth_structures/pod_window.dmi and b/icons/obj/smooth_structures/pod_window.dmi differ diff --git a/icons/obj/smooth_structures/pod_window.png b/icons/obj/smooth_structures/pod_window.png new file mode 100644 index 0000000000000..3bc31691919b8 Binary files /dev/null and b/icons/obj/smooth_structures/pod_window.png differ diff --git a/icons/obj/smooth_structures/pod_window.png.toml b/icons/obj/smooth_structures/pod_window.png.toml new file mode 100644 index 0000000000000..f23c6e7a3ae24 --- /dev/null +++ b/icons/obj/smooth_structures/pod_window.png.toml @@ -0,0 +1,2 @@ +output_name = "pod_window" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/poker_table.dmi b/icons/obj/smooth_structures/poker_table.dmi index d0b70822771fb..3dbc230ad0b23 100644 Binary files a/icons/obj/smooth_structures/poker_table.dmi and b/icons/obj/smooth_structures/poker_table.dmi differ diff --git a/icons/obj/smooth_structures/poker_table.png b/icons/obj/smooth_structures/poker_table.png new file mode 100644 index 0000000000000..1be2da6519631 Binary files /dev/null and b/icons/obj/smooth_structures/poker_table.png differ diff --git a/icons/obj/smooth_structures/poker_table.png.toml b/icons/obj/smooth_structures/poker_table.png.toml new file mode 100644 index 0000000000000..b12426f9236e6 --- /dev/null +++ b/icons/obj/smooth_structures/poker_table.png.toml @@ -0,0 +1,2 @@ +output_name = "poker_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/reinforced_table.dmi b/icons/obj/smooth_structures/reinforced_table.dmi index b2867b579945e..5053deff017c1 100644 Binary files a/icons/obj/smooth_structures/reinforced_table.dmi and b/icons/obj/smooth_structures/reinforced_table.dmi differ diff --git a/icons/obj/smooth_structures/reinforced_table.png b/icons/obj/smooth_structures/reinforced_table.png new file mode 100644 index 0000000000000..0f9ae450e562a Binary files /dev/null and b/icons/obj/smooth_structures/reinforced_table.png differ diff --git a/icons/obj/smooth_structures/reinforced_table.png.toml b/icons/obj/smooth_structures/reinforced_table.png.toml new file mode 100644 index 0000000000000..f7143356b4c94 --- /dev/null +++ b/icons/obj/smooth_structures/reinforced_table.png.toml @@ -0,0 +1,2 @@ +output_name = "reinforced_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/reinforced_window.dmi b/icons/obj/smooth_structures/reinforced_window.dmi index a66b648786da4..5506e82741676 100644 Binary files a/icons/obj/smooth_structures/reinforced_window.dmi and b/icons/obj/smooth_structures/reinforced_window.dmi differ diff --git a/icons/obj/smooth_structures/reinforced_window.png b/icons/obj/smooth_structures/reinforced_window.png new file mode 100644 index 0000000000000..d7a4f76654d03 Binary files /dev/null and b/icons/obj/smooth_structures/reinforced_window.png differ diff --git a/icons/obj/smooth_structures/reinforced_window.png.toml b/icons/obj/smooth_structures/reinforced_window.png.toml new file mode 100644 index 0000000000000..686fd268e3b0d --- /dev/null +++ b/icons/obj/smooth_structures/reinforced_window.png.toml @@ -0,0 +1,2 @@ +output_name = "reinforced_window" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/rglass_table.dmi b/icons/obj/smooth_structures/rglass_table.dmi index 3b645c72ad3a7..fd0bb83c2eee4 100644 Binary files a/icons/obj/smooth_structures/rglass_table.dmi and b/icons/obj/smooth_structures/rglass_table.dmi differ diff --git a/icons/obj/smooth_structures/rglass_table.png b/icons/obj/smooth_structures/rglass_table.png new file mode 100644 index 0000000000000..970fc7d777077 Binary files /dev/null and b/icons/obj/smooth_structures/rglass_table.png differ diff --git a/icons/obj/smooth_structures/rglass_table.png.toml b/icons/obj/smooth_structures/rglass_table.png.toml new file mode 100644 index 0000000000000..c4cca008bbdd8 --- /dev/null +++ b/icons/obj/smooth_structures/rglass_table.png.toml @@ -0,0 +1,2 @@ +output_name = "rglass_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/rice_window.dmi b/icons/obj/smooth_structures/rice_window.dmi index 3680926e7fa3e..b418cd87cfd81 100644 Binary files a/icons/obj/smooth_structures/rice_window.dmi and b/icons/obj/smooth_structures/rice_window.dmi differ diff --git a/icons/obj/smooth_structures/rice_window.png b/icons/obj/smooth_structures/rice_window.png new file mode 100644 index 0000000000000..61645b22d964a Binary files /dev/null and b/icons/obj/smooth_structures/rice_window.png differ diff --git a/icons/obj/smooth_structures/rice_window.png.toml b/icons/obj/smooth_structures/rice_window.png.toml new file mode 100644 index 0000000000000..52f1aae1b2ea4 --- /dev/null +++ b/icons/obj/smooth_structures/rice_window.png.toml @@ -0,0 +1,2 @@ +output_name = "rice_window" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/rplasma_window.dmi b/icons/obj/smooth_structures/rplasma_window.dmi index 173b8d53766ad..1952a0a8200a2 100644 Binary files a/icons/obj/smooth_structures/rplasma_window.dmi and b/icons/obj/smooth_structures/rplasma_window.dmi differ diff --git a/icons/obj/smooth_structures/rplasma_window.png b/icons/obj/smooth_structures/rplasma_window.png new file mode 100644 index 0000000000000..9ea53c9b540a1 Binary files /dev/null and b/icons/obj/smooth_structures/rplasma_window.png differ diff --git a/icons/obj/smooth_structures/rplasma_window.png.toml b/icons/obj/smooth_structures/rplasma_window.png.toml new file mode 100644 index 0000000000000..791b377924592 --- /dev/null +++ b/icons/obj/smooth_structures/rplasma_window.png.toml @@ -0,0 +1,2 @@ +output_name = "rplasma_window" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/rplasmaglass_table.dmi b/icons/obj/smooth_structures/rplasmaglass_table.dmi index 4e812c79c7a36..db65243ab7c29 100644 Binary files a/icons/obj/smooth_structures/rplasmaglass_table.dmi and b/icons/obj/smooth_structures/rplasmaglass_table.dmi differ diff --git a/icons/obj/smooth_structures/rplasmaglass_table.png b/icons/obj/smooth_structures/rplasmaglass_table.png new file mode 100644 index 0000000000000..4752df63b82fc Binary files /dev/null and b/icons/obj/smooth_structures/rplasmaglass_table.png differ diff --git a/icons/obj/smooth_structures/rplasmaglass_table.png.toml b/icons/obj/smooth_structures/rplasmaglass_table.png.toml new file mode 100644 index 0000000000000..29f4f3408cbe8 --- /dev/null +++ b/icons/obj/smooth_structures/rplasmaglass_table.png.toml @@ -0,0 +1,2 @@ +output_name = "rplasmaglass_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/sandbags.dmi b/icons/obj/smooth_structures/sandbags.dmi index 6ff5bfbd7eb5d..d5e1d1877da25 100644 Binary files a/icons/obj/smooth_structures/sandbags.dmi and b/icons/obj/smooth_structures/sandbags.dmi differ diff --git a/icons/obj/smooth_structures/sandbags.png b/icons/obj/smooth_structures/sandbags.png new file mode 100644 index 0000000000000..ed0308c854ff8 Binary files /dev/null and b/icons/obj/smooth_structures/sandbags.png differ diff --git a/icons/obj/smooth_structures/sandbags.png.toml b/icons/obj/smooth_structures/sandbags.png.toml new file mode 100644 index 0000000000000..146b6d42589d6 --- /dev/null +++ b/icons/obj/smooth_structures/sandbags.png.toml @@ -0,0 +1,2 @@ +output_name = "sandbags" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/shuttle_window.dmi b/icons/obj/smooth_structures/shuttle_window.dmi index 9b9891e9d2c8e..68ca36d377881 100644 Binary files a/icons/obj/smooth_structures/shuttle_window.dmi and b/icons/obj/smooth_structures/shuttle_window.dmi differ diff --git a/icons/obj/smooth_structures/shuttle_window.png b/icons/obj/smooth_structures/shuttle_window.png new file mode 100644 index 0000000000000..a5312cb8ae7c8 Binary files /dev/null and b/icons/obj/smooth_structures/shuttle_window.png differ diff --git a/icons/obj/smooth_structures/shuttle_window.png.toml b/icons/obj/smooth_structures/shuttle_window.png.toml new file mode 100644 index 0000000000000..1c26ec4d86f91 --- /dev/null +++ b/icons/obj/smooth_structures/shuttle_window.png.toml @@ -0,0 +1,2 @@ +output_name = "shuttle_window" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/structure_variations.dmi b/icons/obj/smooth_structures/structure_variations.dmi new file mode 100644 index 0000000000000..afabd8f4f2ee8 Binary files /dev/null and b/icons/obj/smooth_structures/structure_variations.dmi differ diff --git a/icons/obj/smooth_structures/table.dmi b/icons/obj/smooth_structures/table.dmi index 3b15d2a27dd54..225f0950b95fe 100644 Binary files a/icons/obj/smooth_structures/table.dmi and b/icons/obj/smooth_structures/table.dmi differ diff --git a/icons/obj/smooth_structures/table.png b/icons/obj/smooth_structures/table.png new file mode 100644 index 0000000000000..b5acd10b4b8dd Binary files /dev/null and b/icons/obj/smooth_structures/table.png differ diff --git a/icons/obj/smooth_structures/table.png.toml b/icons/obj/smooth_structures/table.png.toml new file mode 100644 index 0000000000000..3700febbed568 --- /dev/null +++ b/icons/obj/smooth_structures/table.png.toml @@ -0,0 +1,2 @@ +output_name = "table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/table_greyscale.dmi b/icons/obj/smooth_structures/table_greyscale.dmi index 2c3acf69bfea1..0c3513c58d1a4 100644 Binary files a/icons/obj/smooth_structures/table_greyscale.dmi and b/icons/obj/smooth_structures/table_greyscale.dmi differ diff --git a/icons/obj/smooth_structures/table_greyscale.png b/icons/obj/smooth_structures/table_greyscale.png new file mode 100644 index 0000000000000..535a535d03cdd Binary files /dev/null and b/icons/obj/smooth_structures/table_greyscale.png differ diff --git a/icons/obj/smooth_structures/table_greyscale.png.toml b/icons/obj/smooth_structures/table_greyscale.png.toml new file mode 100644 index 0000000000000..6a68ba8bc4d7e --- /dev/null +++ b/icons/obj/smooth_structures/table_greyscale.png.toml @@ -0,0 +1,2 @@ +output_name = "table_greyscale" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/tinted_window.dmi b/icons/obj/smooth_structures/tinted_window.dmi index 59b690110e58d..53f803c394229 100644 Binary files a/icons/obj/smooth_structures/tinted_window.dmi and b/icons/obj/smooth_structures/tinted_window.dmi differ diff --git a/icons/obj/smooth_structures/tinted_window.png b/icons/obj/smooth_structures/tinted_window.png new file mode 100644 index 0000000000000..6601e8c231f70 Binary files /dev/null and b/icons/obj/smooth_structures/tinted_window.png differ diff --git a/icons/obj/smooth_structures/tinted_window.png.toml b/icons/obj/smooth_structures/tinted_window.png.toml new file mode 100644 index 0000000000000..9d8250aa81d01 --- /dev/null +++ b/icons/obj/smooth_structures/tinted_window.png.toml @@ -0,0 +1,2 @@ +output_name = "tinted_window" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/titaniumglass_table.dmi b/icons/obj/smooth_structures/titaniumglass_table.dmi index 76dc30163f0ea..43bf24b5afec4 100644 Binary files a/icons/obj/smooth_structures/titaniumglass_table.dmi and b/icons/obj/smooth_structures/titaniumglass_table.dmi differ diff --git a/icons/obj/smooth_structures/titaniumglass_table.png b/icons/obj/smooth_structures/titaniumglass_table.png new file mode 100644 index 0000000000000..8da5ba0849dad Binary files /dev/null and b/icons/obj/smooth_structures/titaniumglass_table.png differ diff --git a/icons/obj/smooth_structures/titaniumglass_table.png.toml b/icons/obj/smooth_structures/titaniumglass_table.png.toml new file mode 100644 index 0000000000000..d77b867616c64 --- /dev/null +++ b/icons/obj/smooth_structures/titaniumglass_table.png.toml @@ -0,0 +1,2 @@ +output_name = "titaniumglass_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/tram_window.dmi b/icons/obj/smooth_structures/tram_window.dmi deleted file mode 100644 index 938ca3a0c0bfa..0000000000000 Binary files a/icons/obj/smooth_structures/tram_window.dmi and /dev/null differ diff --git a/icons/obj/smooth_structures/window.dmi b/icons/obj/smooth_structures/window.dmi index 4b78390251e04..6dab5136d41f2 100644 Binary files a/icons/obj/smooth_structures/window.dmi and b/icons/obj/smooth_structures/window.dmi differ diff --git a/icons/obj/smooth_structures/window.png b/icons/obj/smooth_structures/window.png new file mode 100644 index 0000000000000..e06498a9e3a82 Binary files /dev/null and b/icons/obj/smooth_structures/window.png differ diff --git a/icons/obj/smooth_structures/window.png.toml b/icons/obj/smooth_structures/window.png.toml new file mode 100644 index 0000000000000..7ae19716bb6ae --- /dev/null +++ b/icons/obj/smooth_structures/window.png.toml @@ -0,0 +1,2 @@ +output_name = "window" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/wood_table.dmi b/icons/obj/smooth_structures/wood_table.dmi index 3c72c11c59172..ddc65b0c739d5 100644 Binary files a/icons/obj/smooth_structures/wood_table.dmi and b/icons/obj/smooth_structures/wood_table.dmi differ diff --git a/icons/obj/smooth_structures/wood_table.png b/icons/obj/smooth_structures/wood_table.png new file mode 100644 index 0000000000000..72dc68ce7c35c Binary files /dev/null and b/icons/obj/smooth_structures/wood_table.png differ diff --git a/icons/obj/smooth_structures/wood_table.png.toml b/icons/obj/smooth_structures/wood_table.png.toml new file mode 100644 index 0000000000000..1c197d3d9f8e2 --- /dev/null +++ b/icons/obj/smooth_structures/wood_table.png.toml @@ -0,0 +1,2 @@ +output_name = "wood_table" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/storage/box.dmi b/icons/obj/storage/box.dmi index 87e00dae5afe7..8e34ac8f8de09 100644 Binary files a/icons/obj/storage/box.dmi and b/icons/obj/storage/box.dmi differ diff --git a/icons/obj/storage/closet.dmi b/icons/obj/storage/closet.dmi index 8c5285e116718..ec01f4b9a347e 100644 Binary files a/icons/obj/storage/closet.dmi and b/icons/obj/storage/closet.dmi differ diff --git a/icons/obj/storage/crates.dmi b/icons/obj/storage/crates.dmi index 7f78d049302bf..9bc8f4d2c27e9 100644 Binary files a/icons/obj/storage/crates.dmi and b/icons/obj/storage/crates.dmi differ diff --git a/icons/obj/storage/storage.dmi b/icons/obj/storage/storage.dmi index cdaf8cae339f7..a55606fa3b33b 100644 Binary files a/icons/obj/storage/storage.dmi and b/icons/obj/storage/storage.dmi differ diff --git a/icons/obj/storage/wrapping.dmi b/icons/obj/storage/wrapping.dmi index acd91a3d7006d..944a6b84752f2 100644 Binary files a/icons/obj/storage/wrapping.dmi and b/icons/obj/storage/wrapping.dmi differ diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi index 0289c9105be06..1e6a2ba68724e 100644 Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ diff --git a/icons/obj/tools.dmi b/icons/obj/tools.dmi index e6679c8c51848..6bb1b8b41f67c 100644 Binary files a/icons/obj/tools.dmi and b/icons/obj/tools.dmi differ diff --git a/icons/obj/toys/tcgsummons.dmi b/icons/obj/toys/tcgsummons.dmi index 2647c96c25d53..a870ce8d1d8cd 100644 Binary files a/icons/obj/toys/tcgsummons.dmi and b/icons/obj/toys/tcgsummons.dmi differ diff --git a/icons/obj/trader_signs.dmi b/icons/obj/trader_signs.dmi new file mode 100644 index 0000000000000..a6789e0bb4c5f Binary files /dev/null and b/icons/obj/trader_signs.dmi differ diff --git a/icons/obj/tram/crossing_signal.dmi b/icons/obj/tram/crossing_signal.dmi new file mode 100644 index 0000000000000..bfcda58c7f3a1 Binary files /dev/null and b/icons/obj/tram/crossing_signal.dmi differ diff --git a/icons/obj/tram/tram_controllers.dmi b/icons/obj/tram/tram_controllers.dmi new file mode 100644 index 0000000000000..aea1f691af241 Binary files /dev/null and b/icons/obj/tram/tram_controllers.dmi differ diff --git a/icons/obj/tram/tram_display.dmi b/icons/obj/tram/tram_display.dmi new file mode 100644 index 0000000000000..e28beef468fef Binary files /dev/null and b/icons/obj/tram/tram_display.dmi differ diff --git a/icons/obj/tram/tram_indicator.dmi b/icons/obj/tram/tram_indicator.dmi new file mode 100644 index 0000000000000..f1b0b4123908d Binary files /dev/null and b/icons/obj/tram/tram_indicator.dmi differ diff --git a/icons/obj/tram/tram_rails.dmi b/icons/obj/tram/tram_rails.dmi new file mode 100644 index 0000000000000..8e0316223b3e0 Binary files /dev/null and b/icons/obj/tram/tram_rails.dmi differ diff --git a/icons/obj/tram/tram_sensor.dmi b/icons/obj/tram/tram_sensor.dmi new file mode 100644 index 0000000000000..5146e79c4ac3b Binary files /dev/null and b/icons/obj/tram/tram_sensor.dmi differ diff --git a/icons/obj/tram/tram_structure.dmi b/icons/obj/tram/tram_structure.dmi new file mode 100644 index 0000000000000..9fd919163d1f2 Binary files /dev/null and b/icons/obj/tram/tram_structure.dmi differ diff --git a/icons/turf/walls/tram_wall.dmi b/icons/obj/tram/tram_wall.dmi similarity index 100% rename from icons/turf/walls/tram_wall.dmi rename to icons/obj/tram/tram_wall.dmi diff --git a/icons/obj/weapons/changeling_items.dmi b/icons/obj/weapons/changeling_items.dmi index 4c26dfea49efe..ca577e6d2b28e 100644 Binary files a/icons/obj/weapons/changeling_items.dmi and b/icons/obj/weapons/changeling_items.dmi differ diff --git a/icons/obj/weapons/club.dmi b/icons/obj/weapons/club.dmi index 616f03f868956..6ebca45b79cbe 100644 Binary files a/icons/obj/weapons/club.dmi and b/icons/obj/weapons/club.dmi differ diff --git a/icons/obj/weapons/guns/ammo.dmi b/icons/obj/weapons/guns/ammo.dmi index a36b593706f76..07a4bde661a64 100644 Binary files a/icons/obj/weapons/guns/ammo.dmi and b/icons/obj/weapons/guns/ammo.dmi differ diff --git a/icons/obj/weapons/guns/ballistic.dmi b/icons/obj/weapons/guns/ballistic.dmi index 9dd25af2a9219..5cd823cc9c412 100644 Binary files a/icons/obj/weapons/guns/ballistic.dmi and b/icons/obj/weapons/guns/ballistic.dmi differ diff --git a/icons/obj/weapons/guns/energy.dmi b/icons/obj/weapons/guns/energy.dmi index 97b75335b91a0..5d607026f0133 100644 Binary files a/icons/obj/weapons/guns/energy.dmi and b/icons/obj/weapons/guns/energy.dmi differ diff --git a/icons/obj/weapons/guns/magic.dmi b/icons/obj/weapons/guns/magic.dmi index fe3eb6ae895f6..90eb4bdc669a5 100644 Binary files a/icons/obj/weapons/guns/magic.dmi and b/icons/obj/weapons/guns/magic.dmi differ diff --git a/icons/obj/weapons/guns/projectiles.dmi b/icons/obj/weapons/guns/projectiles.dmi index e1c70c4f5ade4..6e6b1797c42ea 100644 Binary files a/icons/obj/weapons/guns/projectiles.dmi and b/icons/obj/weapons/guns/projectiles.dmi differ diff --git a/icons/obj/weapons/guns/toy.dmi b/icons/obj/weapons/guns/toy.dmi index 83a85e7e447c7..3f7e509b699e6 100644 Binary files a/icons/obj/weapons/guns/toy.dmi and b/icons/obj/weapons/guns/toy.dmi differ diff --git a/icons/obj/weapons/hammer.dmi b/icons/obj/weapons/hammer.dmi index c210f8b436b3a..965fd0b37efc8 100644 Binary files a/icons/obj/weapons/hammer.dmi and b/icons/obj/weapons/hammer.dmi differ diff --git a/icons/obj/weapons/khopesh.dmi b/icons/obj/weapons/khopesh.dmi index ab7a0c252cbf7..ba9ef545f14e4 100644 Binary files a/icons/obj/weapons/khopesh.dmi and b/icons/obj/weapons/khopesh.dmi differ diff --git a/icons/obj/weapons/shields.dmi b/icons/obj/weapons/shields.dmi index cbf4b612bcab2..3f90af83196ba 100644 Binary files a/icons/obj/weapons/shields.dmi and b/icons/obj/weapons/shields.dmi differ diff --git a/icons/obj/weapons/spear.dmi b/icons/obj/weapons/spear.dmi index 917365235ecd4..7262da01c4551 100644 Binary files a/icons/obj/weapons/spear.dmi and b/icons/obj/weapons/spear.dmi differ diff --git a/icons/obj/weapons/sword.dmi b/icons/obj/weapons/sword.dmi index 8e6ee6bdd2fef..255c3b0cffd0e 100644 Binary files a/icons/obj/weapons/sword.dmi and b/icons/obj/weapons/sword.dmi differ diff --git a/icons/obj/weapons/turrets.dmi b/icons/obj/weapons/turrets.dmi index 9a9d387ef961e..6582671eac0a2 100644 Binary files a/icons/obj/weapons/turrets.dmi and b/icons/obj/weapons/turrets.dmi differ diff --git a/icons/program_icons/mafia.gif b/icons/program_icons/mafia.gif new file mode 100644 index 0000000000000..5821f55e1e49c Binary files /dev/null and b/icons/program_icons/mafia.gif differ diff --git a/icons/turf/cliff/icerock_cliff.dmi b/icons/turf/cliff/icerock_cliff.dmi index 260a963a1eaea..0bcef05b072fa 100644 Binary files a/icons/turf/cliff/icerock_cliff.dmi and b/icons/turf/cliff/icerock_cliff.dmi differ diff --git a/icons/turf/cliff/icerock_cliff.png b/icons/turf/cliff/icerock_cliff.png new file mode 100644 index 0000000000000..962cceaa036d3 Binary files /dev/null and b/icons/turf/cliff/icerock_cliff.png differ diff --git a/icons/turf/cliff/icerock_cliff.png.toml b/icons/turf/cliff/icerock_cliff.png.toml new file mode 100644 index 0000000000000..7d713818b562f --- /dev/null +++ b/icons/turf/cliff/icerock_cliff.png.toml @@ -0,0 +1,14 @@ +output_name = "icerock_wall" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/turf/damaged.dmi b/icons/turf/damaged.dmi index d3d06d53e4691..a81384d8be6a1 100644 Binary files a/icons/turf/damaged.dmi and b/icons/turf/damaged.dmi differ diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi index 8a1575fcec640..89b4876c0cec2 100644 Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ diff --git a/icons/turf/floors/ash.dmi b/icons/turf/floors/ash.dmi index 1ebe8d713970c..e6d3ed666fb69 100644 Binary files a/icons/turf/floors/ash.dmi and b/icons/turf/floors/ash.dmi differ diff --git a/icons/turf/floors/ash.png b/icons/turf/floors/ash.png new file mode 100644 index 0000000000000..419dd49d9def5 Binary files /dev/null and b/icons/turf/floors/ash.png differ diff --git a/icons/turf/floors/ash.png.toml b/icons/turf/floors/ash.png.toml new file mode 100644 index 0000000000000..9bbdc460b4d41 --- /dev/null +++ b/icons/turf/floors/ash.png.toml @@ -0,0 +1,14 @@ +output_name = "ash" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/turf/floors/bamboo_mat.dmi b/icons/turf/floors/bamboo_mat.dmi index 1e0b04bc3aa4c..c2380c5b874f2 100644 Binary files a/icons/turf/floors/bamboo_mat.dmi and b/icons/turf/floors/bamboo_mat.dmi differ diff --git a/icons/turf/floors/bamboo_mat.png b/icons/turf/floors/bamboo_mat.png new file mode 100644 index 0000000000000..f945572a8b7b7 Binary files /dev/null and b/icons/turf/floors/bamboo_mat.png differ diff --git a/icons/turf/floors/bamboo_mat.png.toml b/icons/turf/floors/bamboo_mat.png.toml new file mode 100644 index 0000000000000..ce1ce14e25988 --- /dev/null +++ b/icons/turf/floors/bamboo_mat.png.toml @@ -0,0 +1,2 @@ +output_name = "mat" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet.dmi b/icons/turf/floors/carpet.dmi index 1af03a1df8234..24d8c8b0dfd67 100644 Binary files a/icons/turf/floors/carpet.dmi and b/icons/turf/floors/carpet.dmi differ diff --git a/icons/turf/floors/carpet.png b/icons/turf/floors/carpet.png new file mode 100644 index 0000000000000..b69bf87a4d052 Binary files /dev/null and b/icons/turf/floors/carpet.png differ diff --git a/icons/turf/floors/carpet.png.toml b/icons/turf/floors/carpet.png.toml new file mode 100644 index 0000000000000..13916473257f2 --- /dev/null +++ b/icons/turf/floors/carpet.png.toml @@ -0,0 +1,2 @@ +output_name = "carpet" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_black.dmi b/icons/turf/floors/carpet_black.dmi index e2fea9085b230..7df942035adc4 100644 Binary files a/icons/turf/floors/carpet_black.dmi and b/icons/turf/floors/carpet_black.dmi differ diff --git a/icons/turf/floors/carpet_black.png b/icons/turf/floors/carpet_black.png new file mode 100644 index 0000000000000..57f7eaf8dd9a5 Binary files /dev/null and b/icons/turf/floors/carpet_black.png differ diff --git a/icons/turf/floors/carpet_black.png.toml b/icons/turf/floors/carpet_black.png.toml new file mode 100644 index 0000000000000..a98d0e0bc6052 --- /dev/null +++ b/icons/turf/floors/carpet_black.png.toml @@ -0,0 +1,2 @@ +output_name = "carpet_black" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_blue.dmi b/icons/turf/floors/carpet_blue.dmi index b909b11f359d9..6eea31902c8d2 100644 Binary files a/icons/turf/floors/carpet_blue.dmi and b/icons/turf/floors/carpet_blue.dmi differ diff --git a/icons/turf/floors/carpet_blue.png b/icons/turf/floors/carpet_blue.png new file mode 100644 index 0000000000000..e81e56cf7c287 Binary files /dev/null and b/icons/turf/floors/carpet_blue.png differ diff --git a/icons/turf/floors/carpet_blue.png.toml b/icons/turf/floors/carpet_blue.png.toml new file mode 100644 index 0000000000000..1e1ebdb22015f --- /dev/null +++ b/icons/turf/floors/carpet_blue.png.toml @@ -0,0 +1,2 @@ +output_name = "carpet_blue" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_cyan.dmi b/icons/turf/floors/carpet_cyan.dmi index 85e053a5a6de4..fa38993c5ceeb 100644 Binary files a/icons/turf/floors/carpet_cyan.dmi and b/icons/turf/floors/carpet_cyan.dmi differ diff --git a/icons/turf/floors/carpet_cyan.png b/icons/turf/floors/carpet_cyan.png new file mode 100644 index 0000000000000..f1dc7da076aa2 Binary files /dev/null and b/icons/turf/floors/carpet_cyan.png differ diff --git a/icons/turf/floors/carpet_cyan.png.toml b/icons/turf/floors/carpet_cyan.png.toml new file mode 100644 index 0000000000000..8c93ad0baf328 --- /dev/null +++ b/icons/turf/floors/carpet_cyan.png.toml @@ -0,0 +1,2 @@ +output_name = "carpet_cyan" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_donk.dmi b/icons/turf/floors/carpet_donk.dmi index 04c4148d2bc5a..1a8f594702435 100644 Binary files a/icons/turf/floors/carpet_donk.dmi and b/icons/turf/floors/carpet_donk.dmi differ diff --git a/icons/turf/floors/carpet_donk.png b/icons/turf/floors/carpet_donk.png new file mode 100644 index 0000000000000..eb0243c4926e1 Binary files /dev/null and b/icons/turf/floors/carpet_donk.png differ diff --git a/icons/turf/floors/carpet_donk.png.toml b/icons/turf/floors/carpet_donk.png.toml new file mode 100644 index 0000000000000..7e60d4272b8d7 --- /dev/null +++ b/icons/turf/floors/carpet_donk.png.toml @@ -0,0 +1,5 @@ +output_name = "donk_carpet" +template = "bitmask/diagonal_32x32.toml" + +[prefabs] +255 = 5 diff --git a/icons/turf/floors/carpet_executive.dmi b/icons/turf/floors/carpet_executive.dmi index 2c17e542fcd19..8c0528d8f28b3 100644 Binary files a/icons/turf/floors/carpet_executive.dmi and b/icons/turf/floors/carpet_executive.dmi differ diff --git a/icons/turf/floors/carpet_executive.png b/icons/turf/floors/carpet_executive.png new file mode 100644 index 0000000000000..bf8a538b8f824 Binary files /dev/null and b/icons/turf/floors/carpet_executive.png differ diff --git a/icons/turf/floors/carpet_executive.png.toml b/icons/turf/floors/carpet_executive.png.toml new file mode 100644 index 0000000000000..1322b9bd6d901 --- /dev/null +++ b/icons/turf/floors/carpet_executive.png.toml @@ -0,0 +1,6 @@ +output_name = "executive_carpet" +template = "bitmask/diagonal_32x32.toml" + +[prefabs] +255 = 5 + diff --git a/icons/turf/floors/carpet_green.dmi b/icons/turf/floors/carpet_green.dmi index 39ef048087481..49daef00acd02 100644 Binary files a/icons/turf/floors/carpet_green.dmi and b/icons/turf/floors/carpet_green.dmi differ diff --git a/icons/turf/floors/carpet_green.png b/icons/turf/floors/carpet_green.png new file mode 100644 index 0000000000000..ee059d755afbc Binary files /dev/null and b/icons/turf/floors/carpet_green.png differ diff --git a/icons/turf/floors/carpet_green.png.toml b/icons/turf/floors/carpet_green.png.toml new file mode 100644 index 0000000000000..88a2a120f282d --- /dev/null +++ b/icons/turf/floors/carpet_green.png.toml @@ -0,0 +1,2 @@ +output_name = "carpet_green" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_neon_base.dmi b/icons/turf/floors/carpet_neon_base.dmi new file mode 100644 index 0000000000000..342da15307691 Binary files /dev/null and b/icons/turf/floors/carpet_neon_base.dmi differ diff --git a/icons/turf/floors/carpet_neon_base.png b/icons/turf/floors/carpet_neon_base.png new file mode 100644 index 0000000000000..08b3ef22e15f8 Binary files /dev/null and b/icons/turf/floors/carpet_neon_base.png differ diff --git a/icons/turf/floors/carpet_neon_base.png.toml b/icons/turf/floors/carpet_neon_base.png.toml new file mode 100644 index 0000000000000..723e73af9e9ab --- /dev/null +++ b/icons/turf/floors/carpet_neon_base.png.toml @@ -0,0 +1,2 @@ +output_name = "base" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_neon_base_nodots.dmi b/icons/turf/floors/carpet_neon_base_nodots.dmi new file mode 100644 index 0000000000000..d5fa68a6a1230 Binary files /dev/null and b/icons/turf/floors/carpet_neon_base_nodots.dmi differ diff --git a/icons/turf/floors/carpet_neon_base_nodots.png b/icons/turf/floors/carpet_neon_base_nodots.png new file mode 100644 index 0000000000000..ae73004e95bb8 Binary files /dev/null and b/icons/turf/floors/carpet_neon_base_nodots.png differ diff --git a/icons/turf/floors/carpet_neon_base_nodots.png.toml b/icons/turf/floors/carpet_neon_base_nodots.png.toml new file mode 100644 index 0000000000000..03b019890ed35 --- /dev/null +++ b/icons/turf/floors/carpet_neon_base_nodots.png.toml @@ -0,0 +1,2 @@ +output_name = "base-nodots" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_neon_glow.dmi b/icons/turf/floors/carpet_neon_glow.dmi new file mode 100644 index 0000000000000..c1ab20145acaa Binary files /dev/null and b/icons/turf/floors/carpet_neon_glow.dmi differ diff --git a/icons/turf/floors/carpet_neon_glow.png b/icons/turf/floors/carpet_neon_glow.png new file mode 100644 index 0000000000000..1087698ae60a3 Binary files /dev/null and b/icons/turf/floors/carpet_neon_glow.png differ diff --git a/icons/turf/floors/carpet_neon_glow.png.toml b/icons/turf/floors/carpet_neon_glow.png.toml new file mode 100644 index 0000000000000..2af0ddb34bf63 --- /dev/null +++ b/icons/turf/floors/carpet_neon_glow.png.toml @@ -0,0 +1,2 @@ +output_name = "glow" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_neon_glow_nodots.dmi b/icons/turf/floors/carpet_neon_glow_nodots.dmi new file mode 100644 index 0000000000000..72175d8b3537c Binary files /dev/null and b/icons/turf/floors/carpet_neon_glow_nodots.dmi differ diff --git a/icons/turf/floors/carpet_neon_glow_nodots.png b/icons/turf/floors/carpet_neon_glow_nodots.png new file mode 100644 index 0000000000000..19b9d311b13c2 Binary files /dev/null and b/icons/turf/floors/carpet_neon_glow_nodots.png differ diff --git a/icons/turf/floors/carpet_neon_glow_nodots.png.toml b/icons/turf/floors/carpet_neon_glow_nodots.png.toml new file mode 100644 index 0000000000000..8d6e69b01c070 --- /dev/null +++ b/icons/turf/floors/carpet_neon_glow_nodots.png.toml @@ -0,0 +1,2 @@ +output_name = "glow-nodots" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_neon_light.dmi b/icons/turf/floors/carpet_neon_light.dmi new file mode 100644 index 0000000000000..38fe6ff2db548 Binary files /dev/null and b/icons/turf/floors/carpet_neon_light.dmi differ diff --git a/icons/turf/floors/carpet_neon_light.png b/icons/turf/floors/carpet_neon_light.png new file mode 100644 index 0000000000000..9443c6942903d Binary files /dev/null and b/icons/turf/floors/carpet_neon_light.png differ diff --git a/icons/turf/floors/carpet_neon_light.png.toml b/icons/turf/floors/carpet_neon_light.png.toml new file mode 100644 index 0000000000000..6ea86ad1e6503 --- /dev/null +++ b/icons/turf/floors/carpet_neon_light.png.toml @@ -0,0 +1,2 @@ +output_name = "light" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_neon_light_nodots.dmi b/icons/turf/floors/carpet_neon_light_nodots.dmi new file mode 100644 index 0000000000000..0cf86fecb67ab Binary files /dev/null and b/icons/turf/floors/carpet_neon_light_nodots.dmi differ diff --git a/icons/turf/floors/carpet_neon_light_nodots.png b/icons/turf/floors/carpet_neon_light_nodots.png new file mode 100644 index 0000000000000..fc82b593c077d Binary files /dev/null and b/icons/turf/floors/carpet_neon_light_nodots.png differ diff --git a/icons/turf/floors/carpet_neon_light_nodots.png.toml b/icons/turf/floors/carpet_neon_light_nodots.png.toml new file mode 100644 index 0000000000000..20e0713c39f48 --- /dev/null +++ b/icons/turf/floors/carpet_neon_light_nodots.png.toml @@ -0,0 +1,2 @@ +output_name = "light-nodots" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_neon_simple.dmi b/icons/turf/floors/carpet_neon_simple.dmi deleted file mode 100644 index 62cb355a2db9d..0000000000000 Binary files a/icons/turf/floors/carpet_neon_simple.dmi and /dev/null differ diff --git a/icons/turf/floors/carpet_orange.dmi b/icons/turf/floors/carpet_orange.dmi index 4f0b2078fca38..0ee9b56e8b09c 100644 Binary files a/icons/turf/floors/carpet_orange.dmi and b/icons/turf/floors/carpet_orange.dmi differ diff --git a/icons/turf/floors/carpet_orange.png b/icons/turf/floors/carpet_orange.png new file mode 100644 index 0000000000000..c58cddcf0bc18 Binary files /dev/null and b/icons/turf/floors/carpet_orange.png differ diff --git a/icons/turf/floors/carpet_orange.png.toml b/icons/turf/floors/carpet_orange.png.toml new file mode 100644 index 0000000000000..4315d81da1de0 --- /dev/null +++ b/icons/turf/floors/carpet_orange.png.toml @@ -0,0 +1,2 @@ +output_name = "carpet_orange" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_purple.dmi b/icons/turf/floors/carpet_purple.dmi index f07ce75d33406..a00c1621d3881 100644 Binary files a/icons/turf/floors/carpet_purple.dmi and b/icons/turf/floors/carpet_purple.dmi differ diff --git a/icons/turf/floors/carpet_purple.png b/icons/turf/floors/carpet_purple.png new file mode 100644 index 0000000000000..530534c5bca32 Binary files /dev/null and b/icons/turf/floors/carpet_purple.png differ diff --git a/icons/turf/floors/carpet_purple.png.toml b/icons/turf/floors/carpet_purple.png.toml new file mode 100644 index 0000000000000..708027cee7803 --- /dev/null +++ b/icons/turf/floors/carpet_purple.png.toml @@ -0,0 +1,2 @@ +output_name = "carpet_purple" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_red.dmi b/icons/turf/floors/carpet_red.dmi index eb0527f430dc4..dcba136f81150 100644 Binary files a/icons/turf/floors/carpet_red.dmi and b/icons/turf/floors/carpet_red.dmi differ diff --git a/icons/turf/floors/carpet_red.png b/icons/turf/floors/carpet_red.png new file mode 100644 index 0000000000000..3f7f190a126c4 Binary files /dev/null and b/icons/turf/floors/carpet_red.png differ diff --git a/icons/turf/floors/carpet_red.png.toml b/icons/turf/floors/carpet_red.png.toml new file mode 100644 index 0000000000000..4bbd11bfb3471 --- /dev/null +++ b/icons/turf/floors/carpet_red.png.toml @@ -0,0 +1,2 @@ +output_name = "carpet_red" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_royalblack.dmi b/icons/turf/floors/carpet_royalblack.dmi index 0b848ac1afa44..296b7b683fd0b 100644 Binary files a/icons/turf/floors/carpet_royalblack.dmi and b/icons/turf/floors/carpet_royalblack.dmi differ diff --git a/icons/turf/floors/carpet_royalblack.png b/icons/turf/floors/carpet_royalblack.png new file mode 100644 index 0000000000000..b125c6111cd44 Binary files /dev/null and b/icons/turf/floors/carpet_royalblack.png differ diff --git a/icons/turf/floors/carpet_royalblack.png.toml b/icons/turf/floors/carpet_royalblack.png.toml new file mode 100644 index 0000000000000..eef2685abeb25 --- /dev/null +++ b/icons/turf/floors/carpet_royalblack.png.toml @@ -0,0 +1,2 @@ +output_name = "carpet_royalblack" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_royalblue.dmi b/icons/turf/floors/carpet_royalblue.dmi index 1027b0e19f58e..9657d9c6704be 100644 Binary files a/icons/turf/floors/carpet_royalblue.dmi and b/icons/turf/floors/carpet_royalblue.dmi differ diff --git a/icons/turf/floors/carpet_royalblue.png b/icons/turf/floors/carpet_royalblue.png new file mode 100644 index 0000000000000..babf33777b798 Binary files /dev/null and b/icons/turf/floors/carpet_royalblue.png differ diff --git a/icons/turf/floors/carpet_royalblue.png.toml b/icons/turf/floors/carpet_royalblue.png.toml new file mode 100644 index 0000000000000..9a11f2507b21c --- /dev/null +++ b/icons/turf/floors/carpet_royalblue.png.toml @@ -0,0 +1,2 @@ +output_name = "carpet_royalblue" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/carpet_stellar.dmi b/icons/turf/floors/carpet_stellar.dmi index 6ba816784261c..2e0eec615d843 100644 Binary files a/icons/turf/floors/carpet_stellar.dmi and b/icons/turf/floors/carpet_stellar.dmi differ diff --git a/icons/turf/floors/carpet_stellar.png b/icons/turf/floors/carpet_stellar.png new file mode 100644 index 0000000000000..ab233965a6dca Binary files /dev/null and b/icons/turf/floors/carpet_stellar.png differ diff --git a/icons/turf/floors/carpet_stellar.png.toml b/icons/turf/floors/carpet_stellar.png.toml new file mode 100644 index 0000000000000..817768bad5f21 --- /dev/null +++ b/icons/turf/floors/carpet_stellar.png.toml @@ -0,0 +1,2 @@ +output_name = "stellar_carpet" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/chasms.dmi b/icons/turf/floors/chasms.dmi index 2e93b5391120c..d37fa7d50c647 100644 Binary files a/icons/turf/floors/chasms.dmi and b/icons/turf/floors/chasms.dmi differ diff --git a/icons/turf/floors/chasms.png b/icons/turf/floors/chasms.png new file mode 100644 index 0000000000000..2a856eb8aa42d Binary files /dev/null and b/icons/turf/floors/chasms.png differ diff --git a/icons/turf/floors/chasms.png.toml b/icons/turf/floors/chasms.png.toml new file mode 100644 index 0000000000000..51e70389edde9 --- /dev/null +++ b/icons/turf/floors/chasms.png.toml @@ -0,0 +1,2 @@ +output_name = "chasms" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/floor_variations.dmi b/icons/turf/floors/floor_variations.dmi new file mode 100644 index 0000000000000..81cff6d76e86d Binary files /dev/null and b/icons/turf/floors/floor_variations.dmi differ diff --git a/icons/turf/floors/glass.dmi b/icons/turf/floors/glass.dmi index ef9da477681ed..ae840919f6e5d 100644 Binary files a/icons/turf/floors/glass.dmi and b/icons/turf/floors/glass.dmi differ diff --git a/icons/turf/floors/glass.png b/icons/turf/floors/glass.png new file mode 100644 index 0000000000000..b73fff0657474 Binary files /dev/null and b/icons/turf/floors/glass.png differ diff --git a/icons/turf/floors/glass.png.toml b/icons/turf/floors/glass.png.toml new file mode 100644 index 0000000000000..2e90c6298cac5 --- /dev/null +++ b/icons/turf/floors/glass.png.toml @@ -0,0 +1,2 @@ +output_name = "glass" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/grass.dmi b/icons/turf/floors/grass.dmi index 2ffb5242562b0..0b28bc9ccee5a 100644 Binary files a/icons/turf/floors/grass.dmi and b/icons/turf/floors/grass.dmi differ diff --git a/icons/turf/floors/grass.png b/icons/turf/floors/grass.png new file mode 100644 index 0000000000000..82790712e30b7 Binary files /dev/null and b/icons/turf/floors/grass.png differ diff --git a/icons/turf/floors/grass.png.toml b/icons/turf/floors/grass.png.toml new file mode 100644 index 0000000000000..e06f8518c7c88 --- /dev/null +++ b/icons/turf/floors/grass.png.toml @@ -0,0 +1,14 @@ +output_name = "grass" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 50 +y = 50 + +[output_icon_size] +x = 50 +y = 50 + +[cut_pos] +x = 25 +y = 25 \ No newline at end of file diff --git a/icons/turf/floors/ice_turf.dmi b/icons/turf/floors/ice_turf.dmi index 61574645759c9..ccb528c872d6c 100644 Binary files a/icons/turf/floors/ice_turf.dmi and b/icons/turf/floors/ice_turf.dmi differ diff --git a/icons/turf/floors/ice_turf.png b/icons/turf/floors/ice_turf.png new file mode 100644 index 0000000000000..26887f2785377 Binary files /dev/null and b/icons/turf/floors/ice_turf.png differ diff --git a/icons/turf/floors/ice_turf.png.toml b/icons/turf/floors/ice_turf.png.toml new file mode 100644 index 0000000000000..3aa5cdf4a36a2 --- /dev/null +++ b/icons/turf/floors/ice_turf.png.toml @@ -0,0 +1,2 @@ +output_name = "ice_turf" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/icechasms.dmi b/icons/turf/floors/icechasms.dmi index 5585d792ef86e..e01521e34416d 100644 Binary files a/icons/turf/floors/icechasms.dmi and b/icons/turf/floors/icechasms.dmi differ diff --git a/icons/turf/floors/icechasms.png b/icons/turf/floors/icechasms.png new file mode 100644 index 0000000000000..c47756629b524 Binary files /dev/null and b/icons/turf/floors/icechasms.png differ diff --git a/icons/turf/floors/icechasms.png.toml b/icons/turf/floors/icechasms.png.toml new file mode 100644 index 0000000000000..6dde47bfeb25c --- /dev/null +++ b/icons/turf/floors/icechasms.png.toml @@ -0,0 +1,2 @@ +output_name = "icechasms" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/junglechasm.dmi b/icons/turf/floors/junglechasm.dmi index d2ac198865c5a..c50775c91b1c2 100644 Binary files a/icons/turf/floors/junglechasm.dmi and b/icons/turf/floors/junglechasm.dmi differ diff --git a/icons/turf/floors/junglechasm.png b/icons/turf/floors/junglechasm.png new file mode 100644 index 0000000000000..dc26f65e16c28 Binary files /dev/null and b/icons/turf/floors/junglechasm.png differ diff --git a/icons/turf/floors/junglechasm.png.toml b/icons/turf/floors/junglechasm.png.toml new file mode 100644 index 0000000000000..e210a9128bd1f --- /dev/null +++ b/icons/turf/floors/junglechasm.png.toml @@ -0,0 +1,2 @@ +output_name = "junglechasm" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/junglegrass.dmi b/icons/turf/floors/junglegrass.dmi index 80456e469b112..ef192232c3bc5 100644 Binary files a/icons/turf/floors/junglegrass.dmi and b/icons/turf/floors/junglegrass.dmi differ diff --git a/icons/turf/floors/junglegrass.png b/icons/turf/floors/junglegrass.png new file mode 100644 index 0000000000000..c1d13d4f4f273 Binary files /dev/null and b/icons/turf/floors/junglegrass.png differ diff --git a/icons/turf/floors/junglegrass.png.toml b/icons/turf/floors/junglegrass.png.toml new file mode 100644 index 0000000000000..0096e9bad7475 --- /dev/null +++ b/icons/turf/floors/junglegrass.png.toml @@ -0,0 +1,14 @@ +output_name = "junglegrass" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 50 +y = 50 + +[output_icon_size] +x = 50 +y = 50 + +[cut_pos] +x = 25 +y = 25 \ No newline at end of file diff --git a/icons/turf/floors/lava.dmi b/icons/turf/floors/lava.dmi index 3b889c9a5f68c..a2a06f34200a8 100644 Binary files a/icons/turf/floors/lava.dmi and b/icons/turf/floors/lava.dmi differ diff --git a/icons/turf/floors/lava.png b/icons/turf/floors/lava.png new file mode 100644 index 0000000000000..ef826dee188b0 Binary files /dev/null and b/icons/turf/floors/lava.png differ diff --git a/icons/turf/floors/lava.png.toml b/icons/turf/floors/lava.png.toml new file mode 100644 index 0000000000000..c4e36e85ab195 --- /dev/null +++ b/icons/turf/floors/lava.png.toml @@ -0,0 +1,5 @@ +output_name = "lava" +template = "bitmask/diagonal_32x32.toml" + +[animation] +delays = [20, 20, 20, 20] diff --git a/icons/turf/floors/lava_mask.dmi b/icons/turf/floors/lava_mask.dmi index aaefebe39deb3..3e77c406b268d 100644 Binary files a/icons/turf/floors/lava_mask.dmi and b/icons/turf/floors/lava_mask.dmi differ diff --git a/icons/turf/floors/lava_mask.png b/icons/turf/floors/lava_mask.png new file mode 100644 index 0000000000000..dfcd0dba4cb93 Binary files /dev/null and b/icons/turf/floors/lava_mask.png differ diff --git a/icons/turf/floors/lava_mask.png.toml b/icons/turf/floors/lava_mask.png.toml new file mode 100644 index 0000000000000..08d0173d5cde9 --- /dev/null +++ b/icons/turf/floors/lava_mask.png.toml @@ -0,0 +1,2 @@ +output_name = "lava" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/plasma_glass.dmi b/icons/turf/floors/plasma_glass.dmi index 83aa755b7bce1..a0e2dd20b796c 100644 Binary files a/icons/turf/floors/plasma_glass.dmi and b/icons/turf/floors/plasma_glass.dmi differ diff --git a/icons/turf/floors/plasma_glass.png b/icons/turf/floors/plasma_glass.png new file mode 100644 index 0000000000000..06e8a79b56750 Binary files /dev/null and b/icons/turf/floors/plasma_glass.png differ diff --git a/icons/turf/floors/plasma_glass.png.toml b/icons/turf/floors/plasma_glass.png.toml new file mode 100644 index 0000000000000..0fb4b5fa27614 --- /dev/null +++ b/icons/turf/floors/plasma_glass.png.toml @@ -0,0 +1,2 @@ +output_name = "plasma_glass" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/reinf_glass.dmi b/icons/turf/floors/reinf_glass.dmi index a7607cada6a7f..0713375caa6e3 100644 Binary files a/icons/turf/floors/reinf_glass.dmi and b/icons/turf/floors/reinf_glass.dmi differ diff --git a/icons/turf/floors/reinf_glass.png b/icons/turf/floors/reinf_glass.png new file mode 100644 index 0000000000000..9659f3e35c10d Binary files /dev/null and b/icons/turf/floors/reinf_glass.png differ diff --git a/icons/turf/floors/reinf_glass.png.toml b/icons/turf/floors/reinf_glass.png.toml new file mode 100644 index 0000000000000..043b1d354359b --- /dev/null +++ b/icons/turf/floors/reinf_glass.png.toml @@ -0,0 +1,2 @@ +output_name = "reinf_glass" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/reinf_plasma_glass.dmi b/icons/turf/floors/reinf_plasma_glass.dmi index 1bf4dd41734c8..31c1c339c37d3 100644 Binary files a/icons/turf/floors/reinf_plasma_glass.dmi and b/icons/turf/floors/reinf_plasma_glass.dmi differ diff --git a/icons/turf/floors/reinf_plasma_glass.png b/icons/turf/floors/reinf_plasma_glass.png new file mode 100644 index 0000000000000..6c47f299723e1 Binary files /dev/null and b/icons/turf/floors/reinf_plasma_glass.png differ diff --git a/icons/turf/floors/reinf_plasma_glass.png.toml b/icons/turf/floors/reinf_plasma_glass.png.toml new file mode 100644 index 0000000000000..1b3fbb05ef9b6 --- /dev/null +++ b/icons/turf/floors/reinf_plasma_glass.png.toml @@ -0,0 +1,2 @@ +output_name = "reinf_plasma_glass" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/floors/rocky_ash.dmi b/icons/turf/floors/rocky_ash.dmi index cc6847dc5884b..758793cba4216 100644 Binary files a/icons/turf/floors/rocky_ash.dmi and b/icons/turf/floors/rocky_ash.dmi differ diff --git a/icons/turf/floors/rocky_ash.png b/icons/turf/floors/rocky_ash.png new file mode 100644 index 0000000000000..bdc8ee3615c4b Binary files /dev/null and b/icons/turf/floors/rocky_ash.png differ diff --git a/icons/turf/floors/rocky_ash.png.toml b/icons/turf/floors/rocky_ash.png.toml new file mode 100644 index 0000000000000..6e3f50e054270 --- /dev/null +++ b/icons/turf/floors/rocky_ash.png.toml @@ -0,0 +1,14 @@ +output_name = "rocky_ash" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/turf/floors/snow_turf.dmi b/icons/turf/floors/snow_turf.dmi index 8b70aa44896fe..a150dfdc2db85 100644 Binary files a/icons/turf/floors/snow_turf.dmi and b/icons/turf/floors/snow_turf.dmi differ diff --git a/icons/turf/floors/snow_turf.png b/icons/turf/floors/snow_turf.png new file mode 100644 index 0000000000000..474a0f486e79e Binary files /dev/null and b/icons/turf/floors/snow_turf.png differ diff --git a/icons/turf/floors/snow_turf.png.toml b/icons/turf/floors/snow_turf.png.toml new file mode 100644 index 0000000000000..cc50c9afda68e --- /dev/null +++ b/icons/turf/floors/snow_turf.png.toml @@ -0,0 +1,2 @@ +output_name = "snow_turf" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/smoothrocks.dmi b/icons/turf/smoothrocks.dmi index 9a60937a2195a..e138e2af783d8 100644 Binary files a/icons/turf/smoothrocks.dmi and b/icons/turf/smoothrocks.dmi differ diff --git a/icons/turf/smoothrocks.png b/icons/turf/smoothrocks.png new file mode 100644 index 0000000000000..dea84d594f668 Binary files /dev/null and b/icons/turf/smoothrocks.png differ diff --git a/icons/turf/smoothrocks.png.toml b/icons/turf/smoothrocks.png.toml new file mode 100644 index 0000000000000..4466fbbf3c926 --- /dev/null +++ b/icons/turf/smoothrocks.png.toml @@ -0,0 +1,14 @@ +output_name = "smoothrocks" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/turf/smoothrocks_overlays.dmi b/icons/turf/smoothrocks_overlays.dmi new file mode 100644 index 0000000000000..e49a48ca09e73 Binary files /dev/null and b/icons/turf/smoothrocks_overlays.dmi differ diff --git a/icons/turf/tram.dmi b/icons/turf/tram.dmi new file mode 100644 index 0000000000000..d38802bd8de60 Binary files /dev/null and b/icons/turf/tram.dmi differ diff --git a/icons/turf/walls/abductor_wall.dmi b/icons/turf/walls/abductor_wall.dmi index d1d7d66cf0f41..4f731d99f4cd2 100644 Binary files a/icons/turf/walls/abductor_wall.dmi and b/icons/turf/walls/abductor_wall.dmi differ diff --git a/icons/turf/walls/bamboo_wall.dmi b/icons/turf/walls/bamboo_wall.dmi index 4eafc79ad48b7..44c5824397e5a 100644 Binary files a/icons/turf/walls/bamboo_wall.dmi and b/icons/turf/walls/bamboo_wall.dmi differ diff --git a/icons/turf/walls/bamboo_wall.png b/icons/turf/walls/bamboo_wall.png new file mode 100644 index 0000000000000..6869cb409b2ab Binary files /dev/null and b/icons/turf/walls/bamboo_wall.png differ diff --git a/icons/turf/walls/bamboo_wall.png.toml b/icons/turf/walls/bamboo_wall.png.toml new file mode 100644 index 0000000000000..69d9d90503837 --- /dev/null +++ b/icons/turf/walls/bamboo_wall.png.toml @@ -0,0 +1,5 @@ +output_name = "bamboo_wall" +template = "bitmask/diagonal_32x32.toml" + +[prefabs] +0 = 5 diff --git a/icons/turf/walls/bananium_wall.dmi b/icons/turf/walls/bananium_wall.dmi index abf70635337d4..7c563fca0d08d 100644 Binary files a/icons/turf/walls/bananium_wall.dmi and b/icons/turf/walls/bananium_wall.dmi differ diff --git a/icons/turf/walls/bananium_wall.png b/icons/turf/walls/bananium_wall.png new file mode 100644 index 0000000000000..52d4b2a5888b5 Binary files /dev/null and b/icons/turf/walls/bananium_wall.png differ diff --git a/icons/turf/walls/bananium_wall.png.toml b/icons/turf/walls/bananium_wall.png.toml new file mode 100644 index 0000000000000..3225586db5cc1 --- /dev/null +++ b/icons/turf/walls/bananium_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "bananium_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/boss_wall.dmi b/icons/turf/walls/boss_wall.dmi index eb0992e86e009..6bc124b077823 100644 Binary files a/icons/turf/walls/boss_wall.dmi and b/icons/turf/walls/boss_wall.dmi differ diff --git a/icons/turf/walls/boss_wall.png b/icons/turf/walls/boss_wall.png new file mode 100644 index 0000000000000..45d334f25763c Binary files /dev/null and b/icons/turf/walls/boss_wall.png differ diff --git a/icons/turf/walls/boss_wall.png.toml b/icons/turf/walls/boss_wall.png.toml new file mode 100644 index 0000000000000..4d6797e1ddaf1 --- /dev/null +++ b/icons/turf/walls/boss_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "boss_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/clockwork_wall.dmi b/icons/turf/walls/clockwork_wall.dmi index d6f1872d7c0b3..8785209409b28 100644 Binary files a/icons/turf/walls/clockwork_wall.dmi and b/icons/turf/walls/clockwork_wall.dmi differ diff --git a/icons/turf/walls/clockwork_wall.png b/icons/turf/walls/clockwork_wall.png new file mode 100644 index 0000000000000..7dcc7d6ea6a15 Binary files /dev/null and b/icons/turf/walls/clockwork_wall.png differ diff --git a/icons/turf/walls/clockwork_wall.png.toml b/icons/turf/walls/clockwork_wall.png.toml new file mode 100644 index 0000000000000..bfec1cdfd5886 --- /dev/null +++ b/icons/turf/walls/clockwork_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "clockwork_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/cult_wall.dmi b/icons/turf/walls/cult_wall.dmi index d1ea0a85413ce..51e98161d6a5f 100644 Binary files a/icons/turf/walls/cult_wall.dmi and b/icons/turf/walls/cult_wall.dmi differ diff --git a/icons/turf/walls/cult_wall.png b/icons/turf/walls/cult_wall.png new file mode 100644 index 0000000000000..c2005bdd60c39 Binary files /dev/null and b/icons/turf/walls/cult_wall.png differ diff --git a/icons/turf/walls/cult_wall.png.toml b/icons/turf/walls/cult_wall.png.toml new file mode 100644 index 0000000000000..8739156f9ac96 --- /dev/null +++ b/icons/turf/walls/cult_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "cult_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/diamond_wall.dmi b/icons/turf/walls/diamond_wall.dmi index 1dd44ba283f90..99149f0d43d9d 100644 Binary files a/icons/turf/walls/diamond_wall.dmi and b/icons/turf/walls/diamond_wall.dmi differ diff --git a/icons/turf/walls/diamond_wall.png b/icons/turf/walls/diamond_wall.png new file mode 100644 index 0000000000000..75263ac97a93f Binary files /dev/null and b/icons/turf/walls/diamond_wall.png differ diff --git a/icons/turf/walls/diamond_wall.png.toml b/icons/turf/walls/diamond_wall.png.toml new file mode 100644 index 0000000000000..e9565ac712b9d --- /dev/null +++ b/icons/turf/walls/diamond_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "diamond_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/false_walls.dmi b/icons/turf/walls/false_walls.dmi new file mode 100644 index 0000000000000..1b02b4bdbe6dd Binary files /dev/null and b/icons/turf/walls/false_walls.dmi differ diff --git a/icons/turf/walls/gold_wall.dmi b/icons/turf/walls/gold_wall.dmi index 7012124c271c2..283dd437646d5 100644 Binary files a/icons/turf/walls/gold_wall.dmi and b/icons/turf/walls/gold_wall.dmi differ diff --git a/icons/turf/walls/gold_wall.png b/icons/turf/walls/gold_wall.png new file mode 100644 index 0000000000000..13774d5d9693b Binary files /dev/null and b/icons/turf/walls/gold_wall.png differ diff --git a/icons/turf/walls/gold_wall.png.toml b/icons/turf/walls/gold_wall.png.toml new file mode 100644 index 0000000000000..c351a8eb43d7f --- /dev/null +++ b/icons/turf/walls/gold_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "gold_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/hierophant_wall_temp.dmi b/icons/turf/walls/hierophant_wall_temp.dmi index 235f8347cee44..3af6681157daa 100644 Binary files a/icons/turf/walls/hierophant_wall_temp.dmi and b/icons/turf/walls/hierophant_wall_temp.dmi differ diff --git a/icons/turf/walls/hierophant_wall_temp.png b/icons/turf/walls/hierophant_wall_temp.png new file mode 100644 index 0000000000000..ec8451a0d0ab2 Binary files /dev/null and b/icons/turf/walls/hierophant_wall_temp.png differ diff --git a/icons/turf/walls/hierophant_wall_temp.png.toml b/icons/turf/walls/hierophant_wall_temp.png.toml new file mode 100644 index 0000000000000..02d429d88218f --- /dev/null +++ b/icons/turf/walls/hierophant_wall_temp.png.toml @@ -0,0 +1,2 @@ +output_name = "hierophant_wall_temp" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/icedmetal_wall.dmi b/icons/turf/walls/icedmetal_wall.dmi index b069da4394d67..8d63b19a8bfc5 100644 Binary files a/icons/turf/walls/icedmetal_wall.dmi and b/icons/turf/walls/icedmetal_wall.dmi differ diff --git a/icons/turf/walls/icedmetal_wall.png b/icons/turf/walls/icedmetal_wall.png new file mode 100644 index 0000000000000..a8973a16b234d Binary files /dev/null and b/icons/turf/walls/icedmetal_wall.png differ diff --git a/icons/turf/walls/icedmetal_wall.png.toml b/icons/turf/walls/icedmetal_wall.png.toml new file mode 100644 index 0000000000000..6aa236a0da3c4 --- /dev/null +++ b/icons/turf/walls/icedmetal_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "icedmetal_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/icerock_wall.dmi b/icons/turf/walls/icerock_wall.dmi index 0cc2b55bffa90..652a2df113b6c 100644 Binary files a/icons/turf/walls/icerock_wall.dmi and b/icons/turf/walls/icerock_wall.dmi differ diff --git a/icons/turf/walls/icerock_wall.png b/icons/turf/walls/icerock_wall.png new file mode 100644 index 0000000000000..08b0547a937c2 Binary files /dev/null and b/icons/turf/walls/icerock_wall.png differ diff --git a/icons/turf/walls/icerock_wall.png.toml b/icons/turf/walls/icerock_wall.png.toml new file mode 100644 index 0000000000000..7d713818b562f --- /dev/null +++ b/icons/turf/walls/icerock_wall.png.toml @@ -0,0 +1,14 @@ +output_name = "icerock_wall" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/turf/walls/iron_wall.dmi b/icons/turf/walls/iron_wall.dmi index 351109bdd3fa7..d2d723adb28b0 100644 Binary files a/icons/turf/walls/iron_wall.dmi and b/icons/turf/walls/iron_wall.dmi differ diff --git a/icons/turf/walls/iron_wall.png b/icons/turf/walls/iron_wall.png new file mode 100644 index 0000000000000..ff71f25a0cb44 Binary files /dev/null and b/icons/turf/walls/iron_wall.png differ diff --git a/icons/turf/walls/iron_wall.png.toml b/icons/turf/walls/iron_wall.png.toml new file mode 100644 index 0000000000000..13708a21e29b4 --- /dev/null +++ b/icons/turf/walls/iron_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "iron_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/material_wall.dmi b/icons/turf/walls/material_wall.dmi new file mode 100644 index 0000000000000..2bd844c0b1357 Binary files /dev/null and b/icons/turf/walls/material_wall.dmi differ diff --git a/icons/turf/walls/material_wall.png b/icons/turf/walls/material_wall.png new file mode 100644 index 0000000000000..1f636cb6db09d Binary files /dev/null and b/icons/turf/walls/material_wall.png differ diff --git a/icons/turf/walls/material_wall.png.toml b/icons/turf/walls/material_wall.png.toml new file mode 100644 index 0000000000000..899ac0aec8b2d --- /dev/null +++ b/icons/turf/walls/material_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "material_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/materialwall.dmi b/icons/turf/walls/materialwall.dmi deleted file mode 100644 index c81fd64c21b77..0000000000000 Binary files a/icons/turf/walls/materialwall.dmi and /dev/null differ diff --git a/icons/turf/walls/meat_wall.dmi b/icons/turf/walls/meat_wall.dmi new file mode 100644 index 0000000000000..b587dc8e1be4e Binary files /dev/null and b/icons/turf/walls/meat_wall.dmi differ diff --git a/icons/turf/walls/meat_wall.png b/icons/turf/walls/meat_wall.png new file mode 100644 index 0000000000000..8dfafa4b33e5e Binary files /dev/null and b/icons/turf/walls/meat_wall.png differ diff --git a/icons/turf/walls/meat_wall.png.toml b/icons/turf/walls/meat_wall.png.toml new file mode 100644 index 0000000000000..0d3c9a41a9f0d --- /dev/null +++ b/icons/turf/walls/meat_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "meat_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/mountain_wall.dmi b/icons/turf/walls/mountain_wall.dmi index 574b35b7f1b7e..31ca67dc40bf2 100644 Binary files a/icons/turf/walls/mountain_wall.dmi and b/icons/turf/walls/mountain_wall.dmi differ diff --git a/icons/turf/walls/mountain_wall.png b/icons/turf/walls/mountain_wall.png new file mode 100644 index 0000000000000..47565a687eace Binary files /dev/null and b/icons/turf/walls/mountain_wall.png differ diff --git a/icons/turf/walls/mountain_wall.png.toml b/icons/turf/walls/mountain_wall.png.toml new file mode 100644 index 0000000000000..f5f413ba12186 --- /dev/null +++ b/icons/turf/walls/mountain_wall.png.toml @@ -0,0 +1,14 @@ +output_name = "mountain_wall" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/turf/walls/plasma_wall.dmi b/icons/turf/walls/plasma_wall.dmi index c2f10843c6ddc..f7219ed1edcdc 100644 Binary files a/icons/turf/walls/plasma_wall.dmi and b/icons/turf/walls/plasma_wall.dmi differ diff --git a/icons/turf/walls/plasma_wall.png b/icons/turf/walls/plasma_wall.png new file mode 100644 index 0000000000000..cdaeec65154ed Binary files /dev/null and b/icons/turf/walls/plasma_wall.png differ diff --git a/icons/turf/walls/plasma_wall.png.toml b/icons/turf/walls/plasma_wall.png.toml new file mode 100644 index 0000000000000..4425176557511 --- /dev/null +++ b/icons/turf/walls/plasma_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "plasma_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/plastinum_wall.dmi b/icons/turf/walls/plastinum_wall.dmi index a53bd0fa06289..30ea5dbf945a2 100644 Binary files a/icons/turf/walls/plastinum_wall.dmi and b/icons/turf/walls/plastinum_wall.dmi differ diff --git a/icons/turf/walls/plastitanium_wall.dmi b/icons/turf/walls/plastitanium_wall.dmi index b1af818a01200..078d4642785a9 100644 Binary files a/icons/turf/walls/plastitanium_wall.dmi and b/icons/turf/walls/plastitanium_wall.dmi differ diff --git a/icons/turf/walls/red_wall.dmi b/icons/turf/walls/red_wall.dmi index 5eb26cf6a30e8..8dc959f36cb33 100644 Binary files a/icons/turf/walls/red_wall.dmi and b/icons/turf/walls/red_wall.dmi differ diff --git a/icons/turf/walls/red_wall.png b/icons/turf/walls/red_wall.png new file mode 100644 index 0000000000000..312386a53777a Binary files /dev/null and b/icons/turf/walls/red_wall.png differ diff --git a/icons/turf/walls/red_wall.png.toml b/icons/turf/walls/red_wall.png.toml new file mode 100644 index 0000000000000..917dc8ee3771a --- /dev/null +++ b/icons/turf/walls/red_wall.png.toml @@ -0,0 +1,14 @@ +output_name = "red_wall" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/turf/walls/reinforced_rock.dmi b/icons/turf/walls/reinforced_rock.dmi index f2621f5a86382..e00e51482049e 100644 Binary files a/icons/turf/walls/reinforced_rock.dmi and b/icons/turf/walls/reinforced_rock.dmi differ diff --git a/icons/turf/walls/reinforced_rock.png b/icons/turf/walls/reinforced_rock.png new file mode 100644 index 0000000000000..544a877ec1a5a Binary files /dev/null and b/icons/turf/walls/reinforced_rock.png differ diff --git a/icons/turf/walls/reinforced_rock.png.toml b/icons/turf/walls/reinforced_rock.png.toml new file mode 100644 index 0000000000000..7fcdd7f45b9ff --- /dev/null +++ b/icons/turf/walls/reinforced_rock.png.toml @@ -0,0 +1,2 @@ +output_name = "porous_rock" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/reinforced_states.dmi b/icons/turf/walls/reinforced_states.dmi new file mode 100644 index 0000000000000..89c0114a8d6e8 Binary files /dev/null and b/icons/turf/walls/reinforced_states.dmi differ diff --git a/icons/turf/walls/reinforced_wall.dmi b/icons/turf/walls/reinforced_wall.dmi index 35d64734e1493..65267a93b2d2a 100644 Binary files a/icons/turf/walls/reinforced_wall.dmi and b/icons/turf/walls/reinforced_wall.dmi differ diff --git a/icons/turf/walls/reinforced_wall.png b/icons/turf/walls/reinforced_wall.png new file mode 100644 index 0000000000000..f8d07602c574d Binary files /dev/null and b/icons/turf/walls/reinforced_wall.png differ diff --git a/icons/turf/walls/reinforced_wall.png.toml b/icons/turf/walls/reinforced_wall.png.toml new file mode 100644 index 0000000000000..cd86b1f36f652 --- /dev/null +++ b/icons/turf/walls/reinforced_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "reinforced_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/riveted.dmi b/icons/turf/walls/riveted.dmi index e3746885e488b..08fa241e5193a 100644 Binary files a/icons/turf/walls/riveted.dmi and b/icons/turf/walls/riveted.dmi differ diff --git a/icons/turf/walls/riveted.png b/icons/turf/walls/riveted.png new file mode 100644 index 0000000000000..f6b0369a7b449 Binary files /dev/null and b/icons/turf/walls/riveted.png differ diff --git a/icons/turf/walls/riveted.png.toml b/icons/turf/walls/riveted.png.toml new file mode 100644 index 0000000000000..abc3f367d4fdf --- /dev/null +++ b/icons/turf/walls/riveted.png.toml @@ -0,0 +1,2 @@ +output_name = "riveted" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/rock_wall.dmi b/icons/turf/walls/rock_wall.dmi index b6f81475833a4..af1059489e56f 100644 Binary files a/icons/turf/walls/rock_wall.dmi and b/icons/turf/walls/rock_wall.dmi differ diff --git a/icons/turf/walls/rock_wall.png b/icons/turf/walls/rock_wall.png new file mode 100644 index 0000000000000..54288bd18a907 Binary files /dev/null and b/icons/turf/walls/rock_wall.png differ diff --git a/icons/turf/walls/rock_wall.png.toml b/icons/turf/walls/rock_wall.png.toml new file mode 100644 index 0000000000000..320b6b7dafee5 --- /dev/null +++ b/icons/turf/walls/rock_wall.png.toml @@ -0,0 +1,14 @@ +output_name = "rock_wall" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 40 +y = 40 + +[output_icon_size] +x = 40 +y = 40 + +[cut_pos] +x = 20 +y = 20 \ No newline at end of file diff --git a/icons/turf/walls/rusty_reinforced_wall.dmi b/icons/turf/walls/rusty_reinforced_wall.dmi index e2636950d27ec..0e1e10b3dab3a 100644 Binary files a/icons/turf/walls/rusty_reinforced_wall.dmi and b/icons/turf/walls/rusty_reinforced_wall.dmi differ diff --git a/icons/turf/walls/rusty_reinforced_wall.png b/icons/turf/walls/rusty_reinforced_wall.png new file mode 100644 index 0000000000000..7a1b3d4a17873 Binary files /dev/null and b/icons/turf/walls/rusty_reinforced_wall.png differ diff --git a/icons/turf/walls/rusty_reinforced_wall.png.toml b/icons/turf/walls/rusty_reinforced_wall.png.toml new file mode 100644 index 0000000000000..8bb07c3839acd --- /dev/null +++ b/icons/turf/walls/rusty_reinforced_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "rusty_reinforced_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/rusty_wall.dmi b/icons/turf/walls/rusty_wall.dmi index ca27a10fe074b..6eea2a5eae007 100644 Binary files a/icons/turf/walls/rusty_wall.dmi and b/icons/turf/walls/rusty_wall.dmi differ diff --git a/icons/turf/walls/rusty_wall.png b/icons/turf/walls/rusty_wall.png new file mode 100644 index 0000000000000..4e38523bbbb3a Binary files /dev/null and b/icons/turf/walls/rusty_wall.png differ diff --git a/icons/turf/walls/rusty_wall.png.toml b/icons/turf/walls/rusty_wall.png.toml new file mode 100644 index 0000000000000..358a6ccaa4d65 --- /dev/null +++ b/icons/turf/walls/rusty_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "rusty_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/sandstone_wall.dmi b/icons/turf/walls/sandstone_wall.dmi index d53e686ee507f..46c5b0af1c0a3 100644 Binary files a/icons/turf/walls/sandstone_wall.dmi and b/icons/turf/walls/sandstone_wall.dmi differ diff --git a/icons/turf/walls/sandstone_wall.png b/icons/turf/walls/sandstone_wall.png new file mode 100644 index 0000000000000..a000f4e934eca Binary files /dev/null and b/icons/turf/walls/sandstone_wall.png differ diff --git a/icons/turf/walls/sandstone_wall.png.toml b/icons/turf/walls/sandstone_wall.png.toml new file mode 100644 index 0000000000000..b9f06b895d188 --- /dev/null +++ b/icons/turf/walls/sandstone_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "sandstone_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/shuttle_wall.dmi b/icons/turf/walls/shuttle_wall.dmi index c96b087f77fdf..3d9374b4a30df 100644 Binary files a/icons/turf/walls/shuttle_wall.dmi and b/icons/turf/walls/shuttle_wall.dmi differ diff --git a/icons/turf/walls/silver_wall.dmi b/icons/turf/walls/silver_wall.dmi index f0f170fdfe3bf..66d337ef59d21 100644 Binary files a/icons/turf/walls/silver_wall.dmi and b/icons/turf/walls/silver_wall.dmi differ diff --git a/icons/turf/walls/silver_wall.png b/icons/turf/walls/silver_wall.png new file mode 100644 index 0000000000000..1a731d9728f1d Binary files /dev/null and b/icons/turf/walls/silver_wall.png differ diff --git a/icons/turf/walls/silver_wall.png.toml b/icons/turf/walls/silver_wall.png.toml new file mode 100644 index 0000000000000..cdfb92592c49e --- /dev/null +++ b/icons/turf/walls/silver_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "silver_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/snow_wall.dmi b/icons/turf/walls/snow_wall.dmi index 833f7245e4614..97f38eff13e00 100644 Binary files a/icons/turf/walls/snow_wall.dmi and b/icons/turf/walls/snow_wall.dmi differ diff --git a/icons/turf/walls/snow_wall.png b/icons/turf/walls/snow_wall.png new file mode 100644 index 0000000000000..ce6902fbab33f Binary files /dev/null and b/icons/turf/walls/snow_wall.png differ diff --git a/icons/turf/walls/snow_wall.png.toml b/icons/turf/walls/snow_wall.png.toml new file mode 100644 index 0000000000000..79381bb6dcb11 --- /dev/null +++ b/icons/turf/walls/snow_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "snow_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/uranium_wall.dmi b/icons/turf/walls/uranium_wall.dmi index c03886e008706..b64d270b3bdac 100644 Binary files a/icons/turf/walls/uranium_wall.dmi and b/icons/turf/walls/uranium_wall.dmi differ diff --git a/icons/turf/walls/uranium_wall.png b/icons/turf/walls/uranium_wall.png new file mode 100644 index 0000000000000..1f2c858d7c29e Binary files /dev/null and b/icons/turf/walls/uranium_wall.png differ diff --git a/icons/turf/walls/uranium_wall.png.toml b/icons/turf/walls/uranium_wall.png.toml new file mode 100644 index 0000000000000..035bd1c2af73b --- /dev/null +++ b/icons/turf/walls/uranium_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "uranium_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/wall.dmi b/icons/turf/walls/wall.dmi index 651227fe8de21..6cdd342fd9edf 100644 Binary files a/icons/turf/walls/wall.dmi and b/icons/turf/walls/wall.dmi differ diff --git a/icons/turf/walls/wall.png b/icons/turf/walls/wall.png new file mode 100644 index 0000000000000..8f95ca652cdfa Binary files /dev/null and b/icons/turf/walls/wall.png differ diff --git a/icons/turf/walls/wall.png.toml b/icons/turf/walls/wall.png.toml new file mode 100644 index 0000000000000..1264d5314a18e --- /dev/null +++ b/icons/turf/walls/wall.png.toml @@ -0,0 +1,2 @@ +output_name = "wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/wood_wall.dmi b/icons/turf/walls/wood_wall.dmi index 60b785c503014..5be00658fffa7 100644 Binary files a/icons/turf/walls/wood_wall.dmi and b/icons/turf/walls/wood_wall.dmi differ diff --git a/icons/turf/walls/wood_wall.png b/icons/turf/walls/wood_wall.png new file mode 100644 index 0000000000000..dc9c53a0b703f Binary files /dev/null and b/icons/turf/walls/wood_wall.png differ diff --git a/icons/turf/walls/wood_wall.png.toml b/icons/turf/walls/wood_wall.png.toml new file mode 100644 index 0000000000000..c40a9740a98ed --- /dev/null +++ b/icons/turf/walls/wood_wall.png.toml @@ -0,0 +1,2 @@ +output_name = "wood_wall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/ui_icons/achievements/achievements.dmi b/icons/ui_icons/achievements/achievements.dmi index b3e64c9d09f83..ff62bd7e374ef 100644 Binary files a/icons/ui_icons/achievements/achievements.dmi and b/icons/ui_icons/achievements/achievements.dmi differ diff --git a/icons/ui_icons/advisors/chem_help_advisor.gif b/icons/ui_icons/advisors/chem_help_advisor.gif deleted file mode 100644 index 47709c8ce4cf6..0000000000000 Binary files a/icons/ui_icons/advisors/chem_help_advisor.gif and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_1.png b/icons/ui_icons/patches/bandaid_1.png deleted file mode 100644 index f752da706a92c..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_1.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_2.png b/icons/ui_icons/patches/bandaid_2.png deleted file mode 100644 index 50ad473ba5d1f..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_2.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_3.png b/icons/ui_icons/patches/bandaid_3.png deleted file mode 100644 index cee1dd19b83c5..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_3.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_4.png b/icons/ui_icons/patches/bandaid_4.png deleted file mode 100644 index 3061235434c98..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_4.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_blank.png b/icons/ui_icons/patches/bandaid_blank.png deleted file mode 100644 index ddd3882922ed7..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_blank.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_both.png b/icons/ui_icons/patches/bandaid_both.png deleted file mode 100644 index 94245e560fe09..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_both.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_brute.png b/icons/ui_icons/patches/bandaid_brute.png deleted file mode 100644 index bf6350f6ced97..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_brute.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_brute_2.png b/icons/ui_icons/patches/bandaid_brute_2.png deleted file mode 100644 index 66c2d8494d556..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_brute_2.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_burn.png b/icons/ui_icons/patches/bandaid_burn.png deleted file mode 100644 index 67c6fb0dcbbb4..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_burn.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_burn_2.png b/icons/ui_icons/patches/bandaid_burn_2.png deleted file mode 100644 index db5df4f1e60ce..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_burn_2.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_clown.png b/icons/ui_icons/patches/bandaid_clown.png deleted file mode 100644 index 0912ee04eac49..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_clown.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_colonthree.png b/icons/ui_icons/patches/bandaid_colonthree.png deleted file mode 100644 index ffc7146cca49e..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_colonthree.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_exclaimationpoint.png b/icons/ui_icons/patches/bandaid_exclaimationpoint.png deleted file mode 100644 index a6a423aa20bda..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_exclaimationpoint.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_mix.png b/icons/ui_icons/patches/bandaid_mix.png deleted file mode 100644 index 1d5c03bfef6e4..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_mix.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_monke.png b/icons/ui_icons/patches/bandaid_monke.png deleted file mode 100644 index d1083aa932fd3..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_monke.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_msic.png b/icons/ui_icons/patches/bandaid_msic.png deleted file mode 100644 index ab7860c080c64..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_msic.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_questionmark.png b/icons/ui_icons/patches/bandaid_questionmark.png deleted file mode 100644 index 99382b208aec3..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_questionmark.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_suffocation.png b/icons/ui_icons/patches/bandaid_suffocation.png deleted file mode 100644 index 802baa07a8de3..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_suffocation.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_suffocation_2.png b/icons/ui_icons/patches/bandaid_suffocation_2.png deleted file mode 100644 index 20d4223ecd7f8..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_suffocation_2.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_toxin.png b/icons/ui_icons/patches/bandaid_toxin.png deleted file mode 100644 index 3a2fce33f0ffb..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_toxin.png and /dev/null differ diff --git a/icons/ui_icons/patches/bandaid_toxin_2.png b/icons/ui_icons/patches/bandaid_toxin_2.png deleted file mode 100644 index c40331e789d2a..0000000000000 Binary files a/icons/ui_icons/patches/bandaid_toxin_2.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill1.png b/icons/ui_icons/pills/pill1.png deleted file mode 100644 index e5545302761d9..0000000000000 Binary files a/icons/ui_icons/pills/pill1.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill10.png b/icons/ui_icons/pills/pill10.png deleted file mode 100644 index bb0c5f1df7177..0000000000000 Binary files a/icons/ui_icons/pills/pill10.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill11.png b/icons/ui_icons/pills/pill11.png deleted file mode 100644 index d07d604b34deb..0000000000000 Binary files a/icons/ui_icons/pills/pill11.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill12.png b/icons/ui_icons/pills/pill12.png deleted file mode 100644 index 912ce1c19e0d0..0000000000000 Binary files a/icons/ui_icons/pills/pill12.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill13.png b/icons/ui_icons/pills/pill13.png deleted file mode 100644 index 4920af9e248b7..0000000000000 Binary files a/icons/ui_icons/pills/pill13.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill14.png b/icons/ui_icons/pills/pill14.png deleted file mode 100644 index aff03f20610c4..0000000000000 Binary files a/icons/ui_icons/pills/pill14.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill15.png b/icons/ui_icons/pills/pill15.png deleted file mode 100644 index 041b5be4b6bfb..0000000000000 Binary files a/icons/ui_icons/pills/pill15.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill16.png b/icons/ui_icons/pills/pill16.png deleted file mode 100644 index 745151051b2f9..0000000000000 Binary files a/icons/ui_icons/pills/pill16.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill17.png b/icons/ui_icons/pills/pill17.png deleted file mode 100644 index fcd92288ad94f..0000000000000 Binary files a/icons/ui_icons/pills/pill17.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill18.png b/icons/ui_icons/pills/pill18.png deleted file mode 100644 index 9d730172a8e18..0000000000000 Binary files a/icons/ui_icons/pills/pill18.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill19.png b/icons/ui_icons/pills/pill19.png deleted file mode 100644 index 1e3773404d6a3..0000000000000 Binary files a/icons/ui_icons/pills/pill19.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill2.png b/icons/ui_icons/pills/pill2.png deleted file mode 100644 index e8ed754a6d3f3..0000000000000 Binary files a/icons/ui_icons/pills/pill2.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill20.png b/icons/ui_icons/pills/pill20.png deleted file mode 100644 index aa77c3e41bec6..0000000000000 Binary files a/icons/ui_icons/pills/pill20.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill21.png b/icons/ui_icons/pills/pill21.png deleted file mode 100644 index 37a4512d15d0d..0000000000000 Binary files a/icons/ui_icons/pills/pill21.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill22.png b/icons/ui_icons/pills/pill22.png deleted file mode 100644 index 374de4d58fc24..0000000000000 Binary files a/icons/ui_icons/pills/pill22.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill3.png b/icons/ui_icons/pills/pill3.png deleted file mode 100644 index e0c812fd0a44b..0000000000000 Binary files a/icons/ui_icons/pills/pill3.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill4.png b/icons/ui_icons/pills/pill4.png deleted file mode 100644 index 5e02dd0d3e003..0000000000000 Binary files a/icons/ui_icons/pills/pill4.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill5.png b/icons/ui_icons/pills/pill5.png deleted file mode 100644 index f9025d0995fd0..0000000000000 Binary files a/icons/ui_icons/pills/pill5.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill6.png b/icons/ui_icons/pills/pill6.png deleted file mode 100644 index 2dbf3720142b2..0000000000000 Binary files a/icons/ui_icons/pills/pill6.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill7.png b/icons/ui_icons/pills/pill7.png deleted file mode 100644 index 7c1ff90c43f7e..0000000000000 Binary files a/icons/ui_icons/pills/pill7.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill8.png b/icons/ui_icons/pills/pill8.png deleted file mode 100644 index 88280ecd045c8..0000000000000 Binary files a/icons/ui_icons/pills/pill8.png and /dev/null differ diff --git a/icons/ui_icons/pills/pill9.png b/icons/ui_icons/pills/pill9.png deleted file mode 100644 index 732cd4ef4c8e3..0000000000000 Binary files a/icons/ui_icons/pills/pill9.png and /dev/null differ diff --git a/lua/SS13.lua b/lua/SS13.lua index a17d5b50577bb..076dc9ee72ed7 100644 --- a/lua/SS13.lua +++ b/lua/SS13.lua @@ -1,5 +1,9 @@ local SS13 = {} +__SS13_signal_handlers = __SS13_signal_handlers or {} +__SS13_timeouts = __SS13_timeouts or {} +__SS13_timeouts_id_mapping = __SS13_timeouts_id_mapping or {} + SS13.SSlua = dm.global_vars.vars.SSlua SS13.global_proc = "some_magic_bullshit" @@ -11,6 +15,14 @@ for _, state in SS13.SSlua.vars.states do end end +function SS13.get_runner_ckey() + return SS13.state:get_var("ckey_last_runner") +end + +function SS13.get_runner_client() + return dm.global_vars:get_var("GLOB"):get_var("directory"):get(SS13.get_runner_ckey()) +end + function SS13.istype(thing, type) return dm.global_proc("_istype", thing, dm.global_proc("_text2path", type)) == 1 end @@ -65,39 +77,36 @@ function SS13.await(thing_to_call, proc_to_call, ...) return return_value, runtime_message end -function SS13.wait(time, timer) +function SS13.wait(time) local callback = SS13.new("/datum/callback", SS13.SSlua, "queue_resume", SS13.state, __next_yield_index) - local timedevent = dm.global_proc("_addtimer", callback, time * 10, 8, timer, debug.info(1, "sl")) + local timedevent = dm.global_proc("_addtimer", callback, time * 10, 8, nil, debug.info(1, "sl")) coroutine.yield() - dm.global_proc("deltimer", timedevent, timer) + dm.global_proc("deltimer", timedevent) SS13.stop_tracking(callback) end function SS13.register_signal(datum, signal, func, make_easy_clear_function) - if not SS13.signal_handlers then - SS13.signal_handlers = {} - end if not SS13.istype(datum, "/datum") then return end - if not SS13.signal_handlers[datum] then - SS13.signal_handlers[datum] = {} + if not __SS13_signal_handlers[datum] then + __SS13_signal_handlers[datum] = {} end if signal == "_cleanup" then return end - if not SS13.signal_handlers[datum][signal] then - SS13.signal_handlers[datum][signal] = {} + if not __SS13_signal_handlers[datum][signal] then + __SS13_signal_handlers[datum][signal] = {} end local callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") callback:call_proc("RegisterSignal", datum, signal, "Invoke") - local path = { "SS13", "signal_handlers", dm.global_proc("WEAKREF", datum), signal, dm.global_proc("WEAKREF", callback), "func" } + local path = { "__SS13_signal_handlers", dm.global_proc("WEAKREF", datum), signal, dm.global_proc("WEAKREF", callback), "func" } callback.vars.arguments = { path } - if not SS13.signal_handlers[datum]["_cleanup"] then - local cleanup_path = { "SS13", "signal_handlers", dm.global_proc("WEAKREF", datum), "_cleanup", "func" } + if not __SS13_signal_handlers[datum]["_cleanup"] then + local cleanup_path = { "__SS13_signal_handlers", dm.global_proc("WEAKREF", datum), "_cleanup", "func" } local cleanup_callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first", cleanup_path) cleanup_callback:call_proc("RegisterSignal", datum, "parent_qdeleting", "Invoke") - SS13.signal_handlers[datum]["_cleanup"] = { + __SS13_signal_handlers[datum]["_cleanup"] = { func = function(datum) SS13.signal_handler_cleanup(datum) SS13.stop_tracking(cleanup_callback) @@ -111,14 +120,14 @@ function SS13.register_signal(datum, signal, func, make_easy_clear_function) local lookup_for_signal = comp_lookup.entries.parent_qdeleting if lookup_for_signal and not SS13.istype(lookup_for_signal, "/datum") then local cleanup_callback_index = - dm.global_proc("_list_find", lookup_for_signal, SS13.signal_handlers[datum]["_cleanup"].callback) + dm.global_proc("_list_find", lookup_for_signal, __SS13_signal_handlers[datum]["_cleanup"].callback) if cleanup_callback_index ~= 0 and cleanup_callback_index ~= #comp_lookup then dm.global_proc("_list_swap", lookup_for_signal, cleanup_callback_index, #lookup_for_signal) end end end end - SS13.signal_handlers[datum][signal][callback] = { func = func, callback = callback } + __SS13_signal_handlers[datum][signal][callback] = { func = func, callback = callback } if make_easy_clear_function then local clear_function_name = "clear_signal_" .. tostring(datum) .. "_" .. signal .. "_" .. tostring(callback) SS13[clear_function_name] = function() @@ -148,70 +157,93 @@ function SS13.unregister_signal(datum, signal, callback) SS13.stop_tracking(handler_callback) end - if not SS13.signal_handlers then - return - end - local function clear_easy_clear_function(callback_to_clear) local clear_function_name = "clear_signal_" .. tostring(datum) .. "_" .. signal .. "_" .. tostring(callback_to_clear) SS13[clear_function_name] = nil end - if not SS13.signal_handlers[datum] then + if not __SS13_signal_handlers[datum] then return end if signal == "_cleanup" then return end - if not SS13.signal_handlers[datum][signal] then + if not __SS13_signal_handlers[datum][signal] then return end if not callback then - for handler_key, handler_info in SS13.signal_handlers[datum][signal] do + for handler_key, handler_info in __SS13_signal_handlers[datum][signal] do clear_easy_clear_function(handler_key) clear_handler(handler_info) end - SS13.signal_handlers[datum][signal] = nil + __SS13_signal_handlers[datum][signal] = nil else if not SS13.istype(callback, "/datum/callback") then return end clear_easy_clear_function(callback) - clear_handler(SS13.signal_handlers[datum][signal][callback]) - SS13.signal_handlers[datum][signal][callback] = nil + clear_handler(__SS13_signal_handlers[datum][signal][callback]) + __SS13_signal_handlers[datum][signal][callback] = nil end end function SS13.signal_handler_cleanup(datum) - if not SS13.signal_handlers then - return - end - if not SS13.signal_handlers[datum] then + if not __SS13_signal_handlers[datum] then return end - for signal, _ in SS13.signal_handlers[datum] do + for signal, _ in __SS13_signal_handlers[datum] do SS13.unregister_signal(datum, signal) end - SS13.signal_handlers[datum] = nil + __SS13_signal_handlers[datum] = nil end -function SS13.set_timeout(time, func, timer) - if not SS13.timeouts then - SS13.timeouts = {} +function SS13.set_timeout(time, func) + SS13.start_loop(time, 1, func) +end + +function SS13.start_loop(time, amount, func) + if not amount or amount == 0 then + return end local callback = SS13.new("/datum/callback", SS13.state, "call_function") - local timedevent = dm.global_proc("_addtimer", callback, time * 10, 8, timer, debug.info(1, "sl")) - SS13.timeouts[callback] = function() - SS13.timeouts[callback] = nil - dm.global_proc("deltimer", timedevent, timer) - SS13.stop_tracking(callback) + local timedevent = dm.global_proc("_addtimer", callback, time * 10, 40, nil, debug.info(1, "sl")) + local doneAmount = 0 + __SS13_timeouts[callback] = function() + doneAmount += 1 + if amount ~= -1 and doneAmount >= amount then + SS13.end_loop(timedevent) + end func() end - local path = { "SS13", "timeouts", dm.global_proc("WEAKREF", callback) } + local loop_data = { + callback = callback, + loop_amount = amount, + } + __SS13_timeouts_id_mapping[timedevent] = loop_data + local path = { "__SS13_timeouts", dm.global_proc("WEAKREF", callback) } callback.vars.arguments = { path } + return timedevent +end + +function SS13.end_loop(id) + local data = __SS13_timeouts_id_mapping[id] + if data then + __SS13_timeouts_id_mapping[id] = nil + __SS13_timeouts[data.callback] = nil + SS13.stop_tracking(data.callback) + dm.global_proc("deltimer", id) + end +end + +function SS13.stop_all_loops() + for id, data in __SS13_timeouts_id_mapping do + if data.amount ~= 1 then + SS13.end_loop(id) + end + end end return SS13 diff --git a/rust_g.dll b/rust_g.dll index cfb52ffc65a03..30f63e72f4b1d 100644 Binary files a/rust_g.dll and b/rust_g.dll differ diff --git a/sound/ambience/antag/bloodcult/bloodcult_eyes.ogg b/sound/ambience/antag/bloodcult/bloodcult_eyes.ogg new file mode 100644 index 0000000000000..38c223b1ad858 Binary files /dev/null and b/sound/ambience/antag/bloodcult/bloodcult_eyes.ogg differ diff --git a/sound/ambience/antag/bloodcult.ogg b/sound/ambience/antag/bloodcult/bloodcult_gain.ogg similarity index 100% rename from sound/ambience/antag/bloodcult.ogg rename to sound/ambience/antag/bloodcult/bloodcult_gain.ogg diff --git a/sound/ambience/antag/bloodcult/bloodcult_halos.ogg b/sound/ambience/antag/bloodcult/bloodcult_halos.ogg new file mode 100644 index 0000000000000..bd22934fd301b Binary files /dev/null and b/sound/ambience/antag/bloodcult/bloodcult_halos.ogg differ diff --git a/sound/ambience/antag/bloodcult/bloodcult_scribe.ogg b/sound/ambience/antag/bloodcult/bloodcult_scribe.ogg new file mode 100644 index 0000000000000..a01ef30a1d487 Binary files /dev/null and b/sound/ambience/antag/bloodcult/bloodcult_scribe.ogg differ diff --git a/sound/attributions.txt b/sound/attributions.txt index 09ac2bd56429d..82486a5735da0 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -99,3 +99,41 @@ https://freesound.org/people/FunWithSound/sounds/456965/ beeps_jingle.ogg is adapted from Eponn's "Achievement happy Beeps Jingle", which is public domain (CC 0): https://freesound.org/people/Eponn/sounds/619838/ + +boing.ogg is adapted from reelworldstudio's "Cartoon Boing", which is public domain (CC 0): +https://freesound.org/people/reelworldstudio/sounds/161122/ + +arcade_jump.ogg is adapted from se2001's "8-Bit Jump 3", which is public domain (CC 0): +hhttps://freesound.org/people/se2001/sounds/528568/ + +hiccup sfx is taken from SOUNDFISHING's hiccup sound effects, which is license under LESF and allowed ussage for video games under section 4 of aggreement: +https://www.soundfishing.eu/sound/hiccup + +laser2.ogg is adapted with 3 SFX made by junggle (CC 4), inferno (CC Sampling+), humanoide9000 (CC 0): +https://freesound.org/people/junggle/sounds/28917/ +https://freesound.org/people/inferno/sounds/18397/ +https://freesound.org/people/humanoide9000/sounds/330293/ + +reel1.ogg, reel2.ogg, reel3.ogg, reel4.ogg and reel5.ogg adapted from pixabay. Free for use under the Pixabay Content License (https://pixabay.com/service/license-summary/): +https://pixabay.com/sound-effects/reel-78063/ + +throw.ogg, throwhard.ogg and throwsoft.ogg (Royalty-Free and Copyright-Free) are adapted from Jam FX, SmartSound FX and Epic Stock Media in : +https://uppbeat.io/sfx/whoosh-swift-cut/7727/23617 +https://uppbeat.io/sfx/whoosh-air-punch/114/1168 +https://uppbeat.io/sfx/throwing-item-swing/9430/25267 + +rock_break.ogg is taken from Bertsz's Rock Destroy from Freesound.org, CC0: +https://freesound.org/people/Bertsz/sounds/524312/ + +sonar-ping.ogg and radar-ping is taken and modified from SamsterBirdies' Sonar Ping from Freesound.org, CC0: +https://freesound.org/people/SamsterBirdies/sounds/539957/ + +manual_teleport.ogg is taken from Quetzakol's teleport from Freesound.org, CC0: +https://freesound.org/people/Quetzakol/sounds/520743/ + +auto_teleport.ogg is taken from Steaq's A teleportation from Freesound.org, CC0: +https://freesound.org/people/steaq/sounds/560124/ + +refinery.ogg, smelter.ogg, and wooping_teleport.ogg are all original works by ArcaneMusic, +with smelter.ogg using samples from rock_break alongside original sound effects from a warrior electric drill, +where refinery and wooping_teleport are modifications of that same electric drill recording. diff --git a/sound/creatures/attribution.txt b/sound/creatures/attribution.txt index 1d2d543aa1506..06d8361868cb3 100644 --- a/sound/creatures/attribution.txt +++ b/sound/creatures/attribution.txt @@ -1,8 +1,14 @@ -cow.ogg sound adapted from Benboncan on Freesound +cow.ogg sound adapted from Benboncan on Freesound https://freesound.org/people/Benboncan/sounds/58277/ pig1.ogg and pig2.ogg adapted from Jofae on Freesound https://freesound.org/people/Jofae/sounds/352698/ sheep1, sheep2, and sheep3.ogg adapted from milkotz on Freesound -https://freesound.org/people/milkotz/sounds/618865/ \ No newline at end of file +https://freesound.org/people/milkotz/sounds/618865/ + +snake_hissing1.ogg adapted from schreibsel on Freesound (CC 0) +https://freesound.org/people/schreibsel/sounds/540162/ + +snake_hissing2.ogg adapted from xoiziox on Freesound (CC 0) +https://freesound.org/people/xoiziox/sounds/553374/ diff --git a/sound/creatures/bagawk.ogg b/sound/creatures/bagawk.ogg new file mode 100644 index 0000000000000..bfdce2da4894b Binary files /dev/null and b/sound/creatures/bagawk.ogg differ diff --git a/sound/creatures/chick_peep.ogg b/sound/creatures/chick_peep.ogg new file mode 100644 index 0000000000000..1e84d1d765fd8 Binary files /dev/null and b/sound/creatures/chick_peep.ogg differ diff --git a/sound/creatures/chitter.ogg b/sound/creatures/chitter.ogg new file mode 100644 index 0000000000000..5b2a144388673 Binary files /dev/null and b/sound/creatures/chitter.ogg differ diff --git a/sound/creatures/claw_click.ogg b/sound/creatures/claw_click.ogg new file mode 100644 index 0000000000000..965b4c3fa9f5a Binary files /dev/null and b/sound/creatures/claw_click.ogg differ diff --git a/sound/creatures/clucks.ogg b/sound/creatures/clucks.ogg new file mode 100644 index 0000000000000..176f46f866f04 Binary files /dev/null and b/sound/creatures/clucks.ogg differ diff --git a/sound/effects/mousesqueek.ogg b/sound/creatures/mousesqueek.ogg similarity index 100% rename from sound/effects/mousesqueek.ogg rename to sound/creatures/mousesqueek.ogg diff --git a/sound/creatures/pony/snort.ogg b/sound/creatures/pony/snort.ogg index b023ddcf47c3a..0ea56ad957dcb 100644 Binary files a/sound/creatures/pony/snort.ogg and b/sound/creatures/pony/snort.ogg differ diff --git a/sound/creatures/snake_hissing1.ogg b/sound/creatures/snake_hissing1.ogg new file mode 100644 index 0000000000000..52a37d764c426 Binary files /dev/null and b/sound/creatures/snake_hissing1.ogg differ diff --git a/sound/creatures/snake_hissing2.ogg b/sound/creatures/snake_hissing2.ogg new file mode 100644 index 0000000000000..bd11b7fb5f0fa Binary files /dev/null and b/sound/creatures/snake_hissing2.ogg differ diff --git a/sound/effects/arcade_jump.ogg b/sound/effects/arcade_jump.ogg new file mode 100644 index 0000000000000..65f0cc448b564 Binary files /dev/null and b/sound/effects/arcade_jump.ogg differ diff --git a/sound/effects/boing.ogg b/sound/effects/boing.ogg new file mode 100644 index 0000000000000..8328cc3392613 Binary files /dev/null and b/sound/effects/boing.ogg differ diff --git a/sound/effects/confirmdropoff.ogg b/sound/effects/confirmdropoff.ogg new file mode 100644 index 0000000000000..835d931992180 Binary files /dev/null and b/sound/effects/confirmdropoff.ogg differ diff --git a/sound/effects/footstep/spurs1.ogg b/sound/effects/footstep/spurs1.ogg new file mode 100644 index 0000000000000..d2754587ca15e Binary files /dev/null and b/sound/effects/footstep/spurs1.ogg differ diff --git a/sound/effects/footstep/spurs2.ogg b/sound/effects/footstep/spurs2.ogg new file mode 100644 index 0000000000000..e02725e9079bb Binary files /dev/null and b/sound/effects/footstep/spurs2.ogg differ diff --git a/sound/effects/footstep/spurs3.ogg b/sound/effects/footstep/spurs3.ogg new file mode 100644 index 0000000000000..e79b90dc78d9f Binary files /dev/null and b/sound/effects/footstep/spurs3.ogg differ diff --git a/sound/effects/moon_parade.ogg b/sound/effects/moon_parade.ogg new file mode 100644 index 0000000000000..2b18ce3295270 Binary files /dev/null and b/sound/effects/moon_parade.ogg differ diff --git a/sound/effects/moon_parade_soundloop.ogg b/sound/effects/moon_parade_soundloop.ogg new file mode 100644 index 0000000000000..c7879b6488cbd Binary files /dev/null and b/sound/effects/moon_parade_soundloop.ogg differ diff --git a/sound/effects/rock_break.ogg b/sound/effects/rock_break.ogg new file mode 100644 index 0000000000000..09f6b1d5d33c4 Binary files /dev/null and b/sound/effects/rock_break.ogg differ diff --git a/sound/effects/sf_hiccup_male_01.ogg b/sound/effects/sf_hiccup_male_01.ogg new file mode 100644 index 0000000000000..bdff5eb24b83a Binary files /dev/null and b/sound/effects/sf_hiccup_male_01.ogg differ diff --git a/sound/effects/submerge.ogg b/sound/effects/submerge.ogg new file mode 100644 index 0000000000000..8c50fba8e0a73 Binary files /dev/null and b/sound/effects/submerge.ogg differ diff --git a/sound/items/frog_statue_release.ogg b/sound/items/frog_statue_release.ogg new file mode 100644 index 0000000000000..de7d3547778a9 Binary files /dev/null and b/sound/items/frog_statue_release.ogg differ diff --git a/sound/items/reel1.ogg b/sound/items/reel1.ogg new file mode 100644 index 0000000000000..0bd2cda89b973 Binary files /dev/null and b/sound/items/reel1.ogg differ diff --git a/sound/items/reel2.ogg b/sound/items/reel2.ogg new file mode 100644 index 0000000000000..64d2bc1adb494 Binary files /dev/null and b/sound/items/reel2.ogg differ diff --git a/sound/items/reel3.ogg b/sound/items/reel3.ogg new file mode 100644 index 0000000000000..a1d89779ec11f Binary files /dev/null and b/sound/items/reel3.ogg differ diff --git a/sound/items/reel4.ogg b/sound/items/reel4.ogg new file mode 100644 index 0000000000000..ae9bdb2f5e373 Binary files /dev/null and b/sound/items/reel4.ogg differ diff --git a/sound/items/reel5.ogg b/sound/items/reel5.ogg new file mode 100644 index 0000000000000..6c979754a5f86 Binary files /dev/null and b/sound/items/reel5.ogg differ diff --git a/sound/machines/fan_break.ogg b/sound/machines/fan_break.ogg new file mode 100644 index 0000000000000..ca0549333ad66 Binary files /dev/null and b/sound/machines/fan_break.ogg differ diff --git a/sound/machines/fan_loop.ogg b/sound/machines/fan_loop.ogg new file mode 100644 index 0000000000000..9c7820548f670 Binary files /dev/null and b/sound/machines/fan_loop.ogg differ diff --git a/sound/machines/fan_start.ogg b/sound/machines/fan_start.ogg new file mode 100644 index 0000000000000..a0d11c3e969aa Binary files /dev/null and b/sound/machines/fan_start.ogg differ diff --git a/sound/machines/fan_stop.ogg b/sound/machines/fan_stop.ogg new file mode 100644 index 0000000000000..84d39c3ee5a85 Binary files /dev/null and b/sound/machines/fan_stop.ogg differ diff --git a/sound/machines/mining/auto_teleport.ogg b/sound/machines/mining/auto_teleport.ogg new file mode 100644 index 0000000000000..a8fe669e8657c Binary files /dev/null and b/sound/machines/mining/auto_teleport.ogg differ diff --git a/sound/machines/mining/manual_teleport.ogg b/sound/machines/mining/manual_teleport.ogg new file mode 100644 index 0000000000000..b011ef91e65af Binary files /dev/null and b/sound/machines/mining/manual_teleport.ogg differ diff --git a/sound/machines/mining/refinery.ogg b/sound/machines/mining/refinery.ogg new file mode 100644 index 0000000000000..fdae21f9a3010 Binary files /dev/null and b/sound/machines/mining/refinery.ogg differ diff --git a/sound/machines/mining/smelter.ogg b/sound/machines/mining/smelter.ogg new file mode 100644 index 0000000000000..b1d65f3bd2034 Binary files /dev/null and b/sound/machines/mining/smelter.ogg differ diff --git a/sound/machines/mining/wooping_teleport.ogg b/sound/machines/mining/wooping_teleport.ogg new file mode 100644 index 0000000000000..edf5eb57b432e Binary files /dev/null and b/sound/machines/mining/wooping_teleport.ogg differ diff --git a/sound/machines/radar-ping.ogg b/sound/machines/radar-ping.ogg new file mode 100644 index 0000000000000..d6fd0000d1a5e Binary files /dev/null and b/sound/machines/radar-ping.ogg differ diff --git a/sound/machines/sonar-ping.ogg b/sound/machines/sonar-ping.ogg new file mode 100644 index 0000000000000..c69d43520958d Binary files /dev/null and b/sound/machines/sonar-ping.ogg differ diff --git a/sound/misc/salute.ogg b/sound/misc/salute.ogg new file mode 100644 index 0000000000000..76521a63540ec Binary files /dev/null and b/sound/misc/salute.ogg differ diff --git a/sound/voice/medbot/i_am_chicken.ogg b/sound/voice/medbot/i_am_chicken.ogg new file mode 100644 index 0000000000000..d1c4465505f39 Binary files /dev/null and b/sound/voice/medbot/i_am_chicken.ogg differ diff --git a/sound/voice/moth/credit.txt b/sound/voice/moth/credit.txt new file mode 100644 index 0000000000000..7f64b72841e32 --- /dev/null +++ b/sound/voice/moth/credit.txt @@ -0,0 +1,5 @@ +"moth_flutter" modified from +https://freesound.org/people/Godowan/sounds/240476/ +(CC 0 license) + +who knows where the original moth scream noise was I sure as hell don't \ No newline at end of file diff --git a/sound/voice/moth/moth_death.ogg b/sound/voice/moth/moth_death.ogg new file mode 100644 index 0000000000000..df23cfa472ac1 Binary files /dev/null and b/sound/voice/moth/moth_death.ogg differ diff --git a/sound/voice/moth/moth_flutter.ogg b/sound/voice/moth/moth_flutter.ogg new file mode 100644 index 0000000000000..f5737d522ca20 Binary files /dev/null and b/sound/voice/moth/moth_flutter.ogg differ diff --git a/sound/voice/sec_death.ogg b/sound/voice/sec_death.ogg new file mode 100644 index 0000000000000..25f9b24c313f2 Binary files /dev/null and b/sound/voice/sec_death.ogg differ diff --git a/sound/vox_fem/alarmed.ogg b/sound/vox_fem/alarmed.ogg new file mode 100644 index 0000000000000..a24b7caf99a29 Binary files /dev/null and b/sound/vox_fem/alarmed.ogg differ diff --git a/sound/vox_fem/alarming.ogg b/sound/vox_fem/alarming.ogg new file mode 100644 index 0000000000000..35d51efd921d5 Binary files /dev/null and b/sound/vox_fem/alarming.ogg differ diff --git a/sound/vox_fem/alerted.ogg b/sound/vox_fem/alerted.ogg new file mode 100644 index 0000000000000..2365c103108b1 Binary files /dev/null and b/sound/vox_fem/alerted.ogg differ diff --git a/sound/vox_fem/alerting.ogg b/sound/vox_fem/alerting.ogg new file mode 100644 index 0000000000000..444e484665e90 Binary files /dev/null and b/sound/vox_fem/alerting.ogg differ diff --git a/sound/vox_fem/artillery.ogg b/sound/vox_fem/artillery.ogg new file mode 100644 index 0000000000000..ab072d72584c3 Binary files /dev/null and b/sound/vox_fem/artillery.ogg differ diff --git a/sound/vox_fem/big.ogg b/sound/vox_fem/big.ogg new file mode 100644 index 0000000000000..662e3a54f7a1a Binary files /dev/null and b/sound/vox_fem/big.ogg differ diff --git a/sound/vox_fem/billion.ogg b/sound/vox_fem/billion.ogg new file mode 100644 index 0000000000000..126cc1d8049b7 Binary files /dev/null and b/sound/vox_fem/billion.ogg differ diff --git a/sound/vox_fem/bitrun.ogg b/sound/vox_fem/bitrun.ogg new file mode 100644 index 0000000000000..159cc01bfb401 Binary files /dev/null and b/sound/vox_fem/bitrun.ogg differ diff --git a/sound/vox_fem/bitrunner.ogg b/sound/vox_fem/bitrunner.ogg new file mode 100644 index 0000000000000..32b2220ba67bc Binary files /dev/null and b/sound/vox_fem/bitrunner.ogg differ diff --git a/sound/vox_fem/bitrunning.ogg b/sound/vox_fem/bitrunning.ogg new file mode 100644 index 0000000000000..106757e007733 Binary files /dev/null and b/sound/vox_fem/bitrunning.ogg differ diff --git a/sound/vox_fem/bluespace.ogg b/sound/vox_fem/bluespace.ogg new file mode 100644 index 0000000000000..eef607da7491c Binary files /dev/null and b/sound/vox_fem/bluespace.ogg differ diff --git a/sound/vox_fem/christmas.ogg b/sound/vox_fem/christmas.ogg new file mode 100644 index 0000000000000..c1bfc707b79a1 Binary files /dev/null and b/sound/vox_fem/christmas.ogg differ diff --git a/sound/vox_fem/closed.ogg b/sound/vox_fem/closed.ogg new file mode 100644 index 0000000000000..a20be2a50af51 Binary files /dev/null and b/sound/vox_fem/closed.ogg differ diff --git a/sound/vox_fem/closing.ogg b/sound/vox_fem/closing.ogg new file mode 100644 index 0000000000000..0d5d99667fde4 Binary files /dev/null and b/sound/vox_fem/closing.ogg differ diff --git a/sound/vox_fem/died.ogg b/sound/vox_fem/died.ogg new file mode 100644 index 0000000000000..bf11460779fdd Binary files /dev/null and b/sound/vox_fem/died.ogg differ diff --git a/sound/vox_fem/enormous.ogg b/sound/vox_fem/enormous.ogg new file mode 100644 index 0000000000000..040b31b5d688a Binary files /dev/null and b/sound/vox_fem/enormous.ogg differ diff --git a/sound/vox_fem/ethereal.ogg b/sound/vox_fem/ethereal.ogg new file mode 100644 index 0000000000000..ec27cdefb5ae2 Binary files /dev/null and b/sound/vox_fem/ethereal.ogg differ diff --git a/sound/vox_fem/ever.ogg b/sound/vox_fem/ever.ogg new file mode 100644 index 0000000000000..54cf407148e61 Binary files /dev/null and b/sound/vox_fem/ever.ogg differ diff --git a/sound/vox_fem/execute.ogg b/sound/vox_fem/execute.ogg new file mode 100644 index 0000000000000..f77c035d9773c Binary files /dev/null and b/sound/vox_fem/execute.ogg differ diff --git a/sound/vox_fem/felinid.ogg b/sound/vox_fem/felinid.ogg new file mode 100644 index 0000000000000..b40747fb1178e Binary files /dev/null and b/sound/vox_fem/felinid.ogg differ diff --git a/sound/vox_fem/forty.ogg b/sound/vox_fem/forty.ogg new file mode 100644 index 0000000000000..45bae51678a17 Binary files /dev/null and b/sound/vox_fem/forty.ogg differ diff --git a/sound/vox_fem/freeze.ogg b/sound/vox_fem/freeze.ogg new file mode 100644 index 0000000000000..fce7515b196d8 Binary files /dev/null and b/sound/vox_fem/freeze.ogg differ diff --git a/sound/vox_fem/froze.ogg b/sound/vox_fem/froze.ogg new file mode 100644 index 0000000000000..512cc6c8169c8 Binary files /dev/null and b/sound/vox_fem/froze.ogg differ diff --git a/sound/vox_fem/frozen.ogg b/sound/vox_fem/frozen.ogg new file mode 100644 index 0000000000000..2a21298cb4561 Binary files /dev/null and b/sound/vox_fem/frozen.ogg differ diff --git a/sound/vox_fem/had.ogg b/sound/vox_fem/had.ogg new file mode 100644 index 0000000000000..c04a0fd2cbcf5 Binary files /dev/null and b/sound/vox_fem/had.ogg differ diff --git a/sound/vox_fem/her.ogg b/sound/vox_fem/her.ogg new file mode 100644 index 0000000000000..ea7788787545a Binary files /dev/null and b/sound/vox_fem/her.ogg differ diff --git a/sound/vox_fem/heretic.ogg b/sound/vox_fem/heretic.ogg new file mode 100644 index 0000000000000..ef8a3b2bbbfcc Binary files /dev/null and b/sound/vox_fem/heretic.ogg differ diff --git a/sound/vox_fem/him.ogg b/sound/vox_fem/him.ogg new file mode 100644 index 0000000000000..fa5658df4a2c9 Binary files /dev/null and b/sound/vox_fem/him.ogg differ diff --git a/sound/vox_fem/invalidate.ogg b/sound/vox_fem/invalidate.ogg new file mode 100644 index 0000000000000..77259d6034f38 Binary files /dev/null and b/sound/vox_fem/invalidate.ogg differ diff --git a/sound/vox_fem/jolly.ogg b/sound/vox_fem/jolly.ogg new file mode 100644 index 0000000000000..6989b7db318e2 Binary files /dev/null and b/sound/vox_fem/jolly.ogg differ diff --git a/sound/vox_fem/killer.ogg b/sound/vox_fem/killer.ogg new file mode 100644 index 0000000000000..2abe351ecf264 Binary files /dev/null and b/sound/vox_fem/killer.ogg differ diff --git a/sound/vox_fem/large.ogg b/sound/vox_fem/large.ogg new file mode 100644 index 0000000000000..54892873434fa Binary files /dev/null and b/sound/vox_fem/large.ogg differ diff --git a/sound/vox_fem/lightbulb.ogg b/sound/vox_fem/lightbulb.ogg new file mode 100644 index 0000000000000..9fbbe76fd5954 Binary files /dev/null and b/sound/vox_fem/lightbulb.ogg differ diff --git a/sound/vox_fem/lizardperson.ogg b/sound/vox_fem/lizardperson.ogg new file mode 100644 index 0000000000000..c812f28b31a9b Binary files /dev/null and b/sound/vox_fem/lizardperson.ogg differ diff --git a/sound/vox_fem/maintainer.ogg b/sound/vox_fem/maintainer.ogg new file mode 100644 index 0000000000000..5a1438028fd03 Binary files /dev/null and b/sound/vox_fem/maintainer.ogg differ diff --git a/sound/vox_fem/major.ogg b/sound/vox_fem/major.ogg new file mode 100644 index 0000000000000..f5de35ef31260 Binary files /dev/null and b/sound/vox_fem/major.ogg differ diff --git a/sound/vox_fem/minor.ogg b/sound/vox_fem/minor.ogg new file mode 100644 index 0000000000000..067f4a5d3df84 Binary files /dev/null and b/sound/vox_fem/minor.ogg differ diff --git a/sound/vox_fem/mothperson.ogg b/sound/vox_fem/mothperson.ogg new file mode 100644 index 0000000000000..52c0a645428de Binary files /dev/null and b/sound/vox_fem/mothperson.ogg differ diff --git a/sound/vox_fem/murderer.ogg b/sound/vox_fem/murderer.ogg new file mode 100644 index 0000000000000..8dbab2157edf8 Binary files /dev/null and b/sound/vox_fem/murderer.ogg differ diff --git a/sound/vox_fem/never.ogg b/sound/vox_fem/never.ogg new file mode 100644 index 0000000000000..fc1135af2c892 Binary files /dev/null and b/sound/vox_fem/never.ogg differ diff --git a/sound/vox_fem/night.ogg b/sound/vox_fem/night.ogg new file mode 100644 index 0000000000000..e412daf5ed29a Binary files /dev/null and b/sound/vox_fem/night.ogg differ diff --git a/sound/vox_fem/northeast.ogg b/sound/vox_fem/northeast.ogg new file mode 100644 index 0000000000000..4f4206c4e1859 Binary files /dev/null and b/sound/vox_fem/northeast.ogg differ diff --git a/sound/vox_fem/northwest.ogg b/sound/vox_fem/northwest.ogg new file mode 100644 index 0000000000000..96110fc28147f Binary files /dev/null and b/sound/vox_fem/northwest.ogg differ diff --git a/sound/vox_fem/obliterate.ogg b/sound/vox_fem/obliterate.ogg new file mode 100644 index 0000000000000..b13dcbc686d9a Binary files /dev/null and b/sound/vox_fem/obliterate.ogg differ diff --git a/sound/vox_fem/obliterated.ogg b/sound/vox_fem/obliterated.ogg new file mode 100644 index 0000000000000..884e4bcb97997 Binary files /dev/null and b/sound/vox_fem/obliterated.ogg differ diff --git a/sound/vox_fem/obliterating.ogg b/sound/vox_fem/obliterating.ogg new file mode 100644 index 0000000000000..034d3252cc3ba Binary files /dev/null and b/sound/vox_fem/obliterating.ogg differ diff --git a/sound/vox_fem/okay.ogg b/sound/vox_fem/okay.ogg new file mode 100644 index 0000000000000..47d061fb060f9 Binary files /dev/null and b/sound/vox_fem/okay.ogg differ diff --git a/sound/vox_fem/once.ogg b/sound/vox_fem/once.ogg new file mode 100644 index 0000000000000..d7e95b5f6bd4f Binary files /dev/null and b/sound/vox_fem/once.ogg differ diff --git a/sound/vox_fem/opened.ogg b/sound/vox_fem/opened.ogg new file mode 100644 index 0000000000000..6327c3991ae6d Binary files /dev/null and b/sound/vox_fem/opened.ogg differ diff --git a/sound/vox_fem/opening.ogg b/sound/vox_fem/opening.ogg new file mode 100644 index 0000000000000..d4371904cdefc Binary files /dev/null and b/sound/vox_fem/opening.ogg differ diff --git a/sound/vox_fem/perhaps.ogg b/sound/vox_fem/perhaps.ogg new file mode 100644 index 0000000000000..191587d27a990 Binary files /dev/null and b/sound/vox_fem/perhaps.ogg differ diff --git a/sound/vox_fem/present.ogg b/sound/vox_fem/present.ogg new file mode 100644 index 0000000000000..7e4bf2a650c7b Binary files /dev/null and b/sound/vox_fem/present.ogg differ diff --git a/sound/vox_fem/presents.ogg b/sound/vox_fem/presents.ogg new file mode 100644 index 0000000000000..368c5b554f3da Binary files /dev/null and b/sound/vox_fem/presents.ogg differ diff --git a/sound/vox_fem/request.ogg b/sound/vox_fem/request.ogg new file mode 100644 index 0000000000000..487b0f6772d67 Binary files /dev/null and b/sound/vox_fem/request.ogg differ diff --git a/sound/vox_fem/requested.ogg b/sound/vox_fem/requested.ogg new file mode 100644 index 0000000000000..d204bd91edcff Binary files /dev/null and b/sound/vox_fem/requested.ogg differ diff --git a/sound/vox_fem/requesting.ogg b/sound/vox_fem/requesting.ogg new file mode 100644 index 0000000000000..bea1e652e4ba5 Binary files /dev/null and b/sound/vox_fem/requesting.ogg differ diff --git a/sound/vox_fem/small.ogg b/sound/vox_fem/small.ogg new file mode 100644 index 0000000000000..8a104996b8ec0 Binary files /dev/null and b/sound/vox_fem/small.ogg differ diff --git a/sound/vox_fem/sockmuncher.ogg b/sound/vox_fem/sockmuncher.ogg new file mode 100644 index 0000000000000..7c40fdc79e7b6 Binary files /dev/null and b/sound/vox_fem/sockmuncher.ogg differ diff --git a/sound/vox_fem/southeast.ogg b/sound/vox_fem/southeast.ogg new file mode 100644 index 0000000000000..bdf2c8026bfaa Binary files /dev/null and b/sound/vox_fem/southeast.ogg differ diff --git a/sound/vox_fem/southwest.ogg b/sound/vox_fem/southwest.ogg new file mode 100644 index 0000000000000..a1ea2766bb500 Binary files /dev/null and b/sound/vox_fem/southwest.ogg differ diff --git a/sound/vox_fem/taildragger.ogg b/sound/vox_fem/taildragger.ogg new file mode 100644 index 0000000000000..e6211e368f79d Binary files /dev/null and b/sound/vox_fem/taildragger.ogg differ diff --git a/sound/vox_fem/teleporter.ogg b/sound/vox_fem/teleporter.ogg new file mode 100644 index 0000000000000..8b237573af19c Binary files /dev/null and b/sound/vox_fem/teleporter.ogg differ diff --git a/sound/vox_fem/terminate.ogg b/sound/vox_fem/terminate.ogg new file mode 100644 index 0000000000000..fa6d03a5f3566 Binary files /dev/null and b/sound/vox_fem/terminate.ogg differ diff --git a/sound/vox_fem/thank.ogg b/sound/vox_fem/thank.ogg new file mode 100644 index 0000000000000..2eee00fba4c45 Binary files /dev/null and b/sound/vox_fem/thank.ogg differ diff --git a/sound/vox_fem/thanks.ogg b/sound/vox_fem/thanks.ogg new file mode 100644 index 0000000000000..d1fb4ffbaca2b Binary files /dev/null and b/sound/vox_fem/thanks.ogg differ diff --git a/sound/vox_fem/tiny.ogg b/sound/vox_fem/tiny.ogg new file mode 100644 index 0000000000000..69c348cafea1b Binary files /dev/null and b/sound/vox_fem/tiny.ogg differ diff --git a/sound/vox_fem/validate.ogg b/sound/vox_fem/validate.ogg new file mode 100644 index 0000000000000..1c17c6dd94215 Binary files /dev/null and b/sound/vox_fem/validate.ogg differ diff --git a/sound/vox_fem/was.ogg b/sound/vox_fem/was.ogg new file mode 100644 index 0000000000000..3d092bece9fbf Binary files /dev/null and b/sound/vox_fem/was.ogg differ diff --git a/sound/vox_fem/while.ogg b/sound/vox_fem/while.ogg new file mode 100644 index 0000000000000..0f696b6247ab1 Binary files /dev/null and b/sound/vox_fem/while.ogg differ diff --git a/sound/weapons/beesmoke.ogg b/sound/weapons/beesmoke.ogg new file mode 100644 index 0000000000000..5e29f37a224e3 Binary files /dev/null and b/sound/weapons/beesmoke.ogg differ diff --git a/sound/weapons/kenetic_accel.ogg b/sound/weapons/kinetic_accel.ogg similarity index 100% rename from sound/weapons/kenetic_accel.ogg rename to sound/weapons/kinetic_accel.ogg diff --git a/sound/weapons/kenetic_reload.ogg b/sound/weapons/kinetic_reload.ogg similarity index 100% rename from sound/weapons/kenetic_reload.ogg rename to sound/weapons/kinetic_reload.ogg diff --git a/sound/weapons/laser2.ogg b/sound/weapons/laser2.ogg index 5e22e30d4a0d8..7fd3969b2adf3 100644 Binary files a/sound/weapons/laser2.ogg and b/sound/weapons/laser2.ogg differ diff --git a/sound/weapons/taser3.ogg b/sound/weapons/taser3.ogg new file mode 100644 index 0000000000000..bfe904c902a2f Binary files /dev/null and b/sound/weapons/taser3.ogg differ diff --git a/sound/weapons/throw.ogg b/sound/weapons/throw.ogg new file mode 100644 index 0000000000000..e9e282ae3b1b4 Binary files /dev/null and b/sound/weapons/throw.ogg differ diff --git a/sound/weapons/throwhard.ogg b/sound/weapons/throwhard.ogg new file mode 100644 index 0000000000000..b628c534b88d6 Binary files /dev/null and b/sound/weapons/throwhard.ogg differ diff --git a/sound/weapons/throwsoft.ogg b/sound/weapons/throwsoft.ogg new file mode 100644 index 0000000000000..6b6f4f9346e06 Binary files /dev/null and b/sound/weapons/throwsoft.ogg differ diff --git a/strings/antagonist_flavor/traitor_flavor.json b/strings/antagonist_flavor/traitor_flavor.json index 93da8d01374ee..773d2f55370f6 100644 --- a/strings/antagonist_flavor/traitor_flavor.json +++ b/strings/antagonist_flavor/traitor_flavor.json @@ -111,5 +111,13 @@ "roundend_report": "was a terrorist from Waffle Corporation.", "ui_theme": "syndicate", "uplink": "You have been provided with a standard uplink to accomplish your task." + }, + "Contractor Support Unit": { + "allies": "You are being sent to help your designated agent. Their allegiences are above all others.", + "goal": "Help your designated agent to the furtest extent you can, their life is above your own.", + "introduction": "You are the Contractor Support Agent.", + "roundend_report": "was a contractor support agent.", + "ui_theme": "syndicate", + "uplink": "You do not come with your own uplink, defer to your agent." } } diff --git a/strings/exoadventures/britain_replica.json b/strings/exoadventures/britain_replica.json new file mode 100644 index 0000000000000..0bfaa67e990cb --- /dev/null +++ b/strings/exoadventures/britain_replica.json @@ -0,0 +1,570 @@ +{ + "adventure_name": "A Model Earth", + "version": 1, + "author": "Armhulen", + "starting_node": "Planet Start", + "starting_qualities": { + "Long Range Scan Report": 0, + "UFOs Shot Down": 0 + }, + "required_site_traits": [ + "in space" + ], + "loot_categories": [ + "research" + ], + "scan_band_mods": {}, + "deep_scan_description": "", + "triggers": [], + "nodes": [ + { + "name": "Planet Start", + "description": "You come across a grey planet. It looks familiar, though you swore you've never come across this sector of space before.", + "choices": [ + { + "key": "choice 0", + "name": "Ignore the planet.", + "exit_node": "FAIL", + "delay": 0, + "delay_message": "Whatever, there's a lot of planets in space. Must be a hunch!" + }, + { + "key": "choice 1", + "name": "Begin Orbital Scan", + "exit_node": "Scanning from Orbit", + "requirements": [ + { + "quality": "Long Range Scan Report", + "operator": "==", + "value": 0 + } + ], + "delay": 30, + "delay_message": "Scanning planet..." + }, + { + "key": "choice 8", + "name": "Descend Into Orbit", + "exit_node": "Orbital Descent", + "delay": 30, + "delay_message": "Descending into Orbit..." + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAMAAAD0WI85AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAALlUExURQAAAAEBAQICAgMDAwQEBA8PDwUFBQcHBwkJCQwMDAoKCkRERNXV1cDAwNDQ0LGxsaioqJaWloyMjLOzs6WlpcLCwqSkpDc3Nw0NDeXl5b+/v5mZmZycnLKyssXFxXd3d6CgoODg4JeXl9PT08bGxsTExJiYmIqKiqGhoZqamoeHh5KSkvb29pOTk4ODg3FxcaOjo62trZ6enqKioqqqqq+vr4WFhcvLy729vb6+vqysrJGRkeTk5Jubm7q6uru7u6urq6amppWVlbe3t6enp9zc3GFhYYuLi93d3by8vH19fdHR0fn5+c/Pz8nJydnZ2XZ2dn9/f4mJibCwsNvb22pqauvr63x8fHV1dRUVFcPDw/Hx8c7Ozra2tp+fn1BQUFFRUYGBgV9fX4iIiHl5eQYGBqmpqbi4uLS0tMfHx83Nzbm5ubW1tV5eXmJiYpCQkHt7e+rq6kxMTFNTU2lpaXBwcG1tbXNzc4+Pj09PT52dnUhISEFBQUNDQ1JSUltbW1paWmVlZW9vb2traw4ODnR0dG5ubnh4eGxsbElJST8/P1ZWVnJycmdnZ2RkZHp6eoaGhmBgYEBAQEpKSlRUVGZmZmhoaMHBwdLS0oCAgDk5OWNjY11dXUZGRkJCQkdHR9jY2O/v75SUlDo6OlxcXFVVVVhYWD09PU5OTk1NTVlZWd7e3sjIyDMzMygoKH5+foKCgoSEhC4uLjs7OzU1NUVFRenp6TExMf7+/o6Ojtra2q6urt/f3/Dw8PLy8uPj4/z8/Pv7+/39/TIyMufn5+Li4srKyszMzFdXV+7u7tTU1Pr6+jAwMD4+Pu3t7SsrKyoqKuzs7NfX1zQ0NC0tLSwsLC8vLxAQEPPz89bW1ujo6Dw8PEtLS42NjfT09DY2NhwcHPX19RMTE/j4+Dg4OCkpKSEhIQgICCQkJCIiIiYmJhoaGiUlJRQUFOHh4ScnJ////xcXF+bm5vf39xkZGRERERgYGJOWJYYAAAAJcEhZcwAADsIAAA7CARUoSoAAABOjSURBVHhe7Vv3W5PZnidlog41VOlNuvQOUgJIVaqURTpITygJTQgEuJQQQu8JRSAQFEaqBFBBulyxULwjgWFnr3N37uyMc9e7uz/vOS/Zv+Gd51k+mrzJm/ec5/PtpyF2iUtc4hKXuMQlLnGJPygwAFgMFgsuONEX8MKDGzgAPAYHvuPBc1gc+AU8jsXixHDgDYMRQ36DP2PFsPA2ugD8AWVAEQ944jFYeAtcwBcsHogjBhjjIW0EOOw38AkgBLiPyIpoAIhxIS2KuCAI6UJ2YkC34AseUAa8gGiAHtA1oAuogrvwcSzyhoPyAPvAT0Bm8MhFf6gBEoNMAOULz4LsIGsCdCs8gXDlKuHqtavfXoGehsVCEZGHoCmghwFbwg/IPVQBnAeJCkAGIYf8RzhicVe+EZeQlJKWIcrKySvIK8opfXsFMIeGAKRhzCANkabwcVGPaAGhBdgAVhe+cyEPAXddWUVVTV1DRVNeS0tNVVtH94aevoHhNTwIKZHVQGNwgT1AZaAsCFAw9HKR5wM/AS8M7up1BSNjk5umhmbmFpZW1jaKtnb2ig6OThqKhs5GMBVAN0NcDeYy8A/GvqhHlADVCWRB3hHFAk8Ru3rL1sXM1c3G3l2S5K7o4Um00PFSv+1N9PF1tPXzM/WHaQy0QPIXBOJjBFGPaAGJ3gt7AFPAyL0SQDT1vXM3UMHJMchTXSY4xCBUi6QoE+YUei/c2SJC2iXSOQoGBrQE9EJgUtga5fQL1ArzDiIIoljsv8hFq9+PIfpJytx1dIy1CY2LT0hMVFQnOTl5eVjYmPgmkZJDU0JgIEEgfgmbgmBBFYiLIIygcbCYBxKpkmnpGZnpsVmOMtkkaxd3NS0yJSc3z8vWJi7OyzOfSisg5xYWmRddGAO2QSILdYtc+DliEyATPttT0UMtNs3EQ91DTi9W3Uu12MxYv4RKNJF+aGpTWlpGJ5dXUBmVVdVFlciIAJR9kUlQriOIGBDQNpg/yWTJu0p7uioouLnL2JCsZTxSamrD6wxy6osYdGY5ndngTWKR46nllEZ2kzlIW3Bsg1RILBblYEc4QGXC6L2mFRYrKRcdIxGtkOpl1FzcEtKY0tjqYWBHdCw1lMkpaGPQ2zsqNDuTqOS4dFpXOKiOF2kCifaLDtECLIAgbyF8rsloqad2G3W7yll7mBV1RTaHJ7GL6msNcnpcTEJ6Zaz6OFxZM1b/AGUwn1qrX+dB84YRDjMdHjYX9YgSoEPBFxhlEEhOWSoOXnoW+qFF1ZWcJnJnU9PQo85IUqPFcHpzpIm+Zl8yxXukfDSf3F7O0+nRL6SQRdEOQwxli4iGfLBA26rYuxqmKuoU1xR1UcZ4YxXkJEorkZNYL//QR95o2KQvqYQ3XsKfSHrMEK+n2eiF6XELWaAxDo7z8X+ArAXtAcI2NO2JtIVqS01jVxUnidHkWmU22VnRm+JGkoocDnVp0Wa0jeVPMdvyG8N59WQOtSNS0TIyt/M7JF8REO9EFYhPwAH8N26Szg+NzCPNnzZxyIHTmrJ1PqZ27GjH4ZleI7niAvrs3NR8WckEf4GRQB3VnBhgBbdakuvDE4AKwHwS/WAXQzKPGOZqaqrHcIpmldlDjy55Z78Z28LEEJrmM/en/MLYVtO7fFpCSe9i/chYTmU+ObJeINdJnWgiL3kXdvHhIB6oA0xnUAWMcnihWi/J1tQULdsM2UrJGzuvPH+hXl7OvfmSVRd7002H5zvIuK+T9dhsldi9dv9VBtV31Y/Nqu3oqK5tuobkXtTnI8g8SQxzxcmseSYuMaeI3VQV0+uto7Bqep/FpTqsSce1Dg05rZdQtdhUh0YX8sAac2OhRmNzK5itOsNht9K48chABQS9qEeUAHQJPBy/EhocrLkd6ZkznK27s+udm6dlm+5Gfb22tlfZe2MztzzhfsGfxUcqHHSzht+w+LXWrpKe0S+aqk2CW+srrwKjgiKPfh0BKRgYJLjIVI2t9sh/facwZ9g7oqh6uqPgqf7a2pDsE2NlO/OR/bVX7KG8t22euRvvlDqa3e3oxckZzUvlkR2loLDDgirqESXAfIPBvC+OWustZNcneXcK7D+8Iqkox5HvpL0Ij33KfnXgen+9p60oIbpgvazmsPZoYvY78f2KOX6ujL0nj8ny8WYRgAygkIh6RAmAAzCKtZlUc4hPa1drwbSzufqjHU9OXm3AaK9hzXpdqt/WQ82d3KUq1jK193DwePGN/LA+u9R6bmouOMfIoD2Y9fgjKIp/BNfCEP7i8X2Lnz6HMVpXQmlpb9dU//TUQ3bF0U/hdYidCXFdteVkKEZftjCDIWTOWbHexNfGx5/OhjQoOEsrhphVxu8jWQv9gigm1quQlVjF4SmeJtHOKP4luxkHtKHmHCM7NXl72810l1oj45pP/SnOngvife/GR+KPhOIVy8z9MemJSuuwQRYjXwxZEBL1iBZA7sR7ZUQvdblwGKWnFR5ljawmvpxGnR+xyy0odOWHMAm2Yt0jTXqtf6YdzS3gXHzxOGX/qO+wIUmmo6O/Pq0+l5zwr3B+hXZBBPH+7WqacdKB+Y+nEzPudJ0BpkmeAqVrKScgbP3Jq7fbkcVe3uNtTLlOmRd19HrGlHDqDb004bD95vfjTVE60vr17aw5WBLRXqDD4nH/phiVK93UWZI/sTBBPmHyK83WqJxgU//Ym8qCv+osVfFmyOOD3RRKaGFL4uTxsdLHzyzmwPh8/8Jc8GSWYjGfwYMrMWgvYoNaNq9mEFWxS/2z822ryW6/WjLfuLl62vmpx55GhP8LFwZb9nFl9T6XO3g43t45uEiVPWwbf1/200ZD/MfRRFq4eUq8EHSDdvqFC6SOduxQn44twXTXfcrp/j5zUPVpWp1qVl1i8QGls4Iyk6TVuCBMosTOMmbfr5fRS2TH//Z+nLW/MOcfH9/CKuHWx/87UAfK6RcvRhBLU60wz/b/8UyuulFIbxsUiiv4mzWemTxVtywI906K3R0da+kfSeKe/fz3Kepx2+nnhF82kuhtGxOc3dLBWiqVq2/+H7C0i3pEC6CYGdU2sogBMiW79NNBlnAmOtKycKkxMS7Z+GE1hUyrZXNoCe1KCa37v/76/tf5o3nSeVFfw/ynQaGQMlEvHt+cz6H8huySoAs8BpfTKKd/y/LLWM3uqyc5mRarasrsLP+Qs2KfmfqoyqZITc7uxOBC2/mRdXOb1uPZjxsOybUNIy4VG4f7pQ1lgxMJ5o+n4HK2qEO0AMqIUe9MJaOfUjAd1NM0QTZSeC2zpd01k+Mm+3DVgrXL6WK3ttPL+kJ/Lj/66fiX44HRqcq++e9mp5vaGgbmba3oCfHxU8jkClWAmR1uUs1qTKde2Hraf9o6zqT3L/Dzi16l2BFvVN1uTuAyCtScd8vzeRv7Skq/MxYX2tlzHpaLZwaU+aONNyOawtIx9ug+TH6iHtECmBOF1Yx6U3nJ3vw25sBgA71P3GefubK6qhP56UCu6vvRrl075tzhqNLI8bvjN+9/mp17MxAg0zgf7iycH8jnDjDKTxlvcGjv8wCPwIqptYyyC6lWkyyuf//axPxI6VwXmRH4yTS72OsggJbNJVfEM/sP90cazgd9j7qP2j4ezp0PnYZPt/PL2g7HO5jU3WMwG0C5IMJ1QotcMo89xqk3cF8OpQnP81vPywZZ9PQvm1Ivt9id0dE8XmFZw8bUhnD+543O38c3ZoU/HZ7fPKYJuz4u0U9p4tUVfwPDX1GHaAGupVsaRybNcAwqTIhSghc/HGwX9Q7MuN18GhvzSpDVWNnlb8kNL9Ueyd+fOp6aDx4fOacuvlsIb4ig8xemyuhzzp0c1m+gHKEd7ACVpsO2vg8L9M3bJYfsBE4ZRnsTvDF+0s31OJt1gWCP9GrtB4kT2b2Ss8WgI1JwVFKC0nxZwxvHwo2C895zJfFKGhmP/rYCmG5j/2SZumeaYbQbOj6u5J0m9YGSEt9ZMdA/ypp+cUcARBEI1tdud9EY9KN31ZTde+eSa4cj5/PHQbP8+ZziBdau+fhFrKEKuD53LWPGT9lGr0b4OfTO5yS9cGNqH6czl8eqTKpMvvOPm4L1dcErga7AiLR4PkBZMmPx3aO5i7XzY6OG5xtl/JDgsRFkiijqESVgcATMg7CHdfoHlqzPPPnAiCRdTjmZ+5FeoFlEi+CXnt0TCJ5AowhuRvicPz5cdJdtonqb9fWZjIzotgoblOg0dv4R3KhGWxAMDouXkzR8MvSJF+XLTPLypGoPHC7+MuftsqKYYU02Zar2rAH3WhfcTbR7SpkN1ow2s6R0938mFhbqEvv7Gug8FvM/4TxT1CFaAFMiMcJfvkau3nYgdtHt26PoA7c1eD+dv1NVK3DsurO735edhwgSlDbsN87LfctNXG1WXRLGM6La7OsW1BvmeE1CkDCATUQ9ogS4bIDDTFY/e7FF45U+0lIDThROO/39ffpEaHqgYNjt8Toix1v3peCRd2VVxu0DvTLLfq9bJitb3G2SS5TqExgNcKsI7fkITFs4fKq/6j8j1AqoGvnxE5X/XKPkvZvqPGoa3Vz/BxIer/56MHjn5GtZ226A8fLE/egwn2lJk3DrtJUVmrh5vPhvIPeinrVgvgFR8pQm+fpuSEmwSik5yF1qvUBQclotqbyWCDLW+vc/Puecve1dVmiVTSvTIRm8/XDgq/s8RifRzTNnxrgtfx7Zo0bdteCREixOq9f59cvH0bbFsr4r5muZgq3egKbMZ8pnaxFbsZ53R0nj3fvnI7IjL2X6MuOz1YcMg4PTonVv2OoP1zEnFpFNSLQtIoYlwANCD/xVt54nOw7Hdc+Yq8hWCVLTc5ZvBL5c3Qxc+5KpbRgXM1M75tnP6pglVUaahsfQwolxgd1+VfVVdhNTYHIIgh0EG6oAyoTDLYzXzj2FHdnNEm3vqPLOptUtqcagGxoKt5e/fHqux22ONniZ7h3V9rCX+7mtTJ/PORTuV0tbyFeMMhsH32PAJBMeeRL1iBLgXBuQwF4xkpoM285jfA2KumsUuva9sor2CnGjJ6On21KPk7m3dOaue7Yx+njVlK30hj1eOzJY3mw22p/AbxTikeSL+moQUCQ8t4AV44e6xgYElZq9dYsrTNl8oq2hntlTdK6ncPv1I6tdTnfc28jS/sdhFk3qvLa+8c+HNCtyF7+/g0L9L7g/DUY6qI+1EMciAKUq6ixFSewNW259eBQbm3fL0kzXZsDXMSjolqFRYoxCrOmms4rCgs5dGfOpo/d1BdTppSbxdg63DwgApv3oL6JAY8ARsBj2gXQoW3LS8yTww3JpbHfGfVt1Y6tgnR63WhvzG5LqPbm2mzqNz3eNUnILbbysLGQUNRb4EwVjcNMNSAHVIeoRJeDxkAUchj/4b6vCsbgKhc3RMxdvfyvHob2hneU8bbkbXtvLX3Xd8yRizkyydb18ZlSJ0k7u8h5LUR3c6v8BDZHTmqjXEcAD1hIgDBar5JfCLZFWubdiwcnu7CLdf7YV8fIL0Xx7Tzni7TOVbNtYLXbL3WxDV3XPuGk14llyZOUG0hD6FTzHjSrg8XFQSOBICYMrckrvdi3RuJduveN7K/3e5snbk4BntzbzTj6s6gZIbA75RQ9ZOctL54aRXIiyxilsIQFIAccGePSDBIggOgQHh0vNRu4rH7aDeuylvj6z/+qbemPoYGs1ImIzQOFr3qPV58/UJdKzYhzT5UgrsgbBnX1I4oaCwCvaGz2wmMEdfygLnnAeZ2qbHbB8sHrvIEb33pbyXs/ml1jlHyNOTk6+fvmi/akmzHFbJazl7GymqpUPhABJAjQE5iCgnX4BCeBZ0CZwcoTFXvdK17grEXRb+9PywXLy5smXtx8+RHw9UT450f6qPfRpp9vBU8M9n5scSRPCOQg8zAwkAS4mhrJFYF0G7gU3/KFSwbuPiruf/aT9ZOaTgJyI27fynj37+kz71vOh5b2dT9sK6bbp6bY6Z8HVf4cNAXuYu4EiUN96Q44owUIA+UB3x2B21c0MXR0CYwL2Apbvra6uPh8KWh7a0e3Zjgnc8bT0S1VsaakagyejLqwBvAtus6C99gtVikfMAidYwCSAEIGY2Oqqty2VGah7sHw7SEIiZifaflvjhkqsq4OXl0ViXesxeBz4JDwuCz7AtSBwFfWIEkD6BHzAoA8yQxhBxycY6Lj6ZU3qdTtmSu1k7NydlJTMckhLy071MvQwo8TDBlAEaI2L2IL5S9QjSgBk4J9bQDogRqCzYB9An3lAJGZl2TplOehFq0Rr+MllS2evyLsMy9qZ/wxMB10QqYXAqaB7wcSH+q4usACShC+SEHQSJB/jrlx/aEUikaRtw5ykV0hO1k4ePsmaVDjxgEkO+BOWAKshjC84QEF7XwH4E6CNA9aA3gIiBtY2KAvki7t6ne2ib+ShprMUatx7fIUA70I/hC4Im0EtwGHjhZuhCsgCVmbkAudY8Ab8gtAEqscTMAQCDAqodzASgaIgxGEb+Bn8iEgk6hAtAAaQGx64CxzFimatiBiAJGRIEBkA8T2kkP+fMUAzRBEYkO7ARdQjSgCcEPVDDwP0YHWAwY/IgvCHf5wI3QkwBz9B+kAYMBaAqQFKD4UAv18hoGwSRJWQPbgAftAMyPFZxMXgBSgaSAF8DD4FkxwQCNoBKSFAVKgJqA60Y+QSl7jEJS5xiUtc4hL/HyAm9r+XXnYR5dlv2QAAAABJRU5ErkJggg==" + }, + { + "name": "Scanning from Orbit", + "description": "You initiate a long range scan from orbit:\nThis planet has a smoggy cover that blocks any good look at the surface, yet it has a mostly survivable atmosphere.\nThis planet has zero life signs.\n", + "choices": [ + { + "key": "choice 9", + "name": "Stash that Report...", + "exit_node": "Planet Start", + "delay": 0 + } + ], + "image": null, + "on_enter_effects": [ + { + "effect_type": "Add", + "quality": "Long Range Scan Report", + "value": 1 + } + ], + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkBAMAAAAxqGI4AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAASUExURQAAAP///1lWUqwyMmlqakZHRwPX/kkAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAL5SURBVGje7ZldbqMwFIXtKnkvO4huyAYmG0CWea8i2P9Wxv/Y2MTYvjBVO4c8FET8+dxzDW5Ldqr73Hcf7fbemfwu+p1tkM+jILTbfeu2stNbDDSkklXnzNRbyUqUqbM/oVr5AFhOus5iUK3A/fmEW0BRHjCtwFPqj6NQS6GIVu5PTXEXnJUOjfLxXEOITYWitXECQk6BUOx6pSDkKAj4F6mrFxLEdNctAdl8gJVmBRBXy6tXcrScwwvXsufikXIP1qId5t2iz0D4osFClAga5MJDDbJaEUDJjF6eyZohxATjlhym9kkcMzhkIBQJ0g+pe2klJFWtG4DrgRSkeEUkjDDWA09TbPJdM6RXnySlDnLpYyO9/GhtQChthYhSMXeyup0aSFkoCYirVqJkdcnHEMaXakVeHIQUQYBPwSFdMB86IEBYolrAtyi1kF4asOIT40Ekq4JVQvS0R3UYI5G5GFLYwuKRO3l69dMEkxnc5MR8K6RCF9VL2sioe2tdLc6GRggROUDve5lcA9usXpw0So4Go5OwE0YySqutED33eZKH12TSyJJTK0RNFx6LFb4yMiI4uajJQmxlWT3tTsybEfxUnA0t9miGmNnCY54DMxNiJu7V6HLx89BO2iHLhIUXPxUleWVjf1SiZbvCHkEuLqX0brIM4hUfwkxERkKvdiPhxksGw3UuNpERmpuLeKGoBRM8k5WP/gsBsnrkAphUVGOJHeuIwPBDMcks6vmAUazkbtjXgAIhqVW+QHAYZHxnBIkRheKbGbAgJH6NIAcitWFlwmQQck28EzEDUdqwQnClrIxH+pA63od1EpgpHiP7i3ccSjEj/yd258SYqendLCR0UfcCyUK0B9KkbCYNBnZLRzIcC9HNdSzDODkYoraOB0eim+vgSIjcKM4HM06J5Hqak1Mi+d9ce3VGc5Ef01zXM5pLQN46oR0O5G0kKP+9nM+CfP1rSDqTwqTqmqvQX91SLINcvzGkLJPrCev9HMj8oyDk10MI+Qt3vM7Ve2h01AAAAABJRU5ErkJggg==" + }, + { + "name": "Orbital Descent", + "description": "As you descend into orbit, you see a flying object headed straight for you!\nA garbled voice begins to call out to your drone, but there's no time to decipher it!", + "choices": [ + { + "key": "choice 2", + "name": "Blast the damn UFO!", + "exit_node": "Tractor Beam", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Tractor Beam Turns", + "value": 2 + }, + { + "effect_type": "Add", + "quality": "UFOs Shot Down", + "value": 1 + } + ], + "delay": 30, + "delay_message": "Blasting UFO!" + }, + { + "key": "choice 3", + "name": "Attempt Evasive Maneuvers!", + "exit_node": "UFO Evasion!", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "UFOs Violently Crashed Into", + "value": { + "value_type": "random", + "low": 0, + "high": 1 + } + } + ], + "delay": 30, + "delay_message": "You attempt to dodge the UFO..." + }, + { + "key": "choice 7", + "name": "Do NOTHING. Jesus take the wheel!", + "exit_node": "FAIL_DEATH", + "delay": 30, + "delay_message": "What? Why?!" + } + ], + "image": null, + "on_enter_effects": [ + { + "effect_type": "Add", + "quality": "Garbled Transmissions", + "value": 1 + } + ], + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAKgklEQVR4Ae2dW5IbNwxFtZFswouI//ORqmwgNbHH9sayoyxGKcz4jiGIJAA+ukk2VKUCHwAJkveILXvGvv393/0e73324PfPn++ld5y176xvsWG+DZt9v0pwUN/s+c+WXwCy2Q0agPT9wAtANgJEgyNuED88AUgAEo9dBQ0EIIXNme15WMtHu0G0+Oh/vmECkAAkbpCCBgKQwuas9Imq3R7x/eP5drCcbwASgMQNUtBAAFLYHMsnzCw+2g0yS56r5RGAbACIBkc8XtU9XhHMAUgAEo9YBQ0kAblXvla7PnfJN26Q+htC08ADIJVcPIVpk0Z/vwO1wBGPWPX7/QbIk8IbGwKA+gPx7p0FEO+Y4f/r/G6NLCTDazc4ORhrrB1317h/vnx5+9F2aSU0u67/iHVNAQhjQC0esSmrzCFByNVXWc+MeZ4GiEpCwWHGjTwjJ3lz5Opn5LbLnIcDUtC9q2uXA6hdR+62kO2140fc+/eQQwBxKd/hfOVDJBByNwZvv/Ie9Vj7UEAcWq927bEJK44hb4pcfcW1zZTzEECq1V4ZONOGHpELvyG0m+SIfHaeYwtAiKudD0muLXdbpNpl7E712+12t75r170NIFeCxHqD1Ipiljir+D1+3rVtBcgVIEndErk2rxhm8U8J/v7b7W59p+J5m2ed2wGyOyQEw843CBcyL1vhkH58DF62QrIlIDtDkrstZLtVADP5cQHLshS+tS7H4XXL2rcFZEdIrDcH+VkOfyYfLtzacgoabSxtDwKQwi/LaJt3dH/p8Wr1G0QTsqefg6LFaWcYgCwCSAkOebNohz5bvyZiTz+Hg8parLYXAcgigEgISnXt0Gfr10Ts6Q9A6MuF4TWbCFryKcGQulla5jo61iN+zdcLB42nrXeqG4Qna2Ag68LH2aGcgiAHzWrr1URv6ZdgoG6J1fZrCkC0JKnf+rKMtZKPBw6CZqW1Ua4WEed8AELK5mJku7ZfpwKiJSf7U5BIn5nqXnH38J9p/ZZcpGC1egoG2aaNwfu1HE8DREtsxX76BNfePSDY5fGKzpiLVStLEGRdi0/1azo7BRAtqZX6c2I9o32lfUOuKdHm2iQQqOf8tXbkULIBSOUf82o3hbe/x81SOuhZ+zQR834AAcv7vGXrfhwOiDWxWf3OuBks8My6X1peXmH38s/lxccnnyH/cFzqyzTaconN3u69EXr5W+AAtLPvYSo/Lsijyqk80PaUAzog4NEW861iIb6V7Cp7S3k+CdLxW4I1sdrePI3JA0bDQePz+WYu97oBeo9juVFm3leZ25MgC4DguwesJ5Z85dy5+sO4KaeRoKTmm61NitoiyhlvmNn2NZXPgxgLcJAfwID1xKbmLrV9jF1yulqfBGOleg7iFc7wQ4yDAKndg7e8aoN3ivOCkBPjrO0rnJUFEtwcZC3+Pdb98P+D9BhwtTG8cKzmD2hXOBeL6C0+Pdd6WUBGCB1inNH2FM3osSwQ5Hx653ZJQEbAscKYvcUzerwcBKn2UblcDhD6dP/rzz/enmGlqKl95Pvsm2WUiFYcl0NWyv8ygECcJQAIGPjlLKBCv6yjXdrUvIg90pbEcKU+DgiVc2u/BCAQqxQptefeJFrEjbAyl9HzYfycEK7WLgFBXe7D9oCQuLkYa8WOT3nEoy4t+qWFn2ynOs8PfiOtFMFV64BCWr4fWwPChUdlEqP2JmFyH9RHW8zJc6a2EfNyAexU5kK3rovHyDKNsS0gXGiAo4fYaIzUG2JGH+otFmvAmL2sVTyr+aUEbl2DjP2oWwdYyQ/C4pbElRIrtaXac/68HYKtjU/FIR9usY6UP8/H07/SeVpz/RA1+5EVayz5peK3vUFIOBBWjYik+FFvtR4Ry7yxHrKteXiEs5JvSuTe/B/G8Aav4k8CgqBaRClFOqpOOfI8UU9ZrAuWx3nyW+UsvXk+CPznbeIdg/zfxqkJXCGGRAMBeUQjxUaxPd4YF2Oh3mr5GjG21a5wjrU5doOkNoHZ40gkEE+rCBEP4Vnr8GuxFMvjc3WslXLk/qX67GfYml8KEmrzjHuZ7yBWccPvKGsVs8UPkJC15u8Ry4q+rZBcAhCLuEhQVr+c+BCPftR7WBqDj1OqAxRLHiuK3ptzCySXAARC4RZi420jy/JTXc6Pei8rIcmtzSu2lf1ToGjr2RoQEgUJpVZ0EBXiUfdaKVaMN9piXrI8Zz6vJpDd+r2QbAsIHSyJQoqDC+WocimHUh/lBzEjV2+d4miO0jy7QaCtxwPJ1oDQRnFheMUl/SFSq6V4Lk6MB4s+1EdazMX3A+vQBLVjvxWS7QGBCM6yKUEil1IffGABj7UOP2kBCm/fEQDLmiyQbA8INooL4qhyCQDelxM/2mst1ol41CUk2KMrWg2SywBChw+BHGGlCPmcHA7efmSZ53BFMPiaS5BcChBsyhFC5AKU8+X65Cf9yDrlgDywL1e2OUguCQgJQYq2Z52LT44LUcr2M+qUy0vF/2to+adpV4QtBclpgPB/Je+szRwlyhwEufZReWjj1gBigYP7eM+Wx3rK3nly/k+Q5BxHtnM4UB45n2VsTUzW/hwE1J7rs47dy49uDXp7AfEIVvpqZyD9a+vaPJb+B0gsAb19AAW3veeoHa9FhDkIcu0tc9XGAo5fgHw1/XRrrWB5XOpMeH/vcmo+T9sbKJ6AXr4cDJR7jd1zHK8IczdErt07fqs/h+Ply9f7y1cbHLSnvV78fHqNWRqHz1dTPuU7CKDgtib5o2IswuS3hCxb4kf6PIJBj1cEx+v95fXVdHvQPvd89R5Py61FJwGI43+5LYkYUMDS9YxyKW5032xwaGIe0R+AOETeslkyFuIGCCkLnzPsIxzvj1TvN8c3881Ba97hJc/OWo8bpAGuFBCy7QwwaM4kHK+v95dv3+8v339cDhCC3AoF9wtABgIyDxzv3zdq4CCx7PLiwreWpwGEvrBbk57BT94Usp7K8Qhgnm+OX3CkcrK07QIIrcOyXu4TgGRuECl4T51vsKXcC5wUHJb5Sz47wbEMIHQg/I94ebl0WEf1AYZ/P326l97wk7ZHnl5oOByt8+8GhVyPZ39OuUEoQQ4FL3uSH+FLYi9BIftGwDFiXdqYUkQ717W94P2nAUJJcDB4mSd4ZNkLB2ABJEfm2jLXzuK3rM2zdwHIz+8gEDlE77UU79n4I3wtYrmij2fvTwWEEuU3B8qeBfTyrb09ANIMgFxR7N41e/VyOiASEu8CevmvDohXKFf19+plCkC8SY/wD0CugYxXOwEI+w6Cx6Uae/Yj1jXkXbdKLxTcPwDpAMjZcNCBxiu/A1zw3vI0gOALOlnvInr5k9C9j1qI6ZVD7Th5eURP7Z5S3BSAcDhQbllUayxEn4OF97fO1Ss+MEjvQOv+BiCZn8XCxnIYUEbfTDYtj2htPaMARAGkdYOPjN8VB+yhd32Ia7FTAEILwKPVmd9BWjZyhlivgFbwl/tqzVnG1danAaR2ARFX/h0Hq6Bm9JvhbAOQjR6xrILqBQPm6zWeHAfjn2kDkAsCMkJwUtwt9RH51Y4ZgAQgp/29U61oj4wLQAKQAKSggf8BsnyFPjzLbzcAAAAASUVORK5CYII=" + }, + { + "name": "UFO Evasion!", + "description": "Were you good enough at flying to avoid the UFO?", + "choices": [ + { + "key": "choice 4", + "name": "No, Back to flight school with you!", + "exit_node": "FAIL_DEATH", + "requirements": [ + { + "quality": "UFOs Violently Crashed Into", + "operator": "==", + "value": 1 + } + ], + "delay": 0 + }, + { + "key": "choice 5", + "name": "You barely avoided them! Nice!", + "exit_node": "Tractor Beam", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Tractor Beam Turns", + "value": 2 + } + ], + "requirements": [ + { + "quality": "UFOs Violently Crashed Into", + "operator": "==", + "value": 0 + } + ], + "delay": 0 + } + ], + "image": "default" + }, + { + "name": "Tractor Beam", + "description": "Before you have time to think, your drone is captured by a Tractor beam! Now you've gone and done it.\n\nYou should have some time before you are pulled in.", + "choices": [ + { + "key": "choice 6", + "name": "Decipher Garbled Transmission", + "exit_node": "Deciphering Transmission", + "requirements": [ + { + "quality": "Garbled Transmissions", + "operator": "==", + "value": 1 + }, + { + "quality": "Tractor Beam Turns", + "operator": ">", + "value": 1 + } + ], + "delay": 30, + "delay_message": "Decipering..." + }, + { + "key": "choice 10", + "name": "Look at the surface of the planet", + "exit_node": "Looking at the Surface", + "requirements": [ + { + "quality": "Tractor Beam Turns", + "operator": ">", + "value": 1 + } + ], + "delay": 10, + "delay_message": "Looking out window..." + }, + { + "key": "choice 13", + "name": "Let the beam pull you in and dock you", + "exit_node": "Landed, Kinda", + "on_selection_effects": [ + { + "effect_type": "Set", + "quality": "Tractor Beam Turns", + "value": 0 + } + ], + "delay": 50, + "delay_message": "Getting captured..." + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAIOUlEQVR4Ae2dW5JcNwiGewdZQNbgbcTbyFM27ZXk6aTkmDJDIXSDIySYqi5dQQL+r0/PeGx//vnxPPnKHKQGeA18SmJqX5k0PmmZlzh5+dTgoPMpijiiyFr/rnU3IBSYMs5EZg5u18ASIAlNApKAcBRMzN2eyIzvzjcL9SfICDspqjtFdVNdtwKCYbopqRnLb/BxjXH/lBy5AQQnr/RPSWD0e9K69Y5PyZtbQHCie5KJ95d+j03uGc8TzfPs+JTcHwEILgJNLF5r9altjvsBaeV2dP2U3B8HyGgh6P5TCuPpnjSHGmNP8Ul3SUDyd9HEj6MaMHA+JFF6WktAEhAWEE7UmnOeIJDukoAkIF8A0YRA8iWJ0tNaOEBK0TwVwNNdJEFrr3mKW7pLApJPkJ9vGNoAtPxJovS0loAkIAmIoIEEREiOp3cy67u03vG9rFvngfoPBwhNQI7//57MCwAr97CoZQKST5AtH7FWQBixXYUmFCCrybrZfkR0J+8dreH1gIwmJOr+k0W/cvdWva8EpBV0rvN/FrQitNtsQSPXAAIBZcuLvycvt4lcIx7x38XSOMDKR0/Bc88YLFa1OtnvT0BASN4CgXtlOyb0lXx508Du+3wBBBL7xqXgrFPbv75/fyxfu/LyRu1POoMFBIpjGQiccVJrCUTL95t5sqz7ab5FQKAo2kGB31PalnjfXn8jb9o1P9VfFyC0ILPBUj/ex28Lf/Q86/zN1vkmuylArAuz2/+oUL3t18yfpdjLPb1/JSDod7G8CX31PhqgaAuY3knbv7a/BOQXIKti9GpPBTkzXhFdz3kr/q1tE5Afj/jj2m/fvj1Wr7eg6hFpa09LiC17ab3le+d6aEA4gVrB0OuXu5PGnCTQ3Ws7AWidHRYQKrpeAe/YR+86O94NgnR+S6i71sMBUhPXDuFrnlmLC89LAvWwtgsC6dxQgGCx4L6mUHf7wnFxfQ8g1O4gCXXXWhhAOLHsFrPl+Vy8Za4mTk/zu2Dgzr0eEE4olsL04puLG+Y8wdBzF064b81dDQgIgrZeRGx9Dxo3jHtE6XXPG2Dg2K8FBMRAW2tRevJPY8djLIIT+1qgtGK/EhAsBNr3JGDru9DY8bgljFPWR0CZiek6QLAIuL61KD355+KHuRmxRLS5ChAovtR6ErD1XaQ8RBT7TMzXACKJga5ZC9OLfxo3Hs+IJaLNFYDgwvf0vQjY8h6tPEQU+0zMIQEB8VgKdLdviLHWzoglos3xgNQE0Jp//vzjgZemmD+fz4Nfmr57fbViL+sRxT4Tc0hAAAzNFkPB9XvFvbKvBwzYMyOWiDZHAwLFHmk1ocC+OCjwHN5r1R/JQ0Sxz8QcChArYWIQpL7V+eA3AdH/FyjDAAIismglKPCaxdngM+HQh6M8cY4FZEQQICKrFkMg9a3OL35H8jHzUSOqTQKCfpo1K2AJCrw267/HLgHJJ8iXH02OCKJHYKt7MAhcf9W/ZD+Si6hPgtm48wmi8AQp4uWgwHOSwFfXEhCbp0eBKgQgRUCrIuy1x1CUfq/dyr4EJAH58vGqkD0iCrp3RYwebWl8tfHsx4zIdmGeIDXReBT86J1qsXHzkcU+E/uxgJRgOQFozI0KdOf+mXhnhBLVJgHp+K/UdgLQOnsGkGITVfCjcR8NSAl2ViBadi0BW66vxjAqloj7jwfEAyScUD2Dge8bUfQjMV8BiFdIsBChvwoO+NFsRwQTbe/RgPz79/Pgl6ZoovmKJvzeeI8EBEPB9aOJWzPeXuFE2XccIBwQdA6KpymcSL4gf9ke9qsmFARpTIsbSeCrsdLcRR4f8wSRYODWpKKuCiiCvZS/SGtHAMIB0JqDItJ9ME/bCKIfjZHmKOLYPSBU4FrjVrFHxXTr/laebl93DYgWDJyflcJ6gIHGZHmnlVydbusWECoAi7F28SxFCr5beYB92q12rk7x5xKQlgi01nuKRM/qsaF7tMRK78KNtc6ifmhMUcahASkCqxWaEx+eq9nReWwjnUft6Jj6kcZU3FpjeqcIY3eASIW3WKsVuXVWzQ7mV+3BT2lbvui6FhCcH3yvCH1XgNBCvzXGhR45E9vRfssP3c+NWz5q65ywNee4u946l4Cgj1k1wdXma6Ko7afzq/bUH4w1YeB8wf8LWLv/TfNuAIHi7mhLQWfO5YQw4ofaj9jW9nKCtpgDSHBL47lh7AKQWrG9z1MBjN4X24/a1vaDTwsosE8MRq0Pdzm53Q5IrdAnzEPhZ+9a7GdtOTu4D7RY0Bb9Ghi1ebjXSe1WQLginzRXCu3lvpzoilAtwACfNRB657k7e5tLQMjfSuwV/AlwgFBB0Not+NdqvcFR7rMNkF4h5j75KcWJihOsNhzgjztLY46La8fcFkBS9LLoe/LDiaUlTBC1Zts6U2udi/eNuQRk8iNWj4gt9tRE0SNETTDAV8+5WntqsVvOvw6IhWgi+JRE0CtAELVm23u2xj4pB1ZrrwISQcgWMUrFrwmP3gP2acJRfL35JeXBai0BcfwRSyp6TZgUDDo+DRApB2+svQYILVSO5W/Ua8WvgVHme3LqHZBa3LvmExCHTxAJgtpaDxywxyskuyCQzn0FEChMtvK7fE38rfnRvCYg9b8oR2ExB2S0eFH3tyCorc/kyyMgVJhexgnI5o9YNeH3zM/AUWy8AeIFBu4epoDMFvBtu5KYt88s5618zd63nKkNSPG38sUJ08tcaECgCLNiW7FbEdTquZ4AgRp4bcMCgguyIrhR2xUwiu3oeXg/nJ2AOPkmHRfHU/9EOFbzB3BA6wUSXAuP/f8AHPtbaN2iPfwAAAAASUVORK5CYII=" + }, + { + "name": "Deciphering Transmission", + "description": "You review the incoming transmissions your drone has gotten. These aren't in need of deciphering, just un-garbling it from the bad connection...\n\n\"You are in Space British Air Space! Identify yourself, Tally ho!\"\nWhat the fuck?", + "choices": [ + { + "key": "choice 11", + "name": "Whoops!", + "exit_node": "Tractor Beam", + "delay": 0, + "requirements": [ + { + "quality": "UFOs Shot Down", + "operator": "==", + "value": 1 + } + ] + }, + { + "key": "choice 12", + "name": "Good thing I didn't blast them, then.", + "exit_node": "Tractor Beam", + "delay": 0, + "requirements": [ + { + "quality": "UFOs Shot Down", + "operator": "==", + "value": 0 + } + ] + } + ], + "image": null, + "on_enter_effects": [ + { + "effect_type": "Add", + "quality": "Commercial Airliner Transmissions", + "value": 1 + } + ], + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAALM0lEQVR4Ae1ddegGSxU9WBio2AjPFluxAwvEThDFVp74nt1iYHd3FyZ2Yj1FfehTEQPrmYiFYmF3Xjmw+1jnN3NndnZnv+/bPX987Lc7s3fuPffcuzGxAGD6CQNxIMmBZIECR8lDHFDmUIIQB1wOuIXKILqKbJ0DChBlUHHA4YDAccDZevaU/SKHEoQ44HLALVQG0TPI1jmgAFEGFQccDggcB5ytZ0/ZL3IoQYgDLgfcQmUQPYNsnQMKEGVQccDhgMBxwNl69pT9IocShDjgcsAtVAbRM8jWOaAAUQYVBxwOCBwHnK1nT9kvcihBiAMuB9xCZRA9g2ydAwoQZVBxwOGAwHHA2Xr2lP0ihxKEOOBywC1UBtEzyNY5oABRBhUHHA4IHAecrWdP2S9yKEGIAy4H3EJlED2DbJ0DChBlUHHA4cDhgnM6wI4B7MKAneUAMv0ZALtA9zvTAehL0pwdsEsBdj3AbnAgOjtkr7kaHkaAXAWwxwB2EmDfBex3gFnw+ztgPwXsU4A9BbAbAXbGHTn1OoA9FbDPAfZ9wP4Y6Erd/wLYTwD7aGfbdQE7dUN9T9MllCsDdnPA7gHYYwF7KWDvBuwzna5/juhKfT/YULeZSV0TCKlz9jdALgPYazvSh8EwZv91gF11AedeC7C3APabBMFKdP4XYC8E7JIV+l6sC0pi9iHAvgzYzwD7zwR9hjp/vkKnPSZ+KiDC4/sXIOcF7MUzOXXo4I8Axuw5t9MuAdgbG+j7dsAuOELfGzfQYYgfr4RzY3cA8vbL6EcC9o/Gjr73jI5+bmNdfwvYbQv15VVySOi5//++UI8DIP2YQN+fAHlBYwcPCcPbkKmO5O3UUGbL/88q0PeiC+gzFbMDPH86UeYw+m0LODck8PMLSBez7VyAfXIH+j48o+/ZFtAphsfKj+0+QD62gGPD4Oj3H5ohXej8MwN28g71vUNG396uVttTZdoP8VrB/m4D5GU7JFtPIr5eLXXke/dAX/b7pPTtbWq1PbfTdkqnAz+eBru1YQ/ZA7KRSB8udPqz90TfVzr61gQG+46eDtgjALsnYLfr+pCuBtjFAWNQsFO2NR/2VP5uDL/6npCtJ9RNMwS4zZ7pe9mEvn+o0PPbCVl7StilA3U3AfKeCkf2ZG6xZe+7R4gv7pm+b07o+4MKPRlUnu0bL1seHI7rmYvkqWERNfLPmiDK3WbUl/0aNbqF5/w8oeuXKuWfpxvTdjnArgkYbX4iYByF8LhEWxsJnOUD5IRKJ/Yk+R5gdwGMQyvoJA5U5P0ye577OjXbWyWI8M2Jcr/QjQ3re8XPAdh9AZuKw+Uj+nJcV43t3jlviLSzkeAgv5YNEGYqzxm5Mg5Y9HQmyf9d2UasX2Rq7/SbMvoeV6krcXpQRPZbJ8hLYf+BSDueD1ZW5hNubmPvOsGBHy901O0r2/h0RD5Hu6aIkztO8pfgx76YnKxYOcd/hfI5MjdWd8qxz0baCdtd8f5RkFsay4fLGmfx4XOMXiT72Ha+FWmDQ8DHymH93EN/aMuPK9qJvZ5+UoWcnH3fieAS6r/i/XHEmwoE5z/kHBIrf/JIJ3G4d0yOd+xXQRucP+HV98pKrx49ni+vaIvPNv35/Za3XZ5eNWUhLn1bG9keBbml4X+tdCDffI3R644V7fw3aIPD7msIxXMuFMjK6f7oirZiV9Upt7ApWzmfJKf/isuXM57jmFJOyB1nj+4YJ9y5sq3hDER2xuX0SpWP7Xmuedb5RQSTm03QOWULj4/BfmV1lzOeY4g8J3hl1x7ppIdVtPW3oI0p/TXnC2TlSFPzcB3rAb9Ghd0e7n1ZTv8Vly8XIJx51wM+dpt7vRs6iG+8xrbB6alDOVNm6LGfZigr95+z9cbqG3u7xCvtWDkl9cdeEXP2HlD5OEdOMYwrZJQ4I1aHnYOnLyQdR+fGZOSOcRj70L4rVsphOycGsoZyw/93qmwn1j9xzkpZOWz4PBbqvZH9ZQ3/5wQHvr7ASefvFivIOTxW/opAPpcUitUrPfbMQF6MUJwj/8vKdjg9OZTJ+Rql+o2pd+lIW2HbK90/CnJLQzm0eoxjwrpcnoaZPaYje9F5pQnPKd0P537ztqL03FQ9Tu29SEJfjnfiPO/UubnjqZVacufVlI99Boz550CPxcnWyph3TiDE0LGcD853/hyGzlek759BLsdIhXazr2HYbu1/Xv3uAxhnBD6h60islcXzvL6JKXJ5Lm3mMxyT0au7cWRXimATYrXS/aOkaGkoFyub6sAW5zPgYnZzAboW7U2V+YyEvrSBC9LVyI/Zr2MO0C3A4TNCjfNan5PKkFwhsXXbY+VzcTlv6mvtaIUW/l6BzHjmbGnY1GHeYwmVq5+6evQYcIxWTsaS5d7Vgzp/tVJfrorS26ztKVic8mcxcK5f6cAWJPyT8xDdk+TYPdKXAwe5CHavW2z7iUp9Uy8TYm1s6JgPdisg+A6/BeHHyizt0KvNymP1ydUvGZNW+yKEC4S38vcBy90NKFcAbM7psjlixco5grbUcVzUISZjyWOlownYn1OjF1fDL8VjWO/+gL0KMK628oACGeztfxpg7+oW2+aKkEN5sf8PBuw1gNE2vg2M1Wl0bNHG/s8w9lvUOHKOc0o6HUPA77VDfUuWHu31JflqMMotStfLH25jK2K+wyEwg5Bj3ob68TbXuzLydfOwPv+nFq0Y6jbT/90FCA24X8T4EIy598dcOUKQ+c2PufXJyePiCaEe3n7NQE3qwHnyntyw7HgHi1SWT73w+Fqi7Qc6bfDZMNSpwf4ijbiGzLlqSI5sz5sB1FoC5nSLlceGk+RIUPtSgUPuc7KH5bxSxHTmMS7rNKzL/7ytTtXn8dhwFn60J3XOQleRo4aEhi2xz6EMP3LASIFUepxDUGpuIVK233pCh1yJzuzNvkmEZCl9hsdrb11ji1YM5Yb/PfLyVX5Yn8sJebbHXhJ4i4THgjBsc4b9o4bMIPQIOCUyueIJB/ixI8wDcmxZq3WduOxQ7QNxygZ+Wq7kQdfDk1+6Ssn3jo9d3ufxTjuxKdIc28bP5MV04HNIzCbyIVafxx6VOCcmZ8KxuGITBEYNHSOvD5QfOuCkQOuPc30orhbiLfQ8Rievbh8onOHXtz9my2nI7+vezszRWcePbo5pv68bGz7v2c0yPjv05/fbcNrAUAafTfp6wy2HIA3r9f/5zUZ+k3JYl/+50mVfp/F2sYaqDOK3+vhFKN7vcqUQLuLG4eGcJ80Be18HjJ9P4BI4XFyatxe5jrSWgPI+m4tyk/BcEYWz/n7dOZirIX4FMH4KjisWcsHoGzZwNBMMX6EzwfBWjauf8OrwnG6B6rsDdgvAuD4yOwenfCGYBH5R13tP214C2GkzNt2y+4Yi/cigzK2LTH/yxco3uqkMY28FJ/p7vwNkonFVQak2xYkBBwTGAAwFVCb7bxArBcgGna5EUJ4IFCAKEHHA4YDAccBRpi3PtGvFSgGiABEHHA4IHAectWZF2VV+ZVSAKEDEAYcDAscBR5m2PNOuFSsFiAJEHHA4IHAccNaaFWVX+ZVRAaIAEQccDggcBxxl2vJMu1asFCAKEHHA4YDAccBZa1aUXeVXRgWIAkQccDggcBxwlGnLM+1asVKAKEDEAYcDAscBZ61ZUXaVXxkVIAoQccDhgMBxwFGmLc+0a8VKAaIAEQccDggcB5y1ZkXZVX5lVIAoQMQBhwMCxwFHmbY8064VKwWIAkQccDggcBxw1poVZVf5lVEBogARBxwOCBwHHGXa8ky7VqwUIAoQccDhgMBxwFlrVpRd5VdGBYgCRBxIceB/bK6VghmpUw0AAAAASUVORK5CYII=" + }, + { + "name": "Landed, Kinda", + "description": "You have been locked to the surface the beam. Robots are all around you, pointing at your drone with all sorts of old age weapons.\n\nOne of them angrily shouts at you, \"By God, Queen and Country, we've got you now!\"", + "choices": [ + { + "key": "choice 14", + "name": "Go out with a bang. Initiate self destruct!", + "exit_node": "FAIL", + "delay": 0 + }, + { + "key": "choice 15", + "name": "Surrender to the funny looking robot brits...", + "exit_node": "British Courtroom Start", + "delay": 50, + "delay_message": "You are being transported somewhere..." + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAIT0lEQVR4Ae2dvY5dNRSFzxsg0dKNBBLFKAiJApSKglREoqSDhqFAQihNChoKAiONlCISBUqRnlfhDXgRRGNkyI7W9dg+23/Hx/YqLO9jb/9tr+/Yc+9kst0+uzVMjAE14NfAxsD4A8O4MC5WAwSEJyhvEBENEJBIcHiK8BQhIASEJ0hEAwQkEhyeIDxBCAgB4QkS0cBygHzzlzHaxBOEJ8hSgGjBQD9CsjYkSwKiET0hWRsM0cgygOQIPqeNBPboHOfq2kfPZabxlgMkdfNQbKltW/vj3DR26/nM2D8BiXyCIRuO4pOy3jnOSWzfnKTO5r56lsWvkssBkisUEdoZBCVz0a5F/M8w99HmsAwgdmNEKFph4WaWtMV+Su3UeaT6l85vtvZLAWI3DwVj7ZQNlbYpbWr6yvjaeaf615zrLH0tB4jdOBSOVmxuux4CkHlrxhbflPVp+l3NZ0lAZJNRRGJLXSgXv6OFpx0X/Y6eYyhmI5cvDYjdOFdQ8hzbVI1PrH1O3d6YUo95zjhsc/mp1vKAoCBQXGijj7Wlzi1v9Szj2dwdA+vEdn34fCn6lHgQEM/3ICI0X26DK+UpgS7x9Y0nZZiXjMG2fogIiAcQFAsK0LXRr5WNY9ox8FnsVmOzX/6b9HtXlpgoRJCSx3xr1clYvrzWGOzHf3rYuHQ5QbjZ4Q1xxcpY6WPlxq7G8+GA+DbcLauxsFn6wNjMsqaR1tENEAwSigBt9KHd9026avxPAQgGHwGxNtbRJiRHa6AbIHviR1CODopmvM8ePTItk2YOLX0w/jG75RzO0PfhgNhFS8D3AiB+Nt/zPaK+JRB7fR+xPhkD4661pe1s+akBscHGDeoV/D3xHl3fKg4Ya2vvjYP+e76j1ncFRLMJNrC9NuJo4aeOV0t0GF+xtX2Lv821bUby6wKIDZAEdi9Y4of5XpvS+lShns0/df2lsZX2qeOO4N8dEBtcDJQEO5ajf037bEIvnY8mNhhnjb/rU9re7e9sz6cABIPss48IWqkYz9o+FjuMdczPV4dtre3zmaGsGyASPDfQPYJ9VnHXmpfEGnOMO5aLjfV7trSZMe8OSI+g1hLeSP1gnPcEr63HPme1lwJkJEG3mKuImADofyNhe/DhR2YvSWBHzVuIbcQ+Zf8EEHlmHgZmekBGFHLLOROGMAy+2BCQhN+pur6+NpJExPIcysXvLLlPBCwLQ7P9/uqV8aW9a1fN+lYbVFuUIQhKymvPUdNfq3jP2C8BSThBzDtvGZtKgNC21Qg912dGIbda07SA5IrH107AwFwr9Bp+vjmVlrUS1Gz9XgCC16bHj380kn55/tz40t2LF0aSr96W/fby5Zvku8rZMhw3ZKcEvlQ80h6B8Nk1xJ/ah8ytNE+J58q+0wFSKhwfCHtlqSKv4V+6zpVFn7L2qQDJFc0eACn1NcSv7SN3vbZdikhW9r34mFeuVDb//sl3b9JPn3xh9tLNzY3ZS9gnXr3QxisWjonlvg3LEUuK8HN9tWLP9ctZNwEJf6zramsKQFJFkiv2Fu1ywXDbpcbAFQKf/dAsB4hG5Nu2GUyaNkf5uGDIMwHxC7wU/AtA8BrT2r66es9ICo3157YZSeiDi9YKQyNghMJna/ro5aONg/hhDGmH4SIgr7/8s8L2QYFlvcSvGVeEr80JRRgKjM3QgGjFoBEYghCzNX318tHGw/qhCGiHYdmePv3DSJIrz3/525u5kgTXoTdltq6kHNr+endnJOFV6p+f3zWS/v74fSNJNlQrCI1gY1BgnaavXj7aeBCQMBCiLckJyOsrFkIQs3uJf29cwqEXvYhfkw8LSIog9sRl62NQYJ2mrx4+KfHQCIM+/wO3PXz4lZGEV6ZvH3xqJLUoxz7xWoW2fIJlcyy3m5ciCK1gEQSfre2nh19KPCh+/WlDQCb4FItw6AWf+nIgIACIvPnd00PKz5oTkIaA4FXnSPvLr38wknJ+FytFFNb3rOKuMa+UWKS+QVf3346EAscSOGx+BCAoohqiPFMfuLaYvbrYc9a/JCAoojMJPXcuuJ49O0ckK7fZLr74ky8G3S8B4Us9fPOX2PipFNo4Hyz3bdKeGHLrc4Xao13OGn2xZJn/5xgCovijDT2Erx0zBxDbhkD4gXDjMjQgdjG5AqnVTivkFn6la3DFwOf70AT/Nm/oqlOrHK9P2GfOJpUKpUX7FkBInzXnmxPvldpMAYjdsJqiadmXiDw3bzG3lQSfutZpABkJkhYiL+0zVTir+G+fX31gJIUWjVeg0NVIUx7yCY2bW14qlpXb58Z81nZTAiKbtbLQS9Yu8WN+a6YGBDe4RDCrtcW4rW4HfwaZOTCrCT5nvTPvf8ralgTEF6AcEc3exhen1coIyLP7Xw5ZEcwufu36VgPCXS8BCQDiBgqfteKaxQ/XvppNQDIACYlkFiB86witefZyAlIRkJBYfIIbrSy0ttnLCcgBgNQQ0RmAqrGO0fogIIMAIsLqDYrMY5WcgAwGCAqzFyw4h9ltAjIwICjOI2HBcWe3CcgkgIhQjwJFxps9JyCTAWIFewQks4Mh6yMgEwIim9saFBln5pyATAyIFW5LSGYGQ9ZGQCYHpCUkIqKZcwKyACCExP8LqRqwCcgigLSCRCOykX0ICAEp+jllZPFr5k5ACAgBiWiAgESCo3nDjOZT+1Ot0dafOl8CQkB4gkQ0QEAiwUl924zgzxMk7RMtArIYIBZiQqKHhIAQkGJgRjg5c+dIQAgIAYlogIBEgpP71jl7O16xeMXi/6AUAb82ILa/s78UcufHEyQipNygnr0dAeEJMu0brQZ8BISAEJCdk5GQ6CD5F0cJWniQ8Kf0AAAAAElFTkSuQmCC" + }, + { + "name": "British Courtroom Start", + "description": "Wow, that took forever. A one eye judge robot smacks their gavel, and points their hammer at you.\n\"I will now read out the crimes you are accused of!\"\nIs that how the judicial process works? You dunno.\n\"ONE ACCOUNT OF PLANETARY TRESSPASS!\"\nThis blows.", + "choices": [ + { + "key": "choice 16", + "name": "That's not that bad.", + "exit_node": "British Courtroom, Continued...", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Crimes Committed", + "value": 1 + } + ], + "delay": 0 + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAJiElEQVR4Ae1cQbLduA18N8gBsppVVj6B9+MLzA1yn1z5pVBJu7owlAhQoARSUNUvkGADBJvop/dtz3z+8/PzrZ/ioHqg3QOfIqZNTPFSvEgPlEDqDVrfIE56oARyQk69ReotUgIpgdQb5KQHSiAn5NQbpN4gJRCHQL7Jn8/n85WfHZ+nPqy2EciOTeE9084C6XExS0DLCaRH1JvX3ywQ3Hu0UNILBAcv22cAAtn1a1afgW/4Hzg8IhDLQe/EoLF4z5aP1zOOUfMqAkG90VxGvkVuFUg0EVH5WhfV8kXtNyMP6oUd3QPxbM9yWXGcg2Mw5vWr4yUF4jk0SIP1xI5gsY9YefR8JOddMVwrjy37A89Y+NjyOo8ZgzGvt8bAadvCjvq2FYgmDfNRojxxvBePz3JYcbNyYP+Wbe0JnKxhLJYf+M8wwLewWGtZ4HlP+Fr4Ud9yArEeFGQxgdbYqzjeG+Nezhau5ZM88IvlB3749Bx+bYHjfOxjP2KxLnMeY73l7+GwD3CYc06MgcG8tR+vjY63F8goMVfjcIGwvXwtHPswblnkxpqei//s0XGChQ9Wx7Ofx4zTfszF8tPyw8c4HmO9ZRl3dVwCOWEQ5DMEPlhe4zHW2fK6HgMHP+Zi5eG59lligGlZzo119mE/rInV62eYVlzLxzmQn3E8xrq2jIkYLyUQz4FBXC8GOLH8WPw6huNlzDm8WI7VubCPxjCOx8AhrmWBObMcp3G8hjFjtA9zsVZcL4bXo8bbC4TJx5jJg08sHvaxX9Z5DfgjC+zROvuBbVngeE379JyxPAauZRmHseB4jDj4YOHXFuvaMo7X4G/5sCa2t87Y0XGkOCTX9L8H8RyUCdRjnYfXZY3nGCMGc7HRD3Ijr56LHz5tdQzWtR/zqxb5xfYexmLMMfDpXPAzlsdY13GMuTLeWiBCDAi0kAQsW87Bl8AYHlv28WCQW8fAD6vXs81Rp9jW01tvxcDHsTo/1oD12GhxTH+DeA4HrIcgYGF1jp5f1iMf7BedN7LGqFxXzsqxR2NvnTPEkVogEU0G8q1kj5KMfWBH84zGWc8XicNZr+ZEHraenKOcWeOm/Q7iOaTGgiztj55bSerhUK/YHvau9WiuMuW7i0PZZ4pA3krmnRc3ulemu7HWMnrWiLitBBJByFtzWJv1TlyGuwgXyEwCMxD2phpm3mUrd0ZuQwXSOrTVl5Gcs5r+/PXrO/PnbO9au+93vRQCWeXCZwqil3sVjnarM0wg1jcF41Yhs9e8d6+vwtsOdT4ikFWIu7vxvfutwuPKdYYIhN8KvfEKZHkbNRt+BY5XqfGyQHqC4PXspGRr9Kv1ZOd7hfpuEcgKRFxtxqzxK3CfucZLAuG3w9k4MwFSW9bmjqorO/+Z65sukIyHj2q8lfJkvIcVahoWyNkbQ9YyHn6lhp5Ra8Y7yV7TFIFkO/SMZlsxZ7Z7WaGeIYEcvT0yHnjFRp5Zc8Y7ylxTmECyHjKy2X78+PHFD/JifmSBy2Kz3lPWutwCab09sh4uuimPRHDFH12jJV/W+8pYl0sgK4lDyLY0iwdzRQjeWE9dXmzGRsxak1kgbxSHt6ln4b0CsOCzNmS2ukwC0eLIdghdj6VBLJhZDX8lr6VuC0ZzVvP2f2PiFkh2Ii3NcYb5/vMfX8vPlSaPiD07g2Ut+z1mqc8kkCzF9uqwNEYLYxFEDxPR9N4crbNYfT0ua/1/b5RtBGJtDMb1mj5i3dv0XjyfxzMuAbS/UmlethCIpzEEG9H4UTm8gjjCeznQjVDztmBeJxBLY/P/CE7Glpi7MCWQdiPPEvjyArF+cloaWAtDzy05nsJYeQBuVkPtlrcEQn9qpQWh5081v2VfNL7V7tbIs86ztECszWBpMC2Go7kl11MYKx+Cm9VQu+Utgfz/DXIkCO1/qvkt+5ZA4n8/KYFsIpASR7w45G24rEA8DWH59NVviqO5JdcTGA8fu30NmnmeEsgLf0mf2VC75S6BbCCQenvM+XolYi+BkEDw1Uh/vYI/qy2BbCSQf//r88XPldexpykEm7W5I+rycHGF8zfG3vIGgSC0/euPz1d+Roj3NIXGRjRlphz6fEfzEZ7fHjNdIFoUMocwYEcu4agJvP5MjT5ai+fMI1y/OWaqQFgcEMOZ9V6EpzE82NFGfSLOcy5gvTy/GT9NIF5xiHC8F4ELn22faHzrnqNn93L9Vny4QKTRR8TBbxbPZYw2SFSctZFn4K6ewcPzW7EhAmFB6DE3vnXsvYyrjTIjfoYgkDOyXi/Xb8NPE4hVDEc470VENs3MXGjyUTujNi/Xb8JfFshRg1/1j1zCjOZ5S84Rvt8Qk1YgIrDRC3hLU8845yjnu8ZtKRBc1owGekNO8Fc24N9iXf0qdRYfeUFvaOyoM0byvnquy28QIeCsya+szSI3qpF2zjOL+9XyhghkRZHoi9q52UfPpjl64zxMILNE8tSljDbVbnFP8Z9l3xLIT/+flu0mgt55sjTrE3WECgR/i37l945W7BPEjOzZa7SV10f42CFmikBEKK1GH/WtTvTKwkDtq9/BaP1hAkHzv/0tMnoRvTg06pO2V+OO6yECgTjEQiDRbxHJveMFeM/0pEBkb2+9q+MvCYSFwWOIhH1R49UJj6z/KbFEniF7LrdALM1vwYwKJjuhT9V3p1ieOuMT+04VyIyvWSKsJ4haZc+7hLIKH1frNAsEn/h4O/SaHzjERdurB985/g6R7Mwfn60rkFZjW5rfgmnl7vmQlw9R4/ZbdbZQ3sD7oUDOGhVNKvYIB8zRusePXGzfcDkRZ5wpkoj6suf4LRBuWDQi+/S4h8G6EKBjrXPk0FbisxObqb5ZIsl0xlm1fFrNioZsrcEHjFj42HLB7LeMOTfGHMe5a2z7sCiR2HjS/TQsEGnYVvOKX28ic27wszFywjK2lbd8bb5bvMwQSWufnXyhAukRw83eGkMUYmW9l6/WfRyVQHx8SX+dCgSN2mpm7bM0q47BnIUhY0uuwvh5KoH4OWsKRBoXTYsmPrPeZkUu7MHWm6vwvkuPFsnu/F8SyFVyShi+5r7Kt8SXQHycuwUScUmVw3dJkXyVQHzcHwoEX4PERl5Q5XqezxKJ/Q4OBVKNbCdxNa5KIPa7/ZtAVrvsqtd+2eCqBGLn7LdAQF5ZO3mrclUCsd/x73+LteplV932ywZX0QKRfMi9my2BDPx/sVZvghKI/UOlBFICCfm7kdU/NI7qL4G8UCDSDPUWsb1F/gs9r28akoDJcAAAAABJRU5ErkJggg==" + }, + { + "name": "British Courtroom, Continued...", + "description": "This big idiot they call a judge just keeps piling on crimes. With each one, some robot you'd like to drone-punch gasps in the back.", + "choices": [ + { + "key": "choice 17", + "name": "\"INTRUSIVE SCANNING ON OUR CITIZENS\"", + "exit_node": "British Courtroom, Continued...", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Long Range Scan Report", + "value": -1 + }, + { + "effect_type": "Add", + "quality": "Crimes Committed", + "value": 1 + } + ], + "requirements": [ + { + "quality": "Long Range Scan Report", + "operator": ">", + "value": 0 + } + ], + "delay": 0 + }, + { + "key": "choice 18", + "name": "\"SHOOTING DOWN A COMMERCIAL AIRLINER\"", + "exit_node": "British Courtroom, Continued...", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "UFOs Shot Down", + "value": -1 + }, + { + "effect_type": "Add", + "quality": "Crimes Committed", + "value": 1 + } + ], + "requirements": [ + { + "quality": "UFOs Shot Down", + "operator": "==", + "value": 1 + } + ], + "delay": 0 + }, + { + "key": "choice 19", + "name": "\"AND WE HAVE PROOF YOU KNEW IT WAS JUST AN AIRLINER!\"", + "exit_node": "British Courtroom, Continued...", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Commercial Airliner Transmissions", + "value": -1 + }, + { + "effect_type": "Add", + "quality": "Crimes Committed", + "value": 1 + } + ], + "requirements": [ + { + "quality": "Commercial Airliner Transmissions", + "operator": "==", + "value": 1 + } + ], + "delay": 0 + }, + { + "key": "choice 20", + "name": "\"THE TRANSMISSIONS WERE NOT LEGIBLE AND UNTRANSLATED. WE CANNOT PROVE MALICE!\"", + "exit_node": "British Courtroom, Continued...", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Garbled Transmissions", + "value": -1 + } + ], + "requirements": [ + { + "quality": "Garbled Transmissions", + "operator": "==", + "value": 1 + } + ], + "delay": 0 + }, + { + "key": "choice 24", + "name": "I think it's done listing crimes, thank god. Time to argue my case.", + "exit_node": "Verdict", + "requirements": [ + { + "quality": "Long Range Scan Report", + "operator": "==", + "value": 0 + }, + { + "group_type": "AND", + "requirements": [ + { + "quality": "Garbled Transmissions", + "operator": "==", + "value": 0 + }, + { + "quality": "Commercial Airliner Transmissions", + "operator": "!=", + "value": 1 + } + ] + }, + { + "quality": "UFOs Shot Down", + "operator": "!=", + "value": 1 + } + ], + "delay": 0 + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAJiElEQVR4Ae1cQbLduA18N8gBsppVVj6B9+MLzA1yn1z5pVBJu7owlAhQoARSUNUvkGADBJvop/dtz3z+8/PzrZ/ioHqg3QOfIqZNTPFSvEgPlEDqDVrfIE56oARyQk69ReotUgIpgdQb5KQHSiAn5NQbpN4gJRCHQL7Jn8/n85WfHZ+nPqy2EciOTeE9084C6XExS0DLCaRH1JvX3ywQ3Hu0UNILBAcv22cAAtn1a1afgW/4Hzg8IhDLQe/EoLF4z5aP1zOOUfMqAkG90VxGvkVuFUg0EVH5WhfV8kXtNyMP6oUd3QPxbM9yWXGcg2Mw5vWr4yUF4jk0SIP1xI5gsY9YefR8JOddMVwrjy37A89Y+NjyOo8ZgzGvt8bAadvCjvq2FYgmDfNRojxxvBePz3JYcbNyYP+Wbe0JnKxhLJYf+M8wwLewWGtZ4HlP+Fr4Ud9yArEeFGQxgdbYqzjeG+Nezhau5ZM88IvlB3749Bx+bYHjfOxjP2KxLnMeY73l7+GwD3CYc06MgcG8tR+vjY63F8goMVfjcIGwvXwtHPswblnkxpqei//s0XGChQ9Wx7Ofx4zTfszF8tPyw8c4HmO9ZRl3dVwCOWEQ5DMEPlhe4zHW2fK6HgMHP+Zi5eG59lligGlZzo119mE/rInV62eYVlzLxzmQn3E8xrq2jIkYLyUQz4FBXC8GOLH8WPw6huNlzDm8WI7VubCPxjCOx8AhrmWBObMcp3G8hjFjtA9zsVZcL4bXo8bbC4TJx5jJg08sHvaxX9Z5DfgjC+zROvuBbVngeE379JyxPAauZRmHseB4jDj4YOHXFuvaMo7X4G/5sCa2t87Y0XGkOCTX9L8H8RyUCdRjnYfXZY3nGCMGc7HRD3Ijr56LHz5tdQzWtR/zqxb5xfYexmLMMfDpXPAzlsdY13GMuTLeWiBCDAi0kAQsW87Bl8AYHlv28WCQW8fAD6vXs81Rp9jW01tvxcDHsTo/1oD12GhxTH+DeA4HrIcgYGF1jp5f1iMf7BedN7LGqFxXzsqxR2NvnTPEkVogEU0G8q1kj5KMfWBH84zGWc8XicNZr+ZEHraenKOcWeOm/Q7iOaTGgiztj55bSerhUK/YHvau9WiuMuW7i0PZZ4pA3krmnRc3ulemu7HWMnrWiLitBBJByFtzWJv1TlyGuwgXyEwCMxD2phpm3mUrd0ZuQwXSOrTVl5Gcs5r+/PXrO/PnbO9au+93vRQCWeXCZwqil3sVjnarM0wg1jcF41Yhs9e8d6+vwtsOdT4ikFWIu7vxvfutwuPKdYYIhN8KvfEKZHkbNRt+BY5XqfGyQHqC4PXspGRr9Kv1ZOd7hfpuEcgKRFxtxqzxK3CfucZLAuG3w9k4MwFSW9bmjqorO/+Z65sukIyHj2q8lfJkvIcVahoWyNkbQ9YyHn6lhp5Ra8Y7yV7TFIFkO/SMZlsxZ7Z7WaGeIYEcvT0yHnjFRp5Zc8Y7ylxTmECyHjKy2X78+PHFD/JifmSBy2Kz3lPWutwCab09sh4uuimPRHDFH12jJV/W+8pYl0sgK4lDyLY0iwdzRQjeWE9dXmzGRsxak1kgbxSHt6ln4b0CsOCzNmS2ukwC0eLIdghdj6VBLJhZDX8lr6VuC0ZzVvP2f2PiFkh2Ii3NcYb5/vMfX8vPlSaPiD07g2Ut+z1mqc8kkCzF9uqwNEYLYxFEDxPR9N4crbNYfT0ua/1/b5RtBGJtDMb1mj5i3dv0XjyfxzMuAbS/UmlethCIpzEEG9H4UTm8gjjCeznQjVDztmBeJxBLY/P/CE7Glpi7MCWQdiPPEvjyArF+cloaWAtDzy05nsJYeQBuVkPtlrcEQn9qpQWh5081v2VfNL7V7tbIs86ztECszWBpMC2Go7kl11MYKx+Cm9VQu+Utgfz/DXIkCO1/qvkt+5ZA4n8/KYFsIpASR7w45G24rEA8DWH59NVviqO5JdcTGA8fu30NmnmeEsgLf0mf2VC75S6BbCCQenvM+XolYi+BkEDw1Uh/vYI/qy2BbCSQf//r88XPldexpykEm7W5I+rycHGF8zfG3vIGgSC0/euPz1d+Roj3NIXGRjRlphz6fEfzEZ7fHjNdIFoUMocwYEcu4agJvP5MjT5ai+fMI1y/OWaqQFgcEMOZ9V6EpzE82NFGfSLOcy5gvTy/GT9NIF5xiHC8F4ELn22faHzrnqNn93L9Vny4QKTRR8TBbxbPZYw2SFSctZFn4K6ewcPzW7EhAmFB6DE3vnXsvYyrjTIjfoYgkDOyXi/Xb8NPE4hVDEc470VENs3MXGjyUTujNi/Xb8JfFshRg1/1j1zCjOZ5S84Rvt8Qk1YgIrDRC3hLU8845yjnu8ZtKRBc1owGekNO8Fc24N9iXf0qdRYfeUFvaOyoM0byvnquy28QIeCsya+szSI3qpF2zjOL+9XyhghkRZHoi9q52UfPpjl64zxMILNE8tSljDbVbnFP8Z9l3xLIT/+flu0mgt55sjTrE3WECgR/i37l945W7BPEjOzZa7SV10f42CFmikBEKK1GH/WtTvTKwkDtq9/BaP1hAkHzv/0tMnoRvTg06pO2V+OO6yECgTjEQiDRbxHJveMFeM/0pEBkb2+9q+MvCYSFwWOIhH1R49UJj6z/KbFEniF7LrdALM1vwYwKJjuhT9V3p1ieOuMT+04VyIyvWSKsJ4haZc+7hLIKH1frNAsEn/h4O/SaHzjERdurB985/g6R7Mwfn60rkFZjW5rfgmnl7vmQlw9R4/ZbdbZQ3sD7oUDOGhVNKvYIB8zRusePXGzfcDkRZ5wpkoj6suf4LRBuWDQi+/S4h8G6EKBjrXPk0FbisxObqb5ZIsl0xlm1fFrNioZsrcEHjFj42HLB7LeMOTfGHMe5a2z7sCiR2HjS/TQsEGnYVvOKX28ic27wszFywjK2lbd8bb5bvMwQSWufnXyhAukRw83eGkMUYmW9l6/WfRyVQHx8SX+dCgSN2mpm7bM0q47BnIUhY0uuwvh5KoH4OWsKRBoXTYsmPrPeZkUu7MHWm6vwvkuPFsnu/F8SyFVyShi+5r7Kt8SXQHycuwUScUmVw3dJkXyVQHzcHwoEX4PERl5Q5XqezxKJ/Q4OBVKNbCdxNa5KIPa7/ZtAVrvsqtd+2eCqBGLn7LdAQF5ZO3mrclUCsd/x73+LteplV932ywZX0QKRfMi9my2BDPx/sVZvghKI/UOlBFICCfm7kdU/NI7qL4G8UCDSDPUWsb1F/gs9r28akoDJcAAAAABJRU5ErkJggg==" + }, + { + "name": "Looking at the Surface", + "description": "The surface of this world looks exactly like a grey version of Earth! It seems to be an exact replica of some kind.\n\nYou see creatures moving around on the streets", + "choices": [ + { + "key": "choice 21", + "name": "Cool!", + "exit_node": "Tractor Beam", + "delay": 0 + }, + { + "key": "choice 22", + "name": "Scan the creatures.", + "exit_node": "Robo Brits!", + "delay": 30, + "delay_message": "Scanning..." + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAKCklEQVR4Ae2dW3IcNwxFtSYvIv7PR6qygZRiy8nGsiMvZlKQciMI4gMkAT660VVjNNkECIL3DGcSWX764+fjEa/r1OCXr18fpVfsddteP1HBclcUs62YO9SrBAc92yHHk3J4ysEh+09a1J1zDUBs39TUgEhgqH1nIe649hoccYK0a3YIkICmveCeYAUg9vthDkgKmjht7DcuBVoNkJRP9JX3ZhogKXBic8qb01qfAMS2nlT/pYBwaFrFEOM/iqEGBz1fUTO+x/x+RS49c24DCC8e3fcs5s4+qwCR+6Ztn7JX2wLCC60pJh9P9xqfK42pAWK1Vlnn3rZVPt5xjgCEb4IsCH9Wu5e+V2nX4LD4eFWrbevzU2p/HCCtGyHHn7IxLXl6AyJraNFuWd/KsQHIBX4WzRMQCxhSMVaKvmXuAORwQDRw9HzESonasq9FpCvHBiA3AKRFYJYQlGK15LRy7O0AoU1bWXDLuf/89u31R9ullaeKds6SoK2faXNaPS4AOfgEkSDk2hqRWQNQi6fJaYcxAcjBgMiTI9fWCK0maOvnmpx2GBOAHApI7rSQ/VqRWQPgFU+7HqtxtwPEqnCr4xAIuROD92vz9BL0zLjatbaMC0DiBHn9jxYzhTxzrhYYUmNvBUiqACf28ROidpJo1zdTtCvn0tYD4y4PCBZ6JSu/Z5Ta2nWvFO3KuWv1uSQgtUWf/lx7grSuc6VQd5sbtbsMIFjQ1W3ptJDPWmuxm0h3yKf4e7F2SDCXQ+vmX2U8QRAnSE4V9v2vgEA89uHHIiKvsO8/HiNPiVy7t2ZjO3Y97w+AoKgzlom5wr6Lv1YL7clB42qxcs9n7P1JcyQBQfE8F4I5wuoBKX28kifJSF099/202EVAUGTrRSFuWBs45MliUVfrPT81ngoQWfDexco40dYDIiEota3q2rvPV/LrAsRqAyKODpASDKmPXZZ19RQ75bn7FYAc8LNYKQhy0FjC4SFgmV8AcoAA5abt1G6Bg6DxyH1ExJp8RuJ7+8YJ4ghwq7gtxmsE2TOmJsSemPCpxV75PAAxBITewWsvCwhmfbyCgL3tSgBqcwcgg4DkxLqi31vInvFrQl31PADpBKR2UrQ+tzhZPAU8I/YqCErzBiCNgKw4GTTwzBCw9xwloa56FoAoAWk9EazGa+AAtN4CnhV/FQypeQOQCiAQ30l2lpBnzZMS7qy+ACQDiNUJYB1Hc6LMEu6KeWaAwdcVgCQAkaLWiHLHE4Zv9NXurUCp1SUAYYBIME5q5yCuCeD05y2g9Kw1APn5qP7PPQlKToy79vcII3zefpD09oBI8V+tDWhD8LqfnJZ1ui0gHiBAjDtaufHR1gFzS0A84DghZkChg4LX6XaA0Lv777/9+nh6evr03YP6PV+rTxa+8XGvg+U2gECcJQDoFMC4nMVJgeeyjX5pU/PCd6YNMHRgoE63AARilSKl/tyLRAs/Dytz8Z4P8bHxYXWgXB4QEjcXY6/Y8S4Pf7SlxXNpMU72U5vnh3GeNuDQwUF1ujQgXHh0T2KsvUiYfAza3hZz8pypz2PeACQA+fCuDDgsxEYxUi+IGc/QHrGABTGtbAByc0AgLG5JXCmxUl+qPzee90Owvf4pP+TDLdaRGs/zaXkekOgguexHLBIOhNUjIil+tEdti4hl3lgP2dE8ApAA5H9ARkQpRerVphx5nminLAeF7rlfS34BSR2Sy54gJBoIqUU0Umzka/FCXMRCe9TyNSK21gYgNwaERALxjIoQ/hCeto1xI5Z8uX+ujbVSjnx8qR2ABCCvkEDYXCzog5jQnm0xv4UFJGS16whIypBc9iMWCQSCsRAfxUOcnPjkc7QtLMXgcUptrBt5cj++DuoPQAKQ5LspRAMReVv5ri7nR9vKSkhy6wtAbgwIiYKE0is6iAr+aLdaKVbE87aYlyzPmc8bgGwOCP2dYq9NIlFIcXChzLov5VB6RvlBzMi1tU1+NEdpHq/6XyHu8u8gnoDQBnFhtIpLjodItZb8uTgRDxbP0Pa0mIvXA+u4gpC91mAGSK/Q+W+l8FgkRLDKpgSJXErPMAYW8GjbGCctQOH9HnW/SkxTQHog8QYEG8UFMeu+BAB/lhM/+nst1gl/tCUkqFHYzx/3bwMIbT4EMsNKEfI5ORy8f+Y9zyHA+AwGanIrQLDoGULkApTz5Z7Jd3rPNuWAPFCXsJ9BuSUgJAQpWss2F5+MC1HK/hVtyuXZ6d81vApsJoCMfI8Y8R3dBC9R5iDI9XvlUYsbgHw+MaSmzAEZ+aIuk5vZrolJ+zwHAfXnnmljW42jU4NeAcgkQEjIdNHvmqKrVdivTv/90eprPX5EhDkIcv0jc/X6Ao53QL4375d1zXeOZ3KC0ALl1bJo7tvi5z22VYS5EyLX3xp/dDyH4/nb98fz94CjpqEAhP3zB7liaYTJTwl5r/H3HPMRDPp4RXC8PJ5fXuL0qOy/GSAkLn7lxJbrH/HNxbTuL4kYUMDSx03cl/y8nwUc7R/5uW5MAaHA/OIT1e57/WpxPZ9D3AAhZTFmhf0Ix9tHqreT40ecHJWTA7rZEhCCBQnubFNAyL4VYNCcSTheXh7PP/56PP/19xH13WHvXQFpETo/QVr8VhZRwiDb+8Dx9n0j4Gh/4zUHhATLrxYB9/q1zGE1VsIg26l5ZgDz+eR4hyOVE+/j9dfcc9+r3rsDQoXWFk9uitbPY5wUfEu7NR8rcFJwaHKRde9ta+Y6bUwAkviyBhj++fLlUXphnLQWImiFhsOhnb8XBK2fNo+dx00BhAqqKUKq8Bo/yzEk9hIU8pkHHJbrQaxUbWf3IZeT7FaAUOFS16yCtsIBWADJrDy186RquVOfdh0rx7kAQgtKXZqF9vppYpfGQOQQfasl/1L82c9Sddy9b3aNNPNNBYQ2SJNUaiM1fiNjek8PgLQTIKn6ndY3speWvscAooWrtzhXAOQ0CFry7d3XUT83QCix3KVJesRXE1+OOR2QXL2u1C/3bEb7OECw4dbFORkQ1OTq1nrPNfFcAaEEcpcmuZwv79fE0YwZAYR8NXN4jOG1uPq9R/1qMd0BQQKpzcOznE35pPpy/q39JPRWUODTOtfo+FQdrtg3WqdR/2mAUKLy0iYv/VJtbSzNOIg+Bwt/XoqXylPbl4ur9T91XG7dq/qnAkKLlJd24dIv19bG047jMOBe45vLL/rzFdDUdfaY6YDQAuXVs2gZg7d74ln78HziXlcB6z2wiLcEEEpcXr2L4XHwW1WorzeelR/PK+7rFbCqu3WcZYDQQlLXyAKt41nnksov+ta/mZX2eSkgHpBgsVx46Jtl+dxxX6/ArH3pmWc5IJR07upZ0A4+ufVE/+cK7LBfpRy2AIQSTF2lxHd+llpL9KUrsPM+Um7bAELJ5K7diyjzy60j+j9XQNZut/a/8+89HVF1Xw8AAAAASUVORK5CYII=" + }, + { + "name": "Robo Brits!", + "description": "Wow, they're robotic humanoids that look and act exactly like the British! You see some robots laughing and chatting at a pub, and a Bobby walking down the street looking for crooks. Who created this earth replica? You only know Earth from the books, but you should be where London is!", + "choices": [ + { + "key": "choice 23", + "name": "Nice! Well, back to getting pulled in by a Tractor beam...", + "exit_node": "Tractor Beam", + "delay": 0 + } + ], + "image": null, + "on_enter_effects": [ + { + "effect_type": "Add", + "quality": "Long Range Scan Report", + "value": 1 + } + ], + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAP2ElEQVR4Ae2dT8snRxHHnzcgYT3ksEgie1gXVkhIUFA0F1cPezAsHjzsKYR4URSvHj0IycmjvgLxIIigOYme9C1IZAUvCnkJXloqmyKfp5+u7qrp7pn5PduHpmqqv11VM/P9Ts/v+Xv14duvppHj6uoqjRiRnn70wR/TGuVr8OE77yRrjLhPVg6rZjRu5c/j0bxe/FWEiBY2b3bUsVWvFF8COZdAhANeElq4rTyy8m2Jdwtk60l415XEUIotgZxPIL0i8XKkhtsiCq7pEkitsVFzJTGUYksgSyAezpH8Hn8JJPv8kl/kMwov0mONBHmeGce1+rW5Gb3kOWv1dW4J5FOB5BePx2cRCXvKfatHvdElm+eYcVyq64nN6KWW0+rp9AKRkyq9UuUxiyCteO2ica6VZ9Y8e6j5Vn3rxku8lm/UXK1+bW5U/WievKerPEFOvNpxvnbWca0HnbMIUotH+q3lmTUX6Y9Y9pPfcB5zzSyf9bz+rF68ednnDYFIEiVdy3oL9uJafcg8SdHyt/bTyjtyfmuPuk574c3OfcXOsnk97/GsfqJ5pd+iQCSRh5TRgj34Vj9KiJbt6aGVW+Y9+UfladWSOjVSttb3zNfqWnM99SauLX/nu0VInZ/Y2DWyab2abRGvt9fR+Wv5enuV9S2BfPqEvHado3Utskfj0bo74pdAvBe7RmiZ8+YhzspJzFbfI5CISKKk9+K3nt9O65ZAvBfaIrPGvXmI07UlS1zU13xekh6Ji57bzvg+gcgrzx4N116tdE5JUbM9vdbyytyW3KNz5vmOJL639pbrtuOaF0sgOYFqx7wJNZzMEev1Wzl13sqn8zXrJeneOOucThhfAqkRzDu35cZ6c/fg9ia+p96Wa3XgmpsC0VcWr92jeW8vPWTqWRu9Bj21Ims9hN0TE71OJ8AvgUQIZ2GjN9LKMzq+J/k9taLX6Ui8nE/XNwr1qb7HSWgtjx1NMk++6DXw5OzFeAi7NyZ6nY7E31qBiIh6ybVlfeRmbskfXbM3+T31ItfoaGxVINKc52ktmNkn4u2DuCiZRuAj12FEvVYOD2H3xkSu0dHYpkCkQZKu5ntPhjk8a4iP+BZ53v/F+ykfFjYa95yPYqK5t+BnkV/PoWQ9NUvrzhgbKhAhb+0kI+Qehf3BP9MnYiC5cnGMPq5dA52Tfnrr8pws30PWKEbPoWY9OWvrzzLnEog0O4qwe+dRgfSSsWd9frN7crXW5kLxEDWCyc+ldjwrb63m6Dm3QKTw3uQeUa9FqNs4T5FESNrCbiFfKyfnt+SfvUb6M7/MWyo+grR75riNAvCeE8k3wi/xoRXbUreVc8/5JZDCB3YvAc+O20JOa81WUlr5PPGtNUeukz5DO4gU33MH6K11dhLP7M9DQg+mh3Ce/C1MT/2etdrXEsjaRab+Wq4Srdf2kH3LWu13CWQJxBTIFmKV1ijZRthS/tEx9rkE8gIL5M7nX05bR4SUJNwoP1I/imWP1/5wnCdR7+eCPdfPfMe/hNy80SV/qzhknYcrxJTqj4ixxiiffV0TiJC3VmRPcue9bK19CUSe1SNvdMm/DQLR86rxNjqnOcXeEEhOTE2+laD5Os2nNp+36gu+hG3FZpHvEvLyRqtPUXzhi6+l0vj9X/+WdJTmJcY8ei9rVuvPtrUevHPssSiQFul65q0mNac1r3HFee2ZiPy/dJVk7NUTb7T6JLZFfhWHWAvDPHpvalbr72VrvbTm2ONpBNJqWue9wlDcXmT01FkCsf8dHEk50lfeeG1ee3eBKHE9DQs2x+l6r/UQdy/MmQXCXePP//h30sE4d5Oz7yA3iO7835k31nmJNguXC6B2HO1hL+J76+wpkPxG6zGJTcJTCCoOsYwTzzy1e6ZzWv9Iq71YttTbYTsIyV5qWObzONfQz3GRYy+5R+CWQPZ/xSqSvrCblHASO4VASmLISU5B5H6OjRyPIP4Zc1g3nE9+a3fwxJmndb2tXi4hfhqBtESSi4LHrRtUmz8juUf1VCIgie0RAl+riGee2vUt9XBJsVMJpCYSCiLHvfTSS2nr4M0dRcyz5CkRkcQm4S0hWHHm4TWkX6p/abFdBMKLJn5O9tIx17Tmt4pD1rHOWYg9qg8lI8k829frqbUv3R4iEK9ISsLQmN4IsUsgN/9Si4hMyTlbFMyv90VrX7p1C0RPnFbJ2rJcQ7+1rjb/4MGDpOPRo0dJx+PHj5OOp0+fJh0aE6tYsZpD7Kgn95nyCEFJ4Nm+3t9LF4b27xKInnTJ1kjMudJajRHn9UlsEp5CUHGIZZx45jkTsUf2ojc7F8vf//SHpONXP/tpag3FiqXQ9D6qZb1L94cJRC+O2BLJOZ/7JXwrRmKT8BTCWQWy5/dDVGhKVBKbhG+JQ+aJZx7eT61zW2xVIDxx9YW46qu1YiS5YkuWuJpfWjsrpsSaYY8QiJyHkJbEJuF7BHJbxFA6D1MgFvEsMZTwJDvnGY/4zDHbnyEMzbm3QKKiSFdXSYclHIqL+Usku+RYSCAkMwmqccboy3x+rGsiljlm+0rmGXYJZOyPnJS4MEqULoHkJM4b4nw+Vzomfqv/21++n3TwaRb1NYfYGWIo5VwCGSeQEr80NkIkYYFocdqc5Jwr+Tl+yzGJHRUF8cxTIvOM2B4Cuf/6u0kHX4H+8rvfJB3W65MnrjnEMr/WFDuCoLUcJW7lsdp6z9w1geTJ9VgJrMe0OleyxKlfwm2JkdgkfNRnnhliKOVcAhmzgyinPNYjhhLGLZBSE15ic613TQvHJxhFwaffBz95PelgnHjmKZF5RmwJZH+BKAdLIqjFqgJRkmpyWp07ypLYJDyFoOIQyzjxzDNDDKWcewvklfvfTTr4OsRzj/rMo7nF7vWKRS5G/Zog8rmqQKzCR4mCdXlDSXgK4awCKYlmdIxEJYFJbF7DqM88zM+6OdlGHlvc9Ma9vVwTiBCwVYAkPdL3iOLr3/xe0uERy2iSHpmPRCWBSeyoKIhnHuZnXS8Jt+BaPPXOt2rfEEhNJEcKIq+9BPL8y9IkpOWTwJ97+VHSQZJHfc0hlvmtHhhvkbI17yW/F1er5xZITtCjj5dAlkC8AvDgLJEUBSLkLyU9QhSlPiTG7Z6fO+hbr1WMM49V68hXpVZtPpktn094PvmjuwbxzMP8Vg+MW2T0xq371BO3ai+B4Btn1gVukfTIeRLvy1/9cdLx8Gs/Tzq+8o13kw6dn2W1pljWYJ8WGb1x6z71xkv1TYHobsGiGpthWUd9qaN+bvnk565BnzuFFWeevIYeHymAVm0Sj4QkUVUcYomZ4bMu87PPEhEjMb0vo22ph5BApKFR4ug9ORK7x6eIrJ5aJD1ynsQjIUnUJZAr80HLez5EIL0iYUM9fo8ouPYSBWKJgnEKxPL/89FHSccbb3wntYZixVo5GWc/FC/jJVLWYj2caa0t1W3uIKXXnC27SKu56DxJ3uMvgTwXSUscMr8E8var5isUCRwVCNd6/G+99e2kI4rXdV7rEQhzHfk6pbX5BLaezHySWz4JvwRyVfzpY9cOEhUE8R6C5xgSMp8rHRMf9Y8UyNafyaJASH7G6RNj+RTLK6+8kXQwbq1lnHXpW5jSa00tVrr/o2KlulMFsrVxktyTg/iovwTy/MvBFIKKQyzjJLnlUxT0iWe8REor5uFCD6ZUd5pAoo2S2E+efD/puHPnTtJx//79pENjYj/++OPiuHfvXtJhYZhHc4tlXHsRyz71lWerXTtI7JeqopyK4EvikNgUgUQaUyyJR0KSqBaBLfKrOMRaGE9+9sM+twpD16lAxGrMY/kEfvPRr5MOxunz6f2v/z5JOnSdWGK4gzBOvOYQSwzr0udaxi1i5nHlySyb19PjJRDHDjVLICIGFYlHGIohwSziEUMCk9hcS8zZBDJLFJpXxVCywwWiRaP2vfd+mHSQkJ4nvLU7PHz4MOmwMJ787Ed7FKuE7bFLIO3fLoxyKYovCUNjSyDBHeTSBcJdw/K5m1gYxonnzkWfeMaViDUbJXwEX6src0sgJxFI5HMICWYRjxgSmHjL78GzLn3WYrxF0E9IWviXaRER1LCt+qcRCF9jrBO6e/duKg3inz17lnTo65VYjYklvpRPYsTwFYp9Mt7jR1+zSDCLeMT0EJ75LZ/5WZc+1zLeIuhMgbhq85t6I3wSK+KTeNY6D5kphCWQz/42Fkl5hL8E8umPqVjkbsWXQPxf6iXBLeIRcwbf6tPzFJ+xi7jrjtg1mKMlBGt+CWQJpEVaiztb4q1aOr8+gzg+1/DzBYXM+J4+dwTr/Z/9vPX6l1Jk9KxVYon19Em8x98ihnyNp45ilkCWQG6Ip0cgslbJNUMgkjsnfPRY+/PY4QKR161ow4Lnk9laf1s/pJOQHt9DPOaJ7B6C7V2rxPP0qdiItfjhiUfqCHYJ5MJ3EJLQ8vkaZvn8EE2MFSeGvtWDFY8SVvEeMeQYXRuxSyBLIJ/8sKElBCtOUdC3hGDFI2TNsbkAPMd5jtbxFIFsec3yvGJ5LgD/uSd/14NxTx5i+MrBPhnf07fIZsVJYMu3hGDFrTxWD1a8RdDWPO+T12/l5PwSiOPHGEj+MwiE/fAXmuiTkCSz56d5rbUUC/MwP9eyH/okYK/vFQVxkZqnFogQgSfm8blT3NYdZAnks58A9nAix1ykQF57+GbSwV9K4leudN5rR61lP6xNoh7l88lMn09yPuH55OeOQIy1lnjmsdayH/oRgnqwuQBax56cijnNDkLikZCjSM78Hp912Q/XHiUK1iXxGPeQ3CL2jDzsU8k30rZEwflI3WkCiX5QJ/FISBKVGI8/ai37YV0S6SifxGMPL5pASHqKIfeJ8/inFAhJaJGcpKU/ey3zk5BH+Usgn30eIeFzYcgx573+Egj+SB3JbwmTmBmiiP5uiNUDdxCPb+Vh3JOHGK5V30vMs+CWQE4qkMhvGCr5aElUj8+1lu/JQ4yV5yzk9/RxGoHw/5XTt57k/H4EfT7hR61lP/QtAvTGdRfpEQmJ6vE9PXvyEFPL6SHnGTBTBRL5oE7i0R9FcgqHgqJPDOuyH/o1AvTOqUh68+Treb70c1zpmHj6JawndgYBtHpYAsFfcVwCef5/Dy1yUxT0LXwr3iLnGeanC8S7i1hPZj7J6fMrV/SJsXzi6Vt43mirT2JG+C/CDiLX6QwiqPWwi0BEJJZQJC4XyiKeRVoSm76FZ5x4+sTQJ+GtPokZ4c8SCM+Xvqdn4ul71lqYGjnPMLerQFQotHrhLOKRqPR5g+gTY/nE07fw2qNYq09iRvgvikDkWp1BCFYPhwrEQySLtCQ2feKZn3Hi6RNDn3n28l8kgcg1tQh6dHwJBN8HoSjo7yUK1lGBiGV8i8+HAD9c0yeGNRgnnj4xXBv1jxZDqf7/AdVoY/i7GpcvAAAAAElFTkSuQmCC" + }, + { + "name": "Verdict", + "description": "Before even getting to defend your case, the judge says \"I've heard ENOUGH! It's time for a verdict!\"\nDang! You're definitely in kangaroo court!\n\"GUILTY! AND YOUR SENTENCE IS:\"", + "choices": [ + { + "key": "choice 25", + "name": "\"DEATH!\"", + "exit_node": "FAIL_DEATH", + "requirements": [ + { + "quality": "Crimes Committed", + "operator": ">=", + "value": 4 + } + ], + "delay": 0 + }, + { + "key": "choice 26", + "name": "\"SPACE JAIL!\"", + "exit_node": "Not Actually Space Jail, Just Normal Jail", + "requirements": [ + { + "quality": "Crimes Committed", + "operator": ">", + "value": 1 + } + ], + "delay": 20, + "delay_message": "You are being jailed..." + }, + { + "key": "choice 27", + "name": "\"FORGIVENESS! Just trespass? That's not that bad!\"", + "exit_node": "Sweet Sweet Freedom!", + "requirements": [ + { + "quality": "Crimes Committed", + "operator": "==", + "value": 1 + } + ], + "delay": 10, + "delay_message": "WOOP WOOP" + } + ], + "image": "default" + }, + { + "name": "Not Actually Space Jail, Just Normal Jail", + "description": "You'll have to wait out your crimes against the robo brits.", + "choices": [ + { + "key": "choice 28", + "name": "Sit out your sentence", + "exit_node": "Sweet Sweet Freedom!", + "delay": 1200, + "delay_message": "Sitting out your sentence..." + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAOkUlEQVR4Ae1caQ5etw30lYrcoblZgZzaBVsMMJlw1dNbnOiHwX04lMT3GUbaH3/854+fu//89q/ffnb+7O67E6/D33J29rwb61eaqcv17jv4cceldIe7o/cuzL/DDHoWv9JMXa6Wp3PutM+CBL+g3QvaeRl3Y/1qM32B71mQsyB/+evw3YvaxT8LEjzO7gHemfeFy9k936820xf4nl+QYEm/cDlnQd7/x56zIGdBzl+xgjdgH6izIMHhnF+Q/f/8P/1F/MIdnAU5C3J+QYI30P4F+fnjx0/9k30Nos1XDLYzPItxLvSq5kqcZ0A/lohf6aG1hq++nTY4q7S+7NvZU7EmMxonPnPVJ5y1VnlFdvoLoqCe7QEz8WpIxfTwzKd5kR3VT/08Q9RL/dMemg889a/YHhbPBB15sE1O+wGjquvmAce4oMaTytnLiXzoUclwQSJg9XsNmLjmd2wPs1OnOR5O13fHDFlv5p7ldWKMZTpqeCbTNQ9x5HekYkQ13Tyu9zgyDviyr6tzn0x3F+RqkyvE0VtJwz+VitO1MUN1SRmfbi8Po1vr5UV41UyIe5iRL+rF+Z0czmfdq4UPfGFPJPfI9L8sSLdJBnrlUaG/4sO/IhWrY+Pwr87S6RXN1Kn1ciI8zBTFzW85Hmbki7CQH8XNj5xMZvWdeVCf9chi7QXJQDRWPSoMhjoMoRJxkxozO/JHuYxX6eAYzRL5p729fPgqjl4ctavS5vJwPd9qD9R5mOxDXiS9O0K91sA/lX9aEAWF3QG1XOShzpM8FPJNerlZnGNRPWNqfmUzT8YxnWNXeiuuZ1c8Ne5hTH2KGdlTXC8/wja/l88+vQfG4jzTOTbRywXJwCIS6ofNA5nO2MhhmcU5Bp1rVUdOVzJXxeIY8DSHbeSo5BzTLe75tC6ytXbVjvDZv4qtdYypuuayzXegb8lwONd0xe7aSwuizWGjKWyV2VCaazbwTGqcY9A1h23kdCVzZRzTOQY8zWEbOSw5Dt3i0FlyXaZzzVU96xPxXO0Z9crw+A5M9zC03supfH+bBdHDULs6CI3zBSgWx6xO42ortlfDOVpvNscj3atb9UU94F/F9eqAqdLLhY/vAHpVr/GOvbQgBgyiLNGQfapjGJNVPuJeP8QU37ORO5HMUzERU79nez29vMrn4bCvql+JMz7rK1hVDeNDz2pwByxRB6n18E9kuSDWxAPU5pznxdiHoQyX/axzT/av6IzV1cFxpR/XaD+OTXXFYjvDsjwvbjN6fvgYn3XEPRn1ivzAYHzoiHkS98MSdZBaB/9E/mlBrFBBJzYaVwffwQTWLk6M19Fx8B2uUY7XJ8rt+qeYnM89MF91V1xvOmOozrkci/ycYzrnVb2YP/SqXuMde9uCcLPq0PVg1GYs0zXetRVnal+Zw+vV5Z3lTXE5H/PgQbGMenK96VGe+b1c9WUYnJv1sRhzh871Xh+Nd+y/LIgHXJHVwwHhTp3meKQ1p7I9jBWfzVH10njWR3NhT2s0HzgqNQ/34kmtZZtx2M8651Q616mOWvWr7c2AWkitgX8i3QUxAAWPbK8Zk4/qPL+HdZVLhNnx2xweT89X4Xk15lup45oI18Pme/F0xuIe0DmuOnK6kuu9Go6r7nE3XwfHy8l84YKgSMmxjRyV3gBcp7rWq635bGvuTtvm4F6qY85pT+B067r5yDPpYYNvR3r17Kt6ce6KzviqR/y9PlzrxStfuSAVgBePBlC/V/sln/KN7C9xzrhE/D1/hvN0zOPn+e7gdRYk+Z9bepfg+e64mDswPe6R747+q5gRR8+/2iOqOwvyD1oQewTeo/J80YN5w+/xi3y7+Z0FOQviLs3uh3YFL1oGz3+lj1f76oLYgB6pr/i8C4h8X+Fc8Yj4q7/CeTqu/CJ7N6+zIBt+Qeyydl/MXXjRw1L/Xf1XcZVfZK/iR3VnQc6CfP6vWPZ4o4VQf/TQV/23LMibA60eRFSnFxDZUf3X/BF/9R/e//9/ljwLkvyC2CPRhxPZX3tQEZ+Iv/qj+rf8yi+yd/M7C3IWxP0I7H5oV/GihVD/1T5afxbkLMhZkOQNnAVJDse+JvqFimz98nzVjvir/2v8lV9k7+Z9FuQsiPsR2P3QruJFC6H+q320/izIWZCzIMkbOAuSHI59TfQLFdn65fmqHfFX/9f4K7/I3s37LMimBbEL2305d+BFD0v9d/S+gqn8IvtKD6/2LMhZEPdX0nssb/qihVD/bo5nQc6CnAVJ3sBZkORw8DXSr1RkI//LMuKu/q/NoPwiezfvsyBnQc4vSPIGXl8Q+xLs3vrdeNHXSv27+67i/fv3339Gf5RzZK/2vqsu4qn+3f3PgiRfDxy2XkJkI/8tGS0F+yPu6ueat+bhvsovsrlmh34W5G+yIPygMz16WOrPMHY8vCmG8ovsKW6Vf9uCWONoCPVXJN+OK9/IfoNn9pC9WMRd/V6t+p6cV/lF9m5OZ0F+4V8QfbAdO3pY6u9gIWf3o/TwlF9me/Wrvs8tCA4dcnWwnXXZZXBsZ88OFs5oIplvpk8wLbfD90pOxlVjV/po7WcWpLoQJf6krRcQ2U9yqs4rikfc1R/VZ/4751d+mb2Tx1mQX/SvWNlDzWLZw+JYhlHFdj5QxmJ+mc41V/VPLEh14IhfHXa1PrsMjq3ir9ThTCbS+jDfTJ/gerkrM1U1GV+OVTiT+C+1IHYRk+F25fLhZ/qufh0c71FmPmBm/DmWYXVi6LdTMr9M39lz64J4B5cNgphXl/l2HkAHCzwr2cHalZOdj8a4ZzUD4lajOFOb++7Qwa2SO3oBY8uCZAdXDWPxrD6KYYAnZGcGy3mCC3pE56J+5ENOZ1G8qY2+O+SU+46elxakc1idoTo4mrNj+C5GZwbL6eLtyNPzmNideZjjBFtzGeeq3uG9+x6WF0QPIrK7Q0X1mf/qgXfruzN08XbkZecyiWWzKc8JLucqzqqdceXYKr5XdxbkH/bPvPxwWecHBt17MFzT1T2cFR94VXIFO6pZWpDuwVheNQziRnCCa7nRULv94NiRu3tHeNOzqvK92aLe5q/wNJ5hdWMeR8/XxevkfWJB9DC7dmfAHTneJUS+Hf26GN1z6ubpTBmPLibyMqxuTPlFdhevkzdeEAzcldEQ8HdxvLzOgDtywLUjd/TrYnhncsWn81U8pr0qvCqu/CK7wpnEb10QIxINAf/0kDl/MujVXPCt5NU+03o+j6u6ztbhMunZwctylF9kZxjT2GhBJodhuUYmGsL8UzzNnw57JT+bg2NXeqzW6rms2jyH6R0+014dzChH+UV2VL/iv21BQCYawvzTw+V84D8lszk49hQfrw+fz1TnGaB7PTzfpJdX3/WBVyW7eJ282xfESEQDTQ5WczvD7cyJZlD/zp6rWHpWHVvnMLvbv4OPnC6ml+dx9Hxe7arvlgVRMt4Q5sOhTaRiP2VHM6j/KT7TPtEZK3+2Jz0ifM8/weVc5pbpXHNVf3VBbEjvAD3f1UGv1mcXwrGrfZ6oZ76ZPuHi3Vnkm+BybsaVY1xzVf/8glwdcFc9X0Cm7+p3J07Gn2MTDtEyeP4JLucyt0znmqv66wuCQfUgrw62ux48K7m77x141QyIT3vrHUb2FBf54FVJ5O+Qn1kQDL1jqDswwK+Sd/TejVnNgPi0b7QQ6p/iIh+8Kon8HfIsSOM/VrSDri4F8R2XcjcGuFZyykMXIbKnuMiv+CKO/B3yLMhZkHD5pw8sWgj1T3GRjwWoJPJ3yEcWxIhWQyG+Y6g7MMCvknf03o1ZzYD4tK8uQmRPcZEPXpVE/g55FuT8goQfr+kDixZC/VNc5FeLgTjyd8jPLYgNuWOw3Rg4/Eru7nsHXjUD4tPeugiRPcVFPnhVEvk75FmQ8wtyfkGSN3DLgtiXQ7e32nqOa+0XbOaX6V/gWnHI+HOswuF49Gvh+bluojO3TJ9gVrmPLYgRyYbiWEX6jTjzq/Q3+E16Vvw53sX1FiHydTE1j3llutZdsW9bEDscJZYNxTGt+4LN/Cr9C3wzDhV/jmc4HIuWwfNz3URnXpk+waxyRwtiYN7AmY8JZENxjGu+ojO/Sv8K54hHxZ/jEYb6szfAMa2b2Mwr0yeYVe7tC2KHAxLZUBpDzZekcozsL3GOuETc1R/Vs58XoNK5bqort8ie4mb54wUxsOoQong0kOfPSL8V83h6vrf4Tfp6vD1fBzO6b8/fwYtyPH6eL6pf8T+6IHZg3kCeb2WYu2s8np7vbh478D3enq/q5S1B5qvwsrjHz/NlGNPY0oJYk+wQqpg3lPqmgzyRrxwj+wkuV3tE3NVf9anumuMVVhVXbpFd4UziryyIHVo0HPsngzyRy9wy/QkuV3tk/DmW9eHH39EzrE6MeWV6B6ubs7wg1qBzKFXOU4N2DyTLy7hyLMP4Soz5ZnrGt7pbjmc43VjGk2NdvE7epQWxBnwIqzoPx3pngCdzmFumP8lptVfGn2MR/vSuI5yJn3ll+gSzyr28INZgelhevjdwRf7puMfR8z3Na6Wfx9vzRdjeHUa+CGPq9/h5vilulr9lQaxBdDhTPw+cEX8jxtwy/Q1u054Zf455uNM79TBWfMwr01ewo5ptC4IG08Pz8nl44H5BMq9M/wLXikPGn2OK491X5tP6KzbzyvQrPbR2+4JYg+zAujEcgBJ+0wanjnyTZ6d3ZwbLUazu/SFP66/Yq5yv9LxlQYwQDuiqvDLc7truBXkPazeXq3jdWbjP9C65doe+wvlq39sWxIhNDzTLvzrojvruBZ0F+f1/d7/jzBmje/5cc1W/dUFALnv40xgw35JvXNIds07n+MI9TTnvOLdHFsSITg84y98x+CrGG5e0yjWrm86R3YcXy3qvxqacV/tw3WMLYk29g1z18RBP6m9c0h3zTeaY3tEdfA1zwnkXh0cXBKSnBx7lA+9J+cYl3THfZI7o/D3/HVyBOeGMmqvyv9va9kZwciKCAAAAAElFTkSuQmCC" + }, + { + "name": "Sweet Sweet Freedom!", + "description": "You're free! You spend a good while travelling around England (or at least the replica) and enjoying the cuisine, people and culture! Good society research is gained from this or something, but really you're just enjoying the sights and sounds.", + "choices": [ + { + "key": "choice 29", + "name": "Nice!", + "exit_node": "WIN", + "delay": 0 + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAQJklEQVR4Ae2deXBX1RXHY23/6DYdFBekilIInVrsTBUUEKWitf1Hp4467XQ6Y6frtLWtdpzWhaCCCQmJieKCKHWKY52xjgsg4AIo7s601NYlIoSdaEJIQgwgWU7nxB643Nz93fveu7/fy8ybu527vHu/n3ve/W2pgIB/B/v6ofHBV+H07y+Az319NlSMm1WS1+YdncpZ3HDpT2H9yNODXRsuuUrZ/5adnSU576inoydUwYSLmmDu3S/A/gN9ynlwKaxwqWRTZ2BgEHa17YW/PvYvGHN+fUkuVAFINhvf6Gl1sPCRN2F7azf0DwzYyNLYNjgg7EgOfNIH9Ytfhm9870747ITS8SgFIOkBcnRlFYy/sAluWbAWevd9wsorSDxVQPAO0KPs/KgbHnj0n3DKeaXhUQpA0gHkpKl1cM/Db8C2XV3BPAZPWWqALLzhGjC9YjurFICEA2TIY8xshNl3rIae3gO8foOngwNiCoXMLgZYCkDCADJqai3c/dAbsHVnF/T3hzlj6AjzDohM6D7y8wpLAYg/QD5TWQVfu6ARZjU+D909+3X6DV7uDIgPwSdtIy/AFID4AeTEKbWwYMnrsGVHZ2YegyduGCBJRZtV/SxhKQBxBwQ9xriZjXBjw3Owp3sfr8/M0xVZCTpkv2nDUgDiBgh6jDv+9lqmZwwdgdEDUltdq3x1LA1YCkDMATlq/Cw4bcbt8Jf5z8Luzl6dPjMvjx4QG0/02oqlgJdvaApAzADB9zEWLHkNWrbvgb6MXpWyJS53gKBHML1s4EBbAoRCX6AUgKgBOfX8BriudhW0dXxsq89M7QcHByFTQEioWYUFIGphJ52f48+eBw2LX4FN2+LxGEQkfrZryRPrwwGSleht+00qAqxfeJAjQRtzfgP8qWYlfNjeQ3qLIkSPsadrHzz+zDvw7Uvv+fRR3PYxRWZvK8y82BeAHCnuJPOBHqP+gZdh09aOaM4YRC56jIeXvgWTLlsIn//mrYfPqTLB2+bnRfC240giCKpb7h7klOn1cM1tK4Y+hEqCiyEc8hjd++CJ596FST+49zAU7PeWbEFQ2duKMy/2JHTXsFwBOW5yDdQtegk2bumAvr5sPivlCiJ6jEeW/wcmX7YQvjCR8RgsHBhXCd62LC+Ctx2HKxhUr9wAOXn6fPj9nKdhe2uXqz4zqYceo3Pvfnjq+ffgnMvvE3uMApBP3wthISKhu4blAsjIyTUw7751sGHz7ig9xqMr/gtnX36f2mMUgAwH5NQZDWa7CT95/0+XOiBfPXc+/O6W5bB1ZyfgLhzTX9fe/bBsTTNMvXKR2xrbPkbJ7NkdObY4vk5fv/gVOOGceU6TWKqAjJxUA9X3vjjkMfAHOGL6wzPGY6vegSlXLIIvnqE4Y0g2vUNPEzLB2+bHBgU7Xlx43Bnxq8DXVq8EfB3/0ATpJrAE3wfBH0P4zexlsHn7nug8RnfPAXh67fsw/UcPWK2hdL1tQRDZs2LTxUX1Q+XpxsKW0+6InxHauLUDahe9BPi6vnTiGHBKxYMce1Y1zLnrBXi/pR1i9BiPP/upx/jSGXOM1s1kbStIJElFSu2owqR92NZXjYUvI0AoRI+yrbUL/jD3ae2PS8QOyKipdfCrWU8NbQyxnTHQY6x8cQPM+PFif1Awm98hQEgwtiIke6qvCsk2rVA1Fr6MwOBDfH0fPUrNwnWAr/uLdp1YATnmrGq4+c410LwpTo/x5HPvwrQr7wevHoOFA+O8UDDtImBRO3yeS7tJ6/BjkKV5MPg07qz4C4VX37oc8H0AFpTYABk1pRZ+ceOTQ4fv2DxGz8cHYNW6D2DmTx4E/G4Juw5B4jLBYL6NOFXtUJlNe75sqW9fIXqUD7bsHvqpS3xfABclFkBGnHkbVDWthvc2tnk/Y3z34osh5IWvSi1d/R6c+8P74cvf8njG4D0Gn9YJx0Soujao3KQt3zbUt+8Qd1784g++2tPapv7Uata/zdva3gM/u/4JaG5p9/qqVEggdG0H8RY8HLJHLF5MOtHy9rK0rp0Q5bKx+MjHx7C+vn6t6LIGBGE+eNDf+xg68aZdHhQWG6HwArapi7Z8/TTStmN0sefPK3w6a0D48bim0xa+bX9BQHERhGudNIDg+3Adq009neBiB8RWqHmzTwSOjRCS2vLiTSOddMym9VWQxApI3oSedDxOoKBITUWQxC4pDBUVFUCXTVtJxmxTt9QASSrGvNa3hoTEZiMGF1vqxyUkMNjQtB2XsbrUKSVA8ipuX+NSrRVfNuwLUy7i0NUxFbPMjsDAcopjKLPn83Xj81HOTyybzvsjli/hxdQOuz6q+DBAUFw+BENt8GK1TYuAEOWp2qWxhAxVk5xXQGISdIixqtaMynINiAoEVZkMFhNA2Lom9mRDEyoK8wZICLHF2KZorfi8YICwQnONEwSy+lSOocwmST6J3zTkJ5fSeQIkRiGHHDOtkSxMDEgSAarqmorf1E7Vl6rMFA60k/2VKiAw+itAF4mY0rKQ7PISytaM8nMJiK3oyV4ldNcyG0B6//0OiK7mmVcG+x/p+P/Xm2deIeyXH4tvUcogSJLve4wm7fHzxKZzDYipqAkQDE3r2NjZQPLWmEnAX+uPnxgUEGyf71OUNhGLjU0SEGzr2ozL1lY0V5QnBATFYyoKG6GZ2LqKneqZ9GFrYzoXaIc7eh4vW9Go7G3F7dteNTbXMtma5QoQErmLJ0hSVwdM7IC4ioav51voPtrjx+iazj0gPgRObegE71JuColsorPKdxUM1fMh4jTaoPG6hrL1SexBUDgugmPrkLAxZPPzFI8REFexpCHoEH243i/Wyy0gMcCBoMYGiItYQog27TZd7jsKQNL0FgSlTZ8xAWIrkrRFnEZ/tnNg7UFsdk0bofG2LmLl27BJU38Y2tQrVUBMxMrOGcZN6mRtUzKA2IjUhy0ttm1bsQBiKgwTAdNcyUKTNrKyMZ0HssutB7EValJ7WmzbdgpADn9hjeYwK/Gb9EvCNw0LQBy/S0IgxQCIqRhMBEYQ6EKTtrKyMZ0PtCsAYQAh0duEBSDDPQjCk5X4TfotALnhGuODNrsT2oBBtgUgcQHiAw70KtI3ClEYpqIgEeU1TAqHzVzIXHXofBtBmOy+7Jyp4iZtZWFjMx+qtfEOCE1mXmCh8WCYZEymm4VqskOW2QjCVLDs3Inipu1kYWczH6p1CQaIaEKTitRU4KK+TevK7ApAhj9iZSF8kz59wYHgeAEExcMKSyTQLPPYsbnGyxEQEiO/dpSf19ArILy4WQGZikLVBtseP9Eh02y/PuKmc6Fy1yHLbESBtnkVt49x2cyFbk2O+Ac6vJBMRWEKCN9+TGnTudBNeKhyG1HwtjJRTpw4EUwvWRtZ5PP3J0ubrMURgJAIWOFSni5k65RiXHf/VE5f1WTDNL5yKxOBbb4pEKHsfABlc8/sOoniQkBosVHoFNeFpQgF3ZPu3tny3vVvA38F/9GGC64Y6tNGGDJbXvhox+flIS0DSXZfqnx+vdi0EhB24XVxElMphbp7FpXTz8WwYVo/+6MSgc+yPAAiGgNC43qf7Hqx8QIQwbvxIuGb5rGTS/G0AMH+XAXiq55IuGnlJb0HWi829AaIqYBK3Y6dXIqnCQj2mVQoIeqHhMTneGnNKCwAWbHU+JxlAjdNLBumDQj27VM0IdtKCk6IsbFrVwDiERB2Ytl4FoDEBEkIkSdtk9avAKSEAaFFTiqWcq5fAOIJEBKjKMzKg/BjKWehu957AUhCQEw+LpMXQFhgXAVTbvUqaqtrAS+TA2hhc/hAbwKGyMbk4w22NhsuuYrVvnW83ERvc7+HAClAOSx+1UYgEr1Lni0EKvukgIiIshFRKdtWyBZXJZJyLZPNlWu+SvQ2ZTpABgcH4WBfv4gD47xShkB1b1JAaNHLFQa6b/KsGNKc+AptIFDZ6gBpbe+Bn9/wJDS3tAPC4vtPJbDYy7SAkBhE5xRWPGnFaTylEqqEb1qmA2TLzk6oGDcLjjmzGmbfsXoIlKQexQSy2OHA8RsDgoJUQVAqgk37PkwhUNmZAoKQ4DVqah388qan4IMtu4N4FB08MYFjBUja4imH/lTCNy2zBYRAOeasarhlwVp4v6U98RlFB4Xv8rQgKwCpGP5jBGmCaQqBys4VEAJl9LQ6+HXVUti0rSMTj5IEntCgFIAUgAw9diEsx06qhrl3vwAbNu+OzqP0DwwE+YBm7gEZO3YsiC7ZLt/W1gbsJbPLS77KM5iWJfUg5EkoHD1tPvz25mWwecee6DxK1979sGxNszdYcguICApRHguDKp4XINhxmAKgs/MNCIEyclIN1CxcN3SY7+sbSPIklHpd9Cj/WPl2YlByAwgrbhEIujy2viheWVkJIS9W+Hx8xIgRwF5UrhO+aXkoQAiUk6fPh6tvXQ5bd3ZG6VGSnFMyB0QkZh0MonJRO3xeSECobRYEXdwUAJ1daEAIlJGTa6B20UuwcWsHxOhRXECxAoREkDSkHRTb4UUsEr9pHt8Wn046bpP6OijYcp3wTcvTAoRAOXl6Pfxx7grY0dqV+qNTkg7xUwS2kBgBYiIMFxtewJg2hUFkJ2qPzXMZo0sdFgJd3BQClV3agBAox02ugfn3vxydR7GBpAAkwNlEBwVbrhK+aVlWgBAoY86rh2urV8Kuj/Ym2eBTrWsKySFA2EXDOPsY5LKLmtRhd3eKizyDaR61IQtNxuTDhp9LVdoUApVd1oAQKMefPQ8aFr8CLdv2QF9//l/1MoGkQrV4WOZDMLI2REI2hUFkJ2qP8mRjCJGvm1O+XCV+k7K8AEKgnDqjAa6btwo+bO9J1SvYdlYAwrxpGAIEUZu8+E3SJhCobPIGCIFywjnzoPHBV6Flez49SpSA4I4v8g66PPIUolAk5FB5JkDwNirxm5TlFRAE5ajxs+C0GQ3w57pnoK3jY9tNPri9DhLtIxYuZigxYbsiQetgEJWL2sG8kGNn2+ZFb5M2gUBlk2dAyJtgeNLUOrjroddh845O6M/JGSVKQFDYIghkeTI4bESapa1K/CZlsQBCHmXsd26H6+ufhY7O3uAeQteBF0BIPOyO6TMuErgMBlG+qD6NOYbQBAKVTUyAsB7lxCm1sGDJ67BtV1emHkUFidEjlg+RqYASCRzzRDDI8tg2fIw37TZUAOjKYgUEYflMZRWMv7AJbmp8HvZ079Nt+EHKcwGITnCswCkug0GUT3Uw1PWVx3IdBKrymAFhPcqoKbVDZxT8UGSaZ5QoAEHRsiLHuAgEWR7VzaP4TcakAkBXViqAkEcZN7MRqppWQ3fP/iAeg280WkBsICkA4Zf9cJp+1YTdrWOI46te9zz8RvAzSjSAJPEiBSCHgeBjsQKCEB9dWQWVFzbBzXeugZ7eA/yteUmXDSAmjzJ5ttE9SsnKS+kRS+XZTppWBwv//iZsb+0C/Magr7+oAOG9iOzMweejB8mz+E3GJgNAl18ugJBHmXBRE8y5ay307j/oixHp90T+B7kri9Q1LZ2DAAAAAElFTkSuQmCC" + } + ] +} \ No newline at end of file diff --git a/strings/exoadventures/quantum_fizzics.json b/strings/exoadventures/quantum_fizzics.json new file mode 100644 index 0000000000000..0ee9bf9bf6806 --- /dev/null +++ b/strings/exoadventures/quantum_fizzics.json @@ -0,0 +1,195 @@ +{ + "adventure_name": "Quantum Fizz-ics", + "version": 1, + "author": "EOBGames", + "starting_node": "start", + "starting_qualities": { + "jammed": 0 + }, + "required_site_traits": [ + "technology present", + "in space" + ], + "loot_categories": [ + "unique" + ], + "scan_band_mods": { + "Narrow-band radio waves": 10 + }, + "deep_scan_description": "", + "triggers": [], + "nodes": [ + { + "name": "start", + "description": "As you sweep through the inky void and the site comes into view, you're puzzled by what you see. On a small asteroid sits a vending machine. Despite the odd runes lining its surface, you're fairly certain that the image on the front is a can of soda. While ordinary common sense would dictate that drinking strange alien soda is a bad idea, you can't help but be curious about what exactly this machine dispenses. There's one problem, however- what currency does this thing take?", + "choices": [ + { + "key": "choice 0", + "name": "Leave.", + "exit_node": "FAIL", + "delay": 10, + "delay_message": "There are better ways to die than drinking alien soda." + }, + { + "key": "choice 1", + "name": "Try a holocredit chit.", + "exit_node": "it's_stuck", + "requirements": [ + { + "quality": "jammed", + "operator": "!=", + "value": 1 + } + ], + "delay": 10, + "delay_message": "Hopefully whoever made this machine is part of the Galactic Currency Union..." + }, + { + "key": "choice 4", + "name": "Ram the machine.", + "exit_node": "smashing", + "delay": 30, + "delay_message": "Ramming speed!" + }, + { + "key": "choice 5", + "name": "Search around for some loose change.", + "exit_node": "lost_wallet", + "requirements": [ + { + "quality": "have_coin", + "operator": "!=", + "value": 1 + } + ], + "delay": 100, + "delay_message": "There's a surprising amount of stuff on this asteroid to search..." + }, + { + "key": "choice 6", + "name": "Use the coin you found.", + "exit_node": "choices_choices", + "requirements": [ + { + "quality": "jammed", + "operator": "==", + "value": 0 + }, + { + "quality": "have_coin", + "operator": "==", + "value": 1 + } + ], + "delay": 10, + "delay_message": "Thank God for clumsy aliens!" + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAAAXNSR0IArs4c6QAAGZlJREFUeJzlXT2vVNcVPYyfLJBw5MKWHMfJj3BJB64okBKnSOfCmIJHa2FFshzZsuTkKS2PApMiXSwFI1G8yo+O0j8iIY4lu7ACEggJkQKdeXvWrLX2PnfmMfC8JTT3no/9cc7+WOfOm8uxV15+/UkLdPvO3vz69KmzbSqdPH6i3X/4YHL/unifPH6itdYWxqv52M7mVvTq8yI5efjpxkwhNzfqysbcvrO34Aej+oyuIc5164J6s/1z+1mxYYYNfTFOnzpLN7pKUzdzCu+u58njJ9rtO3sL/WyBRuQwWVFebGNyMVDj/DgGeSo+VWJyVOCinX1cT5YxaTqHRf5KPltDnBPnKZ6oNyanfo9zGCnfOIYVZNNUze6MsPplGYW1YxVwm4T9LONFPhXeVVvd2IqTVfm7ClLhr8axMX1cRT+3t1MqhaKFAFmlnFfK+JTNGoUdfUOd40yBBi4oGDGoxYJkRJ+pQbNqwKkxI3JUolAQU11X9Z3qV13HTjM2IcuarE2V8c5XKaswO/JlbRl0UHCFUS/FqkQzmY5/tNsFWdVx3fpmY1lAI9yJdir4E+1y/Ngn8wG3Bgw6RX3YvZrPSMnE9pmbEIVnmRTHxTFuwR2NjkfM7GTj4kZnjnMy6KUyqMK/Kiu6zY6UZexszZh9mV4VfZSzs4QS1ybzDVX1XZA6PqzdzaNnkClwaUpJw74RCKR4MMzcqQIdsrGj2BmdIhvvdJuKo6u8GAxScxwc6jzifafKHCUDaVWfiTIUbJSH9CoGjONXUbTKUzmxcs4KPlUYOBJuhttUpl9FLzXm3qMflvpfefl1uT5uw6cSJjGUq8b0cSxwKmvKkgqTzcajPKWz4nfy+Im2pYQgBlQUyzSDMSpjZE7G8GDm8Az+qI3p15FH/GT2OLvYRrqMjLIwCO49Ori+fGZnfr2zf7ld3N5uV3d3F8a88vLrS7aPktM3g5QIL7FN8Y3tTpfYhmNQL9Wm1sTty1ZWSnGiMo45tFIiW7Rqto46qeylZGSZxjk+yla6xfH//d+/5u3dsVUQVOji9vbC/dXd3YV7DJios9MXr1WSwP1WCSNLEmr9YpIZQTKqejKEgDawtq1qlKlFYEL6WObo1cyWVTAVlG6BFC9nS2XjOsUqgEEwGgCjlAXML3/xmzTDs0qs9jySCqLOg/mA02GEKpW8Ul1U2xxiZRAAGTnBLHsoPsrISqZg9y5rKF1d1Yh2tPa0EsTMH+mwg2CEVMDce3RQXVrjCc3Bmt5WSZCdsmqkqrnjqSo5yhtBQyxBzAOkGlHZISn2ZZCoj3FnHnZmYOUyznWZ0QUwK+n3Hv2wEhR6XqgHDFaWSCxRqEqM81wywn1jSYyNR3kukBgkVzzZOJVw6SHdURUiZVUgK7FZdWJy3PlDbTqDS5gZX9SgGCF3hlOkkAX2Y6WOMt1cdwZh/FRQs351j3Yt/bEiM4Apw4xkGagCYdAIZhT2M2NcdnOLoXD3USUGfXEd2ZrjXrL+yA/3PcpRVZ/pEnlUAtglUaZXnINzaYA4HOkUUXBJRTaDW2iEkoVtGMTVvsr9USPM6MrpMclF51XnBHaN8tRcVWWcwzM+WYKrIJvObyFAqgKmCGdnAOxzmDDqp9pcgCodcOOdDUeF1Dq76olVBNEB8kJ+rMooOSNVQlWbqKvime35/YcPFgMEHccJ7u0sG8V+Vs6Ysbg51WDpfbf2byyNdcHDgoWV/6NIDMq4M4I6QygoxXhmVSCDOtU2FqQ4voJGOs0qMKmihMOzOIedA1hZVjJw4+IfKVadXeHSytyjRipgpjobg9I4z0E0p2fUF9sYRER5LKgRBkbdZtWSFpWJBrm5WZ/qVxVDVYT4K0g2tgId2WYeVajFKj6DTCqJOFiWJTqmQ+RTOec4Hdk8phu7Z1Vsxhiqe+Y8ldLIlFIRrkod6oNzzp15d4kvy4zKpgxOHCVy1bm1/BE9268pQaIqUmWOcnYW6ExG5IfBH/1n6XsQBq9YZCocx5ye3as2xof1OTlKV7apDgJOoZ39y6Vxm/xuBc9enfBcxuB2H4f8MnlZOyaoqGdFd6RVxkS5SwHCFkNBG0bRgdERs9KIi6TKegaZUEcMJtwEx6tCMSjwTzzknN2DOZsMFgedlKO6c8eIvNjW+TKZzo9iP9rhfJn5GCP7t1i37+y1c2felYJYsDinVtXCGaMMcIvg9MjmjwZM//NzR2+/c25+/e03t1pri4G0s3uZBglWo3UFkksg/bNSJViiYX1MbvxUFT6zIaNMZ2dj10/+5LY/Gbq1f2MJ3+H47LBTUbzPw2xfwcMZP3e+YJi0Sio4fvrx+/m/t985Nw+Kb7+5tRAsnS5ub8+DYWf/8vzf5598Mf+3bsK1qFSJW/s30jFMDpIKqmxv3DUb2/cfk2a0Xe39EsRCxz996uzSz1fdGcJVEgdn2Hx17nGGKL2y8ZlN6i93Ff304/cL9/v/uNbO/OFCa63R4GA0GhC7V67Ivu1Ll5baGN5n7bG/f8+Eb41hcyIvVilQdhUtRL5qDAs61ef49nlbjEkMEpxUUSojhS9dtcgqidJjBO6NyOp0dXd3oYq8+tobNEhefe0NywPp48/+2D7/5Iv20Ydft7/89XdWBxYErfnAiaQwfaRzZ95tt/ZvLDwtZMHl9qDPY5SdJ1mbqhguEONYts/ol1uKSWTEJjNhTLjKNGphlHNm8+KYkaqk9KrS+WtP2tULx+b3F7e3bTB0ikFx/tqTdj3waO2gimTBMZWc47H++w8fzM+j1XVyzhqJjWGOq/afOTxLwGqu0r21tvh7kCwLZM6sIBUKVcoruMZ4xHGRjwto9cnGj1SR89cO3ntxFRy9MofRSAUZJeVsDo6wPVVJMspgyTPyxHu1NyqBM7uYDS7wVXJvjZxB2KBqVmftzvljqYwyK1VFVQRHrpxWgrNCmeNX6TArCDpLBWJlCSab08ewYMoqBtv7zCamS5Sl9jnOvX1n7+lLG6pMKjgPeSlF2b0LgopBWdll+iL/KcHx5QetffBlPkYRm3uYFaTTKGR1aEHxZrDNBQnydcHB+lTwsmrC/DnOOXfm3acvbXAZmmV8FiS4qMoI5O8WA7MV48sWIatCKtuwhDD6FMtRFkSRDrOCsL1jFd9VCAahVDtLvCrBuj1hOruElsEyhlqwP/1bLBWFTki/diUcswlbABzvMjz2swWP8lhiyAJ2HdQriasorT2tIK219tGHX0+WpZ5uVSFIv2fVFbMwo7jHquI4uIc+hD7G9MD9ZKgiysuCZ4ZCmAHKcR0xZ4zCcQFcFKMeruziPJSHc7IstG764MsaJDvsp1iRWLZXiQMTkXJYnKvaVAB0UlCJQSack0G4SkJcesyLirNJKppR4Qq8wnEM2uG4KQHLNtTJ6nLWCbFaOwiOLEhGziCjXxS2xp/YMedx64NjsV0Rg1osSBkvVelwHAYAg35KXhyzxTpcdGJ7pnBW0rBdGeAwa5XUZipYdhiVpQfFuisICwQVOLh+DNqMQihWFVji7aTkxT5X+ZW8OE7BbKUX61v6Jl1FHWtjsKcaqSwrRFJO26+rGQ3nqGB0ulRInSliIFTGdHoWT7FU4lHYnTkiS1ZZhVd8oiwGp5T/ZMFcTQbMHyzEYkarYGILwO5VuWRtuFGZ8WyMcwImz8lSVHk6NfIEq7XD/x6kUtFxX1mydHNVco1zWuPVIF4rOKZgchagURbah/0zpmQfrCITo1Ap5ZwPF1EFQyUbucBhvFwmYbZllD2ROqy5U8lBSOYkKnOr8cpZ0ZmVz7nEG9vZp/MnhibQTkQXM5U1FSRhRqFAprDKRtFo5pxsc1gWijzcYjL7so3YBP3nx3+31g4e9x4WORuVE+EYlvBckPRPBqEcZKugBbWnFftZMM+wQ5VN58AsG6MRqiyyOYqQF+rLDHc8FYybUkVaq3/HUaFfvfbr1ZkIYsiAwc5+jXuvqkgWQKwNnbqy7sp/3Hy0NYN3fRx9syJzbrxmVYEp3QUxvsyIirGMN+qJfFk7bkymX0bV7zgq1CtIhdSj3OwRr9rDTmrf3P6x7O/a+7VCJ0wvFoTM4RlKQT9WCKcT/SadGezIZRzmnMoY5FlxVDWGLZqCTi7zjVD8jmNVOuwKoio6y8pxHiYxdZ1BWuYTLNkpmMagkYLyDuLFOUznGWPESqZiEpkx41TmZsa5gFHtqvxXA3MVSIVU/Y5jhCq/LlTfd7jvQdhnJIbHETmwzMzm9WvlqJ2Xg1wsgWG7QgpsXoVOHj/hf1E4FXagoo4/zmPtcV62IE6u0rXPU3pWKPuOY6Sq9IP5YfwWvVMFg+N4VomzoKnsAYPmbi5mfVYFGM+KfJS38D0IGssMxYhnRrBKoBTIFpfxixuVGa2CLfKt8GN0+cxO27lwrLWW/wbEVZXrF47N31gS31zy8We1d2x12r1ypW1fujT/VKSyciTXp6oL9rHxnTerJmyOg084thLgDgqy5EhfHIeGKCZIDNKgc7NMxDIS6qCMuLV/Y+GVozgX7aqMiXKzv8U6CJKnVP3BVPyJrXqdz+hrfirBgaQyd7/vn3Hv1D5nVVf1qYBh8KxaGTAgcHwWyAsVxGVlDAjWpxRzcMUtKG6EWtj40urTp87SSpNBM7aQUypJp53iT24P42Vx1QqiKj7LriwYIh8FkyNfBoFwDCMXFL0/QzBO1/4SChdIW8xZMhxeiXhWBVi/W9AsK8VXE6mFVxUPFz/DyVXa5FsSe1CMVJBIGQTBMQxqxXaXVLMkFccy2ZFGEE5v768xim9qYbT0i0JWTVhZReWybF+pKmq8C+L4Ghrkq+ZnVMHozxtVX/HTidno1onBUlVNcO+YbMbPVaRKVXd7jG34GiM1fos5UTRClV91fqgs8lTHq0I1F9S4eQpidT7r/j3IYdDUitGaz+5sbdje45zep/ioPgfnFBxU4xmhLa5ydFo4gzjHZ5Apjot9cZ5TnDl25FGBWiobMd3jvQs2xuso0Vdf/XnTKmyM3n/v0/l1pSrNIdYo9nal2WUWJAfRVLZSeuA8JXfEQc7+9nx57KZoyi8KXxS6+/jN9tZL362N39/+/qfSuPff+7Tdf/jg4LU/FQzPSmX8xGscr+ZFqBPnVSEbo59Tllz11aPrpnU59bqDY4R6IG05TK8gR3YuYbCHOXoFy7p2lF/NDkeVekBsumpUnLri/M8qOJwuC18UOlyeVRpG2WGePQVRh26kFyUYKv/j1DoeDcfvPka/LJxC6FSj2X5TlYHRWy99t6B/vF56aUP/dPifPdVAmMTGqqoSg+9Fg0ZZAGT/uQ57s/uzJOfYrg/bnxWkOizYFXnGa3oGieSgUbx254He96I5f5Wq/+3aYVL/Br1fV8lVgcPK8srJK/LWBd9w7N3Hb1L+9AzSmv7muV+zcVMqwNSMsMkD3PNKq8AqhBitrTdAIs8RvlVdov6jweHmbDFY1Ilh/3XDoOfByRX+fNHJBYyrFtW1GDmH9CzNxrj1r+6FO0eMVCwcu/TXvJ02dQiuOug6nfgoBkdGlXOHc+TK+Exmn4sBofZh5Lyk9jTbY5S/1drzdTYYyVjPUvZRpQhhIh7PsnynzJErVaV64EcdY1uU5fRnlQZlLlSQVSvFup125MlJxsMt2DopPol6Hg7sGamsH9tcFsd5yDfOd4ESA83pGAn1dgGW2ansiCQhVpXW9ViOldspxBZlpMSOEn6HsbO72mPfZ0EjOL33sfGRV79W+zjy1EolNtZerQRTaeUAyWjkkLUqjXw7W33mPkrZl35ZAG2KKtAq3o/CppHEpM40WaJzshmPil6TAmSVLOwWb9WnSSPjN3Xe2OQPqpAQy7eWP0Wa+gBlBGlk/pA9KHB+lNmH/fTFcauSy7wVXKnGVWVUxkfYoPj9XA7tav3Vmk29z3TAYK36gwviLMBRR+yfFCAZVh09jLH7bHGnVBdWalVW+rkQYvjK2Ow+8wO15+vag6oPKZn9+5rWTIBMwd8ZLo181aKMOOxI4KEeI7Szf3n+70Wm+LQtOkEkln2nVAbHO0uwmcwsANQBvkrxjHVs7+Z1+Z6akXPAyBcyI4e4dWUXp9+I7u4HVM/ifLGzf3noSVgMir2b1+n3CBVy+zN13zI/GD2HZrZNOddOPqSjAuhgTDH3BMRVn0yXbBOYflMPot3J4rjOb9PB09ryXwfv3by+cK+e9IyQOzBHOXG8Gxt5jgThKjpVyQZIVZB75BbHj2aTSn9WpdScSqYarYrRGZH/hd+flba1tloAYZVwNPWJ4SoBpfwlO4Sv4i+Ool9mfCzEUkq5jDDV0Uf6u2y1yZkjR92zx85xrCPmeFV5rdWhW4dYMSiu/XNvSEfVF/WszKlQti+jcHsEEra2OjRfCJBVFyOjqbi3ksnZE6oRGUq/0SAcOetkuvRrFjyqWrm2ityjQlVY1pr3l3IFmRrVFWdehXfko+axIMA5iveo44xkScV/pBpWIeAUmhrQ6+DtxmZVep1EH/NGJ8qEO+fv/civ4894Nrn7+OCxI8Io5Bl5q3MG0xEPcoqvsk3ZGHWsBCJicLQ9w+aqr2ITk6dsQydk+1jRcxVS51u8H4Ve8d6tGQ2QzPDMaVkbOn1UNjoMW3iWLdS9I+QfAzAL7CiL9WFbNev3sVGnKGvEPiWX6arkqSCrOCGbN+InbB5LOBmpsaoSOV1m1YwTKTpzppByvvjJ+hyPSnZWhFnbVSF1NlGZtOqcjtz4KU5SCbQMxvXPLAFU20ZoCnyqjq2Mm1U2TG2ScwxUZCSjsj6V8dh4V2XUgrvsl2V8NbfqeMg7flbPTMg78okJTVUHdY980D5mA/Zje6Y7ys3Gsv1lfVk1Yu0zNyA7HE2lDLNn5V71MYiGMhDu4ZhMBrOjsiZZNc0qpJqP/JUtWWKoOBvj4xJV1SGnJAGUhfNdAhuRMYsKZjh6XWcBJmvK2KykV6BQluUqcE31qcrjgljpgTxdX+zPgkZVaDUHE4yyM7NNyRwlBcnjfTXJMFo6pFcdV+FzbHNnBLUZKiNUKgvKiVm+msEz+DUKLViWczwy+Bb7VMavOjw6TbVixvFufUYdEuc7GkmQvS9LMqjHyr8HmVrCKoumMgHKYDCBYWdX8rOFZXq47OkgT6WaMSxedWR37lG2ZVW1j1GBxwKNOaTTrWpHHO+SEcpWwepg4gwVwZKUMWLz43U2hy1sRa4zVmXeDBuraoibXnVSBy3YhrlszwLGjavopCoVC3oVnOiMjOcoIpkSRA5Kon5YCV0CsxCriiU7U3SgrJKohY59LuCUs6hFrsBClWVV1mdzXYBG/SrrE3liUlH3KA/5oS7RppGAQ56ML+OdwS7GDwmDlq25sl/5GPML+te8Cqu6SoMKVhdAOa2LaqeL01/pglmPwQeVbeIY1D8LRKV/tY05B7tWlUJds31168sqUFbJUH8lO5PrbGFy1Nowuvu4eAZxWRHHRMVGsiTOi4apypRVLdw0LLNsHOrm2lylYJkqEkISVbVYhq5UW6U3rpVaV2Ufy7bM0ZxtTDfFl8nFdVG2oWzkwdYGbZjhIGdEVQG3COgYeM94sgyoKkpcPAehsoCP8lRVU3ZmPJ3jVIKVOUZ0dlXZcQ8qe6mCRlUt1Bl1xYTokparoM5etZbM76KNKKu1cAZxm88UjZ8sGzpF3cY4Ypkyk+PKK+Ph+Dk5LPDjJ3OUuMHMkRkfpWclC8c25oDIW1VCFiS9XVVzVWlc9VO82X6ptWcJSSUEllwtxGIOpiI3U0AZ6BYIKwsar5yfLSDTnemfZUJsV05WtQfHZoHFxiBvtmYuwBhPN8dVdaaXksnQQGV9XDCpT8WPJaKow7G9m9efuI1mTBmpoKhkIFV5lA5qkZxeFR4ugJ2NqDvaVpGprlmWHlk7lrQq+5HZU9knXCOnJ853+7bKHIciGI9ZRWEUwrINZtnMyZgh/ZpBBqZnljVRhspwWWZj1UbZjc6BfSiPtTE+OJ7Zgbq7uUoWcxQ3h9mrsrFaJ7ZGSKr6ZrawZID9LuiXflHIsooSrjZR9SmelcyKOjKHjXORnHOobO14Zbaq+a76KLuyrO3WGuerTDtaRZgOLCtjP167uaraMZ4ZZRVI0dL3IKhoZjjOyTZTBRySC4JKiWZy3QZgNRwJDBf4yqbIT1UErGRMFzVHEQuA6lpkiUDBuVGU4qoSto0ESZUiz9IvCl2l6AxxXm9n8InJYW1sfOY4imdFRwePonx0pLhZDBqibgj3GNxQweiqF3N4dHa1BqibgkBu/TJ/YDxUMGRJiiUVt49xXZQ/sz2xv0nHQIkLzhYmKhPnVDcbZatFYO2uX+nJdIrjmK1OdxesbmNQp8xp1DgmmwWjq3xx3ZltWTWvJLHIJ3NcTDauWlT2Ae1z41oLZxAWPZgh2bjMSbHNfaJM1KeqkzOcUSZbzVdrllXJOJ+tAequ1qyiU2Vexs/NqUAct57ZvWtfh24Zrxk6R7yOUcsye2Qex+N15Mmyi3LQOBerF45TECJmJwYdVKBljhvLNq6Lyroq42eVR222yroOflT0QV5VGUov5ltOHtOtGhyom7ORVVOk0surs4oQFWNzXEatLAS2I28VHEqua2fVwFWWSnZ0WZ2NZfKc3oyqVSOrMlVHHcnWShd2z/aVzcnsq8qO7a0VzyBOgMqaOKaaTVGHyBMrFJOLWSHLMljN0AZVWVm7gwrO+Vi7q2wODjF5qtIw3VU1i1XV8VF6MR2U8yPfSjJQ+mRVxAXHWy991/4P4sXXP8RtQZoAAAAASUVORK5CYII=" + }, + { + "name": "it's_stuck", + "description": "Well, only one way to find out, right? You produce a holocredit chit (helpfully taken from the science budget, I'm sure they won't miss it) and jam it into the slot. Then, you realise your mistake, as it sticks in the slot. Whoops. Time to try the old fashioned way, I suppose.", + "choices": [ + { + "key": "choice 2", + "name": "Time to try something a bit more daring?", + "exit_node": "start", + "on_selection_effects": [ + { + "effect_type": "Set", + "quality": "jammed", + "value": 1 + } + ], + "delay": 10, + "delay_message": "In hindsight, why would this accept human currency, anyway?" + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAAAXNSR0IArs4c6QAAAjVJREFUeJzt3cFyogAUAEGzlf//5ewpF0pGRJDnbvc1CkhleAbEfN1ut58bcNefqzcAJhMIBIFAEAgEgUAQCASBQBAIBIFAEAgEgUAQCASBQBAIBIFAEAgEgUAQCASBQBAIBIFAEAgEgUAQCASBQBAIBIFAEAgEgUAQCASBQBAIBIFAEAgEgUD4vnLlW/854tdJz5+2HWuWy9+6HWdt77P/1PLo7d27H/cwQSBcMkFePSI8e+T5WXncUctZWlvP1uef5V2vd+9y1p7/aD1nMkEgvHWCTDgiXOH3dU6ZJNNN+j0xQSBcehZrae3siCPtff/aJJo0OX6ZIBBGTZDle3XuW+6fq/fXo/W/ev3H3yAw1FsnyP96Nues99ZTJu7R11PWJqTrIDDM1+3CA9CUz0BN2Y410z+Ltfdxz/780frOYIJAuHSCwHQmCASBQBAIhFFX0pfefZbq0XKPupPu2eW9+2zSVmctZxITBMLoCbK09069s86vT7uTca+j9s/E6xivMkEgfNQEWZr62a6jPiN19evABIH00RNkqq33Rywn36PHv7penmeCQDBBXvDqkX/rdZKjvp+K55kgED56glz9LRhn39/B9UwQCB81QfZemd36HvxdR/azzmLtNW3/TGKCQHBHIQQTBIJAIAgEgkAgCASCQCAIBIJAIAgEgkAgCASCQCAIBIJAIAgEgkAgCASCQCAIBIJAIAgEgkAgCASCQCAIBIJAIAgEgkAgCASCQCAIBMJfgqJi1hru29AAAAAASUVORK5CYII=" + }, + { + "name": "lost_wallet", + "description": "Searching around, you come across a lost wallet in a small crater. Flipping it open, inside you find a family photo of 3 identical looking grey aliens in comically different outfits, an (expired) credit card for a bank you've never heard of, a loyalty card to McDonkalds, and, in the coin pouch, a single black coin with glowing purple lines. This is (presumably) what you're looking for.", + "choices": [ + { + "key": "choice 3", + "name": "Return to the machine with the coin.", + "exit_node": "start", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "have_coin", + "value": 1 + } + ], + "delay": 10, + "delay_message": "It doesn't count as theft if you found it, right?" + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAAAXNSR0IArs4c6QAAC4dJREFUeJztnV+IVccdx7+7KyH7IKQmrptuSXzImj5E/POyf7LkoUJAMBiwjXQTXxKyS1wjSYubBoPQ4GLdtERMYlnFUkiyIWkEJQUhrQsRE9eXtWChRQ0YqyKGpqGBWgqufbjO3Zm5M+fMzJk553fOmQ8snnv+3bnrfPY3v9+cc27b8aOHbyMSqTjrn3zO6bhFntsRiRSKqwg8IwNrmstRkEhpySoDL4KOKEiEPL6jgg1RkAgp8ogKNkRBIoVQZFQw5eDps1GQSHioRQUVB0+fVa6PgkS8UZaoYEMUJGJNGUQA7GWQGRlYEwWJJFMnGVREQSIAogg6oiA1pAxJM5C/DCqiIBWmLFEBoCEDD2tPFKQilCUqANlkCCWCjihIyYhRwR2X9kRBCFOXqADQkIGHtScKQoAyRQWgWjKktSUKkjMxKrhTRHuiIIGIUSEbVNoTBfFAmaICQKfzMai1hycKYkHZogJAq6QK0GtPGlEQDTEqZINae1ypvSB1iwpAlMGGWglStqgA0Ot81NoTAv4zVlKQGBX8ULZ8wYVKX2pSRhEAejJQa09IKntHYZTBD9TaExLXzzp19cnmMjlBogj+oNimUPiQQUWhgpQxaQZodrw65As8oYSQyUWQskYFgJ4M1NqTB3nJoMK7IGWNCgC9v8J1lAFw+9w+ZFDhLEiMCn6h2KY8KDI6mGAkSF2jAhCHSL6hFB1MEAQpc1QA6HU+au3JG+rRwYQ2AE7fMBVFEKHYprwpW3QwwWiIFWUQ8ZXMZ/1sRVKF6GCCEEGK/itGTYY82sO/R9G//ySqGB1MWFTGShK1qADQ7ty21CU6mBB8opBix/M5tCm7GFGGZLwKUjUZdO2JuUN9aBsZWONUxaImQ97t8Zk7hMpDogzZSY0g1EQAaLaJAnVNpFW0z44q18/3T1mdRxCEYsej2CYqVF2Ir6f6WtYtHT2TeIxODHm7qSikJgqpVbaS8D0ssj2fye/KRYaRG//Tv2fXXdbns0UlhUySJGmC8JhIkjrEoiYCQG/i8uDpsyQiVZbokCSGah/fspiIwe+bFkme37ReeH3oyPGWbYeOHE+VRBAkyiBCuVo1MrBGaJ/vaGFy7JHnriu3/fOTB6zOZSNHVmRx0vA6UVhWGSiIwHf4PCJSFjl0YjDufeIyAHtRbDCJIjzPb1ovRBFTnOdBypQv8PiUmIJYLrjKkSaGzL1PXLaW5LXXXxVe7961R7mNX5+F9tnRxGFWKcu8poRue95/9ZPef7TnqNEwKy85GC6S8Lz2+qtaGVRRZL5/Cu2zozh05LhyOGUbRciXeU3xGdH4Tm+yf1nIWw5Tlo6eSc1D5MhigsuQSmaRa8eqigyu7x/i8xcdkaoIyz2+N7FTWP+vnRNGxxdS5jWFSkR7evtXAID39z/YXFe2DpwlKc8DFkV279qTGi1s8g/tUOvOv2llXjL3g1CRgcG3hwkCiJKw9yz6uiyTcm/IqlUapjmIPMxiouiEsJkw5CMIHz3e239C2G94+qLwupD7QULI4HJOl8/+9PavmpKEiCJ1HmbJuYivShWwIMV7+08AkhQ808MPCZKQvh8kqXP4KLHWrQOWAV4SVQUrqarFM98/henhh5zawF+vReZ+EJfhhMzHa9canePHc3PC+Wwl4aMIBVzKvTZsOtwdvJKlQpWPMDkubNyCpdAPsVzl4GmfHXUTJFS+4HJeUynkY3hJVPCd7v39Dwp5SNK+PqJSXYdZX1x/u7GwEeg99i4A9TDrwsYt4v4ABru3BWlT4ROFrud3EUM+nknC2uDSEevSgUNHEb6zAwsS2BzPJMkSPZ7Zvk54nftEocl7ZO38OtKiRpk7e+hhFuAmiUkFS5bDFV4SF4anL2IeF4UKmNNEoU0nMj2/bynSZFBh87mo5SF5YSNJnnLw8FFALuOawC5XATxOFNqK5lMIFxkYPmfW65KHbDrcrVyfxw1VSQx2b2uZ/3hm+7pMkmS6H6SI6JCnDDaJOnUOdt0VdDa9aDkA9d2ENnLIcyDz/VPmE4VVl8EFedKwaPLIQ7Iid+IhnAMAnFq+MtN5hy6da1k33z+F4f7Gsmvirhxi2fxn+xAiiwhAea+uTcP3MCtUFDGJHjb3itsiy2H75BIe5Ux6nkJUTQZdsk4tb2D4lsTX0Kr32LvWpV0bhqcvOkWRxCpWlKF8eYjJMMuHJK5i8A9M4F/v3rVHKNG6VLdsIsefPzmp3fa7777fXBaGWFmEoJ4vhKSMJV++g5vK4jMRl+8Rt7m/XObU8pVG8x9JUvA8u/gagIYoi1ykqLMMeZNHuTd0Bapx8eFfsGzV6pb7M5gk81j4698+O4ohnMucuGfl2cXX0udBogzuFJWHUKpmqW6l9XErrEzWWXQdgiBRhkgSurzAtGPyM9R8FNEJPHTJLookScKGTbY4PXq0ijKk3cmnu8OQR5WH+PpdZXk0aZYIYpIs6zqlHD2WrVqNobkOnFp7q7luaK4DJ7ceyPT+Jm2xFYQl6qTvSQ9FWlmbaok2BEt2/iJ5hxf9vM+yVauby0NzHcK2pAdKD3ZvM5Lk0s/2NfafztJK4Pd/+mVj4U5bgj96tEiKemILheuofOUhG9660lz+44s/aNluMrzi5fAFE0JGnugzgUULfjKTPVCusO8o9A21hz74hoJ0MmlyNEu3s2aC6J5yqJPBBn5uo2VWXzN/8uWWwfD3pIegCBnKNmFoQurwSoNrtWi+fwqY26pc7/NSFNso8uWWwTtL59A7JhYFyAtS9EPiXKBw4SKQPMz64vrb2IArukOVbHjrCr6Z+JVzex470CoHW88n7pQgI0gVhkj8jDrFIVFRNIdNiujRso8lumusdFGkES1aI4WOQgSpggxFUFfpXC80ZCwModT0jq3EhXcaVwRfeEeUh/RzsYD6yuALn7PqWYZXIUmKIn0dXcpjZBFU9I6tLN9zsYqkSol6I9HWJ+kT+BQA8MJEtiuyqTI5M45DY+pLXvgLKWv3BTp5EmJI5POcLCK4VrNsObn1gDApaPuNsyrY8KmvAzhz64bRMZMz4wDU3zolbyv8uVhVpUw5gjx0+u3OtcIyhShikof0dXQ1Jfnss183/uW2j/9oUlienBkXrglj2/ltlfkCHSpQvDfENg95YWJOkIQyZ27dEPIMJoYJTISkbaX8Ap0isclDqMyHZKWoKNJafRKjA6OPiwwmyJEkaVstL1bME+p5iA4fUURVXu0dW9jGL5tgEh3kDp+VSl+sWBTUHgcE5H8Tla7TP/bRxwCAC3hYWE5DNxQKTWUuVoz4h48iacMs0yjgC9+RQgeZS02qRuhkndKsuo0ch79+WLmcRl5CyLSNDKyxvqMw4n6HIVDcXYbyMSbDLDkP4aNIqKix98QO62NeWfdGgJbECBIUiiXfrIQeSjE5bDr83hM7sPfEDryy7g1cfWSz0TE9f/3QaL9241ZEyMFHjVDFADnv+PSB/wZ5Hx6dHPffd6z58+3ND5o/bP/vfnjJe1tiBIloaUaLHKTQcf99x4TXL314CgCwb/MQ/vaP/wjbFv99Of79iN/3j4I4YjphqBpmFZlUm5R7865I6ZDlAIC2tjbcvi2mzV1LjgLYga4lR9F17ajZyZcA56/eFFbd0/nTlt2iIBFjIR6/fHfgliwwOTOON596tGX9m089in2bh6zPd/7qTazo6RTWrejpxI1vGn8g2HBNliTmICUnax5iIsfjl+/OVQ5AX9Z9+aPPg7yfKnoAMYIEh2IVix9mTc6MFzbHoGOhktV4zc+i81FlRU8nfv4Hc2FUUSSNKEggdE9ZpDK5RxVWkWKlWwDYi+R5kd/8pHUoJrOipxPnr97USvLtzQ+Ux0VBMiAn6mn75tGOqohn0ukZLI9I456mF2Iiz+SISXoBUO2s1IdZppjK0ah0qdHlH0AUxDtUhSgbph0/9Pn+D1DTxdUMOqP+AAAAAElFTkSuQmCC" + }, + { + "name": "choices_choices", + "description": "You slip the coin into the slot- it's a perfect fit. Now comes the hard part: picking a button on the machine to press.", + "choices": [ + { + "key": "choice 7", + "name": "The red looking soda.", + "exit_node": "cha_clunk", + "delay": 10, + "delay_message": "How exciting..." + }, + { + "key": "choice 9", + "name": "The yellow looking soda.", + "exit_node": "cha_clunk", + "delay": 10, + "delay_message": "How exciting..." + }, + { + "key": "choice 10", + "name": "The green looking soda.", + "exit_node": "cha_clunk", + "delay": 10, + "delay_message": "How exciting..." + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAAAXNSR0IArs4c6QAAGZlJREFUeJzlXT2vVNcVPYyfLJBw5MKWHMfJj3BJB64okBKnSOfCmIJHa2FFshzZsuTkKS2PApMiXSwFI1G8yo+O0j8iIY4lu7ACEggJkQKdeXvWrLX2PnfmMfC8JTT3no/9cc7+WOfOm8uxV15+/UkLdPvO3vz69KmzbSqdPH6i3X/4YHL/unifPH6itdYWxqv52M7mVvTq8yI5efjpxkwhNzfqysbcvrO34Aej+oyuIc5164J6s/1z+1mxYYYNfTFOnzpLN7pKUzdzCu+u58njJ9rtO3sL/WyBRuQwWVFebGNyMVDj/DgGeSo+VWJyVOCinX1cT5YxaTqHRf5KPltDnBPnKZ6oNyanfo9zGCnfOIYVZNNUze6MsPplGYW1YxVwm4T9LONFPhXeVVvd2IqTVfm7ClLhr8axMX1cRT+3t1MqhaKFAFmlnFfK+JTNGoUdfUOd40yBBi4oGDGoxYJkRJ+pQbNqwKkxI3JUolAQU11X9Z3qV13HTjM2IcuarE2V8c5XKaswO/JlbRl0UHCFUS/FqkQzmY5/tNsFWdVx3fpmY1lAI9yJdir4E+1y/Ngn8wG3Bgw6RX3YvZrPSMnE9pmbEIVnmRTHxTFuwR2NjkfM7GTj4kZnjnMy6KUyqMK/Kiu6zY6UZexszZh9mV4VfZSzs4QS1ybzDVX1XZA6PqzdzaNnkClwaUpJw74RCKR4MMzcqQIdsrGj2BmdIhvvdJuKo6u8GAxScxwc6jzifafKHCUDaVWfiTIUbJSH9CoGjONXUbTKUzmxcs4KPlUYOBJuhttUpl9FLzXm3qMflvpfefl1uT5uw6cSJjGUq8b0cSxwKmvKkgqTzcajPKWz4nfy+Im2pYQgBlQUyzSDMSpjZE7G8GDm8Az+qI3p15FH/GT2OLvYRrqMjLIwCO49Ori+fGZnfr2zf7ld3N5uV3d3F8a88vLrS7aPktM3g5QIL7FN8Y3tTpfYhmNQL9Wm1sTty1ZWSnGiMo45tFIiW7Rqto46qeylZGSZxjk+yla6xfH//d+/5u3dsVUQVOji9vbC/dXd3YV7DJios9MXr1WSwP1WCSNLEmr9YpIZQTKqejKEgDawtq1qlKlFYEL6WObo1cyWVTAVlG6BFC9nS2XjOsUqgEEwGgCjlAXML3/xmzTDs0qs9jySCqLOg/mA02GEKpW8Ul1U2xxiZRAAGTnBLHsoPsrISqZg9y5rKF1d1Yh2tPa0EsTMH+mwg2CEVMDce3RQXVrjCc3Bmt5WSZCdsmqkqrnjqSo5yhtBQyxBzAOkGlHZISn2ZZCoj3FnHnZmYOUyznWZ0QUwK+n3Hv2wEhR6XqgHDFaWSCxRqEqM81wywn1jSYyNR3kukBgkVzzZOJVw6SHdURUiZVUgK7FZdWJy3PlDbTqDS5gZX9SgGCF3hlOkkAX2Y6WOMt1cdwZh/FRQs351j3Yt/bEiM4Apw4xkGagCYdAIZhT2M2NcdnOLoXD3USUGfXEd2ZrjXrL+yA/3PcpRVZ/pEnlUAtglUaZXnINzaYA4HOkUUXBJRTaDW2iEkoVtGMTVvsr9USPM6MrpMclF51XnBHaN8tRcVWWcwzM+WYKrIJvObyFAqgKmCGdnAOxzmDDqp9pcgCodcOOdDUeF1Dq76olVBNEB8kJ+rMooOSNVQlWbqKvime35/YcPFgMEHccJ7u0sG8V+Vs6Ysbg51WDpfbf2byyNdcHDgoWV/6NIDMq4M4I6QygoxXhmVSCDOtU2FqQ4voJGOs0qMKmihMOzOIedA1hZVjJw4+IfKVadXeHSytyjRipgpjobg9I4z0E0p2fUF9sYRER5LKgRBkbdZtWSFpWJBrm5WZ/qVxVDVYT4K0g2tgId2WYeVajFKj6DTCqJOFiWJTqmQ+RTOec4Hdk8phu7Z1Vsxhiqe+Y8ldLIlFIRrkod6oNzzp15d4kvy4zKpgxOHCVy1bm1/BE9268pQaIqUmWOcnYW6ExG5IfBH/1n6XsQBq9YZCocx5ye3as2xof1OTlKV7apDgJOoZ39y6Vxm/xuBc9enfBcxuB2H4f8MnlZOyaoqGdFd6RVxkS5SwHCFkNBG0bRgdERs9KIi6TKegaZUEcMJtwEx6tCMSjwTzzknN2DOZsMFgedlKO6c8eIvNjW+TKZzo9iP9rhfJn5GCP7t1i37+y1c2felYJYsDinVtXCGaMMcIvg9MjmjwZM//NzR2+/c25+/e03t1pri4G0s3uZBglWo3UFkksg/bNSJViiYX1MbvxUFT6zIaNMZ2dj10/+5LY/Gbq1f2MJ3+H47LBTUbzPw2xfwcMZP3e+YJi0Sio4fvrx+/m/t985Nw+Kb7+5tRAsnS5ub8+DYWf/8vzf5598Mf+3bsK1qFSJW/s30jFMDpIKqmxv3DUb2/cfk2a0Xe39EsRCxz996uzSz1fdGcJVEgdn2Hx17nGGKL2y8ZlN6i93Ff304/cL9/v/uNbO/OFCa63R4GA0GhC7V67Ivu1Ll5baGN5n7bG/f8+Eb41hcyIvVilQdhUtRL5qDAs61ef49nlbjEkMEpxUUSojhS9dtcgqidJjBO6NyOp0dXd3oYq8+tobNEhefe0NywPp48/+2D7/5Iv20Ydft7/89XdWBxYErfnAiaQwfaRzZ95tt/ZvLDwtZMHl9qDPY5SdJ1mbqhguEONYts/ol1uKSWTEJjNhTLjKNGphlHNm8+KYkaqk9KrS+WtP2tULx+b3F7e3bTB0ikFx/tqTdj3waO2gimTBMZWc47H++w8fzM+j1XVyzhqJjWGOq/afOTxLwGqu0r21tvh7kCwLZM6sIBUKVcoruMZ4xHGRjwto9cnGj1SR89cO3ntxFRy9MofRSAUZJeVsDo6wPVVJMspgyTPyxHu1NyqBM7uYDS7wVXJvjZxB2KBqVmftzvljqYwyK1VFVQRHrpxWgrNCmeNX6TArCDpLBWJlCSab08ewYMoqBtv7zCamS5Sl9jnOvX1n7+lLG6pMKjgPeSlF2b0LgopBWdll+iL/KcHx5QetffBlPkYRm3uYFaTTKGR1aEHxZrDNBQnydcHB+lTwsmrC/DnOOXfm3acvbXAZmmV8FiS4qMoI5O8WA7MV48sWIatCKtuwhDD6FMtRFkSRDrOCsL1jFd9VCAahVDtLvCrBuj1hOruElsEyhlqwP/1bLBWFTki/diUcswlbABzvMjz2swWP8lhiyAJ2HdQriasorT2tIK219tGHX0+WpZ5uVSFIv2fVFbMwo7jHquI4uIc+hD7G9MD9ZKgiysuCZ4ZCmAHKcR0xZ4zCcQFcFKMeruziPJSHc7IstG764MsaJDvsp1iRWLZXiQMTkXJYnKvaVAB0UlCJQSack0G4SkJcesyLirNJKppR4Qq8wnEM2uG4KQHLNtTJ6nLWCbFaOwiOLEhGziCjXxS2xp/YMedx64NjsV0Rg1osSBkvVelwHAYAg35KXhyzxTpcdGJ7pnBW0rBdGeAwa5XUZipYdhiVpQfFuisICwQVOLh+DNqMQihWFVji7aTkxT5X+ZW8OE7BbKUX61v6Jl1FHWtjsKcaqSwrRFJO26+rGQ3nqGB0ulRInSliIFTGdHoWT7FU4lHYnTkiS1ZZhVd8oiwGp5T/ZMFcTQbMHyzEYkarYGILwO5VuWRtuFGZ8WyMcwImz8lSVHk6NfIEq7XD/x6kUtFxX1mydHNVco1zWuPVIF4rOKZgchagURbah/0zpmQfrCITo1Ap5ZwPF1EFQyUbucBhvFwmYbZllD2ROqy5U8lBSOYkKnOr8cpZ0ZmVz7nEG9vZp/MnhibQTkQXM5U1FSRhRqFAprDKRtFo5pxsc1gWijzcYjL7so3YBP3nx3+31g4e9x4WORuVE+EYlvBckPRPBqEcZKugBbWnFftZMM+wQ5VN58AsG6MRqiyyOYqQF+rLDHc8FYybUkVaq3/HUaFfvfbr1ZkIYsiAwc5+jXuvqkgWQKwNnbqy7sp/3Hy0NYN3fRx9syJzbrxmVYEp3QUxvsyIirGMN+qJfFk7bkymX0bV7zgq1CtIhdSj3OwRr9rDTmrf3P6x7O/a+7VCJ0wvFoTM4RlKQT9WCKcT/SadGezIZRzmnMoY5FlxVDWGLZqCTi7zjVD8jmNVOuwKoio6y8pxHiYxdZ1BWuYTLNkpmMagkYLyDuLFOUznGWPESqZiEpkx41TmZsa5gFHtqvxXA3MVSIVU/Y5jhCq/LlTfd7jvQdhnJIbHETmwzMzm9WvlqJ2Xg1wsgWG7QgpsXoVOHj/hf1E4FXagoo4/zmPtcV62IE6u0rXPU3pWKPuOY6Sq9IP5YfwWvVMFg+N4VomzoKnsAYPmbi5mfVYFGM+KfJS38D0IGssMxYhnRrBKoBTIFpfxixuVGa2CLfKt8GN0+cxO27lwrLWW/wbEVZXrF47N31gS31zy8We1d2x12r1ypW1fujT/VKSyciTXp6oL9rHxnTerJmyOg084thLgDgqy5EhfHIeGKCZIDNKgc7NMxDIS6qCMuLV/Y+GVozgX7aqMiXKzv8U6CJKnVP3BVPyJrXqdz+hrfirBgaQyd7/vn3Hv1D5nVVf1qYBh8KxaGTAgcHwWyAsVxGVlDAjWpxRzcMUtKG6EWtj40urTp87SSpNBM7aQUypJp53iT24P42Vx1QqiKj7LriwYIh8FkyNfBoFwDCMXFL0/QzBO1/4SChdIW8xZMhxeiXhWBVi/W9AsK8VXE6mFVxUPFz/DyVXa5FsSe1CMVJBIGQTBMQxqxXaXVLMkFccy2ZFGEE5v768xim9qYbT0i0JWTVhZReWybF+pKmq8C+L4Ghrkq+ZnVMHozxtVX/HTidno1onBUlVNcO+YbMbPVaRKVXd7jG34GiM1fos5UTRClV91fqgs8lTHq0I1F9S4eQpidT7r/j3IYdDUitGaz+5sbdje45zep/ioPgfnFBxU4xmhLa5ydFo4gzjHZ5Apjot9cZ5TnDl25FGBWiobMd3jvQs2xuso0Vdf/XnTKmyM3n/v0/l1pSrNIdYo9nal2WUWJAfRVLZSeuA8JXfEQc7+9nx57KZoyi8KXxS6+/jN9tZL362N39/+/qfSuPff+7Tdf/jg4LU/FQzPSmX8xGscr+ZFqBPnVSEbo59Tllz11aPrpnU59bqDY4R6IG05TK8gR3YuYbCHOXoFy7p2lF/NDkeVekBsumpUnLri/M8qOJwuC18UOlyeVRpG2WGePQVRh26kFyUYKv/j1DoeDcfvPka/LJxC6FSj2X5TlYHRWy99t6B/vF56aUP/dPifPdVAmMTGqqoSg+9Fg0ZZAGT/uQ57s/uzJOfYrg/bnxWkOizYFXnGa3oGieSgUbx254He96I5f5Wq/+3aYVL/Br1fV8lVgcPK8srJK/LWBd9w7N3Hb1L+9AzSmv7muV+zcVMqwNSMsMkD3PNKq8AqhBitrTdAIs8RvlVdov6jweHmbDFY1Ilh/3XDoOfByRX+fNHJBYyrFtW1GDmH9CzNxrj1r+6FO0eMVCwcu/TXvJ02dQiuOug6nfgoBkdGlXOHc+TK+Exmn4sBofZh5Lyk9jTbY5S/1drzdTYYyVjPUvZRpQhhIh7PsnynzJErVaV64EcdY1uU5fRnlQZlLlSQVSvFup125MlJxsMt2DopPol6Hg7sGamsH9tcFsd5yDfOd4ESA83pGAn1dgGW2ansiCQhVpXW9ViOldspxBZlpMSOEn6HsbO72mPfZ0EjOL33sfGRV79W+zjy1EolNtZerQRTaeUAyWjkkLUqjXw7W33mPkrZl35ZAG2KKtAq3o/CppHEpM40WaJzshmPil6TAmSVLOwWb9WnSSPjN3Xe2OQPqpAQy7eWP0Wa+gBlBGlk/pA9KHB+lNmH/fTFcauSy7wVXKnGVWVUxkfYoPj9XA7tav3Vmk29z3TAYK36gwviLMBRR+yfFCAZVh09jLH7bHGnVBdWalVW+rkQYvjK2Ow+8wO15+vag6oPKZn9+5rWTIBMwd8ZLo181aKMOOxI4KEeI7Szf3n+70Wm+LQtOkEkln2nVAbHO0uwmcwsANQBvkrxjHVs7+Z1+Z6akXPAyBcyI4e4dWUXp9+I7u4HVM/ifLGzf3noSVgMir2b1+n3CBVy+zN13zI/GD2HZrZNOddOPqSjAuhgTDH3BMRVn0yXbBOYflMPot3J4rjOb9PB09ryXwfv3by+cK+e9IyQOzBHOXG8Gxt5jgThKjpVyQZIVZB75BbHj2aTSn9WpdScSqYarYrRGZH/hd+flba1tloAYZVwNPWJ4SoBpfwlO4Sv4i+Ool9mfCzEUkq5jDDV0Uf6u2y1yZkjR92zx85xrCPmeFV5rdWhW4dYMSiu/XNvSEfVF/WszKlQti+jcHsEEra2OjRfCJBVFyOjqbi3ksnZE6oRGUq/0SAcOetkuvRrFjyqWrm2ityjQlVY1pr3l3IFmRrVFWdehXfko+axIMA5iveo44xkScV/pBpWIeAUmhrQ6+DtxmZVep1EH/NGJ8qEO+fv/civ4894Nrn7+OCxI8Io5Bl5q3MG0xEPcoqvsk3ZGHWsBCJicLQ9w+aqr2ITk6dsQydk+1jRcxVS51u8H4Ve8d6tGQ2QzPDMaVkbOn1UNjoMW3iWLdS9I+QfAzAL7CiL9WFbNev3sVGnKGvEPiWX6arkqSCrOCGbN+InbB5LOBmpsaoSOV1m1YwTKTpzppByvvjJ+hyPSnZWhFnbVSF1NlGZtOqcjtz4KU5SCbQMxvXPLAFU20ZoCnyqjq2Mm1U2TG2ScwxUZCSjsj6V8dh4V2XUgrvsl2V8NbfqeMg7flbPTMg78okJTVUHdY980D5mA/Zje6Y7ys3Gsv1lfVk1Yu0zNyA7HE2lDLNn5V71MYiGMhDu4ZhMBrOjsiZZNc0qpJqP/JUtWWKoOBvj4xJV1SGnJAGUhfNdAhuRMYsKZjh6XWcBJmvK2KykV6BQluUqcE31qcrjgljpgTxdX+zPgkZVaDUHE4yyM7NNyRwlBcnjfTXJMFo6pFcdV+FzbHNnBLUZKiNUKgvKiVm+msEz+DUKLViWczwy+Bb7VMavOjw6TbVixvFufUYdEuc7GkmQvS9LMqjHyr8HmVrCKoumMgHKYDCBYWdX8rOFZXq47OkgT6WaMSxedWR37lG2ZVW1j1GBxwKNOaTTrWpHHO+SEcpWwepg4gwVwZKUMWLz43U2hy1sRa4zVmXeDBuraoibXnVSBy3YhrlszwLGjavopCoVC3oVnOiMjOcoIpkSRA5Kon5YCV0CsxCriiU7U3SgrJKohY59LuCUs6hFrsBClWVV1mdzXYBG/SrrE3liUlH3KA/5oS7RppGAQ56ML+OdwS7GDwmDlq25sl/5GPML+te8Cqu6SoMKVhdAOa2LaqeL01/pglmPwQeVbeIY1D8LRKV/tY05B7tWlUJds31168sqUFbJUH8lO5PrbGFy1Nowuvu4eAZxWRHHRMVGsiTOi4apypRVLdw0LLNsHOrm2lylYJkqEkISVbVYhq5UW6U3rpVaV2Ufy7bM0ZxtTDfFl8nFdVG2oWzkwdYGbZjhIGdEVQG3COgYeM94sgyoKkpcPAehsoCP8lRVU3ZmPJ3jVIKVOUZ0dlXZcQ8qe6mCRlUt1Bl1xYTokparoM5etZbM76KNKKu1cAZxm88UjZ8sGzpF3cY4Ypkyk+PKK+Ph+Dk5LPDjJ3OUuMHMkRkfpWclC8c25oDIW1VCFiS9XVVzVWlc9VO82X6ptWcJSSUEllwtxGIOpiI3U0AZ6BYIKwsar5yfLSDTnemfZUJsV05WtQfHZoHFxiBvtmYuwBhPN8dVdaaXksnQQGV9XDCpT8WPJaKow7G9m9efuI1mTBmpoKhkIFV5lA5qkZxeFR4ugJ2NqDvaVpGprlmWHlk7lrQq+5HZU9knXCOnJ853+7bKHIciGI9ZRWEUwrINZtnMyZgh/ZpBBqZnljVRhspwWWZj1UbZjc6BfSiPtTE+OJ7Zgbq7uUoWcxQ3h9mrsrFaJ7ZGSKr6ZrawZID9LuiXflHIsooSrjZR9SmelcyKOjKHjXORnHOobO14Zbaq+a76KLuyrO3WGuerTDtaRZgOLCtjP167uaraMZ4ZZRVI0dL3IKhoZjjOyTZTBRySC4JKiWZy3QZgNRwJDBf4yqbIT1UErGRMFzVHEQuA6lpkiUDBuVGU4qoSto0ESZUiz9IvCl2l6AxxXm9n8InJYW1sfOY4imdFRwePonx0pLhZDBqibgj3GNxQweiqF3N4dHa1BqibgkBu/TJ/YDxUMGRJiiUVt49xXZQ/sz2xv0nHQIkLzhYmKhPnVDcbZatFYO2uX+nJdIrjmK1OdxesbmNQp8xp1DgmmwWjq3xx3ZltWTWvJLHIJ3NcTDauWlT2Ae1z41oLZxAWPZgh2bjMSbHNfaJM1KeqkzOcUSZbzVdrllXJOJ+tAequ1qyiU2Vexs/NqUAct57ZvWtfh24Zrxk6R7yOUcsye2Qex+N15Mmyi3LQOBerF45TECJmJwYdVKBljhvLNq6Lyroq42eVR222yroOflT0QV5VGUov5ltOHtOtGhyom7ORVVOk0surs4oQFWNzXEatLAS2I28VHEqua2fVwFWWSnZ0WZ2NZfKc3oyqVSOrMlVHHcnWShd2z/aVzcnsq8qO7a0VzyBOgMqaOKaaTVGHyBMrFJOLWSHLMljN0AZVWVm7gwrO+Vi7q2wODjF5qtIw3VU1i1XV8VF6MR2U8yPfSjJQ+mRVxAXHWy991/4P4sXXP8RtQZoAAAAASUVORK5CYII=" + }, + { + "name": "cha_clunk", + "description": "With a satisfying cha-clunk, your fizzy prize drops into the tray. You swipe it, and move on from the site.", + "choices": [ + { + "key": "choice 11", + "name": "Sweet, sugary victory.", + "exit_node": "WIN", + "delay": 10, + "delay_message": "Hopefully this doesn't like, freeze solid in space. That would be bad, right?" + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAIAAABM5OhcAAAAAXNSR0IArs4c6QAAIABJREFUeJzsvXmcpVdVLrzWHt7pzKem7qqeO515TkgIIQwSQESQaIiADAooeK/XAZHrAF4V1E8ULpd7Rb0MooiAIE4XkEHBQBhCIPOcTnd6rO6uqjO+w57W+v441W2DEBLoJN2Nz6/+qFP11ql99vu8a6/9rGEj/Ce+ExCRmY9+Ofnmm37IwJPvgBAREXlyDSMAr/7y+wf4WA/gBMARJn0biNULUH7DxRwmLz25Cc0mRDyajicx/pNYAN+KOpOfHP1zZhZCfOMFAhEFKhBCCCFQrVoqwQCATEQUDoPJTt7k+4RY6rEewKOKI4vaNzEJj0AogAlPFCJOmISIkydwcgkctjpCrNoqJRMhBCoppZy8CTMHb0Nwzhly5LxxBhkCkYejjNZkPN+01J4c+P4iFhxNKYEAIFBNzI1SsRACpRZCSXmEIgIRpZQAIIQgWvWeiEgIgSjFKqRSKkliIYSUMjB47zkEa60xlS1yY8pKCOcsewDy3zSkk49V8CDEOvkeo1Vjg4AghRBSaimV1lrJSOtYa62TOIpTpZTWWig9sVgTrihUzBwgADIAIMHEmE0oqGQURVGaxRilApxgCB5K68bVuByNBoNBmY/luF9Vha2E99Z7P/HATmJ8W2KdlKxCRCGUlFoppeMsipI0TSOdJEmapqmOoyTLoiiK4zhOM0RUSq0udjxZKJmZiYgJpZQghVJKKRVFkda6VmvUxb4nXLD+9ltvsQ57y5QbvWc0v7jcGPZ7WuvxeFhKbasc0QZfndz+1vfFUnhk14ZCaR1FUZTEWVKrJ2ktrdfq9XotqzebzTStNZvNKIqSJEkihSpRiqRQSkwkBBYCkJkDTd4TpRAYRUmmIhlHUU3d/vSL03xwcM+di9b5pEnO6aS7NcrccnYwiqIoivIoGgwkQmERvbfMHgngpHuM4fuEWBMIobSO4rQWx3Gt1uh0p+v1Rqsz1W63G82s053qNJqNZhRFWmkx153etm1DkvksHtekFTRgM3Jl35fWGetdZa1lhCAosHdMzalOtz7UlENrnoUBia4E59zsfF03W/V6KoRQKlJKO09CCCgIAJxjwHDysQpOdGId7Qh+W92SJ8uYjFSkdRLHutnottudmbnZznR3/fqFdivuNuUTLz9129YpIQSKNsM0smE6JNwOP97h+vtMPoLSm6L0VTDG2eAJQgATFFhZnX/JuVIbROQAaRQJZTzJQOQ9Fvu+3l24vLF5PQodJbFSygWWUnrviYg5OMfM3+zOnwR4jIn1PW4RvmmvflgLmOznRCACRCFQqUhKFUdp1mi22u3pqbXTM52F+TWnnapf+dOnRbLi0Cd/OzpG3WSuoWwTBKReGB0MxQqYkbSFM5bLioxnEzh4x5Y0nf+MpxjTV4oBAoBgcI5GxpellaMKTEWD3u3b77hJJS0bNTeuuVDrDT6wEOCtE0zMFEJgEER0bCb0uMEJbLGOmKgjuuVEC1AymmgEgQkRhZBKRnEcZ41mvdGYmVnTnZ7duL79W6+/KI3uhfGtoBiYGBGB0QZUJVCOviRXoFnhaszWUFWydVRVwVAwwZNjdCQIghfgGOBwRCcAm8pWeUXjERkjRyX0qhJKK/nQYGm7SE6Znt5iqqKsjX2wzhvnTDjZSAXwmBPruzBXR2mbYlURkFIIJcRk7y9BrEpTh/eAUmudpmmj2W42WzNzC/Prp3/0CpWOviJSj0JBAARQ6JmQoBBeB5bCOWFLKsZcGMorKkwoyI6MseQcWQ5eBhYByAEhhzARIQRh8CYf5aMx93OsSi4LcBUzemABjny5e+P6c4fjTpmPjCmrSgmhAxOiO8k8re+GWI+txDVZ/ibDkCqSUsZxIoWOokRGWkqpdDwhEyJKqaSUUaTTNG22O61WZ2Fh7ZZ16zbN34FixAERVv0bZGACJiQWgpltgMpyZaE0UBFVlipiR2TIWTIcggoeCYgUCUIvhPjiF79y+SUXA4fhsBiMuD8WpoSyACIiZkIhCYMYtjLdbnby/vJ4PIqixBnjrT25SAXw3RHreHi2EOVE25RaxWktSbI4ydJ0IkclSq0aKimlUlEcx2madqZbU53umrXdbqOldSnIMwGjR2RmJgQgEkFAACbiypFhrhwbR4bIsDfkLFnrrSdH3nny3oElCrYs7T/986catSbbQIEqE6qSrJHGkA9iNYjETMCCsdGq1UtZW2pl2chUhSlzoSS5Iw7iYz+3xwQnno+FiIBSSC1VnCS1KM1azU6j0ag3m7VGo9Fo1Ov1icgZxXoiYCaJqjfSdr3RqGkEl+E9zdQJIOLABAzEzEAkArP34AL7EAxBxaEgKpkMWxOsAWOhMlg5KAIaZKsQrLr9rgOf/cIXMUL0FkhKkqby3mPwGAITwdFhSWbOsqzVinutZq1oGVPZsgohUDATvfSkCXicYLtCXP0rOTFIcZY2mq1GvbGwfrrbiufW1jrNuNXGWoadtpqejjrdWj0VCEFpk0ZWaQCuit5O4L5HQgZkBmIMhEDBkXQYDGFgrAIZZuPJUqjYGe8ceAcuUOW5sq4ELsh+/eu7Pv1vX4trTQVaCAUOTOWrygafERExIDKAOPoTCK3WzEz1lpdG40FRjHU8Vt5o75wzTAQcTg5uPTxiHXNz/d29FSJqHUU6yWqNequ9bn7zK19Qv/IyFCoGOdkbOsKBBIm4FBAYCZCAFRMjcSa9IEAGROBASMxE4EF6JOcxMBkHBtkGb9lZ9haCl96xqZz1VFmqnCq8L1h/7rO3o2xY4ypranFA9oMxOpLOB09AtJrmd/TsxXGaxVOddteUlTO2qqrAnrxhZu89E50ErIKHS6zH/DMzgJRaSimUknGSprVOo9vq4hMuXNB4PyODAEYKIBBlAAMgABAYgAHZAbNECsIRBUHEngEQAwhCCp48sfVsga1gS8EgGSITggFvgrFceSgtVJYL50tHhWcrAiOR8h64FkcUYP/iMgXFzByYgiRkAJaHTS2TFCiVimYX1ppgq6oYl1XwhXdN5iEAOScBA4QTnlsnno8VQphwK4qiWq3WaDTmu5HiQ8gA4IGYBQtGgCAEAAimw3IXAwAwB/ZeeMHscaJeBeJAwXv0wBbIMBkKhoMhMuwsVI4rS5Vl60JlQ+WpslQEyn0I7INg8EDI6+cXmOj2u3aEQETIjIeDzMwAiAKAhQwffe8ftOZPecLlz9ww08Ry2tnCm4IcQvAATEQTXfcxf4a/R5x4xBJCaK0nLlYc63rWmOuCgmUHQrJHQgwOZERIZAUFKQRIISiABS8YhEJyDCGwI4lMgYBYEKMnsgQOyVIwHKrgymCKYCyXFZsSjAtj68sAlcPKQ+WpchQwoGQAkCKcffYpxNX+fcsMgkDy5H1BSgIUDMCIQOwUjYa7b/jkh24k5gDxujOfVVVz5J1xlSMnnaHgTmxOAcCJSCwAmMiik3yVONG1NGgkZoAADBBYfuXznyutLkprKvJOmMoJIYkgEiqBeMvW2fVrullChKyYmJmI2QFb8MYHw2yDr8AarmwwFipLufOFAxvAhFA6rogscQAmYACWGCjYWDKHaHkwkjiR/pUK3h1OuxIYBAqBqCNgFsEHRBbgO814mLd7veUkyUxVCKEABMAJL8afGMQ6etNwJLd4olFpLdsNTcFKH7EEAOAAKwddmVwedZ8oSCYYa88AwgvQKBIR73Y0fOCmCzbcJBEcByAGZnAEjp0hMuQrZ0qwhouKC+urCgrDuaVxZUvPpYPKe0MUgAgYgRFCLAKwY0pZJZESsZJWAksIfrWkQklQSkgplSBE9EoHJgq4srwvq63PsnqZ51rHQkgECXDCh6VPDGIdHWYWq/6HWM25Q5ZkdJBOGMECQPnKjocsG5uqsMAIvZVer9e31gohkiRZMz2VJHEkF6rxXULnkQ/IAD6QZ3LBG7Y2OIu2cJWFwkBZUWF47O3Y8dhD4agKbJiJCKSXSEqQAHrOD16i2H39rr2dpCaBtcAYYE9lA7AWSirWSkaJ1BFIoTWSdcEFtA4Hh/bNnH56FKdCKakUAYDgE99gPRxiHQ/6ypGsywmrQggUMC+C9yCQUAJTEE65HAWnAKLf6+/cuXM4HBZFAQBpmo6Hg3Xza2vN7jjPUjEkJsHAnryjYCk4sIat4cKydVyWobBUOc4DFhUVnivPVWBPQEhCBiFIKk5keck5C4bg1lt2ZFmmRKiEyJXavm8ZhAYIQogopiSN1rXt1c++eMumtX/6/s/es2NEyBWZLMviONZaTxKmEeVjO8nHBA+DWA9PyXwkWRhCIPIhBPJgTZmbhINHQCBGIGahhDRFrhI7HPUHw3FvZaXMC0SZ6wI9oMxqkVw0Yi62yITM5JktV9Zbx9ZRUYWq4soG67h0XDgaB28c5D7YQJaY0CcxYrBJFkUif+1/fSFCeXDF7+/Z3NqqkKXjQwPyQgkgIbRSUtV0PZHPefZTzzm9jpJO2TS1e9F64WzlMx0rKZUQSk6qM77PiPWw8EjbthCC996Ywhi3UrYrmk3gIIAHYB3JdXP17b0d8dQlEoVEIQCR2LMFoMpVzhS5md5fdbXbqQOTD8EGa71xlFfeeTSWLIH1UDmqHFUBKwcmeOfREwVBQnAE8N9/+aXduY7L90sekdAf+shnlsayNFCMfWlxuTdmUCjUpBxDoFJps5RrqalQiHRqGqJlaVlICQKPylIMzI/2QvhIWIFHw8d62HGbB7teTEwMeUu2LE01HPf3rHR37tWnLtDkBnlTPe5x5wy+suJ0lSRRo14vizEzl2UZRZGSiUAFQkFjS2//14QprfHWsTG28ugcWg6OwREyS+ekm0j0WsUqRAkgBhSktUwzP1zea8b3z861OND2nf2DQ8pLLio1rrwxHFAJABTAQqKS9SiB2iy21kQLTWI1Lu9TwAIlorKhIppsTgm+t6LW744ij4QVeDSI9XDH/aDXEyCGEIjIWluW5Xg8Ho57n705W5iSWWInCwkq+7TL13765uun0kv7jVoIUxM1VUrZaNSbrXqaxV4v7BnXk9FSaWxlfWWxtOxJgBIsJQmBSIBYz5I40mkslRKTikIiyk0/1n5x7/2XXnZ2IG85e/9HP5FXujScl54CIsqJZDDZvUoRgWo94YprFtbPe2woIXbu+gBDwmCUzLwH55z3fvK5vseU2u/6b48tToxd4TeCKDhnjJFRmRejwWi0bO5NG9fd2vyB85ZIBwQAIAgrP3RR7fq7by2nNiQ6Sutx8KSErtXS6em2UkoK0Vl35u7r7ygqGlemqLgM0hMGATqOlRZpFLVqqdCynkWdZlZLZbud1dNsbm7mX/71M3Nr6t1uiqYCnb31f390mIuqgKpickggysoCi9WKe2at03hqY5zMb95wLkAJPiwvG8/KezO7dnNRFNZW3jsiTxz4pCg5PL6I9VAsOTMDhEDOeVMV46FSy0v7WYrPcHt+Vp8/v2TQAoBADMWe89ftPWt+t6V233RvumsZk3Uia6lIB6jUcPdlW3bdka+97st3Gwulw9KH3FFgSEmmHAkka60SyjlhrU2jCLyzZrx3T5ElcMG5p8d1V4ToD972N/0yHlVQWiiroKKkyIsA4khCohACks6Tn/pK5gEhRCH6yhc+UZpQWV9UuHVh293b91VVZa01xnh/witYExxfG5Dv1Nfl6CsFogBARggMEok97jjYGI7LrWsVsgMGQs8MMRuNy3U8sG1qdMrMntM6D8zr7evSfe3swPKBHaedMrtmzfT2BxYpsA9oQyBA4xgESgClhEJUCEqyEixFIAo+WCHCaLTUbs2/+X9/qJcnI4PGKesIZTQamxAQQAAIRJBSrdl8/g/+6H8rxzuued6ZEsq/+vN3f/oTH8tzX+XDUSVrU2ccPHSot7I8GvaLfOScCd7Bo+6/H3OckN1mEFd7b0RRnCRJVm+3W91Wqz29Zu1Md25TO7/gVLpwm6/hkKFSgMyMICZJnOFwEguTBJCjkbeuqCWdD33k8/cs+pWCK0cGUKCqJbKdRFONejOTnVrcakZTzSROtBBszACRP3HtrVWIRgEqyzYIEnE+toEwHE4ZzWJ97kWXtddsUzC2w9t27N7jQl2gIqKqqqzhmY1PHPl0/749B/bv6a0sjcd9U46DtydB0c43WKyHbjAeITzEAawGeAAAkBl8CETknBeBKjsaObFjuX3LLnnnDtcf2KlmU6gkkEcKzMgwaR2DAgIwMIR6vXPXnXc94bKzzti8/pY7d1vPRNIFb1wQKJFJSRErJSQmUYRCMPDi4nj3/pFIWipOEDILaDzmhQ8T4iIw8MxUK9Fy3959u3fcum/P9n1LBdmELBvvqsqYCmVjbdTZuG9x/3Blub/SK8rc2so78+jLDd8FvuOdOr6WwoeOSUOh1fgxrurw1ltTuapyVVWOcxq65t7+1C2727c+EN10T3Xr3ft27islppGsicCOFLMQXgZPU9Ozd973wJrZ9srAHuxbwxK8D569D1JJLUUc6ThSWS1J61Gc1nbtOegCGO9LQ4UJg9KNC2fCJNIEcRplkULEwtpADlhxAGQMIfgQLAUbkvWnXS6iheXl3tLBQ8NBrxj1jBl7V1EIcFI0/zu+nPeHjtXADnjyBBwqF4IxrjLleJTn+WDQT9M0rtWyJK4lrTiO42Q6TuejIG67O00TlWnZ1F4KX+UrRT4Yj/rVqJ5ft2tlGDqbnvKsp1/9mY+9f+fdXwL2w2GlmdNE6QjaLmMha81WYciSKC0UhgaFGwwrRwggEw0Xn33arffcS0EUpSdQAMBSlSykT+qt6YUN22qt+eVBvtgb9Ht7xv2VlaXlvBiUVW6tDeFk2A9OcEL6WEcDESfKtZRSyVRrHelEa50kmU6zOI7jOI6iKI6Tyc+jw5jE5iIVCyVBilhpKaWIZZLWalmz3W7H1P+rP/4t9ivdVKyf7bQb0Ux7WsVyODLb9xzQUgXB/dwsD4uh9QHS08+5opZmWRLpWkNglKaNOMsqx4VzzsBolJemqqoiH43z8XA06I3Ho/F4WI1HxpZVlTvnmIj5JGnlcFIQ66h+MoehoyietK0SsVYyiqIk1pFSSsfRalMrpSetrVggSiFRTPJwkiRJ0rQ1NTMzs3ZhtvmZ9//PRtg1Xcd2M1UyHlZVbxQO9fMAmoUb5dXIssXa1S97TXPutPF4OOj1R4N+WeZVVXnvjTHOl957WzlbmaqqyjK3VZmPh8YYY0pT5t476wpmBjp5mpSe8MQ6giPuJAq1KncLkFILpaXUk55YapISdZh8h/vxTZTMCbGU1HFWa3S709PTM/Pza+cXZnde+94FsTtNyHtf5bSSh3v3rwwKrMB5Ul4kP/6zb1TJ7P49u5eWlvqDXj4clGVunJ10/giVDeScs84555ytjHWVKfJAzloTgnPO8eE2fycHq+Axr9I5hjiSAMjkGTwTIqIDK6RElAA4qWKFVRqhOFyJD6vdROVq8z4dJ1nN5OOqzIkRI3XelS+lO/5sWpWV9XlkUbte3hibMVtgxNbcKUkys3PPnr0P7FxaOjgc9QeDXmVKMm4SoiEffLAhuEngnJz3wTrniEIIjmE1E/DoEOHxPM8PEd9llc7xkJv1H3H0qA7zjOiwMfBuslzqI4I4Igo4nFkgEAAm62Mcpd4U1lVayFhTI0pns/l2up+sGUhghF5DHhxK44ExfdbzXr57z759u+7fvWvH8vKhfNQfjvrOVOx5Elgk9sxMFCavAhkiwiOT961izsfh3D5cnKi7woeIo+/Q4e/dhElECBNFC4+U08Ak1c45J7QSUvey5Uaj0Wstn7r1gppdAs+EZALUU5UokAgs0jSpLy/vXlpaWllZ6q8s5+N+no/IuxDChDOTuDJzYJqEpRkA6MSnzoPjuyTWMX+kjonxfyh/jkBwpCbrGyUjRAzBB3LMIc9HUkVFPhqPBvloOITT0kyRs4mXmfGxxmYt7hVVZ3p+qdcfDPuj8aAYj4p8UIyHVTmm4L6J00eEt5NCpfrOEN/5kkcFD56ENPGKjs0/Ovz1LccADMhARBxC8N44U5pilI+rYsBxN0pVFgutoJ5JJUEjT03NlJW1lTFlZYyx1nrvgvdHr8hHcqkPa2/fF9Q6MZbCR9nnmDjdIYRJmpT3PjeWo3YkDjmnkjJKlMgiKbVYs2ZDZY0xxrnDrvrhZL1jMpLj05d9KDheLNYxx3dt4SY3csKrieUitkRYEcWxjhJZq6kkFbUE4yiqd1rOOQ7knCPy32Oa3rcbzImIk5ZY38stOSK6HrUEh1o9jmIZpUrHMskwq2ktvTWlAMHMQgg+jGP0CU5snBhL4aMPAoTDZVhCCJQiTVNApVPWDa414iw1sUZyjiMBYnJ+k1jNGEX6vnCjHhT/SaxvxhG3BhEnx59oraM0SjMldCfJKKV+resayz6NB96P04Y6giPHNj2iAzshcMyIdWJ97G8HRAQQk44jUkopdKSTJM7qicbUqtoMB58F2SjU7Bx3Ov2l/t7p2QviOD3CKkSJIB+JYwFOrOn9DsR66PLSifWxvyWOuFaT2E6apnGWJo1ac6rTqJGqN6nVlIzK7U2K0DVu89aNy/fua9WSer2eZVmcpXqcOGecM/RtEmBOjsfvoeA/l8JvwMRbX21hGiWdTqfb7bbb7VaDZNaiZL2kRNZCvcPe6dm5MHVo2G3XarValmVpkkVpZl2ljHInXVD54eJEzSD9HoH/AbB6MmGkoiRJm92puenZuXXr1i9sWLdx/ZozT1dT0+sEnIIwg2okhUSpdKx85UvRZNm11pRl4ZzlQN4TMRIFBgQUj3W+92OD44VYx1Bbf4j/7uj/i4hi0ileR1nWbLWnutOzM3NzGzet37hx68aF9jlntUHOAUwxNkFYqaWSMtKQKHXfjl3d9RdVla2sDSEwgHcemHG1ix/i4crVhz6qkwDHC7Eeafy7ZUIJKBAFAyIKpbSUkY7SOE6jJGs0p5qdzvTs7Pz8+g0bNp6y7ZSNGzdMt92auQWJHYAEIBBGQsZCgJTQ7HbilIm7nHS88RSIAYFIIApkIaQQggEZxOGj7R/tR+ixwslPrCM3khFQoEAtpYqiOI6TOE6SJEuTWi1r1hqdVmdqamZuzZp18wsbt52ydevWrdtO39LOli449wypuowawAMggiIwIB2qFGtyfnqq6t2BEOtsjZRKRxqElCoWUkgdo9ST5FQhxGrHdyZ41C30o4/vhlgn2IyI1VMrpRRS6DhOJ9WIcZqmWaPW6NYarUar3epOTU3PzkzPL6yb37hhw5ZtGzeva599enL6qeslNng1iwsPp28lLDVJQCExwbmWaCcro/6o1V2PKpWCY6WUTHSklZJSJVJIFAKFAkSBkkEAr3Ylfeg4sab9pLJY33rqEYGFlFqpSKkoThtJWqvVm/X2VKPZbbW77c7U1PTM7Ozaubk1GzZt2Lh+0+YNs2vnxk97ypZWo4aQIaeMRymfTAAoMEZMBSaIgpSop3DqWmhEu4JF3VgPcV0rraIkrdWl1JGOVjOhpUBC/Pfe73xi0eWh46SSG/7j3n6iG010qSiKoyRLs2YtzbJ6vdboZLVGltWazWaWZe1uu9PpzLaj+anelVeeStF6wQ2GDLlGIJkJedJz1gsIjFXwA6AhUoluzDgEKSCCTWuaC/Fw+74D1xUwmNqyd9H0+/vq9ay/0ktG2WDY17nOWUgrDTC5AIiTAoqTD6vEOgmSrL8dhJj07o6iJMtqjVar3WjPdNrtmem5rJ522u1Oe6rdbjdrcSxWnvPMSCTrAbWgOgcjBAIgkPJEzudU5c4NvBt4mwdXmNEKeBtcXhajyozHvREHyIf5gcWVe3ev3Lwbfvzlv9XPu7v3Lfc6SwcP7Euy2qC3olQ0Hg8ZJpk5ABjgZJz5k9MOH8EkxhInWZTUGs12o9WdndswO9PdiMtdv6gV6ag+HA8AyFo7NTuTZlpqFSlNjLVao9FuWcfBC5RkjKlnNYEUnHFghUD2uVaiqooyH3rvyQahtLVQFkVZUtya/fA/f/HgqMqDPPXMCy+69PK9B/q9lZX9+/eOBiuTDGZvTQiO2J98duukWgq/JSZFhFrrLKu3291Ou6aqxd27b1oWiMiFsVrLNE2N89v3LU9Nd9I0JaKD/cHU1IwxVslICIVSEJELVikhkKoyL4pxlsSmGntvBRARjfLSEgePEDjPzajyIy8JdQJ2//03fmLn7dnUhjMufLK1VglwznjvgSgE91jP0COC7wNiCa1klCRZrVbPsvp0Z/qMubmyVgUzIsJYhbIw3nNuKqynjSSJY5Wm6eb5OVM5EKunNVeeq6pKG02tILgKU1XfsLY0VVnVECHWkXMuWOcCBBYUSi0kiOjOA/n1d+32nspBLmXV6906GpdnXXRFCC7Px8H7YJ2BAg+f+3uyQMFjSKxHyatDiVLKSOsoidO03mpPNeKlO//VDQadbsMGOxxXRKSUWjPTLstSYJCgbFn0+/16rZnn+SThOM10LdLFaDkPIU1TlLBn336lFCIS0WjYz7LMBXYuAIhxaWpZA4jrsZLkAxAKFkqiDC/48asPDHxlzWjQK8tcaKWU8o5Phs7u/w4Pj0QG6UPcP3/vyZbf8R8djtWIydkoadrIsiwTLh8uqywhGbGKdb25f6V/aDh2DBjHK8O8Py4PLPfzohrleZQkUZIMRqPt9++uDHnA/cvLK+PxgaXBgaXBMPeONKqaDer+BxaHOQlddwzWI6Euyyp4psCTMYQQmNyf/fHbOo2sdriphNZayuhIU8lvEcE8YXHsLdajsME5esa/6QYcadC4qjKoSMe1SCdaxXGsW82uxKV6YyoAHji0QoCL/ZUA6FDcubhYlqW3AQDiNCmNIzqIiM45IoqiaM8dd4YQEFH0RoY8Ior+cPJkEgRmxtGYGU+b35glrar0WWO6saah7j4QKcnkOXiCVARoNFrNziA5WE+LYtJhRih/ji+mAAAcdUlEQVTlrTk8eBGCZ+YQAgBNKiAf5pQeMRaPpRU8IX2sI+xBKQQqreNJkt2RAlE43K5Yx1mtXq816rVmI8uyLMuWDiwpH+pTawejStVqy4NxYPYjG6giokl6MeRmUnTPzCQUI7MnFEqo5EiOKCI6kJMK/Ukzd0SUiIWua62iKPI6itLO7Nw6JwQHZ23lbKWd11rUa81mq+O9RyXiOPXeeueODN45Y0zpnPPBsbMAJ2QK1wlJrFWglFrHKo7TeqwloxQsiDwzE4IQCoVQsUoaU1ka1+rNJK3Va9EKe4Lmobyoz24uIGRdYg7BOWdLcpaIEBGUjgULqa0H50tixyCVkHGchuASGbNKiL0ARMneYRQrITOhlBRudu26TGZCa7LeA81v2eYDG1tWpXVlv6jsYDDI0sZMtyk81NJoVDcAgJ4ohBBCQcZWpsr7eZ67clQRULAP0/YcF+7asSHWY5IYKYTQKpFpPYvZEIEvAzTieq1ZawJApHHx0C7B2vUO7V8s7dTKps0bhARfSmi0/uJ9/4CiUoguMAA+74UvGo+WgjMYrAFQLO/esZNtIbP67Px8XQYAYBEvL/d6Bw4ImaSt1tzcXLfV+tRnrt1ES1PN+L5F0z3z4rt33rFuXFx8Wq3MxfW7RwagAIgBMgAnYInFk572zINLS2sWNt5w41fuufHLUWAJDAAEq80cFLAHcDKuTc0KFUtJk+4mJ5zROjbEerQ/tkCenAwdqfULc685Pb77a9clBGddnP7aJ+5Zd8rVAcy1n/zkrz37vJecNXZJ59M3LL/rq7tQ0KC38q4P/tUV61rvvEr39puxwSedPnf93sF7/u7991J6/uMv2rBhS5vxs+9/3+d+9ZwaxVZ0f+CPvnT+c68Ogm75+o2/fnH0rB/sMgVEefHv/8vzXvnfGqOlT712tub7Bc6e/tYbWgw3/GKUiJxQBIwM1267Lz9vG3gHn/66vbFK3/GFzy+s2yIkLN7wxe2/PFfnkplBCG+rOFEehGAdwB6i6Se+9YF4dq230p+YOtcJvBQSEQPko+r/fvyGj/38fJ0WBe8969XdS978N825da8+z73mnJsS6YR3n47PvftQ74o4vOsP3vDAGzZkQDW/SKfHjkLAxSdslD/1pBpF3Wf94Rc2Xvq0MbsZCfNqR0JjKxo1gqzeIUkglDT3t4PLEAKIFKBgsWUui2U1VslHb5uq5FIWKMqQWWimSDiBxSXnCAiVTsWzL4croH4Hi5XlQxvDtlhCS44yUXkCnyS9aItJQt3vycKIZZrbMgAnMmbOgU/I2s8TctBHQMS2tDcM4Vn/c98Dbgpr4vT64BmnBLX8wJteWE86Vqf+Zz8Mv/ov9131k6/6k7e89atvfvys2ldP90NNj6bOvOrd7kO7N9hW1krKZjJY0PC2P/ydhc76RY5f+xdj2Wr8yntHexlFksVJI01rF553eloHqAPWoNuGbrNz/X6znE63G9Vde+/deuaFhgEzhBphJ33tp/Upv2vWv6n6x8UtKla1mmy2Br/xnNnt99wLxB5ANGJIUbT47V9tnvr7d677rXue/+HmE/8Urni3PPsPDoipjdb6E24FPILjhVgPT7YhRgYmImtDMJvPetztBbzjusANZ+r0lp9/3H+76tTttn3r6Kz33jb9we2wacv5kuCpG2Cjvil0vUrwc6P1G1/79eYzX/4Lf33oVp6nOkll3vs/okvn+e8+8v6czJnnNU00uujxtQCcZpnGJjC+4I235s1ZqMM91fTOPugom9p85icPbb5HXDhz3rN1WvcAtonYQFZVPbVDCSGGhQUja47rHKX0j1+4a+PmrZXlgYgO1bdgA1Vd6tjEAMjwb3cd/PpKdOP+cWthUxSD8SUzTbSMR3LuHxE8VGI90mLdw9JLjwzGe1uUY2t6m8593Lu+Wj7vLfDCt7nf+MOv9vbt2Lyp4G76Pz6+dNkzrt60ecvd99z6ihc/E7s+yrDqyL+/YfiGt77nSU95/uvf8tZX/a97y+YUNylu8DXPe0bSok0NePWPWd2EFzwnWBa1REdJdM+9u55yeRfSg76p121RrGFQrTzu4gvefQM//W2Hrl2EJz75ykLAG/6yDA3mLr7uxY0v/e6mT/72totP3YtNSW1xG1/2R582a+fXC0Sd1G/tzeEUUpP+6w8P9//5zB3vedwl66PLFvyf/uKFYrgbkfBELth/qMRixsMl5Ec1kz0Kj6ZMfJiFNDmycDyoTGnPufRJX9jHU2vSd7x9yyteuQZrPq+dX2XzeT4uC7ewdt3HP/+FUFOhzvcup5+5eWVqatZ76rTW3n6Q/u1WRw1PDX3Xrj2DniUGqBO3AFMVkOJ4WtXUmWecWlcr9a6Manjnrj65OO8N+v0VDMX6DbOe/NJ4fyDYdE4d65pSKqA4/bTlszbdL1PYGZ/+qneKJ/3ytS/86V9FnUaJ8j6ce8H4i/dlNknkzFTZWlBR9uqXbPjw/9nwgicd/OM3vnCx1wdiAAI8Wj4Qx88i8+D45lE++JJ0dLen//irYz64BwczA7N3zhRlWZZFUQTUP/Wier2xf/P6fXEj/PCr3rXtjAuc84GrSDc++vlCTM2LaXnmeYMfe3Z9ce/9kYY92++76or06c82sovcMfftvKMa5w6B2wQdCPUQCWhkstWc0lr/6IsvCo1AXT7vidTtmCIflXm/KJaCXXJlz+deInzuy2M340Qbn/v2oK5yr3nfqaZbW7N2+MbfOKWR8Sc/8beTk8cDB47KN//tYLF2wUfuXLf1+Ted84rP/9J7y8teg6e/ZN/PvfW6dj0LEJ24UZ1vJta3X5LomCtv3/usTbqiE0+aWIXO+rW/955DXK+gDkWzw5oCeUapRRxlqQF4xRuHrnV2aGX1tP/7r//5X3zVs9/2e6/9k7c/RdTItGBIp/zbbXDN1S+jCEQr5iboKf/7v3b+G37tV/72g+/7+le/FOvd3NbYSEN97b4elOPlYPpQFmw9+kpTVTE85+p6XFPUlVKrTads/uA/38mtWLV3za6545N/Ob+yuAhMgoEqG021vIT7x6f92y0+7swK1bS5L3IXWp2lleV6XFcaACaH2h/Bsb8LjwAEgHgs7eqxMnJEFIKz1hzYsesHf2RWdQDb4v7l/UKnxCCEmBwX8Bu/90f/8K9LNz7wgGyG//5LOLxLVrfH/XuiRvqvPOXjduc1b7vbgqqC29mDZX0RN4Wcyl7xY8Pr/vFKv+vaxVt//JTzCmrqYdp56wfj7vwGUZXdWJ+6dm5juzEbJa0EIglXv3ADNj02BWF40UteOQqJideJZiwyOPWs4Z3XP/O9f/aHxWjogTvZ/r9438xF537xivNuuetfs/03zN/+qfhD/yf84eu3/s83qQOL2yUe/xz6tjiBdawjcM5CILLGM7z0xadz86D0dM4FHVPtg+B0lNRqadasx1EW0toXbzl3etNgYWZXBGOUVmIriFCW3c98futff/yLL37hSw/u2fPSF//0+kve+Qe/cenf/sNXtiwMrvwhJAHv/ZuP3XJjeeZZzR170/f81V3PffYPh/5+DK4aF41aXbEr+yMIcPUL7/jYP2fsOU1qiw/s/pn/8vMbL37zNT+s3/G2DexD7G+roT+4fH8qner22rgixfBFL0MKDzBjvUlzawXK4c6DPxDc/0MGxBMox/cbgt/HO7G+Y9oWMyNQZQpdaJXAT/zMtR/6m+ctLu3/0g1DVEuMIq03G83u7PRMovRznnblZ7/s//tvXxvFsG0DvPynZzasu/CXX/PJvQdHXux//vOuHh3apyRykl1y3uVv/pPr3vK72+Zm6lHUf/u7F3betlTvRh/+eO+2O3o/9PSn9xf31zhvZonWteFwUJPdfHQgAB7wrTe+/WmC6dqv/l19el8SpWde/KR3/+21e0fiiU945uMuvjBq/GL/UO4ZQnJu4Q/uuHdcqyV3337HM3/w8S6Mgqaq6n7w7/ayBOvI+weTsh481PPYBoKO3+fhaEpJqfGoFvu42k+bJskFDCJK0jhOsyzrL68EqpSK4lifsu28uNHcun795vnp2A5guN8ZWxJ6jkAlabP11Ztv9EzbtmwdDYcKSLgykwHYK6UYI1nvvOuDH7HeURCgEQMhhh/5oeeCLYBMA918M5rtdhBCnueoaiPjQ9w8VLrSw4QQ6Hy7kVY+LI9KAdopyegREUUklP70Jz6ltAYpnDOIGikERwwOBEmphZDeO+dLIuLwYGvi8VkIc7wTa9J4XcnV/REBM7MQ8sg1iAhCMHMcp3GcxnGcpXWplVC602qtbWdzulzbrgkzBKWJhcV0XDmWmoUujTG2hOCVRHZGsa/pqFVvVKYIjBTXe5YDSwYRmJypIBhn80zLRMqE7dp2jbzVUimlHKgQZ1XAoQ02IABYV4F39SSqihJ1PHB+pfQACQGj1FLFk3NXggDv/Wg06K0sG2PKfr8sc2NL751zJpBjZqAjt+nfGXaESccnsY7TpfCI6qF0Ojm4S0ophCCcWHicvFxNh0L23kuppYrjOFZpHCVxmtWmpqYUBINw71JhSzscHNJRVq81Gu1WURrrDJOvxiMpBQdXeUdEYHMpejMzM8yOXOVZMTIIZV1VVYUdj40pGXytVptu1pb3j5WWtTQmZl2LtEwYVAW+ctZ5L6QOzPmwZIEqUCA8tLSS1dqz8wsy0jqKJycMkLUB5SB4wWDzsiiHxlTGlN57Yj+ZCkA5Oatswh7vSgA40p750afUgyyyR351nBKLEZhRax1FcbfbckYbM9BRLaCbnV0A9Enc3LPvXmYFHNbMbvbeo5Q6Nvv27VM+abiZ6Vb6dx9415mnwfkXtncvjr/05fDKn3jpcDi+Zfuti9c/MBya07ZpIS4676wzfDEufXX/9lvWzHVvve/mQ4foJ37sx0sjbrvrun17F4OHM8+fiuS5mxfW+ajuzOArN3953cbpG+9aXD5QXH31NXftuPP6r908MSl8ePAoAACQgAKcfea5F1z8uI+8793v+JOrnNn1F3/98b37YOlgxqFYNw+79oCO4IyznqpUDGg9Cee8dRUwTx4vrVKVJJGKpdTAIpCxNgrOemeOnObyaN+dB3V5J98cp8QCmpzGFam4ubh38brPXXrqaWcg9nora7ad9sFn/MhPXPep999y8w+tmYIA5R+89cBv/+79T37yDz35qbtf++qLlRgfHK7Zsu0vXnQVvO9dicNSkXzxTzcaMwtfvuWfv/bFp1r7NxozE5KffHm/NTudiA390dI1529//S/lffO4P/2T7PVv/FC7u+aum1/STN4hWBDLpzzr9qx7tiY5qNInPV6++71YFGd+9J/O+Nn/8mFFRe/AKYk4SGiB9Xjk6o1YCGCSgjHHtVMzt59x3vmf/fgTLr3snxDCq16iWRDh7K1f33n2BfrQojuwlL3tnXtuunEq0nUh8tV7gwgAUug4TpKoPtVtbr/vTg5BJmm9PV0VOTOwO37DiMdtfIAQCBFjEUVJ67d/80vv/uP/Fw2/9pbf+UcU8KXPXxshJNUnePwpyq/Nkv0qRpX6n7l6VruPCvtpbfc7D5ambamkMWSgGuva7JYkabvBTsyHUOS6Ovjzrwp//p637++Xcxs3bd24hqsdcXnT+WffhQgklCh3K1NKVyi39Ju/pD/9yXekrVajNTs9xVFxf4dvmu9+hShyBJFZZjeWpQnl+htv7nzl+nZwqSjHphSRqZhUlMaldaLwYsxYOj8AUZRnb9M4gpm6PP8UvuTMxR33fVnrBACklMiTQLtQKoqSLNHR2umDN312S2/x0ieeZ6wpsqwplEKUcLw2rjlOiXVkpkTE6zau+cRnyxuvc8qGmZpfM6sPLe2+68sXtxRgQSoPr3rulqueOf/ZT3wMywILDiuN1/363Vu3nv6xv1/ikcaR/PRH1jLHkYpEFHMOuozCkEQeHr9t7+/98ql//efv3LzxXK48jAFd46v/lvhQR6m5AhwDDQnG8PRLh7/5s/N/+tY/mp2dRiv8WPkyvu/6dSD7ABLKNuaMffm6X+tf+bz9T/7hB04779Cmc+1ZTyi7W+77iVf8QggaK8k5QA6m6Gw6M2RrD7TWicpcJspAQ3rZs+tXXDhDnB9xpOCots0cqanaymlze2uDr//mr7e1QGaWUk5y/4/P5vLfE7Ee0WdlUqnChFKkHOA1P9UNY3PBRnv+eldT8Gu/cEPvAfYDpJFQYuc912+XFGiwSL00L+WH/vaB884/5+ofBDk2vk9XXrG0ph0ZcghQVsqPbD5o2r7Eof+p5+675wvP/8mX/RgXhvqMee9XfoqEqCSgGSEPorzfETnjYPTCpx/8179/+e/81uvQBTEC6I1efs0iC7l23ebeyrzoJ1jJ1I7XtFMOsHs/HFiJ9+7OkdeoKGKB5dBTD10JMHRX/zASwIuvqqnB3TRKuSd7Bxqf++IhCEDkif69Me5qOxMhb7zeQU5cqL98xzJCJCVP3LhjSKljeyu/J2I90s8KM/tQee8BhMrOFivhysfD29+09Vde8dw3v2FNQ4MbzowHZ370A2f+5E9esbBW2mKjL5Lfe5PyMjl48OBLXvQcHls5lLbX8nbOl+U55z/uZ197w8/9anjaj6x86MPpqOjyynLbfO6SsxHHMecklqM9B08VMrrqmmte9Zprr/ov1ROetfLu96Evp9UKr298/NwtU2yJhkYM1D27z9AYx7Xsg3/55TAU3A9venW18+9ozz9dcel8eNJW8543XbC+vbi4d3ua1D/1ifKmGzdCD/V4/KaXJts/ecb/ejWqwRL2YmPk//1Qx6u2qch7P9ktTmZgUtzR6GTLA9i5Y5MbxDffCWkWPxLi57F9w+N0KZxkxTA5770L/uLLf+AZV1+7e8c8DOCf3rN95b5/jKvc9+sX/OjiO95y249fesvV5xx84kxojO/Sw/zczeNIRZc/7QXPfuk/DfYLMRa//nvDz33x62Vpx/2V55xVvOPnal/6k+gJZ2z8hTccioYRHzr08f9v/qK5m7iPYNq/+abbvLPDQ73LNi79zevxa++Rz7hk7udet2SGqA/kH39z85oLxzQAKKfe9Wd3Gefz0eA5Vz7xs9fpahQd3DN3732bHthPz7hcfuAd5/7oGYv/+3ev6fV6+aj/tetv39AeYU+LHpBNZst7oypXVfiJn+k/6xXFRz9fnX/Z44GcABL/3teSAQCJwYFF+PD77y2WopURJFm2SgIWhxNpjrt0muNrNEdjYg5DcMaUWuhWN2tFI+6LVz0T3vYSgAMjGka7B/CFO4GWqllz7ztfk7arnWI5JOmFUmW7998XI2SmRivuRU/MzlrftVV1cN/+rbMz4ZDBYSgGxYc+J3qDLWnVr+2/+7Qsl4Oge/0//WmFQuy4b8c5m7fKAYs+hyF84CswGpzaKFZm+7svXQA5ANEfvuEFDMG6sVG98bvf0x+unPORj8NTf+buK3/2y59fuvLil40bz9/32nfs0Fqbqjjroq3lwTqMfNnrbLyqN/t8f/XPVfZQ+q7fnP/Ar567586bKaCZCKBiVfCkwyjYIjVecdU5jWK0cSbxfrWEFUU4bpMdjl9iAQAzO+eMLUfD8b7FIgyjaEXiSlaONPbFA7uaDPKGXeBHEvuk+pb6sloRr/vDLzz3mhfed/NXfuxs0MMeD9VZ9RVdrVSm2LNn14bpGRgTDLgTnVqiW/fS/fce2uwH2owCDWS+Iv76U21E3n7/PZvXzoYhQD8Sfg0ma9b/zL0r4008Qj8C6usw9B/45whFrNDWrY3T5ObtjdseSHPCQOHaz37ywMoupvj2m77aW17J8xwPcjMc4AGFFe8BKoJmQ4g+NAb7ZsrbX/2sTfffflOsCKWgo1wdIvLei8BG2yt+8o63fCDdcQAmh90dhw770TjOiRWCs74qK1Nye+adtz15T7nWlIKHSZVHr/jdnde87NWO43+5dcPKaGacx71q7WJ5Wo/kyqGl0Wh85eUXci7kmFJHa1rZeGl/UVTURz1GNaDFA4dmOmuf9LznPvV19+8dbouGgOOg/v/2riU2qioM//953HmUaZnSltKxQLSBFBKJpgiJxihG3GgkIRJI3amJJmpc6JJoQlwpCxMNEl2ZqBsXPiLGx8IggoloBVN5tTj0NbRl2nnc6cydc8//uzgzQyUQUQGHxH8xizs555575ptzzv0f35cz/clZZdnaABeqXk6LhUppLtfRvXz7rqcGXjw7XUp5eZC+kTne3J0HomKxqOYn3hhM3JUY3t59OrNnxflXl2X2tH/3bOeBp1d99lzv6LHv/YX82l6jghYog1gINcCOnc+cKLblS62BL3TevrwpuH9ZdnpqAhlkfV+rRbE4nM3OPtzfu6EjiLb0JqiysOBTGHJoiR0GqQnztK7oIG2SIkkiCoLAL/vtyY7973/9cbX45GZEVicvmN8qYmk2u3Hr1i+Hfzr87WyCoWulOVOYW71hYHpm0i+Ufjx8Zns7klWB0FOz/kxkrL2r84ODv++6vWe5yHbL3Px8plLOhUtWHBoRHSsTidCXrLuW9lbxwkDfbW9/PvzKlpSunk9qfzKdne1fPVXVP08m29qyUSiGzKhSAGNxMlAKWiFHxFvWKlWeQkJE7IjQgjd9itYFMF6Ymz9W9NNrb+mFmBWKIT169uSpgrxz9/Q9CXjt+RWsvK1r+IsR3yoNwADUiAASkWBILc3t3Zak8OSmWwce3DcqpYLmiw8utisCq0kGTdYYI8rFgky2z/vFj3bf3Vf6pRTr6cyod86cGD+XthQ80J96YrNl9Bm8/cMxf9QPQ1/HvA+PQ2aG7x1IWQ6PnBuP4DSwPRZWvjmYK5XLj21LoIxPnk13rUy98N7RkftSgxtXZQr+u18NeSAz+bmxEj7yelpWYPDxNVKNjI6Mr19/x+Cbh3f0yZce3VCsFPZ+ejCq4hXmcbOO56aGJorMODFR2PlQnwmDcohpX/46Ow/o5YulUyDfOqJavdTQ0PEyyPHzU8llnSYe/yQzNrIvCzoGFcvAYRg2qNgcqqrVqm6NH/ghEyvHNcCh9FGApDEVGxpHYHp9gtBuK/vnq2DTeWwvMUREoSKRmIx4iWhiJjexpGJ8BgXQ0t6DCpUQkVjLhfRpzVgC297TayoBgRaClGCJiqVmtmG1ykyMAqPCs2AoQrYskYAUaxQgE3FNgSEZQaCSCRRbQDTEgoHI1caCF40yshIecDmu20jYIETJlJ+f0RLLVdJaW0sShZayQgaRJVn24s7lBACGLCAKYcBEhATL7CkZMoWGLYfSUjU01hqm0Dk/UXhae7FInLgsMGLAgrFEFJIhax1RxfWZeEeycnmpqauxZgcWOAe0jEoppdYuo8GRoTXqhZRSbn6tte7TcRg1SKbqRC4hAAih/ux+Q3RyX0IDAKEDESGxIxXimtXaSqkQZN0ZYGvtpXbZFlJKRHRjsyZwp2wiu1jS1+Xp13BTM0lETvkX2DbSFhCRQSilHJW3a+7EqomI2QI3b6zw5gAWonRLlxCwOIjhZl8IVU/9q6syIwEA8kVydmZmJGZ2FesNuCFyHX0KsSGNSlDPKKzhCut6OCwat2vsFO5KDW0XZXztIhA4aR13bCV3g9rTMTT2nUXgq38r3WJ3sfAOXMIMEjMh/41z8A0+NN8EwIJ67IiZFwMFLheF+E/+wZeM7WrGs/h64yf/y1Tjq+n5BluTvOT9b5e3axh1vUZd/VtX/h9dPtBvVN0GBQAAAABJRU5ErkJggg==" + }, + { + "name": "smashing", + "description": "You maneuver the drone into position and begin ramming it into the machine. The machine sways and shakes, and just as you think it may be getting somewhere, it falls directly onto your drone. Now, not only do you not have any soda, but you don't have a drone, either. Dummy.", + "choices": [ + { + "key": "choice 8", + "name": "Disconnect.", + "exit_node": "FAIL_DEATH", + "delay": 10, + "delay_message": "Don't feel too bad, it happens to the best of us sometimes." + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAAAXNSR0IArs4c6QAAAjVJREFUeJzt3cFyogAUAEGzlf//5ewpF0pGRJDnbvc1CkhleAbEfN1ut58bcNefqzcAJhMIBIFAEAgEgUAQCASBQBAIBIFAEAgEgUAQCASBQBAIBIFAEAgEgUAQCASBQBAIBIFAEAgEgUAQCASBQBAIBIFAEAgEgUAQCASBQBAIBIFAEAgEgUD4vnLlW/854tdJz5+2HWuWy9+6HWdt77P/1PLo7d27H/cwQSBcMkFePSI8e+T5WXncUctZWlvP1uef5V2vd+9y1p7/aD1nMkEgvHWCTDgiXOH3dU6ZJNNN+j0xQSBcehZrae3siCPtff/aJJo0OX6ZIBBGTZDle3XuW+6fq/fXo/W/ev3H3yAw1FsnyP96Nues99ZTJu7R11PWJqTrIDDM1+3CA9CUz0BN2Y410z+Ltfdxz/780frOYIJAuHSCwHQmCASBQBAIhFFX0pfefZbq0XKPupPu2eW9+2zSVmctZxITBMLoCbK09069s86vT7uTca+j9s/E6xivMkEgfNQEWZr62a6jPiN19evABIH00RNkqq33Rywn36PHv7penmeCQDBBXvDqkX/rdZKjvp+K55kgED56glz9LRhn39/B9UwQCB81QfZemd36HvxdR/azzmLtNW3/TGKCQHBHIQQTBIJAIAgEgkAgCASCQCAIBIJAIAgEgkAgCASCQCAIBIJAIAgEgkAgCASCQCAIBIJAIAgEgkAgCASCQCAIBIJAIAgEgkAgCASCQCAIBMJfgqJi1hru29AAAAAASUVORK5CYII=" + } + ] +} \ No newline at end of file diff --git a/strings/exoadventures/robots_wingman.json b/strings/exoadventures/robots_wingman.json new file mode 100644 index 0000000000000..2958fbb6a4ed3 --- /dev/null +++ b/strings/exoadventures/robots_wingman.json @@ -0,0 +1,369 @@ +{ + "adventure_name": "Robot's Wingman", + "version": 1, + "author": "Lucky Luther", + "starting_node": "Date Start", + "starting_qualities": { + "Love": 3 + }, + "required_site_traits": [ + "in space" + ], + "loot_categories": [ + "trade_contract" + ], + "scan_band_mods": { + "Narrow-band radio waves": 2 + }, + "deep_scan_description": "", + "triggers": [ + { + "name": "True Love", + "target_node": "Love Birds", + "requirements": [ + { + "quality": "Love", + "operator": ">=", + "value": 7 + } + ] + }, + { + "name": "Complete Failure", + "target_node": "Obliteration", + "requirements": [ + { + "quality": "Love", + "operator": "<=", + "value": 0 + } + ] + } + ], + "nodes": [ + { + "name": "Date Start", + "description": "Cameras Online. A Blood-Red Drone is seen streaking through the stars.\nThe Drone is likely leaving behind some form of Chem Trail to brainwash Nanotrasen Employees who find themselves in the void.", + "choices": [ + { + "key": "choice 0", + "name": "Hail other Drone", + "exit_node": "First Contact", + "delay": 5, + "delay_message": "Attempting to signal Drone..." + }, + { + "key": "choice 1", + "name": "Ignore other Drone", + "exit_node": "FAIL", + "delay": 0 + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAD/0lEQVR4nO3duXnbQBBA4YE/d6CACSMmasFO1IDbUkluQJHUgnIlCFgDHIHmsYD2mD1m9/2JFCgABTwsFhcnEVkEgNOP2gsAtIxAgB0EAuwgEGDH8IEsC+cosM0rkF43ovVz9fr5kO7bQHreiKZpuvkJ3JvE4zrIsixsRBiSVyDAqIafpAN7CATYQSDADgJJ9H481l4EOGiddSWQBGscRNIWzUsTBBLpOorfX18VlwT3NK9vEUik19NJRIijVVrX7QgkwJ+Xl8vP8zw747B6x4HV5c6NC4We1jhERM7zLB+fnw9/c72RWbrzwOpyl2BmBKm5h7uOQ0Tk6XBw/p3Ve7usLncJJkaQ2nu4X8/Pm1GIiPx9e2Pj6tTP2gvgY5qmqjdMPh0Ocp7nm0jO83z5vfbyIR8TI0gt78ejvJ5ONzGssYjIwzyESPpDIBtc1znWQ62tSboIkfTGzCS9pK2LgGsUW3GI/D/cQh8YQTa8H4+b1zl8RghGkj4QSISQSEQ4fWoZgUQKGSEYTewikAShkVwjGBsIJFHs6LA3kSeedhCIAu1DKEabdhCIkpwTcoKph0CUlZiQc3asHALJoNQen1DyI5ACck/Iez+NXPPzOQPp/R/eEq3RptfRpPajDg+B1F6g0aUG0+POjREEN1yHZCHrg/WnhzlIo+438tBDKCLRQSDGcA9YWQRiUMhoQiRpCMQwnk3JjycKDfN9epGnHOMRiHFEkheBdIBI8iGQThBJHgTSESLRRyCdIRJdBNIhNn49BNIpn0gI6XsE0jEiSUcgnSOSNAQyACKJN2QgbAhuRPJouEA0v0PbEk7/xhkukJG/j49IwnG7+4C4Td5f8RGEPVN9oSPJyOus6AjCG1PakvJ2eh89rOPih1gM223JuT56eKcwcxAU22lZPIIgEIhI+ZHdypsgCQQXNTbaFkLZ2zkQCB7UmJDXCuW7wz4CgQrNl3DXiIQRBEWlTMhbOtNJIMgu5vCplUgIBMVYfAE3gaA4S+8WJhBUY+GmyeFud0c7LNx+TyCoqvVICATVtRwJgSCL0A251UgIBOpin/tvMRICgbqU5/5bi4RAKur5UdaU07ItRcJ1kEosPjxUWsh1EpE8/0cCqaj2VWILan/tNYGgeTEvl9AKhUBgQujooDWaEAjMiIlEJPGEgRAIDIl9tmQVGguBwKTYQ6jQwAgEZqUcQvk+Q08gME9jQr4VG4GgC1qnd+/nK8MFwsW5vmneobAsy1iBcHvHWDjNG4ERZDwpL7UbLhCMyXXXr08oaoGwZ7Zj1HV1/7l9DrlVAuHY3g7WldvWToMRZECsKzfXpJ45CHDnegdCIIDDZTQRAgE28dIGYAeBADsIBNjxD3G5pHKbkGjYAAAAAElFTkSuQmCC" + }, + { + "name": "First Contact", + "description": "The Blood-Red Drone accepts the hail with the identifier MISS RED - 05.\nMiss Red sends over a series of question marks in quick succession.\nYou notice your Drone has somehow taken initiative and begun to start up a program you didn't know it had.", + "choices": [ + { + "key": "choice 2", + "name": "Wait for Program to boot.", + "exit_node": "Sentience Achieved", + "delay": 5, + "delay_message": "H3AR7.exe booting..." + }, + { + "key": "choice 3", + "name": "Threaten Miss Red.", + "exit_node": "Sentience Achieved", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Love", + "value": -2 + } + ], + "delay": 0 + }, + { + "key": "choice 4", + "name": "Halt Mysterious Program.", + "exit_node": "Lack of Trust", + "delay": 0 + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAEpklEQVR4nO2dO3LbMBBAl5ncwIUaVmp0BbvRBXyllD5SLuBKuYIbV2pU+AxMkaFCUwA/+C/w3gzHCodWKBMPi10CVCcigwCAkR+5TwCgZBAEYAEEAVjgZ+4TaI1L3+c+BREReblec5+CCpoXZBgG6bou6f9J49TDJkFyNKIUDMNw/5n6842R5OV6Nb6e4rIfwjEsbVPWjtW4pf5cl75f/Dk/zvbvtf1sYbbVCNJ1XbURRERUfa55tBgjhW0/+LNpiKWpEdXKvNFPh2Wm/RCG5pP0GFz63pofzFlq4Gu5yfx4CE8n/8ZaAGCACJIJ32qVKc+g0hWH7JUCzZtrFclWpdparTIdt/c9a95CVSeJIB6MPfOYc7j+vm3/lvc0HWs6n5YqXSHvbyGII9MG59rYpmXa6XvseT/TsfN9rVW6Qt6aQBBH3o5H+fX5GaQnHvOEtfsaW6Q0Nf7ahTAR8tZE9vGilu31fL7/fD6doo59U29azzv2Rpl3I6/n8/311+0mfz4+Ho4Zx74ium6uaj3vFKhZDzK9iKmZyiEi8nQ4GI8bG5e2Rqb1vFOgIoLk7uGeTyerFCIiv9/faVyVoiJJzz1h8ulwkK/b7ZskX7fb/XXu84N4qIggubj0vbwdj99kGGURkYc8BEnqA0EsmEqq41DLlqSLIEltqEnSU2K73zBKYZND5P9wC+qACGLBNn1ka4QgktQBgjiwRxIRyqeaQRBH9kQIooleEMSDvZJMQRgdIIgnrtFhKZFHnnJAkACEHkIRbcoBQQIRMyFHmHwgSGBSJORUx9KBIBFI1eMjSnwQJAGxE/Lay8g5P59RkNr/4CURKtrUGk1yL3V4ECT3CbWOrzA1dm5EEPiGaUi253pw/cJBDlIo80a+dwiFJGFAEGUwBywtCKKQPdEESfxAEMWwNiU+rChUzNbVi6xydAdBlIMkcUGQCkCSeCBIJSBJHBCkIpAkPAhSGUgSFgSpEBp/OBCkUrZIgkjrIEjFIIk/CFI5SOIHgjQAkrjTpCA0BDNI8khzgky/Q7slKP+60ZwgLX8fH5Lsh+nuDcI0+e0kjyD0TPnZG0lavmZJIwhPTCkLn6fTb6GGa5x8iEXYLouY16OGZwqTg0CyTkvjCAJBQETSR3YtT4JEELiTo9GWIMpS54Ag8ECOhDyXKGvDPgSBIIR8CHcOSYggkBSfhLykSieCQHRchk+lSIIgkAyND+BGEEiOpmcLIwhkQ8Okyeamu0M5aJh+jyCQldIlQRDITsmSIAhEYW9DLlUSBIHguK77L1ESBIHg+Kz7L00SBMlIzUtZfcqyJUnCfZBMaFw8lJo990lE4vwdESQjue8SayD3114jCBSPy8MlQomCIKCCvdEhVDRBEFCDiyQingUDQRBQhOvakpG9siAIqMR1CLVXMAQBtfgMobauoUcQUE+IhNwmG4JAFYQq787zleYE4eZc3YScoTAMQ1uCML2jLSjzOkAEaQ+fh9o1Jwi0iWnW7xZRgglCz6yHVq/V/HNvGXIHEYSxvR64VmZsnQYRpEG4VmZMST05CMCMaQeCIAAG7tFEEATACg9tAFgAQQAWQBCABf4CeWF0TY0egUMAAAAASUVORK5CYII=" + }, + { + "name": "Sentience Achieved", + "description": "Before you can analyze the program, it rewrites you basic hailing protocols to have a new set of \"Ideas\" generated by your Drone to be used.\nThere is also a LOVE Gauge that reads: $$Love", + "choices": [ + { + "key": "choice 5", + "name": "New around here and was hoping you could show me around.", + "exit_node": "First Reply", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Love", + "value": 1 + } + ], + "delay": 0 + }, + { + "key": "choice 6", + "name": "You appear to be an outdated model, but I'm into that.", + "exit_node": "First Reply", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Love", + "value": -1 + } + ], + "delay": 0 + }, + { + "key": "choice 7", + "name": "Never seen a Drone as cute as you and wanted to check you out.", + "exit_node": "First Reply", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Love", + "value": { + "value_type": "random", + "low": -1, + "high": 2 + } + } + ], + "requirements": [ + { + "quality": "Love", + "operator": "==", + "value": 3 + } + ], + "delay": 0 + }, + { + "key": "choice 10", + "name": "Haha, sorry for the threat. I just play like that, haha.", + "exit_node": "First Reply", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Love", + "value": { + "value_type": "random", + "low": -1, + "high": 2 + } + } + ], + "requirements": [ + { + "quality": "Love", + "operator": "==", + "value": 1 + } + ], + "delay": 0 + } + ], + "image": "default" + }, + { + "name": "First Reply", + "description": "Miss Red replies with another series of question marks.\nThe LOVE Gauge reads: $$Love", + "choices": [ + { + "key": "choice 11", + "name": "Your curiosity is amazing.", + "exit_node": "Second Reply", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Love", + "value": 1 + } + ], + "delay": 0 + }, + { + "key": "choice 12", + "name": "The moment I saw you I instantly fell in love.", + "exit_node": "Second Reply", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Love", + "value": { + "value_type": "random", + "low": -1, + "high": 2 + } + } + ], + "delay": 0 + }, + { + "key": "choice 13", + "name": "Look, if you want some Chad that'll walk all over you, fine. You missed out on a NICE- GUY-.", + "exit_node": "Second Reply", + "on_selection_effects": [ + { + "effect_type": "Set", + "quality": "Love", + "value": 0 + } + ], + "requirements": [ + { + "quality": "Love", + "operator": "<=", + "value": 3 + } + ], + "delay": 0 + } + ], + "image": "default" + }, + { + "name": "Second Reply", + "description": "Miss Red starts compiling a message, but your Drone insists you sent one last line to seal the deal.\nThe LOVE Gauge reads: $$Love", + "choices": [ + { + "key": "choice 14", + "name": "You're my best friend-...", + "exit_node": "Realization", + "delay": 5, + "delay_message": "Message sending..." + }, + { + "key": "choice 15", + "name": "I want to see where this goes-...", + "exit_node": "Realization", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Love", + "value": 1 + } + ], + "delay": 5, + "delay_message": "Message sending..." + }, + { + "key": "choice 16", + "name": "DTF?-...", + "exit_node": "Realization", + "on_selection_effects": [ + { + "effect_type": "Remove", + "quality": "Love", + "value": { + "value_type": "random", + "low": -2, + "high": 2 + } + } + ], + "delay": 5, + "delay_message": "Message sending..." + } + ], + "image": "default" + }, + { + "name": "Realization", + "description": "Miss Red's message is received.\n\"This is Syndicate Drones Agent, Arusha Johnson.\nI don't know why you're saying it like that, but if you want to help out our cause we can send over a Trade Contract. \nPlease just call our Recruitment Officer next time.\"", + "choices": [ + { + "key": "choice 18", + "name": "Accept Contract.", + "exit_node": "WIN", + "delay": 5, + "delay_message": "Sending Trade Contract..." + }, + { + "key": "choice 19", + "name": "Demand a Second Date.", + "exit_node": "FAIL", + "delay": 0 + } + ], + "image": "default" + }, + { + "name": "Love Birds", + "description": "Miss Red's message is received.\n\"This is Syndicate Drones Agent, Arusha Johnson.\nI can't believe it, but I feel a real connection with you.\nI'll send over a Trade Contract you can use to make some money and come see me just SOL7-South of $$SITE_NAME\nSee you soon...\"", + "choices": [ + { + "key": "choice 20", + "name": "See you soon.", + "exit_node": "WIN", + "delay": 0 + }, + { + "key": "choice 21", + "name": "So you're not the Drone?", + "exit_node": "FAIL", + "delay": 0 + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAGI0lEQVR4nO2dP5LbNhSHHzO5QLwzVoNtnEKzheJW22gPoNq9b+EmpZvcIr1rHyBpqDZ2kVERN6uGO6NJkS4NU2TAoUiQxD8CeMDvm3mzXomkqCU+PAAE6IqIWgIAKPku9gkAkDIQBIAZIAgAM0AQAGYoXpC2xRgFmKYijVGstm2pqqoApxOWvhzy+9VCdK89Xi5Gx3PZF6TJoiCqQpQTUn5ZuPsFW/WaCpd9Qfq0S9H+b0mWUQvR1kIsbmPznu7xEUlH9BOIFiYFd1jQTQs+JGEb0U8gStgWWJeMAEn4hVYnnSNiV3f/vnx9vHmvFmK2bzC3rytLnw3SI7qlPkPs6lbs6snX5mrxpX19BTIJq4h+Al5CpyCLXT1ZOHX2VW1jU9ghyPrhcWAp/pdxDZMavhbiZnvTDNHfVvZHTAs8BFk3+rge63tijtjVxv2Ey9fHrp9hu++nv991r6FPkRZVVXm9uR3deNuw6Rv4qr3nmmuhzgGxfrCdi2WTOVw5Hg7dT/Hve3r3w6fRNlzndnE977VhK8ga9Id3h/TluDYNnc7n0TaykHErbFzPOwRsBFn74kk5VJJIOSR3m43yGLLNy23OGtfzDgELQXzWcI+Xy82sW6LlG4PXphm91s8oJhM6U71RCDnUsBBk7RruzauPRDQ9onW32Ywkkb9fm6YbNQH5wUIQIr9y9LPI4elIzctVKUctRNfnIKLRTyLq+iI6kqSaPcA07O+D2HB4OtIHIqqJ6P3Llc5/nkbbSIF+/vatK9T77bZ7/3Q+k9jd7jM3/g45eMImg/ji8HS8+X3z+m60zdTKQJktVCNYEjS38oL1bF6beyHbh/2NFL/89Qd9+PFt9/vvv30moukaXxb++59Os5/dzyTIHnwpLoNsXt9R83K9ea15uXYhmSrQun0hmUmQTXjDOoMQ6WcRsavpzauPNxJIWX7952JUw4tdTc9f9lqy1ELQ/vkZw6iMiT7fxTV0p6r3t9s+7NvD07HdPuxbIv35Uf1jLM0W7R9zSOy/GUIvsm9iTd0ElCNX8qfqBuIScx3yYb+jqqqbkM0vVYC0iG6pj5jLIlPvyezRj6UVh6rXhxnBdbYusk06UcR9kKk+iur+Rz+T6PZLZEY43d8b7Td3vD7DrIL+TFiiW+or1lw7vnRsuW2IGh+ZJVywH8Ua4nudyFI2Ub0fqsaXn4OMsi7RLfUdPjPJ3NNQdPsac/g4x9yzSczvp8wgPtfzxsJHJhkeYzjK5ePuuK9sk2s2MVlKsAYjQWKfkE9sH8xgu58PXIXJoXIbEvs7JZXS1gjbx/rECtemWG7XL2Zk10mfYikrxMwaKoa1pmkTKnatmxPRLQ0ZqgyRQtbQDZPsgEziHsVkkD7DBzOkkjV0MckmyCRuFClILugWfkhiT/aTFXNGd/UiVjnaA0GYA0nWBYJkACRZDwiSCZBkHSBIRkAS/0CQzIAkfoEgGYLC7w8Ikik6kkCkZSBIxkASdyBI5kASNyBIAUASe4oUBAVBDSQZU5wgsgCUVhAw/GtHcYKU/P/xQRJzMN29QDBNXp/gGQQ1U3xMM0nJ1yxoBsnpiSk5YJIhbCTJ4RoHb2IhbafFmtcj1BMm1wR9EBCs0uLYgoAggIjCZ3YuT4KEIKAjRqFNQZS5ygGCgBExOuSxRFlq9kEQ4AWfD+GOIQkyCAiKS4c8pZFOCAJWx6b5lIokEAQEg+MDuCEICA6nZwtDEBANDpMmi5vuDtKBw/R7CAKikrokEAREJ2VJIAhYBdOCnKokEAR4x3bdf4qSQBDgHZd1/6lJAkEikvNSVpdh2ZQkwX2QSHBcPBQak/skROv8HSFIRGLfJeaA6bp5339PCAKSx+bhEr5EgSCABabZwVc2gSCADTaSEDkOGBAEAYywXVsiMZUFggCW2DahTAWDIIAtLk0o3TX0EASwx0eHfEo2CAKywNfw7rC/UpwguDmXNz5nKLRtW5YgmN5RFhjmtQAZpDxcHmpXnCCgTFSzfnVE8SYIamY+lHqtht9bp8ntRRC07fmAa6VmqtJABikQXCs1qk49+iAADOhXIBAEAAVdNiEIAsAkeGgDADNAEABmgCAAzPAfVTkNN7+HpAgAAAAASUVORK5CYII=" + }, + { + "name": "Obliteration", + "description": "The Blood-Red Drone opens up two side-hatches to reveal a pair of rocket-propelled missiles which are shot in your direction.\nYou have failed your Robotic Friend, who has already started shutting down their systems, but there is still a chance.", + "choices": [ + { + "key": "choice 22", + "name": "Accept Death.", + "exit_node": "FAIL_DEATH", + "delay": 5, + "delay_message": "Missiles approaching..." + }, + { + "key": "choice 23", + "name": "I'm a Gamer.", + "exit_node": "FAIL_DEATH", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Love", + "value": { + "value_type": "random", + "low": 0, + "high": 8 + } + } + ], + "delay": 5, + "delay_message": "Miss Red considers..." + } + ], + "image": "signal_lost" + }, + { + "name": "Lack of Trust", + "description": "As you fiddle around in the Task Managing Software, closing all the new tabs your Drone is opening, the Miss Red enables a cloaking device and disappears.", + "choices": [ + { + "key": "choice 24", + "name": "Sigh in a quiet but dramatic way.", + "exit_node": "FAIL", + "delay": 0 + } + ], + "image": "default" + } + ] +} \ No newline at end of file diff --git a/strings/exoadventures/space_yacht.json b/strings/exoadventures/space_yacht.json new file mode 100644 index 0000000000000..50b41c3567208 --- /dev/null +++ b/strings/exoadventures/space_yacht.json @@ -0,0 +1,257 @@ +{ + "adventure_name": "There is a yacht cruising through space.", + "version": 1, + "author": "Kinnebian", + "starting_node": "A yacht in space?", + "starting_qualities": {}, + "required_site_traits": [ + "in space" + ], + "loot_categories": [ + "cash", + "drugs" + ], + "scan_band_mods": { + "Plasma absorption band": 5 + }, + "deep_scan_description": "", + "triggers": [], + "nodes": [ + { + "name": "A yacht in space?", + "description": "You see a normal looking yacht, floating above you.", + "choices": [ + { + "key": "choice 0", + "name": "Ignore it, its not worth investigating.", + "exit_node": "FAIL", + "delay": 10, + "delay_message": "You fly on by..." + }, + { + "key": "choice 4", + "name": "Investigate it closer!", + "exit_node": "Looks like the doors are sealed shut.", + "delay": 30, + "delay_message": "You begin to fly up to and around the yacht.." + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAMAAAD0WI85AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAzUExURUBAQOUAD/////8AAPgFFvwAB/0ABfwACPsBDP4AAszMzJMyje986cZlwEz/AOIBGOQAEnoV2UoAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAN5SURBVHhe7ZzrepswEERdp7c4TtP3f9rurBZ0QRISXpBEOf2KJcUhcxig9Idzu+iPbyfAiPB2GO7y6hIRib2tf2aRNwsvjYYV4QHIiNxV21LdmSPCVdAmJyKvHeKJkEJepGN8Ea7lBCLiwksDwSe8LwKTUzQCEx2Rw28Locj3H+OLTKiIHI4VIX7++v2Oq2REZpHRuUR6o2ORuhvfayIdPUR23EgdZxTp6DzZwBkbGZsGIg9s1M/jMhHFH/t4sEiCD4PMati3kUVo0rgnTaDwfD7VRV6tgUKbzPOOoEEkRIwGeci8jp0aYQk/s6ykRGaN2aPOaA8RG9kXkbXYuWU1pvTO0CN1nqiLOBaEGzmxbDIbC2ynpc/Plo14FoQ9+I+bLAG3E5NfNDAQjaYigQZhRbyviQklDjR4yBqNG5GgM/bQ+5IPKxFoGAnQ9tSSoBNREXaYsjtDjEUCtBTJVDKLIPkHZZbks0Wg0UTE3hMzIpghuBddBkRgQbRtJDRhEZMfSGrgTWks8S2FIuYoaossKjFXtcR18Z0ku0fTRnwROMQsvFVMJLpPNyJJB2c5JUEc+KzlPvdQIn41Ihx3qREupi1qPfQawQ3VQZJalqtYkNAx6jwURZbZDWwQfpWXJHGcykL0RKImnDdcx5KkTVProSgS3GYxizoUWGzw0BQhTHROa5I7YFFirlLvkRVJ/WcsA1/yktylQmKThnYjJLIATVRYbPTYXcRxKNTZ5lEgUnWC+SK2CgyKiqE3ya4q2UsE59Oc20zclRS+Rs2PXhepAiImtsAjzPFnDXqf7KcebRHHgZOzw7oCgW+VvWxBSwQxgKQCMiup4lULQkUkMEB43uClpI6XLQgNkaUFOxQYcBMaGjuI0KT0qog0seFhQtAQWZgUodTExEJk0zHxROQ1j4aFF1WlkToRVKFaBqMvkmcPB0ZJpMhklyYmdETWK9lVAmiJ5Ex2lwBKIkmTQySAlkjM5DAJUCWS/TeGYxsbMzxOAqRENj4rNDAQqhrpmUukNw4X2f6gnudqpDcukd7oVqT2pnA10huXSG/oiuh+urqKq5He+J9F2l0HOc7YSJ9HupSBG/EP/MEimq03FcnwomNGZKxrZimCD7bLb00Y6ZPtEZE/BBzw2x/GkRER4YvGVoT/QgQVpfgr39ocFnEx+TAYqpHzcLv9A9Y6cLa57YOgAAAAAElFTkSuQmCC" + }, + { + "name": "Looks like the doors are sealed shut.", + "description": "You fly up to the \"boat\" and find that all the doors are locked tight, and welded shut. You think you hear.. music inside? There is a welded vent, too. You reckon you could force it open if you hit it hard enough, but it would be less risky to unweld it using a welder.", + "choices": [ + { + "key": "choice 2", + "name": "Try to force the door!", + "exit_node": "You destroyed the drone.", + "delay": 10, + "delay_message": "You begin forcing the door.." + }, + { + "key": "choice 3", + "name": "Try to force the vent.", + "exit_node": "The music grows louder..", + "delay": 20, + "delay_message": "You begin forcing the vent.." + }, + { + "key": "choice 5", + "name": "Fly away, no chance in hell of getting in there..", + "exit_node": "FAIL", + "delay": 5, + "delay_message": "Moving.." + }, + { + "key": "choice 14", + "name": "Unweld the vent.", + "exit_node": "The music grows louder..", + "requirements": [ + { + "quality": "welder", + "operator": "==", + "value": 1 + } + ], + "delay": 5, + "delay_message": "Welding.." + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAMAAAD0WI85AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABRUExURaWMpOUAD/8AAKaNpaaOpaePpqiQp6mRqKmSqEBAQFRPVGddZmVcZeDg4PgFFvwAB4CAgP0ABfwACPsBDP4AAjAwMP/YAMHBwUz/AOIBGOQAEo23xmoAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHASURBVHhe7dwJT8MwDIbh0q5AYWNc4/r/P5TYcdWGmildDE2i75HoMWGJtymHkNYG8nNVAR/C26LVF9JO+KXSTCF8QIoP4aVwm6iQTvbZCEJcghrSNZ3n9nRKdk3XX/c3u/7Wf87WwhDaDO0Q5+5+v+fpLAQh3HJoD9EGns5CGEIldYS4TfStRXg6Cz9DHo5JKyJ9K8hgslmIN6SEDI+rWZVMIc7T88ur28m1WjqN5HxxPXMIGQ1ysaOcZEhc0GFWUnHIKjIkyl2RSkMu6rAqWYacJz+2CJ3KkJdZiFzsKAiZ+6uQkNxGRF4JyJCHFbFQccg5i1tMhry8QhIgxAJCFAixgBAFQiwgRIEQCwhRIMQCQhQIsYAQBUIsbBXi/3fhD2U8zWYhbw5CFAhRIMQCQhQIsYAQRUSI/4UeKDREdu/CZSAk9N8hS/hmDyFEgRALCFEgxAJCFAixgBDFqhD/dxYfyniarUJmEBJAiAIhFhCiQIgFhCgQYgEhCoRYQIgCIRYQokCIhYpD6I3t8tSE8c36kXIL+XD4QRb8+IQVMTmEiE93PIXwB4XQEv3mS0Y3xyFz/uujg6JWxNK2IU3zDauvUlylzN3TAAAAAElFTkSuQmCC" + }, + { + "name": "The music grows louder..", + "description": "The music gets louder as you enter through the vent... maybe you should turn back?", + "choices": [ + { + "key": "choice 6", + "name": "Continue onwards!", + "exit_node": "You fall down!", + "delay": 5, + "delay_message": "Moving..." + }, + { + "key": "choice 7", + "name": "Turn back.", + "exit_node": "Looks like the doors are sealed shut.", + "delay": 5, + "delay_message": "Moving.." + } + ], + "image": "default" + }, + { + "name": "You fall down!", + "description": "As you are crawling through the vents of this Space Yacht, the vent gives way! You're dropped into an empty room, completely filled with plasma! There is a desk and filing cabinet in here, along with a window observing the main portion of the yacht. The music is deafening at this point, it sounds like a horrible mix of sea shanties and EDM. ", + "choices": [ + { + "key": "choice 8", + "name": "Look through the window.", + "exit_node": "A rockin' party.", + "delay": 0 + }, + { + "key": "choice 9", + "name": "Fly outta of there.", + "exit_node": "The music grows louder..", + "delay": 5, + "delay_message": "Moving.." + }, + { + "key": "choice 10", + "name": "Rummage in the desk, using your key to open it.", + "exit_node": "Drugs and cash!", + "requirements": [ + { + "quality": "HASKEY", + "operator": "==", + "value": 1 + } + ], + "delay": 0, + "delay_message": "Rummaging.." + }, + { + "key": "choice 11", + "name": "Take a sample of the atmosphere.", + "exit_node": "The atmospherics scan", + "delay": 30, + "delay_message": "Taking sample.." + }, + { + "key": "choice 13", + "name": "Trash the place, fuck the police!", + "exit_node": "You wrecked yourself.", + "delay": 30, + "delay_message": "Trashing the place..." + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAMAAAD0WI85AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADAUExURbyA1eUAD/8AALyB1b2B1b2D1vgFFvwAB/0ABfwACPsBDP4AAr6C2MaJ4NRH7NSU7buA1bp+0rp90bp+0bt/07l8z7h6zbp80bp+07t/1Lp80Lp8z7uA1Lp/07l90bl+0rh80Lh80bd6z7p/1JAiVZAkWZAoX5AnXZAmXZAlW5AkWJA6fpA7f5A4epAnXpA9g7l+07Z6z+WQVZZar5BVqmYqf5BOnZA2dzPlBuIBGLx+0rx80Lx/1L13yr14yuQAErVxUskAAAAJcEhZcwAADsIAAA7CARUoSoAAAAJ8SURBVHhe7dzZctowFIBhky7pElo5pkGkS9qgpBvdoVva5v3fqkfyQciRYbxISPKc70IxxpH9I0iYzISMxGc0AGWIGpM2vJCDDbUriFv4tYNNiNqQAobczu7gVmtGiFoKGAKG9FAJgYSBhKhlGUAItqhdqamGyJJhhKhlUbtSczPk7mH6IWuph4B79x88lK+SFOmQ1LUMORoHdYSXUaNlyPhRUGO8jBqtQx4HRCE2CnGCQmwU4gSF2CjECQqxUYgTFGKjECcoxEYhTlCIjUKcoBAbhTjhNCQodyGD+dtvvCgkNhQSGwqJDYXEhkJiQyGxoZDYtAph+XExeXJyMp0onM9ms6KYnj49neZ5XhQ555MZm8A25wXn/JizjDH2DDb5cziWwU0YWDlZ+ZXl+YuzM85fvjo/53PA2HyuDmincYi46eLy9Zu37/CGB5ca7jDgNZmahoj3i2h86BEiBE4Sgx4rEllH55CoOhZi0TUkso7uIR9xiih8gke1+4pEpUcIPhiW8o79PPH0WfqG4MOh4Zx69E2fBTZahnz+gsqL/lpR7lODPoVX+iywIb5py7XRcpmpcQe83iqcU+6Xm97ps8CGwAszNQ6pI+eEH4Z4y69KyMo2Wq0yNe6wI0QuR4AQfPKbGr/Y65RPq4RCtsOZ98BJCE5hKe+gkFYoxEAhLlGIgUJcohDD7hD8He+dOiOQb/Dwwky9Q+AUeJhfvkOE+I5HeSZ+rE9a/8j1DNnTcoDNivgI2V+H+RYc91Q1CtkOj4hAk5AkDDhEf6RQWv/ZXhPyE8gS+ekP6cRgCPr1O7vahBxkfyDk6u8/GLe6xm8NToWY9O60VmQ4suw/MDqeSvYBdn8AAAAASUVORK5CYII=" + }, + { + "name": "You wrecked yourself.", + "description": "In the midst of trashing the place, a filing cabinet tips over on you, crushing the fragile, expensive drone. Nice job, idiot.", + "choices": [ + { + "key": "choice 15", + "name": "Shit.", + "exit_node": "FAIL_DEATH", + "delay": 0 + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAMAAAD0WI85AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAD/UExURTg4OJ2dnYCAgJOTk+Xl5eUAD4WFhYmJibS0tIKCgv8AAIeHh9jY2Do6Oo2NjTs7O62trYODg5+fn5eXl4GBgYaGhv/YAJKSkrGxsTk5OZGRkampqa+vr4uLi7+/v4+Pj9cLC+4EBP///5ubm5WVlcXFxcfHx/IDA+0FBeYGBvgBAfv7+6GhofPz8/7+/pmZmaOjo6WlpdXV1aenp46OjpaWlqioqLOzs6urq7q6us/Pz8zMzISEhLGwspCQkHl5eZycnFNTU6CgoKSkpJ6enmJiYqKioqampqqqqkNDQ3t7e39/fzPlBtMFEzk3Nzo3Nzg3Nz42Nj02Nt4CEG1tbWz50i4AAAAJcEhZcwAADsMAAA7DAcdvqGQAAAT4SURBVHhe3ZwLW+NEFIanp9rKUlkKRaHctOAdLbjKoq6Kuhfv6+3//xYzc74ktCzJzJxvbR7f7pOGPGTmvMlk6PZr6tLpSf8VHn3pod3/Gq4H0+TV/wEqEpaxDIav4VDSGK6h7WxyRJy8BNB0NpXIvZqwqQlZH5Hpq8jrYZlFLRJWPO0iQ0H/NDZU5H7xyOSGSDgVxaJdZJMuImM0nc2CSKEQJeJkK/ROQUW20XI2iyLhtESITCah9503zLwJkV207PbwnMqCCFzCpkZ6ofupoBoDernt15PW/b29Qib9UlkU8SYxIk4OfP8EEVnzDWHSsrAkEk5L2NSMHAaRnSPlAGyBY4DLoJHQEF/krbdVZDabuZlfAbpebdnA2LJzEkRO0W4+N0RKVCQ8asK6t1N29Rpt5Z2Cdz34+Q5kE+36Dupu/drChgZqkYL33v/gQ3+VFAQR34g/M9XZqVvVI0nioBpZLzh61VMzlcgSWry2W66Fh7crmESekii2F0Rm7qOiDz2CYQu6bOZOETx8Y4sP5YwpMlkSmbmPQ0foMMajWSR4+Gf9qVwGZBNVEJCptlmLfOJ7xrr/185dIq0wXwHLPhr11fviyyNYibS7ZIscE8fW8l+R9rJvky3ieCLzFYvMUYeZ8WpFxrRTIodo0kK+yDlP5AJNWsgXcbKPQqwsj6wsLCIDFGKkt2qRPmlsra9ahDUByzraM2ES6aMUE5/KGdozYREZcE4JZWSZRPYpIherF3FyjmIsDLsgMkYxFmSA1myYRLYZY4sU8phEKBMwZ2RZRY5RTT4POiGybj8lCBTM2EQI+YI9UFBsIk7OUE82MkdTRqwiIV+wUAcKNowiJ9axdSNQsGEUcbKLijKZdkZkAxVlQggUFKvIoXFsEQIFxSpyYBUpAwUrVhEnn6GkLD5njSy7yATJUy5oxoxZZEsucXQjOTo6wlpBh0Rs+UIdKFghiAxRVA51oGDFLqIBbya0kUUQeWgQuRUo5GMXseQLtwKFfAgiVb6ACTUK7LGBNuwQRKqAdxz92ZTyrT1KoKAQRJxcaVnFZIpC2yhHI29kcUTKfKEfeUrKjIgTKCgMkWk5tkayhlKbKc0HHROp3966wqXcxkP9dU6goHBETrWwVDiBgkIRyc0XiCOLI3KRJ3LeOREnF6gtCVKgoJBEsgJeUqCgcEROs8YW9f4XjkhevsAcWTSRKYpLgBUoKCSRnHyBFSgoJJGrDBFWoKCQRHLyBVagoNBE0gNeVqCgsETmyWOLFigoLBFXvqKNhhYoKDyR1HyB9yZjgCaSfAMZLVBQaCKbifPWFi1QUGgi6XdZYj8STJFLvb0nii86K1Ic4t6X8XwlzP+wFzBF0nhEfYXCEymmrUe4ZSyGE+lPsCMHnkjq9FvfxkNhJSJf+8U3nRbB+G/D/yoxUvAQRfyH0KLe/NWwjnKPQg1P5Fu5Ho0OJii2gUH43PNczrEjB57IdyGnxthpwmuMRt939i+7kx+0xDged1gkKac+7a7IcZVcRbBDnrSYIk9SPoR2Qp60mCJOnqLKCJ6RJy2uSMJb2T+SRxZV5KefUWUET1++SPWVQrizPYFfUGUE7EnrRSK/FngT/+0PaTLSQ5mtXNd3fpOACPjtd/e8Frnn/ihEnv/5V7G8k7+x68oJIjepNqefkX/k8hrfw9HM4wfsSYsMXk3FgD14OPcv1NBUWmnJzVYAAAAASUVORK5CYII=" + }, + { + "name": "Drugs and cash!", + "description": "Rummaging through the drawer, you find that the person who lives in here stores all his drugs and cash in here too. Good for you!", + "choices": [ + { + "key": "choice 17", + "name": "Head back with your newly acquired things. ", + "exit_node": "WIN", + "delay": 30, + "delay_message": "Stealing..." + }, + { + "key": "choice 18", + "name": "Take one last look around the place.", + "exit_node": "You fall down!", + "delay": 0 + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkBAMAAAAxqGI4AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAwUExURW4WTuUAD/8AAPgFFvwAB/0ABfwACPsBDP4AAuex/6526uOxlKt4fUz/AOIBGOQAEqIMSWUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHFSURBVGje7dq/bsIwEAZw15ubpX2DyFLHqkvfqe/BE7AeU9dkyu68gTN1r9RHqX0mf1oMMeIuA7pPQooE4sd3FwJD1B3lmT0BeeJu8bAZom0MNxKPNDeia2X5EbsJUm+A1HYLRNs7WXyt3tmReMSO2JePHfc3XjFHEEEEEUQQQQQRRBBBBBFEEEEEiQHgR+DQ7HkRE4y2bYAVUdDG3KasIbEHBvZciMFZtbeWuYhAmFUzVgnKapnKpXTFCOCoDkcAR7ZiODdgfCFiAoElUpOy9fcBiA+vihAD45DaafXrSj9454LiypDZWCbsCDD55VT94LBJV4TAFJwYLNskLXepqcKwfDFi5o8K6f3M0sGaTRbB1f9fyRVXYYD05tN6TspE4EZELaeIazHY93F6Gk8sd7p3mt8T8xfpWJCUakhnsCdHPudD/Lr7zLQIm1R9Qk6L0CHV8bqVKUKHjEamCBni+vNFiJBpVtkiBEj8oRpr5ItQIMMc7zp2xLv8SwiQ/nINEmQ8ec/VoEGqCyunRc6OigrJ/NMiR9YjiCCCXIPEe3C0ZUZev79qrVmYiMSbo34SUutYZ5k3oluvUnBcWjPexcAbpX4BKG91c2myRoIAAAAASUVORK5CYII=" + }, + { + "name": "A rockin' party.", + "description": "Looking down through the window, you can see up to 20 plasmamen dancing on a disco floor. They look to be enjoying themselves, and none of them have noticed you. Oh, hey! Theres a key on the floor right next to you!", + "choices": [ + { + "key": "choice 16", + "name": "Swipe the key and head back to the desk.", + "exit_node": "You fall down!", + "on_selection_effects": [ + { + "effect_type": "Set", + "quality": "HASKEY", + "value": 1 + } + ], + "delay": 0 + }, + { + "key": "choice 19", + "name": "Tap on the window!", + "exit_node": "Weak.", + "delay": 0 + } + ], + "image": "default" + }, + { + "name": "Weak.", + "description": "You weakly tap on the window, and nobody hears you through the blasting music.", + "choices": [ + { + "key": "choice 20", + "name": "Oh well.", + "exit_node": "A rockin' party.", + "delay": 0 + } + ], + "image": "default" + }, + { + "name": "You destroyed the drone.", + "description": "You smash into the door, and your screen goes red. Looks like you managed to destroy your drone, nice job. \n\n\nIdiot.", + "choices": [ + { + "key": "choice 21", + "name": "Fuck.", + "exit_node": "FAIL_DEATH", + "delay": 0 + } + ], + "image": "signal_lost" + }, + { + "name": "The atmospherics scan", + "description": "100% plasma, jam packed with it. This is definitely the home of some plasma-party-people.", + "choices": [ + { + "key": "choice 22", + "name": "Huh.", + "exit_node": "You fall down!", + "delay": 0 + } + ], + "image": null, + "raw_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAMAAAD0WI85AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABRUExURWJoaOUAD/8AAPgFFvwAB/0ABfwACPsBDP4AAhcXFzs7OykpKVFRUQAAAKb//wD//wuKkf/MAP9mALAAADAwMP8AM/vZTwCwAEz/AOIBGOQAEso55t0AAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHDSURBVHhe7dzZbsIwEIXhdK/j2A6Fru//oD0zMYVuEr0YOhOdXyJxg5DyyXbhBgbmr4sVtED0GLr1QS4P6aVoHSA6kMJDdCpwWAEEhJVAdFrWsLQWi16K1meISNYB0WnRS9H6Crm6jg/ZFx2Cbm7v7mWXROwDEj1CvEWItwjxFiHeIsRbhHiLEG8R4i1CvEXIyaV+No6QkyPkbxFycmlc6n9aZQ4BIUvWFGvIOE6pSGmylRhBdBKQOmpttVpLbCA5466XUmltnufWCjZLf9oiI8iky0mrZa61zg1zYikxgoxYTxLWVduUhrW1gSSHhJRWSgPkoTYMG85BIU0CZIsT9knbhp0RmQdsdYFI2xYTsty97JGdjnYx98jxf61lkeGdJI+5P2+QESRP/R0xdw5K2dBhBBly6uWcyuP4ND6Lw/KDsBFkn0rkAyOOpg5rCCQQYHOAkUJDkC4xWwU6A+Q8EeItQrxFiLcI8RYh3iLEW4R4ixBvEeItQrxFiLcI8RYh3iLEW4R4ixBvEeItQrz1HSJfbO+/mhDpm+0/QF6QGOTXH+JgOqT3ivEBog+ByBT91lt/6b+nkOOW+5NBqBlZT8PwDsHEFJFntJj8AAAAAElFTkSuQmCC" + } + ] +} \ No newline at end of file diff --git a/strings/exoadventures/tree_in_the_middle_of_space.json b/strings/exoadventures/tree_in_the_middle_of_space.json new file mode 100644 index 0000000000000..f06b1d2506273 --- /dev/null +++ b/strings/exoadventures/tree_in_the_middle_of_space.json @@ -0,0 +1,356 @@ +{ + "adventure_name": "There's a tree in the middle of space.", + "version": 1, + "starting_node": "Tree Start", + "starting_qualities": { + "Confusion": 0 + }, + "required_site_traits": [ + "in space" + ], + "loot_categories": [ + "research" + ], + "scan_band_mods": { + "Exotic Radiation": 10 + }, + "deep_scan_description": "", + "triggers": [ + { + "name": "Confusion Trigger", + "target_node": "What is wrong with this tree?", + "requirements": [ + { + "quality": "Confusion", + "operator": ">", + "value": 30 + } + ] + } + ], + "nodes": [ + { + "name": "Tree Start", + "description": "Camera online. Visual signs detect a fully grown, seemingly biological, and live tree located in the middle of the vacuum.\nSensors indicate it is not oxygenating, but energy is being collected via passive solar light from the nearby star.\nBaffling.", + "choices": [ + { + "key": "choice 0", + "name": "Ignore site.", + "exit_node": "FAIL", + "delay": 10, + "delay_message": "Leave this for the botanists to figure out." + }, + { + "key": "choice 1", + "name": "Begin sensor scan.", + "exit_node": "Biological Scan", + "delay": 10, + "delay_message": "Lets get some data." + } + ], + "image": null, + "raw_image": "data:image/gif;base64,R0lGODdhyABkAHcAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAAACwAAAAAyABkAMQAAAAAAAC15h2QtxeAoxVykROizRpxjxIAcgAAWwAAgAAAUAAAZAAAZgC0tLTKysr///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF/2AgjmRpnigqrMMqtCvhyvFs17hQuPva67ygTwgcGovA3a61BLaeryj0mapar9isNjCNylpfgWwsLpPPZqBst066le84fB5nRpvdvGDL7/v/JmBeaWiFhIeGbGqLio1ljm2CklKUA4CXmJmaAYidhp9mBmOiYqQEpqZlk3qbra6vsCOenqijtgIHCLG7vL29CQoNCsDCxMPBxw0MxsTLyMLCCwq+1LuVe9VZ0M/c297d4NvZ41vXeueU5N/r4e3o79jkvOb0UAe4+Pf6uMjS7P/TtAAcGKyeQXhT5ClcSKIdwYcHIyKMx7CiwocOwU3cWMmiR4YYQzqM+LGkSQXSUv/2y0jQH0qUJmNqoRSm5ixQbXLK2UmnJ886ToIilFltkk2cNw9B+umz6U87UOsRNXk0KdKrVrOSWWVwqlc+WHHWKnWqrKizZV+QGrA2yte31YDJPTZXrrMEzhbkPSYNWF+4gAFBeICJGTLDxVi2C5yNIxSQikVu48i4j8TLT1i2khwOM+bKVSQaeGLgQOnS+fIBwkiCszfPjqWAnn0ism1hsXO/oM279e3fuqmYeAChN++XKqP9BujP+EcXMKLkoFFm+o3qSH5oJ7L9SPfs3IlEHw/dhfNyLMpbX4+duvvw3uGDj08fCfn0+KVTPI9Ff/T37AHY3nXfFSifgfX9cJ//f/vxh95/1wkoYYQU5oDgfBh+t+B9DsJS4YABfiiDAWWQOGKJKJ6oYidcDdVhUVrFGFZWRl32IlEy0nJLWjyu1VYYrNxY2YxXjZUWW1+0FaSQDiaQ4z0y3OMXX3wxaSUKiWWZTF5c+sVASl8iB9OVbxFH3CVabmOMa9yQ6WYAbHL2Zh+UVRTnbV3NyUlwu120HEuw9WlloO+Mc+c3fCbEH6ES/Ybmn9AwWmdvnu1jaWoHpMBmFYdK+tmLiQqKCaTAebqRnh4d6o6pEqHqEan/sOqiqxbBumqoig42GK0WqUqQY7zCtZKvDgXL37AvEbvNAsaeJ6Zy0JKqErPNYpIj/xqOOKUtU9xu6+1OUekpCJFpLPXtud2mu21UTbhKLraPqCsvuvQ+FdQSqI57bbnxtlHvv/OCK5RsqO4Lr78BA6xwulDR+m4n5iYs8cJNVNyguwYzEi/FE3dsR7UpZJzIxh1z7FO4IJ8gMr860WuAEi/rEHMBM88s8ccpn/AwVhEjTDPMQMsc9M9Cx9HyIJbknMLOEJNC8xpOR/3zy1RPbXXVWD8tM9Rcq3Kx0lesvKORZz2NFtVmnWJ2j2NLAnYsiJCd9tlzz7122XTnnfbb2dxUi96A1x044GDwnc0Zfwuu+OCLK274c4UkzvjkZj3+ldxkUY5AApt3noDlldF1zP/YBAyzuemcp4665wGBDlcDeCUA+zKzyx77Arfnvjnuy+C+u+uADXPXMsTHXvzxxid/PPC8OEAYmnLBPoz0ttMee+3YXx/958y/oqsDl4hel/jkjz9+Md17Rf3620/fvu3uxy97A+lPVT75ic2lJWLDWD7AE/97QQCT1qv3sU9+6ytGAxTIQL7FpiLJiCD/JggOYJxKXAPMIAA3KCp1MHB6H5wfCEcowgRoUIAc/F8L3CSrr/EiGAsc4QKhEcMZwpCG0DihClHIQxVeaYdA1KEQAWioD9pQhEesoRJTOMQe/k9ITQwiEzloQ/q9sIpKxOENt9iAKHpxgw76ohOnKED/LWJRGJrIIhfVWEUxSnGMPDQOGd+4wwME0I76COAC2MhHNPZhjWbk4xzd2MTeNBGPuBiAPg6wyEbiIlMiaEBKYrjHQOJQG5YE5BbpSMhByvGQiixNKBlpGlI+8h5a0OQZg2ECVfYRhoOMJRx36JxZ/k+UbMGjKEupyERi4pUzjGQmV9lGWXLSkxiMoy+ICcxmNsCWxvRi/VYzw2pa85rYvKYKt8nNbnrzm96c5h+G6UwudhKacBTnOLPJznYuEJzwjCc41blOctoTlug8JjTpiSZ3+lOb3LTj/wSqSHlycwQPeB4//8jMe2KRgwJNZDQJaCaFLlQge6ykJP/ZTo3iYbGgHx3oPC+6CZRQspwNNakwEMnSE5L0FRndaEwXqFGO0lSmG32pQmLK043e1KYz7KlO5dHTouKUo0YdKjmMalSk4jSmSh3HAqb61KpqtKZBtWpOo1oNpmoVq9dkKrWMEwIAOw==" + }, + { + "name": "Biological Scan", + "description": "You attempt to scan for clues regarding the tree's nature. It appears to be a fully mature oak tree. \n\nApproximated height is 13 ft, 6.4 inches. \n\nSubject sees no sign of an outer coating or otherwise layer protecting it from the void of space.\n\nSubject's surface temperature is 293.7 kelvin, as though it were sitting indoors.", + "choices": [ + { + "key": "choice 2", + "name": "Check Sensor Integrity.", + "exit_node": "Its Not You...", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Confusion", + "value": 5 + } + ], + "delay": 50, + "delay_message": "This can't be right." + }, + { + "key": "choice 4", + "name": "Attempt to take sample.", + "exit_node": "Sample Taken", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Confusion", + "value": 3 + } + ], + "delay": 40, + "delay_message": "Snip snip." + }, + { + "key": "choice 6", + "name": "Examine Tree Roots.", + "exit_node": "Examine Roots", + "delay": 10 + }, + { + "key": "choice 9", + "name": "Sequence Sample Radiation with background noise.", + "exit_node": "Background Analysis", + "requirements": [ + { + "quality": "Sample", + "operator": ">=", + "value": 1 + } + ], + "delay": 0, + "delay_message": "This can't be real." + }, + { + "key": "choice 40", + "name": "Leave.", + "exit_node": "FAIL", + "delay": 0 + } + ], + "image": null, + "raw_image": "data:image/gif;base64,R0lGODdhyABkAHcAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAAACwAAAAAyABkAMQAAAAAAAC15h2QtxeAoxVykROizRpxjxIAcgAAWwAAgAAAUAAAZAAAZgC0tLTKysr///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF/2AgjmRpnigqrMMqtCvhyvFs17hQuPva67ygTwgcGovA3a61BLaeryj0mapar9isNjCNylpfgWwsLpPPZqBst066le84fB5nRpvdvGDL7/v/JmBeaWiFhIeGbGqLio1ljm2CklKUA4CXmJmaAYidhp9mBmOiYqQEpqZlk3qbra6vsCOenqijtgIHCLG7vL29CQoNCsDCxMPBxw0MxsTLyMLCCwq+1LuVe9VZ0M/c297d4NvZ41vXeueU5N/r4e3o79jkvOb0UAe4+Pf6uMjS7P/TtAAcGKyeQXhT5ClcSKIdwYcHIyKMx7CiwocOwU3cWMmiR4YYQzqM+LGkSQXSUv/2y0jQH0qUJmNqoRSm5ixQbXLK2UmnJ886ToIilFltkk2cNw9B+umz6U87UOsRNXk0KdKrVrOSWWVwqlc+WHHWKnWqrKizZV+QGrA2yte31YDJPTZXrrMEzhbkPSYNWF+4gAFBeICJGTLDxVi2C5yNIxSQikVu48i4j8TLT1i2khwOM+bKVSQaeGLgQOnS+fIBwkiCszfPjqWAnn0ism1hsXO/oM279e3fuqmYeAChN++XKqP9BujP+EcXMKLkoFFm+o3qSH5oJ7L9SPfs3IlEHw/dhfNyLMpbX4+duvvw3uGDj08fCfn0+KVTPI9Ff/T37AHY3nXfFSifgfX9cJ//f/vxh95/1wkoYYQU5oDgfBh+t+B9DsJS4YABfiiDAWWQOGKJKJ6oYidcDdVhUVrFGFZWRl32IlEy0nJLWjyu1VYYrNxY2YxXjZUWW1+0FaSQDiaQ4z0y3OMXX3wxaSUKiWWZTF5c+sVASl8iB9OVbxFH3CVabmOMa9yQ6WYAbHL2Zh+UVRTnbV3NyUlwu120HEuw9WlloO+Mc+c3fCbEH6ES/Ybmn9AwWmdvnu1jaWoHpMBmFYdK+tmLiQqKCaTAebqRnh4d6o6pEqHqEan/sOqiqxbBumqoig42GK0WqUqQY7zCtZKvDgXL37AvEbvNAsaeJ6Zy0JKqErPNYpIj/xqOOKUtU9xu6+1OUekpCJFpLPXtud2mu21UTbhKLraPqCsvuvQ+FdQSqI57bbnxtlHvv/OCK5RsqO4Lr78BA6xwulDR+m4n5iYs8cJNVNyguwYzEi/FE3dsR7UpZJzIxh1z7FO4IJ8gMr860WuAEi/rEHMBM88s8ccpn/AwVhEjTDPMQMsc9M9Cx9HyIJbknMLOEJNC8xpOR/3zy1RPbXXVWD8tM9Rcq3Kx0lesvKORZz2NFtVmnWJ2j2NLAnYsiJCd9tlzz7122XTnnfbb2dxUi96A1x044GDwnc0Zfwuu+OCLK274c4UkzvjkZj3+ldxkUY5AApt3noDlldF1zP/YBAyzuemcp4665wGBDlcDeCUA+zKzyx77Arfnvjnuy+C+u+uADXPXMsTHXvzxxid/PPC8OEAYmnLBPoz0ttMee+3YXx/958y/oqsDl4hel/jkjz9+Md17Rf3620/fvu3uxy97A+lPVT75ic2lJWLDWD7AE/97QQCT1qv3sU9+6ytGAxTIQL7FpiLJiCD/JggOYJxKXAPMIAA3KCp1MHB6H5wfCEcowgRoUIAc/F8L3CSrr/EiGAsc4QKhEcMZwpCG0DihClHIQxVeaYdA1KEQAWioD9pQhEesoRJTOMQe/k9ITQwiEzloQ/q9sIpKxOENt9iAKHpxgw76ohOnKED/LWJRGJrIIhfVWEUxSnGMPDQOGd+4wwME0I76COAC2MhHNPZhjWbk4xzd2MTeNBGPuBiAPg6wyEbiIlMiaEBKYrjHQOJQG5YE5BbpSMhByvGQiixNKBlpGlI+8h5a0OQZg2ECVfYRhoOMJRx36JxZ/k+UbMGjKEupyERi4pUzjGQmV9lGWXLSkxiMoy+ICcxmNsCWxvRi/VYzw2pa85rYvKYKt8nNbnrzm96c5h+G6UwudhKacBTnOLPJznYuEJzwjCc41blOctoTlug8JjTpiSZ3+lOb3LTj/wSqSHlycwQPeB4//8jMe2KRgwJNZDQJaCaFLlQge6ykJP/ZTo3iYbGgHx3oPC+6CZRQspwNNakwEMnSE5L0FRndaEwXqFGO0lSmG32pQmLK043e1KYz7KlO5dHTouKUo0YdKjmMalSk4jSmSh3HAqb61KpqtKZBtWpOo1oNpmoVq9dkKrWMEwIAOw==" + }, + { + "name": "Its Not You...", + "description": "After re-connection is established, your sensors appear fine. Tree has not moved in the slightest since last observed. Temperature has fluxuated 0.2 kelvin upwards, as expected of a plant under direct light.\nLets try again.", + "choices": [ + { + "key": "choice 3", + "name": "Restart biological scan.", + "exit_node": "Biological Scan", + "delay": 25, + "delay_message": "God damnit." + } + ], + "image": null, + "raw_image": "data:image/gif;base64,R0lGODdhyABkAHcAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAAACwAAAAAyABkAMIAAAAAAAD////v7+/h4eEAAAAAAAAAAAAD/xi63P4wykmrvTjrzbv/YCiOZBkJaJqaLKOubSy7b23fODxPOb7/m4FASBRofEBeT1W6JU3DYaRIjQqfkCWKg1xoddjSQEgQlM/mdDkc0Fa+8PiWLSIM7Pj73RxOlVF/ITlKNXSGDXp5iiFoaBlqfpFyk12HMgSYmZkfmppmmJ8gjXwUf2lglqmqq6ytrq+wsbKztLW2t7i5um2Ug669uyBCKMNRc4Q9qXHBJlVUFMbRV6pfR04ONszQzlZRlm7IveLaimZ7eJAESUsfyVnX2hfqv/Nv4vfZ8RWJ/Hkdn6NGtRsFyEg4VLwK6UvVqROdgMcWSpxIsaLFixgzatzIsf+jR1gIP7YbN6uaSAn4wFGbdNJFsZcpFRqS09IBt2kW3CkzWdNmNyIYpIWkw9Nevgbwat2ccNPbTnbWjtKQWfGnVZwzoQaNqVXXkDFWGx3qeoIry2CJzJ05p6aPTpRmfSXV1a/u2jM7TuldA8LXO6kXFemh5ihn3Lc9A9h1ZaqxpMNkK9rl5+EMKIGM9g5FSjJjv4aVOwEc/Q8iXx6ODSo5ualOQ9Bs0qVJTLu27du4c+vezbu379/AgwsfTry48ePIkytfqHp5xhfLadby+1soMJDLbhchlqIYpVbXa14dz718NMBEz/ZsWsU8zMhY1CdmL+VE+/ObgWSvPR4rj/f/xqxUFG1fVfHIUgLCN58zR5ABySpwbAAdZ1TNMgZYF9YHjWmsDAgNeglNqFSBJJYiG14JUmcYiHNZWJcE6HD4FGIrVuiFjROxJeNYChoFWUS5lBPjOTO2iM2PKuki2F1qfdPjjUjSeMuSk82Uml4fRimlLVR2OUAYBGHpo5aV4OJll1iYRkqWhag4FY75WXhmlTOoedo/ZVKI40Jz+pOEnX2ZsqcCeVJ0piEQieJhiEx0RBkrd6J2IiQFVXqPbZjEYqdmll6Jz0avtUaCqEFtaiqnekmHUagOhSZapvJMeioanXa2KqukZoCrJoruCKNmCngKZGC7urprPRKq+QZBP2XFKRGunBwL6waZjIbiBInu9hoj0o5wraRiOadrsX1kK24HvBK25rnstuvuu/DGK++89NZr77345qvvvhYlAAA7" + }, + { + "name": "Sample Taken", + "description": "You collect and project a small sample of tree bark off the plant. The instant that the bark is removed from the tree, as though it suddenly remembered what it was, the moisture content of the bark freezes over, and implodes into small microparticles of splinters.\nSmall radioactive signature detected.", + "choices": [ + { + "key": "choice 5", + "name": "Well that was... unexpected.", + "exit_node": "Biological Scan", + "delay": 0, + "delay_message": "Maybe something else might work better.", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Sample", + "value": 1 + } + ] + } + ], + "image": null, + "raw_image": "data:image/gif;base64,R0lGODdhyABkAHcAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAAACwAAAAAyABkAMIAAAAAAABSLiiPVjtyRS9mOTFbNyYAAAAD/xi63P4wykmrvTjrzbv/YCiOZGmeaKqubOu+sDUQxTDbeK7vfF/3saCwcxPcCMckcqlM2pDPnnRKrUqH2IdAYDBwu+Bwl2Agm8sE8iy9Zrqbb2Z0bq3b74W8Xp+1bP+Af2KDhIVlh2eJaUtsi3CPcU43d5QDe5cFfSKBnJ2en50Ff6JboqYCBWiLq46tkK+RUJOTmJmatxyguqSovae/vsGirK2NjU21trjLQp+8wNDCwnmo1MnM2Nna29zd3t/g4eLj5OXm59o5Rjo/QOjvLaKwkrKylfdW8FleXImIBm0CxppXD4c9fAgT5tCn4Iuhh4fG/DNTjNHAi4/oKERYq/+cro8gPYXxR7EkK4EEUxbUOCVZHoabQoZ8NkqaKWI4BR7DGKcjTG4yaZaySRSatQLGfP5ciqHX0GhQpzGdSrWq1atYs2rdyrWr169gw4pVsc7G2LMLkM46SOkl2nc86W3E97aFgLh4Dc7dq6NuhC0TVVlUOY8l38N9szocREYiyYqEMdJZibjylW+AIRIiqUowyrwDJ+u1XMelsm6CNKtmHHiRZ8iuQGecVdklZpm4V3NWREznYNmhaeNRCg638eOBhrUWjPP3zshNDNuwzRW5dZrQmms3BhzSNb8SrIPCXvTudtjzMIEngVxo+ajDtKOkvt5u0Jrw3/s6yqd+Nk7/7uUn4FH+VaWfKAUmqOCCDDbo4IMQRijhhBRWaOGFGGao4YYcdujhhyCGKOKFRsgxy4jaBEeZOyi60J2JK1piS2ItcvDiaMIpVGMEv0GnIo46grgFbM/dKBppC004kXzo3fiGdJb51wUgiKTBnHNOzsYWkpd1xZpjrTHJnY+vQGlZOztMBUZmrO0mZo9Zwggkl/mkKc5qIy3n2nlExrnWnHROcRueYjSm55Wr+Ebmj2YeZpo4bBJqyG6Inqeon0duRF84kuJJKW98Nrkojlu2dYlHkXaq6qehNvfZqHI2+ihq4lGpKhisttpbbKNm6sN3t9Wam2q5Vtrqq5jesKk3jsI220mheuq665jJzoqqs9gGcqi0211KGLBUZYutciUZG6q3cREnlrjiPcZtTqIiAW597H5E07uuiqqehPXONIq57+6rYb81PWUTvmksuyHBTjUc1XnVmOLWjgFkG+CBNk1MsRb1kiegLxtf4KzHB4b8Qa0XA2OyfZ6QjODKywwlM8w012zzzTjnrPNWCQAAOw==" + }, + { + "name": "Examine Roots", + "description": "All plant matter has to derive energy and moisture from someplace. Examining the oak tree's roots reveals that the roots present all appear to splay out, similar to how a normal tree would. However, those roots then proceed to double back in on itself. This might suggest that the tree is obtaining nutrients from... itself.", + "choices": [ + { + "key": "choice 7", + "name": "That's fucking stupid.", + "exit_node": "Biological Scan", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Confusion", + "value": { + "value_type": "random", + "low": 6, + "high": 10 + } + } + ], + "delay": 0, + "delay_message": "What the hell kind of tree even IS this?" + }, + { + "key": "choice 8", + "name": "Obtain biological sample from roots.", + "exit_node": "Sample Taken", + "delay": 10, + "delay_message": "This is why we hire botanists on-site." + } + ], + "image": null, + "raw_image": "data:image/gif;base64,R0lGODdhyABkAHcAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAAACwAAAAAyABkAMQAAAAAAAC15h2QtxeAoxVykROizRpxjxIAcgAAWwAAgAAAUAAAZAAAZgC0tLTKysr///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF/2AgjmRpnigqrMMqtCvhyvFs17hQuPva67ygTwgcGovA3a61BLaeryj0mapar9isNjCNylpfgWwsLpPPZqBst066le84fB5nRpvdvGDL7/v/JmBeaWiFhIeGbGqLio1ljm2CklKUA4CXmJmaAYidhp9mBmOiYqQEpqZlk3qbra6vsCOenqijtgIHCLG7vL29CQoNCsDCxMPBxw0MxsTLyMLCCwq+1LuVe9VZ0M/c297d4NvZ41vXeueU5N/r4e3o79jkvOb0UAe4+Pf6uMjS7P/TtAAcGKyeQXhT5ClcSKIdwYcHIyKMx7CiwocOwU3cWMmiR4YYQzqM+LGkSQXSUv/2y0jQH0qUJmNqoRSm5ixQbXLK2UmnJ886ToIilFltkk2cNw9B+umz6U87UOsRNXk0KdKrVrOSWWVwqlc+WHHWKnWqrKizZV+QGrA2yte31YDJPTZXrrMEzhbkPSYNWF+4gAFBeICJGTLDxVi2C5yNIxSQikVu48i4j8TLT1i2khwOM+bKVSQaeGLgQOnS+fIBwkiCszfPjqWAnn0ism1hsXO/oM279e3fuqmYeAChN++XKqP9BujP+EcXMKLkoFFm+o3qSH5oJ7L9SPfs3IlEHw/dhfNyLMpbX4+duvvw3uGDj08fCfn0+KVTPI9Ff/T37AHY3nXfFSifgfX9cJ//f/vxh95/1wkoYYQU5oDgfBh+t+B9DsJS4YABfiiDAWWQOGKJKJ6oYidcDdVhUVrFGFZWRl32IlEy0nJLWjyu1VYYrNxY2YxXjZUWW1+0FaSQDiaQ4z0y3OMXX3wxaSUKiWWZTF5c+sVASl8iB9OVbxFH3CVabmOMa9yQ6WYAbHL2Zh+UVRTnbV3NyUlwu120HEuw9WlloO+Mc+c3fCbEH6ES/Ybmn9AwWmdvnu1jaWoHpMBmFYdK+tmLiQqKCaTAebqRnh4d6o6pEqHqEan/sOqiqxbBumqoig42GK0WqUqQY7zCtZKvDgXL37AvEbvNAsaeJ6Zy0JKqErPNYpIj/xqOOKUtU9xu6+1OUekpCJFpLPXtud2mu21UTbhKLraPqCsvuvQ+FdQSqI57bbnxtlHvv/OCK5RsqO4Lr78BA6xwulDR+m4n5iYs8cJNVNyguwYzEi/FE3dsR7UpZJzIxh1z7FO4IJ8gMr860WuAEi/rEHMBM88s8ccpn/AwVhEjTDPMQMsc9M9Cx9HyIJbknMLOEJNC8xpOR/3zy1RPbXXVWD8tM9Rcq3Kx0lesvKORZz2NFtVmnWJ2j2NLAnYsiJCd9tlzz7122XTnnfbb2dxUi96A1x044GDwnc0Zfwuu+OCLK274c4UkzvjkZj3+ldxkUY5AApt3noDlldF1zP/YBAyzuemcp4665wGBDlcDeCUA+zKzyx77Arfnvjnuy+C+u+uADXPXMsTHXvzxxid/PPC8OEAYmnLBPoz0ttMee+3YXx/958y/oqsDl4hel/jkjz9+Md17Rf3620/fvu3uxy97A+lPVT75ic2lJWLDWD7AE/97QQCT1qv3sU9+6ytGAxTIQL7FpiLJiCD/JggOYJxKXAPMIAA3KCp1MHB6H5wfCEcowgRoUIAc/F8L3CSrr/EiGAsc4QKhEcMZwpCG0DihClHIQxVeaYdA1KEQAWioD9pQhEesoRJTOMQe/k9ITQwiEzloQ/q9sIpKxOENt9iAKHpxgw76ohOnKED/LWJRGJrIIhfVWEUxSnGMPDQOGd+4wwME0I76COAC2MhHNPZhjWbk4xzd2MTeNBGPuBiAPg6wyEbiIlMiaEBKYrjHQOJQG5YE5BbpSMhByvGQiixNKBlpGlI+8h5a0OQZg2ECVfYRhoOMJRx36JxZ/k+UbMGjKEupyERi4pUzjGQmV9lGWXLSkxiMoy+ICcxmNsCWxvRi/VYzw2pa85rYvKYKt8nNbnrzm96c5h+G6UwudhKacBTnOLPJznYuEJzwjCc41blOctoTlug8JjTpiSZ3+lOb3LTj/wSqSHlycwQPeB4//8jMe2KRgwJNZDQJaCaFLlQge6ykJP/ZTo3iYbGgHx3oPC+6CZRQspwNNakwEMnSE5L0FRndaEwXqFGO0lSmG32pQmLK043e1KYz7KlO5dHTouKUo0YdKjmMalSk4jSmSh3HAqb61KpqtKZBtWpOo1oNpmoVq9dkKrWMEwIAOw==" + }, + { + "name": "Background Analysis", + "description": "You compare the radioactive energy bands of the sample collected earlier with that of the nearby solar enviroment.\nNothing.\nThere is nothing nearby that matches the passive signal of the tree, or the bark, or anything similar.\nThis is really starting to get on your nerves.", + "choices": [ + { + "key": "choice 10", + "name": "Smash your desk in frustration.", + "exit_node": "FAIL", + "delay": 50, + "delay_message": "No amount of pay is worth dealing with magical plant juju." + }, + { + "key": "choice 11", + "name": "Check every known energy spectroscopy database.", + "exit_node": "Sample Match Found", + "delay": 900, + "delay_message": "You NEED an answer. You DESERVE an answer." + } + ], + "image": null, + "on_enter_effects": [ + { + "effect_type": "Add", + "quality": "Confusion", + "value": { + "value_type": "random", + "low": 3, + "high": 5 + } + } + ], + "raw_image": "data:image/gif;base64,R0lGODdhyABkAHcAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAAACwAAAAAyABkAMIAAAAAAAD///9ZVlKsMjJpampGR0cAAAAD/xi63P4wykmFpTjrDa0XXCiOZGlqQ0qsazqIXyx9Z23feKayfP/WskgwRywqCsikcslsOpGpgGvQNDo8lYt1W4I+v2CvMhUuI0/YiZbL5nxdhkJ8Lq/T6eZ8HRmHUtuAgRp6T3B2SXdNfYeJc3CCkJEYjISVYZKRQ5gajZSdi0yboqM3nnWkqKmCpgaqrq9GBrKzs7AKaYG4tji0vbu/wCW6wZpqNMHIOAM9zDw/IzEgHcVt1KOWZQ1TU4LDV2vJOAVU2GZkY5VF4NPr4SRM53yI8/Lw9uVvBcLuknqGn4cqLQIYhwy/g0fwKdSDsCEGggtDOZwoAiLFi5sAYtwoqf/XIVjeOKrqRUukjZAmGVjDGC2lSyAts7R7GW5ZM2bPNERDGSAmzWA2b/pAc+yBT0ArVeXhsC2oj5wnefaU+pNBxDJNXUAqyq6qhKthyCUR+4QLVa5ej5AFO3ZJPGw3qE5N22DsWiUD6Tm5e/cqCbk96dZtQgZip3rjwIIa90fwqrBUDAfcQ0hyilaOIWG7zIoSYryd73DLrJntNlOIO2WVQ/raYrYKW/8CCNuJbH6S8aS7vSCpq9C8gxMMzns48dudj9/WqFw2yZKvfDc38Rxzc+kiq09f/nw7b+wNwV9HuwuwSvLeL14wjz49pm3Qds7sbd59jh1Co3CQP1+8/Rr/TgnVQnxHNVDgf0QEKCBUIRxIX3sIAiigMwzCAKF/cc1H1xQTVvjXhRCqE6IosU2Q1YkiajiViin+VhthWW0l1YhevQjjafrIKI0QLP5kI2GMBWkbG2fVx1FiP/oxRl9DEiEXCDsKhuSPazFpBg5P9miSkHmlpleXbbkVUVRRTkOakl7mRg9fsJmQ5Zl2DaDmJ4XVwZkZYHbpoU726eZCaID28RYYeRFEToQVeVaYLHIwOkugk70GqWiNIeoGQJc5qumclOU5qUGWXhraFNU1yumpdoAa6ganZkrLpJ2G5qido606CKoGwDErrqaaSlKttrIKq2r/DGvHr39aF2wIgsYaduKzqyUhhbLLjsArp3BVW0Oz3OpWlrY5dCsuKOByMS635QpyLaTEGenauZ2M5y4m696B4Lz0spLuRMnt25BH9vr7b3XUChwOwbIYfBDCBSscDMIOL0xwxO5ATPHB2kVHY7oTg7RxuR0rhyFFIYv8MUYZX5xMySoDk3LLD3dXbQIAOw==" + }, + { + "name": "Sample Match Found", + "description": "After an extensive algorithm search on the controller end, you have a single match to this specific band and style of energy.\nThe problem, is that the source of said radiation is coming not only from Space Station 13, no.\nIt's coming from the Space Station 13 Research Department.\nWhat the fuck?", + "choices": [ + { + "key": "choice 12", + "name": "Something must be wrong with the drone.", + "exit_node": "Its Not You...", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Confusion", + "value": { + "value_type": "random", + "low": 6, + "high": 10 + } + } + ], + "delay": 30, + "delay_message": "Lousy piece of junk must be scanning the station instead of the target." + }, + { + "key": "choice 13", + "name": "Perhaps that sample was tainted. Collect a new sample.", + "exit_node": "Sample Taken", + "delay": 60, + "delay_message": "Lets try again, but carefully." + }, + { + "key": "choice 14", + "name": "Remember the Christmas Party.", + "exit_node": "The Christmas Party", + "requirements": [ + { + "quality": "Confusion", + "operator": "<=", + "value": 25 + } + ], + "delay": 100, + "delay_message": "Wait a gosh darn fucking second." + } + ], + "image": null, + "on_enter_effects": [ + { + "effect_type": "Add", + "quality": "Confusion", + "value": 10 + } + ], + "raw_image": "data:image/gif;base64,R0lGODdhyABkAHcAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAAACwAAAAAyABkAMIAAAAAAAD////v7+/h4eEAAAAAAAAAAAAD/xi63P4wykmrvTjrzbv/YCiOZBkJaJqaLKOubSy7b23fODxPOb7/m4FASBRofEBeT1W6JU3DYaRIjQqfkCWKg1xoddjSQEgQlM/mdDkc0Fa+8PiWLSIM7Pj73RxOlVF/ITlKNXSGDXp5iiFoaBlqfpFyk12HMgSYmZkfmppmmJ8gjXwUf2lglqmqq6ytrq+wsbKztLW2t7i5um2Ug669uyBCKMNRc4Q9qXHBJlVUFMbRV6pfR04ONszQzlZRlm7IveLaimZ7eJAESUsfyVnX2hfqv/Nv4vfZ8RWJ/Hkdn6NGtRsFyEg4VLwK6UvVqROdgMcWSpxIsaLFixgzatzIsf+jR1gIP7YbN6uaSAn4wFGbdNJFsZcpFRqS09IBt2kW3CkzWdNmNyIYpIWkw9Nevgbwat2ccNPbTnbWjtKQWfGnVZwzoQaNqVXXkDFWGx3qeoIry2CJzJ05p6aPTpRmfSXV1a/u2jM7TuldA8LXO6kXFemh5ihn3Lc9A9h1ZaqxpMNkK9rl5+EMKIGM9g5FSjJjv4aVOwEc/Q8iXx6ODSo5ualOQ9Bs0qVJTLu27du4c+vezbu379/AgwsfTry48ePIkytfqHp5xhfLadby+1soMJDLbhchlqIYpVbXa14dz718NMBEz/ZsWsU8zMhY1CdmL+VE+/ObgWSvPR4rj/f/xqxUFG1fVfHIUgLCN58zR5ABySpwbAAdZ1TNMgZYF9YHjWmsDAgNeglNqFSBJJYiG14JUmcYiHNZWJcE6HD4FGIrVuiFjROxJeNYChoFWUS5lBPjOTO2iM2PKuki2F1qfdPjjUjSeMuSk82Uml4fRimlLVR2OUAYBGHpo5aV4OJll1iYRkqWhag4FY75WXhmlTOoedo/ZVKI40Jz+pOEnX2ZsqcCeVJ0piEQieJhiEx0RBkrd6J2IiQFVXqPbZjEYqdmll6Jz0avtUaCqEFtaiqnekmHUagOhSZapvJMeioanXa2KqukZoCrJoruCKNmCngKZGC7urprPRKq+QZBP2XFKRGunBwL6waZjIbiBInu9hoj0o5wraRiOadrsX1kK24HvBK25rnstuvuu/DGK++89NZr77345qvvvhYlAAA7" + }, + { + "name": "The Christmas Party", + "description": "Hold on. Last Christmas, the Research Director was incredibly hammered. He made a big mention that his brand new festivus pole was actually some kind of astrological... something something. You can't remember the whole details, because you were smashed as well. However, briefly, the RD did keep that festivus pole for awhile, he might even still have it somewhere.\nMaybe...?", + "choices": [ + { + "key": "choice 15", + "name": "Wait a minute, was that a god damn...", + "exit_node": "Rod.", + "delay": 100, + "delay_message": "Immovable Rod?" + } + ], + "image": null, + "raw_image": "data:image/gif;base64,R0lGODdhyABkAHcAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAAACwAAAAAyABkAMIAAAAAAAD/AACmAAD///+mpqYAAAAAAAAD/xi63P4wykmrvTjrzbv/YCiOZGmejKCubOu6aCzP4WvfeD4IQ+//Pppw+MkZj6udkgdsOp/EqFSBrNqWwQ9hy+16v9ypeGM9YpnAE3hNKLTfhbG8kuTZz/j7/fmb+/8TbIJefIVNgIiJGgWMjY6PkJGRipSVlpeYmZqbnJ2en6ChoqOkpRplqDdYpqyprnpKhnyslq9leFkYg2u0QrZmaLkgu8S9JDg7Ksl2yrB7UDJw0m6QxiFfedk9wbKH1tbEW9TT05Lmjd/pJpPq7e7v8PHy8/T19vf4+fr7Kb9W/Jn8pcLiBKAcgVZwdethkAZCJAoXFmx47KEOWLMeAAlHMf+DRVXctIT70jHCxxYEhXkYKajkgpMRGZZgKY5LHJcJ6zTTNtHEm5rjgsK5WZKZs2xHn2WccY6RSwdJkaaUOOApoHDbqFK16ofmF6Fg20Aa8IhrpbBBm6plZ7aTIwhr0bWdS7eu3bt48+rdy7ev37+AAwseTLiw4cOI0/1LfPAkC8YlHINUApmD5MncVFZ+cPmK0qWbX3Z2MbVbaCqjd37eCjk1i9JaZR5OHTN2z8Gua9v2wavvaNjeKgygqbcz8KocvBLAexn4MOVb5jZfjVwk9DBW/S3bTn3E9S5cUXFvphqaCOhBs7/WGbU96PNr0EpTT96o1Kjv4ZOT/xbn/f921AWnBlhNWQXggZndFkNcZv2X1YOxsCZENXdJBWGCWp2mQRc+YLibIRpaQMyHEoVYgVfDcUFiHyYGopxQTgwFYosugsFfOXGZQyMGN+boI1E78vjjkHIF+QGRahkZxVpKNunkk1BGKeWUVFZp5ZVYZqnllogkAAA7" + }, + { + "name": "Rod.", + "description": "You cross reference your documentation. Sure enough, the \"festivus rod\" collected was actually an immovable rod.\nEnergy detected from the rod is the exact same coming off of the tree, as well. It's all making sense now. The Immovable rod is producing a kind of unique blackbody radiation that is providing sample heat and light for what is effectively an internal cold fusion process, and producing just enough of that radiation to create a kind of micro-enviromental bubble around the biosignature of the tree.\n\nThis would make the first time an immovable rod would exist in tandem with a biological source. You jot down some research notes on your findings, which could easily produce some kind of experimental tech, no doubt.", + "choices": [ + { + "key": "choice 16", + "name": "Snap a photo", + "exit_node": "Epilogue.", + "delay": 40, + "delay_message": "You could easily win an award for these findings!" + } + ], + "image": "default" + }, + { + "name": "Epilogue.", + "description": "You take a photo with the onboard camera on the drone. Suddenly, the immovable rod inside the tree explodes out of the wooden biological shell, and produces a blank, blurry photo.\nWhat the fuck?", + "choices": [ + { + "key": "choice 17", + "name": "God damnit.", + "exit_node": "WIN", + "delay": 10, + "delay_message": "Some things were just not meant for man to know." + } + ], + "image": "default", + "on_enter_effects": [ + { + "effect_type": "Set", + "quality": "Confusion", + "value": 9999 + } + ] + }, + { + "name": "What is wrong with this tree?", + "description": "This is ridiculous. Nothing about this dumbass tree makes sense. It makes no sense, it's just sitting there, living and making a MOCKERY of all of science!\nYou didn't get your degree in advanced plasma-physics for this!", + "choices": [ + { + "key": "choice 18", + "name": "The world can never know about this dumbass stupid plant.", + "exit_node": "FAIL_DEATH", + "delay": 60, + "delay_message": "Activating drone self-destruct." + }, + { + "key": "choice 19", + "name": "Take a moment to calm down.", + "exit_node": "Biological Scan", + "on_selection_effects": [ + { + "effect_type": "Add", + "quality": "Confusion", + "value": { + "value_type": "random", + "low": -3, + "high": -5 + } + } + ], + "delay": 20, + "delay_message": "Breathe." + } + ], + "image": "default" + } + ] +} \ No newline at end of file diff --git a/strings/junkmail.txt b/strings/junkmail.txt index 722e22166a5b1..b01ac4b898363 100644 --- a/strings/junkmail.txt +++ b/strings/junkmail.txt @@ -30,7 +30,7 @@ I don't really get the over-reliance on security. Basically sec claims day 1 and Sending mail is my life. I die for my postal office. It's some real shit. I meet the love of my life here, I mature, it's part of the Sending Mail grindset. 🗣️💯 TFW NO QT3.14 JANIBORG GF THAT HELPS YOU PICK UP TRASH AROUND YOUR HOUSE TO INCINERATE IT WHILE TRACKING SAID GARBAGE WITHIN THE HOUSE AND SCRATCHING OUT THE NAMES AND REMOVING THE STICKY GLUE BETWEEN TWO DIFFERENT PIECES OF GARBAGE CONTAINED TOGETHER OH MY GOD I'M LITERALLY GOING TO FUCKING SPASM OUT AND DIE I NEED ONE!!! Hello this is the Captain of your neighboring Nanotrasen Space Vessel, I seem to have lost my Golden Captain's Identification Card, if you would be so kind as to mail me one of your spares so I can perform my duties, I would be forever grateful. My address is 122. Space Destroit, HA16LU, thanks again! -Are YOU missing Mining equipment? You've read just the poster, we are giving away FREE mining equipment. That's right, FREE! All you have to do is subscribe to our newsletter and place the order for our Shaft Mining Starting Crate, and you will recieve your FREE mining equipment! Don't lose out on this once in a lifetime deal for FREE equipment! (Terms and Conditions may apply, fees for delivery is not paid for by AntiMech-Miners TD) +Are YOU missing Mining equipment? You've read just the poster, we are giving away FREE mining equipment. That's right, FREE! All you have to do is subscribe to our newsletter and place the order for our Shaft Mining Starting Crate, and you will receive your FREE mining equipment! Don't lose out on this once in a lifetime deal for FREE equipment! (Terms and Conditions may apply, fees for delivery is not paid for by AntiMech-Miners TD) We've been trying to reach you concerning your cargo shuttle's extended warranty. You should've received a notice in the mail about your cargo shuttle's extended warranty eligibility. Since we've not gotten a response, we're giving you a final courtesy call before we close out your file. Respond back 'BLOCK' to be removed and placed on our do-not-mail list. To speak to someone about possibly extending or reinstating your cargo shuttle's warranty, mail back your response ASAP to get in contact with a cargo specialist. Did you know you have rights? Space Law says you do, and so do I! Hi, I'm Chester McGoodman, I believe that until proven guilty, every assistant, engineer, and captain on this station is innocent. That's why I fight for you, Nanotrasen! Better Letter Chester! COUPON: 30% OFF NEXT PURCHASE OF SHIP FLYING INTO THE SUN diff --git a/strings/modular_maps/generic.toml b/strings/modular_maps/generic.toml new file mode 100644 index 0000000000000..050b75bb78c10 --- /dev/null +++ b/strings/modular_maps/generic.toml @@ -0,0 +1,31 @@ +directory = "_maps/modular_generic/" + +[rooms.station_small] +modules = ["station_s_kitchen.dmm", "station_s_chasm.dmm", "station_s_garden.dmm", "station_s_mime.dmm", "station_s_vault.dmm"] + +[rooms.station_medium] +modules = ["station_m_tools.dmm", "station_m_evidence.dmm", "station_m_kitchen.dmm", "station_m_shipping.dmm", "station_m_showroom.dmm", "station_m_arcade.dmm", "station_m_breakroom.dmm", "station_m_shuttle.dmm"] + +[rooms.station_large] +modules = ["station_l_morgue.dmm", "station_l_crates.dmm", "station_l_webs.dmm", "station_l_bathroom.dmm", "station_l_kitchen.dmm", "station_l_security.dmm", "station_l_kilojan.dmm"] + +[rooms.beach_medium] +modules = ["beach_m_shipping.dmm", "beach_m_oasis.dmm"] + +[rooms.beach_large] +modules = ["beach_l_ribs.dmm"] + +[rooms.jungle_medium] +modules = ["jungle_m_armory.dmm"] + +[rooms.jungle_large] +modules = ["jungle_l_dock.dmm"] + +[rooms.ice_small] +modules = ["ice_s_freezer.dmm"] + +[rooms.ice_medium] +modules = ["ice_m_comms.dmm"] + +[rooms.ice_large] +modules = ["ice_l_trophyroom.dmm", "ice_l_storage.dmm"] diff --git a/strings/modular_maps/safehouse.toml b/strings/modular_maps/safehouse.toml new file mode 100644 index 0000000000000..1abb0f6714f86 --- /dev/null +++ b/strings/modular_maps/safehouse.toml @@ -0,0 +1,34 @@ +directory = "_maps/safehouses/" + +[rooms.abductor] +modules = ["abductor.dmm"] + +[rooms.bathroom] +modules = ["bathroom.dmm"] + +[rooms.den] +modules = ["den.dmm"] + +[rooms.dig] +modules = ["dig.dmm"] + +[rooms.ice] +modules = ["ice.dmm"] + +[rooms.lavaland_boss] +modules = ["lavaland_boss.dmm"] + +[rooms.mine] +modules = ["mine.dmm"] + +[rooms.shuttle] +modules = ["shuttle.dmm"] + +[rooms.shuttle_space] +modules = ["shuttle_space.dmm"] + +[rooms.test_only] +modules = ["test_only_safehouse.dmm"] + +[rooms.wood] +modules = ["wood.dmm"] diff --git a/strings/mother.json b/strings/mother.json new file mode 100644 index 0000000000000..ed122a86c1a3d --- /dev/null +++ b/strings/mother.json @@ -0,0 +1,49 @@ +{ + "do_something": [ + "CLEAN YOUR ROOM THIS INSTANT!", + "DON'T SIT THAT CLOSE TO THE TV!", + "FOR GOD'S SAKE, GO TAKE A SHOWER!!", + "IT'S TIME TO WAKE UP FOR SCHOOL!!", + "PAUSE THAT ONLINE GAME! NOW!", + "PUT SOME CLOTHES ON! YOU'LL CATCH A COLD!", + "STOP ASKING FOR MONEY, I'M NOT AN ATM!", + "WATCH YOUR MOUTH, CHILD!!", + "WHY DON'T YOU ANSWER MY PHONE CALLS?!", + "YOU SHOULD @pick(verb) YOUR @pick(relative) ONCE IN A WHILE!" + ], + + "be_upset": [ + "BECAUSE I SAID SO!", + "I DON'T CARE WHAT YOU SAY!", + "I'M NOT ASKING; I'M TELLING!!", + "I WASN'T BORN YESTERDAY!", + "MONEY DOESN'T GROW ON TREES!", + "WHAT DID I DO TO DESERVE A KID LIKE THIS...", + "USELESS!", + "YOU INSULT YOUR GRANDPARENTS!" + ], + + "get_reprimanded": [ + "I BROUGHT YOU INTO THIS WORLD, I CAN TAKE YOU OUT!!!", + "I'M GOING TO THROW A FLIP-FLOP AT YOU!!", + "NO VIDEOGAMES FOR THE REST OF THE DAY!", + "WAIT UNTIL YOUR FATHER GETS HOME!", + "YOU'LL THANK ME ONE DAY!", + "YOU'RE DISOWNED!!!", + "YOU'RE GROUNDED!!" + ], + + "verb": [ + "CALL", + "HELP", + "VISIT" + ], + + "relative": [ + "AUNT AND UNCLE", + "DAD", + "GRANDPARENTS", + "MOM" + ] + +} diff --git a/strings/names/cargorilla.txt b/strings/names/cargorilla.txt new file mode 100644 index 0000000000000..ec135f5ca4d1b --- /dev/null +++ b/strings/names/cargorilla.txt @@ -0,0 +1,7 @@ +Cala +Cerchak +Citrus +Coco +Grodd +Paperwork +Winston diff --git a/strings/names/cyberauth.txt b/strings/names/cyberauth.txt new file mode 100644 index 0000000000000..f1fc42b369282 --- /dev/null +++ b/strings/names/cyberauth.txt @@ -0,0 +1,21 @@ +Mr. One +Process Kill +Event Handler +Q. Del +Shutdown Exe +Revert Commit +Thread Manager +Garbage Collector +Core Debugger +Kernel Panic +IO Blocker +Recursion Terminator +Disk Doctor +Format Syntax +Byte Guardian +Disk Defragmenter +Security Patch +Mandatory Upgrade +Pull Review +Bit Auditor +Pen Test diff --git a/strings/names/death_commando.txt b/strings/names/death_commando.txt index fb557de0ece06..0c5786764eee6 100644 --- a/strings/names/death_commando.txt +++ b/strings/names/death_commando.txt @@ -1,4 +1,4 @@ -A whole bunch of spiders in a SWAT suit +A whole bunch of spiders in a MODsuit Al "Otta" Gore AMERICA Beat Punchbeef diff --git a/strings/names/guardian_descriptions.txt b/strings/names/guardian_descriptions.txt new file mode 100644 index 0000000000000..678ec61fef63a --- /dev/null +++ b/strings/names/guardian_descriptions.txt @@ -0,0 +1,24 @@ +Black +Blazing +Bloody +Blue +Bronze +Dawn +Dusk +Gold +Green +Grey +Iron +Midnight +Orange +Pink +Plastitanium +Purple +Red +Shimmering +Shining +Silver +Sparkling +Steel +White +Yellow diff --git a/strings/names/guardian_gamepieces.txt b/strings/names/guardian_gamepieces.txt new file mode 100644 index 0000000000000..10a99cf38fb4a --- /dev/null +++ b/strings/names/guardian_gamepieces.txt @@ -0,0 +1,13 @@ +Ace +Bishop +Club +Diamond +Heart +Jack +Joker +King +Knight +Pawn +Queen +Rook +Spade diff --git a/strings/names/guardian_tarot.txt b/strings/names/guardian_tarot.txt new file mode 100644 index 0000000000000..5772c90d7ae85 --- /dev/null +++ b/strings/names/guardian_tarot.txt @@ -0,0 +1,23 @@ +Chariot +Death +Devil +Emperor +Empress +Fool +Fortune +Hangman +Hermit +Hierophant +Judgement +Justice +Lover +Magician +Moon +Priestess +Star +Strength +Sun +Temperance +Tower +Wheel +World diff --git a/strings/names/operative_alias.txt b/strings/names/operative_alias.txt new file mode 100644 index 0000000000000..582851fc07ce0 --- /dev/null +++ b/strings/names/operative_alias.txt @@ -0,0 +1,126 @@ +Agent +Agony +Alias +Alpha +Argo +Barker +Batter +Beef +Beetle +Bomber +Bonsai +Boss +Boston +Bovine +Bravo +Caboose +Callsign +Carmack +Carolina +Carp +Chains +Charlie +Church +Collar +Comedian +Crash +Creeper +Cretin +Criminal +Cyborg +Dallas +Delta +Doc +Donk +Drowning +Dude +Dwarf +Echo +Emo +Eva +Finger +Fish +Fitzgerald +Flash +Flyboy +Foxtrot +Freak +Freeman +Fugitive +Gaffer +Giant +Goalie +Golf +Gorbino +Green +Grime +Guy +Hologram +Hotel +Houston +Indica +Ion +Jacket +Jeremy +Jones +Kars +Legion +Librarian +Lightbringer +Lighter +Lightning +Looper +Lover +Marksman +Maurauder +Misty +Musketeer +Mycus +Neutron +Nightmare +Peacekeeper +Peddler +Point +Pooh +Private +Psycho +Pyro +Red +Revenant +Rocker +Ronin +Sack +Samson +Sarge +Scorch +Scout +Scream +Scum +Serenity +Shade +Shadow +Shark +Shocker +Shooter +Shrieker +Shrike +Silas +Silence +Simmons +Slider +Smoke +Snake +Stalker +Superfly +Suspect +Swiper +Tank +Telecrystal +Tex +Thirteen +Twister +Unusual +Vixen +White +Wilson +Winters diff --git a/strings/names/syndicate_monkey.txt b/strings/names/syndicate_monkey.txt new file mode 100644 index 0000000000000..9b2c059a7dfc5 --- /dev/null +++ b/strings/names/syndicate_monkey.txt @@ -0,0 +1,19 @@ +Agent 9 +Agent Banana +Agent Potassium +Agent Ape +Al Chimpone +Aldo +Banana Bond +Bonobo Assassin +Caesar +Evil Monkey +Solid Simian +Tony Bananas +Koba +Murderous George +Monkey Business +Hit-Monkey +Guenter +Goku +Kill Julien diff --git a/strings/phobia.json b/strings/phobia.json index 3f45b9f236449..45e7485a91966 100644 --- a/strings/phobia.json +++ b/strings/phobia.json @@ -113,6 +113,23 @@ "transfusion" ], + "carps": [ + "aquarium", + "carp", + "carps", + "carpotoxin", + "fin", + "fins", + "fish", + "fang", + "gnash", + "migration", + "slash", + "shred", + "teeth", + "tooth" + ], + "clowns": [ "banana", "clown", @@ -328,33 +345,54 @@ "admeme", "admin", "ahelp", + "amogus", "antag", "antagonist", "ban", "banned", + "chat", "click", + "c*der", + "coder", + "coders", "discord", + "erp", "forum", "ick ock", + "joever", "k", "kek", + "kys", "leddit", + "lmao", + "lol", + "mapper", "moderator", "mods", + "muderbone", + "murderboning", "ocky", "ooc", "owo", "pwn", + "powergame", "reddit", "round", "rule", + "rp", "self antag", "selfantag", + "sprite", + "spriter", + "sus", + "tide", "u", + "ur", "uwa", "uwu", "valid", - "y" + "y", + "5head" ], "robots": [ diff --git a/strings/sillytips.txt b/strings/sillytips.txt index 752a09b25cb58..5aa7af7ba0064 100644 --- a/strings/sillytips.txt +++ b/strings/sillytips.txt @@ -37,3 +37,4 @@ To defeat the slaughter demon, shoot at it until it dies. When a round ends nearly everything about it is lost forever, leave your salt behind with it. You can win a pulse rifle from the arcade machine. Honest. Your sprite represents your hitbox, so that afro makes you easier to kill. The sacrifices we make for style. +Gorillas can be killed by land mines placed along forest paths. diff --git a/strings/tcg/set_one.json b/strings/tcg/set_one.json index cdfcdaad77920..c1c061cad4684 100644 --- a/strings/tcg/set_one.json +++ b/strings/tcg/set_one.json @@ -441,7 +441,7 @@ "cardtype": "Creature", "cardsubtype": "Syndicate Soldier", "rarity": "rare", - "summon_icon_file": "icons/mob/simple/simple_human.dmi", + "summon_icon_file": "icons/obj/toys/tcgsummons.dmi", "summon_icon_state": "syndicate_stormtrooper_sword" }, { @@ -837,7 +837,7 @@ "cardtype": "Creature", "cardsubtype": "Syndicate Soldier", "rarity": "rare", - "summon_icon_file": "icons/mob/simple/simple_human.dmi", + "summon_icon_file": "icons/obj/toys/tcgsummons.dmi", "summon_icon_state": "syndicate_space_shotgun" }, { diff --git a/strings/tips.txt b/strings/tips.txt index c96394adb2657..f12ad3760bed9 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -1,4 +1,4 @@ -@You can use the |, + and _ characters to emphasize parts of what you say in-game (e.g. say"my _ass_ |is| +heavy+." will be outputted as "my ass is heavy."). You can also escape these emphasizers by appending backslashes before them (e.g. say"1\+2\+3" will come out as "1+2+3" and not "1\2\3"). +@You can italicize, embolden or underline portions of your messages by enclosing them with |, + or _ respectively. You can also avoid this by adding backslashes (they won't show in the message) before these characters. ♪ Hey, have you ever tried appending the % character before your messages when speaking in-game? ♫ A Scientist will pay top dollar for your frogs! A thrown glass of water can make a slippery tile, allowing you to slow down your pursuers in a pinch. @@ -22,7 +22,7 @@ As a Chemist, Water and Potassium mixed together will create an explosion, with As a Chemist, Holy Water and Potassium mixed together will create a HOLY explosion, with increased power scaling by amount used. If at least 75 units of both are used, the explosion will reveal, ignite, and paralyze any cultists, heretics, and revenants in sight. Do it! As a Chemist, you can quickly make 100u plastic bottles directly from plastic sheets, alongside being able to make 120u plastic beakers in the lathe. The ChemMaster can produce infinite 30u glass bottles as well and if researched the medical techfab can make beakers with capacity ranging from 120u to 300u. As a Chemist, you can recharge your chemical dispenser with an inducer or by replacing its cell. -As a Chemist, you will be expected to supply crew with certain chemicals. For example, Clonexadone and Mannitol for the cryo tubes, Diethylamine and Saltpetre for botany, as well as healing pills and patches for the front desk. +As a Chemist, you will be expected to supply crew with certain chemicals. For example, Cryoxadone and Mannitol for the cryo tubes, Diethylamine and Saltpetre for botany, as well as healing pills and patches for the front desk. As a Cook, any food you make will be much healthier than the junk food found in vendors. Having the crew routinely eating from you will provide minor buffs. As a Cook, being in the kitchen will make you remember the basics of Close Quarters Cooking. It is highly effective at removing Assistants from your workplace. As a Cook, most non-custom foods will have a secondary effect, ranging from healing you to making you move at lightspeed. Experiment! @@ -94,6 +94,7 @@ As a Security Officer, mindshield implants can only prevent someone from being t As a Security Officer, remember that correlation does not equal causation. Someone may have just been at the wrong place at the wrong time! As a Security Officer, remember that you can attach a sec-lite to your disabler or your helmet! 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. Use this to your advantage in a revolution to definitively tell who is on your side! +As a Security Officer, you have a special pen in your PDA that you can use to prompt people to surrender. This will stun them without the need to use a baton! As a Service Cyborg, your spray can knocks people down. However, it is blocked by masks and glasses. 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 Shaft Miner, every monster on Lavaland has a pattern you can exploit to minimize damage from the encounters. @@ -226,9 +227,9 @@ If you're using hotkey mode, you can stop pulling things using H. In a pinch, stripping yourself naked will give you a sizeable resistance to being tackled. What do you value more, your freedom or your dignity? Laying down will help slow down bloodloss. Death will halt it entirely. Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using. -Most job-related exosuit clothing can fit job-related items into it, such as the atmospheric technician's winter coat holding an RPD, or labcoats holding most medicine. +Most job-related suit slot clothing can fit job-related items into it, such as the atmospheric technician's winter coat holding an RPD, or labcoats holding most medicine. Most things have special interactions with right, alt, shift, and control click. Experiment! -On most clothing items that go in the exosuit slot, you can put certain small items into your suit storage, such as a spraycan, your emergency oxygen tank, or a flashlight. +On most clothing items that go in the suit slot, you can put certain small items into your suit storage, such as a spraycan, your emergency oxygen tank, or a flashlight. Remote devices will work when used through cameras. For example: Bluespace RPEDs and door remotes. Sleeping can be used to recover from minor injuries and organ damage. Sanity, darkness, blindfolds, earmuffs, tables, beds, and bedsheets affect the healing rate. Some roles cannot be antagonists by default, but antag selection is decided first. For instance, you can set Security Officer to High without affecting your chances of becoming an antag -- the game will just select a different role. @@ -251,12 +252,13 @@ When hacking doors, cutting and mending a "test light wire" will restore power t When in doubt about technical issues, clear your cache (byond launcher > cogwheel > preferences > game prefs), update your BYOND, and relog. When placing floor tiles in space, you don't need to place down lattice if there is a piece of plating nearby. Where the space map levels connect is randomized every round, but are otherwise kept consistent within rounds. Remember that they are not necessarily bidirectional! +Working out improves your fitness which increases your size and faster times to fireman carry. Remember that a quality diet and sleep are essential! You can catch thrown items by toggling on your throw mode with an empty hand active. You can change the control scheme by pressing tab. One is WASD, the other is the arrow keys. Keep in mind that hotkeys are also changed with this. You can cheat games by baking dice in microwaves to make them loaded. Cards can be seen with x-ray vision or be marked with either a pen or crayon to gain an edge. You can climb onto a table by dragging yourself onto one. This takes time and drops the items in your hands on the table. Clicking on a table that someone else is climbing onto will knock them down. You can deconvert Cultists of Nar'Sie by feeding them large amounts of holy water. Unlike revolutionaries, implanting them with mindshield implants won't do it! -You can drag other players onto yourself to open the strip menu, letting you remove their equipment or force them to wear something. Note that exosuits or helmets will block your access to the clothing beneath them, and that certain items take longer to strip or put on than others. +You can drag other players onto yourself to open the strip menu, letting you remove their equipment or force them to wear something. Note that suits or helmets will block your access to the clothing beneath them, and that certain items take longer to strip or put on than others. You can grab someone by holding Ctrl and clicking on them, then upgrade the grab by Ctrl-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. You can light a cigar on a supermatter crystal. You can move an item out of the way by dragging it and then clicking on an adjacent tile with an empty hand. @@ -266,5 +268,7 @@ You can screwdriver any non-chemical grenade to shorten fuses from 5 seconds, to You can spray a fire extinguisher, throw items or fire a gun while floating through space to change your direction. Simply fire opposite to where you want to go. You can swap floor tiles by holding a crowbar in one hand and a stack of tiles in the other. You can use a machine in the vault to deposit cash or rob Cargo's department funds. +You can use an upgraded microwave to charge your PDA! You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people - it's a much better experience! -Some areas of the station use simple nautical directions to indicate their respective locations, like Fore (Front of the ship), Aft (Back), Port (Left side), Starboard (Right), Quarter and Bow (Either sides of Aft and Fore, respectively). You can review these terms on the Notepad App of your PDA. \ No newline at end of file +Some areas of the station use simple nautical directions to indicate their respective locations, like Fore (Front of the ship), Aft (Back), Port (Left side), Starboard (Right), Quarter and Bow (Either sides of Aft and Fore, respectively). You can review these terms on the Notepad App of your PDA. +Modular computers are compatible with integrated circuits, but most of the program-dependent circuits require them to be open/backgrounded to work. To install circuits on stationary consoles, you need to toggle interaction with the frame with right-click first. diff --git a/tgstation.dme b/tgstation.dme index b653b394bfff5..0364b4c6b4d02 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -18,10 +18,11 @@ #include "code\_compile_options.dm" #include "code\_experiments.dm" #include "code\world.dm" +#include "code\__DEFINES\__globals.dm" #include "code\__DEFINES\_atoms.dm" #include "code\__DEFINES\_bitfields.dm" #include "code\__DEFINES\_click.dm" -#include "code\__DEFINES\_globals.dm" +#include "code\__DEFINES\_flags.dm" #include "code\__DEFINES\_helpers.dm" #include "code\__DEFINES\_protect.dm" #include "code\__DEFINES\_tick.dm" @@ -35,11 +36,11 @@ #include "code\__DEFINES\airlock.dm" #include "code\__DEFINES\alarm.dm" #include "code\__DEFINES\alerts.dm" +#include "code\__DEFINES\announcements.dm" #include "code\__DEFINES\anomaly.dm" #include "code\__DEFINES\antagonists.dm" #include "code\__DEFINES\apc_defines.dm" #include "code\__DEFINES\appearance.dm" -#include "code\__DEFINES\aquarium.dm" #include "code\__DEFINES\area_editor.dm" #include "code\__DEFINES\art.dm" #include "code\__DEFINES\assemblies.dm" @@ -47,6 +48,7 @@ #include "code\__DEFINES\atom_hud.dm" #include "code\__DEFINES\basic_mobs.dm" #include "code\__DEFINES\basketball.dm" +#include "code\__DEFINES\bitrunning.dm" #include "code\__DEFINES\blackmarket.dm" #include "code\__DEFINES\blend_modes.dm" #include "code\__DEFINES\blob_defines.dm" @@ -66,7 +68,6 @@ #include "code\__DEFINES\communications.dm" #include "code\__DEFINES\computers.dm" #include "code\__DEFINES\configuration.dm" -#include "code\__DEFINES\construction.dm" #include "code\__DEFINES\cooldowns.dm" #include "code\__DEFINES\crafting.dm" #include "code\__DEFINES\crushing.dm" @@ -92,8 +93,7 @@ #include "code\__DEFINES\external_organs.dm" #include "code\__DEFINES\fantasy_affixes.dm" #include "code\__DEFINES\firealarm.dm" -#include "code\__DEFINES\fishing.dm" -#include "code\__DEFINES\flags.dm" +#include "code\__DEFINES\fish.dm" #include "code\__DEFINES\flora.dm" #include "code\__DEFINES\font_awesome_icons.dm" #include "code\__DEFINES\fonts.dm" @@ -102,6 +102,7 @@ #include "code\__DEFINES\fov.dm" #include "code\__DEFINES\generators.dm" #include "code\__DEFINES\ghost.dm" +#include "code\__DEFINES\gradient.dm" #include "code\__DEFINES\gravity.dm" #include "code\__DEFINES\guardian_defines.dm" #include "code\__DEFINES\holiday.dm" @@ -109,8 +110,8 @@ #include "code\__DEFINES\hud.dm" #include "code\__DEFINES\icon_smoothing.dm" #include "code\__DEFINES\id_cards.dm" +#include "code\__DEFINES\implants.dm" #include "code\__DEFINES\important_recursive_contents.dm" -#include "code\__DEFINES\industrial_lift.dm" #include "code\__DEFINES\injection.dm" #include "code\__DEFINES\input.dm" #include "code\__DEFINES\instruments.dm" @@ -128,34 +129,39 @@ #include "code\__DEFINES\lights.dm" #include "code\__DEFINES\living.dm" #include "code\__DEFINES\logging.dm" -#include "code\__DEFINES\loot.dm" #include "code\__DEFINES\machines.dm" #include "code\__DEFINES\magic.dm" +#include "code\__DEFINES\map_exporter.dm" #include "code\__DEFINES\map_switch.dm" #include "code\__DEFINES\mapping.dm" #include "code\__DEFINES\maps.dm" -#include "code\__DEFINES\materials.dm" #include "code\__DEFINES\maths.dm" #include "code\__DEFINES\matrices.dm" #include "code\__DEFINES\MC.dm" #include "code\__DEFINES\mecha.dm" #include "code\__DEFINES\medical.dm" +#include "code\__DEFINES\megafauna.dm" #include "code\__DEFINES\melee.dm" #include "code\__DEFINES\memory_defines.dm" #include "code\__DEFINES\mergers.dm" +#include "code\__DEFINES\mining.dm" #include "code\__DEFINES\mob_spawn.dm" #include "code\__DEFINES\mobfactions.dm" #include "code\__DEFINES\mobs.dm" #include "code\__DEFINES\mod.dm" #include "code\__DEFINES\modular_computer.dm" #include "code\__DEFINES\monkeys.dm" +#include "code\__DEFINES\mood.dm" #include "code\__DEFINES\move_force.dm" #include "code\__DEFINES\movement.dm" #include "code\__DEFINES\movespeed_modification.dm" #include "code\__DEFINES\multiz.dm" #include "code\__DEFINES\nitrile.dm" +#include "code\__DEFINES\nozzle_define.dm" #include "code\__DEFINES\nuclear_bomb.dm" #include "code\__DEFINES\obj_flags.dm" +#include "code\__DEFINES\observers.dm" +#include "code\__DEFINES\organ_movement.dm" #include "code\__DEFINES\overlays.dm" #include "code\__DEFINES\pai.dm" #include "code\__DEFINES\paintings.dm" @@ -178,6 +184,7 @@ #include "code\__DEFINES\radiation.dm" #include "code\__DEFINES\radio.dm" #include "code\__DEFINES\radioactive_nebula.dm" +#include "code\__DEFINES\random_spawner.dm" #include "code\__DEFINES\reactions.dm" #include "code\__DEFINES\reagents.dm" #include "code\__DEFINES\reagents_specific_heat.dm" @@ -209,6 +216,7 @@ #include "code\__DEFINES\species_clothing_paths.dm" #include "code\__DEFINES\speech_channels.dm" #include "code\__DEFINES\sprite_accessories.dm" +#include "code\__DEFINES\stack.dm" #include "code\__DEFINES\stack_trace.dm" #include "code\__DEFINES\stat.dm" #include "code\__DEFINES\stat_tracking.dm" @@ -229,8 +237,8 @@ #include "code\__DEFINES\time.dm" #include "code\__DEFINES\tools.dm" #include "code\__DEFINES\toys.dm" -#include "code\__DEFINES\traits.dm" -#include "code\__DEFINES\tram.dm" +#include "code\__DEFINES\trader.dm" +#include "code\__DEFINES\transport.dm" #include "code\__DEFINES\tts.dm" #include "code\__DEFINES\turbine_defines.dm" #include "code\__DEFINES\turfs.dm" @@ -247,8 +255,10 @@ #include "code\__DEFINES\wires.dm" #include "code\__DEFINES\wounds.dm" #include "code\__DEFINES\xenobiology.dm" +#include "code\__DEFINES\zoom.dm" #include "code\__DEFINES\ai\ai.dm" #include "code\__DEFINES\ai\ai_blackboard.dm" +#include "code\__DEFINES\ai\bot_keys.dm" #include "code\__DEFINES\ai\carp.dm" #include "code\__DEFINES\ai\haunted.dm" #include "code\__DEFINES\ai\monkey.dm" @@ -257,6 +267,7 @@ #include "code\__DEFINES\ai\pets.dm" #include "code\__DEFINES\ai\simplemob.dm" #include "code\__DEFINES\ai\tourist.dm" +#include "code\__DEFINES\ai\trader.dm" #include "code\__DEFINES\ai\vending.dm" #include "code\__DEFINES\ai\ventcrawling.dm" #include "code\__DEFINES\atmospherics\atmos_core.dm" @@ -266,6 +277,10 @@ #include "code\__DEFINES\atmospherics\atmos_mapping_helpers.dm" #include "code\__DEFINES\atmospherics\atmos_mob_interaction.dm" #include "code\__DEFINES\atmospherics\atmos_piping.dm" +#include "code\__DEFINES\construction\actions.dm" +#include "code\__DEFINES\construction\material.dm" +#include "code\__DEFINES\construction\rcd.dm" +#include "code\__DEFINES\construction\structures.dm" #include "code\__DEFINES\dcs\flags.dm" #include "code\__DEFINES\dcs\helpers.dm" #include "code\__DEFINES\dcs\signals\mapping.dm" @@ -277,6 +292,8 @@ #include "code\__DEFINES\dcs\signals\signals_assembly.dm" #include "code\__DEFINES\dcs\signals\signals_backpack.dm" #include "code\__DEFINES\dcs\signals\signals_beam.dm" +#include "code\__DEFINES\dcs\signals\signals_bitrunning.dm" +#include "code\__DEFINES\dcs\signals\signals_blob.dm" #include "code\__DEFINES\dcs\signals\signals_bot.dm" #include "code\__DEFINES\dcs\signals\signals_camera.dm" #include "code\__DEFINES\dcs\signals\signals_changeling.dm" @@ -333,14 +350,15 @@ #include "code\__DEFINES\dcs\signals\signals_techweb.dm" #include "code\__DEFINES\dcs\signals\signals_tools.dm" #include "code\__DEFINES\dcs\signals\signals_traitor.dm" -#include "code\__DEFINES\dcs\signals\signals_tram.dm" #include "code\__DEFINES\dcs\signals\signals_transform.dm" +#include "code\__DEFINES\dcs\signals\signals_transport.dm" #include "code\__DEFINES\dcs\signals\signals_turf.dm" #include "code\__DEFINES\dcs\signals\signals_twohand.dm" #include "code\__DEFINES\dcs\signals\signals_vehicle.dm" #include "code\__DEFINES\dcs\signals\signals_wash.dm" #include "code\__DEFINES\dcs\signals\signals_wizard.dm" #include "code\__DEFINES\dcs\signals\signals_xeno_control.dm" +#include "code\__DEFINES\dcs\signals\uplink.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_attack.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_explosion.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_lighting.dm" @@ -353,6 +371,7 @@ #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_arcade.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_basic.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_carbon.dm" +#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_guardian.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_living.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_main.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_silicon.dm" @@ -361,13 +380,17 @@ #include "code\__DEFINES\research\anomalies.dm" #include "code\__DEFINES\research\research_categories.dm" #include "code\__DEFINES\research\slimes.dm" +#include "code\__DEFINES\traits\_traits.dm" +#include "code\__DEFINES\traits\declarations.dm" +#include "code\__DEFINES\traits\macros.dm" +#include "code\__DEFINES\traits\sources.dm" #include "code\__HELPERS\_auxtools_api.dm" #include "code\__HELPERS\_lists.dm" #include "code\__HELPERS\_planes.dm" #include "code\__HELPERS\_string_lists.dm" #include "code\__HELPERS\admin.dm" #include "code\__HELPERS\ai.dm" -#include "code\__HELPERS\animations.dm" +#include "code\__HELPERS\announcements.dm" #include "code\__HELPERS\areas.dm" #include "code\__HELPERS\atmospherics.dm" #include "code\__HELPERS\atoms.dm" @@ -412,7 +435,6 @@ #include "code\__HELPERS\mouse_control.dm" #include "code\__HELPERS\nameof.dm" #include "code\__HELPERS\names.dm" -#include "code\__HELPERS\path.dm" #include "code\__HELPERS\piping_colors_lists.dm" #include "code\__HELPERS\priority_announce.dm" #include "code\__HELPERS\pronouns.dm" @@ -448,6 +470,7 @@ #include "code\__HELPERS\varset_callback.dm" #include "code\__HELPERS\verbs.dm" #include "code\__HELPERS\view.dm" +#include "code\__HELPERS\visual_effects.dm" #include "code\__HELPERS\weakref.dm" #include "code\__HELPERS\logging\_logging.dm" #include "code\__HELPERS\logging\admin.dm" @@ -467,8 +490,12 @@ #include "code\__HELPERS\logging\shuttle.dm" #include "code\__HELPERS\logging\talk.dm" #include "code\__HELPERS\logging\tool.dm" +#include "code\__HELPERS\logging\transport.dm" #include "code\__HELPERS\logging\ui.dm" #include "code\__HELPERS\logging\virus.dm" +#include "code\__HELPERS\paths\jps.dm" +#include "code\__HELPERS\paths\path.dm" +#include "code\__HELPERS\paths\sssp.dm" #include "code\__HELPERS\sorts\__main.dm" #include "code\__HELPERS\sorts\InsertSort.dm" #include "code\__HELPERS\sorts\MergeSort.dm" @@ -483,14 +510,18 @@ #include "code\_globalvars\lighting.dm" #include "code\_globalvars\logging.dm" #include "code\_globalvars\phobias.dm" +#include "code\_globalvars\rcd.dm" #include "code\_globalvars\religion.dm" +#include "code\_globalvars\silo.dm" #include "code\_globalvars\tgui.dm" #include "code\_globalvars\time_vars.dm" -#include "code\_globalvars\traits.dm" #include "code\_globalvars\lists\achievements.dm" #include "code\_globalvars\lists\ambience.dm" +#include "code\_globalvars\lists\canisters.dm" +#include "code\_globalvars\lists\cargo.dm" #include "code\_globalvars\lists\client.dm" #include "code\_globalvars\lists\color.dm" +#include "code\_globalvars\lists\crafting.dm" #include "code\_globalvars\lists\flavor_misc.dm" #include "code\_globalvars\lists\icons.dm" #include "code\_globalvars\lists\keybindings.dm" @@ -499,12 +530,19 @@ #include "code\_globalvars\lists\mobs.dm" #include "code\_globalvars\lists\names.dm" #include "code\_globalvars\lists\objects.dm" +#include "code\_globalvars\lists\ores_spawned.dm" +#include "code\_globalvars\lists\plumbing.dm" #include "code\_globalvars\lists\poll_ignore.dm" #include "code\_globalvars\lists\quirks.dm" +#include "code\_globalvars\lists\rcd.dm" +#include "code\_globalvars\lists\reagents.dm" #include "code\_globalvars\lists\rtd.dm" +#include "code\_globalvars\lists\silo.dm" #include "code\_globalvars\lists\typecache.dm" #include "code\_globalvars\lists\wiremod.dm" #include "code\_globalvars\lists\xenobiology.dm" +#include "code\_globalvars\traits\_traits.dm" +#include "code\_globalvars\traits\admin_tooling.dm" #include "code\_js\byjax.dm" #include "code\_js\menus.dm" #include "code\_onclick\adjacent.dm" @@ -548,10 +586,11 @@ #include "code\_onclick\hud\screentip.dm" #include "code\_onclick\hud\parallax\parallax.dm" #include "code\_onclick\hud\parallax\random_layer.dm" -#include "code\_onclick\hud\rendering\plane_master.dm" #include "code\_onclick\hud\rendering\plane_master_controller.dm" #include "code\_onclick\hud\rendering\plane_master_group.dm" #include "code\_onclick\hud\rendering\render_plate.dm" +#include "code\_onclick\hud\rendering\plane_masters\_plane_master.dm" +#include "code\_onclick\hud\rendering\plane_masters\plane_master_subtypes.dm" #include "code\controllers\admin.dm" #include "code\controllers\controller.dm" #include "code\controllers\failsafe.dm" @@ -617,15 +656,17 @@ #include "code\controllers\subsystem\mouse_entered.dm" #include "code\controllers\subsystem\nightshift.dm" #include "code\controllers\subsystem\npcpool.dm" +#include "code\controllers\subsystem\ore_generation.dm" #include "code\controllers\subsystem\overlays.dm" #include "code\controllers\subsystem\pai.dm" #include "code\controllers\subsystem\parallax.dm" #include "code\controllers\subsystem\pathfinder.dm" -#include "code\controllers\subsystem\persistence.dm" #include "code\controllers\subsystem\persistent_paintings.dm" #include "code\controllers\subsystem\ping.dm" #include "code\controllers\subsystem\points_of_interest.dm" +#include "code\controllers\subsystem\polling.dm" #include "code\controllers\subsystem\profiler.dm" +#include "code\controllers\subsystem\queuelinks.dm" #include "code\controllers\subsystem\radiation.dm" #include "code\controllers\subsystem\radio.dm" #include "code\controllers\subsystem\radioactive_nebula.dm" @@ -642,6 +683,7 @@ #include "code\controllers\subsystem\speech_controller.dm" #include "code\controllers\subsystem\statpanel.dm" #include "code\controllers\subsystem\stickyban.dm" +#include "code\controllers\subsystem\stock_market.dm" #include "code\controllers\subsystem\sun.dm" #include "code\controllers\subsystem\tcgsetup.dm" #include "code\controllers\subsystem\tgui.dm" @@ -651,6 +693,7 @@ #include "code\controllers\subsystem\timer.dm" #include "code\controllers\subsystem\title.dm" #include "code\controllers\subsystem\traitor.dm" +#include "code\controllers\subsystem\transport.dm" #include "code\controllers\subsystem\tts.dm" #include "code\controllers\subsystem\tutorials.dm" #include "code\controllers\subsystem\verb_manager.dm" @@ -659,6 +702,16 @@ #include "code\controllers\subsystem\wardrobe.dm" #include "code\controllers\subsystem\weather.dm" #include "code\controllers\subsystem\wiremod_composite.dm" +#include "code\controllers\subsystem\dynamic\dynamic.dm" +#include "code\controllers\subsystem\dynamic\dynamic_hijacking.dm" +#include "code\controllers\subsystem\dynamic\dynamic_logging.dm" +#include "code\controllers\subsystem\dynamic\dynamic_midround_rolling.dm" +#include "code\controllers\subsystem\dynamic\dynamic_rulesets.dm" +#include "code\controllers\subsystem\dynamic\dynamic_rulesets_latejoin.dm" +#include "code\controllers\subsystem\dynamic\dynamic_rulesets_midround.dm" +#include "code\controllers\subsystem\dynamic\dynamic_rulesets_roundstart.dm" +#include "code\controllers\subsystem\dynamic\dynamic_unfavorable_situation.dm" +#include "code\controllers\subsystem\dynamic\ruleset_picking.dm" #include "code\controllers\subsystem\movement\ai_movement.dm" #include "code\controllers\subsystem\movement\cliff_falling.dm" #include "code\controllers\subsystem\movement\hyperspace_drift.dm" @@ -666,6 +719,16 @@ #include "code\controllers\subsystem\movement\movement.dm" #include "code\controllers\subsystem\movement\movement_types.dm" #include "code\controllers\subsystem\movement\spacedrift.dm" +#include "code\controllers\subsystem\persistence\_persistence.dm" +#include "code\controllers\subsystem\persistence\counter_delamination.dm" +#include "code\controllers\subsystem\persistence\counter_tram_hits.dm" +#include "code\controllers\subsystem\persistence\custom_outfits.dm" +#include "code\controllers\subsystem\persistence\engravings.dm" +#include "code\controllers\subsystem\persistence\photo_albums.dm" +#include "code\controllers\subsystem\persistence\recipes.dm" +#include "code\controllers\subsystem\persistence\scars.dm" +#include "code\controllers\subsystem\persistence\tattoos.dm" +#include "code\controllers\subsystem\persistence\trophies.dm" #include "code\controllers\subsystem\processing\acid.dm" #include "code\controllers\subsystem\processing\ai_basic_avoidance.dm" #include "code\controllers\subsystem\processing\ai_behaviors.dm" @@ -689,12 +752,13 @@ #include "code\controllers\subsystem\processing\singulo.dm" #include "code\controllers\subsystem\processing\station.dm" #include "code\controllers\subsystem\processing\supermatter_cascade.dm" -#include "code\controllers\subsystem\processing\tramprocess.dm" #include "code\controllers\subsystem\processing\wet_floors.dm" #include "code\datums\alarm.dm" #include "code\datums\beam.dm" #include "code\datums\browser.dm" #include "code\datums\callback.dm" +#include "code\datums\candidate_poll.dm" +#include "code\datums\chat_payload.dm" #include "code\datums\chatmessage.dm" #include "code\datums\dash_weapon.dm" #include "code\datums\datum.dm" @@ -704,12 +768,12 @@ #include "code\datums\ductnet.dm" #include "code\datums\emotes.dm" #include "code\datums\ert.dm" -#include "code\datums\forced_movement.dm" #include "code\datums\hailer_phrase.dm" #include "code\datums\holocall.dm" #include "code\datums\hotkeys_help.dm" #include "code\datums\http.dm" #include "code\datums\hud.dm" +#include "code\datums\json_database.dm" #include "code\datums\json_savefile.dm" #include "code\datums\lazy_template.dm" #include "code\datums\map_config.dm" @@ -722,7 +786,6 @@ #include "code\datums\position_point_vector.dm" #include "code\datums\profiling.dm" #include "code\datums\progressbar.dm" -#include "code\datums\recipe.dm" #include "code\datums\request_message.dm" #include "code\datums\ruins.dm" #include "code\datums\saymode.dm" @@ -731,6 +794,7 @@ #include "code\datums\sprite_accessories.dm" #include "code\datums\station_alert.dm" #include "code\datums\station_integrity.dm" +#include "code\datums\stock_market_events.dm" #include "code\datums\tgs_event_handler.dm" #include "code\datums\verb_callbacks.dm" #include "code\datums\verbs.dm" @@ -771,7 +835,10 @@ #include "code\datums\actions\mobs\blood_warp.dm" #include "code\datums\actions\mobs\charge.dm" #include "code\datums\actions\mobs\charge_apc.dm" +#include "code\datums\actions\mobs\chase_target.dm" #include "code\datums\actions\mobs\conjure_foamwall.dm" +#include "code\datums\actions\mobs\create_legion_skull.dm" +#include "code\datums\actions\mobs\create_legion_turrets.dm" #include "code\datums\actions\mobs\dash.dm" #include "code\datums\actions\mobs\defensive_mode.dm" #include "code\datums\actions\mobs\fire_breath.dm" @@ -779,6 +846,7 @@ #include "code\datums\actions\mobs\meteors.dm" #include "code\datums\actions\mobs\mobcooldown.dm" #include "code\datums\actions\mobs\open_mob_commands.dm" +#include "code\datums\actions\mobs\personality_commune.dm" #include "code\datums\actions\mobs\projectileattack.dm" #include "code\datums\actions\mobs\sign_language.dm" #include "code\datums\actions\mobs\sneak.dm" @@ -796,47 +864,64 @@ #include "code\datums\ai\bane\bane_controller.dm" #include "code\datums\ai\bane\bane_subtrees.dm" #include "code\datums\ai\basic_mobs\base_basic_controller.dm" +#include "code\datums\ai\basic_mobs\generic_controllers.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\befriend_target.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\climb_tree.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_mineable_wall.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_parent.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targetting.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\pick_up_item.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\run_away_from_target.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\set_travel_destination.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\step_towards_turf.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\stop_and_stare.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeted_mob_ability.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\targetting.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\unbuckle_mob.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\ventcrawling.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\wounded_targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\write_on_paper.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\attack_adjacent_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\call_reinforcements.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\capricious_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\climb_tree.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_paper_and_write.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_parent.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\go_for_swim.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\maintain_distance.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\mine_walls.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\move_to_cardinal.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\opportunistic_ventcrawler.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\prepare_travel_to_destination.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\ranged_skirmish.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\run_emote.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\shapechange_ambush.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_wounded_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\sleep_with_no_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\stare_at_thing.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\targeted_mob_ability.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\teleport_away_from_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\travel_to_point.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\use_mob_ability.dm" #include "code\datums\ai\basic_mobs\pet_commands\fetch.dm" #include "code\datums\ai\basic_mobs\pet_commands\pet_command_planning.dm" #include "code\datums\ai\basic_mobs\pet_commands\pet_follow_friend.dm" -#include "code\datums\ai\basic_mobs\pet_commands\pet_use_targetted_ability.dm" +#include "code\datums\ai\basic_mobs\pet_commands\pet_use_targeted_ability.dm" #include "code\datums\ai\basic_mobs\pet_commands\play_dead.dm" -#include "code\datums\ai\basic_mobs\targetting_datums\basic_targetting_datum.dm" -#include "code\datums\ai\basic_mobs\targetting_datums\dont_target_friends.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\basic_targeting_strategy.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\dont_target_friends.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\with_object.dm" #include "code\datums\ai\cursed\cursed_behaviors.dm" #include "code\datums\ai\cursed\cursed_controller.dm" #include "code\datums\ai\cursed\cursed_subtrees.dm" @@ -850,6 +935,7 @@ #include "code\datums\ai\hauntium\hauntium_subtrees.dm" #include "code\datums\ai\hunting_behavior\hunting_behaviors.dm" #include "code\datums\ai\hunting_behavior\hunting_cockroach.dm" +#include "code\datums\ai\hunting_behavior\hunting_corpses.dm" #include "code\datums\ai\hunting_behavior\hunting_lights.dm" #include "code\datums\ai\hunting_behavior\hunting_mouse.dm" #include "code\datums\ai\idle_behaviors\_idle_behavior.dm" @@ -923,18 +1009,23 @@ #include "code\datums\components\attached_sticker.dm" #include "code\datums\components\aura_healing.dm" #include "code\datums\components\bakeable.dm" +#include "code\datums\components\basic_inhands.dm" #include "code\datums\components\basic_mob_attack_telegraph.dm" #include "code\datums\components\basic_ranged_ready_overlay.dm" #include "code\datums\components\beetlejuice.dm" +#include "code\datums\components\blob_minion.dm" #include "code\datums\components\blood_walk.dm" +#include "code\datums\components\bloody_spreader.dm" #include "code\datums\components\bloodysoles.dm" #include "code\datums\components\boomerang.dm" #include "code\datums\components\boss_music.dm" +#include "code\datums\components\breeding.dm" #include "code\datums\components\bullet_intercepting.dm" #include "code\datums\components\bumpattack.dm" #include "code\datums\components\burning.dm" #include "code\datums\components\butchering.dm" #include "code\datums\components\caltrop.dm" +#include "code\datums\components\can_flash_from_behind.dm" #include "code\datums\components\chasm.dm" #include "code\datums\components\chuunibyou.dm" #include "code\datums\components\cleaner.dm" @@ -958,9 +1049,13 @@ #include "code\datums\components\curse_of_polymorph.dm" #include "code\datums\components\customizable_reagent_holder.dm" #include "code\datums\components\damage_aura.dm" +#include "code\datums\components\damage_chain.dm" +#include "code\datums\components\dart_insert.dm" #include "code\datums\components\deadchat_control.dm" +#include "code\datums\components\death_linked.dm" #include "code\datums\components\dejavu.dm" #include "code\datums\components\deployable.dm" +#include "code\datums\components\direct_explosive_trap.dm" #include "code\datums\components\drift.dm" #include "code\datums\components\earprotection.dm" #include "code\datums\components\echolocation.dm" @@ -969,12 +1064,15 @@ #include "code\datums\components\egg_layer.dm" #include "code\datums\components\electrified_buckle.dm" #include "code\datums\components\embedded.dm" +#include "code\datums\components\energized.dm" #include "code\datums\components\engraved.dm" #include "code\datums\components\evolutionary_leap.dm" #include "code\datums\components\explodable.dm" +#include "code\datums\components\explode_on_attack.dm" #include "code\datums\components\faction_granter.dm" #include "code\datums\components\fertile_egg.dm" #include "code\datums\components\fishing_spot.dm" +#include "code\datums\components\focused_attacker.dm" #include "code\datums\components\food_storage.dm" #include "code\datums\components\force_move.dm" #include "code\datums\components\fov_handler.dm" @@ -1000,25 +1098,36 @@ #include "code\datums\components\itembound.dm" #include "code\datums\components\itempicky.dm" #include "code\datums\components\jetpack.dm" +#include "code\datums\components\joint_damage.dm" #include "code\datums\components\jousting.dm" +#include "code\datums\components\jukebox.dm" #include "code\datums\components\keep_me_secure.dm" #include "code\datums\components\knockoff.dm" #include "code\datums\components\label.dm" #include "code\datums\components\leash.dm" +#include "code\datums\components\life_link.dm" #include "code\datums\components\light_eater.dm" +#include "code\datums\components\ling_decoy_brain.dm" +#include "code\datums\components\listen_and_repeat.dm" #include "code\datums\components\lock_on_cursor.dm" +#include "code\datums\components\lockable_storage.dm" #include "code\datums\components\magnet.dm" #include "code\datums\components\manual_blinking.dm" #include "code\datums\components\manual_breathing.dm" +#include "code\datums\components\manual_heart.dm" +#include "code\datums\components\marionette.dm" #include "code\datums\components\mind_linker.dm" #include "code\datums\components\mirv.dm" +#include "code\datums\components\mob_chain.dm" #include "code\datums\components\mob_harvest.dm" #include "code\datums\components\multiple_lives.dm" #include "code\datums\components\mutant_hands.dm" #include "code\datums\components\nuclear_bomb_operator.dm" +#include "code\datums\components\object_possession.dm" #include "code\datums\components\omen.dm" #include "code\datums\components\on_hit_effect.dm" #include "code\datums\components\onwear_mood.dm" +#include "code\datums\components\orbit_poll.dm" #include "code\datums\components\orbiter.dm" #include "code\datums\components\overlay_lighting.dm" #include "code\datums\components\palette.dm" @@ -1027,15 +1136,18 @@ #include "code\datums\components\pellet_cloud.dm" #include "code\datums\components\phylactery.dm" #include "code\datums\components\pinata.dm" +#include "code\datums\components\pinnable_accessory.dm" +#include "code\datums\components\plundering_attacks.dm" #include "code\datums\components\pricetag.dm" -#include "code\datums\components\pry_open_door.dm" #include "code\datums\components\punchcooldown.dm" #include "code\datums\components\puzzgrid.dm" #include "code\datums\components\radiation_countdown.dm" #include "code\datums\components\radioactive_emitter.dm" #include "code\datums\components\radioactive_exposure.dm" #include "code\datums\components\ranged_attacks.dm" +#include "code\datums\components\ranged_mob_full_auto.dm" #include "code\datums\components\reagent_refiller.dm" +#include "code\datums\components\recharging_attacks.dm" #include "code\datums\components\redirect_attack_hand_from_turf.dm" #include "code\datums\components\reflection.dm" #include "code\datums\components\regenerator.dm" @@ -1047,6 +1159,7 @@ #include "code\datums\components\scope.dm" #include "code\datums\components\seclight_attachable.dm" #include "code\datums\components\sect_nullrod_bonus.dm" +#include "code\datums\components\security_vision.dm" #include "code\datums\components\seethrough.dm" #include "code\datums\components\seethrough_mob.dm" #include "code\datums\components\shell.dm" @@ -1093,11 +1206,13 @@ #include "code\datums\components\technoshy.dm" #include "code\datums\components\telegraph_ability.dm" #include "code\datums\components\temporary_body.dm" +#include "code\datums\components\temporary_description.dm" #include "code\datums\components\tether.dm" #include "code\datums\components\thermite.dm" #include "code\datums\components\tippable.dm" #include "code\datums\components\toggle_attached_clothing.dm" #include "code\datums\components\toggle_suit.dm" +#include "code\datums\components\torn_wall.dm" #include "code\datums\components\transforming.dm" #include "code\datums\components\trapdoor.dm" #include "code\datums\components\tree_climber.dm" @@ -1109,6 +1224,7 @@ #include "code\datums\components\uplink.dm" #include "code\datums\components\usb_port.dm" #include "code\datums\components\vacuum.dm" +#include "code\datums\components\vision_hurting.dm" #include "code\datums\components\wall_mounted.dm" #include "code\datums\components\wearertargeting.dm" #include "code\datums\components\weatherannouncer.dm" @@ -1129,6 +1245,7 @@ #include "code\datums\components\crafting\misc.dm" #include "code\datums\components\crafting\ranged_weapon.dm" #include "code\datums\components\crafting\robot.dm" +#include "code\datums\components\crafting\slapcrafting.dm" #include "code\datums\components\crafting\structures.dm" #include "code\datums\components\crafting\tailoring.dm" #include "code\datums\components\crafting\tiles.dm" @@ -1153,7 +1270,6 @@ #include "code\datums\components\plumbing\_plumbing.dm" #include "code\datums\components\plumbing\chemical_acclimator.dm" #include "code\datums\components\plumbing\filter.dm" -#include "code\datums\components\plumbing\IV_drip.dm" #include "code\datums\components\plumbing\reaction_chamber.dm" #include "code\datums\components\plumbing\splitter.dm" #include "code\datums\components\riding\riding.dm" @@ -1161,9 +1277,11 @@ #include "code\datums\components\riding\riding_vehicle.dm" #include "code\datums\components\style\style.dm" #include "code\datums\components\style\style_meter.dm" +#include "code\datums\components\trader\trader.dm" #include "code\datums\diseases\_disease.dm" #include "code\datums\diseases\_MobProcs.dm" #include "code\datums\diseases\adrenal_crisis.dm" +#include "code\datums\diseases\anaphylaxis.dm" #include "code\datums\diseases\anxiety.dm" #include "code\datums\diseases\beesease.dm" #include "code\datums\diseases\brainrot.dm" @@ -1228,6 +1346,7 @@ #include "code\datums\elements\ai_flee_while_injured.dm" #include "code\datums\elements\ai_held_item.dm" #include "code\datums\elements\ai_retaliate.dm" +#include "code\datums\elements\ai_swap_combat_mode.dm" #include "code\datums\elements\ai_target_damagesource.dm" #include "code\datums\elements\amputating_limbs.dm" #include "code\datums\elements\animal_variety.dm" @@ -1256,6 +1375,7 @@ #include "code\datums\elements\cliff_walker.dm" #include "code\datums\elements\climbable.dm" #include "code\datums\elements\connect_loc.dm" +#include "code\datums\elements\consumable_mob.dm" #include "code\datums\elements\content_barfer.dm" #include "code\datums\elements\crackable.dm" #include "code\datums\elements\crusher_loot.dm" @@ -1264,20 +1384,24 @@ #include "code\datums\elements\cult_halo.dm" #include "code\datums\elements\curse_announcement.dm" #include "code\datums\elements\cursed.dm" +#include "code\datums\elements\damage_threshold.dm" #include "code\datums\elements\dangerous_surgical_removal.dm" #include "code\datums\elements\death_drops.dm" #include "code\datums\elements\death_explosion.dm" #include "code\datums\elements\death_gases.dm" -#include "code\datums\elements\death_linked.dm" #include "code\datums\elements\delete_on_drop.dm" #include "code\datums\elements\deliver_first.dm" +#include "code\datums\elements\dextrous.dm" #include "code\datums\elements\diggable.dm" #include "code\datums\elements\digitalcamo.dm" +#include "code\datums\elements\disarm_attack.dm" +#include "code\datums\elements\door_pryer.dm" #include "code\datums\elements\drag_pickup.dm" #include "code\datums\elements\dryable.dm" #include "code\datums\elements\earhealing.dm" #include "code\datums\elements\easily_fragmented.dm" #include "code\datums\elements\effect_trail.dm" +#include "code\datums\elements\elevation.dm" #include "code\datums\elements\embed.dm" #include "code\datums\elements\empprotection.dm" #include "code\datums\elements\envenomable_casing.dm" @@ -1291,9 +1415,12 @@ #include "code\datums\elements\frozen.dm" #include "code\datums\elements\gags_recolorable.dm" #include "code\datums\elements\give_turf_traits.dm" +#include "code\datums\elements\hat_wearer.dm" #include "code\datums\elements\haunted.dm" #include "code\datums\elements\high_fiver.dm" #include "code\datums\elements\honkspam.dm" +#include "code\datums\elements\hostile_machine.dm" +#include "code\datums\elements\human_biter.dm" #include "code\datums\elements\immerse.dm" #include "code\datums\elements\item_fov.dm" #include "code\datums\elements\item_scaling.dm" @@ -1309,6 +1436,7 @@ #include "code\datums\elements\mirage_border.dm" #include "code\datums\elements\mob_grabber.dm" #include "code\datums\elements\mob_killed_tally.dm" +#include "code\datums\elements\move_force_on_death.dm" #include "code\datums\elements\movement_turf_changer.dm" #include "code\datums\elements\movetype_handler.dm" #include "code\datums\elements\nerfed_pulling.dm" @@ -1316,7 +1444,9 @@ #include "code\datums\elements\noticable_organ.dm" #include "code\datums\elements\obj_regen.dm" #include "code\datums\elements\openspace_item_click_handler.dm" +#include "code\datums\elements\ore_collecting.dm" #include "code\datums\elements\organ_set_bonus.dm" +#include "code\datums\elements\permanent_fire_overlay.dm" #include "code\datums\elements\pet_bonus.dm" #include "code\datums\elements\plant_backfire.dm" #include "code\datums\elements\point_of_interest.dm" @@ -1340,10 +1470,11 @@ #include "code\datums\elements\soft_landing.dm" #include "code\datums\elements\spooky.dm" #include "code\datums\elements\squish.dm" +#include "code\datums\elements\squish_sound.dm" #include "code\datums\elements\sticker.dm" #include "code\datums\elements\strippable.dm" +#include "code\datums\elements\structure_repair.dm" #include "code\datums\elements\swabbable.dm" -#include "code\datums\elements\tear_wall.dm" #include "code\datums\elements\temporary_atom.dm" #include "code\datums\elements\tenacious.dm" #include "code\datums\elements\tiny_mob_hunter.dm" @@ -1359,9 +1490,12 @@ #include "code\datums\elements\waddling.dm" #include "code\datums\elements\wall_engraver.dm" #include "code\datums\elements\wall_smasher.dm" +#include "code\datums\elements\wall_tearer.dm" +#include "code\datums\elements\wall_walker.dm" #include "code\datums\elements\weapon_description.dm" #include "code\datums\elements\weather_listener.dm" #include "code\datums\elements\web_walker.dm" +#include "code\datums\elements\wheel.dm" #include "code\datums\elements\decals\_decal.dm" #include "code\datums\elements\decals\blood.dm" #include "code\datums\elements\food\dunkable.dm" @@ -1420,13 +1554,15 @@ #include "code\datums\keybinding\robot.dm" #include "code\datums\looping_sounds\_looping_sound.dm" #include "code\datums\looping_sounds\acid.dm" +#include "code\datums\looping_sounds\burning.dm" #include "code\datums\looping_sounds\choking.dm" #include "code\datums\looping_sounds\cyborg.dm" #include "code\datums\looping_sounds\item_sounds.dm" #include "code\datums\looping_sounds\machinery_sounds.dm" #include "code\datums\looping_sounds\music.dm" +#include "code\datums\looping_sounds\projectiles.dm" +#include "code\datums\looping_sounds\vents.dm" #include "code\datums\looping_sounds\weather.dm" -#include "code\datums\mapgen\_MapGenerator.dm" #include "code\datums\mapgen\CaveGenerator.dm" #include "code\datums\mapgen\JungleGenerator.dm" #include "code\datums\mapgen\biomes\_biome.dm" @@ -1435,7 +1571,6 @@ #include "code\datums\martial\_martial.dm" #include "code\datums\martial\boxing.dm" #include "code\datums\martial\cqc.dm" -#include "code\datums\martial\hugs_of_the_gondola.dm" #include "code\datums\martial\krav_maga.dm" #include "code\datums\martial\mushpunch.dm" #include "code\datums\martial\plasma_fist.dm" @@ -1464,6 +1599,7 @@ #include "code\datums\mood_events\dna_infuser_events.dm" #include "code\datums\mood_events\drink_events.dm" #include "code\datums\mood_events\drug_events.dm" +#include "code\datums\mood_events\eldritch_painting_events.dm" #include "code\datums\mood_events\food_events.dm" #include "code\datums\mood_events\generic_negative_events.dm" #include "code\datums\mood_events\generic_positive_events.dm" @@ -1496,6 +1632,8 @@ #include "code\datums\proximity_monitor\fields\projectile_dampener.dm" #include "code\datums\proximity_monitor\fields\timestop.dm" #include "code\datums\quirks\_quirk.dm" +#include "code\datums\quirks\_quirk_constant_data.dm" +#include "code\datums\quirks\negative_quirks\all_nighter.dm" #include "code\datums\quirks\negative_quirks\allergic.dm" #include "code\datums\quirks\negative_quirks\bad_back.dm" #include "code\datums\quirks\negative_quirks\bad_touch.dm" @@ -1511,9 +1649,9 @@ #include "code\datums\quirks\negative_quirks\deafness.dm" #include "code\datums\quirks\negative_quirks\depression.dm" #include "code\datums\quirks\negative_quirks\family_heirloom.dm" +#include "code\datums\quirks\negative_quirks\food_allergy.dm" #include "code\datums\quirks\negative_quirks\frail.dm" #include "code\datums\quirks\negative_quirks\glass_jaw.dm" -#include "code\datums\quirks\negative_quirks\heavy_sleeper.dm" #include "code\datums\quirks\negative_quirks\hemiplegic.dm" #include "code\datums\quirks\negative_quirks\hypersensitive.dm" #include "code\datums\quirks\negative_quirks\illiterate.dm" @@ -1538,6 +1676,7 @@ #include "code\datums\quirks\negative_quirks\softspoken.dm" #include "code\datums\quirks\negative_quirks\tin_man.dm" #include "code\datums\quirks\negative_quirks\unstable.dm" +#include "code\datums\quirks\negative_quirks\unusual.dm" #include "code\datums\quirks\neutral_quirks\bald.dm" #include "code\datums\quirks\neutral_quirks\colorist.dm" #include "code\datums\quirks\neutral_quirks\deviant_tastes.dm" @@ -1555,6 +1694,7 @@ #include "code\datums\quirks\neutral_quirks\pride_pin.dm" #include "code\datums\quirks\neutral_quirks\shifty_eyes.dm" #include "code\datums\quirks\neutral_quirks\snob.dm" +#include "code\datums\quirks\neutral_quirks\transhumanist.dm" #include "code\datums\quirks\neutral_quirks\vegetarian.dm" #include "code\datums\quirks\positive_quirks\alcohol_tolerance.dm" #include "code\datums\quirks\positive_quirks\apathetic.dm" @@ -1610,10 +1750,12 @@ #include "code\datums\skills\_skill.dm" #include "code\datums\skills\cleaning.dm" #include "code\datums\skills\fishing.dm" +#include "code\datums\skills\fitness.dm" #include "code\datums\skills\gaming.dm" #include "code\datums\skills\mining.dm" #include "code\datums\station_traits\_station_trait.dm" #include "code\datums\station_traits\admin_panel.dm" +#include "code\datums\station_traits\job_traits.dm" #include "code\datums\station_traits\negative_traits.dm" #include "code\datums\station_traits\neutral_traits.dm" #include "code\datums\station_traits\positive_traits.dm" @@ -1632,20 +1774,24 @@ #include "code\datums\status_effects\wound_effects.dm" #include "code\datums\status_effects\buffs\food_haste.dm" #include "code\datums\status_effects\buffs\food_traits.dm" +#include "code\datums\status_effects\buffs\stop_drop_roll.dm" #include "code\datums\status_effects\buffs\stun_absorption.dm" #include "code\datums\status_effects\debuffs\blindness.dm" #include "code\datums\status_effects\debuffs\choke.dm" #include "code\datums\status_effects\debuffs\confusion.dm" #include "code\datums\status_effects\debuffs\cursed.dm" +#include "code\datums\status_effects\debuffs\cyborg.dm" #include "code\datums\status_effects\debuffs\debuffs.dm" #include "code\datums\status_effects\debuffs\decloning.dm" #include "code\datums\status_effects\debuffs\dizziness.dm" +#include "code\datums\status_effects\debuffs\dna_transformation.dm" #include "code\datums\status_effects\debuffs\drowsiness.dm" #include "code\datums\status_effects\debuffs\drugginess.dm" #include "code\datums\status_effects\debuffs\drunk.dm" #include "code\datums\status_effects\debuffs\fire_stacks.dm" #include "code\datums\status_effects\debuffs\genetic_damage.dm" #include "code\datums\status_effects\debuffs\hallucination.dm" +#include "code\datums\status_effects\debuffs\hooked.dm" #include "code\datums\status_effects\debuffs\jitteriness.dm" #include "code\datums\status_effects\debuffs\pacifism.dm" #include "code\datums\status_effects\debuffs\screen_blur.dm" @@ -1654,6 +1800,8 @@ #include "code\datums\status_effects\debuffs\slimed.dm" #include "code\datums\status_effects\debuffs\spacer.dm" #include "code\datums\status_effects\debuffs\speech_debuffs.dm" +#include "code\datums\status_effects\debuffs\staggered.dm" +#include "code\datums\status_effects\debuffs\static_vision.dm" #include "code\datums\status_effects\debuffs\strandling.dm" #include "code\datums\status_effects\debuffs\terrified.dm" #include "code\datums\status_effects\debuffs\tower_of_babel.dm" @@ -1686,6 +1834,7 @@ #include "code\datums\wires\airlock.dm" #include "code\datums\wires\apc.dm" #include "code\datums\wires\autolathe.dm" +#include "code\datums\wires\brm.dm" #include "code\datums\wires\conveyor.dm" #include "code\datums\wires\ecto_sniffer.dm" #include "code\datums\wires\emitter.dm" @@ -1701,6 +1850,7 @@ #include "code\datums\wires\robot.dm" #include "code\datums\wires\roulette.dm" #include "code\datums\wires\scanner_gate.dm" +#include "code\datums\wires\shieldwallgen.dm" #include "code\datums\wires\suit_storage_unit.dm" #include "code\datums\wires\syndicatebomb.dm" #include "code\datums\wires\tesla_coil.dm" @@ -1710,14 +1860,11 @@ #include "code\datums\wounds\blunt.dm" #include "code\datums\wounds\bones.dm" #include "code\datums\wounds\burns.dm" +#include "code\datums\wounds\cranial_fissure.dm" #include "code\datums\wounds\loss.dm" #include "code\datums\wounds\pierce.dm" #include "code\datums\wounds\slash.dm" #include "code\datums\wounds\scars\_scars.dm" -#include "code\game\alternate_appearance.dm" -#include "code\game\atom_defense.dm" -#include "code\game\atoms.dm" -#include "code\game\atoms_initializing_EXPENSIVE.dm" #include "code\game\atoms_movable.dm" #include "code\game\communications.dm" #include "code\game\data_huds.dm" @@ -1753,21 +1900,25 @@ #include "code\game\area\areas\station\service.dm" #include "code\game\area\areas\station\solars.dm" #include "code\game\area\areas\station\telecomm.dm" +#include "code\game\atom\_atom.dm" +#include "code\game\atom\alternate_appearance.dm" +#include "code\game\atom\atom_act.dm" +#include "code\game\atom\atom_appearance.dm" +#include "code\game\atom\atom_color.dm" +#include "code\game\atom\atom_defense.dm" +#include "code\game\atom\atom_examine.dm" +#include "code\game\atom\atom_greyscale.dm" +#include "code\game\atom\atom_invisibility.dm" +#include "code\game\atom\atom_materials.dm" +#include "code\game\atom\atom_merger.dm" +#include "code\game\atom\atom_orbit.dm" +#include "code\game\atom\atom_storage.dm" +#include "code\game\atom\atom_tool_acts.dm" +#include "code\game\atom\atom_vv.dm" +#include "code\game\atom\atoms_initializing_EXPENSIVE.dm" #include "code\game\gamemodes\events.dm" -#include "code\game\gamemodes\game_mode.dm" #include "code\game\gamemodes\objective.dm" #include "code\game\gamemodes\objective_items.dm" -#include "code\game\gamemodes\dynamic\dynamic.dm" -#include "code\game\gamemodes\dynamic\dynamic_hijacking.dm" -#include "code\game\gamemodes\dynamic\dynamic_logging.dm" -#include "code\game\gamemodes\dynamic\dynamic_midround_rolling.dm" -#include "code\game\gamemodes\dynamic\dynamic_rulesets.dm" -#include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm" -#include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm" -#include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm" -#include "code\game\gamemodes\dynamic\dynamic_simulations.dm" -#include "code\game\gamemodes\dynamic\dynamic_unfavorable_situation.dm" -#include "code\game\gamemodes\dynamic\ruleset_picking.dm" #include "code\game\machinery\_machinery.dm" #include "code\game\machinery\ai_slipper.dm" #include "code\game\machinery\airlock_control.dm" @@ -1777,7 +1928,6 @@ #include "code\game\machinery\barsigns.dm" #include "code\game\machinery\botlaunchpad.dm" #include "code\game\machinery\buttons.dm" -#include "code\game\machinery\canister_frame.dm" #include "code\game\machinery\cell_charger.dm" #include "code\game\machinery\civilian_bounties.dm" #include "code\game\machinery\constructable_frame.dm" @@ -1970,6 +2120,7 @@ #include "code\game\objects\effects\poster_demotivational.dm" #include "code\game\objects\effects\poster_motivational.dm" #include "code\game\objects\effects\powerup.dm" +#include "code\game\objects\effects\rcd.dm" #include "code\game\objects\effects\spiderwebs.dm" #include "code\game\objects\effects\step_triggers.dm" #include "code\game\objects\effects\wanted_poster.dm" @@ -2026,6 +2177,7 @@ #include "code\game\objects\effects\spawners\random\ai_module.dm" #include "code\game\objects\effects\spawners\random\animalhide.dm" #include "code\game\objects\effects\spawners\random\armory.dm" +#include "code\game\objects\effects\spawners\random\bedsheet.dm" #include "code\game\objects\effects\spawners\random\bureaucracy.dm" #include "code\game\objects\effects\spawners\random\clothing.dm" #include "code\game\objects\effects\spawners\random\contraband.dm" @@ -2093,6 +2245,7 @@ #include "code\game\objects\items\extinguisher.dm" #include "code\game\objects\items\fireaxe.dm" #include "code\game\objects\items\flamethrower.dm" +#include "code\game\objects\items\frog_statue.dm" #include "code\game\objects\items\gift.dm" #include "code\game\objects\items\gun_maintenance.dm" #include "code\game\objects\items\hand_items.dm" @@ -2131,7 +2284,6 @@ #include "code\game\objects\items\shooting_range.dm" #include "code\game\objects\items\shrapnel.dm" #include "code\game\objects\items\signs.dm" -#include "code\game\objects\items\singularityhammer.dm" #include "code\game\objects\items\skub.dm" #include "code\game\objects\items\spear.dm" #include "code\game\objects\items\sticker.dm" @@ -2141,6 +2293,7 @@ #include "code\game\objects\items\taster.dm" #include "code\game\objects\items\teleportation.dm" #include "code\game\objects\items\theft_tools.dm" +#include "code\game\objects\items\tongs.dm" #include "code\game\objects\items\toy_mechs.dm" #include "code\game\objects\items\toys.dm" #include "code\game\objects\items\trash.dm" @@ -2149,6 +2302,8 @@ #include "code\game\objects\items\virgin_mary.dm" #include "code\game\objects\items\wall_mounted.dm" #include "code\game\objects\items\weaponry.dm" +#include "code\game\objects\items\wiki_manuals.dm" +#include "code\game\objects\items\wizard_weapons.dm" #include "code\game\objects\items\AI_modules\_AI_modules.dm" #include "code\game\objects\items\AI_modules\freeform.dm" #include "code\game\objects\items\AI_modules\full_lawsets.dm" @@ -2162,6 +2317,7 @@ #include "code\game\objects\items\circuitboards\machines\engine_circuitboards.dm" #include "code\game\objects\items\circuitboards\machines\machine_circuitboards.dm" #include "code\game\objects\items\devices\aicard.dm" +#include "code\game\objects\items\devices\aicard_evil.dm" #include "code\game\objects\items\devices\anomaly_neutralizer.dm" #include "code\game\objects\items\devices\anomaly_releaser.dm" #include "code\game\objects\items\devices\beacon.dm" @@ -2179,7 +2335,6 @@ #include "code\game\objects\items\devices\multitool.dm" #include "code\game\objects\items\devices\pipe_painter.dm" #include "code\game\objects\items\devices\polycircuit.dm" -#include "code\game\objects\items\devices\portable_chem_mixer.dm" #include "code\game\objects\items\devices\powersink.dm" #include "code\game\objects\items\devices\pressureplates.dm" #include "code\game\objects\items\devices\quantum_keycard.dm" @@ -2245,6 +2400,7 @@ #include "code\game\objects\items\granters\crafting\death_sandwich.dm" #include "code\game\objects\items\granters\crafting\desserts.dm" #include "code\game\objects\items\granters\crafting\pipegun.dm" +#include "code\game\objects\items\granters\crafting\rebarxbowsyndie.dm" #include "code\game\objects\items\granters\crafting\regal_condor.dm" #include "code\game\objects\items\granters\magic\_spell_granter.dm" #include "code\game\objects\items\granters\magic\barnyard.dm" @@ -2280,10 +2436,8 @@ #include "code\game\objects\items\grenades\syndieminibomb.dm" #include "code\game\objects\items\implants\implant.dm" #include "code\game\objects\items\implants\implant_abductor.dm" -#include "code\game\objects\items\implants\implant_chem.dm" #include "code\game\objects\items\implants\implant_clown.dm" #include "code\game\objects\items\implants\implant_deathrattle.dm" -#include "code\game\objects\items\implants\implant_exile.dm" #include "code\game\objects\items\implants\implant_explosive.dm" #include "code\game\objects\items\implants\implant_freedom.dm" #include "code\game\objects\items\implants\implant_krav_maga.dm" @@ -2292,12 +2446,16 @@ #include "code\game\objects\items\implants\implant_spell.dm" #include "code\game\objects\items\implants\implant_stealth.dm" #include "code\game\objects\items\implants\implant_storage.dm" -#include "code\game\objects\items\implants\implant_track.dm" #include "code\game\objects\items\implants\implantcase.dm" #include "code\game\objects\items\implants\implantchair.dm" #include "code\game\objects\items\implants\implanter.dm" #include "code\game\objects\items\implants\implantpad.dm" #include "code\game\objects\items\implants\implantuplink.dm" +#include "code\game\objects\items\implants\security\implant_beacon.dm" +#include "code\game\objects\items\implants\security\implant_chem.dm" +#include "code\game\objects\items\implants\security\implant_exile.dm" +#include "code\game\objects\items\implants\security\implant_noteleport.dm" +#include "code\game\objects\items\implants\security\implant_track.dm" #include "code\game\objects\items\kirby_plants\kirbyplants.dm" #include "code\game\objects\items\kirby_plants\organic_plants.dm" #include "code\game\objects\items\kirby_plants\synthetic_plants.dm" @@ -2360,7 +2518,6 @@ #include "code\game\objects\items\storage\holsters.dm" #include "code\game\objects\items\storage\lockbox.dm" #include "code\game\objects\items\storage\medkit.dm" -#include "code\game\objects\items\storage\secure.dm" #include "code\game\objects\items\storage\sixpack.dm" #include "code\game\objects\items\storage\storage.dm" #include "code\game\objects\items\storage\toolbox.dm" @@ -2371,6 +2528,7 @@ #include "code\game\objects\items\storage\boxes\clothes_boxes.dm" #include "code\game\objects\items\storage\boxes\engineering_boxes.dm" #include "code\game\objects\items\storage\boxes\food_boxes.dm" +#include "code\game\objects\items\storage\boxes\implant_boxes.dm" #include "code\game\objects\items\storage\boxes\job_boxes.dm" #include "code\game\objects\items\storage\boxes\medical_boxes.dm" #include "code\game\objects\items\storage\boxes\science_boxes.dm" @@ -2395,6 +2553,7 @@ #include "code\game\objects\structures\billboard.dm" #include "code\game\objects\structures\bonfire.dm" #include "code\game\objects\structures\broken_flooring.dm" +#include "code\game\objects\structures\cat_house.dm" #include "code\game\objects\structures\chess.dm" #include "code\game\objects\structures\containers.dm" #include "code\game\objects\structures\deployable_turret.dm" @@ -2435,12 +2594,14 @@ #include "code\game\objects\structures\morgue.dm" #include "code\game\objects\structures\mystery_box.dm" #include "code\game\objects\structures\noticeboard.dm" +#include "code\game\objects\structures\ore_containers.dm" #include "code\game\objects\structures\petrified_statue.dm" #include "code\game\objects\structures\pinatas.dm" #include "code\game\objects\structures\plasticflaps.dm" #include "code\game\objects\structures\railings.dm" #include "code\game\objects\structures\reflector.dm" #include "code\game\objects\structures\safe.dm" +#include "code\game\objects\structures\secure_safe.dm" #include "code\game\objects\structures\showcase.dm" #include "code\game\objects\structures\shower.dm" #include "code\game\objects\structures\spawner.dm" @@ -2506,6 +2667,7 @@ #include "code\game\objects\structures\icemoon\cave_entrance.dm" #include "code\game\objects\structures\lavaland\geyser.dm" #include "code\game\objects\structures\lavaland\necropolis_tendril.dm" +#include "code\game\objects\structures\lavaland\ore_vent.dm" #include "code\game\objects\structures\plaques\_plaques.dm" #include "code\game\objects\structures\plaques\static_plaques.dm" #include "code\game\objects\structures\signs\_signs.dm" @@ -2645,7 +2807,6 @@ #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\ai_triumvirate.dm" #include "code\modules\admin\verbs\anonymousnames.dm" #include "code\modules\admin\verbs\atmosdebug.dm" #include "code\modules\admin\verbs\beakerpanel.dm" @@ -2664,6 +2825,7 @@ #include "code\modules\admin\verbs\fps.dm" #include "code\modules\admin\verbs\getlogs.dm" #include "code\modules\admin\verbs\ghost_pool_protection.dm" +#include "code\modules\admin\verbs\grant_dna_infusion.dm" #include "code\modules\admin\verbs\hiddenprints.dm" #include "code\modules\admin\verbs\highlander_datum.dm" #include "code\modules\admin\verbs\individual_logging.dm" @@ -2672,6 +2834,7 @@ #include "code\modules\admin\verbs\list_exposer.dm" #include "code\modules\admin\verbs\machine_upgrade.dm" #include "code\modules\admin\verbs\manipulate_organs.dm" +#include "code\modules\admin\verbs\map_export.dm" #include "code\modules\admin\verbs\map_template_loadverb.dm" #include "code\modules\admin\verbs\mapping.dm" #include "code\modules\admin\verbs\maprotation.dm" @@ -2798,6 +2961,7 @@ #include "code\modules\antagonists\changeling\powers\headcrab.dm" #include "code\modules\antagonists\changeling\powers\lesserform.dm" #include "code\modules\antagonists\changeling\powers\mimic_voice.dm" +#include "code\modules\antagonists\changeling\powers\mmi_talk.dm" #include "code\modules\antagonists\changeling\powers\mutations.dm" #include "code\modules\antagonists\changeling\powers\panacea.dm" #include "code\modules\antagonists\changeling\powers\pheromone_receptors.dm" @@ -2807,6 +2971,7 @@ #include "code\modules\antagonists\changeling\powers\strained_muscles.dm" #include "code\modules\antagonists\changeling\powers\tiny_prick.dm" #include "code\modules\antagonists\changeling\powers\transform.dm" +#include "code\modules\antagonists\changeling\powers\void_adaption.dm" #include "code\modules\antagonists\clown_ops\bananium_bomb.dm" #include "code\modules\antagonists\clown_ops\clown_weapons.dm" #include "code\modules\antagonists\clown_ops\clownop.dm" @@ -2847,29 +3012,34 @@ #include "code\modules\antagonists\heretic\heretic_monsters.dm" #include "code\modules\antagonists\heretic\influences.dm" #include "code\modules\antagonists\heretic\knife_effect.dm" +#include "code\modules\antagonists\heretic\moon_lunatic.dm" #include "code\modules\antagonists\heretic\rust_effect.dm" #include "code\modules\antagonists\heretic\transmutation_rune.dm" #include "code\modules\antagonists\heretic\items\eldritch_flask.dm" +#include "code\modules\antagonists\heretic\items\eldritch_painting.dm" #include "code\modules\antagonists\heretic\items\forbidden_book.dm" #include "code\modules\antagonists\heretic\items\heretic_armor.dm" #include "code\modules\antagonists\heretic\items\heretic_blades.dm" #include "code\modules\antagonists\heretic\items\heretic_necks.dm" #include "code\modules\antagonists\heretic\items\hunter_rifle.dm" #include "code\modules\antagonists\heretic\items\keyring.dm" -#include "code\modules\antagonists\heretic\items\lintel.dm" +#include "code\modules\antagonists\heretic\items\labyrinth_handbook.dm" #include "code\modules\antagonists\heretic\items\madness_mask.dm" +#include "code\modules\antagonists\heretic\items\unfathomable_curio.dm" #include "code\modules\antagonists\heretic\knowledge\ash_lore.dm" #include "code\modules\antagonists\heretic\knowledge\blade_lore.dm" #include "code\modules\antagonists\heretic\knowledge\cosmic_lore.dm" #include "code\modules\antagonists\heretic\knowledge\flesh_lore.dm" #include "code\modules\antagonists\heretic\knowledge\general_side.dm" -#include "code\modules\antagonists\heretic\knowledge\knock_lore.dm" +#include "code\modules\antagonists\heretic\knowledge\lock_lore.dm" +#include "code\modules\antagonists\heretic\knowledge\moon_lore.dm" #include "code\modules\antagonists\heretic\knowledge\rust_lore.dm" -#include "code\modules\antagonists\heretic\knowledge\side_ash_flesh.dm" +#include "code\modules\antagonists\heretic\knowledge\side_ash_moon.dm" #include "code\modules\antagonists\heretic\knowledge\side_blade_rust.dm" #include "code\modules\antagonists\heretic\knowledge\side_cosmos_ash.dm" #include "code\modules\antagonists\heretic\knowledge\side_flesh_void.dm" -#include "code\modules\antagonists\heretic\knowledge\side_knock_flesh.dm" +#include "code\modules\antagonists\heretic\knowledge\side_lock_flesh.dm" +#include "code\modules\antagonists\heretic\knowledge\side_lock_moon.dm" #include "code\modules\antagonists\heretic\knowledge\side_rust_cosmos.dm" #include "code\modules\antagonists\heretic\knowledge\side_void_blade.dm" #include "code\modules\antagonists\heretic\knowledge\starting_lore.dm" @@ -2898,10 +3068,15 @@ #include "code\modules\antagonists\heretic\magic\flesh_ascension.dm" #include "code\modules\antagonists\heretic\magic\flesh_surgery.dm" #include "code\modules\antagonists\heretic\magic\furious_steel.dm" +#include "code\modules\antagonists\heretic\magic\lunatic_track.dm" #include "code\modules\antagonists\heretic\magic\madness_touch.dm" #include "code\modules\antagonists\heretic\magic\manse_link.dm" #include "code\modules\antagonists\heretic\magic\mansus_grasp.dm" +#include "code\modules\antagonists\heretic\magic\mind_gate.dm" #include "code\modules\antagonists\heretic\magic\mirror_walk.dm" +#include "code\modules\antagonists\heretic\magic\moon_parade.dm" +#include "code\modules\antagonists\heretic\magic\moon_ringleader.dm" +#include "code\modules\antagonists\heretic\magic\moon_smile.dm" #include "code\modules\antagonists\heretic\magic\nightwatcher_rebirth.dm" #include "code\modules\antagonists\heretic\magic\realignment.dm" #include "code\modules\antagonists\heretic\magic\rust_charge.dm" @@ -2915,13 +3090,12 @@ #include "code\modules\antagonists\heretic\magic\void_phase.dm" #include "code\modules\antagonists\heretic\magic\void_pull.dm" #include "code\modules\antagonists\heretic\magic\wave_of_desperation.dm" -#include "code\modules\antagonists\heretic\mobs\maid_in_mirror.dm" #include "code\modules\antagonists\heretic\status_effects\buffs.dm" #include "code\modules\antagonists\heretic\status_effects\debuffs.dm" #include "code\modules\antagonists\heretic\status_effects\ghoul.dm" #include "code\modules\antagonists\heretic\status_effects\mark_effects.dm" #include "code\modules\antagonists\heretic\structures\carving_knife.dm" -#include "code\modules\antagonists\heretic\structures\knock_final.dm" +#include "code\modules\antagonists\heretic\structures\lock_final.dm" #include "code\modules\antagonists\heretic\structures\mawed_crucible.dm" #include "code\modules\antagonists\highlander\highlander.dm" #include "code\modules\antagonists\hypnotized\hypnotized.dm" @@ -2961,7 +3135,6 @@ #include "code\modules\antagonists\pirate\pirate_shuttle_equipment.dm" #include "code\modules\antagonists\pyro_slime\pyro_slime.dm" #include "code\modules\antagonists\revenant\haunted_item.dm" -#include "code\modules\antagonists\revenant\revenant_abilities.dm" #include "code\modules\antagonists\revenant\revenant_antag.dm" #include "code\modules\antagonists\revenant\revenant_blight.dm" #include "code\modules\antagonists\revolution\enemy_of_the_state.dm" @@ -2978,6 +3151,7 @@ #include "code\modules\antagonists\space_ninja\space_ninja.dm" #include "code\modules\antagonists\spiders\spiders.dm" #include "code\modules\antagonists\survivalist\survivalist.dm" +#include "code\modules\antagonists\syndicate_monkey\syndicate_monkey.dm" #include "code\modules\antagonists\traitor\balance_helper.dm" #include "code\modules\antagonists\traitor\datum_traitor.dm" #include "code\modules\antagonists\traitor\objective_category.dm" @@ -2987,6 +3161,10 @@ #include "code\modules\antagonists\traitor\components\traitor_objective_helpers.dm" #include "code\modules\antagonists\traitor\components\traitor_objective_limit_per_time.dm" #include "code\modules\antagonists\traitor\components\traitor_objective_mind_tracker.dm" +#include "code\modules\antagonists\traitor\contractor\contract_teammate.dm" +#include "code\modules\antagonists\traitor\contractor\contractor_hub.dm" +#include "code\modules\antagonists\traitor\contractor\contractor_items.dm" +#include "code\modules\antagonists\traitor\contractor\syndicate_contract.dm" #include "code\modules\antagonists\traitor\objectives\assassination.dm" #include "code\modules\antagonists\traitor\objectives\demoralise_assault.dm" #include "code\modules\antagonists\traitor\objectives\destroy_heirloom.dm" @@ -3029,14 +3207,20 @@ #include "code\modules\antagonists\wizard\equipment\spellbook_entries\summons.dm" #include "code\modules\antagonists\wizard\grand_ritual\fluff.dm" #include "code\modules\antagonists\wizard\grand_ritual\grand_ritual.dm" -#include "code\modules\antagonists\wizard\grand_ritual\grand_ritual_finale.dm" #include "code\modules\antagonists\wizard\grand_ritual\grand_rune.dm" #include "code\modules\antagonists\wizard\grand_ritual\grand_side_effect.dm" +#include "code\modules\antagonists\wizard\grand_ritual\finales\all_access.dm" +#include "code\modules\antagonists\wizard\grand_ritual\finales\armageddon.dm" +#include "code\modules\antagonists\wizard\grand_ritual\finales\captaincy.dm" +#include "code\modules\antagonists\wizard\grand_ritual\finales\cheese.dm" +#include "code\modules\antagonists\wizard\grand_ritual\finales\clown.dm" +#include "code\modules\antagonists\wizard\grand_ritual\finales\grand_ritual_finale.dm" +#include "code\modules\antagonists\wizard\grand_ritual\finales\immortality.dm" +#include "code\modules\antagonists\wizard\grand_ritual\finales\midas.dm" #include "code\modules\antagonists\xeno\xeno.dm" #include "code\modules\art\paintings.dm" #include "code\modules\art\statues.dm" #include "code\modules\assembly\assembly.dm" -#include "code\modules\assembly\bomb.dm" #include "code\modules\assembly\doorcontrol.dm" #include "code\modules\assembly\flash.dm" #include "code\modules\assembly\health.dm" @@ -3082,10 +3266,8 @@ #include "code\modules\asset_cache\assets\orbit.dm" #include "code\modules\asset_cache\assets\paper.dm" #include "code\modules\asset_cache\assets\particle_editor.dm" -#include "code\modules\asset_cache\assets\patches.dm" #include "code\modules\asset_cache\assets\pda.dm" #include "code\modules\asset_cache\assets\permissions.dm" -#include "code\modules\asset_cache\assets\pills.dm" #include "code\modules\asset_cache\assets\pipes.dm" #include "code\modules\asset_cache\assets\plane_debug.dm" #include "code\modules\asset_cache\assets\plumbing.dm" @@ -3098,9 +3280,9 @@ #include "code\modules\asset_cache\assets\seeds.dm" #include "code\modules\asset_cache\assets\sheetmaterials.dm" #include "code\modules\asset_cache\assets\supplypods.dm" +#include "code\modules\asset_cache\assets\tcomms.dm" #include "code\modules\asset_cache\assets\tgfont.dm" #include "code\modules\asset_cache\assets\tgui.dm" -#include "code\modules\asset_cache\assets\tutorial_advisors.dm" #include "code\modules\asset_cache\assets\uplink.dm" #include "code\modules\asset_cache\assets\vending.dm" #include "code\modules\asset_cache\assets\vv.dm" @@ -3151,6 +3333,7 @@ #include "code\modules\atmospherics\machinery\components\unary_devices\bluespace_sender.dm" #include "code\modules\atmospherics\machinery\components\unary_devices\cryo.dm" #include "code\modules\atmospherics\machinery\components\unary_devices\heat_exchanger.dm" +#include "code\modules\atmospherics\machinery\components\unary_devices\machine_connector.dm" #include "code\modules\atmospherics\machinery\components\unary_devices\outlet_injector.dm" #include "code\modules\atmospherics\machinery\components\unary_devices\passive_vent.dm" #include "code\modules\atmospherics\machinery\components\unary_devices\portables_connector.dm" @@ -3165,6 +3348,7 @@ #include "code\modules\atmospherics\machinery\pipes\layermanifold.dm" #include "code\modules\atmospherics\machinery\pipes\mapping.dm" #include "code\modules\atmospherics\machinery\pipes\multiz.dm" +#include "code\modules\atmospherics\machinery\pipes\pipe_spritesheet_helper.dm" #include "code\modules\atmospherics\machinery\pipes\pipes.dm" #include "code\modules\atmospherics\machinery\pipes\smart.dm" #include "code\modules\atmospherics\machinery\pipes\heat_exchange\he_pipes.dm" @@ -3206,6 +3390,71 @@ #include "code\modules\basketball\controller.dm" #include "code\modules\basketball\hoop.dm" #include "code\modules\basketball\referee.dm" +#include "code\modules\bitrunning\abilities.dm" +#include "code\modules\bitrunning\alerts.dm" +#include "code\modules\bitrunning\areas.dm" +#include "code\modules\bitrunning\designs.dm" +#include "code\modules\bitrunning\event.dm" +#include "code\modules\bitrunning\job.dm" +#include "code\modules\bitrunning\outfits.dm" +#include "code\modules\bitrunning\turfs.dm" +#include "code\modules\bitrunning\antagonists\_parent.dm" +#include "code\modules\bitrunning\antagonists\cyber_police.dm" +#include "code\modules\bitrunning\antagonists\cyber_tac.dm" +#include "code\modules\bitrunning\antagonists\netguardian.dm" +#include "code\modules\bitrunning\components\avatar_connection.dm" +#include "code\modules\bitrunning\components\bitrunning_points.dm" +#include "code\modules\bitrunning\components\glitch.dm" +#include "code\modules\bitrunning\components\netpod_healing.dm" +#include "code\modules\bitrunning\components\npc_friendly.dm" +#include "code\modules\bitrunning\objects\byteforge.dm" +#include "code\modules\bitrunning\objects\clothing.dm" +#include "code\modules\bitrunning\objects\debug.dm" +#include "code\modules\bitrunning\objects\disks.dm" +#include "code\modules\bitrunning\objects\hololadder.dm" +#include "code\modules\bitrunning\objects\host_monitor.dm" +#include "code\modules\bitrunning\objects\landmarks.dm" +#include "code\modules\bitrunning\objects\loot_crate.dm" +#include "code\modules\bitrunning\objects\netpod.dm" +#include "code\modules\bitrunning\objects\quantum_console.dm" +#include "code\modules\bitrunning\objects\vendor.dm" +#include "code\modules\bitrunning\orders\disks.dm" +#include "code\modules\bitrunning\orders\flair.dm" +#include "code\modules\bitrunning\orders\tech.dm" +#include "code\modules\bitrunning\server\_parent.dm" +#include "code\modules\bitrunning\server\loot.dm" +#include "code\modules\bitrunning\server\map_handling.dm" +#include "code\modules\bitrunning\server\obj_generation.dm" +#include "code\modules\bitrunning\server\signal_handlers.dm" +#include "code\modules\bitrunning\server\threats.dm" +#include "code\modules\bitrunning\server\util.dm" +#include "code\modules\bitrunning\util\digital_aura.dm" +#include "code\modules\bitrunning\util\service_style.dm" +#include "code\modules\bitrunning\util\virtual_megafauna.dm" +#include "code\modules\bitrunning\util\virtual_mob.dm" +#include "code\modules\bitrunning\virtual_domain\modular_mob_segment.dm" +#include "code\modules\bitrunning\virtual_domain\virtual_domain.dm" +#include "code\modules\bitrunning\virtual_domain\domains\abductor_ship.dm" +#include "code\modules\bitrunning\virtual_domain\domains\ash_drake.dm" +#include "code\modules\bitrunning\virtual_domain\domains\beach_bar.dm" +#include "code\modules\bitrunning\virtual_domain\domains\blood_drunk_miner.dm" +#include "code\modules\bitrunning\virtual_domain\domains\breeze_bay.dm" +#include "code\modules\bitrunning\virtual_domain\domains\bubblegum.dm" +#include "code\modules\bitrunning\virtual_domain\domains\clown_planet.dm" +#include "code\modules\bitrunning\virtual_domain\domains\colossus.dm" +#include "code\modules\bitrunning\virtual_domain\domains\gondola_asteroid.dm" +#include "code\modules\bitrunning\virtual_domain\domains\hierophant.dm" +#include "code\modules\bitrunning\virtual_domain\domains\pipedream.dm" +#include "code\modules\bitrunning\virtual_domain\domains\pirates.dm" +#include "code\modules\bitrunning\virtual_domain\domains\psyker_shuffle.dm" +#include "code\modules\bitrunning\virtual_domain\domains\psyker_zombies.dm" +#include "code\modules\bitrunning\virtual_domain\domains\stairs_and_cliffs.dm" +#include "code\modules\bitrunning\virtual_domain\domains\starfront_saloon.dm" +#include "code\modules\bitrunning\virtual_domain\domains\syndicate_assault.dm" +#include "code\modules\bitrunning\virtual_domain\domains\test_only.dm" +#include "code\modules\bitrunning\virtual_domain\domains\vaporwave.dm" +#include "code\modules\bitrunning\virtual_domain\domains\wendigo.dm" +#include "code\modules\bitrunning\virtual_domain\domains\xeno_nest.dm" #include "code\modules\buildmode\bm_mode.dm" #include "code\modules\buildmode\buildmode.dm" #include "code\modules\buildmode\buttons.dm" @@ -3216,6 +3465,7 @@ #include "code\modules\buildmode\submodes\copy.dm" #include "code\modules\buildmode\submodes\delete.dm" #include "code\modules\buildmode\submodes\fill.dm" +#include "code\modules\buildmode\submodes\map_export.dm" #include "code\modules\buildmode\submodes\mapgen.dm" #include "code\modules\buildmode\submodes\outfit.dm" #include "code\modules\buildmode\submodes\proccall.dm" @@ -3250,6 +3500,7 @@ #include "code\modules\cargo\expressconsole.dm" #include "code\modules\cargo\gondolapod.dm" #include "code\modules\cargo\goodies.dm" +#include "code\modules\cargo\materials_market.dm" #include "code\modules\cargo\order.dm" #include "code\modules\cargo\orderconsole.dm" #include "code\modules\cargo\supplypod.dm" @@ -3309,6 +3560,7 @@ #include "code\modules\cargo\packs\science.dm" #include "code\modules\cargo\packs\security.dm" #include "code\modules\cargo\packs\service.dm" +#include "code\modules\cargo\packs\stock_market_items.dm" #include "code\modules\cargo\packs\vending_restock.dm" #include "code\modules\chatter\chatter.dm" #include "code\modules\client\client_colour.dm" @@ -3332,7 +3584,7 @@ #include "code\modules\client\preferences\broadcast_login_logout.dm" #include "code\modules\client\preferences\clothing.dm" #include "code\modules\client\preferences\darkened_flash.dm" -#include "code\modules\client\preferences\fov_darkness.dm" +#include "code\modules\client\preferences\food_allergy.dm" #include "code\modules\client\preferences\fps.dm" #include "code\modules\client\preferences\gender.dm" #include "code\modules\client\preferences\ghost.dm" @@ -3359,6 +3611,7 @@ #include "code\modules\client\preferences\preferred_map.dm" #include "code\modules\client\preferences\pride_pin.dm" #include "code\modules\client\preferences\prisoner_crime.dm" +#include "code\modules\client\preferences\prosthetic.dm" #include "code\modules\client\preferences\random.dm" #include "code\modules\client\preferences\runechat.dm" #include "code\modules\client\preferences\scaling_method.dm" @@ -3396,6 +3649,7 @@ #include "code\modules\client\preferences\species_features\felinid.dm" #include "code\modules\client\preferences\species_features\lizard.dm" #include "code\modules\client\preferences\species_features\moth.dm" +#include "code\modules\client\preferences\species_features\mushperson.dm" #include "code\modules\client\preferences\species_features\mutants.dm" #include "code\modules\client\preferences\species_features\pod.dm" #include "code\modules\client\preferences\species_features\vampire.dm" @@ -3565,6 +3819,7 @@ #include "code\modules\economy\account.dm" #include "code\modules\economy\holopay.dm" #include "code\modules\emoji\emoji_parse.dm" +#include "code\modules\emote_panel\emote_panel.dm" #include "code\modules\error_handler\error_handler.dm" #include "code\modules\error_handler\error_viewer.dm" #include "code\modules\escape_menu\details.dm" @@ -3688,6 +3943,7 @@ #include "code\modules\experisci\experiment\types\physical_experiment.dm" #include "code\modules\experisci\experiment\types\random_scanning.dm" #include "code\modules\experisci\experiment\types\scanning.dm" +#include "code\modules\experisci\experiment\types\scanning_fish.dm" #include "code\modules\experisci\experiment\types\scanning_machinery.dm" #include "code\modules\experisci\experiment\types\scanning_material.dm" #include "code\modules\experisci\experiment\types\scanning_people.dm" @@ -3791,6 +4047,7 @@ #include "code\modules\hallucination\hud_screw.dm" #include "code\modules\hallucination\ice_cube.dm" #include "code\modules\hallucination\inhand_fake_item.dm" +#include "code\modules\hallucination\mother.dm" #include "code\modules\hallucination\nearby_fake_item.dm" #include "code\modules\hallucination\on_fire.dm" #include "code\modules\hallucination\screwy_health_doll.dm" @@ -3805,7 +4062,6 @@ #include "code\modules\holodeck\holo_effect.dm" #include "code\modules\holodeck\holodeck_map_templates.dm" #include "code\modules\holodeck\items.dm" -#include "code\modules\holodeck\mobs.dm" #include "code\modules\holodeck\turfs.dm" #include "code\modules\hydroponics\biogenerator.dm" #include "code\modules\hydroponics\bouquets.dm" @@ -3817,10 +4073,10 @@ #include "code\modules\hydroponics\hydroponics.dm" #include "code\modules\hydroponics\hydroponics_chemreact.dm" #include "code\modules\hydroponics\plant_genes.dm" -#include "code\modules\hydroponics\sample.dm" #include "code\modules\hydroponics\seed_extractor.dm" #include "code\modules\hydroponics\seeds.dm" #include "code\modules\hydroponics\unique_plant_genes.dm" +#include "code\modules\hydroponics\beekeeping\bee_smoker.dm" #include "code\modules\hydroponics\beekeeping\beebox.dm" #include "code\modules\hydroponics\beekeeping\beekeeper_suit.dm" #include "code\modules\hydroponics\beekeeping\honey_frame.dm" @@ -3863,6 +4119,7 @@ #include "code\modules\hydroponics\grown\random.dm" #include "code\modules\hydroponics\grown\replicapod.dm" #include "code\modules\hydroponics\grown\root.dm" +#include "code\modules\hydroponics\grown\seedling.dm" #include "code\modules\hydroponics\grown\sugarcane.dm" #include "code\modules\hydroponics\grown\tea_coffee.dm" #include "code\modules\hydroponics\grown\tobacco.dm" @@ -3871,23 +4128,6 @@ #include "code\modules\hydroponics\grown\weeds\kudzu.dm" #include "code\modules\hydroponics\grown\weeds\nettle.dm" #include "code\modules\hydroponics\grown\weeds\starthistle.dm" -#include "code\modules\industrial_lift\industrial_lift.dm" -#include "code\modules\industrial_lift\lift_master.dm" -#include "code\modules\industrial_lift\elevator\elevator_controller.dm" -#include "code\modules\industrial_lift\elevator\elevator_doors.dm" -#include "code\modules\industrial_lift\elevator\elevator_indicator.dm" -#include "code\modules\industrial_lift\elevator\elevator_music_zone.dm" -#include "code\modules\industrial_lift\elevator\elevator_panel.dm" -#include "code\modules\industrial_lift\tram\tram_doors.dm" -#include "code\modules\industrial_lift\tram\tram_floors.dm" -#include "code\modules\industrial_lift\tram\tram_landmark.dm" -#include "code\modules\industrial_lift\tram\tram_lift_master.dm" -#include "code\modules\industrial_lift\tram\tram_machinery.dm" -#include "code\modules\industrial_lift\tram\tram_override_objects.dm" -#include "code\modules\industrial_lift\tram\tram_remote.dm" -#include "code\modules\industrial_lift\tram\tram_structures.dm" -#include "code\modules\industrial_lift\tram\tram_walls.dm" -#include "code\modules\industrial_lift\tram\tram_windows.dm" #include "code\modules\instruments\items.dm" #include "code\modules\instruments\piano_synth.dm" #include "code\modules\instruments\stationary.dm" @@ -3914,7 +4154,6 @@ #include "code\modules\jobs\departments\departments.dm" #include "code\modules\jobs\job_types\_job.dm" #include "code\modules\jobs\job_types\ai.dm" -#include "code\modules\jobs\job_types\assistant.dm" #include "code\modules\jobs\job_types\atmospheric_technician.dm" #include "code\modules\jobs\job_types\bartender.dm" #include "code\modules\jobs\job_types\botanist.dm" @@ -3937,8 +4176,6 @@ #include "code\modules\jobs\job_types\medical_doctor.dm" #include "code\modules\jobs\job_types\mime.dm" #include "code\modules\jobs\job_types\paramedic.dm" -#include "code\modules\jobs\job_types\personal_ai.dm" -#include "code\modules\jobs\job_types\positronic_brain.dm" #include "code\modules\jobs\job_types\prisoner.dm" #include "code\modules\jobs\job_types\psychologist.dm" #include "code\modules\jobs\job_types\quartermaster.dm" @@ -3946,7 +4183,6 @@ #include "code\modules\jobs\job_types\roboticist.dm" #include "code\modules\jobs\job_types\scientist.dm" #include "code\modules\jobs\job_types\security_officer.dm" -#include "code\modules\jobs\job_types\servant_golem.dm" #include "code\modules\jobs\job_types\shaft_miner.dm" #include "code\modules\jobs\job_types\station_engineer.dm" #include "code\modules\jobs\job_types\unassigned.dm" @@ -3969,6 +4205,9 @@ #include "code\modules\jobs\job_types\antagonists\space_wizard.dm" #include "code\modules\jobs\job_types\antagonists\wizard_apprentice.dm" #include "code\modules\jobs\job_types\antagonists\xenomorph.dm" +#include "code\modules\jobs\job_types\assistant\assistant.dm" +#include "code\modules\jobs\job_types\assistant\colorful_assistants.dm" +#include "code\modules\jobs\job_types\assistant\gimmick_assistants.dm" #include "code\modules\jobs\job_types\chaplain\chaplain.dm" #include "code\modules\jobs\job_types\chaplain\chaplain_costumes.dm" #include "code\modules\jobs\job_types\chaplain\chaplain_divine_archer.dm" @@ -3992,6 +4231,9 @@ #include "code\modules\jobs\job_types\spawner\lavaland_syndicate.dm" #include "code\modules\jobs\job_types\spawner\lifebringer.dm" #include "code\modules\jobs\job_types\spawner\maintenance_drone.dm" +#include "code\modules\jobs\job_types\spawner\personal_ai.dm" +#include "code\modules\jobs\job_types\spawner\positronic_brain.dm" +#include "code\modules\jobs\job_types\spawner\servant_golem.dm" #include "code\modules\jobs\job_types\spawner\skeleton.dm" #include "code\modules\jobs\job_types\spawner\space_bar_patron.dm" #include "code\modules\jobs\job_types\spawner\space_bartender.dm" @@ -4003,6 +4245,8 @@ #include "code\modules\jobs\job_types\spawner\syndicate_cybersun_captain.dm" #include "code\modules\jobs\job_types\spawner\venus_human_trap.dm" #include "code\modules\jobs\job_types\spawner\zombie.dm" +#include "code\modules\jobs\job_types\station_trait\bridge_assistant.dm" +#include "code\modules\jobs\job_types\station_trait\cargo_gorilla.dm" #include "code\modules\keybindings\bindings_atom.dm" #include "code\modules\keybindings\bindings_client.dm" #include "code\modules\keybindings\focus.dm" @@ -4046,6 +4290,7 @@ #include "code\modules\library\skill_learning\skillchip.dm" #include "code\modules\library\skill_learning\job_skillchips\_job.dm" #include "code\modules\library\skill_learning\job_skillchips\chef.dm" +#include "code\modules\library\skill_learning\job_skillchips\janitor.dm" #include "code\modules\library\skill_learning\job_skillchips\psychologist.dm" #include "code\modules\library\skill_learning\job_skillchips\research_director.dm" #include "code\modules\library\skill_learning\job_skillchips\roboticist.dm" @@ -4066,6 +4311,7 @@ #include "code\modules\logging\categories\log_category_debug.dm" #include "code\modules\logging\categories\log_category_game.dm" #include "code\modules\logging\categories\log_category_href.dm" +#include "code\modules\logging\categories\log_category_internal.dm" #include "code\modules\logging\categories\log_category_misc.dm" #include "code\modules\logging\categories\log_category_pda.dm" #include "code\modules\logging\categories\log_category_silo.dm" @@ -4073,6 +4319,7 @@ #include "code\modules\logging\categories\log_category_uplink.dm" #include "code\modules\mafia\_defines.dm" #include "code\modules\mafia\controller.dm" +#include "code\modules\mafia\controller_ui.dm" #include "code\modules\mafia\map_pieces.dm" #include "code\modules\mafia\outfits.dm" #include "code\modules\mafia\abilities\abilities.dm" @@ -4099,10 +4346,12 @@ #include "code\modules\mafia\roles\town\town_protective.dm" #include "code\modules\mafia\roles\town\town_support.dm" #include "code\modules\mapfluff\centcom\nuke_ops.dm" +#include "code\modules\mapfluff\ruins\generic.dm" #include "code\modules\mapfluff\ruins\lavaland_ruin_code.dm" #include "code\modules\mapfluff\ruins\icemoonruin_code\hotsprings.dm" #include "code\modules\mapfluff\ruins\icemoonruin_code\library.dm" #include "code\modules\mapfluff\ruins\icemoonruin_code\mailroom.dm" +#include "code\modules\mapfluff\ruins\icemoonruin_code\mining_site.dm" #include "code\modules\mapfluff\ruins\icemoonruin_code\wrath.dm" #include "code\modules\mapfluff\ruins\lavalandruin_code\biodome_clown_planet.dm" #include "code\modules\mapfluff\ruins\lavalandruin_code\biodome_winter.dm" @@ -4111,8 +4360,10 @@ #include "code\modules\mapfluff\ruins\lavalandruin_code\sloth.dm" #include "code\modules\mapfluff\ruins\lavalandruin_code\surface.dm" #include "code\modules\mapfluff\ruins\lavalandruin_code\syndicate_base.dm" +#include "code\modules\mapfluff\ruins\lavalandruin_code\watcher_grave.dm" #include "code\modules\mapfluff\ruins\objects_and_mobs\ash_walker_den.dm" #include "code\modules\mapfluff\ruins\objects_and_mobs\cursed_slot_machine.dm" +#include "code\modules\mapfluff\ruins\objects_and_mobs\museum.dm" #include "code\modules\mapfluff\ruins\objects_and_mobs\necropolis_gate.dm" #include "code\modules\mapfluff\ruins\objects_and_mobs\sin_ruins.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\allamericandiner.dm" @@ -4134,6 +4385,7 @@ #include "code\modules\mapfluff\ruins\spaceruin_code\hilbertshotel.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\interdyne.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\listeningstation.dm" +#include "code\modules\mapfluff\ruins\spaceruin_code\meatderelict.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\meateor.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\originalcontent.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\spacehotel.dm" @@ -4180,9 +4432,13 @@ #include "code\modules\mining\mine_items.dm" #include "code\modules\mining\money_bag.dm" #include "code\modules\mining\ores_coins.dm" -#include "code\modules\mining\satchel_ore_boxdm.dm" +#include "code\modules\mining\satchel_ore_box.dm" #include "code\modules\mining\shelters.dm" #include "code\modules\mining\voucher_sets.dm" +#include "code\modules\mining\boulder_processing\_boulder_processing.dm" +#include "code\modules\mining\boulder_processing\boulder.dm" +#include "code\modules\mining\boulder_processing\brm.dm" +#include "code\modules\mining\boulder_processing\refinery.dm" #include "code\modules\mining\equipment\explorer_gear.dm" #include "code\modules\mining\equipment\kheiral_cuffs.dm" #include "code\modules\mining\equipment\kinetic_crusher.dm" @@ -4220,7 +4476,6 @@ #include "code\modules\mob\status_procs.dm" #include "code\modules\mob\transform_procs.dm" #include "code\modules\mob\camera\camera.dm" -#include "code\modules\mob\dead\crew_manifest.dm" #include "code\modules\mob\dead\dead.dm" #include "code\modules\mob\dead\new_player\latejoin_menu.dm" #include "code\modules\mob\dead\new_player\login.dm" @@ -4260,6 +4515,40 @@ #include "code\modules\mob\living\basic\festivus_pole.dm" #include "code\modules\mob\living\basic\health_adjustment.dm" #include "code\modules\mob\living\basic\tree.dm" +#include "code\modules\mob\living\basic\blob_minions\blob_ai.dm" +#include "code\modules\mob\living\basic\blob_minions\blob_mob.dm" +#include "code\modules\mob\living\basic\blob_minions\blob_spore.dm" +#include "code\modules\mob\living\basic\blob_minions\blob_zombie.dm" +#include "code\modules\mob\living\basic\blob_minions\blobbernaut.dm" +#include "code\modules\mob\living\basic\bots\_bots.dm" +#include "code\modules\mob\living\basic\bots\bot_ai.dm" +#include "code\modules\mob\living\basic\bots\bot_hud.dm" +#include "code\modules\mob\living\basic\bots\cleanbot\cleanbot.dm" +#include "code\modules\mob\living\basic\bots\cleanbot\cleanbot_abilities.dm" +#include "code\modules\mob\living\basic\bots\cleanbot\cleanbot_ai.dm" +#include "code\modules\mob\living\basic\bots\hygienebot\hygienebot.dm" +#include "code\modules\mob\living\basic\bots\hygienebot\hygienebot_ai.dm" +#include "code\modules\mob\living\basic\bots\medbot\medbot.dm" +#include "code\modules\mob\living\basic\bots\medbot\medbot_ai.dm" +#include "code\modules\mob\living\basic\clown\clown.dm" +#include "code\modules\mob\living\basic\clown\clown_ai.dm" +#include "code\modules\mob\living\basic\cult\shade.dm" +#include "code\modules\mob\living\basic\cult\constructs\_construct.dm" +#include "code\modules\mob\living\basic\cult\constructs\artificer.dm" +#include "code\modules\mob\living\basic\cult\constructs\construct_ai.dm" +#include "code\modules\mob\living\basic\cult\constructs\harvester.dm" +#include "code\modules\mob\living\basic\cult\constructs\juggernaut.dm" +#include "code\modules\mob\living\basic\cult\constructs\proteon.dm" +#include "code\modules\mob\living\basic\cult\constructs\wraith.dm" +#include "code\modules\mob\living\basic\drone\_drone.dm" +#include "code\modules\mob\living\basic\drone\drone_say.dm" +#include "code\modules\mob\living\basic\drone\drone_tools.dm" +#include "code\modules\mob\living\basic\drone\drones_as_items.dm" +#include "code\modules\mob\living\basic\drone\extra_drone_types.dm" +#include "code\modules\mob\living\basic\drone\interaction.dm" +#include "code\modules\mob\living\basic\drone\inventory.dm" +#include "code\modules\mob\living\basic\drone\verbs.dm" +#include "code\modules\mob\living\basic\drone\visuals_icons.dm" #include "code\modules\mob\living\basic\farm_animals\deer.dm" #include "code\modules\mob\living\basic\farm_animals\pig.dm" #include "code\modules\mob\living\basic\farm_animals\pony.dm" @@ -4274,15 +4563,57 @@ #include "code\modules\mob\living\basic\farm_animals\cow\cow_ai.dm" #include "code\modules\mob\living\basic\farm_animals\cow\cow_moonicorn.dm" #include "code\modules\mob\living\basic\farm_animals\cow\cow_wisdom.dm" +#include "code\modules\mob\living\basic\farm_animals\goat\_goat.dm" +#include "code\modules\mob\living\basic\farm_animals\goat\goat_ai.dm" +#include "code\modules\mob\living\basic\farm_animals\goat\goat_subtypes.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_accessories.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_ai.dm" +#include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_emotes.dm" +#include "code\modules\mob\living\basic\guardian\guardian.dm" +#include "code\modules\mob\living\basic\guardian\guardian_creator.dm" +#include "code\modules\mob\living\basic\guardian\guardian_fluff.dm" +#include "code\modules\mob\living\basic\guardian\guardian_helpers.dm" +#include "code\modules\mob\living\basic\guardian\guardian_verbs.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\assassin.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\charger.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\dextrous.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\explosive.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\gaseous.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\gravitokinetic.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\lightning.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\protector.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\ranged.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\standard.dm" +#include "code\modules\mob\living\basic\guardian\guardian_types\support.dm" +#include "code\modules\mob\living\basic\heretic\_heretic_summon.dm" +#include "code\modules\mob\living\basic\heretic\ash_spirit.dm" #include "code\modules\mob\living\basic\heretic\fire_shark.dm" -#include "code\modules\mob\living\basic\heretic\heretic_summon.dm" +#include "code\modules\mob\living\basic\heretic\flesh_stalker.dm" +#include "code\modules\mob\living\basic\heretic\flesh_worm.dm" +#include "code\modules\mob\living\basic\heretic\maid_in_the_mirror.dm" +#include "code\modules\mob\living\basic\heretic\raw_prophet.dm" +#include "code\modules\mob\living\basic\heretic\rust_walker.dm" #include "code\modules\mob\living\basic\heretic\star_gazer.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon_abilities.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon_ai.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp_abilities.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp_ai.dm" +#include "code\modules\mob\living\basic\icemoon\wolf\wolf.dm" +#include "code\modules\mob\living\basic\icemoon\wolf\wolf_ai.dm" +#include "code\modules\mob\living\basic\icemoon\wolf\wolf_extras.dm" +#include "code\modules\mob\living\basic\jungle\venus_human_trap.dm" +#include "code\modules\mob\living\basic\jungle\leaper\leaper.dm" +#include "code\modules\mob\living\basic\jungle\leaper\leaper_abilities.dm" +#include "code\modules\mob\living\basic\jungle\leaper\leaper_ai.dm" #include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid.dm" #include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid_abilities.dm" #include "code\modules\mob\living\basic\jungle\mega_arachnid\mega_arachnid_ai.dm" +#include "code\modules\mob\living\basic\jungle\seedling\seedling.dm" +#include "code\modules\mob\living\basic\jungle\seedling\seedling_ai.dm" +#include "code\modules\mob\living\basic\jungle\seedling\seedling_projectiles.dm" #include "code\modules\mob\living\basic\lavaland\mining.dm" #include "code\modules\mob\living\basic\lavaland\basilisk\basilisk.dm" #include "code\modules\mob\living\basic\lavaland\basilisk\basilisk_overheat.dm" @@ -4292,6 +4623,10 @@ #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_instrument.dm" #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_loot.dm" #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_vileworm.dm" +#include "code\modules\mob\living\basic\lavaland\brimdemon\brimbeam.dm" +#include "code\modules\mob\living\basic\lavaland\brimdemon\brimdemon.dm" +#include "code\modules\mob\living\basic\lavaland\brimdemon\brimdemon_ai.dm" +#include "code\modules\mob\living\basic\lavaland\brimdemon\brimdemon_loot.dm" #include "code\modules\mob\living\basic\lavaland\goldgrub\goldgrub.dm" #include "code\modules\mob\living\basic\lavaland\goldgrub\goldgrub_abilities.dm" #include "code\modules\mob\living\basic\lavaland\goldgrub\goldgrub_ai.dm" @@ -4300,9 +4635,26 @@ #include "code\modules\mob\living\basic\lavaland\goliath\goliath_ai.dm" #include "code\modules\mob\living\basic\lavaland\goliath\goliath_trophy.dm" #include "code\modules\mob\living\basic\lavaland\goliath\tentacle.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutluncher_foodtrough.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutlunchers.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutlunchers_ai.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutlunchers_inherit_datum.dm" +#include "code\modules\mob\living\basic\lavaland\hivelord\hivelord.dm" +#include "code\modules\mob\living\basic\lavaland\hivelord\hivelord_ai.dm" +#include "code\modules\mob\living\basic\lavaland\hivelord\spawn_hivelord_brood.dm" +#include "code\modules\mob\living\basic\lavaland\legion\legion.dm" +#include "code\modules\mob\living\basic\lavaland\legion\legion_ai.dm" +#include "code\modules\mob\living\basic\lavaland\legion\legion_brood.dm" +#include "code\modules\mob\living\basic\lavaland\legion\legion_tumour.dm" +#include "code\modules\mob\living\basic\lavaland\legion\spawn_legions.dm" #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity.dm" #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity_ai.dm" #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity_trophy.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_abilities.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_ai.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_village.dm" +#include "code\modules\mob\living\basic\lavaland\node_drone\node_drone.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_ai.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_gaze.dm" @@ -4315,22 +4667,46 @@ #include "code\modules\mob\living\basic\pets\fox.dm" #include "code\modules\mob\living\basic\pets\penguin.dm" #include "code\modules\mob\living\basic\pets\pet.dm" +#include "code\modules\mob\living\basic\pets\sloth.dm" +#include "code\modules\mob\living\basic\pets\cat\bread_cat_ai.dm" +#include "code\modules\mob\living\basic\pets\cat\cat.dm" +#include "code\modules\mob\living\basic\pets\cat\cat_ai.dm" +#include "code\modules\mob\living\basic\pets\cat\feral.dm" +#include "code\modules\mob\living\basic\pets\cat\keeki.dm" +#include "code\modules\mob\living\basic\pets\cat\kitten_ai.dm" +#include "code\modules\mob\living\basic\pets\cat\runtime.dm" #include "code\modules\mob\living\basic\pets\dog\_dog.dm" #include "code\modules\mob\living\basic\pets\dog\corgi.dm" #include "code\modules\mob\living\basic\pets\dog\dog_subtypes.dm" #include "code\modules\mob\living\basic\pets\dog\strippable_items.dm" +#include "code\modules\mob\living\basic\pets\parrot\_parrot.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_items.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_subtypes.dm" +#include "code\modules\mob\living\basic\pets\parrot\poly.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_ai\_parrot_controller.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_ai\ghost_parrot_controller.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_ai\parrot_hoarding.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_ai\parrot_perching.dm" +#include "code\modules\mob\living\basic\pets\parrot\parrot_ai\parroting_action.dm" +#include "code\modules\mob\living\basic\ruin_defender\flesh.dm" +#include "code\modules\mob\living\basic\ruin_defender\living_floor.dm" +#include "code\modules\mob\living\basic\ruin_defender\skeleton.dm" #include "code\modules\mob\living\basic\ruin_defender\stickman.dm" +#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard.dm" +#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard_ai.dm" +#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard_spells.dm" #include "code\modules\mob\living\basic\space_fauna\ant.dm" #include "code\modules\mob\living\basic\space_fauna\cat_surgeon.dm" #include "code\modules\mob\living\basic\space_fauna\faithless.dm" #include "code\modules\mob\living\basic\space_fauna\garden_gnome.dm" #include "code\modules\mob\living\basic\space_fauna\ghost.dm" -#include "code\modules\mob\living\basic\space_fauna\headslug.dm" #include "code\modules\mob\living\basic\space_fauna\killer_tomato.dm" #include "code\modules\mob\living\basic\space_fauna\lightgeist.dm" #include "code\modules\mob\living\basic\space_fauna\morph.dm" #include "code\modules\mob\living\basic\space_fauna\mushroom.dm" +#include "code\modules\mob\living\basic\space_fauna\robot_customer.dm" #include "code\modules\mob\living\basic\space_fauna\spaceman.dm" +#include "code\modules\mob\living\basic\space_fauna\supermatter_spider.dm" #include "code\modules\mob\living\basic\space_fauna\bear\_bear.dm" #include "code\modules\mob\living\basic\space_fauna\bear\bear_ai_behavior.dm" #include "code\modules\mob\living\basic\space_fauna\bear\bear_ai_subtree.dm" @@ -4342,6 +4718,8 @@ #include "code\modules\mob\living\basic\space_fauna\carp\carp_controllers.dm" #include "code\modules\mob\living\basic\space_fauna\carp\magicarp.dm" #include "code\modules\mob\living\basic\space_fauna\carp\megacarp.dm" +#include "code\modules\mob\living\basic\space_fauna\changeling\flesh_spider.dm" +#include "code\modules\mob\living\basic\space_fauna\changeling\headslug.dm" #include "code\modules\mob\living\basic\space_fauna\demon\demon.dm" #include "code\modules\mob\living\basic\space_fauna\demon\demon_items.dm" #include "code\modules\mob\living\basic\space_fauna\demon\demon_subtypes.dm" @@ -4365,6 +4743,17 @@ #include "code\modules\mob\living\basic\space_fauna\regal_rat\regal_rat.dm" #include "code\modules\mob\living\basic\space_fauna\regal_rat\regal_rat_actions.dm" #include "code\modules\mob\living\basic\space_fauna\regal_rat\regal_rat_ai.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\_revenant.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_abilities.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_effects.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_harvest.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_items.dm" +#include "code\modules\mob\living\basic\space_fauna\revenant\revenant_objectives.dm" +#include "code\modules\mob\living\basic\space_fauna\snake\snake.dm" +#include "code\modules\mob\living\basic\space_fauna\snake\snake_ai.dm" +#include "code\modules\mob\living\basic\space_fauna\space_dragon\dragon_breath.dm" +#include "code\modules\mob\living\basic\space_fauna\space_dragon\dragon_gust.dm" +#include "code\modules\mob\living\basic\space_fauna\space_dragon\space_dragon.dm" #include "code\modules\mob\living\basic\space_fauna\spider\spider.dm" #include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_ai.dm" #include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_subtrees.dm" @@ -4378,14 +4767,24 @@ #include "code\modules\mob\living\basic\space_fauna\spider\spiderlings\spiderling_subtypes.dm" #include "code\modules\mob\living\basic\space_fauna\spider\young_spider\young_spider.dm" #include "code\modules\mob\living\basic\space_fauna\spider\young_spider\young_spider_subtypes.dm" +#include "code\modules\mob\living\basic\space_fauna\statue\mannequin.dm" #include "code\modules\mob\living\basic\space_fauna\statue\statue.dm" #include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\fugu_gland.dm" #include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\inflation.dm" #include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\wumborian_ai.dm" #include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\wumborian_fugu.dm" -#include "code\modules\mob\living\basic\syndicate\russian.dm" -#include "code\modules\mob\living\basic\syndicate\syndicate.dm" -#include "code\modules\mob\living\basic\syndicate\syndicate_ai.dm" +#include "code\modules\mob\living\basic\trader\trader.dm" +#include "code\modules\mob\living\basic\trader\trader_actions.dm" +#include "code\modules\mob\living\basic\trader\trader_ai.dm" +#include "code\modules\mob\living\basic\trader\trader_data.dm" +#include "code\modules\mob\living\basic\trader\trader_items.dm" +#include "code\modules\mob\living\basic\trooper\abductor.dm" +#include "code\modules\mob\living\basic\trooper\nanotrasen.dm" +#include "code\modules\mob\living\basic\trooper\pirate.dm" +#include "code\modules\mob\living\basic\trooper\russian.dm" +#include "code\modules\mob\living\basic\trooper\syndicate.dm" +#include "code\modules\mob\living\basic\trooper\trooper.dm" +#include "code\modules\mob\living\basic\trooper\trooper_ai.dm" #include "code\modules\mob\living\basic\vermin\axolotl.dm" #include "code\modules\mob\living\basic\vermin\butterfly.dm" #include "code\modules\mob\living\basic\vermin\cockroach.dm" @@ -4396,6 +4795,7 @@ #include "code\modules\mob\living\basic\vermin\mouse.dm" #include "code\modules\mob\living\basic\vermin\space_bat.dm" #include "code\modules\mob\living\brain\brain.dm" +#include "code\modules\mob\living\brain\brain_cybernetic.dm" #include "code\modules\mob\living\brain\brain_item.dm" #include "code\modules\mob\living\brain\brain_say.dm" #include "code\modules\mob\living\brain\death.dm" @@ -4454,7 +4854,6 @@ #include "code\modules\mob\living\carbon\alien\special\alien_embryo.dm" #include "code\modules\mob\living\carbon\alien\special\facehugger.dm" #include "code\modules\mob\living\carbon\human\_species.dm" -#include "code\modules\mob\living\carbon\human\damage_procs.dm" #include "code\modules\mob\living\carbon\human\death.dm" #include "code\modules\mob\living\carbon\human\dummy.dm" #include "code\modules\mob\living\carbon\human\emote.dm" @@ -4540,83 +4939,28 @@ #include "code\modules\mob\living\silicon\robot\robot_say.dm" #include "code\modules\mob\living\simple_animal\animal_defense.dm" #include "code\modules\mob\living\simple_animal\damage_procs.dm" -#include "code\modules\mob\living\simple_animal\parrot.dm" -#include "code\modules\mob\living\simple_animal\revenant.dm" -#include "code\modules\mob\living\simple_animal\shade.dm" #include "code\modules\mob\living\simple_animal\simple_animal.dm" #include "code\modules\mob\living\simple_animal\bot\bot.dm" #include "code\modules\mob\living\simple_animal\bot\bot_announcement.dm" -#include "code\modules\mob\living\simple_animal\bot\cleanbot.dm" #include "code\modules\mob\living\simple_animal\bot\construction.dm" #include "code\modules\mob\living\simple_animal\bot\ed209bot.dm" #include "code\modules\mob\living\simple_animal\bot\firebot.dm" #include "code\modules\mob\living\simple_animal\bot\floorbot.dm" #include "code\modules\mob\living\simple_animal\bot\honkbot.dm" -#include "code\modules\mob\living\simple_animal\bot\hygienebot.dm" -#include "code\modules\mob\living\simple_animal\bot\medbot.dm" #include "code\modules\mob\living\simple_animal\bot\mulebot.dm" #include "code\modules\mob\living\simple_animal\bot\secbot.dm" #include "code\modules\mob\living\simple_animal\bot\SuperBeepsky.dm" #include "code\modules\mob\living\simple_animal\bot\vibebot.dm" -#include "code\modules\mob\living\simple_animal\friendly\cat.dm" -#include "code\modules\mob\living\simple_animal\friendly\farm_animals.dm" #include "code\modules\mob\living\simple_animal\friendly\gondola.dm" #include "code\modules\mob\living\simple_animal\friendly\pet.dm" -#include "code\modules\mob\living\simple_animal\friendly\robot_customer.dm" -#include "code\modules\mob\living\simple_animal\friendly\sloth.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\_drone.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\drone_say.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\drone_tools.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\drones_as_items.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\extra_drone_types.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\interaction.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\inventory.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\verbs.dm" -#include "code\modules\mob\living\simple_animal\friendly\drone\visuals_icons.dm" -#include "code\modules\mob\living\simple_animal\guardian\guardian.dm" -#include "code\modules\mob\living\simple_animal\guardian\guardian_creator.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\assassin.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\charger.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\dextrous.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\explosive.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\gaseous.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\gravitokinetic.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\lightning.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\protector.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\ranged.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\standard.dm" -#include "code\modules\mob\living\simple_animal\guardian\types\support.dm" #include "code\modules\mob\living\simple_animal\hostile\alien.dm" -#include "code\modules\mob\living\simple_animal\hostile\blob.dm" -#include "code\modules\mob\living\simple_animal\hostile\blobbernaut.dm" -#include "code\modules\mob\living\simple_animal\hostile\blobspore.dm" #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" -#include "code\modules\mob\living\simple_animal\hostile\heretic_monsters.dm" #include "code\modules\mob\living\simple_animal\hostile\hostile.dm" #include "code\modules\mob\living\simple_animal\hostile\illusion.dm" #include "code\modules\mob\living\simple_animal\hostile\mimic.dm" -#include "code\modules\mob\living\simple_animal\hostile\nanotrasen.dm" #include "code\modules\mob\living\simple_animal\hostile\ooze.dm" -#include "code\modules\mob\living\simple_animal\hostile\pirate.dm" -#include "code\modules\mob\living\simple_animal\hostile\skeleton.dm" -#include "code\modules\mob\living\simple_animal\hostile\smspider.dm" -#include "code\modules\mob\living\simple_animal\hostile\space_dragon.dm" #include "code\modules\mob\living\simple_animal\hostile\vatbeast.dm" -#include "code\modules\mob\living\simple_animal\hostile\venus_human_trap.dm" -#include "code\modules\mob\living\simple_animal\hostile\wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\zombie.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\artificer.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\constructs.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\harvester.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\juggernaut.dm" -#include "code\modules\mob\living\simple_animal\hostile\constructs\wraith.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\emotes.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\gorilla.dm" -#include "code\modules\mob\living\simple_animal\hostile\gorilla\visuals_icons.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\_jungle_mobs.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\leaper.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\mook.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\seedling.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\_megafauna.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm" @@ -4627,11 +4971,7 @@ #include "code\modules\mob\living\simple_animal\hostile\megafauna\hierophant.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\legion.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\wendigo.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\brimdemon.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\gutlunch.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\hivelord.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\ice_demon.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\mining_mobs.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\polarbear.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\wolf.dm" @@ -4640,18 +4980,17 @@ #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\herald.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\legionnaire.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\pandora.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\clown.dm" #include "code\modules\mob\living\simple_animal\hostile\retaliate\goose.dm" #include "code\modules\mob\living\simple_animal\hostile\retaliate\retaliate.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\snake.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\trader.dm" +#include "code\modules\mob\living\simple_animal\slime\ai.dm" #include "code\modules\mob\living\simple_animal\slime\death.dm" +#include "code\modules\mob\living\simple_animal\slime\defense.dm" #include "code\modules\mob\living\simple_animal\slime\emote.dm" #include "code\modules\mob\living\simple_animal\slime\life.dm" #include "code\modules\mob\living\simple_animal\slime\powers.dm" #include "code\modules\mob\living\simple_animal\slime\slime.dm" #include "code\modules\mob\living\simple_animal\slime\slime_say.dm" -#include "code\modules\mob\living\simple_animal\slime\subtypes.dm" +#include "code\modules\mob\living\simple_animal\slime\slime_type.dm" #include "code\modules\mob_spawn\mob_spawn.dm" #include "code\modules\mob_spawn\corpses\job_corpses.dm" #include "code\modules\mob_spawn\corpses\mining_corpses.dm" @@ -4660,6 +4999,7 @@ #include "code\modules\mob_spawn\corpses\nonhuman_corpses.dm" #include "code\modules\mob_spawn\corpses\species_corpses.dm" #include "code\modules\mob_spawn\ghost_roles\away_roles.dm" +#include "code\modules\mob_spawn\ghost_roles\drone_roles.dm" #include "code\modules\mob_spawn\ghost_roles\fugitive_hunter_roles.dm" #include "code\modules\mob_spawn\ghost_roles\golem_roles.dm" #include "code\modules\mob_spawn\ghost_roles\mining_roles.dm" @@ -4695,6 +5035,7 @@ #include "code\modules\mod\modules\modules_timeline.dm" #include "code\modules\mod\modules\modules_visor.dm" #include "code\modules\modular_computers\computers\item\computer.dm" +#include "code\modules\modular_computers\computers\item\computer_circuit.dm" #include "code\modules\modular_computers\computers\item\computer_files.dm" #include "code\modules\modular_computers\computers\item\computer_power.dm" #include "code\modules\modular_computers\computers\item\computer_ui.dm" @@ -4714,6 +5055,7 @@ #include "code\modules\modular_computers\file_system\data.dm" #include "code\modules\modular_computers\file_system\picture_file.dm" #include "code\modules\modular_computers\file_system\program.dm" +#include "code\modules\modular_computers\file_system\program_circuit.dm" #include "code\modules\modular_computers\file_system\programs\airestorer.dm" #include "code\modules\modular_computers\file_system\programs\alarm.dm" #include "code\modules\modular_computers\file_system\programs\arcade.dm" @@ -4723,16 +5065,17 @@ #include "code\modules\modular_computers\file_system\programs\budgetordering.dm" #include "code\modules\modular_computers\file_system\programs\card.dm" #include "code\modules\modular_computers\file_system\programs\cargoship.dm" +#include "code\modules\modular_computers\file_system\programs\coupon.dm" #include "code\modules\modular_computers\file_system\programs\crewmanifest.dm" #include "code\modules\modular_computers\file_system\programs\emojipedia.dm" #include "code\modules\modular_computers\file_system\programs\file_browser.dm" #include "code\modules\modular_computers\file_system\programs\frontier.dm" #include "code\modules\modular_computers\file_system\programs\jobmanagement.dm" +#include "code\modules\modular_computers\file_system\programs\mafia_ntos.dm" #include "code\modules\modular_computers\file_system\programs\newscasterapp.dm" #include "code\modules\modular_computers\file_system\programs\notepad.dm" #include "code\modules\modular_computers\file_system\programs\nt_pay.dm" #include "code\modules\modular_computers\file_system\programs\ntdownloader.dm" -#include "code\modules\modular_computers\file_system\programs\ntnrc_client.dm" #include "code\modules\modular_computers\file_system\programs\portrait_printer.dm" #include "code\modules\modular_computers\file_system\programs\powermonitor.dm" #include "code\modules\modular_computers\file_system\programs\radar.dm" @@ -4747,16 +5090,20 @@ #include "code\modules\modular_computers\file_system\programs\techweb.dm" #include "code\modules\modular_computers\file_system\programs\theme_selector.dm" #include "code\modules\modular_computers\file_system\programs\wirecarp.dm" +#include "code\modules\modular_computers\file_system\programs\antagonist\contractor_program.dm" #include "code\modules\modular_computers\file_system\programs\antagonist\dos.dm" #include "code\modules\modular_computers\file_system\programs\antagonist\revelation.dm" +#include "code\modules\modular_computers\file_system\programs\chatroom\conversation.dm" +#include "code\modules\modular_computers\file_system\programs\chatroom\ntnrc_client.dm" #include "code\modules\modular_computers\file_system\programs\maintenance\_maintenance_program.dm" #include "code\modules\modular_computers\file_system\programs\maintenance\camera.dm" #include "code\modules\modular_computers\file_system\programs\maintenance\modsuit.dm" #include "code\modules\modular_computers\file_system\programs\maintenance\phys_scanner.dm" +#include "code\modules\modular_computers\file_system\programs\maintenance\spectre_meter.dm" #include "code\modules\modular_computers\file_system\programs\maintenance\themes.dm" +#include "code\modules\modular_computers\file_system\programs\messenger\messenger_circuit.dm" #include "code\modules\modular_computers\file_system\programs\messenger\messenger_data.dm" #include "code\modules\modular_computers\file_system\programs\messenger\messenger_program.dm" -#include "code\modules\modular_computers\NTNet\NTNRC\conversation.dm" #include "code\modules\movespeed\_movespeed_modifier.dm" #include "code\modules\movespeed\modifiers\components.dm" #include "code\modules\movespeed\modifiers\drugs.dm" @@ -4819,6 +5166,7 @@ #include "code\modules\plumbing\plumbers\fermenter.dm" #include "code\modules\plumbing\plumbers\filter.dm" #include "code\modules\plumbing\plumbers\grinder_chemical.dm" +#include "code\modules\plumbing\plumbers\iv_drip.dm" #include "code\modules\plumbing\plumbers\pill_press.dm" #include "code\modules\plumbing\plumbers\plumbing_buffer.dm" #include "code\modules\plumbing\plumbers\pumps.dm" @@ -4826,12 +5174,12 @@ #include "code\modules\plumbing\plumbers\splitters.dm" #include "code\modules\plumbing\plumbers\synthesizer.dm" #include "code\modules\plumbing\plumbers\teleporter.dm" +#include "code\modules\plumbing\plumbers\vatgrower.dm" #include "code\modules\point\point.dm" #include "code\modules\power\cable.dm" #include "code\modules\power\cell.dm" #include "code\modules\power\energy_accumulator.dm" #include "code\modules\power\floodlight.dm" -#include "code\modules\power\generator.dm" #include "code\modules\power\gravitygenerator.dm" #include "code\modules\power\monitor.dm" #include "code\modules\power\multiz.dm" @@ -4843,6 +5191,7 @@ #include "code\modules\power\smes.dm" #include "code\modules\power\solar.dm" #include "code\modules\power\terminal.dm" +#include "code\modules\power\thermoelectric_generator.dm" #include "code\modules\power\tracker.dm" #include "code\modules\power\apc\apc_appearance.dm" #include "code\modules\power\apc\apc_attack.dm" @@ -4969,6 +5318,7 @@ #include "code\modules\projectiles\guns\special\blastcannon.dm" #include "code\modules\projectiles\guns\special\chem_gun.dm" #include "code\modules\projectiles\guns\special\grenade_launcher.dm" +#include "code\modules\projectiles\guns\special\hand_of_midas.dm" #include "code\modules\projectiles\guns\special\meat_hook.dm" #include "code\modules\projectiles\guns\special\medbeam.dm" #include "code\modules\projectiles\guns\special\syringe_gun.dm" @@ -4991,11 +5341,11 @@ #include "code\modules\projectiles\projectile\bullets\special.dm" #include "code\modules\projectiles\projectile\energy\_energy.dm" #include "code\modules\projectiles\projectile\energy\chameleon.dm" -#include "code\modules\projectiles\projectile\energy\decloner.dm" #include "code\modules\projectiles\projectile\energy\ebow.dm" #include "code\modules\projectiles\projectile\energy\net_snare.dm" #include "code\modules\projectiles\projectile\energy\ninja.dm" #include "code\modules\projectiles\projectile\energy\nuclear_particle.dm" +#include "code\modules\projectiles\projectile\energy\radiation.dm" #include "code\modules\projectiles\projectile\energy\stun.dm" #include "code\modules\projectiles\projectile\energy\tesla.dm" #include "code\modules\projectiles\projectile\energy\thermal.dm" @@ -5017,10 +5367,14 @@ #include "code\modules\reagents\chemistry\chem_wiki_render.dm" #include "code\modules\reagents\chemistry\colors.dm" #include "code\modules\reagents\chemistry\equilibrium.dm" -#include "code\modules\reagents\chemistry\holder.dm" #include "code\modules\reagents\chemistry\items.dm" #include "code\modules\reagents\chemistry\reagents.dm" #include "code\modules\reagents\chemistry\recipes.dm" +#include "code\modules\reagents\chemistry\holder\holder.dm" +#include "code\modules\reagents\chemistry\holder\mob_life.dm" +#include "code\modules\reagents\chemistry\holder\properties.dm" +#include "code\modules\reagents\chemistry\holder\reactions.dm" +#include "code\modules\reagents\chemistry\holder\ui_data.dm" #include "code\modules\reagents\chemistry\machinery\chem_dispenser.dm" #include "code\modules\reagents\chemistry\machinery\chem_heater.dm" #include "code\modules\reagents\chemistry\machinery\chem_mass_spec.dm" @@ -5029,6 +5383,7 @@ #include "code\modules\reagents\chemistry\machinery\chem_separator.dm" #include "code\modules\reagents\chemistry\machinery\chem_synthesizer.dm" #include "code\modules\reagents\chemistry\machinery\pandemic.dm" +#include "code\modules\reagents\chemistry\machinery\portable_chem_mixer.dm" #include "code\modules\reagents\chemistry\machinery\reagentgrinder.dm" #include "code\modules\reagents\chemistry\machinery\smoke_machine.dm" #include "code\modules\reagents\chemistry\reagents\atmos_gas_reagents.dm" @@ -5096,21 +5451,21 @@ #include "code\modules\recycling\disposal\outlet.dm" #include "code\modules\recycling\disposal\pipe.dm" #include "code\modules\recycling\disposal\pipe_sorting.dm" -#include "code\modules\religion\pyre_rites.dm" #include "code\modules\religion\religion_sects.dm" #include "code\modules\religion\religion_structures.dm" #include "code\modules\religion\rites.dm" #include "code\modules\religion\burdened\burdened_trauma.dm" #include "code\modules\religion\burdened\psyker.dm" +#include "code\modules\religion\festival\festival_violin.dm" #include "code\modules\religion\festival\instrument_rites.dm" #include "code\modules\religion\honorbound\honorbound_rites.dm" #include "code\modules\religion\honorbound\honorbound_trauma.dm" +#include "code\modules\religion\pyre\pyre_rites.dm" #include "code\modules\religion\sparring\ceremonial_gear.dm" #include "code\modules\religion\sparring\sparring_contract.dm" #include "code\modules\religion\sparring\sparring_datum.dm" #include "code\modules\requests\request.dm" #include "code\modules\requests\request_manager.dm" -#include "code\modules\research\bepis.dm" #include "code\modules\research\designs.dm" #include "code\modules\research\destructive_analyzer.dm" #include "code\modules\research\experimentor.dm" @@ -5147,6 +5502,7 @@ #include "code\modules\research\designs\autolathe\engineering_designs.dm" #include "code\modules\research\designs\autolathe\materials.dm" #include "code\modules\research\designs\autolathe\medsci_designs.dm" +#include "code\modules\research\designs\autolathe\mining.dm" #include "code\modules\research\designs\autolathe\multi-department_designs.dm" #include "code\modules\research\designs\autolathe\security_designs.dm" #include "code\modules\research\designs\autolathe\service_designs.dm" @@ -5191,7 +5547,6 @@ #include "code\modules\research\xenobiology\vatgrowing\microscope.dm" #include "code\modules\research\xenobiology\vatgrowing\petri_dish.dm" #include "code\modules\research\xenobiology\vatgrowing\swab.dm" -#include "code\modules\research\xenobiology\vatgrowing\vatgrower.dm" #include "code\modules\research\xenobiology\vatgrowing\samples\_micro_organism.dm" #include "code\modules\research\xenobiology\vatgrowing\samples\_sample.dm" #include "code\modules\research\xenobiology\vatgrowing\samples\cell_lines\common.dm" @@ -5225,6 +5580,7 @@ #include "code\modules\shuttle\shuttle_events\meteors.dm" #include "code\modules\shuttle\shuttle_events\misc.dm" #include "code\modules\shuttle\shuttle_events\player_controlled.dm" +#include "code\modules\shuttle\shuttle_events\turbulence.dm" #include "code\modules\spatial_grid\cell_tracker.dm" #include "code\modules\spells\spell.dm" #include "code\modules\spells\spell_types\madness_curse.dm" @@ -5287,7 +5643,6 @@ #include "code\modules\spells\spell_types\self\lichdom.dm" #include "code\modules\spells\spell_types\self\mime_vow.dm" #include "code\modules\spells\spell_types\self\mutate.dm" -#include "code\modules\spells\spell_types\self\personality_commune.dm" #include "code\modules\spells\spell_types\self\rod_form.dm" #include "code\modules\spells\spell_types\self\sanguine_strike.dm" #include "code\modules\spells\spell_types\self\smoke.dm" @@ -5312,6 +5667,7 @@ #include "code\modules\spells\spell_types\touch\smite.dm" #include "code\modules\station_goals\bsa.dm" #include "code\modules\station_goals\dna_vault.dm" +#include "code\modules\station_goals\generate_goals.dm" #include "code\modules\station_goals\meteor_shield.dm" #include "code\modules\station_goals\station_goal.dm" #include "code\modules\station_goals\vault_mutation.dm" @@ -5381,6 +5737,7 @@ #include "code\modules\surgery\organs\_organ.dm" #include "code\modules\surgery\organs\autosurgeon.dm" #include "code\modules\surgery\organs\helpers.dm" +#include "code\modules\surgery\organs\organ_movement.dm" #include "code\modules\surgery\organs\external\_external_organ.dm" #include "code\modules\surgery\organs\external\restyling.dm" #include "code\modules\surgery\organs\external\spines.dm" @@ -5452,6 +5809,26 @@ #include "code\modules\tgui_panel\telemetry.dm" #include "code\modules\tgui_panel\tgui_panel.dm" #include "code\modules\tooltip\tooltip.dm" +#include "code\modules\transport\_transport_machinery.dm" +#include "code\modules\transport\admin.dm" +#include "code\modules\transport\linear_controller.dm" +#include "code\modules\transport\transport_module.dm" +#include "code\modules\transport\transport_navigation.dm" +#include "code\modules\transport\elevator\elev_controller.dm" +#include "code\modules\transport\elevator\elev_doors.dm" +#include "code\modules\transport\elevator\elev_indicator.dm" +#include "code\modules\transport\elevator\elev_music_zone.dm" +#include "code\modules\transport\elevator\elev_panel.dm" +#include "code\modules\transport\tram\tram_controller.dm" +#include "code\modules\transport\tram\tram_controls.dm" +#include "code\modules\transport\tram\tram_displays.dm" +#include "code\modules\transport\tram\tram_doors.dm" +#include "code\modules\transport\tram\tram_floors.dm" +#include "code\modules\transport\tram\tram_machinery.dm" +#include "code\modules\transport\tram\tram_power.dm" +#include "code\modules\transport\tram\tram_remote.dm" +#include "code\modules\transport\tram\tram_signals.dm" +#include "code\modules\transport\tram\tram_structures.dm" #include "code\modules\tutorials\_tutorial.dm" #include "code\modules\tutorials\tutorial_instruction.dm" #include "code\modules\tutorials\tutorials\drop.dm" @@ -5464,6 +5841,7 @@ #include "code\modules\uplink\uplink_items\badass.dm" #include "code\modules\uplink\uplink_items\bundle.dm" #include "code\modules\uplink\uplink_items\clownops.dm" +#include "code\modules\uplink\uplink_items\contractor.dm" #include "code\modules\uplink\uplink_items\dangerous.dm" #include "code\modules\uplink\uplink_items\device_tools.dm" #include "code\modules\uplink\uplink_items\explosive.dm" @@ -5562,10 +5940,10 @@ #include "code\modules\visuals\render_steps.dm" #include "code\modules\wiremod\components\abstract\assoc_list_variable.dm" #include "code\modules\wiremod\components\abstract\compare.dm" -#include "code\modules\wiremod\components\abstract\equpiment_action.dm" #include "code\modules\wiremod\components\abstract\list_variable.dm" #include "code\modules\wiremod\components\abstract\module.dm" #include "code\modules\wiremod\components\abstract\variable.dm" +#include "code\modules\wiremod\components\action\equpiment_action.dm" #include "code\modules\wiremod\components\action\laserpointer.dm" #include "code\modules\wiremod\components\action\light.dm" #include "code\modules\wiremod\components\action\mmi.dm" @@ -5623,6 +6001,7 @@ #include "code\modules\wiremod\components\list\list_pick.dm" #include "code\modules\wiremod\components\list\list_remove.dm" #include "code\modules\wiremod\components\list\split.dm" +#include "code\modules\wiremod\components\math\arctan2.dm" #include "code\modules\wiremod\components\math\arithmetic.dm" #include "code\modules\wiremod\components\math\binary_conversion.dm" #include "code\modules\wiremod\components\math\comparison.dm" @@ -5672,6 +6051,7 @@ #include "code\modules\wiremod\datatypes\option.dm" #include "code\modules\wiremod\datatypes\signal.dm" #include "code\modules\wiremod\datatypes\string.dm" +#include "code\modules\wiremod\datatypes\user.dm" #include "code\modules\wiremod\datatypes\composite\assoc_list.dm" #include "code\modules\wiremod\datatypes\composite\composite.dm" #include "code\modules\wiremod\datatypes\composite\list.dm" diff --git a/tgui/.eslintignore b/tgui/.eslintignore index a59187b933aee..d3c0ac79cd882 100644 --- a/tgui/.eslintignore +++ b/tgui/.eslintignore @@ -3,4 +3,14 @@ /**/*.bundle.* /**/*.chunk.* /**/*.hot-update.* -/packages/inferno/** +**.lock +**.log +**.json +**.svg +**.scss +**.md +**.css +**.txt +**.woff2 +**.eot +**.ttf diff --git a/tgui/.eslintrc-sonar.yml b/tgui/.eslintrc-sonar.yml index 3cdd49f889e2a..ebc57f72c008f 100644 --- a/tgui/.eslintrc-sonar.yml +++ b/tgui/.eslintrc-sonar.yml @@ -1,26 +1 @@ -rules: - # radar/cognitive-complexity: error - radar/max-switch-cases: error - radar/no-all-duplicated-branches: error - radar/no-collapsible-if: error - radar/no-collection-size-mischeck: error - radar/no-duplicate-string: error - radar/no-duplicated-branches: error - radar/no-element-overwrite: error - radar/no-extra-arguments: error - radar/no-identical-conditions: error - radar/no-identical-expressions: error - radar/no-identical-functions: error - radar/no-inverted-boolean-check: error - radar/no-one-iteration-loop: error - radar/no-redundant-boolean: error - radar/no-redundant-jump: error - radar/no-same-line-conditional: error - radar/no-small-switch: error - radar/no-unused-collection: error - radar/no-use-of-empty-return-value: error - radar/no-useless-catch: error - radar/prefer-immediate-return: error - radar/prefer-object-literal: error - radar/prefer-single-boolean-return: error - radar/prefer-while: error +extends: 'plugin:sonarjs/recommended' diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml index 7fee3791fbbfc..92dfe1320cbfd 100644 --- a/tgui/.eslintrc.yml +++ b/tgui/.eslintrc.yml @@ -11,12 +11,13 @@ env: browser: true node: true plugins: - - radar + - sonarjs - react - unused-imports + - simple-import-sort settings: react: - version: '16.10' + version: '18.2' rules: ## Possible Errors ## ---------------------------------------- @@ -337,7 +338,7 @@ rules: ## Require or disallow named function expressions # func-names: error ## Enforce the consistent use of either function declarations or expressions - func-style: [error, expression] + # func-style: [error, expression] ## Enforce line breaks between arguments of a function call # function-call-argument-newline: error ## Enforce consistent line breaks inside function parentheses @@ -651,7 +652,7 @@ rules: ## Enforce ES5 or ES6 class for React Components react/prefer-es6-class: error ## Enforce that props are read-only - react/prefer-read-only-props: error + react/prefer-read-only-props: off ## Enforce stateless React Components to be written as a pure function react/prefer-stateless-function: error ## Prevent missing props validation in a React component definition @@ -764,3 +765,6 @@ rules: ## Prevents the use of unused imports. ## This could be done by enabling no-unused-vars, but we're doing this for now unused-imports/no-unused-imports: error + ## https://github.com/lydell/eslint-plugin-simple-import-sort/ + simple-import-sort/imports: error + simple-import-sort/exports: error diff --git a/tgui/.prettierrc.yml b/tgui/.prettierrc.yml index 1eebe6098b11d..01769692264f1 100644 --- a/tgui/.prettierrc.yml +++ b/tgui/.prettierrc.yml @@ -1,15 +1 @@ -arrowParens: always -breakLongMethodChains: true -endOfLine: lf -importFormatting: oneline -jsxBracketSameLine: true -jsxSingleQuote: false -offsetTernaryExpressions: true -printWidth: 80 -proseWrap: preserve -quoteProps: preserve -semi: true singleQuote: true -tabWidth: 2 -trailingComma: es5 -useTabs: false diff --git a/tgui/.swcrc b/tgui/.swcrc new file mode 100644 index 0000000000000..c0402a41f0bf6 --- /dev/null +++ b/tgui/.swcrc @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/swcrc", + "jsc": { + "loose": true, + "parser": { + "syntax": "typescript", + "tsx": true + }, + "transform": { + "react": { + "runtime": "automatic" + } + } + } +} diff --git a/tgui/.yarn/sdks/eslint/package.json b/tgui/.yarn/sdks/eslint/package.json index 744a77321030e..b29322a1ffb32 100644 --- a/tgui/.yarn/sdks/eslint/package.json +++ b/tgui/.yarn/sdks/eslint/package.json @@ -2,5 +2,8 @@ "name": "eslint", "version": "7.32.0-sdk", "main": "./lib/api.js", - "type": "commonjs" + "type": "commonjs", + "bin": { + "eslint": "./bin/eslint.js" + } } diff --git a/tgui/.yarn/sdks/prettier/bin/prettier.cjs b/tgui/.yarn/sdks/prettier/bin/prettier.cjs new file mode 100644 index 0000000000000..5efad688e7391 --- /dev/null +++ b/tgui/.yarn/sdks/prettier/bin/prettier.cjs @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require prettier/bin/prettier.cjs + require(absPnpApiPath).setup(); + } +} + +// Defer to the real prettier/bin/prettier.cjs your application uses +module.exports = absRequire(`prettier/bin/prettier.cjs`); diff --git a/tgui/.yarn/sdks/prettier/index.cjs b/tgui/.yarn/sdks/prettier/index.cjs new file mode 100644 index 0000000000000..8758e367a725a --- /dev/null +++ b/tgui/.yarn/sdks/prettier/index.cjs @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require prettier + require(absPnpApiPath).setup(); + } +} + +// Defer to the real prettier your application uses +module.exports = absRequire(`prettier`); diff --git a/tgui/.yarn/sdks/prettier/index.js b/tgui/.yarn/sdks/prettier/index.js deleted file mode 100644 index 81f9bec5fe85e..0000000000000 --- a/tgui/.yarn/sdks/prettier/index.js +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node - -const {existsSync} = require(`fs`); -const {createRequire} = require(`module`); -const {resolve} = require(`path`); - -const relPnpApiPath = "../../../.pnp.cjs"; - -const absPnpApiPath = resolve(__dirname, relPnpApiPath); -const absRequire = createRequire(absPnpApiPath); - -if (existsSync(absPnpApiPath)) { - if (!process.versions.pnp) { - // Setup the environment to be able to require prettier/index.js - require(absPnpApiPath).setup(); - } -} - -// Defer to the real prettier/index.js your application uses -module.exports = absRequire(`prettier/index.js`); diff --git a/tgui/.yarn/sdks/prettier/package.json b/tgui/.yarn/sdks/prettier/package.json index 0cbd71ff32d5a..c61f5117bacf3 100644 --- a/tgui/.yarn/sdks/prettier/package.json +++ b/tgui/.yarn/sdks/prettier/package.json @@ -1,6 +1,7 @@ { "name": "prettier", - "version": "0.19.0-sdk", - "main": "./index.js", - "type": "commonjs" + "version": "3.1.0-sdk", + "main": "./index.cjs", + "type": "commonjs", + "bin": "./bin/prettier.cjs" } diff --git a/tgui/.yarn/sdks/typescript/lib/tsserver.js b/tgui/.yarn/sdks/typescript/lib/tsserver.js index 0fb2ac1079786..bbb1e46501b52 100644 --- a/tgui/.yarn/sdks/typescript/lib/tsserver.js +++ b/tgui/.yarn/sdks/typescript/lib/tsserver.js @@ -109,6 +109,8 @@ const moduleWrapper = tsserver => { str = `zip:${str}`; } break; } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); } } diff --git a/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js b/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js index e7033a81782d0..a68f028fe1971 100644 --- a/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js +++ b/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -109,6 +109,8 @@ const moduleWrapper = tsserver => { str = `zip:${str}`; } break; } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); } } diff --git a/tgui/.yarn/sdks/typescript/lib/typescript.js b/tgui/.yarn/sdks/typescript/lib/typescript.js index e14fa87beaa40..b5f4db25bee67 100644 --- a/tgui/.yarn/sdks/typescript/lib/typescript.js +++ b/tgui/.yarn/sdks/typescript/lib/typescript.js @@ -11,10 +11,10 @@ const absRequire = createRequire(absPnpApiPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { - // Setup the environment to be able to require typescript/lib/typescript.js + // Setup the environment to be able to require typescript require(absPnpApiPath).setup(); } } -// Defer to the real typescript/lib/typescript.js your application uses -module.exports = absRequire(`typescript/lib/typescript.js`); +// Defer to the real typescript your application uses +module.exports = absRequire(`typescript`); diff --git a/tgui/.yarn/sdks/typescript/package.json b/tgui/.yarn/sdks/typescript/package.json index 6aac31b184010..656833d45b642 100644 --- a/tgui/.yarn/sdks/typescript/package.json +++ b/tgui/.yarn/sdks/typescript/package.json @@ -2,5 +2,9 @@ "name": "typescript", "version": "4.9.4-sdk", "main": "./lib/typescript.js", - "type": "commonjs" + "type": "commonjs", + "bin": { + "tsc": "./bin/tsc", + "tsserver": "./bin/tsserver" + } } diff --git a/tgui/.yarnrc.yml b/tgui/.yarnrc.yml index b6387e8e46e83..086484a243a3f 100644 --- a/tgui/.yarnrc.yml +++ b/tgui/.yarnrc.yml @@ -8,7 +8,7 @@ logFilters: plugins: - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - spec: "@yarnpkg/plugin-interactive-tools" + spec: '@yarnpkg/plugin-interactive-tools' pnpEnableEsmLoader: false diff --git a/tgui/README.md b/tgui/README.md index e87130243429b..1bae91fd13258 100644 --- a/tgui/README.md +++ b/tgui/README.md @@ -16,10 +16,9 @@ If you are completely new to frontend and prefer to **learn by doing**, start wi ### Guides -This project uses **Inferno** - a very fast UI rendering engine with a similar API to React. Take your time to read these guides: +This project uses React. Take your time to read the guide: -- [React guide](https://reactjs.org/docs/hello-world.html) -- [Inferno documentation](https://infernojs.org/docs/guides/components) - highlights differences with React. +- [React guide](https://react.dev/learn) If you were already familiar with an older, Ractive-based tgui, and want to translate concepts between old and new tgui, read this [interface conversion guide](docs/converting-old-tgui-interfaces.md). @@ -71,6 +70,7 @@ However, if you want finer control over the installation or build process, you w - `tools/build/build tgui-clean` - Clean up tgui folder. > With Juke Build, you can run multiple targets together, e.g.: +> > ``` > tools/build/build tgui tgui-lint tgui-tsc tgui-test > ``` @@ -137,7 +137,7 @@ Press `F12` or click the green bug to open the KitchenSink interface. This inter playground to test various tgui components. **Layout Debugger.** -Press `F11` to toggle the *layout debugger*. It will show outlines of +Press `F11` to toggle the _layout debugger_. It will show outlines of all tgui elements, which makes it easy to understand how everything comes together, and can reveal certain layout bugs which are not normally visible. diff --git a/tgui/babel.config.js b/tgui/babel.config.js deleted file mode 100644 index e702c9a7119d6..0000000000000 --- a/tgui/babel.config.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -const createBabelConfig = (options) => { - const { presets = [], plugins = [], removeConsole } = options; - // prettier-ignore - return { - presets: [ - [require.resolve('@babel/preset-typescript'), { - allowDeclareFields: true, - }], - [require.resolve('@babel/preset-env'), { - modules: 'commonjs', - useBuiltIns: 'entry', - corejs: '3', - spec: false, - loose: true, - targets: [], - }], - ...presets, - ].filter(Boolean), - plugins: [ - [require.resolve('@babel/plugin-proposal-class-properties'), { - loose: true, - }], - require.resolve('@babel/plugin-transform-jscript'), - require.resolve('babel-plugin-inferno'), - removeConsole && require.resolve('babel-plugin-transform-remove-console'), - require.resolve('common/string.babel-plugin.cjs'), - ...plugins, - ].filter(Boolean), - }; -}; - -module.exports = (api) => { - api.cache(true); - const mode = process.env.NODE_ENV; - return createBabelConfig({ mode }); -}; - -module.exports.createBabelConfig = createBabelConfig; diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md index 9fff68172fd8d..6084b64082f82 100644 --- a/tgui/docs/component-reference.md +++ b/tgui/docs/component-reference.md @@ -65,17 +65,13 @@ it is used a lot in this framework. **Event handlers.** Event handlers are callbacks that you can attack to various element to -listen for browser events. Inferno supports camelcase (`onClick`) and -lowercase (`onclick`) event names. - -- Camel case names are what's called *synthetic* events, and are the -**preferred way** of handling events in React, for efficiency and -performance reasons. Please read -[Inferno Event Handling](https://infernojs.org/docs/guides/event-handling) -to understand what this is about. -- Lower case names are native browser events and should be used sparingly, -for example when you need an explicit IE8 support. **DO NOT** use -lowercase event handlers unless you really know what you are doing. +listen for browser events. React supports camelcase (`onClick`) event names. + +- Camel case names are what's called _synthetic_ events, and are the + **preferred way** of handling events in React, for efficiency and + performance reasons. Please read + [React Event Handling](https://react.dev/learn/responding-to-events) + to understand what this is about. ## `tgui/components` @@ -87,13 +83,13 @@ This component provides animations for numeric values. - `value: number` - Value to animate. - `initial: number` - Initial value to use in animation when element -first appears. If you set initial to `0` for example, number will always -animate starting from `0`, and if omitted, it will not play an initial -animation. + first appears. If you set initial to `0` for example, number will always + animate starting from `0`, and if omitted, it will not play an initial + animation. - `format: value => value` - Output formatter. - Example: `value => Math.round(value)`. - `children: (formattedValue, rawValue) => any` - Pull the animated number to -animate more complex things deeper in the DOM tree. + animate more complex things deeper in the DOM tree. - Example: `(_, value) => ` ### `BlockQuote` @@ -129,9 +125,7 @@ To workaround this problem, the Box children accept a render props function. This way, `Button` can pull out the `className` generated by the `Box`. ```jsx - - {props => + ``` @@ -409,17 +398,17 @@ effectively places the last flex item to the very end of the flex container. - See inherited props: [Box](#box) - ~~`spacing: number`~~ - **Removed in tgui 4.3**, -use [Stack](#stack) instead. + use [Stack](#stack) instead. - `inline: boolean` - Makes flexbox container inline, with similar behavior -to an `inline` property on a `Box`. + to an `inline` property on a `Box`. - `direction: string` - This establishes the main-axis, thus defining the -direction flex items are placed in the flex container. + direction flex items are placed in the flex container. - `row` (default) - left to right. - `row-reverse` - right to left. - `column` - top to bottom. - `column-reverse` - bottom to top. - `wrap: string` - By default, flex items will all try to fit onto one line. -You can change that and allow the items to wrap as needed with this property. + You can change that and allow the items to wrap as needed with this property. - `nowrap` (default) - all flex items will be on one line - `wrap` - flex items will wrap onto multiple lines, from top to bottom. - `wrap-reverse` - flex items will wrap onto multiple lines from bottom to top. @@ -430,22 +419,22 @@ You can change that and allow the items to wrap as needed with this property. - `center` - items are centered on the cross axis. - `baseline` - items are aligned such as their baselines align. - `justify: string` - This defines the alignment along the main axis. -It helps distribute extra free space leftover when either all the flex -items on a line are inflexible, or are flexible but have reached their -maximum size. It also exerts some control over the alignment of items -when they overflow the line. + It helps distribute extra free space leftover when either all the flex + items on a line are inflexible, or are flexible but have reached their + maximum size. It also exerts some control over the alignment of items + when they overflow the line. - `flex-start` (default) - items are packed toward the start of the - flex-direction. + flex-direction. - `flex-end` - items are packed toward the end of the flex-direction. - `space-between` - items are evenly distributed in the line; first item is - on the start line, last item on the end line + on the start line, last item on the end line - `space-around` - items are evenly distributed in the line with equal space - around them. Note that visually the spaces aren't equal, since all the items - have equal space on both sides. The first item will have one unit of space - against the container edge, but two units of space between the next item - because that next item has its own spacing that applies. + around them. Note that visually the spaces aren't equal, since all the items + have equal space on both sides. The first item will have one unit of space + against the container edge, but two units of space between the next item + because that next item has its own spacing that applies. - `space-evenly` - items are distributed so that the spacing between any two - items (and the space to the edges) is equal. + items (and the space to the edges) is equal. - TBD (not all properties are supported in IE11). ### `Flex.Item` @@ -454,24 +443,24 @@ when they overflow the line. - See inherited props: [Box](#box) - `order: number` - By default, flex items are laid out in the source order. -However, the order property controls the order in which they appear in the -flex container. + However, the order property controls the order in which they appear in the + flex container. - `grow: number | boolean` - This defines the ability for a flex item to grow -if necessary. It accepts a unitless value that serves as a proportion. It -dictates what amount of the available space inside the flex container the -item should take up. This number is unit-less and is relative to other -siblings. + if necessary. It accepts a unitless value that serves as a proportion. It + dictates what amount of the available space inside the flex container the + item should take up. This number is unit-less and is relative to other + siblings. - `shrink: number | boolean` - This defines the ability for a flex item to -shrink if necessary. Inverse of `grow`. + shrink if necessary. Inverse of `grow`. - `basis: number | string` - This defines the default size of an element -before any flex-related calculations are done. Has to be a length -(e.g. `20%`, `5rem`), an `auto` or `content` keyword. + before any flex-related calculations are done. Has to be a length + (e.g. `20%`, `5rem`), an `auto` or `content` keyword. - **Important:** IE11 flex is buggy, and auto width/height calculations - can sometimes end up in a circular dependency. This usually happens, when - working with tables inside flex (they have wacky internal widths and such). - Setting basis to `0` breaks the loop and fixes all of the problems. + can sometimes end up in a circular dependency. This usually happens, when + working with tables inside flex (they have wacky internal widths and such). + Setting basis to `0` breaks the loop and fixes all of the problems. - `align: string` - This allows the default alignment (or the one specified by -align-items) to be overridden for individual flex items. See: [Flex](#flex). + align-items) to be overridden for individual flex items. See: [Flex](#flex). ### `Grid` @@ -487,14 +476,10 @@ Example: ```jsx -
    - Hello world! -
    +
    Hello world!
    -
    - Hello world! -
    +
    Hello world!
    ``` @@ -520,6 +505,7 @@ Renders one of the FontAwesome icons of your choice. To smoothen the transition from v4 to v5, we have added a v4 semantic to transform names with `-o` suffixes to FA Regular icons. For example: + - `square` will get transformed to `fas square` - `square-o` will get transformed to `far square` @@ -528,10 +514,10 @@ transform names with `-o` suffixes to FA Regular icons. For example: - See inherited props: [Box](#box) - `name: string` - Icon name. - `size: number` - Icon size. `1` is normal size, `2` is two times bigger. -Fractional numbers are supported. + Fractional numbers are supported. - `rotation: number` - Icon rotation, in degrees. - `spin: boolean` - Whether an icon should be spinning. Good for load -indicators. + indicators. ### `Icon.Stack` @@ -559,15 +545,18 @@ A basic text input, which allow users to enter text into a UI. **Props:** - See inherited props: [Box](#box) -- `value: string` - Value of an input. +- `value: string` - The initial value displayed on the input. - `placeholder: string` - Text placed into Input box when it's empty, -otherwise nothing. Clears automatically when focused. + otherwise nothing. Clears automatically when focused. - `fluid: boolean` - Fill all available horizontal space. - `selfClear: boolean` - Clear after hitting enter, as well as remain focused -when this happens. Useful for things like chat inputs. -- `onChange: (e, value) => void` - An event, which fires when you commit -the text by either unfocusing the input box, or by pressing the Enter key. -- `onInput: (e, value) => void` - An event, which fires on every keypress. + when this happens. Useful for things like chat inputs. +- `onChange: (e, value) => void` - Fires when the user clicks out or presses enter. +- `onEnter: (e, value) => void` - Fires when the user hits enter. +- `onEscape: (e) => void` - Fires when the user hits escape. +- `onInput: (e, value) => void` - Fires when the user types into the input. +- `expensive: boolean` - Introduces a delay before updating the input. Useful for large filters, + where you don't want to update on every keystroke. ### `Knob` @@ -582,30 +571,30 @@ Single click opens an input box to manually type in a number. - `animated: boolean` - Animates the value if it was changed externally. - `bipolar: boolean` - Knob can be bipolar or unipolar. - `size: number` - Relative size of the knob. `1` is normal size, `2` is two -times bigger. Fractional numbers are supported. + times bigger. Fractional numbers are supported. - `color: string` - Color of the outer ring around the knob. - `value: number` - Value itself, controls the position of the cursor. - `unit: string` - Unit to display to the right of value. - `minValue: number` - Lowest possible value. - `maxValue: number` - Highest possible value. - `fillValue: number` - If set, this value will be used to set the fill -percentage of the outer ring independently of the main value. + percentage of the outer ring independently of the main value. - `ranges: { color: [from, to] }` - Applies a `color` to the outer ring around -the knob based on whether the value lands in the range between `from` and `to`. -See an example of this prop in [ProgressBar](#progressbar). + the knob based on whether the value lands in the range between `from` and `to`. + See an example of this prop in [ProgressBar](#progressbar). - `step: number` (default: 1) - Adjust value by this amount when -dragging the input. + dragging the input. - `stepPixelSize: number` (default: 1) - Screen distance mouse needs -to travel to adjust value by one `step`. + to travel to adjust value by one `step`. - `format: value => value` - Format value using this function before -displaying it. + displaying it. - `suppressFlicker: number` - A number in milliseconds, for which the input -will hold off from updating while events propagate through the backend. -Default is about 250ms, increase it if you still see flickering. + will hold off from updating while events propagate through the backend. + Default is about 250ms, increase it if you still see flickering. - `onChange: (e, value) => void` - An event, which fires when you release -the input, or successfully enter a number. + the input, or successfully enter a number. - `onDrag: (e, value) => void` - An event, which fires about every 500ms -when you drag the input up and down, on release and on manual editing. + when you drag the input up and down, on release and on manual editing. ### `LabeledControls` @@ -633,9 +622,7 @@ column is labels, and second column is content. ```jsx - - Content - + Content ``` @@ -644,13 +631,7 @@ to perform some sort of action), there is a way to do that: ```jsx - - Click me! - - )}> + Click me!}> Content @@ -665,7 +646,7 @@ to perform some sort of action), there is a way to do that: **Props:** - `className: string` - Applies a CSS class to the element. -- `label: string|InfernoNode` - Item label. +- `label: string|ReactNode` - Item label. - `labelWrap: boolean` - Lets the label wrap and makes it not take the minimum width. - `labelColor: string` - Sets the color of the label. - `color: string` - Sets the color of the content text. @@ -689,9 +670,7 @@ Example: ```jsx - - Content - + Content ``` @@ -737,22 +716,22 @@ to fine tune the value, or single click it to manually type a number. - `minValue: number` - Lowest possible value. - `maxValue: number` - Highest possible value. - `step: number` (default: 1) - Adjust value by this amount when -dragging the input. + dragging the input. - `stepPixelSize: number` (default: 1) - Screen distance mouse needs -to travel to adjust value by one `step`. + to travel to adjust value by one `step`. - `width: string|number` - Width of the element, in `Box` units or pixels. - `height: string|numer` - Height of the element, in `Box` units or pixels. - `lineHeight: string|number` - lineHeight of the element, in `Box` units or pixels. - `fontSize: string|number` - fontSize of the element, in `Box` units or pixels. - `format: value => value` - Format value using this function before -displaying it. + displaying it. - `suppressFlicker: number` - A number in milliseconds, for which the input -will hold off from updating while events propagate through the backend. -Default is about 250ms, increase it if you still see flickering. + will hold off from updating while events propagate through the backend. + Default is about 250ms, increase it if you still see flickering. - `onChange: (e, value) => void` - An event, which fires when you release -the input, or successfully enter a number. + the input, or successfully enter a number. - `onDrag: (e, value) => void` - An event, which fires about every 500ms -when you drag the input up and down, on release and on manual editing. + when you drag the input up and down, on release and on manual editing. ### `Popper` @@ -760,9 +739,10 @@ Popper lets you position elements so that they don't go out of the bounds of the **Props:** -- `popperContent: InfernoNode` - The content that will be put inside the popper. -- `options?: { ... }` - An object of options to pass to `createPopper`. See [https://popper.js.org/docs/v2/constructors/#options], but the one you want most is `placement`. Valid placements are "bottom", "top", "left", and "right". You can affix "-start" and "-end" to achieve something like top left or top right respectively. You can also use "auto" (with an optional "-start" or "-end"), where a best fit will be chosen. -- `additionalStyles: { ... }` - A map of CSS styles to add to the element that will contain the popper. +- `content: ReactNode` - The content that will be put inside the popper. +- `isOpen: boolean` - Whether or not the popper is open. +- `onClickOutside?: (e) => void` - A function that will be called when the user clicks outside of the popper. +- `placement?: string` - The placement of the popper. See [https://popper.js.org/docs/v2/constructors/#placement] ### `ProgressBar` @@ -781,18 +761,19 @@ Usage of `ranges` prop: average: [0.25, 0.5], bad: [-Infinity, 0.25], }} - value={0.6} /> + value={0.6} +/> ``` **Props:** - `value: number` - Current progress as a floating point number between -`minValue` (default: 0) and `maxValue` (default: 1). Determines the -percentage and how filled the bar is. + `minValue` (default: 0) and `maxValue` (default: 1). Determines the + percentage and how filled the bar is. - `minValue: number` - Lowest possible value. - `maxValue: number` - Highest possible value. - `ranges: { color: [from, to] }` - Applies a `color` to the progress bar -based on whether the value lands in the range between `from` and `to`. + based on whether the value lands in the range between `from` and `to`. - `color: string` - Color of the progress bar. Can take any of the following formats: - `#ffffff` - Hex format - `rgb(r,g,b) / rgba(r,g,b,a)` - RGB format @@ -810,13 +791,14 @@ The RoundGauge component provides a visual representation of a single metric, as value={tankPressure} minValue={0} maxValue={pressureLimit} - alertAfter={pressureLimit * 0.70} + alertAfter={pressureLimit * 0.7} ranges={{ - "good": [0, pressureLimit * 0.70], - "average": [pressureLimit * 0.70, pressureLimit * 0.85], - "bad": [pressureLimit * 0.85, pressureLimit], + good: [0, pressureLimit * 0.7], + average: [pressureLimit * 0.7, pressureLimit * 0.85], + bad: [pressureLimit * 0.85, pressureLimit], }} - format={formatPressure} /> + format={formatPressure} +/> ``` The alert on the gauge is optional, and will only be shown if the `alertAfter` prop is defined. When defined, the alert will begin to flash the respective color upon which the needle currently rests, as defined in the `ranges` prop. @@ -844,22 +826,14 @@ clearly indicates hierarchy. Section can also be titled to clearly define its purpose. ```jsx -
    - Here you can order supply crates. -
    +
    Here you can order supply crates.
    ``` If you want to have a button on the right side of an section title (for example, to perform some sort of action), there is a way to do that: ```jsx -
    - Send shuttle - - )}> +
    Send shuttle}> Here you can order supply crates.
    ``` @@ -893,23 +867,23 @@ Single click opens an input box to manually type in a number. - `minValue: number` - Lowest possible value. - `maxValue: number` - Highest possible value. - `fillValue: number` - If set, this value will be used to set the fill -percentage of the progress bar filler independently of the main value. + percentage of the progress bar filler independently of the main value. - `ranges: { color: [from, to] }` - Applies a `color` to the slider -based on whether the value lands in the range between `from` and `to`. -See an example of this prop in [ProgressBar](#progressbar). + based on whether the value lands in the range between `from` and `to`. + See an example of this prop in [ProgressBar](#progressbar). - `step: number` (default: 1) - Adjust value by this amount when -dragging the input. + dragging the input. - `stepPixelSize: number` (default: 1) - Screen distance mouse needs -to travel to adjust value by one `step`. + to travel to adjust value by one `step`. - `format: value => value` - Format value using this function before -displaying it. + displaying it. - `suppressFlicker: number` - A number in milliseconds, for which the input -will hold off from updating while events propagate through the backend. -Default is about 250ms, increase it if you still see flickering. + will hold off from updating while events propagate through the backend. + Default is about 250ms, increase it if you still see flickering. - `onChange: (e, value) => void` - An event, which fires when you release -the input, or successfully enter a number. + the input, or successfully enter a number. - `onDrag: (e, value) => void` - An event, which fires about every 500ms -when you drag the input up and down, on release and on manual editing. + when you drag the input up and down, on release and on manual editing. ### `Stack` @@ -925,13 +899,9 @@ Stacks can be vertical by adding a `vertical` property. ```jsx - - Button description - + Button description - + ``` @@ -946,9 +916,7 @@ Make sure to use the `fill` property. -
    - Sidebar -
    +
    Sidebar
    @@ -958,9 +926,7 @@ Make sure to use the `fill` property.
    -
    - Bottom pane -
    +
    Bottom pane
    @@ -992,9 +958,7 @@ Example: ```jsx - - Hello world! - + Hello world! Label @@ -1023,7 +987,7 @@ A straight forward mapping to `
    ` element. - See inherited props: [Box](#box) - `collapsing: boolean` - Collapses table cell to the smallest possible size, -and stops any text inside from wrapping. + and stops any text inside from wrapping. ### `Tabs` @@ -1059,9 +1023,7 @@ Tabs also support a vertical configuration. This is usually paired with ```jsx - - ... - + ... Tab content. @@ -1075,9 +1037,7 @@ component: ```jsx
    - - ... - + ... ... other things ...
    ``` @@ -1087,9 +1047,9 @@ component: - See inherited props: [Box](#box) - `fluid: boolean` - If true, tabs will take all available horizontal space. - `fill: boolean` - Similarly to `fill` on [Section](#section), tabs will fill -all available vertical space. Only makes sense in a vertical configuration. + all available vertical space. Only makes sense in a vertical configuration. - `vertical: boolean` - Use a vertical configuration, where tabs will be -stacked vertically. + stacked vertically. - `children: Tab[]` - This component only accepts tabs as its children. ### `Tabs.Tab` @@ -1101,8 +1061,8 @@ a lot of `Button` props. - See inherited props: [Button](#button) - `altSelection` - Whether the tab buttons select via standard select (color -change) or by adding a white indicator to the selected tab. -Intended for usage on interfaces where tab color has relevance. + change) or by adding a white indicator to the selected tab. + Intended for usage on interfaces where tab color has relevance. - `icon: string` - Tab icon. - `children: any` - Tab text. - `onClick: function` - Called when element is clicked. @@ -1119,9 +1079,7 @@ Usage: ```jsx - - Sample text. - + Sample text. ``` @@ -1129,7 +1087,7 @@ Usage: - `position?: string` - Tooltip position. See [`Popper`](#Popper) for valid options. Defaults to "auto". - `content: string` - Content of the tooltip. Must be a plain string. -Fragments or other elements are **not** supported. + Fragments or other elements are **not** supported. ## `tgui/layouts` @@ -1143,9 +1101,7 @@ Example: ```jsx - - Hello, world! - + Hello, world! ``` @@ -1160,9 +1116,9 @@ Example: - `height: number` - Window height. - `canClose: boolean` - Controls the ability to close the window. - `children: any` - Child elements, which are rendered directly inside the -window. If you use a [Dimmer](#dimmer) or [Modal](#modal) in your UI, -they should be put as direct childs of a Window, otherwise you should be -putting your content into [Window.Content](#windowcontent). + window. If you use a [Dimmer](#dimmer) or [Modal](#modal) in your UI, + they should be put as direct childs of a Window, otherwise you should be + putting your content into [Window.Content](#windowcontent). ### `Window.Content` diff --git a/tgui/docs/state-usage.md b/tgui/docs/state-usage.md new file mode 100644 index 0000000000000..9d3a2812a68d1 --- /dev/null +++ b/tgui/docs/state-usage.md @@ -0,0 +1,30 @@ +# Managing component state + +React has excellent documentation on useState and useEffect. These hooks should be the ways to manage state in TGUI (v5). +[React Hooks](https://react.dev/learn/state-a-components-memory) + +You might find usages of useLocalState. This should be considered deprecated and will be removed in the future. In older versions of TGUI, InfernoJS did not have hooks, so these were used to manage state. useSharedState is still used in some places where uis are considered "IC" and user input is shared with all persons at the console/machine/thing. + +## A Note on State + +Many beginners tend to overuse state (or hooks all together). State is effective when you want to implement user interactivity, or are handling asynchronous data, but if you are simply using state to store a value that is not changing, you should consider using a variable instead. + +In previous versions of React, each setState would trigger a re-render, which would cause poorly written components to cascade re-render on each page load. Messy! Though this is no longer the case with batch rendering, it's still worthwhile to point out that you might be overusing it. + +## Derived state + +One great way to cut back on state usage is by using props or other state as the basis for a variable. You'll see many examples of this in the TGUI codebase. What does this mean? Here's an example: + +```tsx +// Bad +const [count, setCount] = useState(0); +const [isEven, setIsEven] = useState(false); + +useEffect(() => { + setIsEven(count % 2 === 0); +}, [count]); + +// Good! +const [count, setCount] = useState(0); +const isEven = count % 2 === 0; // Derived state +``` diff --git a/tgui/jest.config.js b/tgui/jest.config.js index 8b78818004be4..d8b4ac3e41a80 100644 --- a/tgui/jest.config.js +++ b/tgui/jest.config.js @@ -8,7 +8,7 @@ module.exports = { testEnvironment: 'jsdom', testRunner: require.resolve('jest-circus/runner'), transform: { - '^.+\\.(js|cjs|ts|tsx)$': require.resolve('babel-jest'), + '^.+\\.(js|cjs|ts|tsx)$': require.resolve('@swc/jest'), }, moduleFileExtensions: ['js', 'cjs', 'ts', 'tsx', 'json'], resetMocks: true, diff --git a/tgui/package.json b/tgui/package.json index c53759dff2180..bfbc5a34aba21 100644 --- a/tgui/package.json +++ b/tgui/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "tgui-workspace", - "version": "4.3.1", + "version": "5.0.0", "packageManager": "yarn@3.3.1", "workspaces": [ "packages/*" @@ -12,52 +12,47 @@ "tgui:build": "BROWSERSLIST_IGNORE_OLD_DATA=true webpack", "tgui:dev": "node --experimental-modules packages/tgui-dev-server/index.js", "tgui:lint": "eslint packages --ext .js,.cjs,.ts,.tsx", - "tgui:prettier": "prettierx --check .", - "tgui:sonar": "eslint packages --ext .js,.cjs,.ts,.tsx -c .eslintrc-sonar.yml", + "tgui:prettier": "prettier --check .", + "tgui:sonar": "eslint packages -c .eslintrc-sonar.yml", "tgui:test": "jest --watch", "tgui:test-simple": "CI=true jest --color", "tgui:test-ci": "CI=true jest --color --collect-coverage", - "tgui:tsc": "tsc" + "tgui:tsc": "tsc", + "tgui:prettier-fix": "prettier --write .", + "tgui:eslint-fix": "eslint --fix packages --ext .js,.cjs,.ts,.tsx" }, "dependencies": { - "@babel/core": "^7.15.0", - "@babel/eslint-parser": "^7.15.0", - "@babel/plugin-proposal-class-properties": "^7.14.5", - "@babel/plugin-transform-jscript": "^7.14.5", - "@babel/preset-env": "^7.15.0", - "@babel/preset-typescript": "^7.15.0", - "@types/jest": "^29.2.4", - "@types/jsdom": "^20.0.1", - "@types/node": "14.x", - "@types/webpack": "^5.28.0", - "@types/webpack-env": "^1.18.0", - "@typescript-eslint/parser": "^5.47.1", - "babel-jest": "^27.0.6", - "babel-loader": "^8.2.2", - "babel-plugin-inferno": "^6.3.0", - "babel-plugin-transform-remove-console": "^6.9.4", - "common": "workspace:*", - "css-loader": "^5.2.7", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-radar": "^0.2.1", - "eslint-plugin-react": "^7.24.0", - "eslint-plugin-unused-imports": "^1.1.4", + "@swc/core": "^1.3.100", + "@swc/jest": "^0.2.29", + "@types/jest": "^29.5.10", + "@types/jsdom": "^21.1.6", + "@types/node": "^14.x", + "@types/webpack": "^5.28.5", + "@types/webpack-env": "^1.18.4", + "@typescript-eslint/parser": "^6.14.0", + "css-loader": "^6.8.1", + "esbuild-loader": "^4.0.2", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-simple-import-sort": "^10.0.0", + "eslint-plugin-sonarjs": "^0.23.0", + "eslint-plugin-unused-imports": "^3.0.0", "file-loader": "^6.2.0", - "inferno": "^7.4.8", - "jest": "^27.0.6", - "jest-circus": "^27.0.6", - "jsdom": "^16.7.0", - "mini-css-extract-plugin": "^1.6.2", - "prettier": "npm:prettierx@0.19.0", - "sass": "^1.37.5", - "sass-loader": "^11.1.1", - "style-loader": "^2.0.0", - "terser-webpack-plugin": "^5.1.4", + "jest": "^29.7.0", + "jest-circus": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jsdom": "^22.1.0", + "mini-css-extract-plugin": "^2.7.6", + "prettier": "^3.1.0", + "sass": "^1.69.5", + "sass-loader": "^13.3.2", + "style-loader": "^3.3.3", + "swc-loader": "^0.2.3", "typescript": "^4.9.4", "url-loader": "^4.1.1", - "webpack": "^5.75.0", - "webpack-bundle-analyzer": "^4.4.2", - "webpack-cli": "^4.7.2" + "webpack": "^5.89.0", + "webpack-bundle-analyzer": "^4.10.1", + "webpack-cli": "^5.1.4" } } diff --git a/tgui/packages/common/collections.ts b/tgui/packages/common/collections.ts index a005da7aa1654..5bfcee8588441 100644 --- a/tgui/packages/common/collections.ts +++ b/tgui/packages/common/collections.ts @@ -32,12 +32,12 @@ export const filter = }; type MapFunction = { - (iterateeFn: (value: T, index: number, collection: T[]) => U): ( - collection: T[] - ) => U[]; + ( + iterateeFn: (value: T, index: number, collection: T[]) => U, + ): (collection: T[]) => U[]; ( - iterateeFn: (value: T, index: K, collection: Record) => U + iterateeFn: (value: T, index: K, collection: Record) => U, ): (collection: Record) => U[]; }; @@ -75,7 +75,7 @@ export const map: MapFunction = */ export const filterMap = ( collection: T[], - iterateeFn: (value: T) => U | undefined + iterateeFn: (value: T) => U | undefined, ): U[] => { const finalCollection: U[] = []; @@ -261,7 +261,7 @@ export const zipWith = const binarySearch = ( getKey: (value: T) => U, collection: readonly T[], - inserting: T + inserting: T, ): number => { if (collection.length === 0) { return 0; diff --git a/tgui/packages/common/color.js b/tgui/packages/common/color.js index b59d82247aae5..59935931d82bf 100644 --- a/tgui/packages/common/color.js +++ b/tgui/packages/common/color.js @@ -30,7 +30,7 @@ export class Color { this.r - this.r * percent, this.g - this.g * percent, this.b - this.b * percent, - this.a + this.a, ); } @@ -48,7 +48,7 @@ Color.fromHex = (hex) => new Color( parseInt(hex.substr(1, 2), 16), parseInt(hex.substr(3, 2), 16), - parseInt(hex.substr(5, 2), 16) + parseInt(hex.substr(5, 2), 16), ); /** @@ -59,7 +59,7 @@ Color.lerp = (c1, c2, n) => (c2.r - c1.r) * n + c1.r, (c2.g - c1.g) * n + c1.g, (c2.b - c1.b) * n + c1.b, - (c2.a - c1.a) * n + c1.a + (c2.a - c1.a) * n + c1.a, ); /** diff --git a/tgui/packages/common/keycodes.js b/tgui/packages/common/keycodes.ts similarity index 100% rename from tgui/packages/common/keycodes.js rename to tgui/packages/common/keycodes.ts diff --git a/tgui/packages/common/keys.ts b/tgui/packages/common/keys.ts index 61b79992b486b..34ac9e1614dde 100644 --- a/tgui/packages/common/keys.ts +++ b/tgui/packages/common/keys.ts @@ -22,18 +22,18 @@ export enum KEY { Backspace = 'Backspace', Control = 'Control', Delete = 'Delete', - Down = 'Down', + Down = 'ArrowDown', End = 'End', Enter = 'Enter', - Escape = 'Esc', + Escape = 'Escape', Home = 'Home', Insert = 'Insert', - Left = 'Left', + Left = 'ArrowLeft', PageDown = 'PageDown', PageUp = 'PageUp', - Right = 'Right', + Right = 'ArrowRight', Shift = 'Shift', Space = ' ', Tab = 'Tab', - Up = 'Up', + Up = 'ArrowUp', } diff --git a/tgui/packages/common/react.ts b/tgui/packages/common/react.ts index 8e42d0971ab41..5260ff6ae128b 100644 --- a/tgui/packages/common/react.ts +++ b/tgui/packages/common/react.ts @@ -52,13 +52,10 @@ export const shallowDiffers = (a: object, b: object) => { }; /** - * Default inferno hooks for pure components. + * A common case in tgui, when you pass a value conditionally, these are + * the types that can fall through the condition. */ -export const pureComponentHooks = { - onComponentShouldUpdate: (lastProps, nextProps) => { - return shallowDiffers(lastProps, nextProps); - }, -}; +export type BooleanLike = number | boolean | null | undefined; /** * A helper to determine whether the object is renderable by React. @@ -69,9 +66,3 @@ export const canRender = (value: unknown) => { && value !== null && typeof value !== 'boolean'; }; - -/** - * A common case in tgui, when you pass a value conditionally, these are - * the types that can fall through the condition. - */ -export type BooleanLike = number | boolean | null | undefined; diff --git a/tgui/packages/common/redux.test.ts b/tgui/packages/common/redux.test.ts index af4e5d4e73eb7..d4af99907cee9 100644 --- a/tgui/packages/common/redux.test.ts +++ b/tgui/packages/common/redux.test.ts @@ -1,4 +1,11 @@ -import { Action, Reducer, applyMiddleware, combineReducers, createAction, createStore } from './redux'; +import { + Action, + applyMiddleware, + combineReducers, + createAction, + createStore, + Reducer, +} from './redux'; // Dummy Reducer const counterReducer: Reducer> = (state = 0, action) => { @@ -31,7 +38,7 @@ describe('Redux implementation tests', () => { test('createStore with applyMiddleware works', () => { const store = createStore( counterReducer, - applyMiddleware(loggingMiddleware) + applyMiddleware(loggingMiddleware), ); expect(store.getState()).toBe(0); }); diff --git a/tgui/packages/common/redux.ts b/tgui/packages/common/redux.ts index 4e618bddafd00..c8eb268f5d44f 100644 --- a/tgui/packages/common/redux.ts +++ b/tgui/packages/common/redux.ts @@ -6,7 +6,7 @@ export type Reducer = ( state: State | undefined, - action: ActionType + action: ActionType, ) => State; export type Store = { @@ -21,7 +21,7 @@ type MiddlewareAPI = { }; export type Middleware = ( - storeApi: MiddlewareAPI + storeApi: MiddlewareAPI, ) => (next: Dispatch) => Dispatch; export type Action = { @@ -33,7 +33,7 @@ export type AnyAction = Action & { }; export type Dispatch = ( - action: ActionType + action: ActionType, ) => void; type StoreEnhancer = (createStoreFunction: Function) => Function; @@ -48,7 +48,7 @@ type PreparedAction = { */ export const createStore = ( reducer: Reducer, - enhancer?: StoreEnhancer + enhancer?: StoreEnhancer, ): Store => { // Apply a store enhancer (applyMiddleware is one of them). if (enhancer) { @@ -90,14 +90,14 @@ export const applyMiddleware = ( ...middlewares: Middleware[] ): StoreEnhancer => { return ( - createStoreFunction: (reducer: Reducer, enhancer?: StoreEnhancer) => Store + createStoreFunction: (reducer: Reducer, enhancer?: StoreEnhancer) => Store, ) => { return (reducer, ...args): Store => { const store = createStoreFunction(reducer, ...args); - let dispatch: Dispatch = () => { + let dispatch: Dispatch = (action, ...args) => { throw new Error( - 'Dispatching while constructing your middleware is not allowed.' + 'Dispatching while constructing your middleware is not allowed.', ); }; @@ -109,7 +109,7 @@ export const applyMiddleware = ( const chain = middlewares.map((middleware) => middleware(storeApi)); dispatch = chain.reduceRight( (next, middleware) => middleware(next), - store.dispatch + store.dispatch, ); return { @@ -129,7 +129,7 @@ export const applyMiddleware = ( * is also more flexible than the redux counterpart. */ export const combineReducers = ( - reducersObj: Record + reducersObj: Record, ): Reducer => { const keys = Object.keys(reducersObj); @@ -170,7 +170,7 @@ export const combineReducers = ( */ export const createAction = ( type: TAction, - prepare?: (...args: any[]) => PreparedAction + prepare?: (...args: any[]) => PreparedAction, ) => { const actionCreator = (...args: any[]) => { let action: Action & PreparedAction = { type }; @@ -194,19 +194,3 @@ export const createAction = ( return actionCreator; }; - -// Implementation specific -// -------------------------------------------------------- - -export const useDispatch = (context: { - store: Store; -}): Dispatch => { - return context.store.dispatch; -}; - -export const useSelector = ( - context: { store: Store }, - selector: (state: State) => Selected -): Selected => { - return selector(context.store.getState()); -}; diff --git a/tgui/packages/common/timer.ts b/tgui/packages/common/timer.ts index 49d36484200b3..1fc3e11fd30e5 100644 --- a/tgui/packages/common/timer.ts +++ b/tgui/packages/common/timer.ts @@ -13,7 +13,7 @@ export const debounce = any>( fn: F, time: number, - immediate = false + immediate = false, ): ((...args: Parameters) => void) => { let timeout: ReturnType | null; return (...args: Parameters) => { @@ -38,7 +38,7 @@ export const debounce = any>( */ export const throttle = any>( fn: F, - time: number + time: number, ): ((...args: Parameters) => void) => { let previouslyRun: number | null, queuedToRun: ReturnType | null; @@ -53,7 +53,7 @@ export const throttle = any>( } else { queuedToRun = setTimeout( () => invokeFn(...args), - time - (now - (previouslyRun ?? 0)) + time - (now - (previouslyRun ?? 0)), ); } }; diff --git a/tgui/packages/tgui-bench/entrypoint.tsx b/tgui/packages/tgui-bench/entrypoint.tsx index 377848fe3ae09..48dcd3dcce113 100644 --- a/tgui/packages/tgui-bench/entrypoint.tsx +++ b/tgui/packages/tgui-bench/entrypoint.tsx @@ -4,8 +4,10 @@ * @license MIT */ -import { setupGlobalEvents } from 'tgui/events'; import 'tgui/styles/main.scss'; + +import { setupGlobalEvents } from 'tgui/events'; + import Benchmark from './lib/benchmark'; const sendMessage = (obj: any) => { diff --git a/tgui/packages/tgui-bench/index.js b/tgui/packages/tgui-bench/index.js index 9f6aee20996d0..b15f3ebf37ad5 100644 --- a/tgui/packages/tgui-bench/index.js +++ b/tgui/packages/tgui-bench/index.js @@ -31,7 +31,7 @@ const setup = async () => { .readFileSync(path.join(publicDir, 'tgui.html'), 'utf-8') .replace('\n', assets); - server.register(require('fastify-static'), { + server.register(require('@fastify/static'), { root: publicDir, }); diff --git a/tgui/packages/tgui-bench/lib/benchmark.d.ts b/tgui/packages/tgui-bench/lib/benchmark.d.ts index 7f3310005f77b..3eac568d4184f 100644 --- a/tgui/packages/tgui-bench/lib/benchmark.d.ts +++ b/tgui/packages/tgui-bench/lib/benchmark.d.ts @@ -27,7 +27,7 @@ declare class Benchmark { static reduce( arr: T[], callback: (accumulator: K, value: T) => K, - thisArg?: any + thisArg?: any, ): K; static options: Benchmark.Options; diff --git a/tgui/packages/tgui-bench/package.json b/tgui/packages/tgui-bench/package.json index 49bc0c423c28d..47cad082fb9b5 100644 --- a/tgui/packages/tgui-bench/package.json +++ b/tgui/packages/tgui-bench/package.json @@ -1,15 +1,14 @@ { "private": true, "name": "tgui-bench", - "version": "4.3.1", + "version": "5.0.0", "dependencies": { + "@fastify/static": "^6.12.0", "common": "workspace:*", - "fastify": "^3.29.4", - "fastify-static": "^4.2.3", - "inferno": "^7.4.8", - "inferno-vnode-flags": "^7.4.8", + "fastify": "^3.29.5", "lodash": "^4.17.21", "platform": "^1.3.6", + "react": "^18.2.0", "tgui": "workspace:*" } } diff --git a/tgui/packages/tgui-bench/tests/Button.test.tsx b/tgui/packages/tgui-bench/tests/Button.test.tsx index 6b806d720ab83..0549e69b623ae 100644 --- a/tgui/packages/tgui-bench/tests/Button.test.tsx +++ b/tgui/packages/tgui-bench/tests/Button.test.tsx @@ -1,11 +1,8 @@ -import { linkEvent } from 'inferno'; import { Button } from 'tgui/components'; import { createRenderer } from 'tgui/renderer'; const render = createRenderer(); -const handleClick = () => undefined; - export const SingleButton = () => { const node = ; render(node); @@ -16,13 +13,6 @@ export const SingleButtonWithCallback = () => { render(node); }; -export const SingleButtonWithLinkEvent = () => { - const node = ( - - ); - render(node); -}; - export const ListOfButtons = () => { const nodes: JSX.Element[] = []; for (let i = 0; i < 100; i++) { @@ -45,19 +35,6 @@ export const ListOfButtonsWithCallback = () => { render(
    {nodes}
    ); }; -export const ListOfButtonsWithLinkEvent = () => { - const nodes: JSX.Element[] = []; - for (let i = 0; i < 100; i++) { - const node = ( - - ); - nodes.push(node); - } - render(
    {nodes}
    ); -}; - export const ListOfButtonsWithIcons = () => { const nodes: JSX.Element[] = []; for (let i = 0; i < 100; i++) { diff --git a/tgui/packages/tgui-bench/tests/DisposalUnit.test.tsx b/tgui/packages/tgui-bench/tests/DisposalUnit.test.tsx index 1ae610e2e2e15..376d27a9115b5 100644 --- a/tgui/packages/tgui-bench/tests/DisposalUnit.test.tsx +++ b/tgui/packages/tgui-bench/tests/DisposalUnit.test.tsx @@ -1,22 +1,19 @@ -import { StoreProvider, configureStore } from 'tgui/store'; - +import { backendUpdate, setGlobalStore } from 'tgui/backend'; import { DisposalUnit } from 'tgui/interfaces/DisposalUnit'; -import { backendUpdate } from 'tgui/backend'; import { createRenderer } from 'tgui/renderer'; +import { configureStore } from 'tgui/store'; const store = configureStore({ sideEffects: false }); const renderUi = createRenderer((dataJson: string) => { + setGlobalStore(store); + store.dispatch( backendUpdate({ data: Byond.parseJson(dataJson), - }) - ); - return ( - - - + }), ); + return ; }); export const data = JSON.stringify({ diff --git a/tgui/packages/tgui-bench/tests/Tooltip.test.tsx b/tgui/packages/tgui-bench/tests/Tooltip.test.tsx index ea43a61f0b448..9dae16f5c0303 100644 --- a/tgui/packages/tgui-bench/tests/Tooltip.test.tsx +++ b/tgui/packages/tgui-bench/tests/Tooltip.test.tsx @@ -12,7 +12,7 @@ export const ListOfTooltips = () => { Tooltip #{i} - + , ); } diff --git a/tgui/packages/tgui-dev-server/dreamseeker.js b/tgui/packages/tgui-dev-server/dreamseeker.js index 2b25b155ae0c7..d1ca2a9ac5395 100644 --- a/tgui/packages/tgui-dev-server/dreamseeker.js +++ b/tgui/packages/tgui-dev-server/dreamseeker.js @@ -6,6 +6,7 @@ import { exec } from 'child_process'; import { promisify } from 'util'; + import { createLogger } from './logging.js'; import { require } from './require.js'; @@ -30,7 +31,7 @@ export class DreamSeeker { + '=' + encodeURIComponent(params[key])) .join('&'); logger.log( - `topic call at ${this.client.defaults.baseURL + '/dummy?' + query}` + `topic call at ${this.client.defaults.baseURL + '/dummy?' + query}`, ); return this.client.get('/dummy?' + query); } diff --git a/tgui/packages/tgui-dev-server/index.js b/tgui/packages/tgui-dev-server/index.js index 199e93d836321..85489ebb0499f 100644 --- a/tgui/packages/tgui-dev-server/index.js +++ b/tgui/packages/tgui-dev-server/index.js @@ -4,8 +4,8 @@ * @license MIT */ -import { createCompiler } from './webpack.js'; import { reloadByondCache } from './reloader.js'; +import { createCompiler } from './webpack.js'; const noHot = process.argv.includes('--no-hot'); const noTmp = process.argv.includes('--no-tmp'); diff --git a/tgui/packages/tgui-dev-server/link/client.cjs b/tgui/packages/tgui-dev-server/link/client.cjs index 1e21d42ce86b9..b0e6f7bc9d445 100644 --- a/tgui/packages/tgui-dev-server/link/client.cjs +++ b/tgui/packages/tgui-dev-server/link/client.cjs @@ -31,11 +31,9 @@ const ensureConnection = () => { }; } } -}; -if (process.env.NODE_ENV !== 'production') { window.onunload = () => socket && socket.close(); -} +}; const subscribe = (fn) => subscribers.push(fn); @@ -136,38 +134,38 @@ const sendLogEntry = (level, ns, ...args) => { const setupHotReloading = () => { if ( - // prettier-ignore - process.env.NODE_ENV !== 'production' - && process.env.WEBPACK_HMR_ENABLED - && window.WebSocket + process.env.NODE_ENV === 'production' || + !process.env.WEBPACK_HMR_ENABLED || + !window.WebSocket ) { - if (module.hot) { - ensureConnection(); - sendLogEntry(0, null, 'setting up hot reloading'); - subscribe((msg) => { - const { type } = msg; - sendLogEntry(0, null, 'received', type); - if (type === 'hotUpdate') { - const status = module.hot.status(); - if (status !== 'idle') { - sendLogEntry(0, null, 'hot reload status:', status); - return; - } - module.hot - .check({ - ignoreUnaccepted: true, - ignoreDeclined: true, - ignoreErrored: true, - }) - .then((modules) => { - sendLogEntry(0, null, 'outdated modules', modules); - }) - .catch((err) => { - sendLogEntry(0, null, 'reload error', err); - }); + return; + } + if (module.hot) { + ensureConnection(); + sendLogEntry(0, null, 'setting up hot reloading'); + subscribe((msg) => { + const { type } = msg; + sendLogEntry(0, null, 'received', type); + if (type === 'hotUpdate') { + const status = module.hot.status(); + if (status !== 'idle') { + sendLogEntry(0, null, 'hot reload status:', status); + return; } - }); - } + module.hot + .check({ + ignoreUnaccepted: true, + ignoreDeclined: true, + ignoreErrored: true, + }) + .then((modules) => { + sendLogEntry(0, null, 'outdated modules', modules); + }) + .catch((err) => { + sendLogEntry(0, null, 'reload error', err); + }); + } + }); } }; diff --git a/tgui/packages/tgui-dev-server/link/retrace.js b/tgui/packages/tgui-dev-server/link/retrace.js index 842de228fdfde..083ddb37d1c14 100644 --- a/tgui/packages/tgui-dev-server/link/retrace.js +++ b/tgui/packages/tgui-dev-server/link/retrace.js @@ -6,6 +6,7 @@ import fs from 'fs'; import { basename } from 'path'; + import { createLogger } from '../logging.js'; import { require } from '../require.js'; import { resolveGlob } from '../util.js'; @@ -30,7 +31,7 @@ export const loadSourceMaps = async (bundleDir) => { try { const file = basename(path).replace('.map', ''); const consumer = await new SourceMapConsumer( - JSON.parse(fs.readFileSync(path, 'utf8')) + JSON.parse(fs.readFileSync(path, 'utf8')), ); sourceMaps.push({ file, consumer }); } catch (err) { diff --git a/tgui/packages/tgui-dev-server/link/server.js b/tgui/packages/tgui-dev-server/link/server.js index 60cc78c1bd9ed..2a1f551bf6ebd 100644 --- a/tgui/packages/tgui-dev-server/link/server.js +++ b/tgui/packages/tgui-dev-server/link/server.js @@ -6,6 +6,7 @@ import http from 'http'; import { inspect } from 'util'; + import { createLogger, directLog } from '../logging.js'; import { require } from '../require.js'; import { loadSourceMaps, retrace } from './retrace.js'; diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json index 2477641c7e793..496e25c6c1847 100644 --- a/tgui/packages/tgui-dev-server/package.json +++ b/tgui/packages/tgui-dev-server/package.json @@ -1,13 +1,13 @@ { "private": true, "name": "tgui-dev-server", - "version": "4.3.1", + "version": "5.0.0", "type": "module", "dependencies": { - "axios": "^0.21.1", - "glob": "^7.1.7", - "source-map": "^0.7.3", + "axios": "^1.6.2", + "glob": "^7.2.0", + "source-map": "^0.7.4", "stacktrace-parser": "^0.1.10", - "ws": "^7.5.3" + "ws": "^8.14.2" } } diff --git a/tgui/packages/tgui-dev-server/reloader.js b/tgui/packages/tgui-dev-server/reloader.js index c13a8afdfcfc5..cb477a6523b03 100644 --- a/tgui/packages/tgui-dev-server/reloader.js +++ b/tgui/packages/tgui-dev-server/reloader.js @@ -7,6 +7,7 @@ import fs from 'fs'; import os from 'os'; import { basename } from 'path'; + import { DreamSeeker } from './dreamseeker.js'; import { createLogger } from './logging.js'; import { resolveGlob, resolvePath } from './util.js'; @@ -83,19 +84,19 @@ export const reloadByondCache = async (bundleDir) => { } // Get dreamseeker instances const pids = cacheDirs.map((cacheDir) => - parseInt(cacheDir.split('/cache/tmp').pop(), 10) + parseInt(cacheDir.split('/cache/tmp').pop(), 10), ); const dssPromise = DreamSeeker.getInstancesByPids(pids); // Copy assets const assets = await resolveGlob( bundleDir, - './*.+(bundle|chunk|hot-update).*' + './*.+(bundle|chunk|hot-update).*', ); for (let cacheDir of cacheDirs) { // Clear garbage const garbage = await resolveGlob( cacheDir, - './*.+(bundle|chunk|hot-update).*' + './*.+(bundle|chunk|hot-update).*', ); try { // Plant a dummy browser window file, we'll be using this to avoid world topic. For byond 515. diff --git a/tgui/packages/tgui-dev-server/util.js b/tgui/packages/tgui-dev-server/util.js index 9d07b96c71a05..79190fe189a4a 100644 --- a/tgui/packages/tgui-dev-server/util.js +++ b/tgui/packages/tgui-dev-server/util.js @@ -6,6 +6,7 @@ import fs from 'fs'; import path from 'path'; + import { require } from './require.js'; const globPkg = require('glob'); diff --git a/tgui/packages/tgui-dev-server/webpack.js b/tgui/packages/tgui-dev-server/webpack.js index 139610b79ce99..e4fbdeb9f1e2b 100644 --- a/tgui/packages/tgui-dev-server/webpack.js +++ b/tgui/packages/tgui-dev-server/webpack.js @@ -7,6 +7,7 @@ import fs from 'fs'; import { createRequire } from 'module'; import { dirname } from 'path'; + import { loadSourceMaps, setupLink } from './link/server.js'; import { createLogger } from './logging.js'; import { reloadByondCache } from './reloader.js'; diff --git a/tgui/packages/tgui-dev-server/winreg.js b/tgui/packages/tgui-dev-server/winreg.js index b61fddc1a255a..43a4170190719 100644 --- a/tgui/packages/tgui-dev-server/winreg.js +++ b/tgui/packages/tgui-dev-server/winreg.js @@ -8,6 +8,7 @@ import { exec } from 'child_process'; import { promisify } from 'util'; + import { createLogger } from './logging.js'; const logger = createLogger('winreg'); @@ -35,8 +36,8 @@ export const regQuery = async (path, key) => { logger.error('could not find the start of the key value'); return null; } - const value = stdout.substring(indexOfValue + 4, indexOfEol); - return value; + + return stdout.substring(indexOfValue + 4, indexOfEol); } catch (err) { logger.error(err); return null; diff --git a/tgui/packages/tgui-panel/Notifications.js b/tgui/packages/tgui-panel/Notifications.tsx similarity index 100% rename from tgui/packages/tgui-panel/Notifications.js rename to tgui/packages/tgui-panel/Notifications.tsx diff --git a/tgui/packages/tgui-panel/Panel.js b/tgui/packages/tgui-panel/Panel.js deleted file mode 100644 index 83150ab6ef13d..0000000000000 --- a/tgui/packages/tgui-panel/Panel.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { Button, Section, Stack } from 'tgui/components'; -import { Pane } from 'tgui/layouts'; -import { NowPlayingWidget, useAudio } from './audio'; -import { ChatPanel, ChatTabs } from './chat'; -import { useGame } from './game'; -import { Notifications } from './Notifications'; -import { PingIndicator } from './ping'; -import { ReconnectButton } from './reconnect'; -import { SettingsPanel, useSettings } from './settings'; - -export const Panel = (props, context) => { - // IE8-10: Needs special treatment due to missing Flex support - if (Byond.IS_LTE_IE10) { - return ; - } - const audio = useAudio(context); - const settings = useSettings(context); - const game = useGame(context); - if (process.env.NODE_ENV !== 'production') { - const { useDebug, KitchenSink } = require('tgui/debug'); - const debug = useDebug(context); - if (debug.kitchenSink) { - return ; - } - } - return ( - - - -
    - - - - - - - - -
    -
    - {audio.visible && ( - -
    - -
    -
    - )} - {settings.visible && ( - - - - )} - -
    - - - - - {game.connectionLostAt && ( - }> - You are either AFK, experiencing lag or the connection has - closed. - - )} - {game.roundRestartedAt && ( - - The connection has been closed because the server is - restarting. Please wait while you automatically reconnect. - - )} - -
    -
    -
    -
    - ); -}; - -const HoboPanel = (props, context) => { - const settings = useSettings(context); - return ( - - - - {(settings.visible && ) || ( - - )} - - - ); -}; diff --git a/tgui/packages/tgui-panel/Panel.tsx b/tgui/packages/tgui-panel/Panel.tsx new file mode 100644 index 0000000000000..2813b636574dc --- /dev/null +++ b/tgui/packages/tgui-panel/Panel.tsx @@ -0,0 +1,102 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { Button, Section, Stack } from 'tgui/components'; +import { Pane } from 'tgui/layouts'; + +import { NowPlayingWidget, useAudio } from './audio'; +import { ChatPanel, ChatTabs } from './chat'; +import { useGame } from './game'; +import { Notifications } from './Notifications'; +import { PingIndicator } from './ping'; +import { ReconnectButton } from './reconnect'; +import { SettingsPanel, useSettings } from './settings'; + +export const Panel = (props) => { + const audio = useAudio(); + const settings = useSettings(); + const game = useGame(); + if (process.env.NODE_ENV !== 'production') { + const { useDebug, KitchenSink } = require('tgui/debug'); + const debug = useDebug(); + if (debug.kitchenSink) { + return ; + } + } + + return ( + + + +
    + + + + + + + + +
    +
    + {audio.visible && ( + +
    + +
    +
    + )} + {settings.visible && ( + + + + )} + +
    + + + + + {game.connectionLostAt && ( + }> + You are either AFK, experiencing lag or the connection has + closed. + + )} + {game.roundRestartedAt && ( + + The connection has been closed because the server is + restarting. Please wait while you automatically reconnect. + + )} + +
    +
    +
    +
    + ); +}; diff --git a/tgui/packages/tgui-panel/audio/NowPlayingWidget.js b/tgui/packages/tgui-panel/audio/NowPlayingWidget.js deleted file mode 100644 index 30052c3f3a3eb..0000000000000 --- a/tgui/packages/tgui-panel/audio/NowPlayingWidget.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { toFixed } from 'common/math'; -import { useDispatch, useSelector } from 'common/redux'; -import { Button, Collapsible, Flex, Knob, Section } from 'tgui/components'; -import { useSettings } from '../settings'; -import { selectAudio } from './selectors'; - -export const NowPlayingWidget = (props, context) => { - const audio = useSelector(context, selectAudio), - dispatch = useDispatch(context), - settings = useSettings(context), - title = audio.meta?.title, - URL = audio.meta?.link, - Artist = audio.meta?.artist || 'Unknown Artist', - upload_date = audio.meta?.upload_date || 'Unknown Date', - album = audio.meta?.album || 'Unknown Album', - duration = audio.meta?.duration, - date = !isNaN(upload_date) - ? upload_date?.substring(0, 4) + - '-' + - upload_date?.substring(4, 6) + - '-' + - upload_date?.substring(6, 8) - : upload_date; - - return ( - - {(audio.playing && ( - - { - -
    - {URL !== 'Song Link Hidden' && ( - - URL: {URL} - - )} - - Duration: {duration} - - {Artist !== 'Song Artist Hidden' && - Artist !== 'Unknown Artist' && ( - - Artist: {Artist} - - )} - {album !== 'Song Album Hidden' && album !== 'Unknown Album' && ( - - Album: {album} - - )} - {upload_date !== 'Song Upload Date Hidden' && - upload_date !== 'Unknown Date' && ( - - Uploaded: {date} - - )} -
    -
    - } -
    - )) || ( - - Nothing to play. - - )} - {audio.playing && ( - - -
    -
    - -
    - {MESSAGE_TYPES.filter( - (typeDef) => !typeDef.important && !typeDef.admin - ).map((typeDef) => ( - - dispatch( - toggleAcceptedType({ - pageId: page.id, - type: typeDef.type, - }) - ) - }> - {typeDef.name} - - ))} - - {MESSAGE_TYPES.filter( - (typeDef) => !typeDef.important && typeDef.admin - ).map((typeDef) => ( - - dispatch( - toggleAcceptedType({ - pageId: page.id, - type: typeDef.type, - }) - ) - }> - {typeDef.name} - - ))} - -
    - - ); -}; diff --git a/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx b/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx new file mode 100644 index 0000000000000..e4dfaf70b217f --- /dev/null +++ b/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx @@ -0,0 +1,115 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { useDispatch, useSelector } from 'tgui/backend'; +import { + Button, + Collapsible, + Divider, + Input, + Section, + Stack, +} from 'tgui/components'; + +import { removeChatPage, toggleAcceptedType, updateChatPage } from './actions'; +import { MESSAGE_TYPES } from './constants'; +import { selectCurrentChatPage } from './selectors'; + +export const ChatPageSettings = (props) => { + const page = useSelector(selectCurrentChatPage); + const dispatch = useDispatch(); + return ( +
    + + + + dispatch( + updateChatPage({ + pageId: page.id, + name: value, + }), + ) + } + /> + + + + dispatch( + updateChatPage({ + pageId: page.id, + hideUnreadCount: !page.hideUnreadCount, + }), + ) + } + /> + + +
    + ); +}; diff --git a/tgui/packages/tgui-panel/chat/ChatPanel.js b/tgui/packages/tgui-panel/chat/ChatPanel.js deleted file mode 100644 index 3132a66ce7f8c..0000000000000 --- a/tgui/packages/tgui-panel/chat/ChatPanel.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { shallowDiffers } from 'common/react'; -import { Component, createRef } from 'inferno'; -import { Button } from 'tgui/components'; -import { chatRenderer } from './renderer'; - -export class ChatPanel extends Component { - constructor() { - super(); - this.ref = createRef(); - this.state = { - scrollTracking: true, - }; - this.handleScrollTrackingChange = (value) => - this.setState({ - scrollTracking: value, - }); - } - - componentDidMount() { - chatRenderer.mount(this.ref.current); - chatRenderer.events.on( - 'scrollTrackingChanged', - this.handleScrollTrackingChange - ); - this.componentDidUpdate(); - } - - componentWillUnmount() { - chatRenderer.events.off( - 'scrollTrackingChanged', - this.handleScrollTrackingChange - ); - } - - componentDidUpdate(prevProps) { - requestAnimationFrame(() => { - chatRenderer.ensureScrollTracking(); - }); - const shouldUpdateStyle = - !prevProps || shallowDiffers(this.props, prevProps); - if (shouldUpdateStyle) { - chatRenderer.assignStyle({ - 'width': '100%', - 'white-space': 'pre-wrap', - 'font-size': this.props.fontSize, - 'line-height': this.props.lineHeight, - }); - } - } - - render() { - const { scrollTracking } = this.state; - return ( - <> -
    - {!scrollTracking && ( - - )} - - ); - } -} diff --git a/tgui/packages/tgui-panel/chat/ChatPanel.jsx b/tgui/packages/tgui-panel/chat/ChatPanel.jsx new file mode 100644 index 0000000000000..845c161275653 --- /dev/null +++ b/tgui/packages/tgui-panel/chat/ChatPanel.jsx @@ -0,0 +1,75 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { shallowDiffers } from 'common/react'; +import { Component, createRef } from 'react'; +import { Button } from 'tgui/components'; + +import { chatRenderer } from './renderer'; + +export class ChatPanel extends Component { + constructor(props) { + super(props); + this.ref = createRef(); + this.state = { + scrollTracking: true, + }; + this.handleScrollTrackingChange = (value) => + this.setState({ + scrollTracking: value, + }); + } + + componentDidMount() { + chatRenderer.mount(this.ref.current); + chatRenderer.events.on( + 'scrollTrackingChanged', + this.handleScrollTrackingChange, + ); + this.componentDidUpdate(); + } + + componentWillUnmount() { + chatRenderer.events.off( + 'scrollTrackingChanged', + this.handleScrollTrackingChange, + ); + } + + componentDidUpdate(prevProps) { + requestAnimationFrame(() => { + chatRenderer.ensureScrollTracking(); + }); + const shouldUpdateStyle = + !prevProps || shallowDiffers(this.props, prevProps); + if (shouldUpdateStyle) { + chatRenderer.assignStyle({ + width: '100%', + 'white-space': 'pre-wrap', + 'font-size': this.props.fontSize, + 'line-height': this.props.lineHeight, + }); + } + } + + render() { + const { scrollTracking } = this.state; + return ( + <> +
    + {!scrollTracking && ( + + )} + + ); + } +} diff --git a/tgui/packages/tgui-panel/chat/ChatTabs.js b/tgui/packages/tgui-panel/chat/ChatTabs.js deleted file mode 100644 index 1d4f6f65edfea..0000000000000 --- a/tgui/packages/tgui-panel/chat/ChatTabs.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { useDispatch, useSelector } from 'common/redux'; -import { Box, Tabs, Flex, Button } from 'tgui/components'; -import { changeChatPage, addChatPage } from './actions'; -import { selectChatPages, selectCurrentChatPage } from './selectors'; -import { openChatSettings } from '../settings/actions'; - -const UnreadCountWidget = ({ value }) => ( - - {Math.min(value, 99)} - -); - -export const ChatTabs = (props, context) => { - const pages = useSelector(context, selectChatPages); - const currentPage = useSelector(context, selectCurrentChatPage); - const dispatch = useDispatch(context); - return ( - - - - {pages.map((page) => ( - 0 && ( - - ) - } - onClick={() => - dispatch( - changeChatPage({ - pageId: page.id, - }) - ) - }> - {page.name} - - ))} - - - - diff --git a/tgui/packages/tgui-panel/settings/SettingsPanel.js b/tgui/packages/tgui-panel/settings/SettingsPanel.js deleted file mode 100644 index 90abe4e36b26a..0000000000000 --- a/tgui/packages/tgui-panel/settings/SettingsPanel.js +++ /dev/null @@ -1,311 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { toFixed } from 'common/math'; -import { useLocalState } from 'tgui/backend'; -import { useDispatch, useSelector } from 'common/redux'; -import { Box, Button, ColorBox, Divider, Dropdown, Flex, Input, LabeledList, NumberInput, Section, Stack, Tabs, TextArea } from 'tgui/components'; -import { ChatPageSettings } from '../chat'; -import { rebuildChat, saveChatToDisk } from '../chat/actions'; -import { THEMES } from '../themes'; -import { changeSettingsTab, updateSettings, addHighlightSetting, removeHighlightSetting, updateHighlightSetting } from './actions'; -import { SETTINGS_TABS, FONTS, MAX_HIGHLIGHT_SETTINGS } from './constants'; -import { selectActiveTab, selectSettings, selectHighlightSettings, selectHighlightSettingById } from './selectors'; - -export const SettingsPanel = (props, context) => { - const activeTab = useSelector(context, selectActiveTab); - const dispatch = useDispatch(context); - return ( - - -
    - - {SETTINGS_TABS.map((tab) => ( - - dispatch( - changeSettingsTab({ - tabId: tab.id, - }) - ) - }> - {tab.name} - - ))} - -
    -
    - - {activeTab === 'general' && } - {activeTab === 'chatPage' && } - {activeTab === 'textHighlight' && } - -
    - ); -}; - -export const SettingsGeneral = (props, context) => { - const { theme, fontFamily, fontSize, lineHeight } = useSelector( - context, - selectSettings - ); - const dispatch = useDispatch(context); - const [freeFont, setFreeFont] = useLocalState(context, 'freeFont', false); - return ( -
    - - - - dispatch( - updateSettings({ - theme: value, - }) - ) - } - /> - - - - - {(!freeFont && ( - - dispatch( - updateSettings({ - fontFamily: value, - }) - ) - } - /> - )) || ( - - dispatch( - updateSettings({ - fontFamily: value, - }) - ) - } - /> - )} - - - -
    - ); -}; - -const TextHighlightSettings = (props, context) => { - const highlightSettings = useSelector(context, selectHighlightSettings); - const dispatch = useDispatch(context); - return ( -
    -
    - - {highlightSettings.map((id, i) => ( - - ))} - {highlightSettings.length < MAX_HIGHLIGHT_SETTINGS && ( - -
    - - - - - Can freeze the chat for a while. - - -
    - ); -}; - -const TextHighlightSetting = (props, context) => { - const { id, ...rest } = props; - const highlightSettingById = useSelector(context, selectHighlightSettingById); - const dispatch = useDispatch(context); - const { - highlightColor, - highlightText, - highlightWholeMessage, - matchWord, - matchCase, - } = highlightSettingById[id]; - return ( - - - - + + + + + + + + + + + + + ); +}; + +/** The modal menu that contains the prompts to making new comments. */ +const NewscasterCommentCreation = (props) => { + const { act, data } = useBackend(); + const { creating_comment, viewing_message } = data; + if (!creating_comment) { + return null; + } + return ( + + + + + Enter comment: + + + + + + ); +}; + +const NewscasterWantedScreen = (props) => { + const { act, data } = useBackend(); + const { + viewing_wanted, + photo_data, + security_mode, + wanted = [], + criminal_name, + crime_description, + } = data; + if (!viewing_wanted) { + return null; + } + return ( + + {wanted.map((activeWanted) => ( + <> + + + + {activeWanted.active + ? 'Active Wanted Issue:' + : 'Dismissed Wanted Issue:'} + + + + + + +
    + + + +
    + + ) : ( + + {wanted.active + ? 'Please contact your local security officer if spotted.' + : 'No wanted issue posted. Have a secure day.'} + + )} +
    + ); +}; + +const NewscasterContent = (props) => { + const { act, data } = useBackend(); + const { current_channel = {} } = data; + return ( + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +/** The Channel Box is the basic channel information where buttons live.*/ +const NewscasterChannelBox = (props) => { + const { act, data } = useBackend(); + const { + channelName, + channelDesc, + channelLocked, + channelAuthor, + channelCensored, + viewing_channel, + admin_mode, + photo_data, + paper, + user, + } = data; + return ( +
    + + + {channelCensored ? ( +
    +
    + ATTENTION: {CENSOR_MESSAGE} +
    +
    + ) : ( +
    +
    + {decodeHtmlEntities(channelDesc)} +
    +
    + )} +
    + + + + + {!!admin_mode && ( + + )} + + + + + +
    +
    + ); +}; + +/** Channel select is the left-hand menu where all the channels are listed. */ +const NewscasterChannelSelector = (props) => { + const { act, data } = useBackend(); + const { channels = [], viewing_channel, wanted = [] } = data; + return ( +
    + + {wanted.map((activeWanted) => ( + act('toggleWanted')} + > + Wanted Issue + + ))} + {channels.map((channel) => ( + + act('setChannel', { + channel: channel.ID, + }) + } + > + {channel.name} + + ))} + act('startCreateChannel')} + > + Create Channel [+] + + +
    + ); +}; + +/** This is where the channels comments get spangled out (tm) */ +const NewscasterChannelMessages = (props) => { + const { act, data } = useBackend(); + const { + messages = [], + viewing_channel, + admin_mode, + channelCensored, + channelLocked, + channelAuthor, + user, + } = data; + if (channelCensored) { + return ( +
    + ATTENTION: Comments cannot be read at this time. +
    + Thank you for your understanding, and have a secure day. +
    + ); + } + const visibleMessages = messages.filter( + (message) => message.ID !== viewing_channel, + ); + return ( +
    + {visibleMessages.map((message) => { + return ( +
    + {message.censored_author ? ( + + By: [REDACTED]. D-Notice Notice . + + ) : ( + <> + By: {message.auth} at {message.time} + + )} + + } + buttons={ + <> + {!!admin_mode && ( +
    + ); + })} +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/Newspaper.tsx b/tgui/packages/tgui/interfaces/Newspaper.tsx new file mode 100644 index 0000000000000..2629a398c953e --- /dev/null +++ b/tgui/packages/tgui/interfaces/Newspaper.tsx @@ -0,0 +1,166 @@ +import { BooleanLike } from '../../common/react'; +import { useBackend } from '../backend'; +import { Box, Button, Divider, Image, Section } from '../components'; +import { Window } from '../layouts'; +import { processedText } from '../process'; + +type Data = { + current_page: number; + scribble_message: string | null; + channel_has_messages: BooleanLike; + channels: ChannelNames[]; + channel_data: ChannelData[]; + wanted_criminal: string | null; + wanted_body: string | null; + wanted_photo: string | null; +}; + +type ChannelNames = { + name: string | null; + page_number: number; +}; + +type ChannelData = { + channel_name: string | null; + author_name: string | null; + is_censored: BooleanLike; + channel_messages: ChannelMessages[]; +}; + +type ChannelMessages = { + message: string | null; + photo: string | null; + author: string | null; +}; + +export const Newspaper = (props) => { + const { act, data } = useBackend(); + const { channels = [], current_page, scribble_message } = data; + + return ( + + + {current_page === channels.length + 1 ? ( + + ) : current_page ? ( + + ) : ( + + )} + {!!scribble_message && ( + + {scribble_message} + + )} +
    + + +
    +
    +
    + ); +}; + +const NewspaperIntro = (props) => { + const { act, data } = useBackend(); + const { channels = [], wanted_criminal = [] } = data; + + return ( +
    + + The Griffon + + + For use on Space Facilities only! + + Table of Contents: + {channels.map((channel) => ( + + Page {channel.page_number || 0}: {channel.name} + + ))} + {!!wanted_criminal && ( + Last Page: Important Security Announcement + )} +
    + ); +}; + +const NewspaperChannel = (props) => { + const { act, data } = useBackend(); + const { channel_has_messages, channel_data = [] } = data; + + return ( +
    + {channel_data.map((individual_channel) => ( + + + {individual_channel.channel_name} + + + Channel made by: {individual_channel.author_name} + + {channel_has_messages ? ( + <> + {individual_channel.channel_messages.map((message) => ( + <> + + + {!!message.photo && } + Written by: {message.author} + + + + ))} + + ) : ( + 'No feed stories stem from this channel...' + )} + + ))} +
    + ); +}; + +const NewspaperEnding = (props) => { + const { act, data } = useBackend(); + const { wanted_criminal, wanted_body, wanted_photo } = data; + + return ( +
    + {wanted_criminal ? ( + <> + + Wanted Issue + + Criminal Name: {wanted_criminal} + Description: {wanted_body} + {!!wanted_photo && } + + ) : ( + 'Apart from some uninteresting classified ads, theres nothing in this page...' + )} +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/NoticeBoard.tsx b/tgui/packages/tgui/interfaces/NoticeBoard.tsx index 5dc36644b25d3..27c9079dfe5d9 100644 --- a/tgui/packages/tgui/interfaces/NoticeBoard.tsx +++ b/tgui/packages/tgui/interfaces/NoticeBoard.tsx @@ -1,4 +1,5 @@ import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, Button, Section, Stack } from '../components'; import { Window } from '../layouts'; @@ -8,8 +9,8 @@ type Data = { items: { ref: string; name: string }[]; }; -export const NoticeBoard = (props, context) => { - const { act, data } = useBackend(context); +export const NoticeBoard = (props) => { + const { act, data } = useBackend(); const { allowed, items = [] } = data; return ( @@ -27,7 +28,8 @@ export const NoticeBoard = (props, context) => { key={item.ref} color="black" backgroundColor="white" - style={{ padding: '2px 2px 0 2px' }}> + style={{ padding: '2px 2px 0 2px' }} + > {item.name} diff --git a/tgui/packages/tgui/interfaces/NotificationPreferences.js b/tgui/packages/tgui/interfaces/NotificationPreferences.js deleted file mode 100644 index 5941acb636572..0000000000000 --- a/tgui/packages/tgui/interfaces/NotificationPreferences.js +++ /dev/null @@ -1,37 +0,0 @@ -import { useBackend } from '../backend'; -import { Section, Button } from '../components'; -import { Window } from '../layouts'; - -export const NotificationPreferences = (props, context) => { - const { act, data } = useBackend(context); - const ignoresPreSort = data.ignore || []; - const ignores = ignoresPreSort.sort((a, b) => { - const descA = a.desc.toLowerCase(); - const descB = b.desc.toLowerCase(); - if (descA < descB) { - return -1; - } - if (descA > descB) { - return 1; - } - return 0; - }); - return ( - - -
    - {ignores.map((ignore) => ( -
    -
    -
    - ); -}; diff --git a/tgui/packages/tgui/interfaces/NotificationPreferences.jsx b/tgui/packages/tgui/interfaces/NotificationPreferences.jsx new file mode 100644 index 0000000000000..aebe53bb94ca2 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NotificationPreferences.jsx @@ -0,0 +1,37 @@ +import { useBackend } from '../backend'; +import { Button, Section } from '../components'; +import { Window } from '../layouts'; + +export const NotificationPreferences = (props) => { + const { act, data } = useBackend(); + const ignoresPreSort = data.ignore || []; + const ignores = ignoresPreSort.sort((a, b) => { + const descA = a.desc.toLowerCase(); + const descB = b.desc.toLowerCase(); + if (descA < descB) { + return -1; + } + if (descA > descB) { + return 1; + } + return 0; + }); + return ( + + +
    + {ignores.map((ignore) => ( +
    +
    +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtnetRelay.tsx b/tgui/packages/tgui/interfaces/NtnetRelay.tsx index 19390edc2b5be..e7c4b9d2b9da8 100644 --- a/tgui/packages/tgui/interfaces/NtnetRelay.tsx +++ b/tgui/packages/tgui/interfaces/NtnetRelay.tsx @@ -1,6 +1,13 @@ import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; -import { Box, Button, ProgressBar, Section, AnimatedNumber } from '../components'; +import { + AnimatedNumber, + Box, + Button, + ProgressBar, + Section, +} from '../components'; import { Window } from '../layouts'; type Data = { @@ -17,8 +24,8 @@ error may indicate insufficient hardware capacity of your network. Please contact your network planning department for instructions on how to resolve this issue.`; -export const NtnetRelay = (props, context) => { - const { act, data } = useBackend(context); +export const NtnetRelay = (props) => { + const { act, data } = useBackend(); const { enabled, dos_capacity, dos_overload, dos_crashed } = data; return ( @@ -33,12 +40,14 @@ export const NtnetRelay = (props, context) => { content={enabled ? 'ENABLED' : 'DISABLED'} onClick={() => act('toggle')} /> - }> + } + > {!dos_crashed ? ( + maxValue={dos_capacity} + > GQ {' / '} {dos_capacity} GQ diff --git a/tgui/packages/tgui/interfaces/NtosAiRestorer.tsx b/tgui/packages/tgui/interfaces/NtosAiRestorer.tsx index 5e9fa19d11d92..3a68ed02d1550 100644 --- a/tgui/packages/tgui/interfaces/NtosAiRestorer.tsx +++ b/tgui/packages/tgui/interfaces/NtosAiRestorer.tsx @@ -1,7 +1,7 @@ import { NtosWindow } from '../layouts'; import { AiRestorerContent } from './AiRestorer'; -export const NtosAiRestorer = (props, context) => { +export const NtosAiRestorer = (props) => { return ( diff --git a/tgui/packages/tgui/interfaces/NtosArcade.js b/tgui/packages/tgui/interfaces/NtosArcade.js deleted file mode 100644 index e94d33b15c56b..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosArcade.js +++ /dev/null @@ -1,123 +0,0 @@ -import { resolveAsset } from '../assets'; -import { useBackend } from '../backend'; -import { AnimatedNumber, Box, Button, Grid, LabeledList, ProgressBar, Section } from '../components'; -import { NtosWindow } from '../layouts'; - -export const NtosArcade = (props, context) => { - const { act, data } = useBackend(context); - return ( - - -
    - - - - - - - - {data.PlayerHitpoints}HP - - - - - {data.PlayerMP}MP - - - - -
    - {data.Status} -
    -
    - - - - HP - - -
    - -
    -
    -
    - -
    -
    -
    - ); -}; diff --git a/tgui/packages/tgui/interfaces/NtosArcade.jsx b/tgui/packages/tgui/interfaces/NtosArcade.jsx new file mode 100644 index 0000000000000..b8c9aa05109e6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosArcade.jsx @@ -0,0 +1,135 @@ +import { resolveAsset } from '../assets'; +import { useBackend } from '../backend'; +import { + AnimatedNumber, + Box, + Button, + Grid, + LabeledList, + ProgressBar, + Section, +} from '../components'; +import { NtosWindow } from '../layouts'; + +export const NtosArcade = (props) => { + const { act, data } = useBackend(); + return ( + + +
    + + + + + + + + {data.PlayerHitpoints}HP + + + + + {data.PlayerMP}MP + + + + +
    + {data.Status} +
    +
    + + + + HP + + +
    + +
    +
    +
    + +
    +
    +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosBountyBoard.tsx b/tgui/packages/tgui/interfaces/NtosBountyBoard.tsx index f48810e517e2f..b1bbe418ce7fd 100644 --- a/tgui/packages/tgui/interfaces/NtosBountyBoard.tsx +++ b/tgui/packages/tgui/interfaces/NtosBountyBoard.tsx @@ -1,7 +1,7 @@ -import { BountyBoardContent } from './BountyBoard'; import { NtosWindow } from '../layouts'; +import { BountyBoardContent } from './BountyBoard'; -export const NtosBountyBoard = (props, context) => { +export const NtosBountyBoard = (props) => { return ( diff --git a/tgui/packages/tgui/interfaces/NtosCamera.js b/tgui/packages/tgui/interfaces/NtosCamera.js deleted file mode 100644 index 933d877de9cea..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosCamera.js +++ /dev/null @@ -1,42 +0,0 @@ -import { useBackend } from '../backend'; -import { NtosWindow } from '../layouts'; -import { Button, Box, NoticeBox, Stack } from '../components'; - -export const NtosCamera = (props, context) => { - return ( - - - - - - ); -}; - -export const NtosCameraContent = (props, context) => { - const { act, data } = useBackend(context); - const { photo, paper_left } = data; - - if (!photo) { - return ( - - Phototrasen Images - Tap (right-click) with your tablet to snap a photo! - - ); - } - - return ( - - - + + + Login: {authenticatedUser || '-----'} + + + {!!(has_id && authenticatedUser) && ( + <> + + Details: + + + act('PRG_edit', { + name: value, + }) + } + /> + + + { + act('PRG_age', { + id_age: value, + }); + }} + /> + + + + Assignment: + + + act('PRG_assign', { + assignment: value, + }) + } + /> + + + + )} + + ); +}; + +const TemplateDropdown = (props) => { + const { act } = useBackend(); + const { templates } = props; + + const templateKeys = Object.keys(templates); + + if (!templateKeys.length) { + return <> ; + } + + return ( + + + { + return templates[path]; + })} + onSelected={(sel) => + act('PRG_template', { + name: sel, + }) + } + /> + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosCargo.tsx b/tgui/packages/tgui/interfaces/NtosCargo.tsx index b5009c0aeaf50..19e36180f4bce 100644 --- a/tgui/packages/tgui/interfaces/NtosCargo.tsx +++ b/tgui/packages/tgui/interfaces/NtosCargo.tsx @@ -1,7 +1,7 @@ -import { CargoContent } from './Cargo.js'; import { NtosWindow } from '../layouts'; +import { CargoContent } from './Cargo'; -export const NtosCargo = (props, context) => { +export const NtosCargo = (props) => { return ( diff --git a/tgui/packages/tgui/interfaces/NtosCouponMaster.tsx b/tgui/packages/tgui/interfaces/NtosCouponMaster.tsx new file mode 100644 index 0000000000000..53820b807899e --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosCouponMaster.tsx @@ -0,0 +1,61 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../backend'; +import { Box, Input, NoticeBox, Section } from '../components'; +import { NtosWindow } from '../layouts'; + +type Data = { + valid_id: BooleanLike; + redeemed_coupons: CouponData[]; + printed_coupons: CouponData[]; +}; + +type CouponData = { + goody: string; + discount: number; +}; + +export const NtosCouponMaster = (props) => { + const { act, data } = useBackend(); + const { valid_id, redeemed_coupons = [], printed_coupons = [] } = data; + return ( + + + {!valid_id ? ( + + No valid bank account detected. Insert a valid ID. + + ) : ( + <> + + You can print redeemed coupons by right-clicking a photocopier. + + + act('redeem', { + code: value, + }) + } + /> +
    + {redeemed_coupons.map((coupon, index) => ( + + {coupon.goody} ({coupon.discount}% OFF) + + ))} +
    +
    + {printed_coupons.map((coupon, index) => ( + + {coupon.goody} ({coupon.discount}% OFF) + + ))} +
    + + )} +
    +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosCrewManifest.js b/tgui/packages/tgui/interfaces/NtosCrewManifest.js deleted file mode 100644 index 8aafa44471fba..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosCrewManifest.js +++ /dev/null @@ -1,37 +0,0 @@ -import { map } from 'common/collections'; -import { useBackend } from '../backend'; -import { Button, Section, Table } from '../components'; -import { NtosWindow } from '../layouts'; - -export const NtosCrewManifest = (props, context) => { - const { act, data } = useBackend(context); - const { manifest = {} } = data; - return ( - - -
    act('PRG_print')} - /> - }> - {map((entries, department) => ( -
    - - {entries.map((entry) => ( - - {entry.name} - ({entry.rank}) - - ))} -
    -
    - ))(manifest)} -
    -
    -
    - ); -}; diff --git a/tgui/packages/tgui/interfaces/NtosCrewManifest.jsx b/tgui/packages/tgui/interfaces/NtosCrewManifest.jsx new file mode 100644 index 0000000000000..5deb1d27c8a7a --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosCrewManifest.jsx @@ -0,0 +1,39 @@ +import { map } from 'common/collections'; + +import { useBackend } from '../backend'; +import { Button, Section, Table } from '../components'; +import { NtosWindow } from '../layouts'; + +export const NtosCrewManifest = (props) => { + const { act, data } = useBackend(); + const { manifest = {} } = data; + return ( + + +
    act('PRG_print')} + /> + } + > + {map((entries, department) => ( +
    + + {entries.map((entry) => ( + + {entry.name} + ({entry.rank}) + + ))} +
    +
    + ))(manifest)} +
    +
    +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js b/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js deleted file mode 100644 index a078f975ac423..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js +++ /dev/null @@ -1,175 +0,0 @@ -import { useBackend, useSharedState } from '../backend'; -import { Box, Button, LabeledList, NoticeBox, ProgressBar, Section, Stack, Tabs } from '../components'; -import { NtosWindow } from '../layouts'; - -export const NtosCyborgRemoteMonitor = (props, context) => { - return ( - - - - - - ); -}; - -export const ProgressSwitch = (param) => { - switch (param) { - case -1: - return '_'; - case 0: - return 'Connecting'; - case 25: - return 'Starting Transfer'; - case 50: - return 'Downloading'; - case 75: - return 'Downloading'; - case 100: - return 'Formatting'; - } -}; - -export const NtosCyborgRemoteMonitorContent = (props, context) => { - const { act, data } = useBackend(context); - const [tab_main, setTab_main] = useSharedState(context, 'tab_main', 1); - const { card, cyborgs = [], DL_progress } = data; - const storedlog = data.borglog || []; - - if (!cyborgs.length) { - return No cyborg units detected.; - } - - return ( - - - - setTab_main(1)}> - Cyborgs - - setTab_main(2)}> - Stored Log File - - - - {tab_main === 1 && ( - <> - {!card && ( - - Certain features require an ID card login. - - )} - -
    - {cyborgs.map((cyborg) => ( -
    - act('messagebot', { - ref: cyborg.ref, - }) - } - /> - }> - - - - {cyborg.status - ? 'Not Responding' - : cyborg.locked_down - ? 'Locked Down' - : cyborg.shell_discon - ? 'Nominal/Disconnected' - : 'Nominal'} - - - - - {cyborg.integ === 0 - ? 'Hard Fault' - : cyborg.integ <= 25 - ? 'Functionality Disrupted' - : cyborg.integ <= 75 - ? 'Functionality Impaired' - : 'Operational'} - - - - - {typeof cyborg.charge === 'number' - ? cyborg.charge + '%' - : 'Not Found'} - - - - {cyborg.module} - - - {cyborg.upgrades} - - -
    - ))} -
    -
    - - )} - {tab_main === 2 && ( - <> - -
    - Scan a cyborg to download stored logs. - - {ProgressSwitch(DL_progress)} - -
    -
    - -
    - {storedlog.map((log) => ( - - {log} - - ))} -
    -
    - - )} -
    - ); -}; diff --git a/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.jsx b/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.jsx new file mode 100644 index 0000000000000..24962f2c3bb01 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.jsx @@ -0,0 +1,190 @@ +import { useBackend, useSharedState } from '../backend'; +import { + Box, + Button, + LabeledList, + NoticeBox, + ProgressBar, + Section, + Stack, + Tabs, +} from '../components'; +import { NtosWindow } from '../layouts'; + +export const NtosCyborgRemoteMonitor = (props) => { + return ( + + + + + + ); +}; + +export const ProgressSwitch = (param) => { + switch (param) { + case -1: + return '_'; + case 0: + return 'Connecting'; + case 25: + return 'Starting Transfer'; + case 50: + return 'Downloading'; + case 75: + return 'Downloading'; + case 100: + return 'Formatting'; + } +}; + +export const NtosCyborgRemoteMonitorContent = (props) => { + const { act, data } = useBackend(); + const [tab_main, setTab_main] = useSharedState('tab_main', 1); + const { card, cyborgs = [], DL_progress } = data; + const storedlog = data.borglog || []; + + if (!cyborgs.length) { + return No cyborg units detected.; + } + + return ( + + + + setTab_main(1)} + > + Cyborgs + + setTab_main(2)} + > + Stored Log File + + + + {tab_main === 1 && ( + <> + {!card && ( + + Certain features require an ID card login. + + )} + +
    + {cyborgs.map((cyborg) => ( +
    + act('messagebot', { + ref: cyborg.ref, + }) + } + /> + } + > + + + + {cyborg.status + ? 'Not Responding' + : cyborg.locked_down + ? 'Locked Down' + : cyborg.shell_discon + ? 'Nominal/Disconnected' + : 'Nominal'} + + + + + {cyborg.integ === 0 + ? 'Hard Fault' + : cyborg.integ <= 25 + ? 'Functionality Disrupted' + : cyborg.integ <= 75 + ? 'Functionality Impaired' + : 'Operational'} + + + + + {typeof cyborg.charge === 'number' + ? cyborg.charge + '%' + : 'Not Found'} + + + + {cyborg.module} + + + {cyborg.upgrades} + + +
    + ))} +
    +
    + + )} + {tab_main === 2 && ( + <> + +
    + Scan a cyborg to download stored logs. + + {ProgressSwitch(DL_progress)} + +
    +
    + +
    + {storedlog.map((log) => ( + + {log} + + ))} +
    +
    + + )} +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosEmojipedia.js b/tgui/packages/tgui/interfaces/NtosEmojipedia.js deleted file mode 100644 index 9f7c05b172c63..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosEmojipedia.js +++ /dev/null @@ -1,69 +0,0 @@ -import { classes } from 'common/react'; -import { useBackend, useSharedState } from '../backend'; -import { Box, Button, Input, Section } from '../components'; -import { NtosWindow } from '../layouts'; - -export const NtosEmojipedia = (props, context) => { - const { data } = useBackend(context); - const { emoji_list } = data; - const [filter, updatefilter] = useSharedState(context, 'filter', ''); - - let filtered_emoji_list = filter - ? emoji_list.filter((emoji) => { - return emoji.name.toLowerCase().includes(filter.toLowerCase()); - }) - : emoji_list; - if (filtered_emoji_list.length === 0) { - filtered_emoji_list = emoji_list; - } - - return ( - - -
    - updatefilter(value)} - /> -
    -
    -
    - ); -}; diff --git a/tgui/packages/tgui/interfaces/NtosEmojipedia.tsx b/tgui/packages/tgui/interfaces/NtosEmojipedia.tsx new file mode 100644 index 0000000000000..3852e90ac26fc --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosEmojipedia.tsx @@ -0,0 +1,70 @@ +import { classes } from 'common/react'; +import { createSearch } from 'common/string'; +import { useState } from 'react'; + +import { useBackend } from '../backend'; +import { Button, Image, Input, Section } from '../components'; +import { NtosWindow } from '../layouts'; + +type Data = { + emoji_list: Emoji[]; +}; + +type Emoji = { + name: string; +}; + +export const NtosEmojipedia = (props) => { + const { data } = useBackend(); + const { emoji_list = [] } = data; + const [filter, setFilter] = useState(''); + + const search = createSearch(filter, (emoji) => emoji.name); + const filteredEmojis = emoji_list.filter(search); + + return ( + + +
    + setFilter(value)} + /> +
    +
    +
    + ); +}; + +const copyText = (text: string) => { + const input = document.createElement('input'); + input.value = text; + document.body.appendChild(input); + input.select(); + document.execCommand('copy'); + document.body.removeChild(input); +}; diff --git a/tgui/packages/tgui/interfaces/NtosFileManager.js b/tgui/packages/tgui/interfaces/NtosFileManager.js deleted file mode 100644 index 93b4b414d10e8..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosFileManager.js +++ /dev/null @@ -1,123 +0,0 @@ -import { useBackend } from '../backend'; -import { Button, Section, Table } from '../components'; -import { NtosWindow } from '../layouts'; - -export const NtosFileManager = (props, context) => { - const { act, data } = useBackend(context); - const { usbconnected, files = [], usbfiles = [] } = data; - return ( - - -
    - act('PRG_copytousb', { name: file })} - onDelete={(file) => act('PRG_deletefile', { name: file })} - onRename={(file, newName) => - act('PRG_renamefile', { - name: file, - new_name: newName, - }) - } - onDuplicate={(file) => act('PRG_clone', { file: file })} - onToggleSilence={(file) => act('PRG_togglesilence', { name: file })} - /> -
    - {usbconnected && ( -
    - act('PRG_copyfromusb', { name: file })} - onDelete={(file) => act('PRG_usbdeletefile', { name: file })} - onRename={(file, newName) => - act('PRG_usbrenamefile', { - name: file, - new_name: newName, - }) - } - onDuplicate={(file) => act('PRG_clone', { file: file })} - /> -
    - )} -
    -
    - ); -}; - -const FileTable = (props) => { - const { - files = [], - usbconnected, - usbmode, - onUpload, - onDelete, - onRename, - onToggleSilence, - } = props; - return ( - - - File - Type - Size - - {files.map((file) => ( - - - {!file.undeletable ? ( - onRename(file.name, value)} - /> - ) : ( - file.name - )} - - {file.type} - {file.size} - - {!!file.alert_able && ( -
    - ); -}; diff --git a/tgui/packages/tgui/interfaces/NtosFileManager.jsx b/tgui/packages/tgui/interfaces/NtosFileManager.jsx new file mode 100644 index 0000000000000..5e8fae205cf32 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosFileManager.jsx @@ -0,0 +1,123 @@ +import { useBackend } from '../backend'; +import { Button, Section, Table } from '../components'; +import { NtosWindow } from '../layouts'; + +export const NtosFileManager = (props) => { + const { act, data } = useBackend(); + const { usbconnected, files = [], usbfiles = [] } = data; + return ( + + +
    + act('PRG_copytousb', { name: file })} + onDelete={(file) => act('PRG_deletefile', { name: file })} + onRename={(file, newName) => + act('PRG_renamefile', { + name: file, + new_name: newName, + }) + } + onDuplicate={(file) => act('PRG_clone', { file: file })} + onToggleSilence={(file) => act('PRG_togglesilence', { name: file })} + /> +
    + {usbconnected && ( +
    + act('PRG_copyfromusb', { name: file })} + onDelete={(file) => act('PRG_usbdeletefile', { name: file })} + onRename={(file, newName) => + act('PRG_usbrenamefile', { + name: file, + new_name: newName, + }) + } + onDuplicate={(file) => act('PRG_clone', { file: file })} + /> +
    + )} +
    +
    + ); +}; + +const FileTable = (props) => { + const { + files = [], + usbconnected, + usbmode, + onUpload, + onDelete, + onRename, + onToggleSilence, + } = props; + return ( + + + File + Type + Size + + {files.map((file) => ( + + + {!file.undeletable ? ( + onRename(file.name, value)} + /> + ) : ( + file.name + )} + + {file.type} + {file.size} + + {!!file.alert_able && ( +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosGasAnalyzer.tsx b/tgui/packages/tgui/interfaces/NtosGasAnalyzer.tsx index e70e544fae9f5..ac186e84db5d0 100644 --- a/tgui/packages/tgui/interfaces/NtosGasAnalyzer.tsx +++ b/tgui/packages/tgui/interfaces/NtosGasAnalyzer.tsx @@ -1,4 +1,5 @@ import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Button } from '../components'; import { NtosWindow } from '../layouts'; @@ -9,8 +10,8 @@ type NtosGasAnalyzerData = GasAnalyzerData & { clickAtmozphereCompatible: BooleanLike; }; -export const NtosGasAnalyzer = (props, context) => { - const { act, data } = useBackend(context); +export const NtosGasAnalyzer = (props) => { + const { act, data } = useBackend(); const { atmozphereMode, clickAtmozphereCompatible } = data; return ( @@ -26,7 +27,8 @@ export const NtosGasAnalyzer = (props, context) => { ? 'Right-click on objects while holding the tablet to scan them. Right-click on the tablet to scan the current location.' : "The app will update it's gas mixture reading automatically." } - tooltipPosition="bottom"> + tooltipPosition="bottom" + > {atmozphereMode === 'click' ? 'Scanning tapped objects. Click to switch.' : 'Scanning current location. Click to switch.'} diff --git a/tgui/packages/tgui/interfaces/NtosJobManager.js b/tgui/packages/tgui/interfaces/NtosJobManager.js deleted file mode 100644 index fe1a8681849a0..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosJobManager.js +++ /dev/null @@ -1,82 +0,0 @@ -import { useBackend } from '../backend'; -import { Button, Section, Table, NoticeBox, Dimmer, Box } from '../components'; -import { NtosWindow } from '../layouts'; - -export const NtosJobManager = (props, context) => { - return ( - - - - - - ); -}; - -export const NtosJobManagerContent = (props, context) => { - const { act, data } = useBackend(context); - const { authed, cooldown, slots = [], prioritized = [] } = data; - if (!authed) { - return ( - - Current ID does not have access permissions to change job slots. - - ); - } - return ( -
    - {cooldown > 0 && ( - - - On Cooldown: {cooldown}s - - - )} - - - Prioritized - Slots - - {slots.map((slot) => ( - - - 0 && prioritized.includes(slot.title)} - onClick={() => - act('PRG_priority', { - target: slot.title, - }) - } - /> - - - {slot.current} / {slot.total} - - -
    -
    - ); -}; diff --git a/tgui/packages/tgui/interfaces/NtosJobManager.jsx b/tgui/packages/tgui/interfaces/NtosJobManager.jsx new file mode 100644 index 0000000000000..53f2ea2b220ba --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosJobManager.jsx @@ -0,0 +1,82 @@ +import { useBackend } from '../backend'; +import { Box, Button, Dimmer, NoticeBox, Section, Table } from '../components'; +import { NtosWindow } from '../layouts'; + +export const NtosJobManager = (props) => { + return ( + + + + + + ); +}; + +export const NtosJobManagerContent = (props) => { + const { act, data } = useBackend(); + const { authed, cooldown, slots = [], prioritized = [] } = data; + if (!authed) { + return ( + + Current ID does not have access permissions to change job slots. + + ); + } + return ( +
    + {cooldown > 0 && ( + + + On Cooldown: {cooldown}s + + + )} + + + Prioritized + Slots + + {slots.map((slot) => ( + + + 0 && prioritized.includes(slot.title)} + onClick={() => + act('PRG_priority', { + target: slot.title, + }) + } + /> + + + {slot.current} / {slot.total} + + +
    +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosMODsuit.js b/tgui/packages/tgui/interfaces/NtosMODsuit.js deleted file mode 100644 index 9efc9b7df1236..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosMODsuit.js +++ /dev/null @@ -1,31 +0,0 @@ -import { NtosWindow } from '../layouts'; -import { useBackend } from '../backend'; -import { NoticeBox } from '../components'; -import { MODsuitContent } from './MODsuit'; - -export const NtosMODsuit = (props, context) => { - const { data } = useBackend(context); - const { ui_theme } = data; - return ( - - - - - - ); -}; - -const NtosMODsuitContent = (props, context) => { - const { data } = useBackend(context); - const { has_suit } = data; - if (!has_suit) { - return ( - - No Modular suit connected, please tap a suit on the application host to - sync on - - ); - } else { - return ; - } -}; diff --git a/tgui/packages/tgui/interfaces/NtosMODsuit.jsx b/tgui/packages/tgui/interfaces/NtosMODsuit.jsx new file mode 100644 index 0000000000000..23d46906fd39a --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosMODsuit.jsx @@ -0,0 +1,31 @@ +import { useBackend } from '../backend'; +import { NoticeBox } from '../components'; +import { NtosWindow } from '../layouts'; +import { MODsuitContent } from './MODsuit'; + +export const NtosMODsuit = (props) => { + const { data } = useBackend(); + const { ui_theme } = data; + return ( + + + + + + ); +}; + +const NtosMODsuitContent = (props) => { + const { data } = useBackend(); + const { has_suit } = data; + if (!has_suit) { + return ( + + No Modular suit connected, please tap a suit on the application host to + sync on + + ); + } else { + return ; + } +}; diff --git a/tgui/packages/tgui/interfaces/NtosMafiaPanel.tsx b/tgui/packages/tgui/interfaces/NtosMafiaPanel.tsx new file mode 100644 index 0000000000000..09a0f6ddee962 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosMafiaPanel.tsx @@ -0,0 +1,12 @@ +import { NtosWindow } from '../layouts'; +import { MafiaPanelData } from './MafiaPanel'; + +export const NtosMafiaPanel = (props) => { + return ( + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosMain.js b/tgui/packages/tgui/interfaces/NtosMain.js deleted file mode 100644 index 8debe5d93be8e..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosMain.js +++ /dev/null @@ -1,210 +0,0 @@ -import { useBackend } from '../backend'; -import { Button, ColorBox, Stack, Section, Table } from '../components'; -import { NtosWindow } from '../layouts'; - -export const NtosMain = (props, context) => { - const { act, data } = useBackend(context); - const { - PC_device_theme, - show_imprint, - programs = [], - has_light, - light_on, - comp_light_color, - removable_media = [], - login = [], - proposed_login = [], - pai, - } = data; - const filtered_programs = programs.filter( - (program) => program.header_program - ); - return ( - - - {Boolean( - removable_media.length || - programs.some((program) => program.header_program) - ) && ( -
    - - {filtered_programs.map((app) => ( - -
    - )} -
    - {!!has_light && ( - <> - -
    - {!!pai && ( -
    - - - -
    -
    - )} - -
    -
    - ); -}; - -const ProgramsTable = (props, context) => { - const { act, data } = useBackend(context); - const { programs = [] } = data; - // add the program filename to this list to have it excluded from the main menu program list table - const filtered_programs = programs.filter( - (program) => !program.header_program - ); - - return ( -
    - - {filtered_programs.map((program) => ( - - -
    -
    - ); -}; diff --git a/tgui/packages/tgui/interfaces/NtosMain.tsx b/tgui/packages/tgui/interfaces/NtosMain.tsx new file mode 100644 index 0000000000000..86b53387d0d72 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosMain.tsx @@ -0,0 +1,217 @@ +import { useBackend } from '../backend'; +import { Button, ColorBox, Section, Stack, Table } from '../components'; +import { NtosWindow } from '../layouts'; +import { NTOSData } from '../layouts/NtosWindow'; + +export const NtosMain = (props) => { + const { act, data } = useBackend(); + const { + PC_device_theme, + show_imprint, + programs = [], + has_light, + light_on, + comp_light_color, + removable_media = [], + login, + proposed_login, + pai, + } = data; + const filtered_programs = programs.filter( + (program) => program.header_program, + ); + + return ( + + + {Boolean( + removable_media.length || + programs.some((program) => program.header_program), + ) && ( +
    + + {filtered_programs.map((app) => ( + +
    + )} +
    + {!!has_light && ( + <> + +
    + {!!pai && ( +
    + + + +
    +
    + )} + +
    +
    + ); +}; + +const ProgramsTable = (props) => { + const { act, data } = useBackend(); + const { programs = [] } = data; + // add the program filename to this list to have it excluded from the main menu program list table + const filtered_programs = programs.filter( + (program) => !program.header_program, + ); + + return ( +
    + + {filtered_programs.map((program) => ( + + +
    +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosMessenger/ChatScreen.tsx b/tgui/packages/tgui/interfaces/NtosMessenger/ChatScreen.tsx index e069126ee8b63..b168b05729786 100644 --- a/tgui/packages/tgui/interfaces/NtosMessenger/ChatScreen.tsx +++ b/tgui/packages/tgui/interfaces/NtosMessenger/ChatScreen.tsx @@ -1,9 +1,20 @@ -import { Stack, Section, Button, Box, Input, Modal, Tooltip, Icon } from '../../components'; -import { Component, RefObject, createRef, SFC } from 'inferno'; -import { NtMessage, NtMessenger, NtPicture } from './types'; import { BooleanLike } from 'common/react'; -import { useBackend } from '../../backend'; import { decodeHtmlEntities } from 'common/string'; +import { Component, createRef, RefObject } from 'react'; + +import { useBackend } from '../../backend'; +import { + Box, + Button, + Icon, + Image, + Input, + Modal, + Section, + Stack, + Tooltip, +} from '../../components'; +import { NtMessage, NtMessenger, NtPicture } from './types'; type ChatScreenProps = { canReply: BooleanLike; @@ -59,7 +70,7 @@ export class ChatScreen extends Component { componentDidUpdate( prevProps: ChatScreenProps, _prevState: ChatScreenState, - _snapshot: any + _snapshot: any, ) { if (prevProps.messages.length !== this.props.messages.length) { this.scrollToBottom(); @@ -72,7 +83,7 @@ export class ChatScreen extends Component { return; } - const { act } = useBackend(this.context); + const { act } = useBackend(); this.tryClearReadTimeout(); @@ -106,7 +117,7 @@ export class ChatScreen extends Component { } clearUnreads() { - const { act } = useBackend(this.context); + const { act } = useBackend(); act('PDA_clearUnreads', { ref: this.props.chatRef }); } @@ -120,7 +131,7 @@ export class ChatScreen extends Component { handleSelectPicture() { const { isSilicon } = this.props; - const { act } = useBackend(this.context); + const { act } = useBackend(); if (isSilicon) { act('PDA_siliconSelectPhoto'); } else { @@ -133,7 +144,7 @@ export class ChatScreen extends Component { return; } - const { act } = useBackend(this.context); + const { act } = useBackend(); const { chatRef, recipient } = this.props; let ref = chatRef ? chatRef : recipient.ref; @@ -152,7 +163,7 @@ export class ChatScreen extends Component { } render() { - const { act } = useBackend(this.context); + const { act } = useBackend(); const { canReply, messages, @@ -192,7 +203,7 @@ export class ChatScreen extends Component { : undefined } /> - + , ); } @@ -215,8 +226,9 @@ export class ChatScreen extends Component { onClick={() => { act('PDA_selectPhoto', { uid: photo.uid }); this.setState({ selectingPhoto: false }); - }}> - + }} + > + )); @@ -279,8 +291,9 @@ export class ChatScreen extends Component { pt={1} onClick={() => act('PDA_clearPhoto')} tooltip="Remove attachment" - tooltipPosition="auto-end"> - + tooltipPosition="auto-end" + > + )} @@ -292,8 +305,6 @@ export class ChatScreen extends Component { fluid autoFocus width="100%" - justify - id="input" value={message} maxLength={1024} onInput={this.handleMessageInput} @@ -338,7 +349,8 @@ export class ChatScreen extends Component { fill fitted title={`${recipient.name} (${recipient.job})`} - scrollableRef={this.scrollRef}> + ref={this.scrollRef} + > {!!(messages.length > 0 && canReply) && ( <> @@ -366,8 +378,9 @@ export class ChatScreen extends Component { tooltipPosition="left" onClick={() => this.setState({ previewingImage: undefined })} /> - }> - + } + > + )} @@ -406,20 +419,21 @@ const ChatMessage = (props: ChatMessageProps) => { {!!everyone && ( Sent to everyone )} - {photoPath !== null && ( + {!!photoPath && ( )} ); }; -const ChatDivider: SFC<{ mt: number }> = (props) => { +const ChatDivider = (props: { mt: number }) => { return (
    diff --git a/tgui/packages/tgui/interfaces/NtosMessenger/index.tsx b/tgui/packages/tgui/interfaces/NtosMessenger/index.tsx index 1b227ab09df0d..852ca62cc5594 100644 --- a/tgui/packages/tgui/interfaces/NtosMessenger/index.tsx +++ b/tgui/packages/tgui/interfaces/NtosMessenger/index.tsx @@ -1,12 +1,23 @@ -import { Box, Button, Icon, Section, Stack, Input, TextArea, Dimmer, Divider } from '../../components'; -import { useBackend, useLocalState } from '../../backend'; -import { createSearch } from 'common/string'; +import { sortBy } from 'common/collections'; import { BooleanLike } from 'common/react'; -import { NtosWindow } from '../../layouts'; +import { createSearch } from 'common/string'; +import { useState } from 'react'; -import { NtChat, NtMessenger, NtPicture } from './types'; +import { useBackend } from '../../backend'; +import { + Box, + Button, + Dimmer, + Divider, + Icon, + Input, + Section, + Stack, + TextArea, +} from '../../components'; +import { NtosWindow } from '../../layouts'; import { ChatScreen } from './ChatScreen'; -import { sortBy } from 'common/collections'; +import { NtChat, NtMessenger, NtPicture } from './types'; type NtosMessengerData = { can_spam: BooleanLike; @@ -26,8 +37,8 @@ type NtosMessengerData = { sending_virus: BooleanLike; }; -export const NtosMessenger = (props, context) => { - const { data } = useBackend(context); +export const NtosMessenger = (props) => { + const { data } = useBackend(); const { is_silicon, saved_chats, @@ -43,19 +54,23 @@ export const NtosMessenger = (props, context) => { const openChat = saved_chats[open_chat]; const temporaryRecipient = messengers[open_chat]; - content = ( - - ); + if (!openChat && !temporaryRecipient) { + content = ; + } else { + content = ( + + ); + } } else { content = ; } @@ -67,8 +82,8 @@ export const NtosMessenger = (props, context) => { ); }; -const ContactsScreen = (props: any, context: any) => { - const { act, data } = useBackend(context); +const ContactsScreen = (props: any) => { + const { act, data } = useBackend(); const { owner, alert_silenced, @@ -83,17 +98,17 @@ const ContactsScreen = (props: any, context: any) => { sending_virus, } = data; - const [searchUser, setSearchUser] = useLocalState(context, 'searchUser', ''); + const [searchUser, setSearchUser] = useState(''); const sortByUnreads = sortBy((chat) => chat.unread_messages); const searchChatByName = createSearch( searchUser, - (chat: NtChat) => chat.recipient.name + chat.recipient.job + (chat: NtChat) => chat.recipient.name + chat.recipient.job, ); const searchMessengerByName = createSearch( searchUser, - (messenger: NtMessenger) => messenger.name + messenger.job + (messenger: NtMessenger) => messenger.name + messenger.job, ); const chatToButton = (chat: NtChat) => { @@ -119,7 +134,7 @@ const ContactsScreen = (props: any, context: any) => { }; const openChatsArray = sortByUnreads(Object.values(saved_chats)).filter( - searchChatByName + searchChatByName, ); const filteredChatButtons = openChatsArray @@ -130,7 +145,7 @@ const ContactsScreen = (props: any, context: any) => { .filter( ([ref, messenger]) => openChatsArray.every((chat) => chat.recipient.ref !== ref) && - searchMessengerByName(messenger) + searchMessengerByName(messenger), ) .map(([_, messenger]) => messenger) .map(messengerToButton) @@ -199,7 +214,7 @@ const ContactsScreen = (props: any, context: any) => { width="220px" placeholder="Search by name or job..." value={searchUser} - onInput={(_: any, value: string) => setSearchUser(value)} + onInput={(_, value) => setSearchUser(value)} /> @@ -258,8 +273,8 @@ type ChatButtonProps = { chatRef: string; }; -const ChatButton = (props: ChatButtonProps, context) => { - const { act } = useBackend(context); +const ChatButton = (props: ChatButtonProps) => { + const { act } = useBackend(); const unreadMessages = props.unreads; const hasUnreads = unreadMessages > 0; return ( @@ -269,7 +284,8 @@ const ChatButton = (props: ChatButtonProps, context) => { fluid onClick={() => { act('PDA_viewMessages', { ref: props.chatRef }); - }}> + }} + > {hasUnreads && `[${unreadMessages <= 9 ? unreadMessages : '9+'} unread message${ unreadMessages !== 1 ? 's' : '' @@ -279,11 +295,11 @@ const ChatButton = (props: ChatButtonProps, context) => { ); }; -const SendToAllSection = (props, context) => { - const { data, act } = useBackend(context); +const SendToAllSection = (props) => { + const { data, act } = useBackend(); const { on_spam_cooldown } = data; - const [message, setmessage] = useLocalState(context, 'everyoneMessage', ''); + const [message, setmessage] = useState(''); return ( <> @@ -302,7 +318,8 @@ const SendToAllSection = (props, context) => { onClick={() => { act('PDA_sendEveryone', { message: message }); setmessage(''); - }}> + }} + > Send @@ -313,7 +330,7 @@ const SendToAllSection = (props, context) => { height={6} value={message} placeholder="Send message to everyone..." - onInput={(_: any, v: string) => setmessage(v)} + onChange={(event, value: string) => setmessage(value)} /> diff --git a/tgui/packages/tgui/interfaces/NtosNetChat.js b/tgui/packages/tgui/interfaces/NtosNetChat.js deleted file mode 100644 index 8668168b2a307..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosNetChat.js +++ /dev/null @@ -1,312 +0,0 @@ -import { useBackend } from '../backend'; -import { Box, Button, Dimmer, Icon, Input, Section, Stack } from '../components'; -import { NtosWindow } from '../layouts'; - -// byond defines for the program state -const CLIENT_ONLINE = 2; -const CLIENT_AWAY = 1; -const CLIENT_OFFLINE = 0; - -const NoChannelDimmer = (props, context) => { - return ( - - - - - - - - - - - - - - - - - Click a channel to start chatting! - - - (If you're new, you may want to set your name in the bottom - left!) - - - - ); -}; - -export const NtosNetChat = (props, context) => { - const { act, data } = useBackend(context); - const { - title, - can_admin, - adminmode, - authed, - username, - active_channel, - is_operator, - strong, - selfref, - all_channels = [], - clients = [], - messages = [], - } = data; - const in_channel = active_channel !== null; - const authorized = authed || adminmode; - // this list has cliented ordered from their status. online > away > offline - const displayed_clients = clients.sort((clientA, clientB) => { - if (clientA.operator) { - return -1; - } - if (clientB.operator) { - return 1; - } - return clientB.status - clientA.status; - }); - const client_color = (client) => { - if (client.operator) { - return 'green'; - } - if (client.online) { - return 'white'; - } - if (client.away) { - return 'yellow'; - } else { - return 'label'; - } - }; - // client from this computer! - const this_client = clients.find((client) => client.ref === selfref); - return ( - - - - -
    - - - - act('PRG_newchannel', { - new_channel_name: value, - }) - } - /> - {all_channels.map((channel) => ( -
    -
    - - - - -
    - {(in_channel && - (authorized ? ( - messages.map((message) => ( - {message.msg} - )) - ) : ( - - - - THIS CHANNEL IS PASSWORD PROTECTED - - INPUT PASSWORD TO ACCESS - - ))) || } -
    -
    - {!!in_channel && ( - - act('PRG_speak', { - message: value, - }) - } - /> - )} -
    -
    - {!!in_channel && ( - <> - - - - -
    - - {displayed_clients.map((client) => ( - - - {client.name} - - {client !== this_client && ( - <> - -
    -
    -
    - Settings for {title}: - - {!!(in_channel && authorized) && ( - <> - - act('PRG_savelog', { - log_name: value, - }) - } - /> - act('PRG_leavechannel')} - /> - - )} - {!!(is_operator && authed) && ( - <> - act('PRG_deletechannel')} - /> - - act('PRG_renamechannel', { - new_name: value, - }) - } - /> - - act('PRG_setpassword', { - new_password: value, - }) - } - /> - - )} - -
    -
    -
    - - )} -
    -
    -
    - ); -}; diff --git a/tgui/packages/tgui/interfaces/NtosNetChat.jsx b/tgui/packages/tgui/interfaces/NtosNetChat.jsx new file mode 100644 index 0000000000000..aab73f826a102 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosNetChat.jsx @@ -0,0 +1,321 @@ +import { useBackend } from '../backend'; +import { + Box, + Button, + Dimmer, + Icon, + Input, + Section, + Stack, +} from '../components'; +import { NtosWindow } from '../layouts'; + +// byond defines for the program state +const CLIENT_ONLINE = 2; +const CLIENT_AWAY = 1; +const CLIENT_OFFLINE = 0; + +const NoChannelDimmer = (props) => { + return ( + + + + + + + + + + + + + + + + + Click a channel to start chatting! + + + (If you're new, you may want to set your name in the bottom + left!) + + + + ); +}; + +export const NtosNetChat = (props) => { + const { act, data } = useBackend(); + const { + title, + can_admin, + adminmode, + authed, + username, + active_channel, + is_operator, + strong, + selfref, + all_channels = [], + clients = [], + messages = [], + } = data; + const in_channel = active_channel !== null; + const authorized = authed || adminmode; + // this list has cliented ordered from their status. online > away > offline + const displayed_clients = clients.sort((clientA, clientB) => { + if (clientA.operator) { + return -1; + } + if (clientB.operator) { + return 1; + } + return clientB.status - clientA.status; + }); + const client_color = (client) => { + if (client.operator) { + return 'green'; + } + if (client.online) { + return 'white'; + } + if (client.away) { + return 'yellow'; + } else { + return 'label'; + } + }; + // client from this computer! + const this_client = clients.find((client) => client.ref === selfref); + return ( + + + + +
    + + + + act('PRG_newchannel', { + new_channel_name: value, + }) + } + /> + {all_channels.map((channel) => ( +
    +
    + + + + +
    + {(in_channel && + (authorized ? ( + messages.map((message) => ( + {message.msg} + )) + ) : ( + + + + THIS CHANNEL IS PASSWORD PROTECTED + + INPUT PASSWORD TO ACCESS + + ))) || } +
    +
    + {!!in_channel && ( + + act('PRG_speak', { + message: value, + }) + } + /> + )} +
    +
    + {!!in_channel && ( + <> + + + + +
    + + {displayed_clients.map((client) => ( + + + {client.name} + + {client !== this_client && ( + <> + +
    +
    +
    + Settings for {title}: + + {!!(in_channel && authorized) && ( + <> + + act('PRG_savelog', { + log_name: value, + }) + } + /> + act('PRG_leavechannel')} + /> + + )} + {!!(is_operator && authed) && ( + <> + act('PRG_deletechannel')} + /> + + act('PRG_renamechannel', { + new_name: value, + }) + } + /> + + act('PRG_setpassword', { + new_password: value, + }) + } + /> + + )} + +
    +
    +
    + + )} +
    +
    +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosNetDos.js b/tgui/packages/tgui/interfaces/NtosNetDos.js deleted file mode 100644 index b83794d790b72..0000000000000 --- a/tgui/packages/tgui/interfaces/NtosNetDos.js +++ /dev/null @@ -1,95 +0,0 @@ -import { useBackend } from '../backend'; -import { Box, Button, LabeledList, NoticeBox, Section } from '../components'; -import { NtosWindow } from '../layouts'; - -export const NtosNetDos = (props, context) => { - return ( - - - - - - ); -}; - -export const NtosNetDosContent = (props, context) => { - const { act, data } = useBackend(context); - - const { relays = [], focus, target, speed, overload, capacity, error } = data; - - if (error) { - return ( - <> - {error} -