diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index 2f82e49462..2ead5ea068 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -81,6 +81,7 @@ #define SYNTH_ACTIVE_COOLING_MIN_ADJUSTMENT 5 //What is the minimum amount of temp you move towards the target point, even if it would be less with default calculations? #define SYNTH_INTEGRATION_COOLANT_PENALTY 0.4 //Integrating coolant is multiplied with this for calculation of impact on passive cooling. #define SYNTH_INTEGRATION_COOLANT_CAP 0.25 //Integrating coolant is capped at counting as current_blood * this number. This is so you can't just run on salglu or whatever. +#define SYNTH_COLD_OFFSET -125 //How much colder temps Synths can tolerate. Used in their species. #define BODYTEMP_NORMAL 310.15 //The natural temperature for a body #define BODYTEMP_AUTORECOVERY_DIVISOR 11 //This is the divisor which handles how much of the temperature difference between the current body temperature and 310.15K (optimal temperature) humans auto-regenerate each tick. The higher the number, the slower the recovery. This is applied each tick, so long as the mob is alive. diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index edc6f58eaf..30806bfc52 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -104,6 +104,7 @@ #define TRAIT_RESISTCOLD "resist_cold" #define TRAIT_RESISTHIGHPRESSURE "resist_high_pressure" #define TRAIT_RESISTLOWPRESSURE "resist_low_pressure" +#define TRAIT_LOWPRESSURECOOLING "low_pressure_cooling" #define TRAIT_BOMBIMMUNE "bomb_immunity" #define TRAIT_RADIMMUNE "rad_immunity" #define TRAIT_GENELESS "geneless" diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index a3a1a35888..4a8156d268 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -38,6 +38,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_RESISTCOLD" = TRAIT_RESISTCOLD, "TRAIT_RESISTHIGHPRESSURE" = TRAIT_RESISTHIGHPRESSURE, "TRAIT_RESISTLOWPRESSURE" = TRAIT_RESISTLOWPRESSURE, + "TRAIT_LOWPRESSURECOOLING" = TRAIT_LOWPRESSURECOOLING, "TRAIT_BOMBIMMUNE" = TRAIT_BOMBIMMUNE, "TRAIT_RADIMMUNE" = TRAIT_RADIMMUNE, "TRAIT_GENELESS" = TRAIT_GENELESS, diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 7646297163..6ca0416dfd 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -9,7 +9,6 @@ /atom/movable/screen name = "" icon = 'icons/mob/screen_gen.dmi' - layer = HUD_LAYER plane = HUD_PLANE animate_movement = SLIDE_STEPS speech_span = SPAN_ROBOT diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 90982a00ec..b85a33ec3c 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -294,3 +294,8 @@ description = "I hate when my shoes come untied!\n" mood_change = -3 timeout = 1 MINUTES + +/datum/mood_event/sacrifice_bad + description = "Those darn savages!\n" + mood_change = -5 + timeout = 2 MINUTES diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm index b73756c2b1..39b3212c68 100644 --- a/code/datums/mood_events/generic_positive_events.dm +++ b/code/datums/mood_events/generic_positive_events.dm @@ -205,3 +205,8 @@ /datum/mood_event/cleared_stomach description = "Feels nice to get that out of the way!\n" mood_change = 3 + +/datum/mood_event/sacrifice_good + description = "The gods are pleased with this offering!\n" + mood_change = 5 + timeout = 3 MINUTES diff --git a/code/datums/mutations/space_adaptation.dm b/code/datums/mutations/space_adaptation.dm index 4431720375..6defd05ee5 100644 --- a/code/datums/mutations/space_adaptation.dm +++ b/code/datums/mutations/space_adaptation.dm @@ -13,6 +13,7 @@ return ADD_TRAIT(owner, TRAIT_RESISTCOLD, "cold_resistance") ADD_TRAIT(owner, TRAIT_RESISTLOWPRESSURE, "cold_resistance") + ADD_TRAIT(owner, TRAIT_LOWPRESSURECOOLING, "cold_resistance") owner.add_filter("space_glow", 2, list("type" = "outline", "color" = "#ffe46bd8", "size" = 1)) addtimer(CALLBACK(src, .proc/glow_loop, owner), rand(1,19)) @@ -27,5 +28,6 @@ return REMOVE_TRAIT(owner, TRAIT_RESISTCOLD, "cold_resistance") REMOVE_TRAIT(owner, TRAIT_RESISTLOWPRESSURE, "cold_resistance") + REMOVE_TRAIT(owner, TRAIT_LOWPRESSURECOOLING, "cold_resistance") owner.remove_filter("space_glow") diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index 1c6c444fb4..67ba43dc4f 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -778,6 +778,24 @@ GLOBAL_LIST_EMPTY(possible_items_special) target_amount = count update_explanation_text() */ +/datum/objective/protect_object + name = "protect object" + var/obj/protect_target + +/datum/objective/protect_object/proc/set_target(obj/O) + protect_target = O + update_explanation_text() + +/datum/objective/protect_object/update_explanation_text() + . = ..() + if(protect_target) + explanation_text = "Protect \the [protect_target] at all costs." + else + explanation_text = "Free objective." + +/datum/objective/protect_object/check_completion() + return !QDELETED(protect_target) + //Changeling Objectives /datum/objective/absorb diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index 0e7b4aa0fa..4db565aec1 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -1,7 +1,3 @@ -#define AUTOLATHE_MAIN_MENU 1 -#define AUTOLATHE_CATEGORY_MENU 2 -#define AUTOLATHE_SEARCH_MENU 3 - /obj/machinery/autolathe name = "autolathe" desc = "It produces items using metal and glass." @@ -24,13 +20,14 @@ var/shock_wire var/busy = FALSE - var/prod_coeff = 1 + + ///the multiplier for how much materials the created object takes from this machines stored materials + var/creation_efficiency = 1.6 var/datum/design/being_built var/datum/techweb/stored_research var/list/datum/design/matching_designs - var/selected_category - var/screen = 1 + var/selected_category = "None" var/base_price = 25 var/hacked_price = 50 @@ -60,7 +57,7 @@ QDEL_NULL(wires) return ..() -/obj/machinery/autolathe/ui_interact(mob/user) +/obj/machinery/autolathe/ui_interact(mob/user, datum/tgui/ui) . = ..() if(!is_operational()) return @@ -68,101 +65,129 @@ if(shocked && !(stat & NOPOWER)) shock(user,50) - var/dat + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Autolathe", capitalize(src.name)) + ui.open() - switch(screen) - if(AUTOLATHE_MAIN_MENU) - dat = main_win(user) - if(AUTOLATHE_CATEGORY_MENU) - dat = category_win(user,selected_category) - if(AUTOLATHE_SEARCH_MENU) - dat = search_win(user) - - var/datum/browser/popup = new(user, "autolathe", name, 400, 500) - popup.set_content(dat) - popup.open() - -/obj/machinery/autolathe/on_deconstruction() +/obj/machinery/autolathe/ui_data(mob/user) + var/list/data = list() + data["materials"] = list() var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.retrieve_all() + data["materialtotal"] = materials.total_amount + data["materialsmax"] = materials.max_amount + data["categories"] = categories + data["designs"] = list() + data["active"] = busy -/obj/machinery/autolathe/attackby(obj/item/O, mob/user, params) - if (busy) - to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") - return TRUE - - if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", O)) - updateUsrDialog() - return TRUE - - if(default_deconstruction_crowbar(O)) - return TRUE - - if(panel_open && is_wire_tool(O)) - wires.interact(user) - return TRUE - - if(user.a_intent == INTENT_HARM) //so we can hit the machine - return ..() - - if(stat) - return TRUE - - if(istype(O, /obj/item/disk/design_disk)) - user.visible_message("[user] begins to load \the [O] in \the [src]...", - "You begin to load a design from \the [O]...", - "You hear the chatter of a floppy drive.") - busy = TRUE - var/obj/item/disk/design_disk/D = O - if(do_after(user, 14.4, target = src)) - for(var/B in D.blueprints) - if(B) - stored_research.add_design(B) - busy = FALSE - return TRUE - - return ..() - - -/obj/machinery/autolathe/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) - if(istype(item_inserted, /obj/item/stack/ore/bluespace_crystal)) - use_power(MINERAL_MATERIAL_AMOUNT / 10) - else if(custom_materials && custom_materials.len && custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)]) - flick("autolathe_r",src)//plays glass insertion animation by default otherwise + for(var/mat_id in materials.materials) + var/datum/material/M = mat_id + var/mineral_count = materials.materials[mat_id] + var/list/material_data = list( + name = M.name, + mineral_amount = mineral_count, + matcolour = M.color, + ) + data["materials"] += list(material_data) + if(selected_category != "None" && !length(matching_designs)) + data["designs"] = handle_designs(stored_research.researched_designs, TRUE) else - flick("autolathe_o",src)//plays metal insertion animation + data["designs"] = handle_designs(matching_designs, FALSE) + return data +/obj/machinery/autolathe/proc/handle_designs(list/designs, categorycheck) + var/list/output = list() + for(var/v in designs) + var/datum/design/D = categorycheck ? SSresearch.techweb_design_by_id(v) : v + if(categorycheck) + if(!(selected_category in D.category)) + continue + var/unbuildable = FALSE // we can't build the design currently + var/m10 = FALSE // 10x mult + var/m25 = FALSE // 25x mult + var/m50 = FALSE // 50x mult + var/m5 = FALSE // 5x mult + var/sheets = FALSE // sheets or no? + if(disabled || !can_build(D)) + unbuildable = TRUE + var/max_multiplier = unbuildable ? 0 : 1 + if(ispath(D.build_path, /obj/item/stack)) + sheets = TRUE + if(!unbuildable) + var/datum/component/material_container/mats = GetComponent(/datum/component/material_container) + for(var/datum/material/mat in D.materials) + max_multiplier = min(D.maxstack, round(mats.get_material_amount(mat)/D.materials[mat])) + if (max_multiplier>10 && !disabled) + m10 = TRUE + if (max_multiplier>25 && !disabled) + m25 = TRUE + else + if(!unbuildable) + if(!disabled && can_build(D, 5)) + m5 = TRUE + if(!disabled && can_build(D, 10)) + m10 = TRUE + var/datum/component/material_container/mats = GetComponent(/datum/component/material_container) + for(var/datum/material/mat in D.materials) + max_multiplier = min(50, round(mats.get_material_amount(mat)/(D.materials[mat] * creation_efficiency))) - use_power(min(1000, amount_inserted / 100)) - updateUsrDialog() + var/list/design = list( + name = D.name, + id = D.id, + ref = REF(src), + cost = get_design_cost(D), + buildable = unbuildable, + mult5 = m5, + mult10 = m10, + mult25 = m25, + mult50 = m50, + sheet = sheets, + maxmult = max_multiplier, + ) + output += list(design) + return output -/obj/machinery/autolathe/Topic(href, href_list) - if(..()) +/obj/machinery/autolathe/ui_act(action, params) + . = ..() + if(.) return - if (!busy) - if(href_list["menu"]) - screen = text2num(href_list["menu"]) - updateUsrDialog() + if(action == "menu") + selected_category = null + matching_designs.Cut() + . = TRUE - if(href_list["category"]) - selected_category = href_list["category"] - updateUsrDialog() + if(action == "category") + selected_category = params["selectedCategory"] + matching_designs.Cut() + . = TRUE - if(href_list["make"]) + if(action == "search") + matching_designs.Cut() + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(findtext(D.name,params["to_search"])) + matching_designs.Add(D) + . = TRUE + + if(action == "make") + if (!busy) ///////////////// //href protection - being_built = stored_research.isDesignResearchedID(href_list["make"]) + being_built = stored_research.isDesignResearchedID(params["id"]) if(!being_built) return - var/multiplier = text2num(href_list["multiplier"]) + var/multiplier = text2num(params["multiplier"]) + if(!multiplier) + to_chat(usr, span_alert("[src] only accepts a numerical multiplier!")) + return var/is_stack = ispath(being_built.build_path, /obj/item/stack) - multiplier = clamp(multiplier,1,50) + multiplier = clamp(round(multiplier),1,50) ///////////////// - var/coeff = (is_stack ? 1 : prod_coeff) //stacks are unaffected by production coefficient + var/coeff = (is_stack ? 1 : creation_efficiency) //stacks are unaffected by production coefficient var/total_amount = 0 for(var/MAT in being_built.materials) @@ -184,8 +209,8 @@ if(materials.materials[i] > 0) list_to_show += i - used_material = input("Choose [used_material]", "Custom Material") as null|anything in sortList(list_to_show, /proc/cmp_typepaths_asc) - if(!used_material) + used_material = tgui_input_list(usr, "Choose [used_material]", "Custom Material", sortList(list_to_show, /proc/cmp_typepaths_asc)) + if(isnull(used_material)) return //Didn't pick any material, so you can't build shit either. custom_materials[used_material] += amount_needed @@ -193,27 +218,95 @@ if(materials.has_materials(materials_used)) busy = TRUE + to_chat(usr, span_notice("You print [multiplier] item(s) from the [src]")) use_power(power) icon_state = "autolathe_n" var/time = is_stack ? 32 : (32 * coeff * multiplier) ** 0.8 addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack, usr), time) + . = TRUE else - to_chat(usr, "Not enough materials for this operation.") + to_chat(usr, span_alert("Not enough materials for this operation.")) + else + to_chat(usr, span_alert("The autolathe is busy. Please wait for completion of previous operation.")) - if(href_list["search"]) - matching_designs.Cut() +/obj/machinery/autolathe/on_deconstruction() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.retrieve_all() - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(findtext(D.name,href_list["to_search"])) - matching_designs.Add(D) - updateUsrDialog() +/obj/machinery/autolathe/attackby(obj/item/O, mob/living/user, params) + if(busy) + balloon_alert(user, "it's busy!") + return TRUE + + if(user.a_intent == INTENT_HARM) //so we can hit the machine + return ..() + + if(stat) + return TRUE + + if(istype(O, /obj/item/disk/design_disk)) + user.visible_message(span_notice("[user] begins to load \the [O] in \the [src]..."), + balloon_alert(user, "uploading design..."), + span_hear("You hear the chatter of a floppy drive.")) + busy = TRUE + if(do_after(user, 14.4, target = src)) + var/obj/item/disk/design_disk/disky = O + var/list/not_imported + for(var/datum/design/blueprint as anything in disky.blueprints) + if(!blueprint) + continue + if(blueprint.build_type & AUTOLATHE) + stored_research.add_design(blueprint) + else + LAZYADD(not_imported, blueprint.name) + if(not_imported) + to_chat(user, span_warning("The following design[length(not_imported) > 1 ? "s" : ""] couldn't be imported: [english_list(not_imported)]")) + busy = FALSE + return TRUE + + if(panel_open) + balloon_alert(user, "close the panel first!") + return FALSE + + return ..() + +/obj/machinery/autolathe/screwdriver_act(mob/living/user, obj/item/I) + . = ..() + if(busy) + balloon_alert(user, "it's busy!") + return STOP_ATTACK_PROC_CHAIN + + if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", I)) + return STOP_ATTACK_PROC_CHAIN + +/obj/machinery/autolathe/crowbar_act(mob/living/user, obj/item/I) + . = ..() + if(busy) + balloon_alert(user, "it's busy!") + return STOP_ATTACK_PROC_CHAIN + + if(default_deconstruction_crowbar(I)) + return STOP_ATTACK_PROC_CHAIN + +/obj/machinery/autolathe/multitool_act(mob/living/user, obj/item/I) + . = ..() + if(busy) + balloon_alert(user, "it's busy!") + return STOP_ATTACK_PROC_CHAIN + + if(panel_open) + wires.interact(user) + return STOP_ATTACK_PROC_CHAIN + +/obj/machinery/autolathe/proc/AfterMaterialInsert(obj/item/item_inserted, id_inserted, amount_inserted) + if(istype(item_inserted, /obj/item/stack/ore/bluespace_crystal)) + use_power(MINERAL_MATERIAL_AMOUNT / 10) + else if(custom_materials && custom_materials.len && custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)]) + flick("autolathe_r", src)//plays glass insertion animation by default otherwise else - to_chat(usr, "The autolathe is busy. Please wait for completion of previous operation.") + flick("autolathe_o", src)//plays metal insertion animation - updateUsrDialog() - - return + use_power(min(1000, amount_inserted / 100)) /obj/machinery/autolathe/proc/make_item(power, list/materials_used, list/picked_materials, multiplier, coeff, is_stack, mob/user) var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) @@ -223,11 +316,11 @@ materials.use_materials(materials_used) if(is_stack) - var/obj/item/stack/N = new being_built.build_path(A, multiplier) - N.update_icon() + var/obj/item/stack/N = new being_built.build_path(A, multiplier, FALSE) + N.update_appearance() N.autolathe_crafted(src) else - for(var/i=1, i<=multiplier, i++) + for(var/i in 1 to multiplier) var/obj/item/new_item = new being_built.build_path(A) new_item.autolathe_crafted(src) @@ -241,132 +334,30 @@ icon_state = "autolathe" busy = FALSE - updateDialog() /obj/machinery/autolathe/RefreshParts() - var/T = 0 - for(var/obj/item/stock_parts/matter_bin/MB in component_parts) - T += MB.rating*75000 + var/mat_capacity = 0 + for(var/obj/item/stock_parts/matter_bin/new_matter_bin in component_parts) + mat_capacity += new_matter_bin.rating*75000 var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.max_amount = T - T=1.2 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - T -= M.rating*0.2 - prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4 + materials.max_amount = mat_capacity + + var/efficiency=1.2 + for(var/obj/item/stock_parts/manipulator/new_manipulator in component_parts) + efficiency -= new_manipulator.rating*0.2 + creation_efficiency = max(1,efficiency) // creation_efficiency goes 1 -> 0,8 -> 0,6 -> 0,4 per level of manipulator efficiency /obj/machinery/autolathe/examine(mob/user) . += ..() var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Storing up to [materials.max_amount] material units.
Material consumption at [prod_coeff*100]%.
" - -/obj/machinery/autolathe/proc/main_win(mob/user) - var/dat = "

Autolathe Menu:


" - dat += materials_printout() - - dat += "
\ - \ - \ - \ - \ - \ -

" - - var/line_length = 1 - dat += "" - - for(var/C in categories) - if(line_length > 2) - dat += "" - line_length = 1 - - dat += "" - line_length++ - - dat += "
[C]
" - return dat - -/obj/machinery/autolathe/proc/category_win(mob/user,selected_category) - var/dat = "Return to main menu" - dat += "

Browsing [selected_category]:


" - dat += materials_printout() - - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(selected_category in D.category)) - continue - - if(disabled || !can_build(D)) - dat += "[D.name]" - else - dat += "[D.name]" - - if(ispath(D.build_path, /obj/item/stack)) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/max_multiplier - for(var/datum/material/mat in D.materials) - max_multiplier = min(D.maxstack, round(materials.get_material_amount(mat)/D.materials[mat])) - if (max_multiplier>10 && !disabled) - dat += " x10" - if (max_multiplier>25 && !disabled) - dat += " x25" - if(max_multiplier > 0 && !disabled) - dat += " x[max_multiplier]" - else - if(!disabled && can_build(D, 5)) - dat += " x5" - if(!disabled && can_build(D, 10)) - dat += " x10" - - dat += "[get_design_cost(D)]
" - - dat += "
" - return dat - -/obj/machinery/autolathe/proc/search_win(mob/user) - var/dat = "Return to main menu" - dat += "

Search results:


" - dat += materials_printout() - - for(var/v in matching_designs) - var/datum/design/D = v - if(disabled || !can_build(D)) - dat += "[D.name]" - else - dat += "[D.name]" - - if(ispath(D.build_path, /obj/item/stack)) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/max_multiplier - for(var/datum/material/mat in D.materials) - max_multiplier = min(D.maxstack, round(materials.get_material_amount(mat)/D.materials[mat])) - if (max_multiplier>10 && !disabled) - dat += " x10" - if (max_multiplier>25 && !disabled) - dat += " x25" - if(max_multiplier > 0 && !disabled) - dat += " x[max_multiplier]" - - dat += "[get_design_cost(D)]
" - - dat += "
" - return dat - -/obj/machinery/autolathe/proc/materials_printout() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/dat = "Total amount: [materials.total_amount] / [materials.max_amount] cm3
" - for(var/mat_id in materials.materials) - var/datum/material/M = mat_id - var/mineral_amount = materials.materials[mat_id] - if(mineral_amount > 0) - dat += "[M.name] amount: [mineral_amount] cm3
" - return dat + . += span_notice("The status display reads: Storing up to [materials.max_amount] material units.
Material consumption at [creation_efficiency*100]%.") /obj/machinery/autolathe/proc/can_build(datum/design/D, amount = 1) - if(D.make_reagents.len) + if(length(D.make_reagents)) return FALSE - var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) + var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : creation_efficiency) var/list/required_materials = list() @@ -379,7 +370,7 @@ /obj/machinery/autolathe/proc/get_design_cost(datum/design/D) - var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) + var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : creation_efficiency) var/dat for(var/i in D.materials) if(istext(i)) //Category handling @@ -402,7 +393,7 @@ disabled = FALSE /obj/machinery/autolathe/proc/shock(mob/user, prb) - if(stat & (BROKEN|NOPOWER)) // unpowered, no shock + if(stat & (BROKEN|NOPOWER)) // unpowered, no shock return FALSE if(!prob(prb)) return FALSE @@ -424,7 +415,7 @@ else stored_research.remove_design(D) -/obj/machinery/autolathe/hacked/Initialize() +/obj/machinery/autolathe/hacked/Initialize(mapload) . = ..() adjust_hacked(TRUE) diff --git a/code/game/objects/structures/ghost_role_spawners.dm b/code/game/objects/structures/ghost_role_spawners.dm index be92782c74..0077ad82da 100644 --- a/code/game/objects/structures/ghost_role_spawners.dm +++ b/code/game/objects/structures/ghost_role_spawners.dm @@ -1,5 +1,5 @@ //Objects that spawn ghosts in as a certain role when they click on it, i.e. away mission bartenders. - +#define spawnOverride TRUE //Preserved terrarium/seed vault: Spawns in seed vault structures in lavaland. Ghosts become plantpeople and are advised to begin growing plants in the room near them. /obj/effect/mob_spawn/human/seed_vault name = "preserved terrarium" @@ -36,6 +36,44 @@ //Ash walker eggs: Spawns in ash walker dens in lavaland. Ghosts become unbreathing lizards that worship the Necropolis and are advised to retrieve corpses to create more ash walkers. +/obj/structure/ash_walker_eggshell + name = "ash walker egg" + desc = "A man-sized yellow egg, spawned from some unfathomable creature. A humanoid silhouette lurks within. The egg shell looks resistant to temperature but otherwise rather brittle." + icon = 'icons/mob/lavaland/lavaland_monsters.dmi' + icon_state = "large_egg" + resistance_flags = LAVA_PROOF | FIRE_PROOF | FREEZE_PROOF + max_integrity = 80 + var/obj/effect/mob_spawn/human/ash_walker/egg + +/obj/structure/ash_walker_eggshell/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) //lifted from xeno eggs + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(loc, 'sound/effects/attackblob.ogg', 100, TRUE) + else + playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + if(damage_amount) + playsound(loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/structure/ash_walker_eggshell/attack_ghost(mob/user) //Pass on ghost clicks to the mob spawner + if(egg) + egg.attack_ghost(user) + . = ..() + +/obj/structure/ash_walker_eggshell/Destroy() + if(!egg) + return ..() + var/mob/living/carbon/human/yolk = new /mob/living/carbon/human/(get_turf(src)) + yolk.fully_replace_character_name(null,random_unique_lizard_name(gender)) + yolk.set_species(/datum/species/lizard/ashwalker) + yolk.underwear = "Nude" + yolk.equipOutfit(/datum/outfit/ashwalker)//this is an authentic mess we're making + yolk.update_body() + yolk.gib() + QDEL_NULL(egg) + return ..() + /obj/effect/mob_spawn/human/ash_walker name = "ash walker egg" desc = "A man-sized yellow egg, spawned from some unfathomable creature. A humanoid silhouette lurks within." @@ -55,12 +93,25 @@ You have seen lights in the distance... they foreshadow the arrival of outsiders to your domain. \ Ensure your nest remains protected at all costs." assignedrole = "Ash Walker" + var/datum/team/ashwalkers/team + var/obj/structure/ash_walker_eggshell/eggshell + +/obj/effect/mob_spawn/human/ash_walker/Destroy() + eggshell = null + return ..() + +/obj/effect/mob_spawn/human/ash_walker/allow_spawn(mob/user, silent = FALSE) + if(!(user.key in team.players_spawned) || spawnOverride)//one per person unless you get a bonus spawn + return TRUE + to_chat(user, span_warning("You have exhausted your usefulness to the Necropolis.")) + return FALSE /obj/effect/mob_spawn/human/ash_walker/special(mob/living/new_spawn) new_spawn.real_name = random_unique_lizard_name(gender) if(is_mining_level(z)) to_chat(new_spawn, "Drag the corpses of men and beasts to your nest. It will absorb them to create more of your kind. Glory to the Necropolis!") to_chat(new_spawn, "You can expand the weather proof area provided by your shelters by using the 'New Area' key near the bottom right of your HUD.") + to_chat(new_spawn, "Dragging injured ashwalkers to the tentacle or using the sleep verb next to it youself causes the body to remade whole after a short delay!") else to_chat(new_spawn, "You have been born outside of your natural home! Whether you decide to return home, or make due with your new home is your own decision.") @@ -72,10 +123,18 @@ H.undershirt = "Nude" H.socks = "Nude" H.update_body() + new_spawn.mind.add_antag_datum(/datum/antagonist/ashwalker, team) + team.players_spawned += (new_spawn.key) + eggshell.egg = null + QDEL_NULL(eggshell) -/obj/effect/mob_spawn/human/ash_walker/Initialize(mapload) +/obj/effect/mob_spawn/human/ash_walker/Initialize(mapload, datum/team/ashwalkers/ashteam) . = ..() var/area/A = get_area(src) + team = ashteam + eggshell = new /obj/structure/ash_walker_eggshell(get_turf(loc)) + eggshell.egg = src + src.forceMove(eggshell) if(A) notify_ghosts("An ash walker egg is ready to hatch in \the [A.name].", source = src, action=NOTIFY_ATTACK, flashwindow = FALSE, ignore_key = POLL_IGNORE_ASHWALKER, ignore_dnr_observers = TRUE) diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 8dbc2b863a..498951bfd2 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -98,7 +98,7 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/on_body_transfer(mob/living/old_body, mob/living/new_body) SHOULD_CALL_PARENT(TRUE) remove_innate_effects(old_body) - if(!soft_antag && old_body.stat != DEAD && !LAZYLEN(old_body.mind?.antag_datums)) + if(!soft_antag && old_body && old_body.stat != DEAD && !length(old_body.mind?.antag_datums)) old_body.remove_from_current_living_antags() apply_innate_effects(new_body) if(!soft_antag && new_body.stat != DEAD) diff --git a/code/modules/antagonists/ashwalker/ashwalker.dm b/code/modules/antagonists/ashwalker/ashwalker.dm new file mode 100644 index 0000000000..0ba84a201f --- /dev/null +++ b/code/modules/antagonists/ashwalker/ashwalker.dm @@ -0,0 +1,40 @@ +/datum/team/ashwalkers + name = "Ashwalkers" + show_roundend_report = FALSE + var/list/players_spawned = new + +/datum/antagonist/ashwalker + name = "\improper Ash Walker" + job_rank = ROLE_LAVALAND + show_in_antagpanel = FALSE + show_to_ghosts = TRUE + antagpanel_category = "Ash Walkers" + var/datum/team/ashwalkers/ashie_team + +/datum/antagonist/ashwalker/create_team(datum/team/team) + if(team) + ashie_team = team + objectives |= ashie_team.objectives + else + ashie_team = new + +/datum/antagonist/ashwalker/get_team() + return ashie_team + +/datum/antagonist/ashwalker/on_body_transfer(mob/living/old_body, mob/living/new_body) + . = ..() + RegisterSignal(new_body, COMSIG_MOB_EXAMINATE, .proc/on_examinate) + +/datum/antagonist/ashwalker/on_gain() + . = ..() + RegisterSignal(owner.current, COMSIG_MOB_EXAMINATE, .proc/on_examinate) + +/datum/antagonist/ashwalker/on_removal() + . = ..() + UnregisterSignal(owner.current, COMSIG_MOB_EXAMINATE) + +/datum/antagonist/ashwalker/proc/on_examinate(datum/source, atom/A) + SIGNAL_HANDLER + + if(istype(A, /obj/structure/headpike)) + SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "headspear", /datum/mood_event/sacrifice_good) diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm index cd5c2f76f3..668a8dcb92 100644 --- a/code/modules/awaymissions/corpse.dm +++ b/code/modules/awaymissions/corpse.dm @@ -33,6 +33,10 @@ var/ghost_usable = TRUE var/skip_reentry_check = FALSE //Skips the ghost role blacklist time for people who ghost/suicide/cryo +///override this to add special spawn conditions to a ghost role +/obj/effect/mob_spawn/proc/allow_spawn(mob/user, silent = FALSE) + return TRUE + //ATTACK GHOST IGNORING PARENT RETURN VALUE /obj/effect/mob_spawn/attack_ghost(mob/user, latejoinercalling) if(!SSticker.HasRoundStarted() || !loc || !ghost_usable) @@ -43,6 +47,8 @@ if(jobban_isbanned(user, banType)) to_chat(user, "You are jobanned!") return + if(!allow_spawn(user, silent = FALSE)) + return if(QDELETED(src) || QDELETED(user)) return if(isobserver(user)) diff --git a/code/modules/keybindings/keybind/living.dm b/code/modules/keybindings/keybind/living.dm index f76c86691f..7f5ccb3b51 100644 --- a/code/modules/keybindings/keybind/living.dm +++ b/code/modules/keybindings/keybind/living.dm @@ -36,3 +36,25 @@ /datum/keybinding/living/toggle_resting/down(client/user) var/mob/living/L = user.mob L.lay_down() + +/datum/keybinding/living/cancel_action + hotkey_keys = list("Unbound") + name = "cancel_action" + full_name = "Cancel Action" + description = "Cancel the current action." + +/// Technically you shouldn't be doing any actions if you were sleeping either but... +/datum/keybinding/living/can_use(client/user) + . = ..() + var/mob/living/mob = user.mob + return . && (mob.stat == CONSCIOUS) + +/datum/keybinding/living/cancel_action/down(client/user) + var/mob/M = user.mob + if(length(M.do_afters)) + var/atom/target = M.do_afters[M.do_afters.len] + to_chat(M, "You stop interacting with \the [target].") + LAZYREMOVE(M.do_afters, target) + else + to_chat(M, "There's nothing that you can cancel right now.") + return TRUE diff --git a/code/modules/mob/living/carbon/human/species_types/anthropomorph.dm b/code/modules/mob/living/carbon/human/species_types/anthropomorph.dm index 334f76c487..b79c281b3b 100644 --- a/code/modules/mob/living/carbon/human/species_types/anthropomorph.dm +++ b/code/modules/mob/living/carbon/human/species_types/anthropomorph.dm @@ -32,7 +32,7 @@ coldmod = 0.5 heatmod = 1.2 - cold_offset = -125 //Can handle pretty cold environments, but it's still a slightly bad idea if you enter a room thats full of near-absolute-zero gas + cold_offset = SYNTH_COLD_OFFSET //Can handle pretty cold environments, but it's still a slightly bad idea if you enter a room thats full of near-absolute-zero gas blacklisted_quirks = list(/datum/quirk/coldblooded) balance_point_values = TRUE diff --git a/code/modules/mob/living/carbon/human/species_types/ipc.dm b/code/modules/mob/living/carbon/human/species_types/ipc.dm index 4f6b940692..c3fd9e21fc 100644 --- a/code/modules/mob/living/carbon/human/species_types/ipc.dm +++ b/code/modules/mob/living/carbon/human/species_types/ipc.dm @@ -9,13 +9,13 @@ species_traits = list(MUTCOLORS,NOEYES,NOTRANSSTING,HAS_FLESH,HAS_BONE,HAIR,ROBOTIC_LIMBS) hair_alpha = 210 inherent_biotypes = MOB_ROBOTIC|MOB_HUMANOID - mutant_bodyparts = list("ipc_screen" = "Blank", "ipc_antenna" = "None") + mutant_bodyparts = list("ipc_screen" = "Blank", "deco_wings" = "None", "ipc_antenna" = "None") meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ipc gib_types = list(/obj/effect/gibspawner/ipc, /obj/effect/gibspawner/ipc/bodypartless) coldmod = 0.5 heatmod = 1.2 - cold_offset = -125 //Can handle pretty cold environments, but it's still a slightly bad idea if you enter a room thats full of near-absolute-zero gas + cold_offset = SYNTH_COLD_OFFSET //Can handle pretty cold environments, but it's still a slightly bad idea if you enter a room thats full of near-absolute-zero gas blacklisted_quirks = list(/datum/quirk/coldblooded) balance_point_values = TRUE diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index 04c69a4138..0693bf64e1 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -404,7 +404,7 @@ default_color = "00FFFF" species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,HAS_FLESH) inherent_traits = list(TRAIT_TOXINLOVER) - mutant_bodyparts = list("mcolor" = "FFFFFF", "mcolor2" = "FFFFFF","mcolor3" = "FFFFFF", "mam_tail" = "None", "mam_ears" = "None", "mam_body_markings" = "Plain", "mam_snouts" = "None", "taur" = "None", "legs" = "Plantigrade") + mutant_bodyparts = list("mcolor" = "FFFFFF", "mcolor2" = "FFFFFF","mcolor3" = "FFFFFF", "mam_tail" = "None", "mam_ears" = "None", "mam_body_markings" = "Plain", "mam_snouts" = "None", "taur" = "None", "deco_wings" = "None", "legs" = "Plantigrade") say_mod = "says" hair_color = "mutcolor" hair_alpha = 160 //a notch brighter so it blends better. diff --git a/code/modules/mob/living/carbon/human/species_types/synthliz.dm b/code/modules/mob/living/carbon/human/species_types/synthliz.dm index 7fc2da1132..c692370030 100644 --- a/code/modules/mob/living/carbon/human/species_types/synthliz.dm +++ b/code/modules/mob/living/carbon/human/species_types/synthliz.dm @@ -12,7 +12,7 @@ coldmod = 0.5 heatmod = 1.2 - cold_offset = -125 //Can handle pretty cold environments, but it's still a slightly bad idea if you enter a room thats full of near-absolute-zero gas + cold_offset = SYNTH_COLD_OFFSET //Can handle pretty cold environments, but it's still a slightly bad idea if you enter a room thats full of near-absolute-zero gas blacklisted_quirks = list(/datum/quirk/coldblooded) balance_point_values = TRUE diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 9cc96f2ddd..182ae1afb8 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -699,6 +699,9 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put var/heat_efficiency = clamp(1 + ((bodytemperature - heat) * SYNTH_HEAT_EFFICIENCY_COEFF), 0, SYNTH_SINGLE_INFLUENCE_COOLING_EFFECT_CAP) var/pressure_efficiency = clamp(pressure / ONE_ATMOSPHERE, 0, SYNTH_SINGLE_INFLUENCE_COOLING_EFFECT_CAP) + if(HAS_TRAIT(src, TRAIT_LOWPRESSURECOOLING)) + pressure_efficiency = max(pressure_efficiency, 1) //Space adaptation nulls drawbacks of low-pressure cooling. + var/total_environment_efficiency = min(heat_efficiency * pressure_efficiency, SYNTH_TOTAL_ENVIRONMENT_EFFECT_CAP) //At best, you can get 200% total return total_environment_efficiency diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index bc8c3fed87..ca9ca75e6c 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -292,18 +292,21 @@ /mob/living/simple_animal/bot/on_attack_hand(mob/living/carbon/human/H) if(H.a_intent == INTENT_HELP) - interact(H) + ui_interact(H) else return ..() /mob/living/simple_animal/bot/attack_ai(mob/user) if(!topic_denied(user)) - interact(user) + ui_interact(user) else to_chat(user, "[src]'s interface is not responding!") -/mob/living/simple_animal/bot/interact(mob/user) - show_controls(user) +/mob/living/simple_animal/bot/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SimpleBot", name) + ui.open() /mob/living/simple_animal/bot/attackby(obj/item/W, mob/user, params) if(W.tool_behaviour == TOOL_SCREWDRIVER) @@ -795,56 +798,48 @@ Pass a positive integer as an argument to override a bot's default speed. else // no path, so calculate new one calc_summon_path() -/mob/living/simple_animal/bot/Bump(M as mob|obj) //Leave no door unopened! +// Variables sent to TGUI +/mob/living/simple_animal/bot/ui_data(mob/user) + var/list/data = list() + data["can_hack"] = (issilicon(user) || IsAdminGhost(user)) + data["custom_controls"] = list() + data["emagged"] = emagged + data["locked"] = locked + data["pai"] = list() + data["settings"] = list() + if(!locked || issilicon(user) || IsAdminGhost(user)) + data["pai"]["allow_pai"] = allow_pai + data["pai"]["card_inserted"] = paicard + data["settings"]["airplane_mode"] = remote_disabled + data["settings"]["maintenance_lock"] = !open + data["settings"]["power"] = on + data["settings"]["patrol_station"] = auto_patrol + return data + +// Actions received from TGUI +/mob/living/simple_animal/bot/ui_act(action, params) . = ..() - if((istype(M, /obj/machinery/door/airlock) || istype(M, /obj/machinery/door/window)) && (!isnull(access_card))) - var/obj/machinery/door/D = M - if(D.check_access(access_card)) - D.open() - frustration = 0 - -/mob/living/simple_animal/bot/proc/show_controls(mob/M) - users |= M - var/dat = "" - dat = get_controls(M) - var/datum/browser/popup = new(M,window_id,window_name,350,600) - popup.set_content(dat) - popup.open(use_onclose = 0) - onclose(M,window_id,ref=src) - return - -/mob/living/simple_animal/bot/proc/update_controls() - for(var/mob/M in users) - show_controls(M) - -/mob/living/simple_animal/bot/proc/get_controls(mob/M) - return "PROTOBOT - NOT FOR USE" - -/mob/living/simple_animal/bot/Topic(href, href_list) - //No ..() to prevent strip panel showing up - Todo: make that saner - if(href_list["close"])// HUE HUE - if(usr in users) - users.Remove(usr) - return TRUE - - if(topic_denied(usr)) - to_chat(usr, "[src]'s interface is not responding!") - return TRUE - add_fingerprint(usr) - - if((href_list["power"]) && (bot_core.allowed(usr) || !locked)) - if(on) - turn_off() - else - turn_on() - - switch(href_list["operation"]) + if(.) + return + if(!hasSiliconAccessInArea(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) + to_chat(usr, span_warning("Access denied.")) + return + if(action == "lock") + locked = !locked + if(locked && !(issilicon(usr) || IsAdminGhost(usr))) + return + switch(action) + if("power") + on = !on + update_appearance() + if("maintenance") + open = !open if("patrol") if(!hasSiliconAccessInArea(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) return TRUE auto_patrol = !auto_patrol bot_reset() - if("remote") + if("airplane") remote_disabled = !remote_disabled if("hack") if(!hasSiliconAccessInArea(usr) && !IsAdminGhost(usr)) @@ -865,11 +860,11 @@ Pass a positive integer as an argument to override a bot's default speed. hacked = FALSE to_chat(usr, "[text_dehack]") bot_reset() - if("ejectpai") + if("eject_pai") if(paicard && (!locked || hasSiliconAccessInArea(usr) || IsAdminGhost(usr))) to_chat(usr, "You eject [paicard] from [bot_name]") ejectpai(usr) - update_controls() + return /mob/living/simple_animal/bot/update_icon_state() icon_state = "[initial(icon_state)][on]" @@ -897,33 +892,6 @@ Pass a positive integer as an argument to override a bot's default speed. return TRUE return FALSE -/mob/living/simple_animal/bot/proc/hack(mob/user) - var/hack - if(hasSiliconAccessInArea(user) || IsAdminGhost(user)) //Allows silicons or admins to toggle the emag status of a bot. - hack += "[emagged == 2 ? "Software compromised! Unit may exhibit dangerous or erratic behavior." : "Unit operating normally. Release safety lock?"]
" - hack += "Harm Prevention Safety System: [emagged ? "DANGER" : "Engaged"]
" - else if(!locked) //Humans with access can use this option to hide a bot from the AI's remote control panel and PDA control. - hack += "Remote network control radio: [remote_disabled ? "Disconnected" : "Connected"]
" - return hack - -/mob/living/simple_animal/bot/proc/showpai(mob/user) - var/eject = "" - if((!locked || hasSiliconAccessInArea(usr) || IsAdminGhost(usr))) - if(paicard || allow_pai) - eject += "Personality card status: " - if(paicard) - if(client) - eject += "Active" - else - eject += "Inactive" - else if(!allow_pai || key) - eject += "Unavailable" - else - eject += "Not inserted" - eject += "
" - eject += "
" - return eject - /mob/living/simple_animal/bot/proc/insertpai(mob/user, obj/item/paicard/card) if(paicard) to_chat(user, "A [paicard] is already inserted!") diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm index 5b812086c8..c683267a58 100644 --- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm +++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm @@ -443,37 +443,33 @@ /obj/machinery/bot_core/cleanbot req_one_access = list(ACCESS_JANITOR, ACCESS_ROBOTICS) -/mob/living/simple_animal/bot/cleanbot/get_controls(mob/user) - var/dat - dat += hack(user) - dat += showpai(user) - dat += text({" -Status: [on ? "On" : "Off"]
-Behaviour controls are [locked ? "locked" : "unlocked"]
-Maintenance panel panel is [open ? "opened" : "closed"]"}) - if(!locked || issilicon(user)|| IsAdminGhost(user)) - dat += "
Clean Blood: [blood ? "Yes" : "No"]" - dat += "
Clean Trash: [trash ? "Yes" : "No"]" - dat += "
Clean Graffiti: [drawn ? "Yes" : "No"]" - dat += "
Exterminate Pests: [pests ? "Yes" : "No"]" - dat += "

Patrol Station: [auto_patrol ? "Yes" : "No"]" - return dat +// Variables sent to TGUI +/mob/living/simple_animal/bot/cleanbot/ui_data(mob/user) + var/list/data = ..() -/mob/living/simple_animal/bot/cleanbot/Topic(href, href_list) - if(..()) - return 1 - if(href_list["operation"]) - switch(href_list["operation"]) - if("blood") - blood = !blood - if("pests") - pests = !pests - if("trash") - trash = !trash - if("drawn") - drawn = !drawn - get_targets() - update_controls() + if(!locked || issilicon(user)|| IsAdminGhost(user)) + data["custom_controls"]["clean_blood"] = blood + data["custom_controls"]["clean_trash"] = trash + data["custom_controls"]["clean_graffiti"] = drawn + data["custom_controls"]["pest_control"] = pests + return data + +// Actions received from TGUI +/mob/living/simple_animal/bot/cleanbot/ui_act(action, params) + . = ..() + if(. || !hasSiliconAccessInArea(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) + return TRUE + switch(action) + if("clean_blood") + blood = !blood + if("pest_control") + pests = !pests + if("clean_trash") + trash = !trash + if("clean_graffiti") + drawn = !drawn + get_targets() + return /obj/machinery/bot_core/cleanbot/medbay req_one_access = list(ACCESS_JANITOR, ACCESS_ROBOTICS, ACCESS_MEDICAL) diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index 604a72b43e..c3ae3898dd 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -96,64 +96,45 @@ text_dehack = "You restore [name]'s combat inhibitor." text_dehack_fail = "[name] ignores your attempts to restrict him!" -/mob/living/simple_animal/bot/ed209/get_controls(mob/user) - var/dat - dat += hack(user) - dat += showpai(user) - dat += text({" -Security Unit v2.6 controls

-Status: []
-Behaviour controls are [locked ? "locked" : "unlocked"]
-Maintenance panel panel is [open ? "opened" : "closed"]
"}, - -"[on ? "On" : "Off"]" ) - +// Variables sent to TGUI +/mob/living/simple_animal/bot/ed209/ui_data(mob/user) + var/list/data = ..() if(!locked || hasSiliconAccessInArea(user)|| IsAdminGhost(user)) if(!lasercolor) - dat += text({"
-Arrest Unidentifiable Persons: []
-Arrest for Unauthorized Weapons: []
-Arrest for Warrant: []
-
-Operating Mode: []
-Report Arrests[]
-Auto Patrol[]"}, + data["custom_controls"]["check_id"] = idcheck + data["custom_controls"]["check_weapons"] = weaponscheck + data["custom_controls"]["check_warrants"] = check_records + data["custom_controls"]["handcuff_targets"] = !arrest_type + data["custom_controls"]["arrest_alert"] = declare_arrests + return data -"[idcheck ? "Yes" : "No"]", -"[weaponscheck ? "Yes" : "No"]", -"[check_records ? "Yes" : "No"]", -"[arrest_type ? "Detain" : "Arrest"]", -"[declare_arrests ? "Yes" : "No"]", -"[auto_patrol ? "On" : "Off"]" ) - - return dat - -/mob/living/simple_animal/bot/ed209/Topic(href, href_list) +// Actions received from TGUI +/mob/living/simple_animal/bot/ed209/ui_act(action, params) if(lasercolor && ishuman(usr)) var/mob/living/carbon/human/H = usr - if((lasercolor == "b") && (istype(H.wear_suit, /obj/item/clothing/suit/redtag)))//Opposing team cannot operate it - return - else if((lasercolor == "r") && (istype(H.wear_suit, /obj/item/clothing/suit/bluetag))) - return - if(..()) - return 1 + switch(lasercolor) //Opposing team cannot operate it + if("b") + if(istype(H.wear_suit, /obj/item/clothing/suit/redtag)) + return TRUE + if("r") + if(istype(H.wear_suit, /obj/item/clothing/suit/bluetag)) + return TRUE + . = ..() + if(. || !hasSiliconAccessInArea(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) + return TRUE - switch(href_list["operation"]) - if("idcheck") + switch(action) + if("check_id") idcheck = !idcheck - update_controls() - if("weaponscheck") + if("check_weapons") weaponscheck = !weaponscheck - update_controls() - if("ignorerec") + if("check_warrants") check_records = !check_records - update_controls() - if("switchmode") + if("handcuff_targets") arrest_type = !arrest_type - update_controls() - if("declarearrests") + if("arrest_alert") declare_arrests = !declare_arrests - update_controls() + return /mob/living/simple_animal/bot/ed209/proc/judgement_criteria() var/final = FALSE diff --git a/code/modules/mob/living/simple_animal/bot/firebot.dm b/code/modules/mob/living/simple_animal/bot/firebot.dm index d0e969dc4e..66d1955502 100644 --- a/code/modules/mob/living/simple_animal/bot/firebot.dm +++ b/code/modules/mob/living/simple_animal/bot/firebot.dm @@ -102,23 +102,6 @@ text_dehack = "You detect errors in [name] and reset his programming." text_dehack_fail = "[name] is not responding to reset commands!" -/mob/living/simple_animal/bot/firebot/get_controls(mob/user) - var/dat - dat += hack(user) - dat += showpai(user) - dat += "Mobile Fire Extinguisher v1.0

" - dat += "Status: [on ? "On" : "Off"]
" - dat += "Maintenance panel panel is [open ? "opened" : "closed"]
" - - dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
" - if(!locked || hasSiliconAccessInArea(user) || IsAdminGhost(user)) - dat += "Extinguish Fires: [extinguish_fires ? "Yes" : "No"]
" - dat += "Extinguish People: [extinguish_people ? "Yes" : "No"]
" - dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
" - dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
" - - return dat - /mob/living/simple_animal/bot/firebot/emag_act(mob/user) . = ..() if(emagged == 1) @@ -138,20 +121,29 @@ internal_ext.max_water = INFINITY internal_ext.refill() -/mob/living/simple_animal/bot/firebot/Topic(href, href_list) - if(..()) - return TRUE +// Variables sent to TGUI +/mob/living/simple_animal/bot/firebot/ui_data(mob/user) + var/list/data = ..() + if(!locked || issilicon(user) || IsAdminGhost(user)) + data["custom_controls"]["extinguish_fires"] = extinguish_fires + data["custom_controls"]["extinguish_people"] = extinguish_people + data["custom_controls"]["stationary_mode"] = stationary_mode + return data - switch(href_list["operation"]) +// Actions received from TGUI +/mob/living/simple_animal/bot/firebot/ui_act(action, params) + . = ..() + if(. || !hasSiliconAccessInArea(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) + return TRUE + switch(action) if("extinguish_fires") extinguish_fires = !extinguish_fires if("extinguish_people") extinguish_people = !extinguish_people if("stationary_mode") stationary_mode = !stationary_mode - - update_controls() - update_icon() + update_appearance() + return /mob/living/simple_animal/bot/firebot/proc/is_burning(atom/target) if(ismob(target)) diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm index a67642f8ca..5f0075aaa2 100644 --- a/code/modules/mob/living/simple_animal/bot/floorbot.dm +++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm @@ -71,36 +71,6 @@ text_dehack = "You detect errors in [name] and reset his programming." text_dehack_fail = "[name] is not responding to reset commands!" -/mob/living/simple_animal/bot/floorbot/get_controls(mob/user) - var/dat - dat += hack(user) - dat += showpai(user) - dat += "Floor Repairer Controls v1.1

" - dat += "Status: [on ? "On" : "Off"]
" - dat += "Maintenance panel panel is [open ? "opened" : "closed"]
" - dat += "Special tiles: " - if(specialtiles) - dat += "Loaded \[[specialtiles]/[maxtiles]\]
" - else - dat += "None Loaded
" - - dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
" - if(!locked || hasSiliconAccessInArea(user) || IsAdminGhost(user)) - dat += "Add tiles to new hull plating: [autotile ? "Yes" : "No"]
" - dat += "Place floor tiles: [placetiles ? "Yes" : "No"]
" - dat += "Replace existing floor tiles with custom tiles: [replacetiles ? "Yes" : "No"]
" - dat += "Repair damaged tiles and platings: [fixfloors ? "Yes" : "No"]
" - dat += "Traction Magnets: [anchored ? "Engaged" : "Disengaged"]
" - dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
" - var/bmode - if(targetdirection) - bmode = dir2text(targetdirection) - else - bmode = "disabled" - dat += "Line Mode : [bmode]
" - - return dat - /mob/living/simple_animal/bot/floorbot/attackby(obj/item/W , mob/user, params) if(istype(W, /obj/item/stack/tile/plasteel)) to_chat(user, "The floorbot can produce normal tiles itself.") @@ -161,26 +131,44 @@ if(user) to_chat(user, "[src] buzzes and beeps.") -/mob/living/simple_animal/bot/floorbot/Topic(href, href_list) - if(..()) - return 1 +// Variables sent to TGUI +/mob/living/simple_animal/bot/floorbot/ui_data(mob/user) + var/list/data = ..() + if(!locked || issilicon(user) || IsAdminGhost(user)) + data["custom_controls"]["tile_hull"] = autotile + data["custom_controls"]["place_tiles"] = placetiles + data["custom_controls"]["place_custom"] = replacetiles + data["custom_controls"]["repair_damage"] = fixfloors + data["custom_controls"]["traction_magnets"] = anchored + data["custom_controls"]["tile_stack"] = 0 + data["custom_controls"]["line_mode"] = FALSE + if(specialtiles) + data["custom_controls"]["tile_stack"] = specialtiles + if(targetdirection) + data["custom_controls"]["line_mode"] = dir2text(targetdirection) + return data - switch(href_list["operation"]) - if("replace") +// Actions received from TGUI +/mob/living/simple_animal/bot/floorbot/ui_act(action, params) + . = ..() + if(. || !hasSiliconAccessInArea(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) + return TRUE + switch(action) + if("place_custom") replacetiles = !replacetiles - if("place") + if("place_tiles") placetiles = !placetiles - if("fix") + if("repair_damage") fixfloors = !fixfloors - if("autotile") + if("tile_hull") autotile = !autotile - if("anchor") + if("traction_magnets") anchored = !anchored - if("eject") + if("eject_tiles") if(specialtiles && tiletype != null) empty_tiles() - if("linemode") + if("line_mode") var/setdir = input("Select construction direction:") as null|anything in list("north","east","south","west","disable") switch(setdir) if("north") @@ -193,7 +181,7 @@ targetdirection = 8 if("disable") targetdirection = null - update_controls() + return /mob/living/simple_animal/bot/floorbot/proc/empty_tiles() new tiletype(drop_location(), specialtiles) diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index 9625d2b084..eb415b9bb5 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -78,24 +78,6 @@ text_dehack = "You reboot [name] and restore the sound control system." text_dehack_fail = "[name] refuses to accept your authority!" -/mob/living/simple_animal/bot/honkbot/get_controls(mob/user) - var/dat - dat += hack(user) - dat += showpai(user) - dat += text({" -Honkomatic Bike Horn Unit v1.0.7 controls

-Status: []
-Behaviour controls are [locked ? "locked" : "unlocked"]
-Maintenance panel panel is [open ? "opened" : "closed"]"}, - -"[on ? "On" : "Off"]" ) - - if(!locked || hasSiliconAccessInArea(user) || IsAdminGhost(user)) - dat += text({"
Auto Patrol: []"}, - -"[auto_patrol ? "On" : "Off"]" ) - return dat - /mob/living/simple_animal/bot/honkbot/proc/judgement_criteria() var/final = NONE if(check_records) diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm index b343328e7f..e00adfd8e8 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -155,87 +155,63 @@ /mob/living/simple_animal/bot/medbot/attack_paw(mob/user) return attack_hand(user) -/mob/living/simple_animal/bot/medbot/get_controls(mob/user) - var/dat - dat += hack(user) - dat += showpai(user) - dat += "Medical Unit Controls v1.1

" - dat += "Status: [on ? "On" : "Off"]
" - dat += "Maintenance panel panel is [open ? "opened" : "closed"]
" - dat += "Beaker: " +// Variables sent to TGUI +/mob/living/simple_animal/bot/medbot/ui_data(mob/user) + var/list/data = ..() if(reagent_glass) - dat += "Loaded \[[reagent_glass.reagents.total_volume]/[reagent_glass.reagents.maximum_volume]\]" - else - dat += "None Loaded" - dat += "
Behaviour controls are [locked ? "locked" : "unlocked"]
" + data["custom_controls"]["beaker"] = reagent_glass + data["custom_contrlos"]["reagents"] = "[reagent_glass.reagents.total_volume]/[reagent_glass.reagents.maximum_volume]" if(!locked || hasSiliconAccessInArea(user) || IsAdminGhost(user)) - dat += "Healing Threshold: " - dat += "-- " - dat += "- " - dat += "[heal_threshold] " - dat += "+ " - dat += "++" - dat += "
" + data["custom_controls"]["injection_amount"] = injection_amount + data["custom_controls"]["use_beaker"] = use_beaker + data["custom_controls"]["treat_virus"] = treat_virus + data["custom_controls"]["heal_threshold"] = heal_threshold + data["custom_controls"]["speaker"] = !shut_up + data["custom_controls"]["crit_alerts"] = declare_crit + data["custom_controls"]["stationary_mode"] = stationary_mode + return data - dat += "Injection Level: " - dat += "- " - dat += "[injection_amount] " - dat += "+ " - dat += "
" +// Actions received from TGUI +/mob/living/simple_animal/bot/medbot/ui_act(action, params) + . = ..() + if(. || !hasSiliconAccessInArea(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) + return TRUE + switch(action) + if("heal_threshold") + var/adjust_num = round(text2num(params["threshold"])) + heal_threshold = adjust_num + if(heal_threshold < 5) + heal_threshold = 5 + if(heal_threshold > 75) + heal_threshold = 75 - dat += "Reagent Source: " - dat += "[use_beaker ? "Loaded Beaker (When available)" : "Internal Synthesizer"]
" + if("injection_amount") + var/adjust_num = round(text2num(params["amount"])) + injection_amount = adjust_num + if(injection_amount < 1) + injection_amount = 1 + if(injection_amount > 15) + injection_amount = 15 - dat += "Treat Viral Infections: [treat_virus ? "Yes" : "No"]
" - dat += "The speaker switch is [shut_up ? "off" : "on"]. Toggle
" - dat += "Critical Patient Alerts: [declare_crit ? "Yes" : "No"]
" - dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
" - dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
" + if("use_beaker") + use_beaker = !use_beaker - return dat + if("eject") + if(!isnull(reagent_glass)) + reagent_glass.forceMove(drop_location()) + reagent_glass = null -/mob/living/simple_animal/bot/medbot/Topic(href, href_list) - if(..()) - return 1 + if("speaker") + shut_up = !shut_up + if("crit_alerts") + declare_crit = !declare_crit + if("stationary_mode") + stationary_mode = !stationary_mode + path = list() + update_appearance() - if(href_list["adj_threshold"]) - var/adjust_num = text2num(href_list["adj_threshold"]) - heal_threshold += adjust_num - if(heal_threshold < 5) - heal_threshold = 5 - if(heal_threshold > 75) - heal_threshold = 75 - - else if(href_list["adj_inject"]) - var/adjust_num = text2num(href_list["adj_inject"]) - injection_amount += adjust_num - if(injection_amount < 5) - injection_amount = 5 - if(injection_amount > 15) - injection_amount = 15 - - else if(href_list["use_beaker"]) - use_beaker = !use_beaker - - else if(href_list["eject"] && (!isnull(reagent_glass))) - reagent_glass.forceMove(drop_location()) - reagent_glass = null - - else if(href_list["togglevoice"]) - shut_up = !shut_up - - else if(href_list["critalerts"]) - declare_crit = !declare_crit - - else if(href_list["stationary"]) - stationary_mode = !stationary_mode - path = list() - update_icon() - - else if(href_list["virus"]) - treat_virus = !treat_virus - - update_controls() + if("virus") + treat_virus = !treat_virus return /mob/living/simple_animal/bot/medbot/attackby(obj/item/W as obj, mob/user as mob, params) @@ -251,7 +227,6 @@ reagent_glass = W to_chat(user, "You insert [W].") - show_controls(user) else if(istype(W, /obj/item/reagent_containers/syringe/piercing)) if(bot_core.allowed(user) && open && !(upgrades & UPGRADE_MEDICAL_PIERERCING)) diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index 077e31062a..dcb5e5f237 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -276,56 +276,6 @@ if("ejectpai") ejectpairemote(user) -// TODO: remove this; PDAs currently depend on it -/mob/living/simple_animal/bot/mulebot/get_controls(mob/user) - var/ai = hasSiliconAccessInArea(user) - var/dat - dat += "

Multiple Utility Load Effector Mk. V

" - dat += "ID: [id]
" - dat += "Power: [on ? "On" : "Off"]
" - dat += "

Status

" - dat += "
" - switch(mode) - if(BOT_IDLE) - dat += "Ready" - if(BOT_DELIVER) - dat += "[mode_name[BOT_DELIVER]]" - if(BOT_GO_HOME) - dat += "[mode_name[BOT_GO_HOME]]" - if(BOT_BLOCKED) - dat += "[mode_name[BOT_BLOCKED]]" - if(BOT_NAV,BOT_WAIT_FOR_NAV) - dat += "[mode_name[BOT_NAV]]" - if(BOT_NO_ROUTE) - dat += "[mode_name[BOT_NO_ROUTE]]" - dat += "
" - - dat += "Current Load: [load ? load.name : "none"]
" - dat += "Destination: [!destination ? "none" : destination]
" - dat += "Power level: [cell ? cell.percent() : 0]%" - - if(locked && !ai && !IsAdminGhost(user)) - dat += " 
Controls are locked
Unlock Controls" - else - dat += " 
Controls are unlocked
Lock Controls

" - - dat += "Toggle Power
" - dat += "Stop
" - dat += "Proceed
" - dat += "Return to Home
" - dat += "Set Destination
" - dat += "Set Bot ID
" - dat += "Set Home
" - dat += "Toggle Auto Return Home ([auto_return ? "On":"Off"])
" - dat += "Toggle Auto Pickup Crate ([auto_pickup ? "On":"Off"])
" - dat += "Toggle Delivery Reporting ([report_delivery ? "On" : "Off"])
" - if(load) - dat += "Unload Now
" - dat += "
The maintenance hatch is closed.
" - - return dat - - // returns true if the bot has power /mob/living/simple_animal/bot/mulebot/proc/has_power() return !open && cell && cell.charge > 0 && (!wires.is_cut(WIRE_POWER1) && !wires.is_cut(WIRE_POWER2)) diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index 8bb30a11a6..c3ab805d62 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -209,57 +209,34 @@ text_dehack = "You reboot [name] and restore the target identification." text_dehack_fail = "[name] refuses to accept your authority!" -/mob/living/simple_animal/bot/secbot/get_controls(mob/user) - var/dat - dat += hack(user) - dat += showpai(user) - dat += text({" -Securitron v1.6 controls

-Status: []
-Behaviour controls are [locked ? "locked" : "unlocked"]
-Maintenance panel panel is [open ? "opened" : "closed"]"}, - -"[on ? "On" : "Off"]" ) - +// Variables sent to TGUI +/mob/living/simple_animal/bot/secbot/ui_data(mob/user) + var/list/data = ..() if(!locked || hasSiliconAccessInArea(user) || IsAdminGhost(user)) - dat += text({"
-Arrest Unidentifiable Persons: []
-Arrest for Unauthorized Weapons: []
-Arrest for Warrant: []
-Operating Mode: []
-Report Arrests[]
-Auto Patrol: []"}, + data["custom_controls"]["check_id"] = idcheck + data["custom_controls"]["check_weapons"] = weaponscheck + data["custom_controls"]["check_warrants"] = check_records + data["custom_controls"]["handcuff_targets"] = !arrest_type + data["custom_controls"]["arrest_alert"] = declare_arrests + return data -"[idcheck ? "Yes" : "No"]", -"[weaponscheck ? "Yes" : "No"]", -"[check_records ? "Yes" : "No"]", -"[arrest_type ? "Detain" : "Arrest"]", -"[declare_arrests ? "Yes" : "No"]", -"[auto_patrol ? "On" : "Off"]" ) - - return dat - -/mob/living/simple_animal/bot/secbot/Topic(href, href_list) - if(..()) - return 1 - if(!hasSiliconAccessInArea(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) +// Actions received from TGUI +/mob/living/simple_animal/bot/secbot/ui_act(action, params) + . = ..() + if(. || !hasSiliconAccessInArea(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) return TRUE - switch(href_list["operation"]) - if("idcheck") + switch(action) + if("check_id") idcheck = !idcheck - update_controls() - if("weaponscheck") + if("check_weapons") weaponscheck = !weaponscheck - update_controls() - if("ignorerec") + if("check_warrants") check_records = !check_records - update_controls() - if("switchmode") + if("handcuff_targets") arrest_type = !arrest_type - update_controls() - if("declarearrests") + if("arrest_alert") declare_arrests = !declare_arrests - update_controls() + return /mob/living/simple_animal/bot/secbot/proc/retaliate(mob/living/carbon/human/H) var/judgement_criteria = judgement_criteria() diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index dd7c946d3c..2a978bedb4 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -308,10 +308,15 @@ adjustHealth(unsuitable_atmos_damage) /mob/living/simple_animal/gib(no_brain, no_organs, no_bodyparts, datum/explosion/was_explosion) - if(butcher_results) + if(butcher_results || guaranteed_butcher_results) + var/list/butcher = list() + if(butcher_results) + butcher += butcher_results + if(guaranteed_butcher_results) + butcher += guaranteed_butcher_results var/atom/Tsec = drop_location() - for(var/path in butcher_results) - for(var/i in 1 to butcher_results[path]) + for(var/path in butcher) + for(var/i in 1 to butcher[path]) new path(Tsec) ..() diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index c408ad096e..e51e4852e6 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -495,7 +495,11 @@ /datum/status_effect/stabilized/orange/tick() var/body_temperature_difference = BODYTEMP_NORMAL - owner.bodytemperature - owner.adjust_bodytemperature(min(5,body_temperature_difference)) + var/cooling_cap = -5 + if(HAS_TRAIT(owner, TRAIT_ROBOTIC_ORGANISM)) + cooling_cap *= 0.5 //Only cools by half as much (which is 5 per life tick since this ticks twice as much as life) so it isn't true spaceproofness.. + body_temperature_difference += SYNTH_COLD_OFFSET //.. But also cools towards a cold temp, provided there is nothing that counters it. + owner.adjust_bodytemperature(clamp(body_temperature_difference, cooling_cap, 5)) return ..() /datum/status_effect/stabilized/purple diff --git a/code/modules/ruins/objects_and_mobs/ash_walker_den.dm b/code/modules/ruins/objects_and_mobs/ash_walker_den.dm index 2401fc63a0..d6693fcca2 100644 --- a/code/modules/ruins/objects_and_mobs/ash_walker_den.dm +++ b/code/modules/ruins/objects_and_mobs/ash_walker_den.dm @@ -12,14 +12,29 @@ max_integrity = 200 var/faction = list("ashwalker") var/meat_counter = 6 + var/datum/team/ashwalkers/ashies + var/datum/linked_objective -/obj/structure/lavaland/ash_walker/Initialize() +/obj/structure/lavaland/ash_walker/Initialize(mapload) .=..() + ashies = new /datum/team/ashwalkers() + var/datum/objective/protect_object/objective = new + objective.set_target(src) + linked_objective = objective + ashies.objectives += objective START_PROCESSING(SSprocessing, src) +/obj/structure/lavaland/ash_walker/Destroy() + ashies.objectives -= linked_objective + ashies = null + QDEL_NULL(linked_objective) + STOP_PROCESSING(SSprocessing, src) + return ..() + /obj/structure/lavaland/ash_walker/deconstruct(disassembled) new /obj/item/assembly/signaler/anomaly (get_step(loc, pick(GLOB.alldirs))) new /obj/effect/collapse(loc) + return ..() /obj/structure/lavaland/ash_walker/process() consume() @@ -28,20 +43,64 @@ /obj/structure/lavaland/ash_walker/proc/consume() for(var/mob/living/H in view(src, 1)) //Only for corpse right next to/on same tile if(H.stat) - visible_message("Serrated tendrils eagerly pull [H] to [src], tearing the body apart as its blood seeps over the eggs.") - playsound(get_turf(src),'sound/magic/demon_consume.ogg', 100, 1) for(var/obj/item/W in H) if(!H.dropItemToGround(W)) qdel(W) + if(issilicon(H)) //no advantage to sacrificing borgs... + H.gib() + visible_message(span_notice("Serrated tendrils eagerly pull [H] apart, but find nothing of interest.")) + return + + if(H.mind?.has_antag_datum(/datum/antagonist/ashwalker) && (H.key || H.get_ghost(FALSE, TRUE))) //special interactions for dead lava lizards with ghosts attached + visible_message(span_warning("Serrated tendrils carefully pull [H] to [src], absorbing the body and creating it anew.")) + var/datum/mind/deadmind + if(H.key) + deadmind = H + else + deadmind = H.get_ghost(FALSE, TRUE) + to_chat(deadmind, "Your body has been returned to the nest. You are being remade anew, and will awaken shortly.
Your memories will remain intact in your new body, as your soul is being salvaged") + SEND_SOUND(deadmind, sound('sound/magic/enter_blood.ogg',volume=100)) + addtimer(CALLBACK(src, .proc/remake_walker, H.mind, H.real_name), 20 SECONDS) + new /obj/effect/gibspawner/generic(get_turf(H)) + qdel(H) + return + if(ismegafauna(H)) meat_counter += 20 else meat_counter++ + visible_message(span_warning("Serrated tendrils eagerly pull [H] to [src], tearing the body apart as its blood seeps over the eggs.")) + playsound(get_turf(src),'sound/magic/demon_consume.ogg', 100, TRUE) + var/deliverykey = H.fingerprintslast //key of whoever brought the body + var/mob/living/deliverymob = get_mob_by_key(deliverykey) //mob of said key + //there is a 40% chance that the Lava Lizard unlocks their respawn with each sacrifice + if(deliverymob && (deliverymob.mind?.has_antag_datum(/datum/antagonist/ashwalker)) && (deliverykey in ashies.players_spawned) && (prob(40))) + to_chat(deliverymob, span_warning("The Necropolis is pleased with your sacrifice. You feel confident your existence after death is secure.")) + ashies.players_spawned -= deliverykey H.gib() obj_integrity = min(obj_integrity + max_integrity*0.05,max_integrity)//restores 5% hp of tendril + for(var/mob/living/L in view(src, 5)) + if(L.mind?.has_antag_datum(/datum/antagonist/ashwalker)) + SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "headspear", /datum/mood_event/sacrifice_good) + else + SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "headspear", /datum/mood_event/sacrifice_bad) + +/obj/structure/lavaland/ash_walker/proc/remake_walker(datum/mind/oldmind, oldname) + var/mob/living/carbon/human/M = new /mob/living/carbon/human(get_step(loc, pick(GLOB.alldirs))) + M.set_species(/datum/species/lizard/ashwalker) + M.real_name = oldname + M.underwear = "Nude" + M.undershirt = "Nude" + M.socks = "Nude" + M.update_body() + M.remove_language(/datum/language/common) + oldmind.transfer_to(M) + M.mind.grab_ghost() + to_chat(M, "You have been pulled back from beyond the grave, with a new body and renewed purpose. Glory to the Necropolis!") + playsound(get_turf(M),'sound/magic/exit_blood.ogg', 100, TRUE) /obj/structure/lavaland/ash_walker/proc/spawn_mob() if(meat_counter >= ASH_WALKER_SPAWN_THRESHOLD) - new /obj/effect/mob_spawn/human/ash_walker(get_step(loc, pick(GLOB.alldirs))) + new /obj/effect/mob_spawn/human/ash_walker(get_step(loc, pick(GLOB.alldirs)), ashies) visible_message("One of the eggs swells to an unnatural size and tumbles free. It's ready to hatch!") meat_counter -= ASH_WALKER_SPAWN_THRESHOLD diff --git a/code/modules/shuttle/custom_shuttle.dm b/code/modules/shuttle/custom_shuttle.dm index 8047e972db..e9a5ca4688 100644 --- a/code/modules/shuttle/custom_shuttle.dm +++ b/code/modules/shuttle/custom_shuttle.dm @@ -35,67 +35,63 @@ . = ..() . += distance_multiplier < 1 ? "Bluespace shortcut module installed. Route is [distance_multiplier]x the original length." : "" -/obj/machinery/computer/custom_shuttle/ui_interact(mob/user) +/obj/machinery/computer/custom_shuttle/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CustomShuttleConsole", name) + ui.open() + +/obj/machinery/computer/custom_shuttle/ui_data(mob/user) + var/list/data = list() var/list/options = params2list(possible_destinations) var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) - var/dat = "[M ? "Current Location : [M.getStatusText()]" : "Shuttle link required."]

" + data["docked_location"] = M ? M.get_status_text_tgui() : null if(M) - dat += "Run Flight Calculations
" - dat += "Shuttle Data
" - dat += "Shuttle Mass: [calculated_mass/10]tons
" - dat += "Engine Force: [calculated_dforce]kN ([calculated_engine_count] engines)
" - dat += "Sublight Speed: [calculated_speed]ms-1
" - dat += calculated_speed < 1 ? "INSUFFICIENT ENGINE POWER
" : "" - dat += calculated_non_operational_thrusters > 0 ? "Warning: [calculated_non_operational_thrusters] thrusters offline.
" : "" - dat += "Fuel Consumption: [calculated_consumption]units per distance
" - dat += "Engine Cooldown: [calculated_cooldown]s
" - var/destination_found + calculateStats(FALSE, 0, TRUE) + data["ship_name"] = M.area_type ? M.area_type:name : "ERROR" + data["shuttle_mass"] = calculated_mass/10 + data["engine_force"] = calculated_dforce + data["engines"] = calculated_engine_count + data["calculated_speed"] = calculated_speed + data["damaged_engines"] = calculated_non_operational_thrusters + data["calculated_consumption"] = calculated_consumption + data["calculated_cooldown"] = calculated_cooldown + data["locations"] = list() for(var/obj/docking_port/stationary/S in SSshuttle.stationary) if(!options.Find(S.id)) continue - if(!M.check_dock(S, silent=TRUE)) + if(!M.check_dock(S, silent = TRUE)) continue - if(calculated_speed == 0) - break - destination_found = TRUE - var/dist = round(calculateDistance(S)) - dat += "Target [S.name] (Dist: [dist] | Fuel Cost: [round(dist * calculated_consumption)] | Time: [round(dist / calculated_speed)])
" - if(!destination_found) - dat += "No valid destinations
" - dat += "
[targetLocation ? "Target Location : [targetLocation]" : "No Target Location"]" - dat += "
Initate Flight
" - dat += "Close" + var/list/location_data = list( + id = S.id, + name = S.name, + dist = round(calculateDistance(S)) + ) + data["locations"] += list(location_data) + data["destination"] = targetLocation + return data - popup = new(user, "computer", M ? M.name : "shuttle", 350, 450) - popup.set_content("
[dat]
") - popup.open() - -/obj/machinery/computer/custom_shuttle/Topic(href, href_list) - if(..()) +/obj/machinery/computer/custom_shuttle/ui_act(action, params) + . = ..() + if(.) return - usr.set_machine(src) - src.add_fingerprint(usr) if(!allowed(usr)) to_chat(usr, "Access denied.") return - if(href_list["calculate"]) - calculateStats() - ui_interact(usr) - return var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) if(!M) + to_chat(usr, "Shuttle Link Required.") return if(M.launch_status == ENDGAME_LAUNCHED) return - if(href_list["setloc"]) - SetTargetLocation(href_list["setloc"]) - ui_interact(usr) - return - else if(href_list["fly"]) - Fly() - ui_interact(usr) - return + + switch(action) + if("setloc") + SetTargetLocation(params["setloc"]) + if("fly") + Fly() + return /obj/machinery/computer/custom_shuttle/proc/calculateDistance(var/obj/docking_port/stationary/port) var/deltaX = port.x - x diff --git a/html/changelogs/AutoChangeLog-pr-15444.yml b/html/changelogs/AutoChangeLog-pr-15444.yml new file mode 100644 index 0000000000..58a745168f --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15444.yml @@ -0,0 +1,4 @@ +author: "LiteralMushroom" +delete-after: True +changes: + - rscadd: "Slimes can have wings now." diff --git a/html/changelogs/AutoChangeLog-pr-15448.yml b/html/changelogs/AutoChangeLog-pr-15448.yml new file mode 100644 index 0000000000..6de9c39d46 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15448.yml @@ -0,0 +1,4 @@ +author: "CoreFlare" +delete-after: True +changes: + - rscadd: "Humanity is... Ok. Blood is fuel. IPCs have wings." diff --git a/html/changelogs/AutoChangeLog-pr-15466.yml b/html/changelogs/AutoChangeLog-pr-15466.yml new file mode 100644 index 0000000000..ae3094a427 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15466.yml @@ -0,0 +1,7 @@ +author: "SakuraOran" +delete-after: True +changes: + - rscadd: "Ashwalkers can now heal, even from death, by being incapacitated (asleep/dead) near the tendril" + - rscadd: "Added the ability for antags to have the \"protect object\" objective, which the ashwalkers now have to protect the tendril. This is determined by if the object is broken or not, and can be set to any object or structure that can reasonably be destroyed" + - balance: "Ashwalkers get one life, and have a 40% chance to earn an additional life when they sacrifice to the tendril." + - bugfix: "fixed mobs not dropping guaranteed_butcher_results when gibbed" diff --git a/html/changelogs/AutoChangeLog-pr-15480.yml b/html/changelogs/AutoChangeLog-pr-15480.yml new file mode 100644 index 0000000000..4a03278ac5 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15480.yml @@ -0,0 +1,4 @@ +author: "SandPoot" +delete-after: True +changes: + - rscadd: "Adds a keybinding where you can cancel actions." diff --git a/html/changelogs/AutoChangeLog-pr-15484.yml b/html/changelogs/AutoChangeLog-pr-15484.yml new file mode 100644 index 0000000000..449836f16b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15484.yml @@ -0,0 +1,4 @@ +author: "DeltaFire15" +delete-after: True +changes: + - balance: "The Space Adaptation mutation when applied to Synthetics now nullifies the penalty for passive cooling if in low-pressure environments." diff --git a/html/changelogs/AutoChangeLog-pr-15485.yml b/html/changelogs/AutoChangeLog-pr-15485.yml new file mode 100644 index 0000000000..1ef80faa47 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15485.yml @@ -0,0 +1,4 @@ +author: "SandPoot" +delete-after: True +changes: + - bugfix: "You can once again see your previews for picking target bodyparts on your hud." diff --git a/html/changelogs/AutoChangeLog-pr-15487.yml b/html/changelogs/AutoChangeLog-pr-15487.yml new file mode 100644 index 0000000000..f7c9d1923c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15487.yml @@ -0,0 +1,5 @@ +author: "DeltaFire15" +delete-after: True +changes: + - balance: "Stabilized orange extracts can only cool by -5 degrees per tick base instead of by as much as the difference is." + - balance: "Stabilized orange extracts only have halved cooling effects on Synthetics (2.5 per status tick / 5 per life), but also cool them towards ~-100°C instead of base bodytemp." diff --git a/html/changelogs/AutoChangeLog-pr-15491.yml b/html/changelogs/AutoChangeLog-pr-15491.yml new file mode 100644 index 0000000000..5f93c6a8b6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15491.yml @@ -0,0 +1,6 @@ +author: "SandPoot" +delete-after: True +changes: + - rscadd: "TGUI autolathe +tweak: You can now input a custom amount to print(hard limit at 50 or maximum stack size) +tweak: Can now search and change categories while the autolathe is busy, line up those designs!" diff --git a/html/changelogs/AutoChangeLog-pr-15493.yml b/html/changelogs/AutoChangeLog-pr-15493.yml new file mode 100644 index 0000000000..18bbbc577b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15493.yml @@ -0,0 +1,5 @@ +author: "SandPoot" +delete-after: True +changes: + - rscadd: "All of your favorite simple bot friends aboard the station now have a much better interface in TGUI" + - qol: "You can now lock and unlock bots, including their maintenance hatches, via the UI." diff --git a/html/changelogs/AutoChangeLog-pr-15494.yml b/html/changelogs/AutoChangeLog-pr-15494.yml new file mode 100644 index 0000000000..54d1fd8a7e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15494.yml @@ -0,0 +1,4 @@ +author: "SandPoot" +delete-after: True +changes: + - rscadd: "Made a cool UI for the custom shuttles." diff --git a/tgstation.dme b/tgstation.dme index ff13b237dc..50a44347a4 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1562,6 +1562,7 @@ #include "code\modules\antagonists\abductor\machinery\dispenser.dm" #include "code\modules\antagonists\abductor\machinery\experiment.dm" #include "code\modules\antagonists\abductor\machinery\pad.dm" +#include "code\modules\antagonists\ashwalker\ashwalker.dm" #include "code\modules\antagonists\blob\blob.dm" #include "code\modules\antagonists\blob\blob\blob_report.dm" #include "code\modules\antagonists\blob\blob\overmind.dm" diff --git a/tgui/packages/tgui/interfaces/Autolathe.js b/tgui/packages/tgui/interfaces/Autolathe.js new file mode 100644 index 0000000000..ede46b5526 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Autolathe.js @@ -0,0 +1,202 @@ +import { useBackend, useLocalState } from '../backend'; +import { Button, LabeledList, Section, ProgressBar, Flex, Box, Table, Collapsible, Input, Dimmer, Icon } from '../components'; +import { Window } from '../layouts'; +import { capitalize } from "common/string"; + +export const Autolathe = (props, context) => { + const { act, data } = useBackend(context); + // Extract `health` and `color` variables from the `data` object. + const { + materialtotal, + materialsmax, + materials = [], + categories = [], + designs = [], + active, + } = data; + const [ + current_category, + setCategory, + ] = useLocalState(context, 'current_category', "None"); + const filteredmaterials = materials.filter(material => + material.mineral_amount > 0); + return ( + + +
+ + + + {materialtotal + '/' + materialsmax + ' cm³'} + + + + {filteredmaterials.length > 0 && ( + + + {filteredmaterials.map(filteredmaterial => ( + + +
{filteredmaterial.mineral_amount + ' cm³'}
+
+
+ ))} +
+
)} +
+
+
+
+ { + if (value.length) { + act('search', { + to_search: value, + }); + setCategory('results for "' + value + '"'); + } + }} /> +
+
+ + {categories.map(category => ( + // eslint-disable-next-line react/jsx-key +
+ {current_category.toString() !== "None" && ( +
{ + act('menu'); + setCategory("None"); + }} /> + )}> + {active === 1 && ( + + + {'Building items...'} + + )} + + + {designs.length + && (designs.map(design => ( + + +
+
+
+ )} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/CustomShuttleConsole.js b/tgui/packages/tgui/interfaces/CustomShuttleConsole.js new file mode 100644 index 0000000000..f6d76754a7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CustomShuttleConsole.js @@ -0,0 +1,136 @@ +/* eslint-disable react/jsx-closing-tag-location */ +import { useBackend } from '../backend'; +import { Box, Button, Flex, Icon, LabeledList, Modal, Section, Table, Tooltip } from '../components'; +import { Window } from '../layouts'; + +export const CustomShuttleConsole = (props, context) => { + const { act, data } = useBackend(context); + const { + docked_location, + ship_name = "ERROR", + shuttle_mass = 0, + engine_force = 0, + engines = 0, + calculated_speed = 0, + calculated_non_operational_thrusters = 0, + calculated_consumption = 0, + calculated_cooldown = 0, + locations = [], + destination = null, + } = data; + return ( + + +
+ {!docked_location ? ( + + + + + + + {"Shuttle Link Required."} + + + + ) : ( + <> +
{ship_name}
+
+ + + {docked_location} + + + {shuttle_mass / 10}ton{shuttle_mass !== 1 ? "s" : null} + + + {engine_force}Kn ({engines} engine{engines !== 1 ? "s" : null}) + + + {calculated_speed}ms{-1} {calculated_speed < 1 ? : null} + + {calculated_non_operational_thrusters.len + ? + {calculated_non_operational_thrusters} thruster{calculated_non_operational_thrusters !== 1 ? "s are" : " is"} not operational. + + : null} + + {calculated_consumption} unit{calculated_consumption !== 1 ? "s" : null} per travel + + + {calculated_cooldown}s + + +
+
+ {locations.length===0 && ( + + No valid destinations + + ) || ( + + {locations.map(location => ( + + {location.name} + + + ))} +
({location.dist}m) +
+ )} +
+
+
+ + )} +
+
+
+ ); +}; + +const getLocationNameById = (locations, id) => { + return locations?.find(location => location.id === id)?.name; +}; + +const getLocationIdByName = (locations, name) => { + return locations?.find(location => location.name === name)?.id; +}; + +const STATUS_COLOR_KEYS = { + "In Transit": "good", + "Idle": "average", + "Igniting": "average", + "Recharging": "average", + "Missing": "bad", + "Unauthorized Access": "bad", + "Locked": "bad", +}; diff --git a/tgui/packages/tgui/interfaces/SimpleBot.tsx b/tgui/packages/tgui/interfaces/SimpleBot.tsx new file mode 100644 index 0000000000..b5cfce4be7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/SimpleBot.tsx @@ -0,0 +1,351 @@ +import { multiline } from '../../common/string'; +import { useBackend } from '../backend'; +import { Button, Icon, LabeledControls, NoticeBox, Section, Slider, Stack, Tooltip } from '../components'; +import { Window } from '../layouts'; + +type SimpleBotContext = { + can_hack: number; + locked: number; + emagged: number; + pai: Pai; + settings: Settings; + custom_controls: Controls; +}; + +type Pai = { + allow_pai: number; + card_inserted: number; +}; + +type Settings = { + power: number; + airplane_mode: number; + maintenance_lock: number; + patrol_station: number; +}; + +type Controls = { + [Control: string]: [Value: number]; +}; + +export const SimpleBot = (_, context) => { + const { data } = useBackend(context); + const { can_hack, locked } = data; + const access = (!locked || can_hack); + + return ( + + + + +
}> + {!access + ? (Locked!) + : ()} +
+
+ {access && ( + +
+ +
+
+ )} +
+
+
+ ); +}; + +/** Creates a lock button at the top of the controls */ +const TabDisplay = (_, context) => { + const { act, data } = useBackend(context); + const { can_hack, locked, pai } = data; + const { allow_pai } = pai; + + return ( + <> + {!!can_hack && } + {!!allow_pai && } + + + ); +}; + +/** If user is a bad silicon, they can press this button to hack the bot */ +const HackButton = (_, context) => { + const { act, data } = useBackend(context); + const { can_hack, emagged } = data; + + return ( + + ); +}; + +/** Creates a button indicating PAI status and offers the eject action */ +const PaiButton = (_, context) => { + const { act, data } = useBackend(context); + const { card_inserted } = data.pai; + + if (!card_inserted) { + return ( + + ); + } else { + return ( + + ); + } +}; + +/** Displays the bot's standard settings: Power, patrol, etc. */ +const SettingsDisplay = (_, context) => { + const { act, data } = useBackend(context); + const { settings } = data; + const { airplane_mode, patrol_station, power, maintenance_lock } = settings; + + return ( + + + + act('power')} + /> + + + + + act('airplane')} + /> + + + + + act('patrol')} + /> + + + + + act('maintenance')} + /> + + + + ); +}; + +/** Iterates over custom controls. + * Calls the helper to identify which button to use. + */ +const ControlsDisplay = (_, context) => { + const { data } = useBackend(context); + const { custom_controls } = data; + + return ( + + {Object.entries(custom_controls).map((control) => { + return ( + + letter.toUpperCase())}> + + + ); + })} + + ); +}; + +/** Helper function which identifies which button to create. + * Might need some fine tuning if you are using more advanced controls. + */ +const ControlHelper = (props, context) => { + const { act } = useBackend(context); + const { control } = props; + if (control[0] === 'sync_tech') { + /** Control is for sync - this is medbot specific */ + return ; + } else if (control[0] === 'heal_threshold') { + /** Control is a threshold - this is medbot specific */ + return ; + } else if (control[0] === 'injection_amount') { + /** Control is for injection - this is medbot specific */ + return ; + } else if (control[0] === 'tile_stack') { + return ; + } else if (control[0] === 'line_mode') { + return ; + } else { + /** Control is a boolean of some type */ + return ( + act(control[0])} + /> + ); + } +}; + +/** Small button to sync medbots with research. */ +const MedbotSync = (_, context) => { + const { act } = useBackend(context); + + return ( + + act('sync_tech')} + /> + + ); +}; + +/** Slider button for medbot injection thresholds */ +const InjectionThreshold = (props, context) => { + const { act } = useBackend(context); + const { control } = props; + + return ( + + act(control[0], { amount: value })} + /> + + ); +}; + +/** Slider button for medbot healing thresholds */ +const MedbotThreshold = (props, context) => { + const { act } = useBackend(context); + const { control } = props; + + return ( + + act(control[0], { threshold: value })} + /> + + ); +}; + +/** Tile stacks for floorbots - shows number and eject button */ +const FloorbotTiles = (props, context) => { + const { act } = useBackend(context); + const { control } = props; + + return ( + + ); +}; + +/** Direction indicator for floorbot when line mode is chosen. */ +const FloorbotLine = (props, context) => { + const { act } = useBackend(context); + const { control } = props; + + return ( + + act('line_mode')} + size={!control[1] ? 2 : 1.5}> + {' '} + {control[1] ? control[1].toString().charAt(0).toUpperCase() : ''} + + + ); +};