mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-04-02 10:00:53 +01:00
* Begin clickcode attack_self fix Begins the work to make everything call back to parent for attack_self so that signals are sacred. * Makes MORE things call the attack_self() parent Yes, I could make special_handling a var on obj/item HOWEVER i want it to be specific so it can be tracked down later and ONLY the objects that use it can be refactored instead of sitting there literally forever and it just becoming 'a thing'. * Finishes making the rest of attack_self call parent. As mentioned, things such as 'specialty_goggles' 'special_handling' and the such are only there to help with attack_self until the attack_self is recoded for those items. * begone foul demon * some more cleanup * These * GOD this was annoying * yeh * Fix this * fLARES * Thesee too * toys! * Even more! * More fixes * Even more * rest of em * these too * Update syndie.dm * hardref clear * Update code/game/gamemodes/nuclear/pinpointer.dm * Update code/game/objects/effects/mines.dm * Update code/game/objects/items/blueprints_vr.dm * Update code/game/objects/items/blueprints_vr.dm * Update code/game/objects/items/contraband_vr.dm * Update code/game/objects/items/crayons.dm * Update code/game/objects/items/crayons.dm * Update code/game/objects/items/gunbox.dm * Update code/game/objects/items/gunbox.dm * Update code/game/objects/items/gunbox_vr.dm * Update code/game/objects/items/gunbox_vr.dm * Update code/game/objects/items/weapons/gift_wrappaper.dm * Update code/game/objects/items/crayons.dm * Update code/game/objects/items/crayons.dm * Update code/game/objects/items/gunbox.dm * these too * Update maintpanel_stack.dm * angry warning * Fixes packaged snacks. Fixes improper var default. * Special handling for these * proper poly types * Fixes magclaws Makes the 'features' it had just part of base magboots that can be adjusted via varswap. * Fixes jackets Fixes https://github.com/VOREStation/VOREStation/issues/18941 * Small bugfix Makes p_Theyre properly capitialize Makes examine show proper wording * Update gift_wrappaper.dm
517 lines
14 KiB
Plaintext
517 lines
14 KiB
Plaintext
/* Stack type objects!
|
|
* Contains:
|
|
* Stacks
|
|
* Recipe datum
|
|
* Recipe list datum
|
|
*/
|
|
|
|
/*
|
|
* Stacks
|
|
*/
|
|
|
|
/obj/item/stack
|
|
gender = PLURAL
|
|
origin_tech = list(TECH_MATERIAL = 1)
|
|
icon = 'icons/obj/stacks.dmi'
|
|
randpixel = 7
|
|
center_of_mass_x = 0
|
|
center_of_mass_y = 0
|
|
var/list/datum/stack_recipe/recipes
|
|
var/singular_name
|
|
var/amount = 1
|
|
var/max_amount //also see stack recipes initialisation, param "max_res_amount" must be equal to this max_amount
|
|
var/stacktype //determines whether different stack types can merge
|
|
var/build_type = null //used when directly applied to a turf
|
|
var/uses_charge = 0
|
|
var/list/charge_costs = null
|
|
var/list/datum/matter_synth/synths = null
|
|
var/no_variants = TRUE // Determines whether the item should update it's sprites based on amount.
|
|
|
|
var/pass_color = FALSE // Will the item pass its own color var to the created item? Dyed cloth, wood, etc.
|
|
var/strict_color_stacking = FALSE // Will the stack merge with other stacks that are different colors? (Dyed cloth, wood, etc)
|
|
var/is_building = FALSE
|
|
|
|
var/custom_handling = FALSE
|
|
var/beacons = FALSE
|
|
var/sandbags = FALSE
|
|
|
|
/obj/item/stack/Initialize(mapload, var/starting_amount)
|
|
. = ..()
|
|
if(!stacktype)
|
|
stacktype = type
|
|
if(!isnull(starting_amount)) // Could be 0
|
|
// Negative numbers are 'give full stack', like -1
|
|
if(starting_amount < 0)
|
|
// But sometimes a coder forgot to define what that even means
|
|
if(max_amount)
|
|
starting_amount = max_amount
|
|
else
|
|
starting_amount = 1
|
|
set_amount(starting_amount, TRUE)
|
|
update_icon()
|
|
AddElement(/datum/element/sellable/material_stack)
|
|
|
|
/obj/item/stack/Destroy()
|
|
if (src && usr && usr.check_current_machine(src))
|
|
usr << browse(null, "window=stack")
|
|
if(islist(synths))
|
|
synths.Cut()
|
|
return ..()
|
|
|
|
/obj/item/stack/get_material_composition(breakdown_flags)
|
|
. = ..()
|
|
for(var/M in .)
|
|
.[M] *= amount
|
|
|
|
/obj/item/stack/update_icon()
|
|
if(no_variants)
|
|
icon_state = initial(icon_state)
|
|
else
|
|
if(amount <= (max_amount * (1/3)))
|
|
icon_state = initial(icon_state)
|
|
else if (amount <= (max_amount * (2/3)))
|
|
icon_state = "[initial(icon_state)]_2"
|
|
else
|
|
icon_state = "[initial(icon_state)]_3"
|
|
item_state = initial(icon_state)
|
|
|
|
/obj/item/stack/proc/get_examine_string()
|
|
if(!uses_charge)
|
|
return "There [src.amount == 1 ? "is" : "are"] [src.amount] [src.singular_name]\s in the stack."
|
|
else
|
|
return "There is enough charge for [get_amount()]."
|
|
|
|
/obj/item/stack/examine(mob/user)
|
|
. = ..()
|
|
|
|
if(Adjacent(user))
|
|
. += get_examine_string()
|
|
|
|
/obj/item/stack/attack_self(mob/user)
|
|
. = ..(user)
|
|
if(.)
|
|
return TRUE
|
|
if(custom_handling)
|
|
return FALSE
|
|
tgui_interact(user)
|
|
|
|
/obj/item/stack/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "MaterialStack", name)
|
|
ui.open()
|
|
|
|
/obj/item/stack/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
|
|
var/list/data = ..()
|
|
|
|
data["amount"] = get_amount()
|
|
|
|
return data
|
|
|
|
/obj/item/stack/tgui_static_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
|
|
var/list/data = ..()
|
|
|
|
data["recipes"] = recursively_build_recipes(recipes)
|
|
|
|
return data
|
|
|
|
/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/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/tgui_state(mob/user)
|
|
return GLOB.tgui_hands_state
|
|
|
|
/obj/item/stack/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
|
|
if(..())
|
|
return TRUE
|
|
|
|
switch(action)
|
|
if("make")
|
|
if(get_amount() < 1)
|
|
qdel(src)
|
|
return
|
|
|
|
var/datum/stack_recipe/R = locate(params["ref"])
|
|
if(!is_valid_recipe(R, recipes)) //href exploit protection
|
|
return FALSE
|
|
var/multiplier = text2num(params["multiplier"])
|
|
if(!multiplier || (multiplier <= 0)) //href exploit protection
|
|
return
|
|
produce_recipe(R, multiplier, ui.user)
|
|
return TRUE
|
|
|
|
/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/proc/produce_recipe(datum/stack_recipe/recipe, var/quantity, mob/user)
|
|
var/required = quantity*recipe.req_amount
|
|
var/produced = min(quantity*recipe.res_amount, recipe.max_res_amount)
|
|
|
|
if(is_building)
|
|
return
|
|
|
|
if (!can_use(required))
|
|
if (produced>1)
|
|
to_chat(user, span_warning("You haven't got enough [src] to build \the [produced] [recipe.title]\s!"))
|
|
else
|
|
to_chat(user, span_warning("You haven't got enough [src] to build \the [recipe.title]!"))
|
|
return
|
|
|
|
if (recipe.one_per_turf && (locate(recipe.result_type) in user.loc))
|
|
to_chat(user, span_warning("There is another [recipe.title] here!"))
|
|
return
|
|
|
|
if (recipe.on_floor && !isfloor(user.loc))
|
|
to_chat(user, span_warning("\The [recipe.title] must be constructed on the floor!"))
|
|
return
|
|
|
|
if (recipe.time)
|
|
to_chat(user, span_notice("Building [recipe.title] ..."))
|
|
is_building = TRUE
|
|
if (!do_after(user, recipe.time, target = src))
|
|
is_building = FALSE
|
|
return
|
|
|
|
is_building = FALSE
|
|
if (use(required))
|
|
var/atom/O
|
|
if(recipe.use_material)
|
|
O = new recipe.result_type(user.loc, recipe.use_material)
|
|
|
|
if(istype(O, /obj/item))
|
|
var/obj/item/Ob = O
|
|
|
|
if(LAZYLEN(Ob.matter)) // Law of equivalent exchange.
|
|
Ob.matter.Cut()
|
|
|
|
else
|
|
Ob.matter = list()
|
|
|
|
var/mattermult = istype(Ob, /obj/item) ? min(2000, 400 * Ob.w_class) : 2000
|
|
|
|
Ob.matter[recipe.use_material] = mattermult / produced * required
|
|
|
|
else
|
|
O = new recipe.result_type(user.loc)
|
|
|
|
if(recipe.matter_material)
|
|
if(istype(O, /obj/item))
|
|
var/obj/item/Ob = O
|
|
|
|
if(LAZYLEN(Ob.matter)) // Law of equivalent exchange.
|
|
Ob.matter.Cut()
|
|
|
|
else
|
|
Ob.matter = list()
|
|
|
|
var/mattermult = istype(Ob, /obj/item) ? min(2000, 400 * Ob.w_class) : 2000
|
|
|
|
Ob.matter[recipe.matter_material] = mattermult / produced * required
|
|
|
|
O.set_dir(user.dir)
|
|
O.add_fingerprint(user)
|
|
if (istype(O, /obj/item/stack))
|
|
var/obj/item/stack/S = O
|
|
S.amount = produced
|
|
S.add_to_stacks(user)
|
|
if (isitem(O))
|
|
var/obj/item/P = O
|
|
P.persist_storable = FALSE
|
|
if (istype(O, /obj/item/storage)) //BubbleWrap - so newly formed boxes are empty
|
|
for (var/obj/item/I in O)
|
|
qdel(I)
|
|
|
|
if ((pass_color || recipe.pass_color))
|
|
if(!color)
|
|
if(recipe.use_material)
|
|
var/datum/material/MAT = get_material_by_name(recipe.use_material)
|
|
if(MAT.icon_colour)
|
|
O.color = MAT.icon_colour
|
|
else
|
|
return
|
|
else
|
|
O.color = color
|
|
|
|
//Return 1 if an immediate subsequent call to use() would succeed.
|
|
//Ensures that code dealing with stacks uses the same logic
|
|
/obj/item/stack/proc/can_use(var/used)
|
|
if(used < 0 || (used != round(used)))
|
|
stack_trace("Tried to use a bad stack amount: [used]")
|
|
return 0
|
|
if(get_amount() < used)
|
|
return 0
|
|
return 1
|
|
|
|
/obj/item/stack/proc/use(var/used)
|
|
if(!can_use(used))
|
|
return 0
|
|
if(!uses_charge)
|
|
amount -= used
|
|
if (amount <= 0)
|
|
// Tell container that we used up a stack
|
|
if(istype( loc, /obj/item/storage))
|
|
var/obj/item/storage/holder = loc
|
|
holder.remove_from_storage( src, null)
|
|
qdel(src) //should be safe to qdel immediately since if someone is still using this stack it will persist for a little while longer
|
|
update_icon()
|
|
return 1
|
|
else
|
|
if(get_amount() < used)
|
|
return 0
|
|
for(var/i = 1 to uses_charge)
|
|
var/datum/matter_synth/S = synths[i]
|
|
S.use_charge(charge_costs[i] * used) // Doesn't need to be deleted
|
|
return 1
|
|
|
|
/obj/item/stack/proc/add(var/extra)
|
|
if(extra < 0 || (extra != round(extra)))
|
|
stack_trace("Tried to add a bad stack amount: [extra]")
|
|
return 0
|
|
if(!uses_charge)
|
|
if(amount + extra > get_max_amount())
|
|
return 0
|
|
else
|
|
amount += extra
|
|
update_icon()
|
|
return 1
|
|
else if(!synths || synths.len < uses_charge)
|
|
return 0
|
|
else
|
|
for(var/i = 1 to uses_charge)
|
|
var/datum/matter_synth/S = synths[i]
|
|
S.add_charge(charge_costs[i] * extra)
|
|
|
|
/obj/item/stack/proc/set_amount(var/new_amount, var/no_limits = FALSE)
|
|
if(new_amount < 0 || (new_amount != round(new_amount)))
|
|
stack_trace("Tried to set a bad stack amount: [new_amount]")
|
|
return 0
|
|
|
|
// Clean up the new amount
|
|
new_amount = max(round(new_amount), 0)
|
|
|
|
// Can exceed max if you really want
|
|
if(new_amount > max_amount && !no_limits)
|
|
new_amount = max_amount
|
|
|
|
amount = new_amount
|
|
|
|
// Can set it to 0 without qdel if you really want
|
|
if(amount == 0 && !no_limits)
|
|
qdel(src)
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/*
|
|
The transfer and split procs work differently than use() and add().
|
|
Whereas those procs take no action if the desired amount cannot be added or removed these procs will try to transfer whatever they can.
|
|
They also remove an equal amount from the source stack.
|
|
*/
|
|
|
|
//attempts to transfer amount to S, and returns the amount actually transferred
|
|
/obj/item/stack/proc/transfer_to(obj/item/stack/S, var/tamount=null, var/type_verified)
|
|
if (!get_amount())
|
|
return 0
|
|
if ((stacktype != S.stacktype) && !type_verified)
|
|
return 0
|
|
if ((strict_color_stacking || S.strict_color_stacking) && S.color != color)
|
|
return 0
|
|
|
|
if (isnull(tamount))
|
|
tamount = src.get_amount()
|
|
|
|
if(tamount < 0 || (tamount != round(tamount)))
|
|
stack_trace("Tried to transfer a bad stack amount: [tamount]")
|
|
return 0
|
|
|
|
var/transfer = max(min(tamount, src.get_amount(), (S.get_max_amount() - S.get_amount())), 0)
|
|
|
|
var/orig_amount = src.get_amount()
|
|
if (transfer && src.use(transfer))
|
|
S.add(transfer)
|
|
if (prob(transfer/orig_amount * 100))
|
|
transfer_fingerprints_to(S)
|
|
transfer_blooddna_to(S)
|
|
return transfer
|
|
return 0
|
|
|
|
//creates a new stack with the specified amount
|
|
/obj/item/stack/proc/split(var/tamount)
|
|
if (!amount)
|
|
return null
|
|
if(uses_charge)
|
|
return null
|
|
|
|
if(tamount < 0 || (tamount != round(tamount)))
|
|
stack_trace("Tried to split a bad stack amount: [tamount]")
|
|
return null
|
|
|
|
var/transfer = max(min(tamount, src.amount, initial(max_amount)), 0)
|
|
|
|
var/orig_amount = src.amount
|
|
if (transfer && src.use(transfer))
|
|
var/obj/item/stack/newstack = new src.type(loc, transfer)
|
|
newstack.color = color
|
|
if (prob(transfer/orig_amount * 100))
|
|
transfer_fingerprints_to(newstack)
|
|
transfer_blooddna_to(newstack)
|
|
return newstack
|
|
return null
|
|
|
|
/obj/item/stack/proc/get_amount()
|
|
if(uses_charge)
|
|
if(!synths || synths.len < uses_charge)
|
|
return 0
|
|
var/datum/matter_synth/S = synths[1]
|
|
. = round(S.get_charge() / charge_costs[1])
|
|
if(uses_charge > 1)
|
|
for(var/i = 2 to uses_charge)
|
|
S = synths[i]
|
|
. = min(., round(S.get_charge() / charge_costs[i]))
|
|
return
|
|
return amount
|
|
|
|
/obj/item/stack/proc/get_max_amount()
|
|
if(uses_charge)
|
|
if(!synths || synths.len < uses_charge)
|
|
return 0
|
|
var/datum/matter_synth/S = synths[1]
|
|
. = round(S.max_energy / charge_costs[1])
|
|
if(uses_charge > 1)
|
|
for(var/i = 2 to uses_charge)
|
|
S = synths[i]
|
|
. = min(., round(S.max_energy / charge_costs[i]))
|
|
return
|
|
return max_amount
|
|
|
|
/obj/item/stack/proc/add_to_stacks(mob/user as mob)
|
|
for (var/obj/item/stack/item in user.loc)
|
|
if (item==src)
|
|
continue
|
|
var/transfer = src.transfer_to(item)
|
|
if (transfer)
|
|
to_chat(user, span_notice("You add a new [item.singular_name] to the stack. It now contains [item.amount] [item.singular_name]\s."))
|
|
if(!amount)
|
|
break
|
|
|
|
/obj/item/stack/attack_hand(mob/user as mob)
|
|
if (user.get_inactive_hand() == src)
|
|
var/N = tgui_input_number(user, "How many stacks of [src] would you like to split off? There are currently [amount].", "Split stacks", 1, amount, 1)
|
|
if(N != round(N))
|
|
to_chat(user, span_warning("You cannot separate a non-whole number of stacks!"))
|
|
return
|
|
if(N)
|
|
var/obj/item/stack/F = src.split(N)
|
|
if (F)
|
|
user.put_in_hands(F)
|
|
src.add_fingerprint(user)
|
|
F.add_fingerprint(user)
|
|
spawn(0)
|
|
if (src && user.check_current_machine(src))
|
|
src.interact(user)
|
|
else
|
|
..()
|
|
return
|
|
|
|
/obj/item/stack/attackby(obj/item/W as obj, mob/user as mob)
|
|
if(istype(W, /obj/item/gripper))
|
|
var/obj/item/gripper/G = W
|
|
G.consolidate_stacks(src)
|
|
|
|
else if(istype(W, /obj/item/stack))
|
|
var/obj/item/stack/S = W
|
|
src.transfer_to(S)
|
|
|
|
spawn(0) //give the stacks a chance to delete themselves if necessary
|
|
if (S && user.check_current_machine(S))
|
|
S.interact(user)
|
|
if (src && user.check_current_machine(src))
|
|
src.interact(user)
|
|
else
|
|
return ..()
|
|
|
|
/obj/item/stack/proc/combine_in_loc()
|
|
return //STUBBED for now, as it seems to randomly delete stacks
|
|
|
|
/obj/item/stack/dropped(atom/old_loc)
|
|
. = ..()
|
|
if(isturf(loc))
|
|
combine_in_loc()
|
|
|
|
/obj/item/stack/Moved(atom/old_loc, direction, forced)
|
|
. = ..()
|
|
if(pulledby && isturf(loc))
|
|
combine_in_loc()
|
|
|
|
/obj/item/stack/proc/reagents_per_sheet()
|
|
return REAGENTS_PER_SHEET // units total of reagents when grinded
|
|
|
|
/*
|
|
* Recipe datum
|
|
*/
|
|
/datum/stack_recipe
|
|
var/title = "ERROR"
|
|
var/result_type
|
|
var/req_amount = 1 //amount of material needed for this recipe
|
|
var/res_amount = 1 //amount of stuff that is produced in one batch (e.g. 4 for floor tiles)
|
|
var/max_res_amount = 1
|
|
var/time = 0
|
|
var/one_per_turf = 0
|
|
var/on_floor = 0
|
|
var/use_material
|
|
var/pass_color
|
|
var/matter_material // Material type used for recycling. Default, uses use_material. For non-material-based objects however, matter_material is needed.
|
|
|
|
/datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1, time = 0, one_per_turf = 0, on_floor = 0, supplied_material = null, pass_stack_color, recycle_material = null)
|
|
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.use_material = supplied_material
|
|
src.pass_color = pass_stack_color
|
|
|
|
if(!recycle_material && src.use_material)
|
|
src.matter_material = src.use_material
|
|
|
|
else if(recycle_material)
|
|
src.matter_material = recycle_material
|
|
|
|
/*
|
|
* Recipe list datum
|
|
*/
|
|
/datum/stack_recipe_list
|
|
var/title = "ERROR"
|
|
var/list/recipes = null
|
|
/datum/stack_recipe_list/New(title, recipes)
|
|
src.title = title
|
|
src.recipes = recipes
|