Files
Mike LongandGitHub c7c61ec373 Enhance bitrunner domain creation blackbox logging (#94913)
## About The Pull Request

Improves some blackbox logging for bitrunner domain creation, by also
logging how much information they had when they chose it.

Also some minor code improvements, by making methods for whether the
name/reward of a domain is visible.

## Why It's Good For The Game

Right now, if we just look at blackbox logs, we cannot easily tell if a
domain is being deliberately avoided when possible, as we cannot tell if
it's run unintentionally or intentionally. Now we will record that
information, so we can act on it, and hopefully perform actions to
improve the domains that people attempt to avoid.
2026-01-28 11:40:33 -06:00

248 lines
8.2 KiB
Plaintext

#define POLLING_COOLDOWN_TIME 2 MINUTES
/// Gives all current occupants a notification that the server is going down
/obj/machinery/quantum_server/proc/begin_shutdown(mob/user)
if(isnull(generated_domain))
return
if(!length(avatar_connection_refs))
balloon_alert_to_viewers("powering down domain...")
playsound(src, 'sound/machines/terminal/terminal_off.ogg', 40, vary = TRUE)
reset()
return
balloon_alert_to_viewers("notifying clients...")
playsound(src, 'sound/machines/terminal/terminal_alert.ogg', 100, vary = TRUE)
user.visible_message(
span_danger("[user] begins depowering the server!"),
span_notice("You start disconnecting clients..."),
span_danger("You hear frantic keying on a keyboard."),
)
SEND_SIGNAL(src, COMSIG_BITRUNNER_SHUTDOWN_ALERT, user)
if(!do_after(user, 20 SECONDS, src))
return
reset()
/// Links all the loading processes together - does validation for booting a map
/obj/machinery/quantum_server/proc/cold_boot_map(map_key, was_random_selection)
if(!is_ready)
return FALSE
if(isnull(map_key))
balloon_alert_to_viewers("no domain specified!")
return FALSE
if(generated_domain)
balloon_alert_to_viewers("stop the current domain first!")
return FALSE
if(length(avatar_connection_refs))
balloon_alert_to_viewers("all clients must disconnect!")
return FALSE
is_ready = FALSE
playsound(src, 'sound/machines/terminal/terminal_processing.ogg', 30, 2)
/// If any one of these fail, it reverts the entire process
if(!load_domain(map_key) || !load_map_items() || !load_mob_segments())
balloon_alert_to_viewers("initialization failed!")
scrub_vdom()
is_ready = TRUE
return FALSE
//We will want to record how the domain was selected. Either entirely randomly, with the name redacted, or with full information.
//Without this, it is difficult to determine what domains are selected more often intentionallly, vs unintentionally.
var/selection_type
if(was_random_selection)
selection_type = "random selection"
else
if(generated_domain.can_view_name(scanner_tier, points))
selection_type = "full information"
else
selection_type = "redacted information"
SSblackbox.record_feedback("nested tally", "bitrunning_domain_loaded", 1, list(selection_type, map_key))
is_ready = TRUE
var/spawn_chance = clamp((threat * glitch_chance), 5, threat_prob_max)
if(prob(spawn_chance))
setup_glitch()
playsound(src, 'sound/machines/terminal/terminal_insert_disc.ogg', 30, vary = TRUE)
balloon_alert_to_viewers("domain loaded.")
generated_domain.start_time = world.time
points -= generated_domain.cost
update_use_power(ACTIVE_POWER_USE)
update_appearance()
if(broadcasting)
start_broadcasting_network(BITRUNNER_CAMERA_NET)
if(generated_domain.announce_to_ghosts)
notify_ghosts("Bitrunners have loaded a domain that offers ghost interactions. Check the spawners menu for more information.",
src,
"Matrix Glitch",
)
return TRUE
/// Initializes a new domain if the given key is valid and the user has enough points
/obj/machinery/quantum_server/proc/load_domain(map_key)
for(var/datum/lazy_template/virtual_domain/available in SSbitrunning.all_domains)
if(map_key == available.key && points >= available.cost)
generated_domain = available
break
if(!generated_domain)
return FALSE
if(generated_domain.mission_min_candidates && (!COOLDOWN_FINISHED(src, polling_cooldown)))
say("Advanced NPC algorithms resetting, please wait [DisplayTimeText(polling_cooldown)] or load a different domain.")
playsound(src, "sound/machines/buzz-[pick("sigh", "two")].ogg", 50, TRUE)
return FALSE
var/list/mob/lucky_ghosts
if(generated_domain.mission_min_candidates)
playsound(src, 'sound/machines/chime.ogg', 50, TRUE)
say("Loading advanced NPCs...")
var/list/mob/candidates = SSpolling.poll_ghost_candidates("Do you want to play as a virtual [generated_domain.spawner_role] in a bitrunner domain?", ROLE_GHOST_ROLE, ROLE_GHOST_ROLE, 15 SECONDS, POLL_IGNORE_SHUTTLE_DENIZENS, TRUE)
for(var/amount in 1 to generated_domain.mission_max_candidates)
if(length(candidates)) // If no candidates, fails in code below anyways
LAZYADD(lucky_ghosts, pick_n_take(candidates))
if(length(lucky_ghosts) < generated_domain.mission_min_candidates)
notify_ghosts("Not enough candidates for [generated_domain.spawner_role]! Aborting mission!")
playsound(src, "sound/machines/buzz-[pick("sigh", "two")].ogg", 50, TRUE)
say("Error! Unable to load advanced NPCs. Please try again or select different domain.")
COOLDOWN_START(src, polling_cooldown, POLLING_COOLDOWN_TIME)
return FALSE
playsound(src, 'sound/machines/ping.ogg', 50, TRUE)
say("Success!")
generated_domain.load_advanced_npcs(lucky_ghosts)
RegisterSignal(generated_domain, COMSIG_LAZY_TEMPLATE_LOADED, PROC_REF(on_template_loaded))
generated_domain.lazy_load()
return TRUE
/// Loads in necessary map items like hololadder spawns, caches, etc
/obj/machinery/quantum_server/proc/load_map_items()
var/turf/goal_turfs = list()
var/turf/cache_turfs = list()
var/turf/curiosity_turfs = list()
for(var/obj/effect/landmark/bitrunning/thing in GLOB.landmarks_list)
if(istype(thing, /obj/effect/landmark/bitrunning/hololadder_spawn))
exit_turfs += get_turf(thing)
qdel(thing) // i'm worried about multiple servers getting confused so lets clean em up
continue
if(istype(thing, /obj/effect/landmark/bitrunning/cache_goal_turf))
var/turf/tile = get_turf(thing)
goal_turfs += tile
RegisterSignal(tile, COMSIG_ATOM_ENTERED, PROC_REF(on_goal_turf_entered))
RegisterSignal(tile, COMSIG_ATOM_EXAMINE, PROC_REF(on_goal_turf_examined))
qdel(thing)
continue
if(istype(thing, /obj/effect/landmark/bitrunning/cache_spawn))
cache_turfs += get_turf(thing)
qdel(thing)
continue
if(istype(thing, /obj/effect/mob_spawn))
generated_domain.ghost_spawners += thing
continue
if(istype(thing, /obj/effect/landmark/bitrunning/curiosity_spawn))
curiosity_turfs += get_turf(thing)
qdel(thing)
continue
if(istype(thing, /obj/effect/landmark/bitrunning/loot_signal))
var/turf/signaler_turf = get_turf(thing)
signaler_turf.AddComponent(/datum/component/bitrunning_points, generated_domain)
qdel(thing)
continue
if(istype(thing, /obj/effect/landmark/bitrunning/permanent_exit))
var/turf/tile = get_turf(thing)
exit_turfs += tile
qdel(thing)
new /obj/structure/hololadder(tile)
if(!length(exit_turfs))
CRASH("Failed to find exit turfs on generated domain.")
if(!length(goal_turfs))
CRASH("Failed to find send turfs on generated domain.")
if(!attempt_spawn_cache(cache_turfs))
return FALSE
while(length(curiosity_turfs))
var/turf/picked_turf = attempt_spawn_curiosity(curiosity_turfs)
if(!picked_turf)
break
generated_domain.secondary_loot_generated += 1
curiosity_turfs -= picked_turf
return TRUE
/// Stops the current virtual domain and disconnects all users
/obj/machinery/quantum_server/proc/reset(fast = FALSE)
is_ready = FALSE
sever_connections()
if(!fast)
notify_spawned_threats()
addtimer(CALLBACK(src, PROC_REF(scrub_vdom)), 15 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
else
scrub_vdom() // used in unit testing, no need to wait for callbacks
addtimer(CALLBACK(src, PROC_REF(cool_off)), ROUND_UP(server_cooldown_time * capacitor_coefficient), TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME)
update_appearance()
update_use_power(IDLE_POWER_USE)
domain_randomized = FALSE
retries_spent = 0
stop_broadcasting_network(BITRUNNER_CAMERA_NET)
/// Tries to clean up everything in the domain
/obj/machinery/quantum_server/proc/scrub_vdom()
sever_connections() /// just in case someone's connected
SEND_SIGNAL(src, COMSIG_BITRUNNER_DOMAIN_SCRUBBED) // avatar cleanup just in case
if(length(generated_domain.reservations))
var/datum/turf_reservation/res = generated_domain.reservations[1]
res.Release()
var/list/creatures = spawned_threat_refs + mutation_candidate_refs
for(var/datum/weakref/creature_ref as anything in creatures)
var/mob/living/creature = creature_ref?.resolve()
if(isnull(creature))
continue
qdel(creature)
generated_domain.secondary_loot_generated = 0
avatar_connection_refs.Cut()
exit_turfs = list()
generated_domain = null
mutation_candidate_refs.Cut()
spawned_threat_refs.Cut()
#undef POLLING_COOLDOWN_TIME