[MIRROR] Collector event machine (#10962)

Co-authored-by: SatinIsle <98125273+SatinIsle@users.noreply.github.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-05-29 04:21:24 -07:00
committed by GitHub
parent 2d8c2baf79
commit baaced85c4
11 changed files with 562 additions and 1 deletions

View File

@@ -0,0 +1,73 @@
/*
Event Collector Admin Commands
These need to be added to admin_verb_lists_vr.dm
*/
/client/proc/modify_event_collector(var/obj/structure/event_collector/target in GLOB.event_collectors)
set category = "Fun.Event Kit"
set desc="Configure Event Collector"
set name="Configure Collector"
if(!check_rights(R_ADMIN))
return
var/msg = "---------------\n"
if(target?.active_recipe?.len > 0)
msg += " [target] has [target.active_recipe.len] left in its current recipe\n"
for(var/i in target.active_recipe)
msg += "* [i] \n"
else
msg += "[target] has no more required items! \n"
if(target.calls_remaining > 0)
msg += "[target] has [target.calls_remaining] progress to go! - unless stopped or slowed, this is about [(target.calls_remaining / 10 ) * 2] seconds! \n"
var/blockers = target.get_blockers()
if(blockers > 0)
msg += "[target] has [blockers] things blocking/slowing it down! Anything more than 10 means it's stopped!"
to_chat(usr,msg)
var/list/options = list(
"Cancel",
"Start New Recipe",
"Clear Current Recipe",
"Force Clear Blockers"
)
var/option = tgui_input_list(usr, "What Would You Like To Do?", "Event Collector",options,"Cancel")
switch(option)
if("Cancel")
return
if("Start New Recipe")
target.pick_new_recipe()
if("Clear Current Recipe")
target.active_recipe = list()
target.awaiting_next_recipe = TRUE
target.calls_remaining = 0
if("Force Clear Blockers")
if(islist(GLOB.event_collector_blockers[target.blocker_channel]))
for(var/obj/structure/event_collector_blocker/tofix in GLOB.event_collector_blockers[target.blocker_channel])
tofix.fix()
if("Empty Stored Items")
target.empty_items()
/client/proc/induce_malfunction(var/obj/structure/event_collector_blocker/target in GLOB.event_collector_blockers)
set category = "Fun.Event Kit"
set desc="Configure Collector Blocker"
set name="Toggle Malfunction State"
if(!check_rights(R_ADMIN))
return
if(target.block_amount)
target.fix()
else
target.induce_failure()

View File

@@ -0,0 +1,131 @@
/obj/structure/event_collector_blocker
var/blocker_channel = "collector"
var/base_icon = "blocker"
icon = 'icons/obj/general_collector.dmi'
icon_state = "blocker_on"
desc = "blocker? I barely know er"
var/tools_to_fix = FALSE
//how much we're currently blocking
var/block_amount = 0
//how much we'll block when broken
var/default_block_amount = 100
//tool to what's fucked up
var/list/problem_descs = list(
TOOL_CROWBAR = "a panel is dislodged.",
TOOL_MULTITOOL = "a light next to a dataport is flashing some errors.",
TOOL_SCREWDRIVER = "there's some loose screws inside.",
TOOL_WIRECUTTER = "some loose wires are shorting things out.",
TOOL_WRENCH = "One of the supporting bolts are concerningly loose.",
TOOL_CABLE_COIL = "There's some wires that need replacement.",
TOOL_WELDER = "One of the brackets has a cracked weld."
)
//tool to how we unfuck it
var/list/fix_descs = list(
TOOL_CROWBAR = "you lodge the panel back in place.",
TOOL_MULTITOOL = "you reset the panel with the multitool.",
TOOL_SCREWDRIVER = "you tighten the loose screws.",
TOOL_WIRECUTTER = "you remove the excess wiring.",
TOOL_WRENCH = "you tighten up the supporting bolts.",
TOOL_CABLE_COIL = "you replace the worn wires.",
TOOL_WELDER = "you fix up the worn weld."
)
//what tools we need
var/list/active_repair_steps = list()
/obj/structure/event_collector_blocker/Initialize(mapload)
. = ..()
GLOB.event_collector_blockers |= src
if(GLOB.event_collector_associations == null)
GLOB.event_collector_associations = list()
if(GLOB.event_collector_associations[blocker_channel] == null)
GLOB.event_collector_associations[blocker_channel] = list()
GLOB.event_collector_associations[blocker_channel] |= src
/obj/structure/event_collector_blocker/Destroy()
GLOB.event_collector_blockers -= src
if(GLOB.event_collector_associations[blocker_channel])
GLOB.event_collector_associations[blocker_channel] -= src
. = ..()
/obj/structure/event_collector_blocker/update_icon()
. = ..()
icon_state = "[base_icon]_[block_amount ? "off" : "on"]"
/obj/structure/event_collector_blocker/proc/induce_failure(var/intensity = -1) //progress to remove from the machine
if(intensity == -1)
intensity = default_block_amount
block_amount = intensity
if(tools_to_fix)
active_repair_steps = list()
for(var/i in 1 to 4)
active_repair_steps += pick(list(TOOL_CROWBAR,TOOL_MULTITOOL,TOOL_SCREWDRIVER,TOOL_WRENCH,TOOL_CABLE_COIL,TOOL_WELDER)) //todo, make this a different list on the obj "Possible failures" or whatever.
update_icon();
/obj/structure/event_collector_blocker/proc/fix()
block_amount = 0
active_repair_steps = list()
update_icon();
/obj/structure/event_collector_blocker/examine(mob/user, infix, suffix)
. = ..()
if(block_amount)
if(tools_to_fix)
if(active_repair_steps.len >= 1)
. += span_warning(problem_descs[active_repair_steps[active_repair_steps.len]])
else
. += span_warning("It looks kinda messed up!")
else
. += span_warning("Looks like the breaker flipped!")
else
. += span_notice("Looks like it's functioning normally")
/obj/structure/event_collector_blocker/proc/get_repair_message(var/mob/user)
return "[user] repairs [src]"
/obj/structure/event_collector_blocker/attack_hand(mob/user)
if(!tools_to_fix && block_amount > 0)
user.visible_message("[user] fixes [src] up!") //swap this with a message var, or just change it to be suitable
fix()
. = ..()
/obj/structure/event_collector_blocker/attackby(obj/item/O, mob/user)
. = ..()
if(tools_to_fix)
if(active_repair_steps.len >= 1)
if(O.has_tool_quality(active_repair_steps[active_repair_steps.len]))
if(do_after(user, 2 SECONDS))
to_chat(usr,span_notice(fix_descs[active_repair_steps[active_repair_steps.len]]))
active_repair_steps.len = active_repair_steps.len - 1
if(active_repair_steps.len == 0)
fix()
else
to_chat(user,"WRONG TOOL!")
/obj/structure/event_collector_blocker/breaker //for these, if you change the base_icon you'll be good to go. ae, base_icon = breaker or circuit or whatever
name = "Breaker"
desc = "I barely know er!"
/obj/structure/event_collector_blocker/breaker/get_repair_message(var/mob/user)
return "[user] flips [src]!"
/obj/structure/event_collector_blocker/circuit_panel
name = "Circuit Panel"
desc = "Looks complicated!"
tools_to_fix = TRUE

View File

@@ -0,0 +1,262 @@
GLOBAL_LIST_INIT(event_collector_associations,list())
GLOBAL_LIST_INIT(event_collectors,list()) //for the verbs
GLOBAL_LIST_INIT(event_collector_blockers,list()) //ditto
/obj/structure/event_collector //set anchored, solid, etc to taste.
name = "event collector"
desc = "you really should set this up properly :("
icon = 'icons/obj/general_collector.dmi'
var/blocker_channel = "collector" //used for list management - blockers that have the same key will add themselves as a disabler to every collector with the same key
var/recipe_size = 3 //how many ingredients do we pick out from the ingredients list for a "recipe"?
var/blocker_insertion_impedement_threshold = -1 //if we have more blockers than this, we can't place item in :(
var/show_blocker_in_examine = TRUE
var/list/possible_ingredients = list(
/obj/item/trash,
/obj/item/toy/plushie/ipc,
/obj/item/toy/tennis
) //list of items that can make up a recipe.
var/no_dupes_in_recipe = FALSE //do we care about repeats? if so, set to true
var/need_recipe_in_order = FALSE //start from the first one! or ignore it and do whatever, man...
var/item_theft_mode = TRUE //if true, we store it in our contents for later use, otherwise, we just delete it.
var/completion_time = 60 //how long to "complete" a recipe before starting the next one - this is in processing calls, 2 seconds if no time dialation
var/step_insertion_time = 2 SECONDS //how long it takes to put an object into us!
var/list/step_insertion_verbs = list("inserts") //X inserts Item into [Src], picks from the list! Tosses, inserts, throws, etc
var/list/step_initiation_verbs = list("insert") //when we start it. X starts to insert Item into [src]. picks from list. put, insert, shove, etc
var/automatic_recipe_restart = TRUE //do we start a new recipe as soon as the active one's done? if not, admin only!
var/noisy_recipe_completion = TRUE //if true, alerts admins when a recipe is completed
var/noisy_step_completion = TRUE //if true, tells admins when a step is completed
var/step_in_examine = TRUE //if true, simply tells the user what type of item they need next. note that we use typeof here, so a gold screwdriver counts as a screwdriver, but it'll just ask for a screwdriver
var/animate_on_recipe_complete = TRUE //jiggle on complete?
var/animate_on_recipe_process = TRUE //jiggle with less intensity when working?
var/sound_for_recipe_complete
var/wait_between_items = 0 //How long must you wait before adding another item
var/next_item_added = 0 //world time when the next item can be added
var/type_to_spawn_on_complete
var/list/recipe_process_sounds = list('sound/effects/smoke.ogg', 'sound/effects/bubbles.ogg')
var/recipe_process_sound_chance = 50 //prob(50) per active process tick
//internal stuff, don't touch this with subtypes.
var/calls_remaining
var/awaiting_next_recipe = FALSE //are we waiting for the timer to get negatives?
var/list/disabling_sources //what things are disabling us?
var/list/active_recipe //volatile, when given an item it removes it
var/current_step = 0 //current step for icon states
/obj/structure/event_collector/Initialize(mapload)
. = ..()
GLOB.event_collectors |= src
/obj/strucutre/event_collector/Destroy()
GLOB.event_collectors -= src
. = ..()
/obj/structure/event_collector/proc/get_blockers()
. = 0
if(GLOB.event_collector_associations)
if(GLOB.event_collector_associations[blocker_channel])
for(var/obj/structure/event_collector_blocker/blocker in GLOB.event_collector_associations[blocker_channel])
. += blocker.block_amount
/obj/structure/event_collector/proc/jiggle_animation(var/intensity = 1)
var/matrix/secondary_effect = matrix()
var/matrix/effect = matrix()
effect.Turn(-5*intensity)
effect.Scale(1,1-intensity)
secondary_effect.Turn(5*intensity)
secondary_effect.Scale(1,1+intensity)
animate(src, transform = effect, time = 2)
animate(transform = secondary_effect, time = 2)
animate(transform = null, time = 2)
/obj/structure/event_collector/proc/recipe_completed()
if(animate_on_recipe_complete)
jiggle_animation(0.2)
if(automatic_recipe_restart)
pick_new_recipe()
if(noisy_recipe_completion)
message_admins("\[EVENT\]: Event Collection object [src] has completed a recipe!")
if(sound_for_recipe_complete)
playsound(src,sound_for_recipe_complete,75,1)
if(type_to_spawn_on_complete)
new type_to_spawn_on_complete(get_turf(loc))
current_step = 0
update_icon()
/obj/structure/event_collector/update_icon()
. = ..() //here more as a reminder than anything
/obj/structure/event_collector/proc/recipe_failed() //called when reset by an admin assuming they want it to be
return
/obj/structure/event_collector/proc/pick_new_recipe()
active_recipe = list() //clear it out
if(no_dupes_in_recipe)
var/list/destructive_clone = possible_ingredients.Copy()
for(var/i in 1 to recipe_size)
var/temp = pick(destructive_clone)
active_recipe += temp
destructive_clone -= temp
else
for(var/i in 1 to recipe_size)
active_recipe += pick(possible_ingredients)
if(noisy_step_completion)
var/next_item = "Nothing! The sequence is done!"
next_item = active_recipe[1]
message_admins("\[EVENT\] Event Collection object [src] has started a recipe! If it's in sequence, the next one is [next_item] ")
/obj/structure/event_collector/process()
var/blockers = get_blockers()
if(awaiting_next_recipe && blockers < 10)
if( recipe_process_sounds && prob(recipe_process_sound_chance) )
playsound(src,pick(recipe_process_sounds),25,TRUE)
calls_remaining -= max(0, 10-blockers) //10's a multiplier in case we want to scale it based on how many blockers
if(calls_remaining <= 0)
awaiting_next_recipe = FALSE
recipe_completed()
if(animate_on_recipe_process)
jiggle_animation(0.1)
/obj/structure/event_collector/Initialize(mapload)
. = ..()
START_PROCESSING(SSobj, src)
/obj/structure/event_collector/Destroy()
STOP_PROCESSING(SSobj, src)
return ..()
/obj/structure/event_collector/examine(mob/user)
. = ..()
if(!active_recipe) return
var/blocker_count = get_blockers()
if(step_in_examine == TRUE)
if(active_recipe.len == 0)
. += span_notice("It doesn't seem to need anything at the moment!")
else
if(need_recipe_in_order)
if(active_recipe.len > 0)
var/atom/firstitem = active_recipe[1]
. += span_notice("The next object in the sequence is a... [initial(firstitem.name)]")
else
. += span_notice("it doesn't seem to need anything at the moment!")
else
var/message = "It seems to need a "
for(var/i in 1 to active_recipe.len)
var/atom/x = active_recipe[i]
.+= span_notice(message + initial(x.name))
message = pick("and a ", "a ", "with a ")
if(show_blocker_in_examine)
if(blocker_count > 10)
. += span_danger("It's nonfunctional!")
else
if(blocker_count > 0)
. += span_warning("It's impeded!")
//following's for debug, comment out if ur happy with it
//. += "There are uhhhh this many things blocking: [blocker_count]."
/obj/structure/event_collector/attackby(obj/item/O, mob/user)
if(blocker_insertion_impedement_threshold > 0 && ( get_blockers() > blocker_insertion_impedement_threshold) )
to_chat(usr,"It's fucked! Fix it first!")
return
if(world.time < next_item_added)
to_chat(user,span_warning("It's not ready to take another item yet!"))
return
if(active_recipe.len > 0) //do we have something active at all
var/stored_index = -1 //shortcut
if(need_recipe_in_order) //can we put this in?
if(!(istype(O,active_recipe[1]))) //if we need the recipe in order, check the first thing in the list
to_chat(user,span_warning("That's not the next object in the recipe!"))
return
else
stored_index = 1
else
var/found = FALSE
for(var/ind in 1 to active_recipe.len) //isType != if(O.type in list) unfortunately
if(istype(O, active_recipe[ind]))
found = TRUE
stored_index = ind
break;
if(!found)
return;
//put it in
user.visible_message("[user] begins to [pick(step_initiation_verbs)] \The [O] into \The [src]")
if(do_after(user, step_insertion_time, src)) //wait a second or two
user.visible_message("[user] [pick(step_insertion_verbs)] \The [O] into \The [src]!")
if(ishuman(user)) //should always be?
var/mob/living/carbon/human/h = user
h.drop_item() //drop held item. this is also what plays the item sound via association
if(noisy_step_completion)
var/next_item = "Nothing! The sequence is done!"
if(active_recipe.len > 1)
next_item = active_recipe[2]
message_admins("\[EVENT\] Event Collection object [src] has completed a step in its recipe with [O]! if it's in sequence, the next one is [next_item] ")
active_recipe -= active_recipe[stored_index]
if(item_theft_mode)
O.forceMove(src) //note that this does NOT delete anything! ever! or release it manually! entirely so admins can manually collect or do stuff later via moving/ejecting.
else
qdel(O)
jiggle_animation(0.1)
if(active_recipe.len == 0)
start_recipe_process()
current_step += 1
update_icon()
post_recipe_complete(user)
next_item_added = (world.time + wait_between_items)
else
user.visible_message("[user] gives up!") //shitty, change later
/obj/structure/event_collector/proc/start_recipe_process()
awaiting_next_recipe = TRUE
calls_remaining = completion_time * 10
message_admins("\[EVENT\] Event Collection object [src] has started processing its current recipe! ETA: [(calls_remaining/10) / 2] ish seconds.")
/obj/structure/event_collector/proc/empty_items() //manual call only atm
for(var/atom/movable/to_move in contents)
to_move.forceMove(get_turf(src))
/obj/structure/event_collector/proc/post_object_insert(var/mob/user)
return
/obj/structure/event_collector/proc/post_recipe_complete()
return

View File

@@ -0,0 +1,47 @@
/obj/structure/event_collector/nukies
name = "Experimental Nukies Mixer"
desc = "A machine rigged together from various bits and pieces, designed to mix some reagents into new Nukies."
icon = 'icons/obj/cooking_event.dmi'
icon_state = "equipment_empty"
possible_ingredients = list(
/obj/item/collector_item/nukies_acid,
/obj/item/collector_item/nukies_formula,
/obj/item/collector_item/nukies_sludge
)
no_dupes_in_recipe = TRUE
automatic_recipe_restart = FALSE
step_in_examine = FALSE
wait_between_items = 1 MINUTE
need_recipe_in_order = TRUE
type_to_spawn_on_complete = /obj/item/reagent_containers/food/drinks/cans/nukie_one
/obj/structure/event_collector/update_icon()
. = ..()
if(!current_step)
icon_state = "equipment_empty"
else if(current_step <= 3)
icon_state = "equipment_[current_step]"
//simple obj defs here. overwrite or change as requested.
/obj/item/collector_item
icon = 'icons/obj/cooking_event.dmi'
icon_state = "acid"
name = "Mewriatic Acid"
desc = "Not a typo or label misprint. There's an entire dissolved cat in here. Says so right on the ingredients label."
/obj/item/collector_item/nukies_acid
/obj/item/collector_item/nukies_formula
icon_state = "formula"
name = "Nukies Secret Formula"
desc = "A disgustingly thick bottle of... something. It smells bad, in a good way."
/obj/item/collector_item/nukies_sludge
icon_state = "sludge"
name = "Sludge"
desc = "Eughghhgh. gross. Smells like gasoline and gamers."