Files
Leland Kemble d49c825f8e Bitrunning glitches take much longer to escape their simulation if there are remaining active bitrunners (#95934)
## About The Pull Request

For every living, connected bit avatar remaining in the simulation, the
`do_after()` for leaving the simulation as a bitrunning glitch ghost
role is multiplied by 5. The base timer remains at it was, two seconds.

Additionally, there is an exception in place for the "Island Brawl"
domain, which is the FFA with infinite respawns. It still multiplies by
5 for each active bitrunner, but that multiplier is reduced by 1 for
each death, so you likely only have to kill one wave of bitrunners for
normal escape time.

Additionally, because of necessity for the exception and also basic good
sense, bitrunning points are no longer component-based on specific turfs
in the domain, and are held in the server. It never should have been a
component, it had no reason to be one. This, as a consenquence, also
fixes that you could completely softlock any domain using points by
simply crowbarring the turf that the crate was yet to spawn on.

## Why It's Good For The Game

Yesterday, I saw one of the lamest things I've ever seen in the game. A
bitrunning glitch spawns in, walks literally right by two bitrunners as
they ask who he is, and leaves the simulation to go on a killing spree
unburdened. There was extenuating circumstances in that situation, but
it still was a miserable breakdown of what I think the intended gameplay
of a bitrunning glitch is, and shows how ridiculously easy it is to
simply not engage with the intended hurdle in your path. The
primary(and, really, only) job of a glitch is to kill bit avatars, and
this PR makes it significantly more difficult to just... walk by them to
go on your killing spree on station. Work, then play.

## Changelog
🆑

balance: For every living, connected bitrunner remaining in a domain,
bitrunning glitches will take five times longer to escape.
fix: You can no longer softlock any virtual domain that uses points by
crowbarring the reward turf before the crate spawns.

/🆑
2026-05-29 19:35:45 +02:00

207 lines
7.4 KiB
Plaintext

/// Adds threats to the list and notifies players
/obj/machinery/quantum_server/proc/add_threats(mob/living/threat)
spawned_threat_refs.Add(WEAKREF(threat))
SEND_SIGNAL(src, COMSIG_BITRUNNER_THREAT_CREATED)
threat.AddComponent(/datum/component/virtual_entity, src)
/// Choses which antagonist role is spawned based on threat
/obj/machinery/quantum_server/proc/get_antagonist_role()
var/list/available = list()
for(var/datum/antagonist/bitrunning_glitch/subtype as anything in subtypesof(/datum/antagonist/bitrunning_glitch))
if(threat >= initial(subtype.threat))
available += subtype
shuffle_inplace(available)
var/datum/antagonist/bitrunning_glitch/chosen = pick(available)
threat -= initial(chosen.threat) * 0.5
return chosen
/// Selects a target to mutate. Gives two attempts, then crashes if it fails.
/obj/machinery/quantum_server/proc/get_mutation_target()
var/datum/weakref/target_ref = pick(mutation_candidate_refs)
var/mob/living/resolved = target_ref.resolve()
if(resolved)
return resolved
mutation_candidate_refs.Remove(target_ref)
if(!length(mutation_candidate_refs))
return
target_ref = pick(mutation_candidate_refs)
resolved = target_ref.resolve()
return resolved
/// Finds any mobs with minds in the zones and gives them the bad news
/obj/machinery/quantum_server/proc/notify_spawned_threats()
for(var/datum/weakref/baddie_ref as anything in spawned_threat_refs)
var/mob/living/baddie = baddie_ref.resolve()
if(isnull(baddie?.mind) || baddie.stat >= UNCONSCIOUS)
continue
var/atom/movable/screen/alert/bitrunning/alert = baddie.throw_alert(
ALERT_BITRUNNER_RESET,
/atom/movable/screen/alert/bitrunning,
new_master = src,
)
alert.name = "Queue Deletion"
alert.desc = "The server is resetting. Oblivion awaits."
to_chat(baddie, span_userdanger("You have been flagged for deletion! Thank you for your service."))
/// Removes a specific threat - used when station spawning
/obj/machinery/quantum_server/proc/remove_threat(mob/living/threat)
spawned_threat_refs.Remove(WEAKREF(threat))
/// Selects the role and waits for a ghost orbiter
/obj/machinery/quantum_server/proc/setup_glitch(datum/antagonist/bitrunning_glitch/forced_role)
if(!validate_mutation_candidates())
return
var/mob/living/mutation_target = get_mutation_target()
if(isnull(mutation_target))
CRASH("vdom: After two attempts, no valid mutation target was found.")
var/atom/thing = mutation_target
thing.create_digital_aura()
var/datum/antagonist/bitrunning_glitch/chosen_role = forced_role || get_antagonist_role()
var/role_name = initial(chosen_role.name)
var/mob/chosen_one = SSpolling.poll_ghosts_for_target(
question = "<span class='ooc'>A temporary antagonist role is spawning in the virtual domain.</span>\
\n<span class='boldnotice'>You will return to your previous body on conclusion.</span>",
check_jobban = ROLE_GLITCH,
poll_time = 20 SECONDS,
checked_target = mutation_target,
ignore_category = POLL_IGNORE_GLITCH,
alert_pic = mutation_target,
role_name_text = "Malfunction: [role_name]",
)
spawn_glitch(chosen_role, mutation_target, chosen_one)
return mutation_target
/// Orbit poll has concluded - spawn the antag
/obj/machinery/quantum_server/proc/spawn_glitch(datum/antagonist/bitrunning_glitch/chosen_role, mob/living/mutation_target, mob/dead/observer/ghost)
if(QDELETED(mutation_target))
return
if(QDELETED(src) || isnull(ghost) || isnull(generated_domain) || !is_ready || !is_operational)
var/atom/thing = mutation_target
thing.remove_digital_aura()
return
var/role_name = initial(chosen_role.name)
var/mob/living/new_mob
switch(role_name)
if(ROLE_NETGUARDIAN)
new_mob = new /mob/living/basic/netguardian(mutation_target.loc)
else // any other humanoid mob
new_mob = new /mob/living/carbon/human(mutation_target.loc)
mutation_target.gib(DROP_ALL_REMAINS)
var/datum/mind/ghost_mind = ghost.mind
new_mob.PossessByPlayer(ghost.key)
if(ghost_mind)
new_mob.AddComponent(/datum/component/temporary_body, ghost_mind, return_on_death = TRUE)
var/datum/mind/antag_mind = new_mob.mind
antag_mind.add_antag_datum(chosen_role)
antag_mind.set_assigned_role(SSjob.get_job_type(/datum/job/bitrunning_glitch))
playsound(new_mob, 'sound/effects/magic/ethereal_exit.ogg', 50, vary = TRUE)
message_admins("[ADMIN_LOOKUPFLW(new_mob)] has been made into virtual antagonist by an event.")
new_mob.log_message("was spawned as a virtual antagonist by an event.", LOG_GAME)
add_threats(new_mob)
/// Oh boy - transports the antag station side
/obj/machinery/quantum_server/proc/station_spawn(mob/living/antag, obj/machinery/byteforge/chosen_forge)
antag.balloon_alert(antag, "scanning...")
chosen_forge.setup_particles(angry = TRUE)
var/obj/machinery/announcement_system/aas = get_announcement_system(null, src, list(RADIO_CHANNEL_SUPPLY))
if (aas)
aas.broadcast("QUANTUM SERVER ALERT: Security breach detected. Unauthorized entry sequence in progress...", list(RADIO_CHANNEL_SUPPLY))
SEND_SIGNAL(src, COMSIG_BITRUNNER_STATION_SPAWN)
var/timeout = 2 SECONDS
if(!ishuman(antag))
if (aas)
aas.broadcast("QUANTUM SERVER ALERT: Fabrication protocols have crashed unexpectedly. Please evacuate the area.", list(RADIO_CHANNEL_SUPPLY))
timeout = 10 SECONDS
var/bitrunners_alive = 0
var/island_brawl_exception = istype(generated_domain, /datum/lazy_template/virtual_domain/island_brawl)
for(var/datum/weakref/bitrunner_ref in avatar_connection_refs)
var/mob/living/bitrunner = astype(bitrunner_ref.resolve(), /datum/component/avatar_connection)?.parent
if(!bitrunner)
continue
if((bitrunner.stat > CONSCIOUS) || !bitrunner.client)
continue
if(island_brawl_exception)
timeout *= max(5 - generated_domain.main_crate_points, 1)
continue
bitrunners_alive++
timeout *= 5
if(bitrunners_alive)
to_chat(antag, span_warning("[bitrunners_alive] criminals still remain here, pilfering your domain. It will be more difficult to leave until they are handled."))
if(!do_after(antag, timeout) || QDELETED(chosen_forge) || QDELETED(antag) || QDELETED(src) || !is_ready || !is_operational)
chosen_forge.setup_particles()
return
var/datum/component/glitch/effect = antag.AddComponent(/datum/component/glitch, \
server = src, \
forge = chosen_forge, \
)
chosen_forge.flicker(angry = TRUE)
if(!do_after(antag, 1 SECONDS))
chosen_forge.setup_particles()
qdel(effect)
return
chosen_forge.flash()
if(ishuman(antag))
reset_equipment(antag)
else if (aas)
aas.broadcast("QUANTUM SERVER CRITICAL ALERT: Unregistered mechanical entity deployed.", list())
var/datum/antagonist/antag_datum = antag.mind?.has_antag_datum(/datum/antagonist/bitrunning_glitch)
if(istype(antag_datum))
antag_datum.show_in_roundend = TRUE
var/datum/component/temp_body = antag.GetComponent(/datum/component/temporary_body)
if(temp_body)
qdel(temp_body)
do_teleport(antag, get_turf(chosen_forge), forced = TRUE, asoundin = 'sound/effects/magic/ethereal_enter.ogg', asoundout = 'sound/effects/magic/ethereal_exit.ogg', channel = TELEPORT_CHANNEL_QUANTUM)
/// Removes any invalid candidates from the list
/obj/machinery/quantum_server/proc/validate_mutation_candidates()
for(var/datum/weakref/creature_ref as anything in mutation_candidate_refs)
var/mob/living/creature = creature_ref?.resolve()
if(isnull(creature) || creature.mind)
mutation_candidate_refs.Remove(creature_ref)
if(!length(mutation_candidate_refs))
return FALSE
shuffle_inplace(mutation_candidate_refs)
return TRUE