mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-01 12:31:32 +00:00
* Modifies right click logic so that it is not the same priority as modifier keys. (#59656) Strips out the existing right click code - Due to the myriad of ways right clicking has been implemented, dedicated signals and procs for right clicking without modifiers are fundamentally incompatible with our system of primary and secondary attacks. Adds additional signals to attacking code. These signals allow atoms to cancel the attack chain early on secondary attacks, or override the standard procs and not send signals to prevent any undesired behaviour from signal handlers. Items that used RightClick procs have been converted to attack_hand_secondary. The slaughter demon, having its own set of snowflake code as poor OOP principles have been applied in UnarmedAttack() procs with lacking calls to parent procs and arbitrary redefinition of behaviour, checks for a right click in its own UnarmedAttack() and performs a bodyslam off that. Storage components now hijack the secondary attackby stage via signals to handle their opening and closing shortcuts on right click. When you right click a storage component equipped item with an object in your active hand, the object has an opportunity to perform its logic in pre secondary attack code and cancel the attack chain. If it does not cancel the attack chain in pre-attack, then the storage component takes over for attackby and, if possible, opens the relevant inventory and ends the attack chain. The forensic scanner is a proof-of-concept of this working in action. With its scan logic moved from afterattack code to pre attack code for right clicking, right clicking with the scanner will now perform a scan where previously one was impossible. Left clicking still does what it always does - Scans at the very end of the attack chain. The logic still isn't perfect - For example, you still can't attack containers in melee even in combat mode (you'll either open them or put your weapon into them regardless of which option you choose) - But this is a better setup overall which allows for items to at least override this behaviour in pre-attack if needed. * Modifies right click logic so that it is not the same priority as modifier keys. * a Co-authored-by: Timberpoes <silent_insomnia_pp@hotmail.co.uk> Co-authored-by: Gandalf <jzo123@hotmail.com>
561 lines
18 KiB
Plaintext
561 lines
18 KiB
Plaintext
/* Stack type objects!
|
|
* Contains:
|
|
* Stacks
|
|
* Recipe datum
|
|
* Recipe list datum
|
|
*/
|
|
|
|
/*
|
|
* Stacks
|
|
*/
|
|
|
|
/obj/item/stack
|
|
icon = 'icons/obj/stack_objects.dmi'
|
|
gender = PLURAL
|
|
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 = 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
|
|
var/full_w_class = WEIGHT_CLASS_NORMAL //The weight class the stack should have at amount > 2/3rds max_amount
|
|
var/novariants = TRUE //Determines whether the item should update it's sprites based on amount.
|
|
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
|
|
//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)
|
|
|
|
// The following are all for medical treatment, they're here instead of /stack/medical because sticky tape can be used as a makeshift bandage or splint
|
|
/// If set and this used as a splint for a broken bone wound, this is used as a multiplier for applicable slowdowns (lower = better) (also for speeding up burn recoveries)
|
|
var/splint_factor
|
|
/// How much blood flow this stack can absorb if used as a bandage on a cut wound, note that absorption is how much we lower the flow rate, not the raw amount of blood we suck up
|
|
var/absorption_capacity
|
|
/// How quickly we lower the blood flow on a cut wound we're bandaging. Expected lifetime of this bandage in seconds is thus absorption_capacity/absorption_rate, or until the cut heals, whichever comes first
|
|
var/absorption_rate
|
|
/// Amount of matter for RCD
|
|
var/matter_amount = 0
|
|
/// Does this stack require a unique girder in order to make a wall?
|
|
var/has_unique_girder = FALSE
|
|
|
|
/obj/item/stack/Initialize(mapload, new_amount, merge = TRUE, list/mat_override=null, mat_amt=1)
|
|
if(new_amount != null)
|
|
amount = new_amount
|
|
while(amount > max_amount)
|
|
amount -= max_amount
|
|
new type(loc, max_amount, FALSE)
|
|
if(!merge_type)
|
|
merge_type = type
|
|
|
|
if(LAZYLEN(mat_override))
|
|
set_mats_per_unit(mat_override, mat_amt)
|
|
else 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(can_merge(S))
|
|
INVOKE_ASYNC(src, .proc/merge, S)
|
|
//Merge can call qdel on us, so let's be safe yeah?
|
|
if(QDELETED(src))
|
|
return
|
|
var/list/temp_recipes = get_main_recipes()
|
|
recipes = temp_recipes.Copy()
|
|
if(material_type)
|
|
var/datum/material/M = GET_MATERIAL_REF(material_type) //First/main material
|
|
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_appearance()
|
|
var/static/list/loc_connections = list(
|
|
COMSIG_ATOM_ENTERED = .proc/on_entered,
|
|
)
|
|
AddElement(/datum/element/connect_loc, loc_connections)
|
|
|
|
/** 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, is_update=TRUE)
|
|
|
|
/**
|
|
* Override to make things like metalgen accurately set custom materials
|
|
*/
|
|
/obj/item/stack/set_custom_materials(list/materials, multiplier=1, is_update=FALSE)
|
|
return is_update ? ..() : set_mats_per_unit(materials, multiplier/(amount || 1))
|
|
|
|
|
|
/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, span_warning("[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()
|
|
if(amount <= (max_amount * (1/3)))
|
|
w_class = clamp(full_w_class-2, WEIGHT_CLASS_TINY, full_w_class)
|
|
else if (amount <= (max_amount * (2/3)))
|
|
w_class = clamp(full_w_class-1, WEIGHT_CLASS_TINY, full_w_class)
|
|
else
|
|
w_class = full_w_class
|
|
|
|
/obj/item/stack/update_icon_state()
|
|
if(novariants)
|
|
return ..()
|
|
if(amount <= (max_amount * (1/3)))
|
|
icon_state = initial(icon_state)
|
|
return ..()
|
|
if (amount <= (max_amount * (2/3)))
|
|
icon_state = "[initial(icon_state)]_2"
|
|
return ..()
|
|
icon_state = "[initial(icon_state)]_3"
|
|
return ..()
|
|
|
|
/obj/item/stack/examine(mob/user)
|
|
. = ..()
|
|
if(is_cyborg)
|
|
if(singular_name)
|
|
. += "There is enough energy for [get_amount()] [singular_name]\s."
|
|
else
|
|
. += "There is enough energy for [get_amount()]."
|
|
return
|
|
if(singular_name)
|
|
if(get_amount()>1)
|
|
. += "There are [get_amount()] [singular_name]\s in the stack."
|
|
else
|
|
. += "There is [get_amount()] [singular_name] in the stack."
|
|
else if(get_amount()>1)
|
|
. += "There are [get_amount()] in the stack."
|
|
else
|
|
. += "There is [get_amount()] in the stack."
|
|
. += span_notice("<b>Right-click</b> with an empty hand to take a custom amount.")
|
|
|
|
/obj/item/stack/proc/get_amount()
|
|
if(is_cyborg)
|
|
. = round(source?.energy / cost)
|
|
else
|
|
. = (amount)
|
|
|
|
/**
|
|
* 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
|
|
|
|
/**
|
|
* 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]",
|
|
)
|
|
|
|
/**
|
|
* 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(.)
|
|
return
|
|
|
|
switch(action)
|
|
if("make")
|
|
if(get_amount() < 1 && !is_cyborg)
|
|
qdel(src)
|
|
return
|
|
var/datum/stack_recipe/recipe = locate(params["ref"])
|
|
if(!is_valid_recipe(recipe, recipes)) //href exploit protection
|
|
return
|
|
var/multiplier = text2num(params["multiplier"])
|
|
if(!multiplier || (multiplier <= 0)) //href exploit protection
|
|
return
|
|
if(!building_checks(recipe, multiplier))
|
|
return
|
|
if(recipe.time)
|
|
var/adjusted_time = 0
|
|
usr.visible_message(span_notice("[usr] starts building \a [recipe.title]."), span_notice("You start building \a [recipe.title]..."))
|
|
if(HAS_TRAIT(usr, recipe.trait_booster))
|
|
adjusted_time = (recipe.time * recipe.trait_modifier)
|
|
else
|
|
adjusted_time = recipe.time
|
|
if(!do_after(usr, adjusted_time, target = usr))
|
|
return
|
|
if(!building_checks(recipe, multiplier))
|
|
return
|
|
|
|
var/obj/O
|
|
if(recipe.max_res_amount > 1) //Is it a stack?
|
|
O = new recipe.result_type(usr.drop_location(), recipe.res_amount * multiplier)
|
|
else if(ispath(recipe.result_type, /turf))
|
|
var/turf/T = usr.drop_location()
|
|
if(!isturf(T))
|
|
return
|
|
T.PlaceOnTop(recipe.result_type, flags = CHANGETURF_INHERIT_AIR)
|
|
else
|
|
O = new recipe.result_type(usr.drop_location())
|
|
if(O)
|
|
O.setDir(usr.dir)
|
|
use(recipe.req_amount * multiplier)
|
|
|
|
if(recipe.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, recipe.req_amount / recipe.res_amount)
|
|
else
|
|
O.set_custom_materials(mats_per_unit, recipe.req_amount / recipe.res_amount)
|
|
|
|
if(QDELETED(O))
|
|
return //It's a stack and has already been merged
|
|
|
|
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
|
|
return TRUE
|
|
|
|
/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/recipe, multiplier)
|
|
if (get_amount() < recipe.req_amount*multiplier)
|
|
if (recipe.req_amount*multiplier>1)
|
|
to_chat(usr, span_warning("You haven't got enough [src] to build \the [recipe.req_amount*multiplier] [recipe.title]\s!"))
|
|
else
|
|
to_chat(usr, span_warning("You haven't got enough [src] to build \the [recipe.title]!"))
|
|
return FALSE
|
|
var/turf/dest_turf = get_turf(usr)
|
|
|
|
// If we're making a window, we have some special snowflake window checks to do.
|
|
if(ispath(recipe.result_type, /obj/structure/window))
|
|
var/obj/structure/window/result_path = recipe.result_type
|
|
if(!valid_window_location(dest_turf, usr.dir, is_fulltile = initial(result_path.fulltile)))
|
|
to_chat(usr, span_warning("The [recipe.title] won't fit here!"))
|
|
return FALSE
|
|
|
|
if(recipe.one_per_turf && (locate(recipe.result_type) in dest_turf))
|
|
to_chat(usr, span_warning("There is another [recipe.title] here!"))
|
|
return FALSE
|
|
|
|
if(recipe.on_floor)
|
|
if(!isfloorturf(dest_turf))
|
|
to_chat(usr, span_warning("\The [recipe.title] must be constructed on the floor!"))
|
|
return FALSE
|
|
|
|
for(var/obj/object in dest_turf)
|
|
if(istype(object, /obj/structure/grille))
|
|
continue
|
|
if(istype(object, /obj/structure/table))
|
|
continue
|
|
if(istype(object, /obj/structure/window))
|
|
var/obj/structure/window/window_structure = object
|
|
if(!window_structure.fulltile)
|
|
continue
|
|
if(object.density || NO_BUILD & object.obj_flags)
|
|
to_chat(usr, span_warning("There is \a [object.name] here. You can\'t make \a [recipe.title] here!"))
|
|
return FALSE
|
|
if(recipe.placement_checks)
|
|
switch(recipe.placement_checks)
|
|
if(STACK_CHECK_CARDINALS)
|
|
var/turf/step
|
|
for(var/direction in GLOB.cardinals)
|
|
step = get_step(dest_turf, direction)
|
|
if(locate(recipe.result_type) in step)
|
|
to_chat(usr, span_warning("\The [recipe.title] must not be built directly adjacent to another!"))
|
|
return FALSE
|
|
if(STACK_CHECK_ADJACENT)
|
|
if(locate(recipe.result_type) in range(1, dest_turf))
|
|
to_chat(usr, span_warning("\The [recipe.title] must be constructed at least one tile away from others of its type!"))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/item/stack/use(used, transfer = FALSE, check = TRUE) // return 0 = borked; return 1 = had enough
|
|
if(check && zero_amount())
|
|
return FALSE
|
|
if(is_cyborg)
|
|
return source.use_charge(used * cost)
|
|
if (amount < used)
|
|
return FALSE
|
|
amount -= used
|
|
if(check && zero_amount())
|
|
return TRUE
|
|
if(length(mats_per_unit))
|
|
update_custom_materials()
|
|
update_appearance()
|
|
update_weight()
|
|
return TRUE
|
|
|
|
/obj/item/stack/tool_use_check(mob/living/user, amount)
|
|
if(get_amount() < amount)
|
|
if(singular_name)
|
|
if(amount > 1)
|
|
to_chat(user, span_warning("You need at least [amount] [singular_name]\s to do this!"))
|
|
else
|
|
to_chat(user, span_warning("You need at least [amount] [singular_name] to do this!"))
|
|
else
|
|
to_chat(user, span_warning("You need at least [amount] to do this!"))
|
|
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/obj/item/stack/proc/zero_amount()
|
|
if(is_cyborg)
|
|
return source.energy < cost
|
|
if(amount < 1)
|
|
qdel(src)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/** 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)
|
|
else
|
|
amount += _amount
|
|
if(length(mats_per_unit))
|
|
update_custom_materials()
|
|
update_appearance()
|
|
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(mats_per_unit != check.mats_per_unit)
|
|
return FALSE
|
|
if(is_cyborg) // No merging cyborg stacks into other stacks
|
|
return FALSE
|
|
return TRUE
|
|
|
|
///Merges src into S, as much as possible. If present, the limit arg overrides S.max_amount for transfer.
|
|
/obj/item/stack/proc/merge(obj/item/stack/S, limit)
|
|
if(QDELETED(S) || QDELETED(src) || S == src) //amusingly this can cause a stack to consume itself, let's not allow that.
|
|
return
|
|
var/transfer = get_amount()
|
|
if(S.is_cyborg)
|
|
transfer = min(transfer, round((S.source.max_energy - S.source.energy) / S.cost))
|
|
else
|
|
transfer = min(transfer, (limit ? limit : S.max_amount) - S.amount)
|
|
if(pulledby)
|
|
pulledby.start_pulling(S)
|
|
S.copy_evidences(src)
|
|
use(transfer, TRUE)
|
|
S.add(transfer)
|
|
return transfer
|
|
|
|
/obj/item/stack/proc/on_entered(datum/source, atom/movable/crossing)
|
|
SIGNAL_HANDLER
|
|
if(!crossing.throwing && can_merge(crossing))
|
|
INVOKE_ASYNC(src, .proc/merge, crossing)
|
|
|
|
/obj/item/stack/hitby(atom/movable/hitting, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
|
|
if(can_merge(hitting))
|
|
merge(hitting)
|
|
. = ..()
|
|
|
|
//ATTACK HAND IGNORING PARENT RETURN VALUE
|
|
/obj/item/stack/attack_hand(mob/user, list/modifiers)
|
|
if(user.get_inactive_held_item() == src)
|
|
if(zero_amount())
|
|
return
|
|
return split_stack(user, 1)
|
|
else
|
|
. = ..()
|
|
|
|
/obj/item/stack/attack_hand_secondary(mob/user, modifiers)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return
|
|
|
|
if(is_cyborg || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, FALSE, !iscyborg(user)) || zero_amount())
|
|
return SECONDARY_ATTACK_CONTINUE_CHAIN
|
|
var/max = get_amount()
|
|
var/stackmaterial = round(input(user, "How many sheets do you wish to take out of this stack? (Maximum [max])", "Stack Split") 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 SECONDARY_ATTACK_CONTINUE_CHAIN
|
|
split_stack(user, stackmaterial)
|
|
to_chat(user, span_notice("You take [stackmaterial] sheets out of the stack."))
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
/** 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 null
|
|
var/obj/item/stack/F = new type(user? user : drop_location(), amount, FALSE, mats_per_unit)
|
|
. = F
|
|
F.copy_evidences(src)
|
|
if(user)
|
|
if(!user.put_in_hands(F, merge_stacks = FALSE))
|
|
F.forceMove(user.drop_location())
|
|
add_fingerprint(user)
|
|
F.add_fingerprint(user)
|
|
zero_amount()
|
|
|
|
/obj/item/stack/attackby(obj/item/W, mob/user, params)
|
|
if(can_merge(W))
|
|
var/obj/item/stack/S = W
|
|
if(merge(S))
|
|
to_chat(user, span_notice("Your [S.name] stack now contains [S.get_amount()] [S.singular_name]\s."))
|
|
else
|
|
. = ..()
|
|
|
|
/obj/item/stack/proc/copy_evidences(obj/item/stack/from)
|
|
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)
|
|
M.dirty += amount
|
|
|
|
/*
|
|
* Recipe datum
|
|
*/
|
|
/datum/stack_recipe
|
|
var/title = "ERROR"
|
|
var/result_type
|
|
var/req_amount = 1
|
|
var/res_amount = 1
|
|
var/max_res_amount = 1
|
|
var/time = 0
|
|
var/one_per_turf = FALSE
|
|
var/on_floor = FALSE
|
|
var/placement_checks = FALSE
|
|
var/applies_mats = FALSE
|
|
var/trait_booster = null
|
|
var/trait_modifier = 1
|
|
|
|
/datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1,time = 0, one_per_turf = FALSE, on_floor = FALSE, window_checks = FALSE, placement_checks = FALSE, applies_mats = FALSE, trait_booster = null, trait_modifier = 1)
|
|
src.title = title
|
|
src.result_type = result_type
|
|
src.req_amount = req_amount
|
|
src.res_amount = res_amount
|
|
src.max_res_amount = max_res_amount
|
|
src.time = time
|
|
src.one_per_turf = one_per_turf
|
|
src.on_floor = on_floor
|
|
src.placement_checks = placement_checks
|
|
src.applies_mats = applies_mats
|
|
src.trait_booster = trait_booster
|
|
src.trait_modifier = trait_modifier
|
|
/*
|
|
* Recipe list datum
|
|
*/
|
|
/datum/stack_recipe_list
|
|
var/title = "ERROR"
|
|
var/list/recipes
|
|
|
|
/datum/stack_recipe_list/New(title, recipes)
|
|
src.title = title
|
|
src.recipes = recipes
|