diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm index 41068048a1..8793dbabc1 100644 --- a/code/_globalvars/lists/flavor_misc.dm +++ b/code/_globalvars/lists/flavor_misc.dm @@ -158,6 +158,10 @@ GLOBAL_LIST_INIT(ai_core_display_screens, list( else if(input == "Random") input = pick(GLOB.ai_core_display_screens - "Random") + if(input == "Portrait") + var/datum/portrait_picker/tgui = new(usr)//create the datum + tgui.ui_interact(usr)//datum has a tgui component, here we open the window + return "ai-portrait" //just take this until they decide return "ai-[lowertext(input)]" GLOBAL_LIST_INIT(security_depts_prefs, list(SEC_DEPT_RANDOM, SEC_DEPT_NONE, SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY)) diff --git a/code/controllers/subsystem/materials.dm b/code/controllers/subsystem/materials.dm index 23d5a7a2b7..2134be0176 100644 --- a/code/controllers/subsystem/materials.dm +++ b/code/controllers/subsystem/materials.dm @@ -1,6 +1,8 @@ /*! How material datums work Materials are now instanced datums, with an associative list of them being kept in SSmaterials. We only instance the materials once and then re-use these instances for everything. + These materials call on_applied() on whatever item they are applied to, common effects are adding components, changing color and changing description. This allows us to differentiate items based on the material they are made out of.area + */ SUBSYSTEM_DEF(materials) @@ -14,12 +16,16 @@ SUBSYSTEM_DEF(materials) var/list/materialtypes_by_category ///A cache of all material combinations that have been used var/list/list/material_combos - ///List of stackcrafting recipes for materials using rigid materials + ///List of stackcrafting recipes for materials using base recipes + var/list/base_stack_recipes = list( + new /datum/stack_recipe("Chair", /obj/structure/chair/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE), + new /datum/stack_recipe("Toilet", /obj/structure/toilet/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE), + new /datum/stack_recipe("Sink Frame", /obj/structure/sink/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE), + new /datum/stack_recipe("Floor tile", /obj/item/stack/tile/material, 1, 4, 20, applies_mats = TRUE), + ) + ///List of stackcrafting recipes for materials using rigid recipes var/list/rigid_stack_recipes = list( - new /datum/stack_recipe("chair", /obj/structure/chair/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE), - new /datum/stack_recipe("toilet", /obj/structure/toilet/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE), - new /datum/stack_recipe("sink", /obj/structure/sink/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE), - new /datum/stack_recipe("Floor tile", /obj/item/stack/tile/material, 1, 4, 20, applies_mats = TRUE) + // new /datum/stack_recipe("Carving block", /obj/structure/carving_block, 5, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE), ) ///Ran on initialize, populated the materials and materials_by_category dictionaries with their appropiate vars (See these variables for more info) @@ -29,7 +35,11 @@ SUBSYSTEM_DEF(materials) materialtypes_by_category = list() material_combos = list() for(var/type in subtypesof(/datum/material)) - var/datum/material/ref = new type + var/datum/material/ref = type + // if(!(initial(ref.init_flags) & MATERIAL_INIT_MAPLOAD)) + // continue // Do not initialize + + ref = new ref materials[type] = ref for(var/c in ref.categories) materials_by_category[c] += list(ref) @@ -40,7 +50,6 @@ SUBSYSTEM_DEF(materials) InitializeMaterials() return materials[fakemat] || fakemat - ///Returns a list to be used as an object's custom_materials. Lists will be cached and re-used based on the parameters. /datum/controller/subsystem/materials/proc/FindOrCreateMaterialCombo(list/materials_declaration, multiplier) if(!material_combos) diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 520a400630..c426f02733 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -8,15 +8,17 @@ /* * Stacks */ + /obj/item/stack icon = 'icons/obj/stack_objects.dmi' gender = PLURAL - material_modifier = 0.01 + material_modifier = 0.05 //5%, so that a 50 sheet stack has the effect of 5k materials instead of 100k. + max_integrity = 100 var/list/datum/stack_recipe/recipes var/singular_name var/amount = 1 var/max_amount = 50 //also see stack recipes initialisation, param "max_res_amount" must be equal to this max_amount - var/is_cyborg = 0 // It's 1 if module is used by a cyborg, and uses its storage + var/is_cyborg = FALSE // It's TRUE if module is used by a cyborg, and uses its storage var/datum/robot_energy_storage/source var/cost = 1 // How much energy from storage it costs var/merge_type = null // This path and its children should merge with this stack, defaults to src.type @@ -25,7 +27,6 @@ var/list/mats_per_unit //list that tells you how much is in a single unit. ///Datum material type that this stack is made of var/material_type - max_integrity = 100 //NOTE: When adding grind_results, the amounts should be for an INDIVIDUAL ITEM - these amounts will be multiplied by the stack size in on_grind() var/obj/structure/table/tableVariant // we tables now (stores table variant to be built from this stack) @@ -36,16 +37,8 @@ var/absorption_capacity /// How quickly we lower the blood flow on a cut wound we're bandaging. Expected lifetime of this bandage in ticks is thus absorption_capacity/absorption_rate, or until the cut heals, whichever comes first var/absorption_rate - -/obj/item/stack/on_grind() - for(var/i in 1 to grind_results.len) //This should only call if it's ground, so no need to check if grind_results exists - grind_results[grind_results[i]] *= get_amount() //Gets the key at position i, then the reagent amount of that key, then multiplies it by stack size - -/obj/item/stack/grind_requirements() - if(is_cyborg) - to_chat(usr, "[src] is electronically synthesized in your chassis and can't be ground up!") - return - return TRUE + /// Amount of matter for RCD + var/matter_amount = 0 /obj/item/stack/Initialize(mapload, new_amount, merge = TRUE) if(new_amount != null) @@ -55,16 +48,16 @@ new type(loc, max_amount, FALSE) if(!merge_type) merge_type = type - if(custom_materials && custom_materials.len) - mats_per_unit = list() - var/in_process_mat_list = custom_materials.Copy() - for(var/i in custom_materials) - mats_per_unit[SSmaterials.GetMaterialRef(i)] = in_process_mat_list[i] - custom_materials[i] *= amount + + if(LAZYLEN(mats_per_unit)) + set_mats_per_unit(mats_per_unit, 1) + else if(LAZYLEN(custom_materials)) + set_mats_per_unit(custom_materials, amount ? 1/amount : 1) + . = ..() if(merge) for(var/obj/item/stack/S in loc) - if(S.merge_type == merge_type) + if(can_merge(S)) INVOKE_ASYNC(src, .proc/merge, S) var/list/temp_recipes = get_main_recipes() recipes = temp_recipes.Copy() @@ -73,12 +66,42 @@ for(var/i in M.categories) switch(i) if(MAT_CATEGORY_BASE_RECIPES) + var/list/temp = SSmaterials.base_stack_recipes.Copy() + recipes += temp + if(MAT_CATEGORY_RIGID) var/list/temp = SSmaterials.rigid_stack_recipes.Copy() recipes += temp update_weight() update_icon() +/** Sets the amount of materials per unit for this stack. + * + * Arguments: + * - [mats][/list]: The value to set the mats per unit to. + * - multiplier: The amount to multiply the mats per unit by. Defaults to 1. + */ +/obj/item/stack/proc/set_mats_per_unit(list/mats, multiplier=1) + mats_per_unit = SSmaterials.FindOrCreateMaterialCombo(mats, multiplier) + update_custom_materials() + +/** Updates the custom materials list of this stack. + */ +/obj/item/stack/proc/update_custom_materials() + set_custom_materials(mats_per_unit, amount) + +/obj/item/stack/on_grind() + . = ..() + for(var/i in 1 to length(grind_results)) //This should only call if it's ground, so no need to check if grind_results exists + grind_results[grind_results[i]] *= get_amount() //Gets the key at position i, then the reagent amount of that key, then multiplies it by stack size + +/obj/item/stack/grind_requirements() + if(is_cyborg) + to_chat(usr, "[src] is electronically synthesized in your chassis and can't be ground up!") + return + return TRUE + /obj/item/stack/proc/get_main_recipes() + SHOULD_CALL_PARENT(TRUE) return list()//empty list /obj/item/stack/proc/update_weight() @@ -99,12 +122,6 @@ else icon_state = "[initial(icon_state)]_3" - -/obj/item/stack/Destroy() - if (usr && usr.machine==src) - usr << browse(null, "window=stack") - . = ..() - /obj/item/stack/examine(mob/user) . = ..() if (is_cyborg) @@ -126,151 +143,158 @@ /obj/item/stack/proc/get_amount() if(is_cyborg) - . = round(source.energy / cost) + . = round(source?.energy / cost) else . = (amount) -/obj/item/stack/attack_self(mob/user) - interact(user) +/** + * Builds all recipes in a given recipe list and returns an association list containing them + * + * Arguments: + * * recipe_to_iterate - The list of recipes we are using to build recipes + */ +/obj/item/stack/proc/recursively_build_recipes(list/recipe_to_iterate) + var/list/L = list() + for(var/recipe in recipe_to_iterate) + if(istype(recipe, /datum/stack_recipe_list)) + var/datum/stack_recipe_list/R = recipe + L["[R.title]"] = recursively_build_recipes(R.recipes) + if(istype(recipe, /datum/stack_recipe)) + var/datum/stack_recipe/R = recipe + L["[R.title]"] = build_recipe(R) + return L -/obj/item/stack/interact(mob/user, sublist) - ui_interact(user, sublist) +/** + * Returns a list of properties of a given recipe + * + * Arguments: + * * R - The stack recipe we are using to get a list of properties + */ +/obj/item/stack/proc/build_recipe(datum/stack_recipe/R) + return list( + "res_amount" = R.res_amount, + "max_res_amount" = R.max_res_amount, + "req_amount" = R.req_amount, + "ref" = "\ref[R]", + ) -/obj/item/stack/ui_interact(mob/user, recipes_sublist) +/** + * Checks if the recipe is valid to be used + * + * Arguments: + * * R - The stack recipe we are checking if it is valid + * * recipe_list - The list of recipes we are using to check the given recipe + */ +/obj/item/stack/proc/is_valid_recipe(datum/stack_recipe/R, list/recipe_list) + for(var/S in recipe_list) + if(S == R) + return TRUE + if(istype(S, /datum/stack_recipe_list)) + var/datum/stack_recipe_list/L = S + if(is_valid_recipe(R, L.recipes)) + return TRUE + return FALSE + +/obj/item/stack/ui_state(mob/user) + return GLOB.hands_state + +/obj/item/stack/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Stack", name) + ui.open() + +/obj/item/stack/ui_data(mob/user) + var/list/data = list() + data["amount"] = get_amount() + return data + +/obj/item/stack/ui_static_data(mob/user) + var/list/data = list() + data["recipes"] = recursively_build_recipes(recipes) + return data + +/obj/item/stack/ui_act(action, params) . = ..() - if (!recipes) + if(.) return - if (!src || get_amount() <= 0) - user << browse(null, "window=stack") - user.set_machine(src) //for correct work of onclose - var/list/recipe_list = recipes - if (recipes_sublist && recipe_list[recipes_sublist] && istype(recipe_list[recipes_sublist], /datum/stack_recipe_list)) - var/datum/stack_recipe_list/srl = recipe_list[recipes_sublist] - recipe_list = srl.recipes - var/t1 = "Amount Left: [get_amount()]
" - for(var/i in 1 to length(recipe_list)) - var/E = recipe_list[i] - if (isnull(E)) - t1 += "
" - continue - if (i>1 && !isnull(recipe_list[i-1])) - t1+="
" - if (istype(E, /datum/stack_recipe_list)) - var/datum/stack_recipe_list/srl = E - t1 += "[srl.title]" - - if (istype(E, /datum/stack_recipe)) - var/datum/stack_recipe/R = E - var/max_multiplier = round(get_amount() / R.req_amount) - var/title - var/can_build = 1 - can_build = can_build && (max_multiplier>0) - - if (R.res_amount>1) - title+= "[R.res_amount]x [R.title]\s" - else - title+= "[R.title]" - title+= " ([R.req_amount] [singular_name]\s)" - if (can_build) - t1 += text("[title] ") - else - t1 += text("[]", title) - continue - if (R.max_res_amount>1 && max_multiplier>1) - max_multiplier = min(max_multiplier, round(R.max_res_amount/R.res_amount)) - t1 += " |" - var/list/multipliers = list(5,10,25) - for (var/n in multipliers) - if (max_multiplier>=n) - t1 += " [n*R.res_amount]x" - if (!(max_multiplier in multipliers)) - t1 += " [max_multiplier*R.res_amount]x" - - var/datum/browser/popup = new(user, "stack", name, 400, 400) - popup.set_content(t1) - popup.open(0) - onclose(user, "stack") - -/obj/item/stack/Topic(href, href_list) - ..() - if (usr.restrained() || usr.stat || usr.get_active_held_item() != src) - return - if (href_list["sublist"] && !href_list["make"]) - interact(usr, text2num(href_list["sublist"])) - if (href_list["make"]) - if (get_amount() < 1 && !is_cyborg) - qdel(src) - - var/list/recipes_list = recipes - if (href_list["sublist"]) - var/datum/stack_recipe_list/srl = recipes_list[text2num(href_list["sublist"])] - recipes_list = srl.recipes - var/datum/stack_recipe/R = recipes_list[text2num(href_list["make"])] - var/multiplier = text2num(href_list["multiplier"]) - if (!multiplier ||(multiplier <= 0)) //href protection - return - if(!building_checks(R, multiplier)) - return - if (R.time) - var/adjusted_time = 0 - usr.visible_message("[usr] starts building [R.title].", "You start building [R.title]...") - if(HAS_TRAIT(usr, R.trait_booster)) - adjusted_time = (R.time * R.trait_modifier) - else - adjusted_time = R.time - if (!do_after(usr, adjusted_time, target = usr)) + switch(action) + if("make") + if(get_amount() < 1 && !is_cyborg) + qdel(src) + return + var/datum/stack_recipe/R = locate(params["ref"]) + if(!is_valid_recipe(R, recipes)) //href exploit protection + return + var/multiplier = text2num(params["multiplier"]) + if(!multiplier || (multiplier <= 0)) //href exploit protection return if(!building_checks(R, multiplier)) return + if(R.time) + var/adjusted_time = 0 + usr.visible_message("[usr] starts building \a [R.title].", "You start building \a [R.title]...") + if(HAS_TRAIT(usr, R.trait_booster)) + adjusted_time = (R.time * R.trait_modifier) + else + adjusted_time = R.time + if(!do_after(usr, adjusted_time, target = usr)) + return + if(!building_checks(R, multiplier)) + return - var/obj/O - if(R.max_res_amount > 1) //Is it a stack? - O = new R.result_type(usr.drop_location(), R.res_amount * multiplier) - else if(ispath(R.result_type, /turf)) - var/turf/T = usr.drop_location() - if(!isturf(T)) - return - T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR) - else - O = new R.result_type(get_turf(usr)) - if(O) - O.setDir(usr.dir) - log_craft("[O] crafted by [usr] at [loc_name(O.loc)]") + var/obj/O + if(R.max_res_amount > 1) //Is it a stack? + O = new R.result_type(usr.drop_location(), R.res_amount * multiplier) + else if(ispath(R.result_type, /turf)) + var/turf/T = usr.drop_location() + if(!isturf(T)) + return + T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR) + else + O = new R.result_type(usr.drop_location()) + if(O) + O.setDir(usr.dir) + use(R.req_amount * multiplier) - use(R.req_amount * multiplier) + if(R.applies_mats && LAZYLEN(mats_per_unit)) + if(isstack(O)) + var/obj/item/stack/crafted_stack = O + crafted_stack.set_mats_per_unit(mats_per_unit, R.req_amount / R.res_amount) + else + O.set_custom_materials(mats_per_unit, R.req_amount / R.res_amount) - if(R.applies_mats && custom_materials && custom_materials.len) - var/list/used_materials = list() - for(var/i in custom_materials) - used_materials[SSmaterials.GetMaterialRef(i)] = R.req_amount / R.res_amount * (MINERAL_MATERIAL_AMOUNT / custom_materials.len) - O.set_custom_materials(used_materials) + if(istype(O, /obj/structure/windoor_assembly)) + var/obj/structure/windoor_assembly/W = O + W.ini_dir = W.dir + else if(istype(O, /obj/structure/window)) + var/obj/structure/window/W = O + W.ini_dir = W.dir - //START: oh fuck i'm so sorry - if(istype(O, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = O - W.ini_dir = W.dir - else if(istype(O, /obj/structure/window)) - var/obj/structure/window/W = O - W.ini_dir = W.dir - //END: oh fuck i'm so sorry + if(QDELETED(O)) + return //It's a stack and has already been merged - else if(istype(O, /obj/item/restraints/handcuffs/cable)) - var/obj/item/cuffs = O - cuffs.color = color + if(isitem(O)) + usr.put_in_hands(O) + O.add_fingerprint(usr) - if (QDELETED(O)) - return //It's a stack and has already been merged + //BubbleWrap - so newly formed boxes are empty + if(istype(O, /obj/item/storage)) + for (var/obj/item/I in O) + qdel(I) + //BubbleWrap END + return TRUE - if (isitem(O)) - usr.put_in_hands(O) - O.add_fingerprint(usr) - - //BubbleWrap - so newly formed boxes are empty - if ( istype(O, /obj/item/storage) ) - for (var/obj/item/I in O) - qdel(I) - //BubbleWrap END +/obj/item/stack/vv_edit_var(vname, vval) + if(vname == NAMEOF(src, amount)) + add(clamp(vval, 1-amount, max_amount - amount)) //there must always be one. + return TRUE + else if(vname == NAMEOF(src, max_amount)) + max_amount = max(vval, 1) + add((max_amount < amount) ? (max_amount - amount) : 0) //update icon, weight, ect + return TRUE + return ..() /obj/item/stack/proc/building_checks(datum/stack_recipe/R, multiplier) if (get_amount() < R.req_amount*multiplier) @@ -330,10 +354,7 @@ if(check && zero_amount()) return TRUE if(length(mats_per_unit)) - var/temp_materials = custom_materials.Copy() - for(var/i in mats_per_unit) - temp_materials[i] = mats_per_unit[i] * src.amount - set_custom_materials(temp_materials) + update_custom_materials() update_icon() update_weight() return TRUE @@ -357,22 +378,36 @@ return source.energy < cost if(amount < 1) qdel(src) - return 1 - return 0 + return TRUE + return FALSE -/obj/item/stack/proc/add(amount) +/** Adds some number of units to this stack. + * + * Arguments: + * - _amount: The number of units to add to this stack. + */ +/obj/item/stack/proc/add(_amount) if (is_cyborg) - source.add_charge(amount * cost) + source.add_charge(_amount * cost) else - src.amount += amount + amount += _amount if(length(mats_per_unit)) - var/temp_materials = custom_materials.Copy() - for(var/i in mats_per_unit) - temp_materials[i] = mats_per_unit[i] * src.amount - set_custom_materials(temp_materials) + update_custom_materials() update_icon() update_weight() +/** Checks whether this stack can merge itself into another stack. + * + * Arguments: + * - [check][/obj/item/stack]: The stack to check for mergeability. + */ +/obj/item/stack/proc/can_merge(obj/item/stack/check) + if(!istype(check, merge_type)) + return FALSE + if(!check.is_cyborg && (mats_per_unit != check.mats_per_unit)) // Cyborg stacks don't have materials. This lets them recycle sheets and floor tiles. + return FALSE + return TRUE + /obj/item/stack/proc/merge(obj/item/stack/S) //Merge src into S, as much as possible if(QDELETED(S) || QDELETED(src) || S == src) //amusingly this can cause a stack to consume itself, let's not allow that. return @@ -388,50 +423,51 @@ S.add(transfer) return transfer -/obj/item/stack/Crossed(obj/o) - if(istype(o, merge_type) && !o.throwing) - merge(o) +/obj/item/stack/Crossed(atom/movable/crossing) + if(!crossing.throwing && can_merge(crossing)) + merge(crossing) . = ..() -/obj/item/stack/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(istype(AM, merge_type)) - merge(AM) +/obj/item/stack/hitby(atom/movable/hitting, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(can_merge(hitting)) + merge(hitting) . = ..() -/obj/item/stack/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/stack/on_attack_hand(mob/user) if(user.get_inactive_held_item() == src) if(zero_amount()) return - return change_stack(user,1) - else - . = ..() + return split_stack(user, 1) /obj/item/stack/AltClick(mob/living/user) . = ..() - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + if(isturf(loc)) // to prevent people that are alt clicking a tile to see its content from getting undesidered pop ups return - if(is_cyborg) + if(is_cyborg || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, FALSE) || zero_amount()) //, !iscyborg(user) return - else - if(zero_amount()) - return - //get amount from user - var/max = get_amount() - var/stackmaterial = round(input(user,"How many sheets do you wish to take out of this stack? (Maximum [max])") as null|num) - max = get_amount() - stackmaterial = min(max, stackmaterial) - if(stackmaterial == null || stackmaterial <= 0 || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return TRUE - else - change_stack(user, stackmaterial) - to_chat(user, "You take [stackmaterial] sheets out of the stack") - return TRUE + //get amount from user + var/max = get_amount() + var/stackmaterial = round(input(user,"How many sheets do you wish to take out of this stack? (Maximum [max])") as null|num) + max = get_amount() + stackmaterial = min(max, stackmaterial) + if(stackmaterial == null || stackmaterial <= 0 || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, FALSE)) //, !iscyborg(user) + return + split_stack(user, stackmaterial) + to_chat(user, "You take [stackmaterial] sheets out of the stack.") -/obj/item/stack/proc/change_stack(mob/user, amount) +/** Splits the stack into two stacks. + * + * Arguments: + * - [user][/mob]: The mob splitting the stack. + * - amount: The number of units to split from this stack. + */ +/obj/item/stack/proc/split_stack(mob/user, amount) if(!use(amount, TRUE, FALSE)) - return FALSE + return null var/obj/item/stack/F = new type(user? user : drop_location(), amount, FALSE) . = F + F.set_mats_per_unit(mats_per_unit, 1) // Required for greyscale sheets and tiles. F.copy_evidences(src) if(user) if(!user.put_in_hands(F, merge_stacks = FALSE)) @@ -441,7 +477,7 @@ zero_amount() /obj/item/stack/attackby(obj/item/W, mob/user, params) - if(istype(W, merge_type)) + if(can_merge(W)) var/obj/item/stack/S = W if(merge(S)) to_chat(user, "Your [S.name] stack now contains [S.get_amount()] [S.singular_name]\s.") @@ -449,14 +485,11 @@ . = ..() /obj/item/stack/proc/copy_evidences(obj/item/stack/from) - if(from.blood_DNA) - blood_DNA = from.blood_DNA.Copy() - if(from.fingerprints) - fingerprints = from.fingerprints.Copy() - if(from.fingerprintshidden) - fingerprintshidden = from.fingerprintshidden.Copy() - if(from.fingerprintslast) - fingerprintslast = from.fingerprintslast + add_blood_DNA(from.return_blood_DNA()) + add_fingerprint_list(from.return_fingerprints()) + add_hiddenprint_list(from.return_hiddenprints()) + fingerprintslast = from.fingerprintslast + //TODO bloody overlay /obj/item/stack/microwave_act(obj/machinery/microwave/M) if(istype(M) && M.dirty < 100) diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm index a5ff27e75e..ddc366736e 100644 --- a/code/modules/mining/machine_stacking.dm +++ b/code/modules/mining/machine_stacking.dm @@ -7,7 +7,9 @@ desc = "Controls a stacking machine... in theory." density = FALSE circuit = /obj/item/circuitboard/machine/stacking_unit_console + /// Connected stacking machine var/obj/machinery/mineral/stacking_machine/machine + /// Direction for which console looks for stacking machine to connect to var/machinedir = SOUTHEAST /obj/machinery/mineral/stacking_unit_console/Initialize() @@ -16,50 +18,53 @@ if (machine) machine.CONSOLE = src -/obj/machinery/mineral/stacking_unit_console/ui_interact(mob/user) - . = ..() - - if(!machine) - to_chat(user, "[src] is not linked to a machine!") - return - - var/obj/item/stack/sheet/s - var/dat - - dat += text("Stacking unit console

") - - for(var/O in machine.stack_list) - s = machine.stack_list[O] - if(s.amount > 0) - dat += text("[capitalize(s.name)]: [s.amount] Release
") - - dat += text("
Stacking: [machine.stack_amt]

") - - user << browse(dat, "window=console_stacking_machine") - /obj/machinery/mineral/stacking_unit_console/multitool_act(mob/living/user, obj/item/I) - if(istype(I, /obj/item/multitool)) - var/obj/item/multitool/M = I - M.buffer = src - to_chat(user, "You store linkage information in [I]'s buffer.") - return TRUE - -/obj/machinery/mineral/stacking_unit_console/Topic(href, href_list) - if(..()) + if(!multitool_check_buffer(user, I)) return - usr.set_machine(src) - src.add_fingerprint(usr) - if(href_list["release"]) - if(!(text2path(href_list["release"]) in machine.stack_list)) - return //someone tried to spawn materials by spoofing hrefs - var/obj/item/stack/sheet/inp = machine.stack_list[text2path(href_list["release"])] - var/obj/item/stack/sheet/out = new inp.type(null, inp.amount) - inp.amount = 0 - machine.unload_mineral(out) + var/obj/item/multitool/M = I + M.buffer = src + to_chat(user, "You store linkage information in [I]'s buffer.") + return TRUE - src.updateUsrDialog() - return +/obj/machinery/mineral/stacking_unit_console/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "StackingConsole", name) + ui.open() +/obj/machinery/mineral/stacking_unit_console/ui_data(mob/user) + var/list/data = list() + data["machine"] = machine ? TRUE : FALSE + data["stacking_amount"] = null + data["contents"] = list() + if(machine) + data["stacking_amount"] = machine.stack_amt + for(var/stack_type in machine.stack_list) + var/obj/item/stack/sheet/stored_sheet = machine.stack_list[stack_type] + if(stored_sheet.amount <= 0) + continue + data["contents"] += list(list( + "type" = stored_sheet.type, + "name" = capitalize(stored_sheet.name), + "amount" = stored_sheet.amount, + )) + return data + +/obj/machinery/mineral/stacking_unit_console/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("release") + var/obj/item/stack/sheet/released_type = text2path(params["type"]) + if(!released_type || !(initial(released_type.merge_type) in machine.stack_list)) + return //someone tried to spawn materials by spoofing hrefs + var/obj/item/stack/sheet/inp = machine.stack_list[initial(released_type.merge_type)] + var/obj/item/stack/sheet/out = new inp.type(null, inp.amount) + inp.amount = 0 + machine.unload_mineral(out) + return TRUE /**********************Mineral stacking unit**************************/ @@ -108,6 +113,17 @@ /obj/machinery/mineral/stacking_machine/proc/process_sheet(obj/item/stack/sheet/inp) if(QDELETED(inp)) return + + // Dump the sheets to the silo if attached + if(materials.silo && !materials.on_hold()) + var/matlist = inp.custom_materials & materials.mat_container.materials + if (length(matlist)) + var/inserted = materials.mat_container.insert_item(inp) + materials.silo_log(src, "collected", inserted, "sheets", matlist) + qdel(inp) + return + + // No silo attached process to internal storage var/key = inp.merge_type var/obj/item/stack/sheet/storage = stack_list[key] if(!storage) //It's the first of this sheet added @@ -115,15 +131,6 @@ storage.amount += inp.amount //Stack the sheets qdel(inp) - if(materials.silo && !materials.on_hold()) //Dump the sheets to the silo - var/matlist = storage.custom_materials & materials.mat_container.materials - if (length(matlist)) - var/inserted = materials.mat_container.insert_item(storage) - materials.silo_log(src, "collected", inserted, "sheets", matlist) - if (QDELETED(storage)) - stack_list -= key - return - while(storage.amount >= stack_amt) //Get rid of excessive stackage var/obj/item/stack/sheet/out = new inp.type(null, stack_amt) unload_mineral(out) diff --git a/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm b/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm new file mode 100644 index 0000000000..ba785251b7 --- /dev/null +++ b/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm @@ -0,0 +1,78 @@ + +//Portrait picker! It's a tgui window that lets you look through all the portraits, and choose one as your AI. + +//very similar to centcom_podlauncher in terms of how this is coded, so i kept a lot of comments from it +//^ wow! it's the second time i've said this! i'm a real coder now, copying my statement of copying other people's stuff. + + +#define TAB_LIBRARY 1 +#define TAB_SECURE 2 +#define TAB_PRIVATE 3 + +/datum/portrait_picker + var/client/holder //client of whoever is using this datum + +/datum/portrait_picker/New(user)//user can either be a client or a mob due to byondcode(tm) + if (istype(user, /client)) + var/client/user_client = user + holder = user_client //if its a client, assign it to holder + else + var/mob/user_mob = user + holder = user_mob.client //if its a mob, assign the mob's client to holder + +/datum/portrait_picker/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PortraitPicker") + ui.open() + +/datum/portrait_picker/ui_close() + qdel(src) + +/datum/portrait_picker/ui_state(mob/user) + return GLOB.conscious_state + +/datum/portrait_picker/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/simple/portraits/library), + get_asset_datum(/datum/asset/simple/portraits/library_secure), + get_asset_datum(/datum/asset/simple/portraits/library_private) + ) + +/datum/portrait_picker/ui_data(mob/user) + var/list/data = list() + data["library"] = SSpersistence.paintings["library"] ? SSpersistence.paintings["library"] : 0 + data["library_secure"] = SSpersistence.paintings["library_secure"] ? SSpersistence.paintings["library_secure"] : 0 + data["library_private"] = SSpersistence.paintings["library_private"] ? SSpersistence.paintings["library_private"] : 0 //i'm gonna regret this, won't i? + return data + +/datum/portrait_picker/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("select") + var/list/tab2key = list(TAB_LIBRARY = "library", TAB_SECURE = "library_secure", TAB_PRIVATE = "library_private") + var/folder = tab2key[params["tab"]] + var/list/current_list = SSpersistence.paintings[folder] + var/list/chosen_portrait = current_list[params["selected"]] + var/png = "data/paintings/[folder]/[chosen_portrait["md5"]].png" + var/icon/portrait_icon = new(png) + var/mob/living/ai = holder.mob + var/w = portrait_icon.Width() + var/h = portrait_icon.Height() + var/mutable_appearance/MA = mutable_appearance(portrait_icon) + if(w == 23 || h == 23) + to_chat(ai, "Small note: 23x23 Portraits are accepted, but they do not fit perfectly inside the display frame.") + MA.pixel_x = 5 + MA.pixel_y = 5 + else if(w == 24 || h == 24) + to_chat(ai, "Portrait Accepted. Enjoy!") + MA.pixel_x = 4 + MA.pixel_y = 4 + else + to_chat(ai, "Sorry, only 23x23 and 24x24 Portraits are accepted.") + return + ai.cut_overlays() //so people can't keep repeatedly select portraits to add stacking overlays + ai.icon_state = "ai-portrait-active"//background + ai.add_overlay(MA) diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index a00145f9dc..b865af8cf4 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -11,7 +11,6 @@ #define MODE_WRITING 1 #define MODE_STAMPING 2 - /** * Paper is now using markdown (like in github pull notes) for ALL rendering * so we do loose a bit of functionality but we gain in easy of use of @@ -23,9 +22,6 @@ icon = 'icons/obj/bureaucracy.dmi' icon_state = "paper" item_state = "paper" - // inhand_icon_state = "paper" - // worn_icon_state = "paper" - // custom_fire_overlay = "paper_onfire_overlay" throwforce = 0 w_class = WEIGHT_CLASS_TINY throw_range = 1 @@ -102,8 +98,8 @@ /obj/item/paper/Initialize() . = ..() - pixel_y = rand(-8, 8) - pixel_x = rand(-9, 9) + pixel_x = base_pixel_x + rand(-9, 9) + pixel_y = base_pixel_y + rand(-8, 8) update_icon() /obj/item/paper/update_icon_state() @@ -193,13 +189,6 @@ user.visible_message(ignition_message) add_fingerprint(user) fire_act(I.get_temperature()) -//I would have it become a paper plane before the throw, but that would risk runtimes -/obj/item/paper/DoRevenantThrowEffects(atom/target) - sleep(10) - if(HAS_TRAIT(src, TRAIT_SPOOKY_THROW)) - return - new /obj/item/paperplane(get_turf(src)) - qdel(src) /obj/item/paper/attackby(obj/item/P, mob/living/user, params) if(burn_paper_product_attackby_check(P, user)) @@ -253,6 +242,8 @@ /obj/item/paper/ui_data(mob/user) var/list/data = list() + data["edit_usr"] = "[user]" + var/obj/O = user.get_active_held_item() if(istype(O, /obj/item/toy/crayon)) var/obj/item/toy/crayon/PEN = O @@ -291,7 +282,8 @@ return data /obj/item/paper/ui_act(action, params,datum/tgui/ui) - if(..()) + . = ..() + if(.) return switch(action) if("stamp") @@ -317,7 +309,8 @@ LAZYADD(stamped, stamp_icon_state) update_static_data(usr,ui) - ui.user.visible_message("[ui.user] stamps [src] with [stamp_class]!", "You stamp [src] with [stamp_class]!") + var/obj/O = ui.user.get_active_held_item() + ui.user.visible_message("[ui.user] stamps [src] with \the [O.name]!", "You stamp [src] with \the [O.name]!") else to_chat(usr, pick("You try to stamp but you miss!", "There is no where else you can stamp!")) . = TRUE diff --git a/tgstation.dme b/tgstation.dme index ea8da532d6..edfa146354 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2635,6 +2635,7 @@ #include "code\modules\mob\living\silicon\silicon_movement.dm" #include "code\modules\mob\living\silicon\ai\ai.dm" #include "code\modules\mob\living\silicon\ai\ai_defense.dm" +#include "code\modules\mob\living\silicon\ai\ai_portrait_picker.dm" #include "code\modules\mob\living\silicon\ai\death.dm" #include "code\modules\mob\living\silicon\ai\examine.dm" #include "code\modules\mob\living\silicon\ai\laws.dm" diff --git a/tgui/packages/tgui/interfaces/ThermoMachine.js b/tgui/packages/tgui/interfaces/ThermoMachine.js index e7e4c0ecd3..bac4f91d4d 100644 --- a/tgui/packages/tgui/interfaces/ThermoMachine.js +++ b/tgui/packages/tgui/interfaces/ThermoMachine.js @@ -36,13 +36,13 @@ export const ThermoMachine = (props, context) => { onClick={() => act('power')} /> )}> - + {/*