More varied and balanced maintenance loot. (#26500)

* More varied and balanced maintenance loot.

* linting

* assign updatepaths script PR number

* went a bit overboard with the trash

* get rid of varedited lootdrop spawners on maps

* Separate trash and food trash for mappers

* fix incorrect food/other trash statistics

* TM feedback tweaking round 1

Significantly lower occurrence of all tier 2 items.

* allow randomizing angles, use for litter

* move trait delta to define, bump loot chance 10%
This commit is contained in:
warriorstar-orion
2024-08-26 21:35:14 +00:00
committed by GitHub
parent 71c1ba511e
commit 12cf1ed3ad
30 changed files with 2184 additions and 1671 deletions
@@ -0,0 +1,131 @@
/obj/effect/spawner/random_spawners
name = "random spawners"
icon = 'icons/effects/spawner_icons.dmi'
icon_state = "questionmark"
var/list/result = list(
/datum/nothing = 1,
/obj/effect/decal/cleanable/blood/splatter = 1,
/obj/effect/decal/cleanable/blood/oil = 1,
/obj/effect/decal/cleanable/fungus = 1)
var/spawn_inside = null
// This needs to use New() instead of Initialize() because the thing it creates might need to be initialized too
// AA 2022-08-11: The above comment doesnt even make sense. If extra atoms are loaded during SSatoms.Initialize(), they still get initialised!
/obj/effect/spawner/random_spawners/New()
. = ..()
var/turf/T = get_turf(src)
if(!T)
stack_trace("Spawner placed in nullspace!")
return
randspawn(T)
/obj/effect/spawner/random_spawners/proc/randspawn(turf/T)
var/thing_to_place = pickweight(result)
if(ispath(thing_to_place, /datum/nothing))
// Nothing.
qdel(src) // See line 13, this needs moving to /Initialize() so we can use the qdel hint already
return
else if(ispath(thing_to_place, /turf))
T.ChangeTurf(thing_to_place)
else
if(ispath(spawn_inside, /obj))
var/obj/O = new thing_to_place(T)
var/obj/E = new spawn_inside(T)
O.forceMove(E)
else
new thing_to_place(T)
qdel(src)
/obj/effect/spawner/random_spawners/blood_maybe
name = "blood maybe"
icon_state = "blood"
result = list(
/datum/nothing = 20,
/obj/effect/decal/cleanable/blood/splatter = 1)
/obj/effect/spawner/random_spawners/blood_often
name = "blood often"
icon_state = "blood"
result = list(
/datum/nothing = 5,
/obj/effect/decal/cleanable/blood/splatter = 1)
/obj/effect/spawner/random_spawners/oil_maybe
name = "oil maybe"
icon_state = "oil"
result = list(
/datum/nothing = 20,
/obj/effect/decal/cleanable/blood/oil = 1)
/obj/effect/spawner/random_spawners/oil_often
name = "oil often"
icon_state = "oil"
result = list(
/datum/nothing = 5,
/obj/effect/decal/cleanable/blood/oil = 1)
/obj/effect/spawner/random_spawners/cobweb_left_frequent
name = "cobweb left frequent"
icon_state = "cobwebl"
result = list(
/datum/nothing = 1,
/obj/effect/decal/cleanable/cobweb = 1)
/obj/effect/spawner/random_spawners/cobweb_right_frequent
name = "cobweb right frequent"
icon_state = "cobwebr"
result = list(
/datum/nothing = 1,
/obj/effect/decal/cleanable/cobweb2 = 1)
/obj/effect/spawner/random_spawners/cobweb_left_rare
name = "cobweb left rare"
icon_state = "cobwebl"
result = list(
/datum/nothing = 10,
/obj/effect/decal/cleanable/cobweb = 1)
/obj/effect/spawner/random_spawners/cobweb_right_rare
name = "cobweb right rare"
icon_state = "cobwebr"
result = list(
/datum/nothing = 10,
/obj/effect/decal/cleanable/cobweb2 = 1)
/obj/effect/spawner/random_spawners/dirt_frequent
name = "dirt frequent"
icon_state = "dirt"
result = list(
/datum/nothing = 1,
/obj/effect/decal/cleanable/dirt = 1)
/obj/effect/spawner/random_spawners/dirt_often
name = "dirt often"
icon_state = "dirt"
result = list(
/datum/nothing = 5,
/obj/effect/decal/cleanable/dirt = 1)
/obj/effect/spawner/random_spawners/dirt_maybe
name = "dirt maybe"
icon_state = "dirt"
result = list(
/datum/nothing = 7,
/obj/effect/decal/cleanable/dirt = 1)
/obj/effect/spawner/random_spawners/fungus_maybe
name = "fungus maybe"
icon_state = "fungus"
color = "#D5820B"
result = list(
/datum/nothing = 7,
/obj/effect/decal/cleanable/fungus = 1)
/obj/effect/spawner/random_spawners/fungus_probably
name = "fungus probably"
icon_state = "fungus"
color = "#D5820B"
result = list(
/datum/nothing = 1,
/obj/effect/decal/cleanable/fungus = 7)
@@ -1,185 +1,6 @@
/obj/effect/spawner/random_spawners
name = "random spawners"
icon = 'icons/effects/spawner_icons.dmi'
icon_state = "questionmark"
var/list/result = list(
/datum/nothing = 1,
/obj/effect/decal/cleanable/blood/splatter = 1,
/obj/effect/decal/cleanable/blood/oil = 1,
/obj/effect/decal/cleanable/fungus = 1)
var/spawn_inside = null
// This needs to use New() instead of Initialize() because the thing it creates might need to be initialized too
// AA 2022-08-11: The above comment doesnt even make sense. If extra atoms are loaded during SSatoms.Initialize(), they still get initialised!
/obj/effect/spawner/random_spawners/New()
. = ..()
var/turf/T = get_turf(src)
if(!T)
stack_trace("Spawner placed in nullspace!")
return
randspawn(T)
/obj/effect/spawner/random_spawners/proc/randspawn(turf/T)
var/thing_to_place = pickweight(result)
if(ispath(thing_to_place, /datum/nothing))
// Nothing.
qdel(src) // See line 13, this needs moving to /Initialize() so we can use the qdel hint already
return
else if(ispath(thing_to_place, /turf))
T.ChangeTurf(thing_to_place)
else
if(ispath(spawn_inside, /obj))
var/obj/O = new thing_to_place(T)
var/obj/E = new spawn_inside(T)
O.forceMove(E)
else
new thing_to_place(T)
qdel(src)
/obj/effect/spawner/random_spawners/blood_maybe
name = "blood maybe"
icon_state = "blood"
result = list(
/datum/nothing = 20,
/obj/effect/decal/cleanable/blood/splatter = 1)
/obj/effect/spawner/random_spawners/blood_often
name = "blood often"
icon_state = "blood"
result = list(
/datum/nothing = 5,
/obj/effect/decal/cleanable/blood/splatter = 1)
/obj/effect/spawner/random_spawners/oil_maybe
name = "oil maybe"
icon_state = "oil"
result = list(
/datum/nothing = 20,
/obj/effect/decal/cleanable/blood/oil = 1)
/obj/effect/spawner/random_spawners/oil_often
name = "oil often"
icon_state = "oil"
result = list(
/datum/nothing = 5,
/obj/effect/decal/cleanable/blood/oil = 1)
/obj/effect/spawner/random_spawners/proc/rustify(turf/T)
var/turf/simulated/wall/W = T
if(istype(W) && !W.rusted)
W.rust()
/obj/effect/spawner/random_spawners/wall_rusted_probably
name = "rusted wall probably"
icon_state = "rust"
/obj/effect/spawner/random_spawners/wall_rusted_probably/randspawn(turf/T)
if(prob(75))
rustify(T)
qdel(src)
/obj/effect/spawner/random_spawners/wall_rusted_maybe
name = "rusted wall maybe"
icon_state = "rust"
/obj/effect/spawner/random_spawners/wall_rusted_maybe/randspawn(turf/T)
if(prob(25))
rustify(T)
qdel(src)
/obj/effect/spawner/random_spawners/wall_rusted_always
name = "rusted wall always"
icon_state = "rust"
/obj/effect/spawner/random_spawners/wall_rusted_always/randspawn(turf/T)
rustify(T)
qdel(src)
/obj/effect/spawner/random_spawners/cobweb_left_frequent
name = "cobweb left frequent"
icon_state = "cobwebl"
result = list(
/datum/nothing = 1,
/obj/effect/decal/cleanable/cobweb = 1)
/obj/effect/spawner/random_spawners/cobweb_right_frequent
name = "cobweb right frequent"
icon_state = "cobwebr"
result = list(
/datum/nothing = 1,
/obj/effect/decal/cleanable/cobweb2 = 1)
/obj/effect/spawner/random_spawners/cobweb_left_rare
name = "cobweb left rare"
icon_state = "cobwebl"
result = list(
/datum/nothing = 10,
/obj/effect/decal/cleanable/cobweb = 1)
/obj/effect/spawner/random_spawners/cobweb_right_rare
name = "cobweb right rare"
icon_state = "cobwebr"
result = list(
/datum/nothing = 10,
/obj/effect/decal/cleanable/cobweb2 = 1)
/obj/effect/spawner/random_spawners/dirt_frequent
name = "dirt frequent"
icon_state = "dirt"
result = list(
/datum/nothing = 1,
/obj/effect/decal/cleanable/dirt = 1)
/obj/effect/spawner/random_spawners/dirt_often
name = "dirt often"
icon_state = "dirt"
result = list(
/datum/nothing = 5,
/obj/effect/decal/cleanable/dirt = 1)
/obj/effect/spawner/random_spawners/dirt_maybe
name = "dirt maybe"
icon_state = "dirt"
result = list(
/datum/nothing = 7,
/obj/effect/decal/cleanable/dirt = 1)
/obj/effect/spawner/random_spawners/fungus_maybe
name = "fungus maybe"
icon_state = "fungus"
color = "#D5820B"
result = list(
/datum/nothing = 7,
/obj/effect/decal/cleanable/fungus = 1)
/obj/effect/spawner/random_spawners/fungus_probably
name = "fungus probably"
icon_state = "fungus"
color = "#D5820B"
result = list(
/datum/nothing = 1,
/obj/effect/decal/cleanable/fungus = 7)
/obj/effect/spawner/random_spawners/mod
name = "MOD module spawner"
desc = "Modularize this, please."
icon_state = "circuit"
/obj/effect/spawner/random_spawners/mod/maint
name = "maint MOD module spawner"
result = list(
/obj/item/mod/module/springlock = 2,
/obj/item/mod/module/balloon = 1,
/obj/item/mod/module/stamp = 1
)
// z6 DEPOT SPAWNERS
/// Spawners for the Syndicate depot ruin.
/obj/effect/spawner/random_spawners/syndicate
// Turrets
/obj/effect/spawner/random_spawners/syndicate/turret
@@ -27,175 +27,6 @@
/obj/item/gun/projectile/automatic/pistol/deagle
)
/obj/effect/spawner/lootdrop/maintenance
name = "maintenance loot spawner (1 item)"
icon_state = "loot"
//How to balance this table
//-------------------------
//The total added weight of all the entries should be (roughly) equal to the total number of lootdrops
//(take in account those that spawn more than one object!)
//
//While this is random, probabilities tells us that item distribution will have a tendency to look like
//the content of the weighted table that created them.
//The less lootdrops, the less even the distribution.
//
//If you want to give items a weight <1 you can multiply all the weights by 10
//
//the "" entry will spawn nothing, if you increase this value,
//ensure that you balance it with more spawn points
//table data:
//-----------
//aft maintenance: 24 items, 18 spots 2 extra (28/08/2014)
//asmaint: 16 items, 11 spots 0 extra (08/08/2014)
//asmaint2: 36 items, 26 spots 2 extra (28/08/2014)
//fpmaint: 5 items, 4 spots 0 extra (08/08/2014)
//fpmaint2: 12 items, 11 spots 2 extra (28/08/2014)
//fsmaint: 0 items, 0 spots 0 extra (08/08/2014)
//fsmaint2: 40 items, 27 spots 5 extra (28/08/2014)
//maintcentral: 2 items, 2 spots 0 extra (08/08/2014)
//port: 5 items, 5 spots 0 extra (08/08/2014)
loot = list(
/obj/item/bodybag = 10,
/obj/item/clothing/glasses/meson = 20,
/obj/item/clothing/glasses/sunglasses = 10,
/obj/item/clothing/gloves/color/yellow/fake = 15,
/obj/item/clothing/gloves/color/fyellow = 10,
/obj/item/clothing/gloves/color/yellow = 5,
/obj/item/clothing/gloves/color/black = 20,
/obj/item/clothing/head/hardhat = 10,
/obj/item/clothing/head/hardhat/red = 10,
/obj/item/clothing/head/that = 10,
/obj/item/clothing/head/ushanka = 10,
/obj/item/clothing/head/welding = 10,
/obj/item/clothing/mask/gas = 10,
/obj/item/clothing/suit/storage/hazardvest = 10,
/obj/item/clothing/under/misc/vice = 10,
/obj/item/assembly/prox_sensor = 40,
/obj/item/assembly/timer = 30,
/obj/item/flashlight = 40,
/obj/item/flashlight/pen = 10,
/obj/item/multitool = 20,
/obj/item/radio/off = 20,
/obj/item/t_scanner = 60,
/obj/item/stack/cable_coil = 40,
/obj/item/stack/cable_coil{amount = 5} = 60,
/obj/item/stack/medical/bruise_pack/advanced = 10,
/obj/item/stack/medical/ointment/advanced = 10,
/obj/item/stack/rods{amount = 10} = 80,
/obj/item/stack/rods{amount = 23} = 20,
/obj/item/stack/rods{amount = 50} = 10,
/obj/item/stack/sheet/cardboard = 20,
/obj/item/stack/sheet/metal{amount = 20} = 10,
/obj/item/stack/sheet/mineral/plasma = 10,
/obj/item/stack/sheet/rglass = 10,
/obj/item/stack/sheet/cloth{amount = 3} = 40,
/obj/item/book/manual/wiki/engineering_construction = 10,
/obj/item/book/manual/wiki/hacking = 10,
/obj/item/clothing/head/cone = 10,
/obj/item/geiger_counter = 30,
/obj/item/coin/silver = 10,
/obj/item/coin/twoheaded = 10,
/obj/item/poster/random_contraband = 10,
/obj/item/crowbar = 10,
/obj/item/crowbar/red = 10,
/obj/item/restraints/handcuffs/toy = 5,
/obj/item/extinguisher = 90,
/obj/item/hand_labeler = 10,
/obj/item/paper/crumpled = 10,
/obj/item/pen = 10,
/obj/item/cultivator = 10,
/obj/item/reagent_containers/spray/pestspray = 10,
/obj/item/stock_parts/cell = 30,
/obj/item/storage/belt/utility = 20,
/obj/item/storage/box = 20,
/obj/item/storage/box/cups = 10,
/obj/item/storage/box/donkpockets = 10,
/obj/item/storage/box/lights/mixed = 30,
/obj/item/storage/fancy/cigarettes/dromedaryco = 10,
/obj/item/storage/toolbox/mechanical = 10,
/obj/item/screwdriver = 30,
/obj/item/tank/internals/emergency_oxygen = 20,
/obj/item/tank/internals/emergency_oxygen/engi = 10,
/obj/item/vending_refill/cola = 10,
/obj/item/weldingtool = 30,
/obj/item/wirecutters = 10,
/obj/item/wrench = 40,
/obj/item/relic = 35,
/obj/item/weaponcrafting/receiver = 2,
/obj/item/clothing/shoes/black = 30,
/obj/item/seeds/ambrosia/deus = 10,
/obj/item/seeds/ambrosia = 20,
/obj/item/clothing/under/color/black = 30,
/obj/item/stack/tape_roll = 10,
/obj/item/storage/bag/plasticbag = 20,
/obj/item/storage/wallet = 20,
/obj/item/storage/wallet/random = 5,
/obj/item/scratch = 10,
/obj/item/caution = 10,
/obj/item/mod/construction/broken_core = 4,
/obj/effect/spawner/random_spawners/mod/maint = 10,
/obj/item/melee/knuckleduster = 10,
////////////////CONTRABAND STUFF//////////////////
/obj/item/grenade/clown_grenade = 3,
/obj/item/grenade/smokebomb = 3,
/obj/item/seeds/ambrosia/cruciatus = 3,
/obj/item/gun/projectile/automatic/pistol = 1,
/obj/item/ammo_box/magazine/m10mm = 4,
/obj/item/soap/syndie = 7,
/obj/item/gun/syringe/syndicate = 2,
/obj/item/suppressor = 4,
/obj/item/clothing/under/chameleon = 2,
/obj/item/stamp/chameleon = 2,
/obj/item/clothing/shoes/chameleon/noslip = 5,
/obj/item/clothing/mask/chameleon = 4,
/obj/item/clothing/mask/chameleon/voice_change = 2,
/obj/item/clothing/mask/gas/voice_modulator = 2,
/obj/item/clothing/mask/gas/voice_modulator/chameleon = 2,
/obj/item/dnascrambler = 1,
/obj/item/storage/backpack/satchel_flat = 2,
/obj/item/storage/toolbox/syndicate = 2,
/obj/item/storage/backpack/duffel/syndie/med/surgery_fake = 2,
/obj/item/storage/belt/military/traitor = 2,
/obj/item/storage/box/syndie_kit/space = 2,
/obj/item/multitool/ai_detect = 2,
/obj/item/bio_chip_implanter/storage = 1,
/obj/item/deck/cards/syndicate = 2,
/obj/item/storage/secure/briefcase/syndie = 2,
/obj/item/storage/fancy/cigarettes/cigpack_syndicate = 2,
/obj/item/storage/pill_bottle/fakedeath = 2,
/obj/item/clothing/suit/jacket/bomber/syndicate = 5,
/obj/item/clothing/suit/storage/iaa/blackjacket/armored = 2, // More armored than bomber and has pockets, so it is rarer
/obj/item/melee/knuckleduster/syndie = 2,
/obj/item/reagent_containers/spray/sticky_tar = 1,
"" = 61 // This should be a decently high number for chances where no loot will spawn
)
/obj/effect/spawner/lootdrop/maintenance/Initialize(mapload)
if(HAS_TRAIT(SSstation, STATION_TRAIT_EMPTY_MAINT) && prob(50))
return qdel(src)
if(HAS_TRAIT(SSstation, STATION_TRAIT_FILLED_MAINT) && prob(50))
lootcount = min(lootcount * 2, 12)
. = ..()
/obj/effect/spawner/lootdrop/maintenance/two
name = "maintenance loot spawner (2 items)"
icon_state = "doubleloot"
lootcount = 2
/obj/effect/spawner/lootdrop/maintenance/three
name = "maintenance loot spawner (3 items)"
icon_state = "moreloot"
lootcount = 3
/obj/effect/spawner/lootdrop/maintenance/eight
name = "maintenance loot spawner (8 items)"
icon_state = "megaloot"
lootcount = 8
/// for ruins
/obj/effect/spawner/lootdrop/crate_spawner
name = "lootcrate spawner"
@@ -0,0 +1,54 @@
/obj/effect/spawner/random/engineering
icon = 'icons/effects/random_spawners.dmi'
icon_state = "wrench"
/obj/effect/spawner/random/engineering/tools
name = "Tool spawner"
loot = list(
/obj/item/wrench = 2,
/obj/item/wirecutters = 2,
/obj/item/screwdriver = 2,
/obj/item/crowbar = 2,
/obj/item/weldingtool = 2,
/obj/item/stack/cable_coil = 2,
/obj/item/analyzer = 2,
/obj/item/t_scanner = 2,
/obj/item/geiger_counter = 2,
/obj/item/multitool = 1,
)
/obj/effect/spawner/random/engineering/materials
name = "Materials spawner"
icon_state = "metal"
loot = list(
list(
/obj/item/stack/rods,
/obj/item/stack/sheet/metal,
/obj/item/stack/sheet/glass,
/obj/item/stack/sheet/rglass,
/obj/item/stack/sheet/wood,
) = 8,
list(
/obj/item/stack/sheet/plastic,
/obj/item/stack/sheet/plasteel,
/obj/item/stack/sheet/mineral/plasma,
) = 2,
)
/obj/effect/spawner/random/engineering/materials/make_item(spawn_loc, type_path_to_make)
var/obj/item/stack/item = ..()
if(istype(item))
item.amount = rand(1, 10)
item.update_icon()
return item
/obj/effect/spawner/random/engineering/toolbox
name = "Toolbox spawner"
icon_state = "toolbox"
loot = list(
/obj/item/storage/toolbox/mechanical,
/obj/item/storage/toolbox/electrical,
/obj/item/storage/toolbox/emergency
)
@@ -0,0 +1,20 @@
/obj/effect/spawner/random/snacks
name = "snacks spawner"
icon = 'icons/effects/random_spawners.dmi'
icon_state = "donkpocket_single"
loot = list(
list(
/obj/item/food/candy/candybar,
/obj/item/reagent_containers/drinks/dry_ramen,
/obj/item/food/chips,
/obj/item/food/twimsts,
/obj/item/food/sosjerky,
/obj/item/food/no_raisin,
/obj/item/food/pistachios,
/obj/item/food/spacetwinkie,
/obj/item/food/cheesiehonkers,
/obj/item/food/tastybread,
) = 5,
/obj/item/food/stroopwafel = 1,
)
@@ -0,0 +1,19 @@
#define TRAIT_CHANCE_DELTA 25
/obj/effect/spawner/random/maintenance
name = "Maintenance loot spawner"
spawn_loot_chance = 65
spawn_random_offset_max_pixels = 8
/obj/effect/spawner/random/maintenance/Initialize(mapload)
loot = GLOB.maintenance_loot_tables
spawn_loot_count = rand(2, 4)
if(HAS_TRAIT(SSstation, STATION_TRAIT_EMPTY_MAINT))
spawn_loot_chance -= TRAIT_CHANCE_DELTA
else if(HAS_TRAIT(SSstation, STATION_TRAIT_FILLED_MAINT))
spawn_loot_chance += TRAIT_CHANCE_DELTA
. = ..()
#undef TRAIT_CHANCE_DELTA
@@ -0,0 +1,92 @@
/obj/effect/spawner/random/dice
loot = list(
/obj/item/dice/d4,
/obj/item/dice/d6,
/obj/item/dice/d8,
/obj/item/dice/d10,
/obj/item/dice/d12,
)
/obj/effect/spawner/random/dice/Initialize()
. = ..()
spawn_loot_count = rand(1, 2)
/obj/effect/spawner/random/bureaucracy
icon = 'icons/effects/random_spawners.dmi'
icon_state = "folder"
name = "bureaucracy spawner"
loot = list(
/obj/item/hand_labeler,
/obj/item/hand_labeler_refill,
/obj/item/stack/tape_roll,
/obj/item/paper_bin,
/obj/item/pen,
/obj/item/pen/blue,
/obj/item/pen/red,
/obj/item/folder/blue,
/obj/item/folder/red,
/obj/item/folder/yellow,
/obj/item/clipboard,
)
/obj/effect/spawner/random/book
icon = 'icons/effects/random_spawners.dmi'
icon_state = "book"
name = "book spawner"
loot = list(
/obj/item/book/manual/atmospipes,
/obj/item/book/manual/barman_recipes,
/obj/item/book/manual/detective,
/obj/item/book/manual/engineering_particle_accelerator,
/obj/item/book/manual/engineering_singularity_safety,
/obj/item/book/manual/evaguide,
/obj/item/book/manual/hydroponics_pod_people,
/obj/item/book/manual/medical_cloning,
/obj/item/book/manual/research_and_development,
/obj/item/book/manual/ripley_build_and_repair,
/obj/item/book/manual/supermatter_engine,
/obj/item/book/manual/wiki/botanist,
/obj/item/book/manual/wiki/chef_recipes,
/obj/item/book/manual/wiki/engineering_construction,
/obj/item/book/manual/wiki/engineering_guide,
/obj/item/book/manual/wiki/experimentor,
/obj/item/book/manual/wiki/faxes,
/obj/item/book/manual/wiki/hacking,
/obj/item/book/manual/wiki/hydroponics,
/obj/item/book/manual/wiki/robotics_cyborgs,
/obj/item/book/manual/wiki/security_space_law,
/obj/item/book/manual/wiki/security_space_law/black,
/obj/item/book/manual/wiki/sop_command,
/obj/item/book/manual/wiki/sop_engineering,
/obj/item/book/manual/wiki/sop_general,
/obj/item/book/manual/wiki/sop_legal,
/obj/item/book/manual/wiki/sop_medical,
/obj/item/book/manual/wiki/sop_science,
/obj/item/book/manual/wiki/sop_security,
/obj/item/book/manual/wiki/sop_service,
/obj/item/book/manual/wiki/sop_supply,
/obj/item/book/manual/zombie_manual,
)
/obj/effect/spawner/random/book/record_item(type_path_to_make)
SSblackbox.record_feedback("tally", "random_spawners", 1, "[/obj/item/book]")
/obj/effect/spawner/random/mod/maint
name = "maint MOD module spawner"
loot = list(
/obj/item/mod/module/springlock = 2,
/obj/item/mod/module/balloon = 1,
/obj/item/mod/module/stamp = 1
)
/obj/effect/spawner/random/janitor/supplies
icon = 'icons/effects/random_spawners.dmi'
icon_state = "mopbucket"
name = "janitorial supplies spawner"
loot = list(
/obj/item/storage/box/mousetraps,
/obj/item/storage/box/lights/tubes,
/obj/item/storage/box/lights/mixed,
/obj/item/storage/box/lights/bulbs,
)
@@ -0,0 +1,143 @@
/**
* Base class for all random spawners.
*/
/obj/effect/spawner/random
icon = 'icons/effects/spawner_icons.dmi'
icon_state = "loot"
layer = OBJ_LAYER
/// Stops persistent lootdrop spawns from being shoved into lockers
anchored = TRUE
/// A list of possible items to spawn e.g. list(/obj/item, /obj/structure, /obj/effect)
var/list/loot
/// The subtypes AND type to combine with the loot list
var/loot_type_path
/// The subtypes (this excludes the provided path) to combine with the loot list
var/loot_subtype_path
/// How many items will be spawned
var/spawn_loot_count = 1
/// If the same item can be spawned twice
var/spawn_loot_double = TRUE
/// Whether the items should be distributed to offsets 0,1,-1,2,-2,3,-3.. This overrides pixel_x/y on the spawner itself
var/spawn_loot_split = FALSE
/// The pixel x/y divider offsets for spawn_loot_split (spaced 1 pixel apart by default)
var/spawn_loot_split_pixel_offsets = 2
/// Whether the spawner should spawn all the loot in the list
var/spawn_all_loot = FALSE
/// The chance for the spawner to create loot (ignores spawn_loot_count)
var/spawn_loot_chance = 100
/// Determines how big of a range (in tiles) we should scatter things in.
var/spawn_scatter_radius = 0
/// Whether the items should have a random pixel_x/y offset (maximum offset distance is ± spawn_random_offset_max_pixels for x/y)
var/spawn_random_offset = FALSE
/// Maximum offset distance for random pixel offsets.
var/spawn_random_offset_max_pixels = 16
/// Whether the spawned items should be rotated randomly.
var/spawn_random_angle = FALSE
// Brief explanation:
// Rather then setting up and then deleting spawners, we block all atomlike setup
// and do the absolute bare minimum
// This is with the intent of optimizing mapload
// TODO: Bring this optimization up one level if possible
/obj/effect/spawner/random/Initialize(mapload)
SHOULD_CALL_PARENT(FALSE)
if(initialized)
stack_trace("Warning: [src]([type]) initialized multiple times!")
initialized = TRUE
spawn_loot()
return INITIALIZE_HINT_QDEL
///If the spawner has any loot defined, randomly picks some and spawns it. Does not cleanup the spawner.
/obj/effect/spawner/random/proc/spawn_loot(lootcount_override)
if(!prob(spawn_loot_chance))
return
var/list/spawn_locations = get_spawn_locations(spawn_scatter_radius)
var/spawn_loot_count = isnull(lootcount_override) ? src.spawn_loot_count : lootcount_override
if(spawn_all_loot)
spawn_loot_count = INFINITY
spawn_loot_double = FALSE
if(loot_type_path)
loot += typesof(loot_type_path)
if(loot_subtype_path)
loot += subtypesof(loot_subtype_path)
if(length(loot))
var/loot_spawned = 0
var/pixel_divider = FLOOR(spawn_random_offset_max_pixels / spawn_loot_split_pixel_offsets, 1)
while((spawn_loot_count-loot_spawned) && length(loot))
var/lootspawn = pick_weight_recursive(loot)
if(!spawn_loot_double)
loot.Remove(lootspawn)
if(lootspawn)
var/turf/spawn_loc = loc
if(spawn_scatter_radius > 0 && length(spawn_locations))
spawn_loc = pick(spawn_locations)
var/atom/movable/spawned_loot = make_item(spawn_loc, lootspawn)
spawned_loot.setDir(dir)
if(!spawn_loot_split && !spawn_random_offset)
if(pixel_x != 0)
spawned_loot.pixel_x = pixel_x
if(pixel_y != 0)
spawned_loot.pixel_y = pixel_y
else if(spawn_random_offset)
spawned_loot.pixel_x = rand(-spawn_random_offset_max_pixels, spawn_random_offset_max_pixels)
spawned_loot.pixel_y = rand(-spawn_random_offset_max_pixels, spawn_random_offset_max_pixels)
else if(spawn_loot_split)
if(loot_spawned)
var/column = FLOOR(loot_spawned / pixel_divider, 1)
spawned_loot.pixel_x = spawn_loot_split_pixel_offsets * (loot_spawned % pixel_divider) + (column * spawn_loot_split_pixel_offsets)
spawned_loot.pixel_y = spawn_loot_split_pixel_offsets * (loot_spawned % pixel_divider)
loot_spawned++
/**
* Makes the actual item related to our spawner.
*
* spawn_loc - where are we spawning it?
* type_path_to_make - what are we spawning?
**/
/obj/effect/spawner/random/proc/make_item(spawn_loc, type_path_to_make)
var/result = new type_path_to_make(spawn_loc)
record_item(type_path_to_make)
var/atom/item = result
if(spawn_random_angle && istype(item))
item.transform = turn(item.transform, rand(0, 360))
return result
/obj/effect/spawner/random/proc/record_item(type_path_to_make)
if(ispath(type_path_to_make, /obj/effect))
return
SSblackbox.record_feedback("tally", "random_spawners", 1, "[type_path_to_make]")
///If the spawner has a spawn_scatter_radius set, this creates a list of nearby turfs available that are in view and have an unblocked line to them.
/obj/effect/spawner/random/proc/get_spawn_locations(radius)
var/list/scatter_locations = list()
if(!radius)
return scatter_locations
for(var/turf/turf_in_view in view(radius, get_turf(src)))
if(!isfloorturf(turf_in_view))
continue
if(!has_unblocked_line(turf_in_view))
continue
scatter_locations += turf_in_view
return scatter_locations
/obj/effect/spawner/random/proc/has_unblocked_line(destination)
for(var/turf/potential_blockage as anything in get_line(get_turf(src), destination))
if(!is_blocked_turf(potential_blockage, exclude_mobs = TRUE))
continue
return FALSE
return TRUE
@@ -0,0 +1,425 @@
# Random Spawners
## About
Random spawners are an organized tool primarily for mapping to enhance replayability. The spawners can create objects, effects, and structures with different tweakable settings to get the desired outcome. You can make a spawner determine direction, rarity, number of items to spawn, pixel spacing between items, and even spread it over a large tile radius. This lets you control the atmosphere of a location. You could for instance spawn different piles of trash in maint or spawn decoration items for a room to give it more randomized flavor. The choice is yours!
*(note the audience of this README is directed towards mappers who lack knowledge of coding)*
## Variables
The following variables are defined in `code/game/objects/effects/spawners/random/random.dm` that control how a spawner works.
- `loot` - a list of possible items to spawn e.g. list(/obj/item, /obj/structure, /obj/effect)
- `loot_type_path` - this combines the subtypes AND type list with the loot list
- `loot_subtype_path` - this combines ONLY the subtypes (excludes the `loot_subtype_path`) with the loot list
- `spawn_loot_count` - how many items will be spawned
- `spawn_loot_double` - if the same item can be spawned twice from the loot list
- `spawn_loot_split` - whether the items should be distributed to offsets 0,1,-1,2,-2,3,-3.. This overrides pixel_x/y on the spawner itself
- `spawn_all_loot` - whether the spawner should spawn all the loot in the list (ignores `spawn_loot_count`)
- `spawn_loot_chance` - the chance for the spawner to create loot (ignores `spawn_loot_count`)
- `spawn_scatter_radius` - determines how big of a range (in tiles) we should scatter things in
These variables are set to the following default values for the base `random.dm` object that all objects inherit from:
```dm
/// these three loot values are all empty
var/list/loot
var/loot_type_path
var/loot_subtype_path
var/spawn_loot_count = 1 // by default one item will be selected from the loot list
var/spawn_loot_double = TRUE // by default duplicate items CAN be spawned from the loot list
var/spawn_loot_split = FALSE // by default items will NOT spread out on the same tile
var/spawn_all_loot = FALSE // by default the spawner will only spawn the number of items set in spawn_loot_count
var/spawn_loot_chance = 100 // by default the spawner has a 100% chance to spawn the item(s)
var/spawn_scatter_radius = 0 // by default the spawner will spawn the items ONLY on the tile it is on
```
However there are some categories that overwrite these default values so pay attention to the folder or category you group your spawner in. For instance the `obj/effect/spawner/random/techstorage` category overwrites the `spawn_all_loot` and the `spawn_loot_split` variables.
```dm
// Tech storage circuit board spawners
/obj/effect/spawner/random/techstorage
name = "generic circuit board spawner"
spawn_loot_split = TRUE
spawn_all_loot = TRUE
```
This means any spawner you create under the techstorage will also have those variables set to that by default. This can be overridden quite easily just be resetting the variables back to the normal state like so:
```dm
/obj/effect/spawner/random/techstorage/data_disk
name = "data disk spawner"
spawn_all_loot = FALSE // now our loot won't all be spawned
loot = list(
/obj/item/disk/data = 49,
/obj/item/disk/nuclear/fake/obvious = 1,
)
```
## Template
All the random spawners follow the same template format to keep things consistent and unison.
```dm
/obj/effect/spawner/random/INSERT_SPAWNER_GROUP/INSERT_SPAWNER_NAME
name = "INSERT_SPAWNER_NAME spawner"
loot = list(
/obj/item/PATH/INSERT_OBJ_1,
/obj/item/PATH/INSERT_OBJ_2,
/obj/item/PATH/INSERT_OBJ_3,
)
```
All the capitalized code is the parts where you are supposed to swap out with your objects like so:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(
/obj/item/stack/medical/suture,
/obj/item/stack/medical/mesh,
/obj/item/stack/medical/gauze,
)
```
Find the path to different objects and add them to the list but try to be consistent with the types of the object and the spawner. For example a medical spawner shouldn't have a emag in the loot list. (use an antag spawner for that instead!)
## Probability
Be aware that the `loot` list uses a *weighted chance* formula to determine probability. So if there are no numbers set in the `loot` list then each object defaults to 1 and has the same probability to be selected. For our above example for the `minor_healing` spawner each medical item has a 1/3 chance to be spawned. But if we rearranged the values to this:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(
/obj/item/stack/medical/suture = 2,
/obj/item/stack/medical/mesh = 1,
/obj/item/stack/medical/gauze = 1,
)
```
Then now `suture` has a 50% chance of being spawned (2/4), `mesh` has a 25% chance of being spawned (1/4), and `gauze` also has a 25% chance of being spawned (1/4). If we add another item into the mix then we get the following:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(
/obj/item/stack/medical/suture = 2,
/obj/item/stack/medical/mesh = 1,
/obj/item/stack/medical/gauze = 1,
/obj/item/reagent_containers/syringe = 1,
)
```
Suture is 40% (2/5), Mesh is 20% (1/5), Gauze is 20% (1/5), and Syringe is 20% (1/5). A weighted list has the advantage of not needing to update every item in the list when adding a new item. If the list was based on a straight percent values, then each new item would require to manually go and edit ALL the items in the list. For big lists that would become very tedious. This is why we use weighted lists to determine probability!
## Style
Here are some simple guidelines that you should stick to when making a new spawner:
### If ALL the items have the same chance, we should not set a weighted value to the item
Do not put `/obj/item/ = 1` unless other items have different spawn chances
Good:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(
/obj/item/stack/medical/suture = 2,
/obj/item/stack/medical/mesh = 1,
/obj/item/stack/medical/gauze = 1,
/obj/item/reagent_containers/syringe = 1,
)
```
Also Good:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(
/obj/item/stack/medical/suture,
/obj/item/stack/medical/mesh,
/obj/item/stack/medical/gauze,
/obj/item/reagent_containers/syringe,
)
```
Bad:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(
/obj/item/stack/medical/suture = 1,
/obj/item/stack/medical/mesh = 1,
/obj/item/stack/medical/gauze = 1,
/obj/item/reagent_containers/syringe = 1,
)
```
### Sort the list from highest probability to lowest
Sort from top to bottom. The rarest items for your spawner should be at the bottom of the list.
Good:
```dm
/obj/effect/spawner/random/contraband/armory
name = "armory loot spawner"
loot = list(
/obj/item/gun/ballistic/automatic/pistol = 8,
/obj/item/gun/ballistic/shotgun/automatic/combat = 5,
/obj/item/storage/box/syndie_kit/throwing_weapons = 3,
/obj/item/grenade/clusterbuster/teargas = 2,
/obj/item/grenade/clusterbuster = 2,
/obj/item/gun/ballistic/automatic/pistol/deagle = 1,
/obj/item/gun/ballistic/revolver/mateba = 1,
)
```
Bad:
```dm
/obj/effect/spawner/random/contraband/armory
name = "armory loot spawner"
loot = list(
/obj/item/storage/box/syndie_kit/throwing_weapons = 3,
/obj/item/gun/ballistic/automatic/pistol = 8,
/obj/item/gun/ballistic/revolver/mateba = 1,
/obj/item/grenade/clusterbuster/teargas = 2,
/obj/item/gun/ballistic/automatic/pistol/deagle = 1,
/obj/item/grenade/clusterbuster = 2,
/obj/item/gun/ballistic/shotgun/automatic/combat = 5,
)
```
### Always put the `loot` list at the bottom of your spawner
This is just to keep things organized.
Good:
```dm
/obj/effect/spawner/random/food_or_drink/donkpockets
name = "donk pocket box spawner"
spawn_loot_double = FALSE
loot = list(
/obj/item/storage/box/donkpockets/donkpocketspicy,
/obj/item/storage/box/donkpockets/donkpocketteriyaki,
/obj/item/storage/box/donkpockets/donkpocketpizza,
/obj/item/storage/box/donkpockets/donkpocketberry,
/obj/item/storage/box/donkpockets/donkpockethonk,
)
```
Bad:
```dm
/obj/effect/spawner/random/food_or_drink/donkpockets
name = "donk pocket box spawner"
loot = list(
/obj/item/storage/box/donkpockets/donkpocketspicy,
/obj/item/storage/box/donkpockets/donkpocketteriyaki,
/obj/item/storage/box/donkpockets/donkpocketpizza,
/obj/item/storage/box/donkpockets/donkpocketberry,
/obj/item/storage/box/donkpockets/donkpockethonk,
)
spawn_loot_double = FALSE
```
### Always put a comma at the last item in the `loot` list
This will make it easier for people to add items to your spawner later without getting frustrating code errors.
Good:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(
/obj/item/stack/medical/suture,
/obj/item/stack/medical/mesh,
/obj/item/stack/medical/gauze,
)
```
Bad:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(
/obj/item/stack/medical/suture,
/obj/item/stack/medical/mesh,
/obj/item/stack/medical/gauze // if someone adds an item to the list later it will cause an error
)
```
### Keep the same tab formatting for the `loot` list (unless there is only one item)
Again, this is just good code organization. If there is only one item, then encase that item in `loot = list(item)`
Good:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(
/obj/item/stack/medical/suture,
/obj/item/stack/medical/mesh,
/obj/item/stack/medical/gauze,
)
```
Also Good:
```dm
/obj/effect/spawner/random/structure/crate_abandoned
icon = 'icons/effects/landmarks_static.dmi'
icon_state = "loot_site"
spawn_loot_chance = 20
loot = list(/obj/structure/closet/crate/secure/loot)
```
Bad:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(
/obj/item/stack/medical/suture,
/obj/item/stack/medical/mesh,
/obj/item/stack/medical/gauze,
)
```
Also Bad:
```dm
/obj/effect/spawner/random/medical/minor_healing
name = "minor healing spawner"
loot = list(/obj/item/stack/medical/suture,
/obj/item/stack/medical/mesh,
/obj/item/stack/medical/gauze,)
```
### Try to keep the total combined weight of your `loot` list to sane values (Aim for 5, 10, 20, 50, or 100)
This makes the math probability easier for people to calculate. (this is recommended, but not always possible)
Good:
```dm
/obj/effect/spawner/random/trash/cigbutt
name = "cigarette butt spawner"
loot = list(
/obj/item/cigbutt = 65,
/obj/item/cigbutt/roach = 20,
/obj/item/cigbutt/cigarbutt = 15,
)
```
Also Good:
```dm
/obj/effect/spawner/random/trash/botanical_waste
name = "botanical waste spawner"
loot = list(
/obj/item/grown/bananapeel = 6,
/obj/item/grown/corncob = 3,
/obj/item/food/grown/bungopit = 1,
)
```
Bad:
```dm
/obj/effect/spawner/random/entertainment/money_large
name = "large money spawner"
loot = list(
/obj/item/stack/spacecash/c1 = 521,
/obj/item/stack/spacecash/c10 = 378,
/obj/item/stack/spacecash/c20 = 212,
/obj/item/stack/spacecash/c50 = 205,
/obj/item/stack/spacecash/c100 = 71,
/obj/item/stack/spacecash/c200 = 60,
/obj/item/stack/spacecash/c500 = 57,
/obj/item/stack/spacecash/c1000 = 41,
/obj/item/stack/spacecash/c10000 = 12,
)
```
### Do not put empty items in the loot list
Instead use the `spawn_loot_chance` var to control the chance for the spawner to spawn nothing.
Good:
```dm
/obj/effect/spawner/random/structure/crate_abandoned
name = "locked crate spawner"
spawn_loot_chance = 20
loot = list(/obj/structure/closet/crate/secure/loot)
```
Bad:
```dm
/obj/effect/spawner/lootdrop/crate_spawner
name = "lootcrate spawner"
loot = list(
"" = 80
/obj/structure/closet/crate/secure/loot = 20,
)
```
### Avoid making a spawner that is a duplicate
We don't want copy-cat spawners that are almost identical. Instead merge spawners together if possible.
Good:
```dm
/obj/effect/spawner/random/contraband/armory
name = "armory loot spawner"
icon_state = "pistol"
loot = list(
/obj/item/gun/ballistic/automatic/pistol = 8,
/obj/item/gun/ballistic/shotgun/automatic/combat = 5,
/obj/item/storage/box/syndie_kit/throwing_weapons = 3,
/obj/item/grenade/clusterbuster/teargas = 2,
/obj/item/grenade/clusterbuster = 2,
/obj/item/gun/ballistic/automatic/pistol/deagle,
/obj/item/gun/ballistic/revolver/mateba,
)
```
Bad:
```dm
/obj/effect/spawner/lootdrop/armory_contraband
loot = list(
/obj/item/gun/ballistic/automatic/pistol = 8,
/obj/item/gun/ballistic/shotgun/automatic/combat = 5,
/obj/item/gun/ballistic/automatic/pistol/deagle,
/obj/item/gun/ballistic/revolver/mateba
)
/obj/effect/spawner/lootdrop/armory_contraband/metastation
loot = list(
/obj/item/gun/ballistic/automatic/pistol = 8,
/obj/item/gun/ballistic/shotgun/automatic/combat = 5,
/obj/item/storage/box/syndie_kit/throwing_weapons = 3,
/obj/item/gun/ballistic/automatic/pistol/deagle,
/obj/item/gun/ballistic/revolver/mateba
)
```
@@ -0,0 +1,75 @@
/// Food trash spawner, for when you specifically want it to look like someone
/// didn't clean up after themselves after lunch.
/obj/effect/spawner/random/food_trash
icon = 'icons/effects/random_spawners.dmi'
icon_state = "tray"
name = "Food trash spawner"
loot = list(
/obj/item/trash/bowl,
/obj/item/trash/candle,
/obj/item/trash/candy,
/obj/item/trash/cheesie,
/obj/item/trash/chips,
/obj/item/trash/fried_vox,
/obj/item/trash/gum,
/obj/item/trash/liquidfood,
/obj/item/trash/pistachios,
/obj/item/trash/plate,
/obj/item/trash/popcorn,
/obj/item/trash/popsicle_stick,
/obj/item/trash/raisins,
/obj/item/trash/semki,
/obj/item/trash/snack_bowl,
/obj/item/trash/sosjerky,
/obj/item/trash/spacetwinkie,
/obj/item/trash/syndi_cakes,
/obj/item/trash/tastybread,
/obj/item/trash/tray,
/obj/item/trash/twimsts,
/obj/item/trash/waffles,
)
spawn_random_angle = TRUE
/obj/effect/spawner/random/food_trash/record_item(type_path_to_make)
SSblackbox.record_feedback("tally", "random_spawners", 1, "[/obj/item/trash]")
/obj/effect/spawner/random/trash
icon = 'icons/effects/random_spawners.dmi'
icon_state = "trash"
name = "Trash spawner"
loot = list(
// Food litter often
/obj/effect/spawner/random/food_trash = 8,
// Some regular trash
list(
/obj/item/broken_bottle,
/obj/item/cigbutt,
/obj/item/cigbutt/roach,
/obj/item/flashlight/flare/glowstick/used,
/obj/item/flashlight/flare/used,
/obj/item/paper/crumpled,
/obj/item/shard,
/obj/item/trash/tapetrash,
) = 5,
// Ammo casings rarely
list(
/obj/item/ammo_casing/c10mm,
/obj/item/trash/spentcasing,
) = 1,
)
// TODO: Random spawner scatter behavior doesn't work well with items in
// containers or on dense objects like racks. Fix up so we can scatter trash.
spawn_random_angle = TRUE
spawn_random_offset = TRUE
spawn_random_offset_max_pixels = 8
/obj/effect/spawner/random/trash/record_item(type_path_to_make)
if(istype(type_path_to_make, /obj/effect/spawner))
return
SSblackbox.record_feedback("tally", "random_spawners", 1, "[/obj/item/trash]")
@@ -0,0 +1,30 @@
/obj/effect/spawner/random_spawners/proc/rustify(turf/T)
var/turf/simulated/wall/W = T
if(istype(W) && !W.rusted)
W.rust()
/obj/effect/spawner/random_spawners/wall_rusted_probably
name = "rusted wall probably"
icon_state = "rust"
/obj/effect/spawner/random_spawners/wall_rusted_probably/randspawn(turf/T)
if(prob(75))
rustify(T)
qdel(src)
/obj/effect/spawner/random_spawners/wall_rusted_maybe
name = "rusted wall maybe"
icon_state = "rust"
/obj/effect/spawner/random_spawners/wall_rusted_maybe/randspawn(turf/T)
if(prob(25))
rustify(T)
qdel(src)
/obj/effect/spawner/random_spawners/wall_rusted_always
name = "rusted wall always"
icon_state = "rust"
/obj/effect/spawner/random_spawners/wall_rusted_always/randspawn(turf/T)
rustify(T)
qdel(src)
@@ -226,6 +226,22 @@
attack_verb = list("burnt", "singed")
START_PROCESSING(SSobj, src)
/obj/item/flashlight/flare/used
/obj/item/flashlight/flare/used/Initialize()
. = ..()
// fuel gets set on New which is annoying so these can't just be vars
fuel = 0
on = 0
update_icon()
/obj/item/flashlight/flare/glowstick/used/Initialize()
. = ..()
// fuel gets set on New which is annoying so these can't just be vars
fuel = 0
on = 0
update_icon()
/obj/item/flashlight/flare/decompile_act(obj/item/matter_decompiler/C, mob/user)
if(isdrone(user) && !fuel)
C.stored_comms["metal"] += 1