mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2025-12-20 23:31:04 +00:00
415 lines
13 KiB
Plaintext
415 lines
13 KiB
Plaintext
/* Stack type objects!
|
|
* Contains:
|
|
* Stacks
|
|
* Recipe datum
|
|
* Recipe list datum
|
|
*/
|
|
|
|
/*
|
|
* Stacks
|
|
*/
|
|
/obj/item/stack
|
|
origin_tech = "materials=1"
|
|
/// Whether this stack is a `/cyborg` subtype or not.
|
|
var/is_cyborg = FALSE
|
|
/// The energy storage datum that will be used with this stack. Used only with `/cyborg` type stacks.
|
|
var/datum/robot_energy_storage/source
|
|
/// Which `robot_energy_storage` to choose when this stack is created in cyborgs. Used only with `/cyborg` type stacks.
|
|
var/energy_type
|
|
/// How much energy using 1 sheet from the stack costs. Used only with `/cyborg` type stacks.
|
|
var/cost = 1
|
|
/// A list of recipes buildable with this stack.
|
|
var/list/recipes = list()
|
|
var/singular_name
|
|
var/amount = 1
|
|
var/to_transfer = 0
|
|
var/max_amount = 50 //also see stack recipes initialisation, param "max_res_amount" must be equal to this max_amount
|
|
/// This path and its children should merge with this stack, defaults to src.type
|
|
var/merge_type = null
|
|
var/recipe_width = 400 //Width of the recipe popup
|
|
var/recipe_height = 400 //Height of the recipe popup
|
|
/// What sort of table is made when applying this stack to a frame?
|
|
var/table_type
|
|
/// If this stack has a dynamic icon_state based on amount / max_amount
|
|
var/dynamic_icon_state = FALSE
|
|
/// if true, then this item can't stack with subtypes
|
|
var/parent_stack = FALSE
|
|
|
|
/obj/item/stack/Initialize(mapload, new_amount, merge = TRUE)
|
|
. = ..()
|
|
if(dynamic_icon_state) //If we have a dynamic icon state, we don't want item states to follow the same pattern.
|
|
item_state = initial(icon_state)
|
|
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(merge && !(amount >= max_amount))
|
|
for(var/obj/item/stack/S in loc)
|
|
if(S.merge_type == merge_type)
|
|
merge(S)
|
|
update_icon(UPDATE_ICON_STATE)
|
|
|
|
/obj/item/stack/update_icon_state()
|
|
. = ..()
|
|
if(!dynamic_icon_state)
|
|
return
|
|
var/state = CEILING((amount/max_amount) * 3, 1)
|
|
if(state <= 1)
|
|
icon_state = initial(icon_state)
|
|
return
|
|
|
|
icon_state = "[initial(icon_state)]_[state]"
|
|
|
|
/obj/item/stack/Crossed(obj/O, oldloc)
|
|
if(amount >= max_amount || ismob(loc)) // Prevents unnecessary call. Also prevents merging stack automatically in a mob's inventory
|
|
return
|
|
if(istype(O, merge_type) && !O.throwing)
|
|
merge(O)
|
|
..()
|
|
|
|
/obj/item/stack/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
|
|
if(istype(AM, merge_type) && !(amount >= max_amount))
|
|
merge(AM)
|
|
. = ..()
|
|
|
|
/obj/item/stack/Destroy()
|
|
if(usr && usr.machine == src)
|
|
usr << browse(null, "window=stack")
|
|
return ..()
|
|
|
|
/obj/item/stack/examine(mob/user)
|
|
. = ..()
|
|
if(!in_range(user, src))
|
|
return
|
|
|
|
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)
|
|
. += "There are [amount] [singular_name]\s in the stack."
|
|
else
|
|
. += "There are [amount] [name]\s in the stack."
|
|
. +="<span class='notice'>Alt-click to take a custom amount.</span>"
|
|
|
|
/obj/item/stack/proc/add(newamount)
|
|
if(is_cyborg)
|
|
source.add_charge(newamount * cost)
|
|
else
|
|
amount += newamount
|
|
update_icon(UPDATE_ICON_STATE)
|
|
|
|
/obj/item/stack/attack_self(mob/user)
|
|
list_recipes(user)
|
|
|
|
/obj/item/stack/attack_self_tk(mob/user)
|
|
list_recipes(user)
|
|
|
|
/obj/item/stack/attack_tk(mob/user)
|
|
if(user.stat || !isturf(loc)) return
|
|
// Allow remote stack splitting, because telekinetic inventory managing
|
|
// is really cool
|
|
if(src in user.tkgrabbed_objects)
|
|
var/obj/item/stack/F = split(user, 1)
|
|
F.attack_tk(user)
|
|
if(src && user.machine == src)
|
|
spawn(0)
|
|
interact(user)
|
|
else
|
|
..()
|
|
|
|
|
|
/obj/item/stack/proc/list_recipes(mob/user, recipes_sublist)
|
|
if(!recipes)
|
|
return
|
|
|
|
if(get_amount() <= 0)
|
|
user << browse(null, "window=stack")
|
|
if(is_cyborg)
|
|
to_chat(user, "<span class='warning'>You don't have enough energy to dispense more [name]!</span>")
|
|
return
|
|
|
|
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()]<br>"
|
|
for(var/i in 1 to recipe_list.len)
|
|
var/E = recipe_list[i]
|
|
if(isnull(E))
|
|
t1 += "<hr>"
|
|
continue
|
|
|
|
if(i > 1 && !isnull(recipe_list[i - 1]))
|
|
t1 += "<br>"
|
|
|
|
if(istype(E, /datum/stack_recipe_list))
|
|
var/datum/stack_recipe_list/srl = E
|
|
t1 += "<a href='?src=[UID()];sublist=[i]'>[srl.title]</a>"
|
|
|
|
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] [src.singular_name]\s)"
|
|
if(can_build)
|
|
t1 += "<A href='?src=[UID()];sublist=[recipes_sublist];make=[i]'>[title]</A> "
|
|
else
|
|
t1 += "[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 += " <A href='?src=[UID()];make=[i];multiplier=[n]'>[n * R.res_amount]x</A>"
|
|
if(!(max_multiplier in multipliers))
|
|
t1 += " <A href='?src=[UID()];make=[i];multiplier=[max_multiplier]'>[max_multiplier * R.res_amount]x</A>"
|
|
|
|
var/datum/browser/popup = new(user, "stack", name, recipe_width, recipe_height)
|
|
popup.set_content(t1)
|
|
popup.open(0)
|
|
onclose(user, "stack")
|
|
|
|
/obj/item/stack/Topic(href, href_list)
|
|
..()
|
|
if(usr.incapacitated() || !usr.is_in_active_hand(src))
|
|
return 0
|
|
|
|
if(href_list["sublist"] && !href_list["make"])
|
|
list_recipes(usr, text2num(href_list["sublist"]))
|
|
|
|
if(href_list["make"])
|
|
if(amount < 0 && !is_cyborg)
|
|
qdel(src) //Never should happen
|
|
|
|
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 || multiplier > 50 || !IS_INT(multiplier)) // Href exploit checks
|
|
if(multiplier) // It existed but they tried to fuck with it
|
|
message_admins("[key_name_admin(usr)] just attempted to href exploit sheet crafting with an invalid multiplier. Ban highly advised.")
|
|
multiplier = 1
|
|
|
|
if(get_amount() < R.req_amount * multiplier)
|
|
if(R.req_amount * multiplier > 1)
|
|
to_chat(usr, "<span class='warning'>You haven't got enough [src] to build \the [R.req_amount * multiplier] [R.title]\s!</span>")
|
|
else
|
|
to_chat(usr, "<span class='warning'>You haven't got enough [src] to build \the [R.title]!</span>")
|
|
return FALSE
|
|
|
|
if(R.window_checks && !valid_window_location(get_turf(src), usr.dir))
|
|
to_chat(usr, "<span class='warning'>\The [R.title] won't fit here!</span>")
|
|
return FALSE
|
|
|
|
if(R.one_per_turf && (locate(R.result_type) in get_turf(src)))
|
|
to_chat(usr, "<span class='warning'>There is another [R.title] here!</span>")
|
|
return FALSE
|
|
|
|
if(R.on_floor && !issimulatedturf(get_turf(src)))
|
|
to_chat(usr, "<span class='warning'>\The [R.title] must be constructed on the floor!</span>")
|
|
return FALSE
|
|
if(R.on_floor_or_lattice && !(issimulatedturf(get_turf(src)) || locate(/obj/structure/lattice) in get_turf(src)))
|
|
to_chat(usr, "<span class='warning'>\The [R.title] must be constructed on the floor or lattice!</span>")
|
|
return FALSE
|
|
|
|
if(R.cult_structure)
|
|
if(usr.holy_check())
|
|
return
|
|
if(!is_level_reachable(usr.z))
|
|
to_chat(usr, "<span class='warning'>The energies of this place interfere with the metal shaping!</span>")
|
|
return
|
|
if(locate(/obj/structure/cult) in get_turf(src))
|
|
to_chat(usr, "<span class='warning'>There is a structure here!</span>")
|
|
return FALSE
|
|
|
|
if(R.time)
|
|
to_chat(usr, "<span class='notice'>Building [R.title]...</span>")
|
|
if(!do_after(usr, R.time, target = loc))
|
|
return FALSE
|
|
|
|
if(R.cult_structure && locate(/obj/structure/cult) in get_turf(src)) //Check again after do_after to prevent queuing construction exploit.
|
|
to_chat(usr, "<span class='warning'>There is a structure here!</span>")
|
|
return FALSE
|
|
|
|
if(get_amount() < R.req_amount * multiplier)
|
|
return
|
|
|
|
var/atom/O
|
|
if(R.max_res_amount > 1) //Is it a stack?
|
|
O = new R.result_type(get_turf(src), R.res_amount * multiplier)
|
|
else
|
|
O = new R.result_type(get_turf(src))
|
|
O.setDir(usr.dir)
|
|
use(R.req_amount * multiplier)
|
|
updateUsrDialog()
|
|
|
|
R.post_build(src, O)
|
|
|
|
if(amount < 1) // Just in case a stack's amount ends up fractional somehow
|
|
var/oldsrc = src
|
|
src = null //dont kill proc after qdel()
|
|
usr.unEquip(oldsrc, 1)
|
|
qdel(oldsrc)
|
|
if(isitem(O))
|
|
usr.put_in_hands(O)
|
|
|
|
O.add_fingerprint(usr)
|
|
//BubbleWrap - so newly formed boxes are empty
|
|
if(isstorage(O))
|
|
for(var/obj/item/I in O)
|
|
qdel(I)
|
|
//BubbleWrap END
|
|
|
|
if(src && usr.machine == src) //do not reopen closed window
|
|
spawn(0)
|
|
interact(usr)
|
|
return
|
|
|
|
/obj/item/stack/use(used, check = TRUE)
|
|
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()
|
|
update_icon(UPDATE_ICON_STATE)
|
|
return TRUE
|
|
|
|
/obj/item/stack/proc/get_amount()
|
|
if(!is_cyborg)
|
|
return amount
|
|
|
|
if(!source) // The energy source has not yet been initializied
|
|
return 0
|
|
return round(source.energy / cost)
|
|
|
|
/obj/item/stack/proc/get_max_amount()
|
|
return max_amount
|
|
|
|
/obj/item/stack/proc/get_amount_transferred()
|
|
return to_transfer
|
|
|
|
/obj/item/stack/proc/split(mob/user, amt)
|
|
var/obj/item/stack/F = new type(loc, amt)
|
|
F.copy_evidences(src)
|
|
if(isliving(user))
|
|
add_fingerprint(user)
|
|
F.add_fingerprint(user)
|
|
use(amt)
|
|
return F
|
|
|
|
/obj/item/stack/attack_hand(mob/user)
|
|
if(user.is_in_inactive_hand(src) && get_amount() > 1)
|
|
change_stack(user, 1)
|
|
if(src && usr.machine == src)
|
|
spawn(0)
|
|
interact(usr)
|
|
else
|
|
..()
|
|
|
|
/obj/item/stack/AltClick(mob/living/user)
|
|
if(!istype(user) || user.incapacitated())
|
|
to_chat(user, "<span class='warning'>You can't do that right now!</span>")
|
|
return
|
|
if(!in_range(src, user))
|
|
return
|
|
if(is_cyborg)
|
|
return
|
|
if(!ishuman(usr))
|
|
return
|
|
if(amount < 1)
|
|
return
|
|
//get amount from user
|
|
var/min = 0
|
|
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)
|
|
if(stackmaterial == null || stackmaterial <= min || stackmaterial > get_amount())
|
|
return
|
|
if(!Adjacent(user, 1))
|
|
return
|
|
change_stack(user,stackmaterial)
|
|
to_chat(user, "<span class='notice'>You take [stackmaterial] sheets out of the stack.</span>")
|
|
|
|
/obj/item/stack/proc/change_stack(mob/user,amount)
|
|
var/obj/item/stack/F = new type(user, amount, FALSE)
|
|
. = F
|
|
F.copy_evidences(src)
|
|
user.put_in_hands(F)
|
|
add_fingerprint(user)
|
|
F.add_fingerprint(user)
|
|
use(amount)
|
|
|
|
/obj/item/stack/attackby(obj/item/W, mob/user, params)
|
|
if((!parent_stack && istype(W, merge_type)) || (parent_stack && W.type == type))
|
|
var/obj/item/stack/S = W
|
|
merge(S)
|
|
to_chat(user, "<span class='notice'>Your [S.name] stack now contains [S.get_amount()] [S.singular_name]\s.</span>")
|
|
else
|
|
return ..()
|
|
|
|
// Returns TRUE if the stack amount is zero.
|
|
// Also qdels the stack gracefully if it is.
|
|
/obj/item/stack/proc/zero_amount()
|
|
if(is_cyborg)
|
|
return source.energy < cost
|
|
if(amount < 1)
|
|
if(ismob(loc))
|
|
var/mob/living/L = loc // At this stage, stack code is so horrible and atrocious, I wouldn't be all surprised ghosts can somehow have stacks. If this happens, then the world deserves to burn.
|
|
L.unEquip(src, TRUE)
|
|
if(amount < 1)
|
|
// If you stand on top of a stack, and drop a - different - 0-amount stack on the floor,
|
|
// the two get merged, so the amount of items in the stack can increase from the 0 that it had before.
|
|
// Check the amount again, to be sure we're not qdeling healthy stacks.
|
|
qdel(src)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/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 FALSE
|
|
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, S.max_amount - S.amount)
|
|
if(transfer <= 0)
|
|
return
|
|
if(pulledby)
|
|
pulledby.start_pulling(S)
|
|
S.copy_evidences(src)
|
|
S.add(transfer)
|
|
use(transfer)
|
|
return transfer
|
|
|
|
/obj/item/stack/proc/copy_evidences(obj/item/stack/from)
|
|
blood_DNA = from.blood_DNA
|
|
fingerprints = from.fingerprints
|
|
fingerprintshidden = from.fingerprintshidden
|
|
fingerprintslast = from.fingerprintslast
|