diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2c6d5d072dece..915dda724d53c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -125,7 +125,8 @@ # tralezab /code/__DEFINES/basic_mobs.dm @tralezab -/code/datums/ai @tralezab +/code/datums/ai/ @tralezab +/code/modules/religion/ @tralezab # Watermelon914 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 c9723d905b5f1..3fbc7843f1883 100644 --- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm +++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm @@ -1,32 +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 + program_flags = PROGRAM_REQUIRES_NTNET tgui_id = "NtosNetDownloader" program_icon = "download" + ///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 = "" + ///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_CREW, - PROGRAM_CATEGORY_ENGI, - PROGRAM_CATEGORY_SCI, - PROGRAM_CATEGORY_SUPL, - PROGRAM_CATEGORY_MISC, + 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/kill_program(mob/user) - . = ..() + abort_file_download() ui_header = null + . = ..() /datum/computer_file/program/ntnetdownload/proc/begin_file_download(filename) if(downloaded_file) @@ -38,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)) @@ -83,7 +90,7 @@ 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(NTNET_LOW_SIGNAL) @@ -92,9 +99,13 @@ download_netspeed = NTNETSPEED_HIGHSIGNAL 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) @@ -103,7 +114,6 @@ if("PRG_reseterror") if(downloaderror) download_completion = FALSE - download_netspeed = FALSE downloaded_file = null downloaderror = "" return TRUE @@ -121,7 +131,6 @@ 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 @@ -129,35 +138,27 @@ data["emagged"] = (computer.obj_flags & EMAGGED) var/list/repo = SSmodular_computers.available_antag_software | SSmodular_computers.available_station_software - var/list/program_categories = list() 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" = 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 9b5617364c0aa..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() diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm index 52bfafdcf8e97..00bd00f3e67b5 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" @@ -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 6e3e69cdccfc5..c9e4fc087f364 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 diff --git a/code/modules/modular_computers/file_system/programs/signalcommander.dm b/code/modules/modular_computers/file_system/programs/signalcommander.dm index e8140b62b17c2..1e6e3e54051fb 100644 --- a/code/modules/modular_computers/file_system/programs/signalcommander.dm +++ b/code/modules/modular_computers/file_system/programs/signalcommander.dm @@ -1,13 +1,15 @@ /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? @@ -36,10 +38,11 @@ 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) @@ -56,27 +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)) - computer.balloon_alert(usr, "cooling down!") + if(user) + computer.balloon_alert(user, "cooling down!") return COOLDOWN_START(src, signal_cooldown, signal_cooldown_time) - computer.balloon_alert(usr, "signaled") + if(user) + computer.balloon_alert(user, "signaled") var/time = time2text(world.realtime,"hh:mm:ss") var/turf/T = get_turf(computer) - - var/logging_data = "[time] : [key_name(usr)] used the computer '[initial(computer.name)]' @ location ([T.x],[T.y],[T.z]) : [format_frequency(signal_frequency)]/[signal_code]" + 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/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) + +/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 77d0a0900e4a5..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 @@ -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 49358223e3508..b782f2fc9593d 100644 --- a/code/modules/movespeed/modifiers/mobs.dm +++ b/code/modules/movespeed/modifiers/mobs.dm @@ -81,8 +81,8 @@ blacklisted_movetypes = FLOATING variable = TRUE -/datum/movespeed_modifier/shove - multiplicative_slowdown = SHOVE_SLOWDOWN_STRENGTH +/datum/movespeed_modifier/staggered + multiplicative_slowdown = STAGGERED_SLOWDOWN_STRENGTH /datum/movespeed_modifier/human_carry multiplicative_slowdown = HUMAN_CARRY_SLOWDOWN diff --git a/code/modules/movespeed/modifiers/status_effects.dm b/code/modules/movespeed/modifiers/status_effects.dm index 65245880ef42b..4768f66a544f4 100644 --- a/code/modules/movespeed/modifiers/status_effects.dm +++ b/code/modules/movespeed/modifiers/status_effects.dm @@ -53,3 +53,6 @@ /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/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/hud.dm b/code/modules/pai/hud.dm index 523d57d17b31c..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" diff --git a/code/modules/pai/pai.dm b/code/modules/pai/pai.dm index 3998470f74878..400cf3660b2b8 100644 --- a/code/modules/pai/pai.dm +++ b/code/modules/pai/pai.dm @@ -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( @@ -151,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) @@ -193,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) @@ -216,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() @@ -272,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(). @@ -461,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 a03c79f44d066..f9eafa901aa51 100644 --- a/code/modules/paperwork/fax.dm +++ b/code/modules/paperwork/fax.dm @@ -120,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. @@ -139,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)) @@ -174,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 @@ -537,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 ed99e7ea179dc..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) 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 47588a65706c7..b72ce806dd3a7 100644 --- a/code/modules/paperwork/paper_premade.dm +++ b/code/modules/paperwork/paper_premade.dm @@ -167,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/pen.dm b/code/modules/paperwork/pen.dm index 69af56d341902..5ee432b365ebe 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 */ @@ -262,6 +330,7 @@ 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) 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..c24b8fd73e8b2 100644 --- a/code/modules/paperwork/ticketmachine.dm +++ b/code/modules/paperwork/ticketmachine.dm @@ -40,7 +40,7 @@ return ..() /obj/machinery/ticket_machine/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) new /obj/item/wallframe/ticket_machine(loc) qdel(src) diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm index b168aaf54daf8..dae12f33bd4cf 100644 --- a/code/modules/photography/camera/camera.dm +++ b/code/modules/photography/camera/camera.dm @@ -16,7 +16,7 @@ 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 @@ -241,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)) @@ -327,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/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/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/bottler.dm b/code/modules/plumbing/plumbers/bottler.dm index bba9238fa2681..8e0158b61da78 100644 --- a/code/modules/plumbing/plumbers/bottler.dm +++ b/code/modules/plumbing/plumbers/bottler.dm @@ -79,7 +79,7 @@ return PROCESS_KILL ///see if machine has enough to fill, is anchored down and has any inputspot objects to pick from - if(reagents.total_volume + 0.01 >= wanted_amount && anchored && length(inputspot.contents)) + 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 //allowed containers @@ -91,13 +91,13 @@ 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) 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) + 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, transferred_by = src, methods = INJECT) + 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 index 1db36c137e6d7..4bf4d71ec9f9f 100644 --- a/code/modules/plumbing/plumbers/iv_drip.dm +++ b/code/modules/plumbing/plumbers/iv_drip.dm @@ -7,9 +7,9 @@ density = TRUE use_internal_storage = TRUE -/obj/machinery/iv_drip/plumbing/Initialize(mapload) +/obj/machinery/iv_drip/plumbing/Initialize(mapload, bolt, layer) . = ..() - AddComponent(/datum/component/plumbing/iv_drip, anchored) + 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) @@ -33,9 +33,8 @@ 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 TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/machinery/iv_drip/plumbing/deconstruct(disassembled = TRUE) qdel(src) diff --git a/code/modules/plumbing/plumbers/pill_press.dm b/code/modules/plumbing/plumbers/pill_press.dm index f325d12b48d52..26835e7c92d6f 100644 --- a/code/modules/plumbing/plumbers/pill_press.dm +++ b/code/modules/plumbing/plumbers/pill_press.dm @@ -78,7 +78,7 @@ return //shift & check to account for floating point inaccuracies - if(reagents.total_volume + 0.01 >= current_volume) + if(reagents.total_volume >= current_volume) var/obj/item/reagent_containers/container = locate(packaging_type) container = new container(src) var/suffix 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 c1e5637840877..2b56bfb4ae6c9 100644 --- a/code/modules/plumbing/plumbers/reaction_chamber.dm +++ b/code/modules/plumbing/plumbers/reaction_chamber.dm @@ -3,9 +3,6 @@ /// coefficient to convert temperature to joules. same lvl as acclimator #define HEATER_COEFFICIENT 0.05 -/// maximum number of attempts the reaction chamber will make to balance the ph(More means better results but higher tick usage) -#define MAX_PH_ADJUSTMENTS 5 - /obj/machinery/plumbing/reaction_chamber name = "mixing chamber" desc = "Keeps chemicals separated until given conditions are met." @@ -107,32 +104,34 @@ switch(action) if("add") - var/selected_reagent = tgui_input_list(ui.user, "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 - - return TRUE + return TRUE + return FALSE if("remove") var/reagent = get_chem_id(params["chem"]) if(reagent) required_reagents.Remove(reagent) - return TRUE + return TRUE + return FALSE if("temperature") var/target = text2num(params["target"]) - if(target != null) + if(!isnull(target)) target_temperature = clamp(target, 0, 1000) - return TRUE + return TRUE + return FALSE var/result = handle_ui_act(action, params, ui, state) if(isnull(result)) @@ -173,8 +172,7 @@ return ..() /obj/machinery/plumbing/reaction_chamber/chem/handle_reagents(seconds_per_tick) - var/ph_balance_attempts = 0 - while(ph_balance_attempts < MAX_PH_ADJUSTMENTS && (reagents.ph < acidic_limit || reagents.ph > alkaline_limit)) + if(reagents.ph < acidic_limit || reagents.ph > alkaline_limit) //no power if(machine_stat & NOPOWER) return @@ -194,14 +192,13 @@ return //transfer buffer and handle reactions - var/ph_change = (reagents.ph > alkaline_limit ? (reagents.ph - alkaline_limit) : (acidic_limit - reagents.ph)) - var/buffer_amount = ((ph_change * reagents.total_volume) / (BUFFER_IONIZING_STRENGTH * num_of_reagents)) - if(!buffer.trans_to(reagents, buffer_amount * seconds_per_tick)) + 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 * seconds_per_tick) - ph_balance_attempts += 1 + 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) @@ -227,4 +224,3 @@ return FALSE #undef HEATER_COEFFICIENT -#undef MAX_PH_ADJUSTMENTS diff --git a/code/modules/plumbing/plumbers/synthesizer.dm b/code/modules/plumbing/plumbers/synthesizer.dm index 3dddd648e6165..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 @@ -52,10 +51,13 @@ /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) @@ -63,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)) @@ -72,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() 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 aaa63c05d853a..509eb4f05b90d 100644 --- a/code/modules/power/apc/apc_attack.dm +++ b/code/modules/power/apc/apc_attack.dm @@ -299,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 3f5a7590c39a6..450142b7e92e4 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)) @@ -295,7 +300,7 @@ . += "The cover is closed." /obj/machinery/power/apc/deconstruct(disassembled = TRUE) - if(flags_1 & NODECONSTRUCT_1) + if(obj_flags & NO_DECONSTRUCTION) return if(!(machine_stat & BROKEN)) set_broken() @@ -458,11 +463,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) @@ -482,6 +489,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 @@ -655,10 +667,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 3b0703688043d..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" 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 55c9b34a35086..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) @@ -220,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 0d0bb09c9975d..ec6c23b00c6f6 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -20,12 +20,12 @@ 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 = STANDARD_CELL_CHARGE - 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) ///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. diff --git a/code/modules/power/floodlight.dm b/code/modules/power/floodlight.dm index 7155ce6aa2383..d3c5c1de569ae 100644 --- a/code/modules/power/floodlight.dm +++ b/code/modules/power/floodlight.dm @@ -261,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..a2307039959cb 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,13 +379,13 @@ 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) + if(obj_flags & NO_DECONSTRUCTION) qdel(src) return var/obj/structure/light_construct/new_light = null @@ -420,7 +422,7 @@ ..() 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 +449,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 +657,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 +718,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 af48e9c5944f8..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() 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 a42df53b60d55..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_PLAY) - 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,10 +123,28 @@ 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/basic/construct/harvester, user, cultoverride = TRUE, loc_override = loc) @@ -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 44d318d3467c8..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 ..() diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm index f79e75fdaac93..64c09a987ed87 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar.dm @@ -122,7 +122,7 @@ azimuth_current = new_angle /obj/machinery/power/solar/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) if(disassembled) var/obj/item/solar_assembly/S = locate() in src if(S) diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 1ad022bcd934d..c7bba14e49c5e 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -177,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) @@ -212,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 @@ -243,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 . @@ -301,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) @@ -489,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) @@ -534,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( @@ -559,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 @@ -1014,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/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_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 972c98d4862c4..def7bf7aa4d28 100644 --- a/code/modules/power/tesla/coil.dm +++ b/code/modules/power/tesla/coil.dm @@ -73,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)) @@ -153,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)) diff --git a/code/modules/power/thermoelectric_generator.dm b/code/modules/power/thermoelectric_generator.dm new file mode 100644 index 0000000000000..1f8319ad51d39 --- /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() + 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..d39840f875ebc 100644 --- a/code/modules/power/tracker.dm +++ b/code/modules/power/tracker.dm @@ -138,7 +138,7 @@ unset_control() /obj/machinery/power/tracker/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) if(disassembled) var/obj/item/solar_assembly/S = locate() in src if(S) diff --git a/code/modules/power/turbine/turbine.dm b/code/modules/power/turbine/turbine.dm index bb4afcede94c5..fa1a885044a1d 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) + 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,7 +539,9 @@ /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() @@ -522,9 +551,7 @@ 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/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/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/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/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm index 9a733dbc47a84..e5de3df5d50d4 100644 --- a/code/modules/projectiles/ammunition/energy/special.dm +++ b/code/modules/projectiles/ammunition/energy/special.dm @@ -8,13 +8,13 @@ projectile_type = /obj/projectile/ion/weak 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' @@ -81,6 +81,7 @@ /obj/item/ammo_casing/energy/fisher projectile_type = /obj/projectile/energy/fisher - select_name = "light-buster" + 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/boxes_magazines/_box_magazine.dm b/code/modules/projectiles/boxes_magazines/_box_magazine.dm index f6555548a58e6..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" 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/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 b8713fae26f70..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) @@ -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/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm index 9f7ab7e354c94..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, 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/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm index 82f24e21b73cc..0f5158317fb7f 100644 --- a/code/modules/projectiles/guns/ballistic/launchers.dm +++ b/code/modules/projectiles/guns/ballistic/launchers.dm @@ -91,9 +91,9 @@ 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) diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index 2b943a5f6db63..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 @@ -212,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)) @@ -223,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)) @@ -240,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 @@ -286,15 +296,11 @@ 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 = /obj/item/gun/ballistic/revolver/syndicate::name + desc = /obj/item/gun/ballistic/revolver/syndicate::desc clumsy_check = FALSE icon_state = "revolversyndie" -/obj/item/gun/ballistic/revolver/reverse/Initialize(mapload) - . = ..() - var/obj/item/gun/ballistic/revolver/syndicate/syndie_revolver = /obj/item/gun/ballistic/revolver/syndicate - name = initial(syndie_revolver.name) - desc = initial(syndie_revolver.desc) - /obj/item/gun/ballistic/revolver/reverse/can_trigger_gun(mob/living/user, akimbo_usage) if(akimbo_usage) return FALSE 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/dueling.dm b/code/modules/projectiles/guns/energy/dueling.dm index 932117abb5bcd..f35769e663c71 100644 --- a/code/modules/projectiles/guns/energy/dueling.dm +++ b/code/modules/projectiles/guns/energy/dueling.dm @@ -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/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index ec5a34379a5c3..7ff033d772028 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -153,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 @@ -166,7 +166,7 @@ projectile_type = /obj/projectile/kinetic select_name = "kinetic" e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE * 0.5) - fire_sound = 'sound/weapons/kenetic_accel.ogg' // fine spelling there chap + fire_sound = 'sound/weapons/kinetic_accel.ogg' /obj/item/ammo_casing/energy/kinetic/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") ..() @@ -363,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 diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index 27710eabed8c1..561525fe6fe9d 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -14,7 +14,7 @@ // 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, /datum/crafting_recipe/decloner) + var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/xraylaser, /datum/crafting_recipe/hellgun, /datum/crafting_recipe/ioncarbine) AddComponent( /datum/component/slapcrafting,\ @@ -122,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/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 index 69d3430cf9e73..5c9cb1fd9781b 100644 --- a/code/modules/projectiles/guns/special/hand_of_midas.dm +++ b/code/modules/projectiles/guns/special/hand_of_midas.dm @@ -45,7 +45,9 @@ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN if(!victim.reagents) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN - if(!victim.reagents.has_reagent(/datum/reagent/gold, check_subtypes = TRUE)) + + 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") @@ -53,8 +55,8 @@ qdel(gold_beam) balloon_alert(user, "link broken") return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN - handle_gold_charges(user, victim.reagents.get_reagent_amount(/datum/reagent/gold, include_subtypes = TRUE)) - victim.reagents.remove_all_type(/datum/reagent/gold, victim.reagents.get_reagent_amount(/datum/reagent/gold, include_subtypes = TRUE)) + 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 diff --git a/code/modules/projectiles/guns/special/meat_hook.dm b/code/modules/projectiles/guns/special/meat_hook.dm index e96e51fbfefa3..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,8 +24,22 @@ /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." @@ -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, 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/pins.dm b/code/modules/projectiles/pins.dm index 45763feff043b..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 diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 68dc20f2bfd0e..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 @@ -278,8 +277,8 @@ 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 BULLET_ACT_BLOCK @@ -793,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 @@ -812,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)) @@ -828,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) @@ -910,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 diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index 771d2639e2b4c..33c0a32373386 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -179,7 +179,7 @@ if(pierce_hits <= 0) projectile_piercing = NONE pierce_hits -= 1 - ..() + return ..() /obj/projectile/beam/emitter name = "emitter beam" 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/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/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/net_snare.dm b/code/modules/projectiles/projectile/energy/net_snare.dm index c60c0c35d172e..925096f63514d 100644 --- a/code/modules/projectiles/projectile/energy/net_snare.dm +++ b/code/modules/projectiles/projectile/energy/net_snare.dm @@ -69,7 +69,7 @@ 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() @@ -88,7 +88,7 @@ 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/magic.dm b/code/modules/projectiles/projectile/magic.dm index 2f3214d5d90ab..ae91fb6c60318 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -173,41 +173,12 @@ /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" @@ -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)) diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm index 49abb8c4f3d32..23df9c9c50a6f 100644 --- a/code/modules/projectiles/projectile/special/curse.dm +++ b/code/modules/projectiles/projectile/special/curse.dm @@ -7,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 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/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 f46c636c50f59..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_QUANTISATION_LEVEL) - delta_chem_factor = CHEMICAL_QUANTISATION_LEVEL //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 a93f0b7d5c9ff..0000000000000 --- a/code/modules/reagents/chemistry/holder.dm +++ /dev/null @@ -1,2109 +0,0 @@ -#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 ..() - -/** - * 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 - - // Prevents small amount problems, as well as zero and below zero amounts. - amount = FLOOR(amount, CHEMICAL_QUANTISATION_LEVEL) - if(amount <= 0) - 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 = FLOOR(process_mob_reagent_purity(glob_reagent, amount, added_purity), CHEMICAL_QUANTISATION_LEVEL) - if(adjusted_vol <= 0) //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 - - update_total() - var/cached_total = total_volume - if(cached_total + amount > maximum_volume) - amount = FLOOR(maximum_volume - cached_total, CHEMICAL_QUANTISATION_LEVEL) //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_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 = FLOOR(iter_reagent.volume + 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 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 - */ -/datum/reagents/proc/remove_reagent(datum/reagent/reagent_type, amount, safety = TRUE) - 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 - - // Prevents small amount problems, as well as zero and below zero amounts. - amount = FLOOR(amount, CHEMICAL_QUANTISATION_LEVEL) - if(amount <= 0) - return FALSE - - var/list/cached_reagents = reagent_list - for(var/datum/reagent/cached_reagent as anything in cached_reagents) - if(cached_reagent.type == reagent_type) - cached_reagent.volume = FLOOR(max(cached_reagent.volume - amount, 0), CHEMICAL_QUANTISATION_LEVEL) - 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) - - return TRUE - return FALSE - -/** - * Removes a reagent at random by the specified amount - * 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 = FLOOR(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 - 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 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 - - // Prevents small amount problems, as well as zero and below zero amounts. - amount = FLOOR(amount, CHEMICAL_QUANTISATION_LEVEL) - if(amount <= 0) - return FALSE - - var/list/cached_reagents = reagent_list - var/part = amount / total_volume - var/remove_amount - var/removed_amount = 0 - - for(var/datum/reagent/reagent as anything in cached_reagents) - remove_amount = FLOOR(reagent.volume * part, CHEMICAL_QUANTISATION_LEVEL) - remove_reagent(reagent.type, remove_amount) - removed_amount += remove_amount - - handle_reactions() - return removed_amount - -/** - * Removes all reagent of X type - * Arguments - * - * * [reagent_type][datum/reagent] - the reagent typepath we are trying to remove - * * amount - the volume of reagent to remove - * * strict - If TRUE will also remove childs of this reagent type - */ -/datum/reagents/proc/remove_all_type(datum/reagent/reagent_type, amount, strict = 0, safety = 1) - if(!ispath(reagent_type)) - stack_trace("invalid reagent path passed to remove all type [reagent_type]") - return FALSE - - if(!IS_FINITE(amount)) - stack_trace("non finite amount passed to remove all type reagent [amount] [reagent_type]") - return FALSE - - // Prevents small amount problems, as well as zero and below zero amounts. - amount = FLOOR(amount, CHEMICAL_QUANTISATION_LEVEL) - if(amount <= 0) - return FALSE - - 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 - -/** - * 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 = 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) - -/// 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) - -/** - * Check if this holder contains this reagent. Reagent takes a PATH to a reagent - * Needs matabolizing takes into consideration if the chemical is metabolizing when it's checked. - * Arguments - * - * * [target_reagent][datum/reagent] - the reagent typepath to check for - * * 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. - */ -/datum/reagents/proc/has_reagent( - datum/reagent/target_reagent, - amount = -1, - needs_metabolizing = FALSE, - check_subtypes = FALSE -) - if(!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) - if (check_subtypes ? ispath(holder_reagent.type, target_reagent) : holder_reagent.type == target_reagent) - if(!amount) - if(needs_metabolizing && !holder_reagent.metabolizing) - if(check_subtypes) - continue - return FALSE - return holder_reagent - else - if(holder_reagent.volume >= 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 - * - * Arguments - * * chemical_flag - the flag to check 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 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 - 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_multiple] and [/datum/reagent/proc/on_transfer] - * * show_message - passed through to [/datum/reagents/proc/expose_multiple] - * * 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, - ignore_stomach = FALSE -) - if(QDELETED(target) || !total_volume) - return - - if(!IS_FINITE(amount)) - stack_trace("non finite amount passed to trans_to [amount] amount of reagents") - 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 = FLOOR(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 = FLOOR(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 = amount / total_volume - 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(preserve_data) - trans_data = copy_data(reagent) - if(reagent.intercept_reagents_transfer(target_holder, cached_amount)) - continue - transfer_amount = reagent.volume * part - 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 - - //expose target to reagent changes - target_holder.expose_multiple(r_to_send, isorgan(target_atom) ? target : target_atom, methods, part, show_message) - - //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) - - 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() - target_holder.update_total() - if(!no_react) - target_holder.handle_reactions() - src.handle_reactions() - return FLOOR(total_transfered_amount, CHEMICAL_QUANTISATION_LEVEL) - -/** - * Transfer a specific reagent id to the target object - * Arguments - * - * * [target][obj] - the target to transfer reagents to - * * [reagent_type][datum/reagent] - the type of reagent to transfer to the target - * * amount - volume to transfer - * * preserve_data- if TRUE reagent user data will remain preserved - */ -/datum/reagents/proc/trans_id_to( - obj/target, - datum/reagent/reagent_type, - amount = 1, - preserve_data = 1 -) - if (QDELETED(target) || !total_volume) - return - - if(!IS_FINITE(amount)) - stack_trace("non finite amount passed to trans_id_to [amount] [reagent_type]") - return FALSE - - var/cached_amount = amount - - var/available_volume = get_reagent_amount(reagent_type) - var/datum/reagents/holder - if(istype(target, /datum/reagents)) - holder = target - else if(target.reagents && available_volume) - holder = target.reagents - else - return - - // Prevents small amount problems, as well as zero and below zero amounts. - amount = FLOOR(min(amount, available_volume, holder.maximum_volume - holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) - if(amount <= 0) - return - - var/list/cached_reagents = reagent_list - - 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_type) - 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 - * 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 = FLOOR(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 = reagent.data - 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 FLOOR(total_transfered_amount, CHEMICAL_QUANTISATION_LEVEL) - -/** - * 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() - - -/// 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 = toxin.volume - if(belly) - amount = FLOOR(amount + belly.reagents.get_reagent_amount(toxin.type), CHEMICAL_QUANTISATION_LEVEL) - - 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 - * 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() - -/** - * 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_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 && !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: -* * [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 - //Cap off values - for(var/datum/reagent/reagent as anything in reagent_list) - reagent.volume = FLOOR(reagent.volume, CHEMICAL_QUANTISATION_LEVEL)//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 = FLOOR(min(multiplier, get_reagent_amount(reagent) / cached_required_reagents[reagent]), CHEMICAL_QUANTISATION_LEVEL) - - 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) - -/// 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 - . = 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 - - //remove very small amounts of reagents - if((reagent.volume <= 0.05 && !is_reacting) || reagent.volume <= CHEMICAL_QUANTISATION_LEVEL) - //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) - - //assign the final values - total_volume = . - if(!.) - 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) - -/** - * 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) - -/// Is this holder full or not -/datum/reagents/proc/holder_full() - return total_volume + 0.01 >= 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 - * * include_subtypes - if TRUE returns the sum of volumes of all subtypes of the above param reagent - */ -/datum/reagents/proc/get_reagent_amount(datum/reagent/reagent, include_subtypes = FALSE) - 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) - if((!include_subtypes && cached_reagent.type == reagent) || (include_subtypes && ispath(cached_reagent.type, reagent))) - total_amount += cached_reagent.volume - - return FLOOR(total_amount, CHEMICAL_QUANTISATION_LEVEL) - -/** - * Gets the sum of volumes of all reagent type paths present in the list - * Arguments - * * [reagents][list] - list of reagent typepaths - */ -/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 += FLOOR(cached_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) - return total_amount - -/** - * 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 - -/** - * 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) - -/** - * 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) - 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 - -/** - * 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 - -/** - * Get a reference to the reagent if it exists - * Arguments - * * [type][datum/reagent] - the typepath of the reagent to look up - */ -/datum/reagents/proc/get_reagent(datum/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 - * 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() - -/** 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) - 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 = get_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) - -/** - * 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] ([FLOOR(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] ([FLOOR(reagent.volume, CHEMICAL_QUANTISATION_LEVEL)]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.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_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 - -#undef REAGENT_TRANSFER_AMOUNT -#undef REAGENT_PURITY 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..0f6ea479a96ce 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 @@ -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..6c036d37bef01 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() + 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..e49bc3ec5d6b2 100644 --- a/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm +++ b/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm @@ -75,7 +75,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 6955cede78de4..bbc06437b84c7 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -124,30 +124,36 @@ . += 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 @@ -337,10 +343,11 @@ // 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", @@ -392,7 +399,7 @@ 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 @@ -403,7 +410,7 @@ 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 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/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 d98044102d30a..f4be905a0738b 100644 --- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm +++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm @@ -142,10 +142,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) diff --git a/code/modules/reagents/chemistry/machinery/smoke_machine.dm b/code/modules/reagents/chemistry/machinery/smoke_machine.dm index 88072874e0fff..a8d1765891bf1 100644 --- a/code/modules/reagents/chemistry/machinery/smoke_machine.dm +++ b/code/modules/reagents/chemistry/machinery/smoke_machine.dm @@ -77,7 +77,6 @@ /obj/machinery/smoke_machine/process() - ..() if(reagents.total_volume == 0) on = FALSE update_appearance() @@ -95,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) diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm index 9b388c660857d..714b2ac21979f 100644 --- a/code/modules/reagents/chemistry/reagents.dm +++ b/code/modules/reagents/chemistry/reagents.dm @@ -155,7 +155,9 @@ */ /datum/reagent/proc/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) SHOULD_CALL_PARENT(TRUE) - current_cycle++ + +///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 if(isnull(holder)) @@ -170,6 +172,7 @@ 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) return @@ -203,15 +206,12 @@ Primarily used in reagents/reaction_agents /datum/reagent/proc/on_mob_end_metabolize(mob/living/affected_mob) return -/// Called when a reagent is inside of a mob when they are dead. Returning UPDATE_MOB_HEALTH will cause updatehealth() to be called on the holder mob by /datum/reagents/proc/metabolize. +/** + * 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) - if(!(chemical_flags & REAGENT_DEAD_PROCESS)) - return - current_cycle++ - if(length(reagent_removal_skip_list)) - return - if(holder) - holder.remove_reagent(type, metabolization_rate * affected_mob.metabolism_efficiency * seconds_per_tick) + SHOULD_CALL_PARENT(TRUE) /// Called after add_reagents creates a new reagent. /datum/reagent/proc/on_new(data) @@ -222,10 +222,6 @@ 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. 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 diff --git a/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm b/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm index 4550edbdfdf41..7b13b2d28b1d2 100644 --- a/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm @@ -130,13 +130,12 @@ if(!HAS_TRAIT(breather, TRAIT_KNOCKEDOUT)) return - . = ..() for(var/obj/item/organ/organ_being_healed as anything in breather.organs) if(!organ_being_healed.damage) continue if(organ_being_healed.apply_organ_damage(-0.5 * REM * seconds_per_tick, required_organ_flag = ORGAN_ORGANIC)) - return UPDATE_MOB_HEALTH + . = UPDATE_MOB_HEALTH /datum/reagent/zauker name = "Zauker" diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm index 0c1dd26e17399..8fb16f6333ac6 100644 --- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm @@ -26,6 +26,7 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/c2/helbital/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() 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 @@ -45,7 +46,11 @@ . = 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,7 +79,6 @@ affected_mob.revive(HEAL_ALL) holder.del_reagent(type) return - return ..() || . /datum/reagent/medicine/c2/helbital/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() @@ -94,6 +98,8 @@ 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) @@ -157,13 +163,16 @@ /*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) . = ..() @@ -182,6 +191,8 @@ 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) . = ..() @@ -491,13 +502,15 @@ show_message = 0 if(!(methods & (PATCH|TOUCH|VAPOR))) return - var/harmies = min(carbies.getBruteLoss(), carbies.adjustBruteLoss(-1.25 * reac_volume, updating_health = FALSE, required_bodytype = affected_bodytype)*-1) - var/burnies = min(carbies.getFireLoss(), carbies.adjustFireLoss(-1.25 * reac_volume, updating_health = FALSE, 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) 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) //0.5 - 0.75 + 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() diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm index 7a3dc6ab8fba6..93954ab944fc7 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm @@ -147,6 +147,7 @@ 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 @@ -159,6 +160,20 @@ /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!" @@ -405,7 +420,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 @@ -638,9 +653,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" @@ -1327,7 +1342,6 @@ 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.adjustCloneLoss(-5 * REM * seconds_per_tick, updating_health = FALSE) 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) @@ -1736,6 +1750,7 @@ /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) @@ -2713,7 +2728,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 @@ -2722,7 +2737,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 @@ -2731,7 +2746,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 @@ -2740,7 +2755,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 @@ -2749,7 +2764,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 @@ -2758,7 +2773,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 @@ -2767,7 +2782,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 @@ -2776,7 +2791,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 @@ -2785,7 +2800,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 @@ -2794,7 +2809,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 @@ -2812,7 +2827,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 @@ -2821,7 +2836,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 @@ -2830,7 +2845,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 @@ -2839,7 +2854,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 @@ -2848,7 +2863,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 @@ -2857,7 +2872,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 @@ -2866,7 +2881,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 28039f8b5b63a..e76e0e8fbb5c5 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm @@ -225,7 +225,7 @@ 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 @@ -251,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" @@ -272,6 +271,7 @@ 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) @@ -279,7 +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) - return ..() || . /datum/reagent/consumable/tea name = "Tea" diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 67fb8104efaa9..837a34daf7f15 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -84,7 +84,7 @@ 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) @@ -117,7 +117,7 @@ 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) + 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!")) @@ -175,7 +175,7 @@ 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) @@ -237,7 +237,7 @@ 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.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) @@ -518,7 +518,7 @@ /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 @@ -579,7 +579,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)) @@ -625,7 +625,6 @@ /datum/reagent/drug/blastoff/on_mob_life(mob/living/carbon/dancer, seconds_per_tick, times_fired) . = ..() - if(dancer.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)) . = UPDATE_MOB_HEALTH dancer.AdjustKnockdown(-20) @@ -818,8 +817,8 @@ //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.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) diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 3701b16320ef6..f7d070ee33dab 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -65,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 @@ -341,28 +341,20 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /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(affected_mob)) - heating = rand(5, 20) if(15 to 25) heating = 10 - if(isslime(affected_mob)) - heating = rand(10, 20) if(25 to 35) heating = 15 - if(isslime(affected_mob)) - heating = rand(15, 20) if(35 to INFINITY) heating = 20 - if(isslime(affected_mob)) - heating = rand(20, 25) affected_mob.adjust_bodytemperature(heating * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick) - return ..() /datum/reagent/consumable/frostoil name = "Frost Oil" @@ -376,32 +368,24 @@ default_container = /obj/item/reagent_containers/cup/bottle/frostoil /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(affected_mob)) - cooling = -rand(5, 20) if(15 to 25) cooling = -20 - if(isslime(affected_mob)) - cooling = -rand(10, 20) if(25 to 35) cooling = -30 if(prob(1)) affected_mob.emote("shiver") - if(isslime(affected_mob)) - cooling = -rand(15, 20) if(35 to INFINITY) cooling = -40 if(prob(5)) affected_mob.emote("shiver") - if(isslime(affected_mob)) - cooling = -rand(20, 25) affected_mob.adjust_bodytemperature(cooling * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 50) - return ..() /datum/reagent/consumable/frostoil/expose_turf(turf/exposed_turf, reac_volume) . = ..() @@ -460,10 +444,10 @@ return ..() /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)) affected_mob.visible_message(span_warning("[affected_mob] [pick("dry heaves!","coughs!","splutters!")]")) - return ..() /datum/reagent/consumable/salt name = "Table Salt" @@ -803,8 +787,8 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /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) - return ..() /datum/reagent/consumable/honey name = "Honey" @@ -826,8 +810,8 @@ mytray.adjust_pestlevel(rand(1, 2)) /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) . = ..() + holder.add_reagent(/datum/reagent/consumable/sugar, 3 * REM * seconds_per_tick) var/need_mob_update if(SPT_PROB(33, seconds_per_tick)) need_mob_update = affected_mob.adjustBruteLoss(-1, updating_health = FALSE, required_bodytype = affected_bodytype) diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents.dm index 1a06ae11cd960..59baceab5579f 100644 --- a/code/modules/reagents/chemistry/reagents/impure_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/impure_reagents.dm @@ -60,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 @@ -112,11 +113,14 @@ 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(current_cycle >= 60) - holder.remove_reagent(type, volume) // remove it all if we're past 60 cycles - return ..() + . = ..() if(!cube || affected_mob.loc != cube) 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) 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 b317bfafa1ac7..a54f117265da1 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 @@ -131,17 +131,21 @@ 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/affected_mob, amount) +/datum/reagent/inverse/libitoil/on_mob_add(mob/living/affected_mob, amount) . = ..() var/mob/living/carbon/consumer = affected_mob if(!consumer) @@ -151,21 +155,21 @@ 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/affected_mob) +/datum/reagent/inverse/libitoil/on_mob_delete(mob/living/affected_mob) . = ..() var/mob/living/carbon/consumer = affected_mob UnregisterSignal(consumer, COMSIG_CARBON_LOSE_ORGAN) @@ -205,18 +209,18 @@ Basically, we fill the time between now and 2s from now with hands based off the 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/affected_mob) +/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/affected_mob) +/datum/reagent/inverse/lentslurri/on_mob_end_metabolize(mob/living/carbon/affected_mob) . = ..() affected_mob.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/lenturi) @@ -250,24 +254,21 @@ Basically, we fill the time between now and 2s from now with hands based off the 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/affected_mob, amount) - . = ..() - amount_of_blur_applied = creation_purity * (volume / metabolization_rate) * 2 SECONDS - affected_mob.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/affected_mob, 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) . = ..() - affected_mob.adjust_eye_blur(-amount_of_blur_applied) + return TRUE //Hercuri //inverse @@ -769,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 @@ -788,5 +789,160 @@ 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 + +/datum/reagent/inverse/salbutamol/on_mob_metabolize(mob/living/affected_mob) + . = ..() + ADD_TRAIT(affected_mob, TRAIT_EASYBLEED, type) - hearing_args[HEARING_RAW_MESSAGE] = "[hearing_args[HEARING_RAW_MESSAGE]]" +/datum/reagent/inverse/salbutamol/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() + REMOVE_TRAIT(affected_mob, TRAIT_EASYBLEED, type) + +/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 + +/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/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 68eefcdf0cd44..303a85c6b4b6a 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -85,6 +85,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) @@ -93,7 +94,6 @@ affected_mob.AdjustParalyzed(-20 * REM * seconds_per_tick) if(holder.has_reagent(/datum/reagent/toxin/mindbreaker)) 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)) if(affected_mob.adjustToxLoss(1, updating_health = FALSE, required_biotype = affected_biotype)) @@ -107,12 +107,12 @@ 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) if(holder.has_reagent(/datum/reagent/toxin/histamine)) 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)) if(affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) @@ -158,7 +158,6 @@ 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 - need_mob_update += affected_mob.adjustCloneLoss(-power * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) for(var/i in affected_mob.all_wounds) var/datum/wound/iter_wound = i iter_wound.on_xadone(power * REM * seconds_per_tick) @@ -171,22 +170,6 @@ 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) - if(affected_mob.adjustCloneLoss((0.00006 * (affected_mob.bodytemperature ** 2) - 6) * REM * seconds_per_tick, updating_health = FALSE)) - . = UPDATE_MOB_HEALTH - REMOVE_TRAIT(affected_mob, TRAIT_DISFIGURED, TRAIT_GENERIC) - 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." @@ -214,7 +197,6 @@ 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) - need_mob_update += affected_mob.adjustCloneLoss(-power * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) if(need_mob_update) . = UPDATE_MOB_HEALTH for(var/i in affected_mob.all_wounds) @@ -224,20 +206,25 @@ /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) . = ..() - var/need_mob_update - need_mob_update = affected_mob.setCloneLoss(0) //Rezadone is almost never used in favor of cryoxadone. Hopefully this will change that. // No such luck so far - need_mob_update += affected_mob.heal_bodypart_damage(brute = 1 * REM * seconds_per_tick, burn = 1 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_biotype) - if(need_mob_update) + 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) @@ -285,6 +272,8 @@ 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) . = ..() @@ -478,6 +467,8 @@ 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) . = ..() @@ -528,6 +519,8 @@ metabolization_rate = 0.5 * REAGENTS_METABOLISM ph = 1 //One of the best buffers, NEVERMIND! chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + inverse_chem_val = 0.4 + inverse_chem = /datum/reagent/inverse/pen_acid /datum/reagent/medicine/pen_acid/on_mob_metabolize(mob/living/affected_mob) . = ..() @@ -554,6 +547,8 @@ 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) . = ..() @@ -579,6 +574,8 @@ 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) . = ..() @@ -655,11 +652,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) - return ..() /datum/reagent/medicine/morphine name = "Morphine" @@ -706,7 +703,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 @@ -821,6 +818,8 @@ overdose_threshold = 35 ph = 12 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + inverse_chem_val = 0.35 + inverse_chem = /datum/reagent/inverse/atropine /datum/reagent/medicine/atropine/on_mob_add(mob/living/affected_mob) . = ..() @@ -874,14 +873,11 @@ REMOVE_TRAIT(affected_mob, TRAIT_NOCRITDAMAGE, type) /datum/reagent/medicine/epinephrine/on_mob_life(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) if(SPT_PROB(10, seconds_per_tick)) holder.add_reagent(/datum/reagent/toxin/histamine, 4) - return ..() - - . = ..() + return var/need_mob_update if(affected_mob.health <= affected_mob.crit_threshold) @@ -905,6 +901,12 @@ 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)) @@ -997,7 +999,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 @@ -1100,11 +1102,11 @@ 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) - return ..() /datum/reagent/medicine/neurine/on_mob_dead(mob/living/carbon/affected_mob, seconds_per_tick) . = ..() @@ -1148,7 +1150,7 @@ . = ..() 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.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()) @@ -1212,9 +1214,9 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/insulin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() affected_mob.AdjustSleeping(-20 * REM * seconds_per_tick) holder.remove_reagent(/datum/reagent/consumable/sugar, 3 * REM * seconds_per_tick) - return ..() //Trek Chems, used primarily by medibots. Only heals a specific damage type, but is very efficient. @@ -1278,14 +1280,13 @@ 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) - need_mob_update += affected_mob.adjustCloneLoss(-3 * REM * seconds_per_tick, updating_health = FALSE) 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 /datum/reagent/medicine/earthsblood //Created by ambrosia gaia plants @@ -1306,7 +1307,6 @@ 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.adjustCloneLoss(-0.1 * 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 @@ -1314,7 +1314,6 @@ 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.adjustCloneLoss(-1 * 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) @@ -1506,13 +1505,14 @@ /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, 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 + 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) . = ..() diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index e42cfc55107d7..148894386f893 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) @@ -480,6 +488,7 @@ /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) @@ -531,7 +540,7 @@ 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) if(holder) - holder.remove_reagent(type, 0.5*seconds_per_tick) + holder.remove_reagent(type, 0.5 * seconds_per_tick) if(need_mob_update) return UPDATE_MOB_HEALTH @@ -615,31 +624,17 @@ 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) @@ -726,8 +721,6 @@ to_chat(affected_mob, span_warning("You've become \a [lowertext(initial(species_type.name))]!")) return - return ..() - /datum/reagent/mutationtoxin/classic //The one from plasma on green slimes name = "Mutation Toxin" description = "A corruptive toxin." @@ -923,9 +916,8 @@ /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" @@ -1000,8 +992,8 @@ 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")) @@ -1053,9 +1045,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" @@ -1076,7 +1068,7 @@ /datum/reagent/fluorine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() if(affected_mob.adjustToxLoss(0.5*REM*seconds_per_tick, updating_health = FALSE)) - . = TRUE + return UPDATE_MOB_HEALTH /datum/reagent/sodium name = "Sodium" @@ -1113,7 +1105,7 @@ /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")) @@ -1407,7 +1399,6 @@ /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 @@ -1574,6 +1565,7 @@ REMOVE_TRAIT(affected_mob, TRAIT_BLOODY_MESS, type) /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 @@ -1585,7 +1577,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 @@ -1830,8 +1821,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" @@ -1853,7 +1844,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 @@ -2158,9 +2149,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) @@ -2535,11 +2526,11 @@ /datum/reagent/bz_metabolites/on_mob_metabolize(mob/living/ling) . = ..() - ADD_TRAIT(ling, CHANGELING_HIVEMIND_MUTE, type) + ADD_TRAIT(ling, TRAIT_CHANGELING_HIVEMIND_MUTE, type) /datum/reagent/bz_metabolites/on_mob_end_metabolize(mob/living/ling) . = ..() - REMOVE_TRAIT(ling, CHANGELING_HIVEMIND_MUTE, type) + REMOVE_TRAIT(ling, TRAIT_CHANGELING_HIVEMIND_MUTE, type) /datum/reagent/bz_metabolites/on_mob_life(mob/living/carbon/target, seconds_per_tick, times_fired) . = ..() @@ -2562,12 +2553,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" @@ -2725,7 +2716,7 @@ return var/metal_amount = 0 - var/list/materials_to_transmute = target.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] @@ -2735,7 +2726,6 @@ var/list/metal_dat = list((metal_ref) = metal_amount) target.material_flags = applied_material_flags target.set_custom_materials(metal_dat) - ADD_TRAIT(target, TRAIT_MAT_TRANSMUTED, type) /datum/reagent/gravitum name = "Gravitum" @@ -2824,7 +2814,7 @@ /datum/reagent/eldritch/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) . = ..() var/need_mob_update = FALSE - if(IS_HERETIC(drinker)) + if(IS_HERETIC_OR_MONSTER(drinker)) drinker.adjust_drowsiness(-10 * REM * seconds_per_tick) drinker.AdjustAllImmobility(-40 * REM * seconds_per_tick) need_mob_update += drinker.adjustStaminaLoss(-10 * REM * seconds_per_tick, updating_stamina = FALSE) @@ -3053,7 +3043,6 @@ /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) diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index 2db3682ef2185..30757231625f4 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -279,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/affected_human = affected_mob affected_human.adjust_coretemperature(15 * REM * seconds_per_tick) - return ..() /datum/reagent/pyrosium/burn(datum/reagents/holder) if(holder.has_reagent(/datum/reagent/oxygen)) @@ -333,16 +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) - 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) + 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/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index 390c3d7bfd4dd..7457acd0687b9 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -98,9 +98,9 @@ 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) /datum/reagent/toxin/plasma/on_mob_metabolize(mob/living/carbon/affected_mob) @@ -154,9 +154,9 @@ 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) affected_mob.adjust_bodytemperature(-7 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, affected_mob.get_body_temp_normal()) if(ishuman(affected_mob)) @@ -679,7 +679,6 @@ // chance to either decay into histamine or go the normal route of toxin metabolization if(SPT_PROB(8, seconds_per_tick)) - current_cycle++ holder.add_reagent(/datum/reagent/toxin/histamine, pick(5, 10)) holder.remove_reagent(/datum/reagent/toxin/venom, 1.1) else @@ -782,7 +781,7 @@ 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 ..() || . @@ -799,26 +798,27 @@ /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) - var/need_mob_update - switch(picked_option) - if(1) - affected_mob.Paralyze(60) - 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), 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 - 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 + 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" @@ -963,7 +963,7 @@ 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) . = ..() @@ -1030,14 +1030,14 @@ /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] + 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) + 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) . = ..() @@ -1164,13 +1164,14 @@ /datum/reagent/toxin/delayed/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - if(current_cycle > delay) - if(holder) - holder.remove_reagent(type, actual_metaboliztion_rate * affected_mob.metabolism_efficiency * seconds_per_tick) - 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) + if(current_cycle <= delay) + return + if(holder) + holder.remove_reagent(type, actual_metaboliztion_rate * affected_mob.metabolism_efficiency * seconds_per_tick) + 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" @@ -1213,14 +1214,15 @@ . = ..() if(affected_mob.adjustStaminaLoss(7.5 * REM * seconds_per_tick, updating_stamina = FALSE)) . = UPDATE_MOB_HEALTH - 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!")) + 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) . = ..() diff --git a/code/modules/reagents/chemistry/reagents/unique/eigenstasium.dm b/code/modules/reagents/chemistry/reagents/unique/eigenstasium.dm index 73dcf8aa60b48..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 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 13d1882653e48..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) diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm index dcdef3d350baf..505b46ebc850b 100644 --- a/code/modules/reagents/chemistry/recipes/others.dm +++ b/code/modules/reagents/chemistry/recipes/others.dm @@ -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 05bc622faa70f..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) @@ -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) @@ -574,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 0f81a37815777..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) . = ..() 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 c15ba1a017f65..ed334adeac036 100644 --- a/code/modules/reagents/reagent_containers/cups/_cup.dm +++ b/code/modules/reagents/reagent_containers/cups/_cup.dm @@ -124,7 +124,7 @@ return var/trans = reagents.trans_to(target, amount_per_transfer_from_this, transferred_by = user) - to_chat(user, span_notice("You transfer [round(trans, 0.01)] unit\s of the solution to [target].")) + to_chat(user, span_notice("You transfer [trans] unit\s of the solution to [target].")) else if(target.is_drainable()) //A dispenser. Transfer FROM it TO us. if(!target.reagents.total_volume) @@ -136,7 +136,7 @@ return var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this, transferred_by = user) - to_chat(user, span_notice("You fill [src] with [round(trans, 0.01)] unit\s of the contents of [target].")) + to_chat(user, span_notice("You fill [src] with [trans] unit\s of the contents of [target].")) target.update_appearance() @@ -157,7 +157,7 @@ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this, transferred_by = user) - to_chat(user, span_notice("You fill [src] with [round(trans, 0.01)] unit\s of the contents of [target].")) + to_chat(user, span_notice("You fill [src] with [trans] unit\s of the contents of [target].")) target.update_appearance() return SECONDARY_ATTACK_CONTINUE_CHAIN @@ -167,34 +167,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 +350,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 +377,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 +495,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 +511,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..8745e61cd9236 100644 --- a/code/modules/reagents/reagent_containers/cups/drinkingglass.dm +++ b/code/modules/reagents/reagent_containers/cups/drinkingglass.dm @@ -34,7 +34,7 @@ /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) @@ -68,13 +68,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 +101,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 88b7d5baabfe3..12a3d023c72ca 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 @@ -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) diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm index e204c6803fb89..83754dd571f3e 100644 --- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm +++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm @@ -479,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) @@ -601,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 @@ -803,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/dropper.dm b/code/modules/reagents/reagent_containers/dropper.dm index ac8d0af0d1c6b..beb6f3e6314cd 100644 --- a/code/modules/reagents/reagent_containers/dropper.dm +++ b/code/modules/reagents/reagent_containers/dropper.dm @@ -46,7 +46,7 @@ target.visible_message(span_danger("[user] tries to squirt something into [target]'s eyes, but fails!"), \ span_userdanger("[user] tries to squirt something into your eyes, but fails!")) - to_chat(user, span_notice("You transfer [round(trans, 0.01)] unit\s of the solution.")) + to_chat(user, span_notice("You transfer [trans] unit\s of the solution.")) update_appearance() return else if(isalien(target)) //hiss-hiss has no eyes! @@ -66,7 +66,7 @@ log_combat(user, M, "squirted", R) trans = src.reagents.trans_to(target, amount_per_transfer_from_this, transferred_by = user) - to_chat(user, span_notice("You transfer [round(trans, 0.01)] unit\s of the solution.")) + to_chat(user, span_notice("You transfer [trans] unit\s of the solution.")) update_appearance() target.update_appearance() @@ -82,7 +82,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 [round(trans, 0.01)] unit\s of the solution.")) + to_chat(user, span_notice("You fill [src] with [trans] unit\s of the solution.")) update_appearance() target.update_appearance() 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_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 20da98599d14a..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) . = ..() @@ -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..47f477382e491 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) @@ -278,7 +278,7 @@ /obj/machinery/disposal/deconstruct(disassembled = TRUE) var/turf/T = loc - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) if(stored) var/obj/structure/disposalconstruct/construct = stored stored = null @@ -292,19 +292,17 @@ ..() ///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 +384,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 +401,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 +549,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/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 5fa4d5dd9b7dd..10108c8304819 100644 --- a/code/modules/recycling/sortingmachinery.dm +++ b/code/modules/recycling/sortingmachinery.dm @@ -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 d4c752751b8bc..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 @@ -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 c9c9e71135404..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" @@ -64,7 +67,7 @@ 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 98% rename from code/modules/religion/pyre_rites.dm rename to code/modules/religion/pyre/pyre_rites.dm index c36783e6b1223..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 diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index 3135fddeace8d..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 @@ -123,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 @@ -168,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) @@ -286,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) @@ -298,21 +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_MAGIC) + 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" @@ -431,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 ae61004b46e83..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 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_refinery.dm b/code/modules/research/anomaly/anomaly_refinery.dm index ce09ebb917627..533443a199557 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)) 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/designs.dm b/code/modules/research/designs.dm index 4d21aef796219..b196a06d30118 100644 --- a/code/modules/research/designs.dm +++ b/code/modules/research/designs.dm @@ -32,7 +32,7 @@ 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. 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/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index a73745399e11d..a73b445933394 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -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" @@ -1120,7 +1120,6 @@ 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 @@ -1136,3 +1135,16 @@ 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 e9d31c0ba85c2..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,7 +1500,10 @@ 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 @@ -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 150c69bc21fd2..0b09edf44ac94 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -352,7 +352,7 @@ 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 @@ -390,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( @@ -404,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 @@ -417,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 @@ -429,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, @@ -447,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, @@ -465,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, @@ -483,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, @@ -510,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, @@ -535,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, @@ -553,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, @@ -571,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, @@ -588,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, @@ -606,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, @@ -624,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, @@ -708,7 +708,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( @@ -731,7 +731,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( @@ -744,7 +744,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( @@ -767,7 +767,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( @@ -780,7 +780,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( @@ -803,7 +803,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( @@ -816,7 +816,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( @@ -839,7 +839,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( @@ -852,7 +852,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( @@ -865,7 +865,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, @@ -882,7 +882,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, @@ -899,7 +899,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, @@ -916,7 +916,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( @@ -949,7 +949,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( @@ -967,7 +967,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( diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm index a99c5e24217c9..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" 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 f0bafdaec29d4..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" 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..72fd12ced7046 100644 --- a/code/modules/research/destructive_analyzer.dm +++ b/code/modules/research/destructive_analyzer.dm @@ -1,225 +1,190 @@ -/* -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/Initialize(mapload) . = ..() - 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() + register_context() + +/obj/machinery/rnd/destructive_analyzer/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(loaded_item) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Remove Item" + else if(!isnull(held_item)) + context[SCREENTIP_CONTEXT_LMB] = "Insert Item" + return CONTEXTUAL_SCREENTIP_SET + +/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/finish_loading() - update_appearance() - reset_busy() +/obj/machinery/rnd/destructive_analyzer/AltClick(mob/user) + . = ..() + unload_item() /obj/machinery/rnd/destructive_analyzer/update_icon_state() icon_state = "[base_icon_state][loaded_item ? "_l" : null]" return ..() -/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() - - qdel(thing) - loaded_item = null - if (!innermode) - update_appearance() - return TRUE +/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 + data["loaded_item"] = null + return data -/obj/machinery/rnd/destructive_analyzer/proc/user_try_decon_id(id, mob/user) - if(!istype(loaded_item)) - return FALSE +/obj/machinery/rnd/destructive_analyzer/ui_static_data(mob/user) + var/list/data = list() + data["research_point_id"] = DESTRUCTIVE_ANALYZER_DESTROY_POINTS + return data - 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/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) + return - 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 + 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 + +/** + * 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) - add_fingerprint(usr) - usr.set_machine(src) +/** + * 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 4668753f0483f..dd282040ab04c 100644 --- a/code/modules/research/machinery/_production.dm +++ b/code/modules/research/machinery/_production.dm @@ -10,7 +10,7 @@ /// 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. @@ -32,11 +32,8 @@ . = ..() cached_designs = list() - materials = AddComponent( - /datum/component/remote_materials, \ - mapload, \ - mat_container_flags = BREAKDOWN_FLAGS_LATHE, \ - ) + materials = AddComponent(/datum/component/remote_materials, mapload) + AddComponent( /datum/component/payment, \ 0, \ @@ -107,10 +104,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() @@ -162,7 +156,7 @@ return data -/obj/machinery/rnd/production/ui_act(action, list/params) +/obj/machinery/rnd/production/ui_act(action, list/params, datum/tgui/ui) . = ..() if(.) @@ -178,7 +172,7 @@ materials.eject_sheets(material, amount) if("build") - user_try_print_id(params["ref"], params["amount"]) + user_try_print_id(ui.user, params["ref"], params["amount"]) /// Updates the fabricator's efficiency coefficient based on the installed parts. /obj/machinery/rnd/production/proc/calculate_efficiency() @@ -199,19 +193,13 @@ efficiency_coeff = max(total_rating, 0) -/obj/machinery/rnd/production/proc/do_print(path, amount) - for(var/i in 1 to amount) - new path(get_turf(src)) - - SSblackbox.record_feedback("nested tally", "item_printed", amount, list("[type]", "[path]")) - /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 else return efficiency_coeff -/obj/machinery/rnd/production/proc/user_try_print_id(design_id, print_quantity) +/obj/machinery/rnd/production/proc/user_try_print_id(mob/user, design_id, print_quantity) if(!design_id) return FALSE @@ -249,55 +237,83 @@ print_quantity = clamp(print_quantity, 1, 50) var/coefficient = build_efficiency(design.build_path) - //check if sufficient materials are available + // check if sufficient materials are available. if(!materials.mat_container.has_materials(design.materials, coefficient, print_quantity)) - say("Not enough materials to complete prototype[print_quantity > 1? "s" : ""].") + say("Not enough materials to complete prototype[print_quantity > 1 ? "s" : ""].") return FALSE //use power - var/power = active_power_usage + var/total_charge = 0 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]") - //produce item - busy = TRUE + total_charge += round(design.materials[material] * coefficient * print_quantity / 35) + var/charge_per_item = total_charge / print_quantity + 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() + + var/total_time = (design.construction_time * design.lathe_time_factor * print_quantity) ** 0.8 + var/time_per_item = total_time / print_quantity + start_making(design, print_quantity, time_per_item, coefficient, charge_per_item) return TRUE +/// Begins the act of making the given design the given number of items +/// Does not check or use materials/power/etc +/obj/machinery/rnd/production/proc/start_making(datum/design/design, build_count, build_time_per_item, build_efficiency, charge_per_item) + PROTECTED_PROC(TRUE) + + busy = TRUE + update_static_data_for_all_viewers() + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, build_efficiency, build_time_per_item, charge_per_item, build_count), build_time_per_item) + +/// Callback for start_making, actually makes the item +/// Called using timers started by start_making +/obj/machinery/rnd/production/proc/do_make_item(datum/design/design, build_efficiency, time_per_item, charge_per_item, items_remaining) + PROTECTED_PROC(TRUE) + + if(!items_remaining) // how + finalize_build() + return + + if(!directly_use_power(charge_per_item)) + say("Unable to continue production, power failure.") + finalize_build() + return + + var/is_stack = ispath(design.build_path, /obj/item/stack) + var/list/design_materials = design.materials + if(!materials.mat_container.has_materials(design_materials, build_efficiency, is_stack ? items_remaining : 1)) + say("Unable to continue production, missing materials.") + return + materials.use_materials(design_materials, build_efficiency, is_stack ? items_remaining : 1, "built", "[design.name]") + + var/atom/movable/created + if(is_stack) + created = new design.build_path(get_turf(src), items_remaining) + else + created = new design.build_path(get_turf(src)) + split_materials_uniformly(design_materials, build_efficiency, created) + + created.pixel_x = created.base_pixel_x + rand(-6, 6) + created.pixel_y = created.base_pixel_y + rand(-6, 6) + + if(is_stack) + items_remaining = 0 + else + items_remaining -= 1 + + if(!items_remaining) + finalize_build() + return + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, build_efficiency, time_per_item, items_remaining), time_per_item) + +/// 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) + busy = FALSE + update_static_data_for_all_viewers() + // 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) . = ..() 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..a109e5061df1b 100644 --- a/code/modules/research/ordnance/doppler_array.dm +++ b/code/modules/research/ordnance/doppler_array.dm @@ -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)) 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..ff03b368291c5 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 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..4ab9d73dca7b5 100644 --- a/code/modules/research/rdmachines.dm +++ b/code/modules/research/rdmachines.dm @@ -11,9 +11,10 @@ var/hacked = FALSE var/console_link = TRUE //allow console link. 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. var/datum/techweb/stored_research + ///The item loaded inside the machine, used by experimentors and destructive analyzers only. + var/obj/item/loaded_item /obj/machinery/rnd/proc/reset_busy() busy = FALSE @@ -59,14 +60,6 @@ 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 ..() - /obj/machinery/rnd/crowbar_act(mob/living/user, obj/item/tool) return default_deconstruction_crowbar(tool) @@ -103,36 +96,32 @@ 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 - //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() if(loaded_item) - loaded_item.forceMove(loc) + loaded_item.forceMove(drop_location()) ..() /obj/machinery/rnd/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm index 56be6864b5467..b3c10eaac93b8 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, is_right_clicking) +/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)) 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 bd1e5cc4a80d8..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 @@ -353,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) @@ -400,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 f5e1481e62cff..c36eb88627137 100644 --- a/code/modules/research/techweb/_techweb_node.dm +++ b/code/modules/research/techweb/_techweb_node.dm @@ -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 diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 3da5c4bb23405..12b72f283a0d5 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -319,6 +319,7 @@ design_ids = list( "circuit_multitool", "comp_access_checker", + "comp_arctan2", "comp_arithmetic", "comp_assoc_list_pick", "comp_assoc_list_remove", @@ -852,7 +853,7 @@ design_ids = list( "assembly_shell", "bot_shell", - "comp_mod_action", + "comp_equip_action", "controller_shell", "dispenser_shell", "door_shell", @@ -874,7 +875,6 @@ "bci_implanter", "bci_shell", "comp_bar_overlay", - "comp_bci_action", "comp_counter_overlay", "comp_install_detector", "comp_object_overlay", @@ -923,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." + 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) @@ -1012,6 +1016,7 @@ "borg_upgrade_lavaproof", "borg_upgrade_rped", "borg_upgrade_hypermod", + "borg_upgrade_inducer", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000) @@ -1108,6 +1113,7 @@ "holosignrestaurant", "holosignbar", "inducer", + "inducerengi", "tray_goggles", "holopad", "vendatray", @@ -1465,6 +1471,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) @@ -1877,6 +1886,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" @@ -1993,7 +2014,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 @@ -2148,7 +2169,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, @@ -2191,7 +2212,7 @@ "alien_scalpel", ) - boost_item_paths = list( + required_items_to_unlock = list( /obj/item/abductor, /obj/item/cautery/alien, /obj/item/circuitboard/machine/abductor, @@ -2230,7 +2251,7 @@ "alien_wrench", ) - boost_item_paths = list( + required_items_to_unlock = list( /obj/item/abductor, /obj/item/circuitboard/machine/abductor, /obj/item/crowbar/abductor, @@ -2255,7 +2276,6 @@ "advanced_camera", "ai_cam_upgrade", "borg_syndicate_module", - "decloner", "donksoft_refill", "donksofttoyvendor", "largecrossbow", @@ -2283,12 +2303,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//////////////////////// @@ -2396,15 +2416,3 @@ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) hidden = TRUE experimental = TRUE - -/datum/techweb_node/advanced_plastic_surgery - id = "plastic_surgery" - display_name = "Advanced Plastic Surgery" - description = "A Procedure long lost due to licensing problems now once again available." - prereq_ids = list("base") - design_ids = list( - "surgery_advanced_plastic_surgery" - ) - 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 04d37c62b7caa..a1d4eff50a6ed 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -473,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 @@ -991,8 +991,6 @@ healing_types += BURN if(owner.getToxLoss() > 0) healing_types += TOX - if(owner.getCloneLoss() > 0) - healing_types += CLONE if(length(healing_types)) owner.heal_damage_type(heal_amount, damagetype = pick(healing_types)) diff --git a/code/modules/research/xenobiology/crossbreeding/_weapons.dm b/code/modules/research/xenobiology/crossbreeding/_weapons.dm index a7e54fa2c7030..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 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 ec90edd6550bf..84084b2302e85 100644 --- a/code/modules/research/xenobiology/crossbreeding/consuming.dm +++ b/code/modules/research/xenobiology/crossbreeding/consuming.dm @@ -125,7 +125,6 @@ Consuming extracts: 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.adjustCloneLoss(-5, updating_health = FALSE) need_mob_update += M.adjustOrganLoss(ORGAN_SLOT_BRAIN, -5) if(need_mob_update) M.updatehealth() 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 0152b343c45df..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" @@ -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 2bbefb64ec343..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) @@ -467,7 +470,7 @@ /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.")) @@ -801,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 @@ -1033,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 @@ -1050,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 @@ -1062,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 6d3d9326d6d41..210a1d7dc9d9b 100644 --- a/code/modules/security_levels/keycard_authentication.dm +++ b/code/modules/security_levels/keycard_authentication.dm @@ -160,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/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 4e46fcffc59ce..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, ANNOUNCEMENT_TYPE_PRIORITY, color_override = "orange") + 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, ANNOUNCEMENT_TYPE_PRIORITY, color_override = "orange") + 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, ANNOUNCEMENT_TYPE_PRIORITY, color_override = "orange") + 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, ANNOUNCEMENT_TYPE_PRIORITY, color_override = "orange") + 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, ANNOUNCEMENT_TYPE_PRIORITY, color_override = "orange") + 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/shuttle.dm b/code/modules/shuttle/shuttle.dm index 6284461a337e3..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 @@ -514,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.") @@ -820,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 d46396a0e8ba9..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 diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm index 0a18857efeda7..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() @@ -201,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 diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm index 969bb29efb0e9..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,22 +59,23 @@ 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.get_contained_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 + 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/turf/shuttle_turf in shuttle_area.get_contained_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 + 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 @@ -135,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 @@ -160,14 +161,14 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( 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/rejected_orders = list() //list of all orders that exceeded the available budget and are uncancelable + 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 price = spawning_order.get_final_cost() - //department orders EARN money for cargo, not the other way around + // department orders EARN money for cargo, not the other way around var/datum/bank_account/paying_for_this if(!spawning_order.department_destination && spawning_order.charge_on_purchase) if(spawning_order.paying_account) //Someone paid out of pocket @@ -186,7 +187,8 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( 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 - rejected_orders += spawning_order + SSshuttle.shopping_list -= spawning_order + clean_up_orders += spawning_order continue pack_cost = spawning_order.pack.get_cost() @@ -194,17 +196,14 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( 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 - pack_cost) //Cargo gets the handling fee value += pack_cost - SSshuttle.shopping_list -= spawning_order - SSshuttle.order_history += spawning_order - QDEL_NULL(spawning_order.applied_coupon) if(!spawning_order.pack.goody) //we handle goody crates below var/obj/structure/closet/crate = spawning_order.generate(pick_n_take(empty_turfs)) @@ -219,10 +218,9 @@ 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++ - //clear out all rejected uncancellable orders - for(var/datum/supply_order/rejected_order in rejected_orders) - SSshuttle.shopping_list -= rejected_order - qdel(rejected_order) + // 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 @@ -255,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) @@ -272,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 @@ -290,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. @@ -306,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 d4d6938d28148..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 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/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/mind_transfer.dm b/code/modules/spells/spell_types/pointed/mind_transfer.dm index 72441362b652e..fa401c3b432f6 100644 --- a/code/modules/spells/spell_types/pointed/mind_transfer.dm +++ b/code/modules/spells/spell_types/pointed/mind_transfer.dm @@ -69,7 +69,7 @@ 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 @@ -96,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/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/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/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/_shapeshift.dm b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm index 5aecd863bce43..59c9ffdde3b0b 100644 --- a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm +++ b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm @@ -128,7 +128,7 @@ 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) 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/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/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index b8a7d7e8bbcae..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 @@ -156,9 +158,6 @@ /// 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 @@ -231,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) @@ -402,31 +412,24 @@ var/atom/drop_loc = drop_location() if(IS_ORGANIC_LIMB(src)) playsound(drop_loc, 'sound/misc/splort.ogg', 50, TRUE, -1) - QDEL_NULL(current_gauze) - for(var/obj/item/organ/bodypart_organ as anything in get_organs()) - bodypart_organ.transfer_to_limb(src, owner) - for(var/obj/item/organ/external/external as anything 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) @@ -736,70 +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/set_owner(new_owner) - SHOULD_CALL_PARENT(TRUE) +/// 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) + 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 @@ -878,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)) @@ -959,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 @@ -977,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) @@ -1044,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 diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index e863341eb4344..042628c2a33c3 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,39 +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 - return . - ///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) @@ -131,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) @@ -201,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) @@ -265,7 +214,7 @@ 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 @@ -276,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)) @@ -332,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) @@ -346,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 @@ -361,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) @@ -387,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 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 01ddea49df15f..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 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 0ebc781085212..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" @@ -35,8 +35,18 @@ /// 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 ..() @@ -81,7 +91,7 @@ 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 @@ -93,7 +103,7 @@ 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 @@ -104,14 +114,14 @@ 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 @@ -127,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 \ @@ -143,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) @@ -172,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 @@ -180,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) @@ -190,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) @@ -200,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' @@ -216,7 +252,7 @@ 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 @@ -229,11 +265,10 @@ 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" desc = "Over 87% of humans are right handed. That figure is much lower \ @@ -249,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) @@ -277,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 @@ -285,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) @@ -295,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) @@ -305,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' @@ -321,7 +349,7 @@ 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 @@ -334,7 +362,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 appendage_noun = "scythe-like hand" @@ -344,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 @@ -375,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 @@ -411,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) @@ -421,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) @@ -440,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' @@ -452,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 @@ -469,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) @@ -497,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 @@ -515,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) @@ -534,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' @@ -546,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 586cf7366a5e8..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,7 +109,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/emp_act(severity) . = ..() @@ -130,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 @@ -153,7 +156,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/right/robot/emp_act(severity) . = ..() @@ -175,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 @@ -196,15 +200,16 @@ biological_state = (BIO_ROBOTIC) - damage_examines = list(BRUTE = ROBOTIC_BRUTE_EXAMINE_TEXT, BURN = ROBOTIC_BURN_EXAMINE_TEXT, CLONE = DEFAULT_CLONE_EXAMINE_TEXT) - - var/wired = FALSE - var/obj/item/stock_parts/cell/cell = null + 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 + /obj/item/bodypart/chest/robot/emp_act(severity) . = ..() if(!. || isnull(owner)) @@ -348,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 @@ -369,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 @@ -459,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" @@ -471,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) @@ -482,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) @@ -493,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) @@ -504,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 feda164b6f259..3eeafa6f4e1a8 100644 --- a/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm @@ -37,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 @@ -54,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 8c971174d6d93..24178d9bd1610 100644 --- a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm @@ -14,6 +14,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' @@ -22,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 ca0a4d32dea71..83758f920c4cb 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -4,38 +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 @@ -83,28 +93,28 @@ is_dimorphic = TRUE dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage - wing_types = NONE + 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 @@ -120,14 +130,14 @@ biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_SLIMEPERSON is_dimorphic = TRUE - wing_types = NONE + 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 @@ -135,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 @@ -149,22 +159,22 @@ biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_LUMINESCENT is_dimorphic = TRUE - wing_types = NONE + 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 @@ -173,6 +183,7 @@ 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 @@ -222,6 +233,7 @@ /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' @@ -230,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' @@ -321,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 @@ -328,6 +342,7 @@ 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 @@ -335,24 +350,28 @@ 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 @@ -372,31 +391,31 @@ 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 @@ -489,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( @@ -500,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' @@ -523,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( @@ -534,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' @@ -556,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' @@ -569,7 +586,7 @@ dmg_overlay_type = null unarmed_damage_low = 7 unarmed_damage_high = 21 - unarmed_stun_threshold = 11 + unarmed_effectiveness = 25 ///flesh diff --git a/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm index 4dabec067fd0c..faed53371af60 100644 --- a/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm @@ -23,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' @@ -34,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 8070072521f34..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,7 @@ 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 @@ -34,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' @@ -45,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' @@ -56,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' @@ -67,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 94c503614a2a2..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 @@ -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) diff --git a/code/modules/surgery/bone_mending.dm b/code/modules/surgery/bone_mending.dm index 48114fe6d04ac..cac9051ceb44f 100644 --- a/code/modules/surgery/bone_mending.dm +++ b/code/modules/surgery/bone_mending.dm @@ -153,15 +153,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 +203,87 @@ 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/cranial_reconstruction/can_start(mob/living/user, mob/living/carbon/target) + . = ..() + if(!.) + return FALSE + + var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected) + return !isnull(targeted_bodypart.get_wound_type(targetable_wound)) + +/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/core_removal.dm b/code/modules/surgery/core_removal.dm index 4ada9e7b59a32..47f1e983f1986 100644 --- a/code/modules/surgery/core_removal.dm +++ b/code/modules/surgery/core_removal.dm @@ -49,10 +49,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/eye_surgery.dm b/code/modules/surgery/eye_surgery.dm index 7b6d7844ee6c4..748dab1f4ec85 100644 --- a/code/modules/surgery/eye_surgery.dm +++ b/code/modules/surgery/eye_surgery.dm @@ -22,10 +22,7 @@ /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 !isnull(target_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/organic_steps.dm b/code/modules/surgery/organic_steps.dm index 73e48db857f85..aa697cb107271 100644 --- a/code/modules/surgery/organic_steps.dm +++ b/code/modules/surgery/organic_steps.dm @@ -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 5b78cb30796e7..d7f08a7be35b6 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -6,6 +6,8 @@ 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. @@ -77,130 +79,16 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) volume = reagent_vol,\ after_eat = CALLBACK(src, PROC_REF(OnEatFrom))) - if(!IS_ROBOTIC_ORGAN(src)) - add_blood_DNA(blood_dna_info) - -/* - * 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 - - 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 - - - // 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) - - if(!IS_ROBOTIC_ORGAN(src) && !(item_flags & NO_BLOOD_ON_ITEM) && !QDELING(src)) - AddElement(/datum/element/decal/blood) - - 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) @@ -237,15 +125,6 @@ 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) - RemoveElement(/datum/element/decal/blood) - /obj/item/organ/wash(clean_types) . = ..() @@ -448,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) - return 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 921acf808ed90..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 diff --git a/code/modules/surgery/organs/external/_external_organ.dm b/code/modules/surgery/organs/external/_external_organ.dm index db0d253564093..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,23 +53,19 @@ 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(!.) @@ -84,54 +78,28 @@ 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) @@ -165,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) @@ -181,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 @@ -287,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..524810a2a67c3 100644 --- a/code/modules/surgery/organs/external/spines.dm +++ b/code/modules/surgery/organs/external/spines.dm @@ -7,42 +7,32 @@ zone = BODY_ZONE_CHEST slot = ORGAN_SLOT_EXTERNAL_SPINES - preference = "feature_lizard_spines" - dna_block = DNA_SPINES_BLOCK restyle_flags = EXTERNAL_RESTYLE_FLESH 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/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, moving) - . = ..() - if(paired_tail) - paired_tail.paired_spines = null - paired_tail = null +/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 (wagging gets updated by tail) +///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 e20f4f700f777..5c1ec393d5d21 100644 --- a/code/modules/surgery/organs/external/tails.dm +++ b/code/modules/surgery/organs/external/tails.dm @@ -17,8 +17,10 @@ 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)) @@ -32,45 +34,94 @@ 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) . = ..() + if(wag_flags & WAG_WAGGING) + wag(organ_owner, start = FALSE) + UnregisterSignal(organ_owner, COMSIG_ORGAN_WAG_TAIL) 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) - -/obj/item/organ/external/tail/proc/wag(mob/user, start = TRUE, stop_after = 0) +/obj/item/organ/external/tail/proc/wag(mob/living/carbon/organ_owner, 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) + if(start_wag(organ_owner) && stop_after) + addtimer(CALLBACK(src, PROC_REF(wag), organ_owner, FALSE), stop_after, TIMER_STOPPABLE|TIMER_DELETE_ME) else - stop_wag() - owner.update_body_parts() + 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/start_wag() +/obj/item/organ/external/tail/proc/start_wag(mob/living/carbon/organ_owner) + if(wag_flags & WAG_WAGGING) // 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 + 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(stop_wag)) + return TRUE ///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() +/obj/item/organ/external/tail/proc/stop_wag(mob/living/carbon/organ_owner) + SIGNAL_HANDLER + var/datum/bodypart_overlay/mutant/tail/accessory = bodypart_overlay wag_flags &= ~WAG_WAGGING accessory.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 + + organ_owner.update_body_parts() + UnregisterSignal(organ_owner, COMSIG_LIVING_DEATH) ///Tail parent type, with wagging functionality /datum/bodypart_overlay/mutant/tail @@ -121,34 +172,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 +183,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 45dee7691c865..aacf6f08f6a5c 100644 --- a/code/modules/surgery/organs/external/wings/functional_wings.dm +++ b/code/modules/surgery/organs/external/wings/functional_wings.dm @@ -29,13 +29,13 @@ // 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, drop_if_replaced) +/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) @@ -203,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 bb02c8b9ef9e9..e5190f1282ec7 100644 --- a/code/modules/surgery/organs/internal/appendix/_appendix.dm +++ b/code/modules/surgery/organs/internal/appendix/_appendix.dm @@ -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 @@ -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 486284b8b6d2c..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_LIVING_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_LIVING_EARLY_UNARMED_ATTACK) + UnregisterSignal(arm_owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK) /obj/item/organ/internal/cyberimp/arm/muscle/emp_act(severity) . = ..() @@ -430,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 1ea3a1bf9c4ab..513f0794c31cb 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm @@ -52,9 +52,8 @@ slot = ORGAN_SLOT_HEART_AID var/revive_cost = 0 var/reviving = FALSE - /// revival/defibrillation possibility flag that gathered from owner's .can_defib() proc - var/can_defib_owner 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 @@ -81,19 +80,13 @@ 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(can_defib_owner == DEFIB_POSSIBLE) + if(COOLDOWN_FINISHED(src, defib_cooldown)) revive_dead() - can_defib_owner = null - revive_cost += 10 MINUTES // Additional 10 minutes cooldown after revival. - // this check goes after revive_dead() to delay revival a bit - if(owner.stat == DEAD) - can_defib_owner = owner.can_defib() - if(can_defib_owner == DEFIB_POSSIBLE) - owner.notify_ghost_cloning("You are being revived by [src]!") - owner.grab_ghost() + /// boolean that stands for if PHYSICAL damage being patched var/body_damage_patched = FALSE var/need_mob_update = FALSE @@ -119,8 +112,14 @@ /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) @@ -182,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 f057883296912..67a02e71d7ea5 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm @@ -87,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() ..() @@ -113,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 7d2b0f3c54951..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.")) @@ -122,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) @@ -138,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 6933374140dc3..2f84b9ed93b56 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) @@ -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 @@ -327,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) @@ -359,7 +360,7 @@ /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() @@ -368,7 +369,7 @@ 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.set_light_on(FALSE) eye.update_brightness(victim) @@ -401,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) . = ..() @@ -425,20 +426,21 @@ 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) @@ -467,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 @@ -490,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"] @@ -521,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.light_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() /** @@ -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() @@ -622,11 +625,11 @@ /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 @@ -721,7 +724,7 @@ 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) @@ -740,7 +743,7 @@ apply_organ_damage(-10) //heal quickly . = ..() -/obj/item/organ/internal/eyes/night_vision/maintenance_adapted/Remove(mob/living/carbon/unadapted, special = FALSE) +/obj/item/organ/internal/eyes/night_vision/maintenance_adapted/on_mob_remove(mob/living/carbon/unadapted, special = FALSE) //remove lighting adapt_light.set_light_on(FALSE) adapt_light.update_brightness(unadapted) diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index fb97ca7eda00b..2773f588b24da 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -10,54 +10,84 @@ 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 - if(owner.health <= owner.crit_threshold && beat != BEAT_SLOW) + // Beyond deals with sound effects, so nothing needs to be done if no client + if(isnull(owner.client)) + return + + 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 @@ -118,12 +146,14 @@ else return ..() -/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) . = ..() + 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) -/obj/item/organ/internal/heart/cursed/Remove(mob/living/carbon/accursed, special = FALSE) +/obj/item/organ/internal/heart/cursed/on_mob_remove(mob/living/carbon/accursed, special = FALSE) . = ..() + qdel(accursed.GetComponent(/datum/component/manual_heart)) /obj/item/organ/internal/heart/cybernetic @@ -222,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 8ad9301fe7412..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."), @@ -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) @@ -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 4eb5c665db6fd..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)) diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 2e99f70d98916..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 @@ -503,12 +504,12 @@ /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)) diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm index 63fefca156ccb..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) diff --git a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm index c1632f33329d4..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) 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 9bd6bec8ccdb5..a39bdfc727ef7 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) @@ -306,7 +308,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)) @@ -546,7 +548,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) 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 424251143c33c..9224c29b2db63 100644 --- a/code/modules/surgery/plastic_surgery.dm +++ b/code/modules/surgery/plastic_surgery.dm @@ -16,7 +16,9 @@ ) /datum/surgery/plastic_surgery/advanced - name = "advanced plastic surgery" + 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, diff --git a/code/modules/surgery/revival.dm b/code/modules/surgery/revival.dm index f7e9fcce390e9..0d920b80c8135 100644 --- a/code/modules/surgery/revival.dm +++ b/code/modules/surgery/revival.dm @@ -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)) diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index cafa1161693a3..6d20a58b9e426 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,7 +65,7 @@ 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 diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm index c5f62883b5f11..571e8c422d98e 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") @@ -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 @@ -294,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. @@ -473,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 @@ -535,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) @@ -559,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") @@ -597,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/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 0ea9c45d310b8..4749ef278725e 100644 --- a/code/modules/tgui_input/alert.dm +++ b/code/modules/tgui_input/alert.dm @@ -77,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 9204e67ba3607..53b264038dc20 100644 --- a/code/modules/tgui_input/checkboxes.dm +++ b/code/modules/tgui_input/checkboxes.dm @@ -70,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 a58b96b06e38b..174f16fc7b57c 100644 --- a/code/modules/tgui_input/list.dm +++ b/code/modules/tgui_input/list.dm @@ -29,6 +29,9 @@ 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) @@ -62,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 @@ -81,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 e0a3f1951e5a7..68998acb0331f 100644 --- a/code/modules/tgui_input/number.dm +++ b/code/modules/tgui_input/number.dm @@ -92,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/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 f78ededab5d96..f97e0326d58ef 100644 --- a/code/modules/tgui_input/text.dm +++ b/code/modules/tgui_input/text.dm @@ -90,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 index 2d10b4ada5d2a..1cbbbdeb24b57 100644 --- a/code/modules/transport/_transport_machinery.dm +++ b/code/modules/transport/_transport_machinery.dm @@ -114,7 +114,7 @@ SIGNAL_HANDLER INVOKE_ASYNC(src, PROC_REF(try_fix_machine), source, user, tool) - return COMPONENT_BLOCK_TOOL_ATTACK + 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) diff --git a/code/modules/transport/elevator/elev_indicator.dm b/code/modules/transport/elevator/elev_indicator.dm index cf9fa46e96328..9751b44e0ff12 100644 --- a/code/modules/transport/elevator/elev_indicator.dm +++ b/code/modules/transport/elevator/elev_indicator.dm @@ -17,7 +17,6 @@ light_range = 1 light_power = 1 light_color = COLOR_DISPLAY_BLUE - luminosity = 1 maptext_x = 18 maptext_y = 20 diff --git a/code/modules/transport/tram/tram_controller.dm b/code/modules/transport/tram/tram_controller.dm index c20fb1bfef4ac..5665755520e66 100644 --- a/code/modules/transport/tram/tram_controller.dm +++ b/code/modules/transport/tram/tram_controller.dm @@ -128,6 +128,7 @@ /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) @@ -528,6 +529,7 @@ 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. @@ -554,8 +556,8 @@ 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() - tram_registration.collisions += 1 +/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() @@ -686,8 +688,8 @@ integrity_failure = 0.25 layer = SIGN_LAYER req_access = list(ACCESS_TCOMMS) - idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 4.8 - active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 4.8 + 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 @@ -697,7 +699,7 @@ /obj/machinery/transport/tram_controller/hilbert configured_transport_id = HILBERT_LINE_1 - flags_1 = NODECONSTRUCT_1 + obj_flags = parent_type::obj_flags | NO_DECONSTRUCTION /obj/machinery/transport/tram_controller/Initialize(mapload) . = ..() @@ -742,6 +744,9 @@ 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"].") @@ -848,7 +853,7 @@ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN /obj/machinery/transport/tram_controller/deconstruct(disassembled = TRUE) - if(flags_1 & NODECONSTRUCT_1) + if(obj_flags & NO_DECONSTRUCTION) return var/turf/drop_location = find_obstruction_free_location(1, src) diff --git a/code/modules/transport/tram/tram_controls.dm b/code/modules/transport/tram/tram_controls.dm index 2ecdad304bbc6..db8fe767155d2 100644 --- a/code/modules/transport/tram/tram_controls.dm +++ b/code/modules/transport/tram/tram_controls.dm @@ -9,6 +9,8 @@ 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 @@ -64,6 +66,9 @@ 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 * diff --git a/code/modules/transport/tram/tram_displays.dm b/code/modules/transport/tram/tram_displays.dm index 908651f3b1122..0e36295f97ec7 100644 --- a/code/modules/transport/tram/tram_displays.dm +++ b/code/modules/transport/tram/tram_displays.dm @@ -82,7 +82,7 @@ . += span_notice("It is secured to the tram wall with [EXAMINE_HINT("bolts.")]") /obj/machinery/transport/destination_sign/deconstruct(disassembled = TRUE) - if(flags_1 & NODECONSTRUCT_1) + if(obj_flags & NO_DECONSTRUCTION) return if(disassembled) new /obj/item/wallframe/indicator_display(drop_location()) diff --git a/code/modules/transport/tram/tram_floors.dm b/code/modules/transport/tram/tram_floors.dm index 2afb59f9b4a6e..1e1fad836c3b2 100644 --- a/code/modules/transport/tram/tram_floors.dm +++ b/code/modules/transport/tram/tram_floors.dm @@ -135,6 +135,9 @@ 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" @@ -239,7 +242,7 @@ secured = TRUE to_chat(user, span_notice("The tile is securely screwed in place.")) - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/structure/thermoplastic/crowbar_act_secondary(mob/living/user, obj/item/tool) . = ..() @@ -257,12 +260,12 @@ user.put_in_hands(pulled_tile) qdel(src) - return TOOL_ACT_TOOLTYPE_SUCCESS + 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 TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS if(!tool.tool_start_check(user, amount = 0)) return FALSE to_chat(user, span_notice("You begin repairing [src]...")) @@ -271,7 +274,7 @@ atom_integrity = max_integrity to_chat(user, span_notice("You repair [src].")) update_appearance() - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/item/stack/thermoplastic name = "thermoplastic tram tile" 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 index 08e127bc6b976..4176117d8b2f8 100644 --- a/code/modules/transport/tram/tram_remote.dm +++ b/code/modules/transport/tram/tram_remote.dm @@ -3,7 +3,7 @@ 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/device.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 @@ -108,6 +108,10 @@ 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) diff --git a/code/modules/transport/tram/tram_signals.dm b/code/modules/transport/tram/tram_signals.dm index 9983b32fe3312..101ae1027a306 100644 --- a/code/modules/transport/tram/tram_signals.dm +++ b/code/modules/transport/tram/tram_signals.dm @@ -5,21 +5,20 @@ icon = 'icons/obj/tram/crossing_signal.dmi' icon_state = "crossing-inbound" base_icon_state = "crossing-inbound" - plane = GAME_PLANE_UPPER 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 * 2.4 - active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.48 + 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/transport + subsystem_type = /datum/controller/subsystem/processing/fastprocess light_color = LIGHT_COLOR_BABY_BLUE - luminosity = 1 /// green, amber, or red for tram, blue if it's emag, tram missing, etc. var/signal_state = XING_STATE_MALF /// the sensor we use @@ -73,17 +72,15 @@ desc = "Indicates to pedestrians if it's safe to cross the tracks." icon = 'icons/obj/tram/crossing_signal.dmi' icon_state = "crossing-inbound" - plane = GAME_PLANE_UPPER + layer = TRAM_SIGNAL_LAYER max_integrity = 250 integrity_failure = 0.25 - idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 2.4 - active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.74 + 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 - luminosity = 1 var/sign_dir = INBOUND /obj/machinery/static_signal/northwest @@ -175,6 +172,8 @@ /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) @@ -311,13 +310,14 @@ * Returns whether we are still processing. */ /obj/machinery/transport/crossing_signal/proc/update_operating() - use_power(idle_power_usage) 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() @@ -331,8 +331,6 @@ set_signal_state(XING_STATE_MALF, force = !is_operational) return PROCESS_KILL - use_power(active_power_usage) - var/obj/structure/transport/linear/tram_part = tram.return_closest_platform_to(src) if(QDELETED(tram_part)) @@ -502,7 +500,7 @@ 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 = 0 + use_power = NO_POWER_USE circuit = /obj/item/circuitboard/machine/guideway_sensor /// Sensors work in a married pair var/datum/weakref/paired_sensor diff --git a/code/modules/transport/tram/tram_structures.dm b/code/modules/transport/tram/tram_structures.dm index c6291f775b3f7..ccf6a99d92967 100644 --- a/code/modules/transport/tram/tram_structures.dm +++ b/code/modules/transport/tram/tram_structures.dm @@ -153,7 +153,7 @@ /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 TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS if(!tool.tool_start_check(user, amount = 0)) return FALSE to_chat(user, span_notice("You begin repairing [src]...")) @@ -161,7 +161,7 @@ atom_integrity = max_integrity to_chat(user, span_notice("You repair [src].")) update_appearance() - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/structure/tram/attackby_secondary(obj/item/tool, mob/user, params) switch(state) @@ -212,7 +212,7 @@ return ..() /obj/structure/tram/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) + if(!(obj_flags & NO_DECONSTRUCTION)) if(disassembled) new girder_type(loc) if(mineral_amount) @@ -422,7 +422,7 @@ name = "bamboo tram" desc = "A tram with a bamboo framing." icon = 'icons/turf/walls/bamboo_wall.dmi' - icon_state = "wall-0" + 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 @@ -596,7 +596,7 @@ deploy_spoiler() update_appearance() - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS to_chat(user, span_notice("You begin repairing [src]...")) if(!tool.use_tool(src, user, 4 SECONDS, volume = 50)) @@ -604,7 +604,7 @@ atom_integrity = max_integrity to_chat(user, span_notice("You repair [src].")) update_appearance() - return TOOL_ACT_TOOLTYPE_SUCCESS + return ITEM_INTERACT_SUCCESS /obj/structure/tram/spoiler/update_overlays() . = ..() diff --git a/code/modules/transport/transport_module.dm b/code/modules/transport/transport_module.dm index 56aa52beebc90..7a57529b8ff22 100644 --- a/code/modules/transport/transport_module.dm +++ b/code/modules/transport/transport_module.dm @@ -424,6 +424,7 @@ 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") @@ -452,17 +453,20 @@ 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) - if(istype(transport_controller_datum, /datum/transport_controller/linear/tram)) - SSpersistence.tram_hits_this_round++ - SSblackbox.record_feedback("amount", "tram_collision", 1) - var/datum/transport_controller/linear/tram/tram_controller = transport_controller_datum - tram_controller.register_collision() + 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. @@ -927,8 +931,6 @@ addtimer(CALLBACK(src, PROC_REF(clear_turfs), turfs, iterations), 1) /obj/structure/transport/linear/tram/proc/estop_throw(throw_direction) - if(prob(50)) - do_sparks(2, FALSE, src) 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) 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 fee6101045fa5..61bc9ec6d4e6e 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -169,6 +169,7 @@ #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" @@ -194,6 +195,7 @@ #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" @@ -222,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" @@ -255,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/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 a7223319f8e62..0ba46441957b3 100644 --- a/code/modules/unit_tests/barsigns.dm +++ b/code/modules/unit_tests/barsigns.dm @@ -14,7 +14,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) && !(sign.icon in barsign_icon_states_ss220)) // BANDASTATION EDIT Barsigns + if(!(sign.icon_state in barsign_icon_states) && !(sign.icon_state in barsign_icon_states_ss220)) // BANDASTATION EDIT Barsigns TEST_FAIL("Icon state for [sign_type] does not exist in [barsign_icon].") /** @@ -37,3 +37,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/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/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/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/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/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 4ac5bce8d3d3a..b55136cfacebe 100644 --- a/code/modules/unit_tests/heretic_rituals.dm +++ b/code/modules/unit_tests/heretic_rituals.dm @@ -66,7 +66,10 @@ 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. @@ -106,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/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 index 0c964f9a0432c..4c8c7e4e03124 100644 --- a/code/modules/unit_tests/ling_decap.dm +++ b/code/modules/unit_tests/ling_decap.dm @@ -9,11 +9,12 @@ 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.") - TEST_ASSERT_NULL(noggin.brainmob.mind, "Changeling's mind was moved to their head after decapitation, but it should have remained in their body.") + 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 = noggin.brain + var/obj/item/organ/internal/brain/oldbrain = locate(/obj/item/organ/internal/brain) in noggin noggin.drop_organs() - TEST_ASSERT_NULL(noggin.brain, "Changeling's head failed to drop its brain.") + 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.") @@ -33,9 +34,10 @@ var/obj/item/bodypart/head/noggin = normal_guy.get_bodypart(BODY_ZONE_HEAD) noggin.dismember() - TEST_ASSERT_EQUAL(noggin.brainmob.mind, my_guys_mind, "Dummy's mind was not moved to their head after decapitation.") + 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 = noggin.brain + 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.") 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_damage.dm b/code/modules/unit_tests/mob_damage.dm index 50046141a88b7..c27bc31ffe0f2 100644 --- a/code/modules/unit_tests/mob_damage.dm +++ b/code/modules/unit_tests/mob_damage.dm @@ -28,9 +28,6 @@ // Testing whether or not TRAIT_TOXINLOVER and TRAIT_TOXIMMUNE are working as intended test_toxintraits(dummy) - // Testing whether or not TRAIT_NOCLONELOSS is working as intended - test_nocloneloss(dummy) - // Testing the proc ordered_healing() test_ordered_healing(dummy) @@ -100,9 +97,6 @@ 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 & CLONELOSS) - TEST_ASSERT_EQUAL(testing_mob.getCloneLoss(), amount, \ - "[testing_mob] should have [amount] clone damage, instead they have [testing_mob.getCloneLoss()]!") 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()]!") @@ -136,10 +130,6 @@ 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 & CLONELOSS) - damage_returned = testing_mob.adjustCloneLoss(amount, updating_health = FALSE, forced = forced, required_biotype = biotypes) - TEST_ASSERT_EQUAL(damage_returned, expected, \ - "adjustCloneLoss() 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, \ @@ -177,10 +167,6 @@ 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 & CLONELOSS) - damage_returned = testing_mob.setCloneLoss(amount, updating_health = FALSE, forced = forced, required_biotype = biotypes) - TEST_ASSERT_EQUAL(damage_returned, expected, \ - "setCloneLoss() 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, \ @@ -331,15 +317,15 @@ 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|CLONELOSS|STAMINALOSS)) + 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|CLONELOSS|STAMINALOSS, biotypes = MOB_ORGANIC)) + 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|CLONELOSS|STAMINALOSS, biotypes = MOB_MINERAL)) + 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 @@ -347,15 +333,15 @@ // 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|CLONELOSS|STAMINALOSS, biotypes = MOB_MINERAL)) + 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|CLONELOSS|STAMINALOSS, biotypes = MOB_MINERAL, forced = TRUE)) + 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|CLONELOSS|STAMINALOSS, biotypes = MOB_ORGANIC)) + 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 @@ -413,29 +399,6 @@ REMOVE_TRAIT(dummy, TRAIT_TOXINLOVER, TRAIT_SOURCE_UNIT_TESTS) REMOVE_TRAIT(dummy, TRAIT_TOXIMMUNE, TRAIT_SOURCE_UNIT_TESTS) -/// Testing cloneloss with TRAIT_NOCLONELOSS -/datum/unit_test/mob_damage/proc/test_nocloneloss(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_TRAIT_NOCLONELOSS is supposed to prevent cloneloss damage and healing. Let's make sure that's the case. - ADD_TRAIT(dummy, TRAIT_NOCLONELOSS, TRAIT_SOURCE_UNIT_TESTS) - // force some cloneloss here - dummy.setCloneLoss(2, updating_health = FALSE, forced = TRUE) - - // Try to take more cloneloss damage with TRAIT_NOCLONELOSS. It should not work. - if(!test_apply_damage(dummy, 2, expected = 0, amount_after = dummy.getCloneLoss(), included_types = CLONELOSS)) - TEST_FAIL("ABOVE FAILURE: failed test_nocloneloss! mob took cloneloss damage with TRAIT_NOCLONELOSS") - - // Healing the cloneloss should not work either, unless we force it - if(!test_apply_damage(dummy, -2, expected = 0, amount_after = dummy.getCloneLoss(), included_types = CLONELOSS)) - TEST_FAIL("ABOVE FAILURE: failed test_nocloneloss! mob healed cloneloss damage with TRAIT_NOCLONELOSS") - // so let's force it - if(!test_apply_damage(dummy, -2, expected = 2, amount_after = dummy.getCloneLoss()-2, included_types = CLONELOSS, forced = TRUE)) - TEST_FAIL("ABOVE FAILURE: failed test_nocloneloss! mob could not heal cloneloss damage with forced = TRUE and TRAIT_NOCLONELOSS") - - REMOVE_TRAIT(dummy, TRAIT_NOCLONELOSS, 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 @@ -478,7 +441,7 @@ 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, CLONE = 1, STAMINA = 1, OXY = 1) + gusgus.damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 1, OXY = 1) // tank mouse gusgus.maxHealth = 200 @@ -492,19 +455,16 @@ * 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 * 5) + * * 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 & CLONELOSS) - TEST_ASSERT_EQUAL(testing_mob.getCloneLoss(), 0, \ - "[testing_mob] should have [0] clone damage, instead they have [testing_mob.getCloneLoss()]!") if(included_types & BRUTELOSS) - TEST_ASSERT_EQUAL(round(testing_mob.getBruteLoss(), 1), expected || amount * 5, \ - "[testing_mob] should have [expected || amount * 5] brute damage, instead they have [testing_mob.getBruteLoss()]!") + 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()]!") @@ -527,18 +487,18 @@ 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 10 brute, 2 staminaloss) + // 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 5 brute, 1 staminaloss) + // 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 5 brute damage remaining) - if(!apply_damage(gusgus, -11, expected = 5, included_types = TOXLOSS)) + // 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)) 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 4ba51e0870c00..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]) 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/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_cyberpolice.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_glitch.png similarity index 100% rename from code/modules/unit_tests/screenshots/screenshot_antag_icons_cyberpolice.png rename to code/modules/unit_tests/screenshots/screenshot_antag_icons_glitch.png 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_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_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/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index c25aa2cd2e58a..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, @@ -51,31 +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/polarbear, /mob/living/simple_animal/hostile/asteroid/polarbear/lesser, /mob/living/simple_animal/hostile/asteroid/wolf, /mob/living/simple_animal/hostile/dark_wizard, - /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/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/megafauna, /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner, /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/doom, @@ -109,22 +81,8 @@ /mob/living/simple_animal/hostile/retaliate/goose, /mob/living/simple_animal/hostile/retaliate/goose/vomit, /mob/living/simple_animal/hostile/vatbeast, - /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/pet/gondola/virtual_domain, 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 9b5804e352062..3b598e9994200 100644 --- a/code/modules/unit_tests/spell_shapeshift.dm +++ b/code/modules/unit_tests/spell_shapeshift.dm @@ -87,7 +87,7 @@ 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. 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..ceb82e98c0d6b --- /dev/null +++ b/code/modules/unit_tests/tail_wag.dm @@ -0,0 +1,90 @@ +/// 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 + SEND_SIGNAL(dummy, COMSIG_ORGAN_WAG_TAIL, TRUE) + if(!(dummy_tail.wag_flags & WAG_WAGGING)) + TEST_FAIL("Tail did not start wagging when it should have!") + + // stop wagging + SEND_SIGNAL(dummy, COMSIG_ORGAN_WAG_TAIL, FALSE) + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("Tail did not stop wagging when it should have!") + + // TESTING WAG_ABLE FLAG + + // flip the wag flag to unwaggable + dummy_tail.wag_flags &= ~WAG_ABLE + + // try to wag it again + SEND_SIGNAL(dummy, COMSIG_ORGAN_WAG_TAIL, TRUE) + 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 + SEND_SIGNAL(dummy, COMSIG_ORGAN_WAG_TAIL, TRUE) + if(!(dummy_tail.wag_flags & WAG_WAGGING)) + TEST_FAIL("Tail did not start wagging when it should have!") + + // TESTING STOP_AFTER + + // stop wagging + SEND_SIGNAL(dummy, COMSIG_ORGAN_WAG_TAIL, FALSE) + 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 + SEND_SIGNAL(dummy, COMSIG_ORGAN_WAG_TAIL, TRUE, 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 + SEND_SIGNAL(dummy, COMSIG_ORGAN_WAG_TAIL, TRUE) + 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) + SEND_SIGNAL(dummy, COMSIG_ORGAN_WAG_TAIL, TRUE) + 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 + SEND_SIGNAL(dummy, COMSIG_ORGAN_WAG_TAIL, TRUE) + if(dummy_tail.wag_flags & WAG_WAGGING) + TEST_FAIL("A dead mob was able to wag their tail!") 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..32783504fe871 100644 --- a/code/modules/uplink/uplink_items.dm +++ b/code/modules/uplink/uplink_items.dm @@ -137,6 +137,10 @@ to_chat(user, span_boldnotice("[A] materializes onto the floor!")) return A +///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" weight = -1 diff --git a/code/modules/uplink/uplink_items/badass.dm b/code/modules/uplink/uplink_items/badass.dm index 67676edcd7da9..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,7 +50,7 @@ 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 restricted = TRUE illegal_tech = FALSE diff --git a/code/modules/uplink/uplink_items/bundle.dm b/code/modules/uplink/uplink_items/bundle.dm index a930784fe1462..f236aa4da253a 100644 --- a/code/modules/uplink/uplink_items/bundle.dm +++ b/code/modules/uplink/uplink_items/bundle.dm @@ -146,13 +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 + 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 diff --git a/code/modules/uplink/uplink_items/clownops.dm b/code/modules/uplink/uplink_items/clownops.dm index 88cd9b6464f63..852676dbcbb74 100644 --- a/code/modules/uplink/uplink_items/clownops.dm +++ b/code/modules/uplink/uplink_items/clownops.dm @@ -127,7 +127,7 @@ /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 recieve a bag with a bargain-bin gun, ammunition, and some miscellaneous supplies." + 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 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 f1788c6e1dec3..970741876bb7a 100644 --- a/code/modules/uplink/uplink_items/dangerous.dm +++ b/code/modules/uplink/uplink_items/dangerous.dm @@ -61,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) @@ -82,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) @@ -96,3 +96,10 @@ 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/job.dm b/code/modules/uplink/uplink_items/job.dm index 01bef40289eb4..8793bc0fbe8e3 100644 --- a/code/modules/uplink/uplink_items/job.dm +++ b/code/modules/uplink/uplink_items/job.dm @@ -146,6 +146,13 @@ 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. \ @@ -355,10 +362,21 @@ /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 recieve a bag with a bargain-bin gun, ammunition, and some miscellaneous supplies." + 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 b872fc61c3a85..d8bead5da6781 100644 --- a/code/modules/uplink/uplink_items/nukeops.dm +++ b/code/modules/uplink/uplink_items/nukeops.dm @@ -47,12 +47,12 @@ /datum/uplink_item/weapon_kits/core name = "Core Equipment Box (Essential)" - desc = "This box contains an airlock authentification override card, a C-4 explosive charge, a freedom implant and a stimpack injector. \ + 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 = 14 //freedom 5, doormag 3, c-4 1, stimpack 5 + 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 @@ -172,8 +172,8 @@ /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 @@ -354,8 +354,8 @@ item = /obj/item/ammo_box/magazine/sniper_rounds/marksman /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." @@ -595,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 @@ -704,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 diff --git a/code/modules/uplink/uplink_items/stealthy.dm b/code/modules/uplink/uplink_items/stealthy.dm index 2f205a9d0bd69..793120fe56f34 100644 --- a/code/modules/uplink/uplink_items/stealthy.dm +++ b/code/modules/uplink/uplink_items/stealthy.dm @@ -76,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) @@ -99,6 +99,7 @@ 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 = 12 + cost = 7 surplus = 50 - purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) + 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 b72449de091c3..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) diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm index 6b019cac27059..d5a3d7bdc7d80 100644 --- a/code/modules/vehicles/cars/clowncar.dm +++ b/code/modules/vehicles/cars/clowncar.dm @@ -13,6 +13,7 @@ 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 diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index f586a9656e82b..dc7304a138807 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) @@ -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 @@ -267,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) @@ -396,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) @@ -467,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...") @@ -521,7 +520,8 @@ 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) @@ -572,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) @@ -588,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) @@ -721,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 ..() @@ -745,7 +747,7 @@ ///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 diff --git a/code/modules/vehicles/mecha/combat/gygax.dm b/code/modules/vehicles/mecha/combat/gygax.dm index 223ab66ca31d8..82fd77f22890d 100644 --- a/code/modules/vehicles/mecha/combat/gygax.dm +++ b/code/modules/vehicles/mecha/combat/gygax.dm @@ -47,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 2fe8da4bdc73c..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( @@ -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 1851e6b39b19a..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 @@ -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) @@ -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 aea1a21c0829e..d650ce66f2ead 100644 --- a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm +++ b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm @@ -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 de96f3ad5f657..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. @@ -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 a879341629650..335e8bc5a3e12 100644 --- a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm +++ b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm @@ -162,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 @@ -540,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 43301c19605e3..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 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 3df6ba94992d8..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 @@ -115,18 +115,15 @@ return ..() /obj/vehicle/sealed/mecha/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit) //wrapper - . = ..() - if(. != BULLET_ACT_HIT) - return . - //allows bullets to hit the pilot of open-canopy mechs - if(!enclosed \ + if(!(mecha_flags & IS_ENCLOSED) \ && LAZYLEN(occupants) \ && !(mecha_flags & SILICON_PILOT) \ && (def_zone == BODY_ZONE_HEAD || def_zone == BODY_ZONE_CHEST)) - 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 + 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 @@ -138,6 +135,7 @@ armour_penetration = hitting_projectile.armour_penetration, ), def_zone) + /obj/vehicle/sealed/mecha/ex_act(severity, target) log_message("Affected by explosion of severity: [severity].", LOG_MECHA, color="red") return ..() @@ -199,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) @@ -227,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) @@ -346,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") @@ -524,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_mob_interaction.dm b/code/modules/vehicles/mecha/mecha_mob_interaction.dm index 07b13a27ec054..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 @@ -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 e8c43ae48f94a..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())) @@ -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 76d8b4613fac8..1bf5b8674a47d 100644 --- a/code/modules/vehicles/mecha/mecha_ui.dm +++ b/code/modules/vehicles/mecha/mecha_ui.dm @@ -97,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()) @@ -190,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) 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 d2b13d48d26ab..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. 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 5afcd58c0c0ca..ed9884a9ea3cb 100644 --- a/code/modules/vehicles/vehicle_actions.dm +++ b/code/modules/vehicles/vehicle_actions.dm @@ -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() 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..41ed5a0445782 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -301,7 +301,7 @@ /obj/machinery/vending/deconstruct(disassembled = TRUE) if(refill_canister) return ..() - if(!(flags_1 & NODECONSTRUCT_1)) //the non constructable vendors drop metal instead of a machine frame. + if(!(obj_flags & NO_DECONSTRUCTION)) //the non constructable vendors drop metal instead of a machine frame. new /obj/item/stack/sheet/iron(loc, 3) qdel(src) @@ -620,7 +620,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 +746,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 +1015,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 +1039,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 +1066,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 @@ -1208,11 +1206,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 +1590,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 +1600,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 +1615,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 +1631,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) @@ -1670,9 +1670,10 @@ * 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 +1682,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 02daa0ce7e7dd..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( 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/games.dm b/code/modules/vending/games.dm index e51205c00e4a0..33fefd08d2e79 100644 --- a/code/modules/vending/games.dm +++ b/code/modules/vending/games.dm @@ -52,11 +52,12 @@ /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, - /obj/item/skillchip/master_angler = 2, ), ), list( @@ -80,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/security.dm b/code/modules/vending/security.dm index b54edffe4c044..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,7 +27,6 @@ /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, @@ -46,4 +46,5 @@ 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 d7de76e80fd1d..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, @@ -170,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, @@ -636,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/speech.dm b/code/modules/wiremod/components/action/speech.dm index 4bf735cf82bf0..0e2936bcfbfdb 100644 --- a/code/modules/wiremod/components/action/speech.dm +++ b/code/modules/wiremod/components/action/speech.dm @@ -31,5 +31,5 @@ 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/object_overlay.dm b/code/modules/wiremod/components/bci/hud/object_overlay.dm index ad32a5408cd87..28e54cb7ee277 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) @@ -121,15 +122,11 @@ ) 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/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/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..3fb736540ec18 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() . = ..() @@ -323,11 +319,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..dfd9845bc05b1 100644 --- a/code/modules/wiremod/shell/bot.dm +++ b/code/modules/wiremod/shell/bot.dm @@ -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..f41d4fa8bf191 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) diff --git a/code/modules/wiremod/shell/controller.dm b/code/modules/wiremod/shell/controller.dm index 7040e554a0a4a..3092d3315f165 100644 --- a/code/modules/wiremod/shell/controller.dm +++ b/code/modules/wiremod/shell/controller.dm @@ -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/keyboard.dm b/code/modules/wiremod/shell/keyboard.dm index 4231a6dc814d4..05b9ded074baa 100644 --- a/code/modules/wiremod/shell/keyboard.dm +++ b/code/modules/wiremod/shell/keyboard.dm @@ -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..20eb596eb7267 100644 --- a/code/modules/wiremod/shell/moneybot.dm +++ b/code/modules/wiremod/shell/moneybot.dm @@ -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/zombie/items.dm b/code/modules/zombie/items.dm index dca26d366d876..1d5caf8e9093b 100644 --- a/code/modules/zombie/items.dm +++ b/code/modules/zombie/items.dm @@ -79,7 +79,6 @@ 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.adjustCloneLoss(-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) diff --git a/code/modules/zombie/organs.dm b/code/modules/zombie/organs.dm index 43a6130c77336..2ed2bf541d88c 100644 --- a/code/modules/zombie/organs.dm +++ b/code/modules/zombie/organs.dm @@ -23,31 +23,31 @@ 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_insert(mob/living/carbon/organ_owner, special) +/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_remove(mob/living/carbon/organ_owner, special) +/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 - qdel(src) // Congrats you somehow died so hard you stopped being a zombie + 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, span_warning("Inside the head is a disgusting black \ diff --git a/config/config.txt b/config/config.txt index 3190a7ae9ce25..35e6054e35735 100644 --- a/config/config.txt +++ b/config/config.txt @@ -411,8 +411,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. diff --git a/config/game_options.txt b/config/game_options.txt index 8339f3905cc48..fb0c58b869e85 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -531,3 +531,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/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/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-79356.yml b/html/changelogs/AutoChangeLog-pr-79356.yml deleted file mode 100644 index abf7b7d66c923..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79356.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "nikothedude" -delete-after: True -changes: - - qol: "Character preferences now have descriptions as tooltips - hover over their names to see them" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79389.yml b/html/changelogs/AutoChangeLog-pr-79389.yml deleted file mode 100644 index 68402f1d51e96..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79389.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "san7890" -delete-after: True -changes: - - 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." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79405.yml b/html/changelogs/AutoChangeLog-pr-79405.yml deleted file mode 100644 index d84185f4c0f07..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79405.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothymtorres" -delete-after: True -changes: - - rscdel: "Remove duplicate machinery from Metastation: morgue disposal bin, medical break room air alarm, and IV drip in permabrig medroom" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79423.yml b/html/changelogs/AutoChangeLog-pr-79423.yml deleted file mode 100644 index 8a87d024187e6..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79423.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "jlsnow301" -delete-after: True -changes: - - rscadd: "Added a new fishing map to bitrunning." - - rscadd: "You are no longer limited to pina coladas on the beach bar domain. Cheers!" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79432.yml b/html/changelogs/AutoChangeLog-pr-79432.yml deleted file mode 100644 index a4e1736610690..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79432.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Jacquerel" -delete-after: True -changes: - - 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." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79452.yml b/html/changelogs/AutoChangeLog-pr-79452.yml deleted file mode 100644 index 2fbdec7e2413b..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79452.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "jlsnow301" -delete-after: True -changes: - - bugfix: "Added feedback for both extractor and extractee while using fulton extraction packs." - - qol: "Extraction packs now have better exam text." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79456.yml b/html/changelogs/AutoChangeLog-pr-79456.yml deleted file mode 100644 index 4adc8c44877cc..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79456.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "vinylspiders" -delete-after: True -changes: - - bugfix: "Softspoken quirk will no longer be applied to sign language" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79458.yml b/html/changelogs/AutoChangeLog-pr-79458.yml deleted file mode 100644 index 5ee16c407ca80..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79458.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "rageguy505" -delete-after: True -changes: - - rscdel: "Removed a extra coffee pot in birdshot's security breakroom" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79459.yml b/html/changelogs/AutoChangeLog-pr-79459.yml deleted file mode 100644 index a014cab31a2be..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79459.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Zergspower" -delete-after: True -changes: - - bugfix: "Bounties - Virus bounties work once more" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79461.yml b/html/changelogs/AutoChangeLog-pr-79461.yml deleted file mode 100644 index a83f49003aea3..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79461.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - admin: "Admins without `R_POLL` no longer have access to \"Server Poll Management\", not that they could have used it anyways." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79469.yml b/html/changelogs/AutoChangeLog-pr-79469.yml deleted file mode 100644 index 193e762b62f6c..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79469.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "lizardqueenlexi" -delete-after: True -changes: - - refactor: "Shades now use the basic mob framework. Please report any bugs." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79471.yml b/html/changelogs/AutoChangeLog-pr-79471.yml deleted file mode 100644 index b9a871d1deef7..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79471.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "jlsnow301" -delete-after: True -changes: - - bugfix: "Fixes midround selection for observers" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-79495.yml b/html/changelogs/AutoChangeLog-pr-79495.yml deleted file mode 100644 index 7f51234606b87..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-79495.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Tattle" -delete-after: True -changes: - - bugfix: "you should now be able to scrub through the library without lagging the server" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81170.yml b/html/changelogs/AutoChangeLog-pr-81170.yml new file mode 100644 index 0000000000000..8cefce36647c7 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81170.yml @@ -0,0 +1,5 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Fixed text effects for runechat messages (the stuff enclosed in +, | and _ characters)." + - spellcheck: "Improved the tip for say/text effects." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81281.yml b/html/changelogs/AutoChangeLog-pr-81281.yml new file mode 100644 index 0000000000000..1dd471b0e3fba --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81281.yml @@ -0,0 +1,5 @@ +author: "EEASAS" +delete-after: True +changes: + - bugfix: "Fixed some things in Ice Box's gas station ruin" + - rscadd: "Adds some things in Ice Box's gas station ruin" \ No newline at end of file diff --git a/html/changelogs/archive/2023-11.yml b/html/changelogs/archive/2023-11.yml index 5c5b05f117fdd..9240318c4d340 100644 --- a/html/changelogs/archive/2023-11.yml +++ b/html/changelogs/archive/2023-11.yml @@ -172,3 +172,980 @@ - 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..8001507bdbd48 --- /dev/null +++ b/html/changelogs/archive/2024-02.yml @@ -0,0 +1,193 @@ +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. 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 cbfe463efa516..66098018f7598 100644 Binary files a/icons/area/areas_station.dmi and b/icons/area/areas_station.dmi differ diff --git a/icons/effects/bitrunning.dmi b/icons/effects/bitrunning.dmi index bfdc7c63436c2..8efa429389c3a 100644 Binary files a/icons/effects/bitrunning.dmi 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/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 bbde393b5ef83..072fbe0255848 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/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/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/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/screen_alert.dmi b/icons/hud/screen_alert.dmi index 593a3878c2e6f..289d02da46ada 100644 Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.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_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/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 2c6bf51072438..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 9ed1f1652359a..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/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/utility.dmi b/icons/mob/clothing/head/utility.dmi index ada1b90c4b0be..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..375de24626918 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..fcfdb55f52250 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/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 a6f25d91c59bf..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/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/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/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/huds/hud.dmi b/icons/mob/huds/hud.dmi index d71ba4b0940a6..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 2150b3c2c810f..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 886f0bf793b6b..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/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/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/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/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/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 3e29ac726f995..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 1a640935e0c6a..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/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/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/lavaland/lavaland_monsters.dmi b/icons/mob/simple/lavaland/lavaland_monsters.dmi index f68e3db4a6cb9..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/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/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 19e2e68c4f8f3..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/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 a5e34e9cc27cb..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 fdba37254eb42..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/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/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/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/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/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 dc507017cd25d..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 b98af70ed046e..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/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/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 fe74b6c11c5c7..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/fishing.dmi b/icons/obj/fishing.dmi index f7ab9fc1ad9c2..8e8be783fb7a9 100644 Binary files a/icons/obj/fishing.dmi and b/icons/obj/fishing.dmi differ diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi index 46d7459e0b0b7..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 7cc7bf0fcf9c5..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/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/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 6a929f211b8dc..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/bitrunning.dmi b/icons/obj/machines/bitrunning.dmi index b3f8ad63a6c99..d61e910d195be 100644 Binary files a/icons/obj/machines/bitrunning.dmi and b/icons/obj/machines/bitrunning.dmi differ diff --git a/icons/obj/machines/computer.dmi b/icons/obj/machines/computer.dmi index 9cb0dda4967fd..cba0069cf6f38 100644 Binary files a/icons/obj/machines/computer.dmi and b/icons/obj/machines/computer.dmi 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/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/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/wallmounts.dmi b/icons/obj/machines/wallmounts.dmi index c5f2254c4bb15..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/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/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 d4c603834d21b..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 ee275066d1653..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 b43be78e717b0..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/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/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/service/bureaucracy.dmi b/icons/obj/service/bureaucracy.dmi index d8190ba2e723f..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/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_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 f4489b01fa053..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 db6f93878d06f..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/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 9ece919c21332..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/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 c06fe4d8112b6..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/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 a41aa161d0f10..40420db37050c 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/tram/tram_controllers.dmi b/icons/obj/tram/tram_controllers.dmi index 93462e0b41e0b..aea1f691af241 100644 Binary files a/icons/obj/tram/tram_controllers.dmi and b/icons/obj/tram/tram_controllers.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/magic.dmi b/icons/obj/weapons/guns/magic.dmi index 7cab0cdfc2592..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 05ad142ff58d3..a4c321a05435d 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/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/floors.dmi b/icons/turf/floors.dmi index 9dca06d4153ca..6fc1178a6b464 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/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.dmi b/icons/turf/walls/meat.dmi deleted file mode 100644 index fbbeb8a9c3cac..0000000000000 Binary files a/icons/turf/walls/meat.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/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/attributions.txt b/sound/attributions.txt index e7ceb44f08b0e..82486a5735da0 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -113,3 +113,27 @@ laser2.ogg is adapted with 3 SFX made by junggle (CC 4), inferno (CC Sampling+), 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/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/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/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/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/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/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/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/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/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/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/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 a0f75a9fad467..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! @@ -227,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. @@ -252,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. @@ -270,3 +271,4 @@ You can use a machine in the vault to deposit cash or rob Cargo's department fun 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. +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 65ce2b7836aaf..b0c149704f7cf 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -110,6 +110,7 @@ #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\injection.dm" #include "code\__DEFINES\input.dm" @@ -128,9 +129,9 @@ #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" @@ -143,6 +144,7 @@ #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" @@ -155,8 +157,11 @@ #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" @@ -232,7 +237,6 @@ #include "code\__DEFINES\tools.dm" #include "code\__DEFINES\toys.dm" #include "code\__DEFINES\trader.dm" -#include "code\__DEFINES\traits.dm" #include "code\__DEFINES\transport.dm" #include "code\__DEFINES\tts.dm" #include "code\__DEFINES\turbine_defines.dm" @@ -250,8 +254,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" @@ -363,6 +369,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" @@ -371,13 +378,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" @@ -457,6 +468,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" @@ -498,11 +510,13 @@ #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" @@ -514,15 +528,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" @@ -566,10 +584,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" @@ -635,14 +654,15 @@ #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" @@ -680,6 +700,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" @@ -687,6 +717,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" @@ -715,6 +755,8 @@ #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" @@ -724,12 +766,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" @@ -742,7 +784,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" @@ -751,6 +792,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" @@ -791,7 +833,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" @@ -799,6 +844,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" @@ -818,20 +864,22 @@ #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_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_targetting.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" @@ -842,6 +890,7 @@ #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" @@ -866,11 +915,11 @@ #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\targetting_datums\with_object.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" @@ -968,11 +1017,13 @@ #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" @@ -996,10 +1047,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" @@ -1012,6 +1066,7 @@ #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" @@ -1043,13 +1098,17 @@ #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" @@ -1061,6 +1120,7 @@ #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" @@ -1073,6 +1133,7 @@ #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\punchcooldown.dm" @@ -1081,6 +1142,7 @@ #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" @@ -1094,6 +1156,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" @@ -1309,6 +1372,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" @@ -1317,6 +1381,7 @@ #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" @@ -1326,12 +1391,14 @@ #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" @@ -1345,9 +1412,11 @@ #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" @@ -1372,6 +1441,7 @@ #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\pet_bonus.dm" #include "code\datums\elements\plant_backfire.dm" @@ -1485,9 +1555,9 @@ #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" @@ -1496,7 +1566,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" @@ -1525,6 +1594,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" @@ -1557,6 +1627,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" @@ -1575,7 +1647,6 @@ #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" @@ -1617,6 +1688,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" @@ -1677,6 +1749,7 @@ #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" @@ -1695,6 +1768,7 @@ #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" @@ -1711,6 +1785,7 @@ #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" @@ -1719,6 +1794,7 @@ #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" @@ -1752,6 +1828,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" @@ -1767,6 +1844,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" @@ -1776,14 +1854,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" @@ -1819,21 +1894,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" @@ -2159,6 +2238,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" @@ -2197,7 +2277,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" @@ -2216,6 +2295,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" @@ -2247,7 +2328,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" @@ -2313,6 +2393,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" @@ -2348,10 +2429,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" @@ -2360,12 +2439,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" @@ -2428,7 +2511,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" @@ -2439,6 +2521,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" @@ -2463,6 +2546,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" @@ -2503,12 +2587,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" @@ -2574,6 +2660,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" @@ -2713,7 +2800,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" @@ -2741,6 +2827,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" @@ -2918,29 +3005,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" @@ -2969,10 +3061,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" @@ -2991,7 +3088,7 @@ #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" @@ -3057,6 +3154,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" @@ -3113,7 +3214,6 @@ #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" @@ -3173,9 +3273,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" @@ -3291,13 +3391,18 @@ #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\virtual_elite_mob.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" @@ -3309,15 +3414,20 @@ #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\quantum_server.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\virtual_domain\safehouses.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" @@ -3332,6 +3442,7 @@ #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" @@ -3347,6 +3458,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" @@ -3466,7 +3578,6 @@ #include "code\modules\client\preferences\clothing.dm" #include "code\modules\client\preferences\darkened_flash.dm" #include "code\modules\client\preferences\food_allergy.dm" -#include "code\modules\client\preferences\fov_darkness.dm" #include "code\modules\client\preferences\fps.dm" #include "code\modules\client\preferences\gender.dm" #include "code\modules\client\preferences\ghost.dm" @@ -3531,6 +3642,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" @@ -3700,6 +3812,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" @@ -3953,7 +4066,6 @@ #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" @@ -4035,7 +4147,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" @@ -4058,8 +4169,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" @@ -4067,7 +4176,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" @@ -4090,6 +4198,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" @@ -4113,6 +4224,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" @@ -4124,6 +4238,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" @@ -4167,6 +4283,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" @@ -4187,6 +4304,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" @@ -4194,6 +4312,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" @@ -4220,6 +4339,7 @@ #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" @@ -4304,9 +4424,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" @@ -4344,7 +4468,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" @@ -4389,6 +4512,16 @@ #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" @@ -4429,6 +4562,22 @@ #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" @@ -4444,7 +4593,13 @@ #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" @@ -4472,6 +4627,10 @@ #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" @@ -4487,6 +4646,7 @@ #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" @@ -4500,14 +4660,33 @@ #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" @@ -4590,6 +4769,7 @@ #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" @@ -4606,6 +4786,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" @@ -4749,38 +4930,20 @@ #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\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\gondola.dm" #include "code\modules\mob\living\simple_animal\friendly\pet.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\dark_wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\hostile.dm" @@ -4788,10 +4951,7 @@ #include "code\modules\mob\living\simple_animal\hostile\mimic.dm" #include "code\modules\mob\living\simple_animal\hostile\ooze.dm" #include "code\modules\mob\living\simple_animal\hostile\vatbeast.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\jungle\_jungle_mobs.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\leaper.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" @@ -4803,7 +4963,6 @@ #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\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\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" @@ -4814,13 +4973,15 @@ #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\pandora.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\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" @@ -4865,6 +5026,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" @@ -4884,6 +5046,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" @@ -4893,6 +5056,7 @@ #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" @@ -4903,7 +5067,6 @@ #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" @@ -4918,16 +5081,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" @@ -5004,7 +5171,6 @@ #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" @@ -5016,6 +5182,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" @@ -5165,11 +5332,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" @@ -5191,10 +5358,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" @@ -5203,6 +5374,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" @@ -5270,15 +5442,16 @@ #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" @@ -5320,6 +5493,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" @@ -5397,6 +5571,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" @@ -5459,7 +5634,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" @@ -5484,6 +5658,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" @@ -5553,6 +5728,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" @@ -5640,6 +5816,7 @@ #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" @@ -5655,6 +5832,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" @@ -5753,10 +5931,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" @@ -5814,6 +5992,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" @@ -5863,6 +6042,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..1370aacc48a05 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,45 @@ "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" }, "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..67028168f8e86 --- /dev/null +++ b/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx @@ -0,0 +1,100 @@ +/** + * @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, + }), + ) + } + /> + + + + + + +
    + {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/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 index 2f7aea1ef947f..09a0f6ddee962 100644 --- a/tgui/packages/tgui/interfaces/NtosMafiaPanel.tsx +++ b/tgui/packages/tgui/interfaces/NtosMafiaPanel.tsx @@ -1,7 +1,7 @@ -import { MafiaPanelData } from './MafiaPanel'; import { NtosWindow } from '../layouts'; +import { MafiaPanelData } from './MafiaPanel'; -export const NtosMafiaPanel = (props, context) => { +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 fca2fb099e913..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, @@ -71,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, @@ -87,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) => { @@ -123,7 +134,7 @@ const ContactsScreen = (props: any, context: any) => { }; const openChatsArray = sortByUnreads(Object.values(saved_chats)).filter( - searchChatByName + searchChatByName, ); const filteredChatButtons = openChatsArray @@ -134,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) @@ -203,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)} /> @@ -262,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 ( @@ -273,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' : '' @@ -283,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 ( <> @@ -306,7 +318,8 @@ const SendToAllSection = (props, context) => { onClick={() => { act('PDA_sendEveryone', { message: message }); setmessage(''); - }}> + }} + > Send @@ -317,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} -