Files
Leland Kemble dec8c784f6 Bitrunners can't leave domains while glitches remain in them (#96171)
## About The Pull Request

When bitrunners attempt to leave their domain via the ladder, they will
fail if there is an active, living bitrunning glitch antagonist within
the domain. Other methods of leaving such as death or taking the red
pill are unaffected.

they can also leave if the crate's delivered

sorta a sister PR to #95934

## Why It's Good For The Game

enter domain -> get alert about bitrunning glitch -> leave -> turn off
domain
What a fun gameplay loop, for everyone involved.
You'll fight the glitch and you'll like it. (liking it
[pending](https://github.com/tgstation/tgstation/pull/96138))

You can still leave if you REALLY, REALLY need to at the cost of a
little brain damage, but otherwise the literal only obstacle that will
ever be in your path should be something you have to engage with.

## Changelog
🆑

balance: Bitrunners can't back out of domains via the ladder while
bitrunning glitches remain in them

/🆑
2026-06-01 16:23:46 -04:00

208 lines
6.1 KiB
Plaintext

#define GRADE_D "D"
#define GRADE_C "C"
#define GRADE_B "B"
#define GRADE_A "A"
#define GRADE_S "S"
/// Handles calculating rewards based on number of players, parts, threats, etc
/obj/machinery/quantum_server/proc/calculate_rewards()
var/rewards_base = 0.8
if(domain_randomized)
rewards_base += 0.2
rewards_base += servo_bonus
rewards_base += (length(spawned_threat_refs) * 2)
rewards_base += get_multiplayer_bonus()
rewards_base += get_nohit_bouns()
return rewards_base
/// Calculates total bonus from completing the domain in multiplayer
/obj/machinery/quantum_server/proc/get_multiplayer_bonus()
var/total = 0
var/multiplayer = FALSE
for(var/datum/weakref/connection_ref as anything in avatar_connection_refs)
var/datum/component/avatar_connection/connection = connection_ref.resolve()
if(isnull(connection))
continue
if(multiplayer)
total += multiplayer_bonus
multiplayer = TRUE
return total
/// Calculates total bonus from completing the domain without taking damage
/obj/machinery/quantum_server/proc/get_nohit_bouns()
if(generated_domain.domain_flags & DOMAIN_NO_NOHIT_BONUS)
return 0
var/total = 0
for(var/datum/weakref/connection_ref as anything in avatar_connection_refs)
var/datum/component/avatar_connection/connection = connection_ref.resolve()
if(connection?.nohit)
total += nohit_bonus
return total
/// Handles spawning the (new) crate and deleting the former
/obj/machinery/quantum_server/proc/generate_loot(obj/cache, obj/machinery/byteforge/chosen_forge)
SSblackbox.record_feedback("tally", "bitrunning_domain_primary_completed", 1, generated_domain.key)
for(var/mob/person in cache.contents)
SEND_SIGNAL(person, COMSIG_BITRUNNER_CACHE_SEVER)
spark_at_location(cache) // abracadabra!
qdel(cache) // and it's gone!
SEND_SIGNAL(src, COMSIG_BITRUNNER_DOMAIN_COMPLETE, cache, generated_domain.reward_points)
points += generated_domain.reward_points
playsound(src, 'sound/machines/terminal/terminal_success.ogg', 30, vary = TRUE)
var/bonus = calculate_rewards()
var/time_difference = world.time - generated_domain.start_time
var/grade = grade_completion(time_difference)
var/obj/item/paper/certificate = new()
certificate.add_raw_text(get_completion_certificate(time_difference, grade))
certificate.name = "certificate of domain completion"
certificate.update_appearance()
var/obj/structure/closet/crate/secure/bitrunning/decrypted/reward_cache = new(src, generated_domain, bonus)
reward_cache.manifest = WEAKREF(certificate)
reward_cache.update_appearance()
if(can_generate_tech_disk(grade))
SSblackbox.record_feedback("tally", "bitrunning_bepis_rewarded", 1, generated_domain.key)
new /obj/item/disk/design_disk/bepis/remove_tech(reward_cache)
generated_domain.disk_reward_spawned = TRUE
chosen_forge.start_to_spawn(reward_cache)
domain_complete = TRUE
return TRUE
/// Builds secondary loot if the achievements were met
/obj/machinery/quantum_server/proc/generate_secondary_loot(obj/curiosity, obj/machinery/byteforge/chosen_forge)
SSblackbox.record_feedback("tally", "bitrunning_domain_secondary_completed", 1, generated_domain.key)
spark_at_location(curiosity) // abracadabra!
qdel(curiosity) // and it's gone!
var/obj/item/storage/lockbox/bitrunning/decrypted/reward_curiosity = new(src, generated_domain)
chosen_forge.start_to_spawn(reward_curiosity)
return TRUE
/// Returns the markdown text containing domain completion information
/obj/machinery/quantum_server/proc/get_completion_certificate(time_difference, grade)
var/base_points = generated_domain.reward_points
if(domain_randomized)
base_points -= 1
var/bonuses = calculate_rewards()
var/domain_threats = length(spawned_threat_refs)
var/completion_time = "### Completion Time: [DisplayTimeText(time_difference)]\n"
var/completion_grade = "\n---\n\n# Rating: [grade]"
var/text = "# Certificate of Domain Completion\n\n---\n\n"
text += "### [generated_domain.name][domain_randomized ? " (Randomized)" : ""]\n"
text += "- **Difficulty:** [generated_domain.difficulty]\n"
text += "- **Threats:** [domain_threats]\n"
text += "- **Base Reward:** [base_points][domain_randomized ? " +1" : ""]\n\n"
text += "- **Total Bonus:** [bonuses]x\n\n"
if(bonuses <= 1)
text += completion_time
text += completion_grade
return text
text += "### Bonuses\n"
if(domain_randomized)
text += "- **Randomized:** + 0.2\n"
var/mp_bonus = get_multiplayer_bonus()
if(mp_bonus)
text += "- **Multiplayer:** + [mp_bonus]\n"
var/nohit_bonus = get_nohit_bouns()
if(nohit_bonus)
text += "- **No hit:** + [nohit_bonus]\n"
if(domain_threats > 0)
text += "- **Threats:** + [domain_threats * 2]\n"
var/servo_rating = servo_bonus
if(servo_rating > 0.2)
text += "- **Components:** + [servo_rating]\n"
text += completion_time
text += completion_grade
return text
/// Checks if the players should get a bepis reward
/obj/machinery/quantum_server/proc/can_generate_tech_disk(grade)
if(generated_domain.disk_reward_spawned)
return FALSE
if(!LAZYLEN(SSresearch.techweb_nodes_experimental))
return FALSE
var/static/list/passing_grades = list()
if(!passing_grades.len)
passing_grades = list(GRADE_A,GRADE_S)
return generated_domain.difficulty >= BITRUNNER_DIFFICULTY_MEDIUM && (grade in passing_grades)
/// Grades the player's run based on several factors
/obj/machinery/quantum_server/proc/grade_completion(completion_time)
var/score = length(spawned_threat_refs) * 5
score += generated_domain.reward_points
var/base = generated_domain.difficulty + 1
var/time_score = 1
if(completion_time <= 1 MINUTES)
time_score = 10
else if(completion_time <= 2 MINUTES)
time_score = 5
else if(completion_time <= 5 MINUTES)
time_score = 3
else if(completion_time <= 10 MINUTES)
time_score = 2
else
time_score = 1
score += time_score * base
// Increases the chance for glitches to spawn based on how well they're doing
threat += score
switch(score)
if(1 to 4)
return GRADE_D
if(5 to 7)
return GRADE_C
if(8 to 10)
return GRADE_B
if(11 to 13)
return GRADE_A
else
return GRADE_S
#undef GRADE_D
#undef GRADE_C
#undef GRADE_B
#undef GRADE_A
#undef GRADE_S