Stasis Cage Exporting (#19612)

* sellable stasis cage

* Sellable mobs

* crate code cleanup

* export fix

* clamp

* sold mobs should vanish
This commit is contained in:
Will
2026-07-16 20:12:59 -04:00
committed by GitHub
parent f16080262e
commit 5d2ed77b57
19 changed files with 176 additions and 37 deletions
+5 -1
View File
@@ -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))
+62 -1
View File
@@ -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))
@@ -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
@@ -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)
+8 -2
View File
@@ -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))
+18 -5
View File
@@ -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
@@ -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
@@ -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)
. = ..()
@@ -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"
@@ -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")
@@ -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", \
@@ -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
@@ -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."
@@ -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)
@@ -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."
@@ -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\
@@ -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")
@@ -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")
@@ -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