From 5d2ed77b57e6a83c85dcb7c7afb415ea44d12fd3 Mon Sep 17 00:00:00 2001 From: Will <7099514+Willburd@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:12:59 -0400 Subject: [PATCH] Stasis Cage Exporting (#19612) * sellable stasis cage * Sellable mobs * crate code cleanup * export fix * clamp * sold mobs should vanish --- code/controllers/subsystems/supply.dm | 6 +- code/datums/elements/sellable.dm | 63 ++++++++++++++++++- .../structures/crates_lockers/__closets.dm | 41 +++++++----- .../structures/crates_lockers/crates.dm | 29 ++++++--- code/game/objects/structures/stasis_cage.dm | 10 ++- code/modules/economy/cargo_point_scanner.dm | 23 +++++-- .../mob/living/simple_mob/simple_mob.dm | 6 +- .../animal/giant_spider/_giant_spider.dm | 2 + .../simple_mob/subtypes/animal/space/alien.dm | 3 + .../simple_mob/subtypes/animal/space/bear.dm | 3 + .../simple_mob/subtypes/animal/space/carp.dm | 3 + .../simple_mob/subtypes/vore/bigdragon.dm | 3 + .../subtypes/vore/corrupt_hounds.dm | 3 + .../simple_mob/subtypes/vore/deathclaw.dm | 3 + .../simple_mob/subtypes/vore/greatwolf.dm | 3 + .../simple_mob/subtypes/vore/leopardmander.dm | 3 + .../simple_mob/subtypes/vore/solargrub.dm | 3 + .../simple_mob/subtypes/vore/solarmoth.dm | 3 + .../living/simple_mob/subtypes/vore/vore.dm | 3 + 19 files changed, 176 insertions(+), 37 deletions(-) diff --git a/code/controllers/subsystems/supply.dm b/code/controllers/subsystems/supply.dm index 99a1556637..143ff32319 100644 --- a/code/controllers/subsystems/supply.dm +++ b/code/controllers/subsystems/supply.dm @@ -32,6 +32,7 @@ SUBSYSTEM_DEF(supply) var/list/order_history = list() // History of orders, showing edits made by users var/list/adm_order_history = list() // Complete history of all orders, for admin use var/list/adm_export_history = list() // Complete history of all crates sent back on the shuttle, for admin use + var/list/exported_research_mobs = list()// Number of mob types sold from cargo, used to calculate diminishing returns //shuttle movement var/movetime = 1200 var/datum/shuttle/autodock/ferry/supply/shuttle @@ -58,7 +59,10 @@ SUBSYSTEM_DEF(supply) //To stop things being sent to CentCom which should not be sent to centcomm. Recursively checks for these types. /datum/controller/subsystem/supply/proc/forbidden_atoms_check(atom/A) if(isliving(A)) - return 1 + var/mob/living/check_living = A + // Mobs inside stasis cages can be sold. otherwise loose mobs or mobs with clients are forbidden. + if(!istype(check_living.loc, /obj/structure/stasis_cage) || check_living.client) + return 1 if(istype(A,/obj/item/disk/nuclear)) return 1 if(istype(A,/obj/machinery/nuclearbomb)) diff --git a/code/datums/elements/sellable.dm b/code/datums/elements/sellable.dm index 3b372bf4a3..ed517d5da5 100644 --- a/code/datums/elements/sellable.dm +++ b/code/datums/elements/sellable.dm @@ -1,6 +1,8 @@ /datum/element/sellable var/sale_info = "This can be sold on the cargo shuttle if packed in a crate." var/needs_crate = TRUE + var/datum/techweb/research_default_techweb = null + var/department = DEPARTMENT_CARGO // Which kind of points does selling this give, by default it gives cargo supply points /datum/element/sellable/Attach(datum/target) . = ..() @@ -45,7 +47,15 @@ "value" = calculate_sell_value(source), "quantity" = calculate_sell_quantity(source) ) - EC.value += EC.contents[EC.contents.len]["value"] + + var/point_value = EC.contents[EC.contents.len]["value"] + switch(department) + if(DEPARTMENT_CARGO) + EC.value += point_value + if(DEPARTMENT_RESEARCH) + if(!research_default_techweb) // Lets do this when we sell a research giving item, instead of fighting subsystem init order + research_default_techweb = locate(/datum/techweb/science) in SSresearch.techwebs + research_default_techweb.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = point_value)) return TRUE /datum/element/sellable/proc/on_examine(datum/source, mob/user, list/examine_texts) @@ -398,3 +408,54 @@ amount /= 20 return FLOOR(amount,5) + + +// Selling xenomobs for science!! +/datum/element/sellable/stasis_cage + sale_info = "This can be export on the cargo shuttle. Central command will reward the station with research points if the creature within has scientific value." + needs_crate = FALSE + department = DEPARTMENT_RESEARCH + +/datum/element/sellable/stasis_cage/sell_error(obj/source) + var/obj/structure/stasis_cage/cage = source + if(!cage.contained) + return "Error: [cage] was empty." + // Check if this creature can even be sold at all + if(!istype(cage.contained, /mob/living/simple_mob)) + return "Error: This creature has no scientific value." + if(cage.contained.client) // Lets not allow player possessed creatures to be sold + return "Error: This creature has an undesirable mutation and has no scientific value." + var/mob/living/simple_mob/our_mob = cage.contained + if(our_mob.stat == DEAD) + return "Error: The creature must be alive to study. It has no scientific value." + if(!our_mob.export_research_value || !our_mob.export_research_diminished_max) + return "Error: This creature has no scientific value." + // We know it HAD value, see if it has any value remaining + var/check_val = calculate_sell_value(source) + if(!check_val) + return "Error: This creature no longer has any scientific value." + // Increase sold count, now that we've reached successful export + var/mob_key = "[cage.contained.type]" + if(!(mob_key in SSsupply.exported_research_mobs)) + SSsupply.exported_research_mobs[mob_key] = 0 + SSsupply.exported_research_mobs[mob_key] += 1 + return null + +/datum/element/sellable/stasis_cage/calculate_sell_value(obj/source) + var/obj/structure/stasis_cage/cage = source + if(!cage.contained) + return 0 + if(!istype(cage.contained, /mob/living/simple_mob)) + return 0 + var/mob/living/simple_mob/our_mob = cage.contained + if(our_mob.stat == DEAD) + return 0 + if(!our_mob.export_research_value || !our_mob.export_research_diminished_max) + return 0 + // Calculate diminishing returns from number of mobs that have already been sold of this type + var/mob_key = "[cage.contained.type]" + var/sold_mobs = 0 + if(mob_key in SSsupply.exported_research_mobs) + sold_mobs = SSsupply.exported_research_mobs[mob_key] + var/research_val = our_mob.export_research_value // Each mob sold will linearly scale the value of research points recieved, until it hits 25% of it's value. + return max(1, FLOOR(lerp(research_val, research_val * 0.25, CLAMP(sold_mobs / our_mob.export_research_diminished_max, 0, 1)), 1)) diff --git a/code/game/objects/structures/crates_lockers/__closets.dm b/code/game/objects/structures/crates_lockers/__closets.dm index 4441ed6ab0..2afe1c3447 100644 --- a/code/game/objects/structures/crates_lockers/__closets.dm +++ b/code/game/objects/structures/crates_lockers/__closets.dm @@ -269,19 +269,20 @@ /obj/structure/closet/attackby(obj/item/W as obj, mob/user as mob) if(W.has_tool_quality(TOOL_WRENCH)) - if(opened) - if(anchored) - user.visible_message("\The [user] begins unsecuring \the [src] from the floor.", "You start unsecuring \the [src] from the floor.") - else - user.visible_message("\The [user] begins securing \the [src] to the floor.", "You start securing \the [src] to the floor.") - if(do_after(user, 2 SECONDS * W.toolspeed, target = src)) - if(!src) return - to_chat(user, span_notice("You [anchored? "un" : ""]secured \the [src]!")) - anchored = !anchored - return - else + if(!opened) to_chat(user, span_notice("You can't reach the anchoring bolts when the door is closed!")) - else if(opened) + return + if(anchored) + user.visible_message("\The [user] begins unsecuring \the [src] from the floor.", "You start unsecuring \the [src] from the floor.") + else + user.visible_message("\The [user] begins securing \the [src] to the floor.", "You start securing \the [src] to the floor.") + if(do_after(user, 2 SECONDS * W.toolspeed, target = src)) + if(!src) return + to_chat(user, span_notice("You [anchored? "un" : ""]secured \the [src]!")) + anchored = !anchored + return + + if(opened) if(istype(W, /obj/item/grab)) var/obj/item/grab/G = W MouseDrop_T(G.affecting, user) //act like they were dragged onto the closet @@ -319,9 +320,15 @@ if(W) W.do_drop_animation(user) W.forceMove(loc) - else if(istype(W, /obj/item/packageWrap)) return - else if(seal_tool) + + if(istype(W, /obj/item/packageWrap)) + return + + if(istype(W,/obj/item/cargo_scanner)) + return + + if(seal_tool) if(istype(W, seal_tool)) var/obj/item/S = W if(S.has_tool_quality(TOOL_WELDER)) @@ -338,9 +345,9 @@ update_icon() for(var/mob/M in viewers(src)) M.show_message(span_warning("[src] has been [sealed?"sealed":"unsealed"] by [user.name]."), 3) - else - attack_hand(user) - return + return + + return attack_hand(user) /obj/structure/closet/MouseDrop_T(atom/movable/O as mob|obj, mob/user as mob) if(istype(O, /atom/movable/screen)) //fix for HUD elements making their way into the world -Pete diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index ef2f784ec6..a43d2d92a9 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -74,9 +74,11 @@ /obj/structure/closet/crate/attackby(obj/item/W as obj, mob/user as mob) if(W.has_tool_quality(TOOL_WRENCH) && istype(src,/obj/structure/closet/crate/bin)) return ..() - else if(W.has_tool_quality(TOOL_WELDER)) + + if(W.has_tool_quality(TOOL_WELDER)) return ..() - else if(opened) + + if(opened) if(isrobot(user)) return if(W.loc != user) // This should stop mounted modules ending up outside the module. @@ -86,9 +88,15 @@ user.drop_item() if(W) W.forceMove(src.loc) - else if(istype(W, /obj/item/packageWrap)) return - else if(istype(W, /obj/item/stack/cable_coil)) + + if(istype(W, /obj/item/packageWrap)) + return + + if(istype(W,/obj/item/cargo_scanner)) + return + + if(istype(W, /obj/item/stack/cable_coil)) var/obj/item/stack/cable_coil/C = W if(rigged) to_chat(user, span_notice("[src] is already rigged!")) @@ -97,19 +105,22 @@ to_chat(user , span_notice("You rig [src].")) rigged = 1 return - else if(istype(W, /obj/item/radio/electropack)) + + if(istype(W, /obj/item/radio/electropack)) if(rigged) to_chat(user , span_notice("You attach [W] to [src].")) user.drop_item() W.forceMove(src) - return - else if(W.has_tool_quality(TOOL_WIRECUTTER)) + return + + if(W.has_tool_quality(TOOL_WIRECUTTER)) if(rigged) to_chat(user , span_notice("You cut away the wiring.")) playsound(src, W.usesound, 100, 1) rigged = 0 - return - else return attack_hand(user) + return + + return attack_hand(user) /obj/structure/closet/crate/ex_act(severity) switch(severity) diff --git a/code/game/objects/structures/stasis_cage.dm b/code/game/objects/structures/stasis_cage.dm index c547b0a0c9..44f03c361b 100644 --- a/code/game/objects/structures/stasis_cage.dm +++ b/code/game/objects/structures/stasis_cage.dm @@ -14,6 +14,7 @@ var/mob/living/simple_mob/A = locate() in loc if(A) contain(A) + AddElement(/datum/element/sellable/stasis_cage) /obj/structure/stasis_cage/attack_hand(mob/user) release() @@ -48,9 +49,14 @@ desc = initial(desc) /obj/structure/stasis_cage/Destroy() + // If sold by cargo, we need to delete our contents instead of releasing them + // Check for admin z instead of shuttle area. Cause destroying it on the station will make the mob qdel. + if(z in using_map.admin_levels) + for(var/atom/thing in contents) + qdel(thing) + contained = null release() - - return ..() + . = ..() /mob/living/simple_mob/MouseDrop(obj/structure/stasis_cage/over_object) if(istype(over_object) && Adjacent(over_object) && CanMouseDrop(over_object, usr)) diff --git a/code/modules/economy/cargo_point_scanner.dm b/code/modules/economy/cargo_point_scanner.dm index 8d5f440e63..ca561247cb 100644 --- a/code/modules/economy/cargo_point_scanner.dm +++ b/code/modules/economy/cargo_point_scanner.dm @@ -27,23 +27,36 @@ if(istype(AM,/obj/effect)) return 0 - var/final_output = span_boldnotice("\The [src] assesses the supply point value of \the [AM]...\n") + var/final_output = span_boldnotice("\The [src] assesses the value of \the [AM]...\n") playsound(src, 'sound/machines/beep.ogg', 50, 1) // Some things cannot be sold - if(isliving(AM) || isstructure(AM) || isturf(AM)) + if(isliving(AM) || isturf(AM)) final_output += span_danger("-Cannot be sold.") to_chat(user,final_output) return 0 - // Get item value - var/value = SEND_SIGNAL(AM,COMSIG_ITEM_SCAN_PROFIT) + var/value = 0 + if(istype(AM,/obj/structure/closet/crate)) + // Scan all contents in crate + for(var/atom/movable/thing in AM.contents) + value += SEND_SIGNAL(thing,COMSIG_ITEM_SCAN_PROFIT) + else + // Get single item value + value = SEND_SIGNAL(AM,COMSIG_ITEM_SCAN_PROFIT) + if(!value) final_output += span_danger("-It's worth nothing.") to_chat(user,final_output) return 0 + // Stasis cages are for RESEARCH! + if(istype(AM,/obj/structure/stasis_cage)) + final_output += span_notice("-Its contents can be studied for [value] research points.") + to_chat(user,final_output) + return value + var/price = SSsupply.points_to_cash(value) - final_output += span_notice("-It can be sold for [value] points, or [price] [price > 1 ? "thalers" : "thaler"]") + final_output += span_notice("-It can be sold for [value] supply points, or [price] [price > 1 ? "thalers" : "thaler"]") to_chat(user,final_output) return value diff --git a/code/modules/mob/living/simple_mob/simple_mob.dm b/code/modules/mob/living/simple_mob/simple_mob.dm index 539fcb67f6..01e4add8f3 100644 --- a/code/modules/mob/living/simple_mob/simple_mob.dm +++ b/code/modules/mob/living/simple_mob/simple_mob.dm @@ -166,12 +166,14 @@ var/heal_countdown = 5 //VOREStation Edit - A cooldown ticker for passive healing var/list/myid_access = list() //VOREStation Edit var/ID_provided = FALSE //VOREStation Edit - // VOREStation Add: Move/Shoot/Attack delays based on damage var/damage_fatigue_mult = 1 // Our multiplier for how heavily mobs are affected by injury. [UPDATE THIS IF THE FORMULA CHANGES]: Formula = injury_level = round(rand(1,3) * damage_fatigue_mult * clamp(((rand(2,5) * (h / getMaxHealth())) - rand(0,2)), 1, 5)) var/injury_level = 0 // What our injury level is. Rather than being the flat damage, this is the amount added to various delays to simulate injuries in a manner as lightweight as possible. var/threshold = 0.6 // When we start slowing down. Configure this setting per-mob. Default is 60% var/injury_enrages = FALSE // Do injuries enrage (aka strengthen) our mob? If yes, we'll interpret how hurt we are differently. - // VOREStation Add End + + // Stasis cage cargo export values + var/export_research_value // Amount of research points gained when this mob is sold from cargo in a stasis cage + var/export_research_diminished_max // Amount of mobs that can be sold before the value reaches its lowest point var/has_recoloured = FALSE var/hunting_cooldown = 0 diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm index c419a07443..2359daaf8e 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/_giant_spider.dm @@ -129,6 +129,8 @@ no_pull_when_living = TRUE + export_research_value = TECHWEB_TIER_1_POINTS + export_research_diminished_max = 3 /mob/living/simple_mob/animal/giant_spider/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/alien.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/alien.dm index bc0d04ad16..453f56d7d4 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/space/alien.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/alien.dm @@ -35,6 +35,9 @@ can_be_drop_prey = FALSE + export_research_value = TECHWEB_TIER_2_POINTS + export_research_diminished_max = 4 + /mob/living/simple_mob/animal/space/alien/drone name = "alien drone" icon_state = "aliend_running" diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/bear.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/bear.dm index 9674d4230b..d8d0115da2 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/space/bear.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/bear.dm @@ -30,6 +30,9 @@ can_be_drop_prey = FALSE allow_mind_transfer = TRUE + export_research_value = TECHWEB_TIER_1_POINTS + export_research_diminished_max = 3 + /datum/say_list/bear speak = list("RAWR!","Rawr!","GRR!","Growl!") emote_see = list("stares ferociously", "stomps") diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm index 404ae6ecac..76b35d8684 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/carp.dm @@ -67,6 +67,9 @@ var/random_color = FALSE var/rarechance = 1 + export_research_value = TECHWEB_TIER_1_POINTS + export_research_diminished_max = 4 + var/static/list/carp_colors = list(\ "lightpurple" = "#c3b9f1", \ "lightpink" = "#da77a8", \ diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm b/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm index 3a0b132312..9f853f975f 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm @@ -110,6 +110,9 @@ I think I covered everything. special_attack_max_range = 10 special_attack_cooldown = 80 + export_research_value = TECHWEB_TIER_4_POINTS + export_research_diminished_max = 2 + plane = MOB_PLANE //Dragon vars diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm b/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm index dd2caf5fd6..72cbcf2af4 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm @@ -78,6 +78,9 @@ can_be_drop_prey = FALSE allow_mind_transfer = TRUE + export_research_value = TECHWEB_TIER_1_POINTS + export_research_diminished_max = 3 + /mob/living/simple_mob/vore/aggressive/corrupthound/prettyboi name = "corrupt corrupt hound" desc = "Bad boy machine broke as well. Seems an attempt was made to achieve a less threatening look, and this one is definitely having some conflicting feelings about it." diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/deathclaw.dm b/code/modules/mob/living/simple_mob/subtypes/vore/deathclaw.dm index 74d62469ed..528a888a87 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/deathclaw.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/deathclaw.dm @@ -72,6 +72,9 @@ can_be_drop_prey = FALSE + export_research_value = TECHWEB_TIER_2_POINTS + export_research_diminished_max = 3 + /mob/living/simple_mob/vore/aggressive/deathclaw/Login() . = ..() if(!riding_datum) diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/greatwolf.dm b/code/modules/mob/living/simple_mob/subtypes/vore/greatwolf.dm index 7f5766366f..ce8a60744f 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/greatwolf.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/greatwolf.dm @@ -58,6 +58,9 @@ pain_emote_1p = list("yelp", "whine", "bark", "growl") pain_emote_3p = list("yelps", "whines", "barks", "growls") + export_research_value = TECHWEB_TIER_2_POINTS + export_research_diminished_max = 3 + /mob/living/simple_mob/vore/greatwolf/black name = "great black wolf" desc = "A massive black wolf with a sandy colored underside, and intimidating amber eyes. Much like a dire wolf, but bigger. Passive, until you give it a reason to not be." diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/leopardmander.dm b/code/modules/mob/living/simple_mob/subtypes/vore/leopardmander.dm index f0231ad6f9..d991be65c0 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/leopardmander.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/leopardmander.dm @@ -53,6 +53,9 @@ pain_emote_1p = list("yelp", "whine", "bark", "growl") pain_emote_3p = list("yelps", "whines", "barks", "growls") + export_research_value = TECHWEB_TIER_2_POINTS + export_research_diminished_max = 2 + /datum/category_item/catalogue/fauna/leopardmander name = "Sivian Fauna - Va'aen Drake" desc = "Classification: S Draconis uncia\ diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/solargrub.dm b/code/modules/mob/living/simple_mob/subtypes/vore/solargrub.dm index 7c23dbf4ea..e959d33067 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/solargrub.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/solargrub.dm @@ -65,6 +65,9 @@ GLOBAL_VAR_INIT(moth_amount, 0) allow_mind_transfer = TRUE glow_override = TRUE + export_research_value = TECHWEB_TIER_1_POINTS + export_research_diminished_max = 6 + /datum/say_list/solargrub emote_see = list("squelches", "squishes") diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/solarmoth.dm b/code/modules/mob/living/simple_mob/subtypes/vore/solarmoth.dm index 40365cfe47..4d68440aae 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/solarmoth.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/solarmoth.dm @@ -72,6 +72,9 @@ glow_override = TRUE + export_research_value = TECHWEB_TIER_2_POINTS + export_research_diminished_max = 4 + /datum/say_list/solarmoth emote_see = list("flutters") diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm b/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm index 2646fc84a6..2ada96f78f 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm @@ -120,3 +120,6 @@ /mob/living/simple_mob/vore/aggressive mob_bump_flag = HEAVY + + export_research_value = TECHWEB_TIER_1_POINTS + export_research_diminished_max = 5