Files
JacquerelandRoxy 434e4e435e Removes Secondary & Final Objectives from Traitors (#89466)
## About The Pull Request


![image](https://github.com/user-attachments/assets/cb0bffb3-33be-4fb1-baec-a4d0f4a4cd57)
Pre-discussed with @Watermelon914, this PR removes Secondary & Final
Objectives from all Traitors, rather than just midround ones. It also
removes all of the surrounding supporting code.
Randomly assigned Primary Objectives still exist, I just used the
ability to rewrite mine to take the screenshot.

In terms of final objectives, the surrounding items that were available
still exist but don't necessarily have sources.
If anyone has good ideas for readding these in some other form it can be
done in future PRs.

It also allows all traitors to buy the Contractor kit, previously
limited to midround traitors which lacked secondary objectives, because
now all traitors lack secondary objectives.

This essentially limits all traitors to a maximum of 20 TC (16 if they
spawn with an uplink implant). Currently I don't foresee that they
strictly need any additional way of gaining TC during a round as 20 is
quite sufficient, but it may take some time to adjust and get used to it
after such a long time of having access to more. If we need to adjust
the starting value or add a slow drip of more points over time or
something, that can be done in followup PRs.

This also removes the ability to recreate your uplink added by my
beautiful wife in #74315
This was part of the progression traitor design document, but ultimately
probably a bad idea as it essentially made traitors impossible to
properly disarm. You will once more just need to carefully protect your
uplink.

**This does not remove the threat/progression system**. 
Like midround traitors, all Reputation requirements on gear are now
simple timelocks, most of which will have elapsed by the time 30 minutes
have passed.

**Finally** this PR also adds Romerol to the traitor uplink for 25 TC
and 30 minutes of reputation, as a treat (and because I removed the
final objective that previously granted it).

## Why It's Good For The Game

We've tried this system for a long time (3 years last month!) and while
I think it had a lot of promise, enabled some cool moments, and also
solved several of the problems it set out to solve, overall I think some
of the behaviours it has encouraged in players have been overall
negative for the game.
While the _game systems_ are fine, even quite fun and cool (especially
final objectives) I am of the opinion that having them in the game
creates a net negative purely in the way that they react with players'
_brains_, creating incentives towards behaviour we don't actually want
people to pursue.

While it's hard-to-impossible to prove any of this with hard data, there
has been a prevailing feeling for some time among many (though certainly
not all) people that the simple fact of _having_ a constant drip-feed of
objective available to players leads directly to less interesting
antagonist play. While certainly nobody is _forced_ to do secondary
objectives you are directly and quite strongly rewarded for doing so,
doing so efficiently, and doing so in a way which makes sure that nobody
(alive) sees you do it. This leads to a tendency to play defensively and
try to maximise the number of tasks you can complete in one round, which
also has a knock-on effect of generally minimising the number of people
you attempt to interact with in a round (unless you are killing them).

Even people who _intend_ on doing some more interesting gimmick can fall
into this trap, as "having more tools" is always useful for anyone who
is intending on any kind of plan at all, but then executing on the
secondary objectives again incentivises you to lay low, not interact
with anyone, be efficient, and then reduces the time you are spending
doing the thing that's your actual plan for the round. Removing the
ever-present temptation to fish for extra TC leaves "doing whatever your
actual plan is" as the sole thing to optimise.

Final Objectives too have created unfortunate psychological effects
between crewsided players and other antagonists. Because of the _threat_
(no matter how remote, Final Objectives have always been tuned to be
appropriately rare) that leaving any antagonist alone will cause them to
snowball by acquiring more power, it starts to feel foolish to respond
to any threat with less than the maximum possible level of force even if
they seem relatively innocuous in the moment. This even has an effect on
other non-progression antagonists, as traitors are the most common
antagonist type and how people treat them is going to be their default
level of reaction to most other station threats.

While there has always been the promise of expanding the system with
novel and exciting objectives that leverage appearing mid-round to do
something unique, we've taken very little advantage of that over time.
Most objectives we have added that didn't boil down to "kill someone,
with a twist" have been somewhat unsuccessful, serving either as ways to
get yourself arrested and killed for no reason or ways to get free
telecrystals by doing something the crew don't really care about
stopping you from doing. The option still exists to add more roundstart
objectives to traitors, if someone suddenly has a great idea that would
fit in this space.

The ideal outcome of making this change is a slight relaxation of crew
attitude towards feeling like their only option after catching an
antagonist that isn't sandbagging is to permanently remove them from the
round (although it's fine to do this still in many scenarios), and a
broadening of traitorous activity which is not purely focused on
collecting as many checkboxes as possible and might give people more
time to roleplay with other players, not worrying that this time could
have been more efficiently spent pursuing a different secondary goal.
I don't anticipate or desire that this will prevent traitors from
killing anyone (or even stop them from killing people they don't have a
specific objective to kill), I just want to remove the FOMO from
people's minds.

Also this gives us something to talk about at the coder townhall meeting
on the 22nd.

## Changelog

🆑
del: Misplaced or stolen traitor uplinks can no longer be recreated
using a radio code and special device, guard yours carefully or buy a
backup implant.
del: Roundstart traitors can no longer take on additional objectives in
order to earn additional Telecrystals and fast-forward any unlock timers
on items. They also cannot earn the ability to complete a Final
Objective.
balance: Roundstart traitors can now buy the Contractor Kit from their
traitor uplink, rather than only midround traitors.
add: Traitors can buy Romerol for 25 TC, after 30 minutes of time has
passed in a round.
/🆑
2025-03-12 16:00:35 -04:00

199 lines
8.2 KiB
Plaintext

/// number of emagged meteor shields to get the first warning, a simple say message
#define EMAGGED_METEOR_SHIELD_THRESHOLD_ONE 3
/// number of emagged meteor shields to get the second warning, telling the user an announcement is coming
#define EMAGGED_METEOR_SHIELD_THRESHOLD_TWO 6
/// number of emagged meteor shields to get the third warning + an announcement to the crew
#define EMAGGED_METEOR_SHIELD_THRESHOLD_THREE 7
/// number of emagged meteor shields to get the fourth... ah shit the dark matt-eor is coming.
#define EMAGGED_METEOR_SHIELD_THRESHOLD_FOUR 10
/// how long between emagging meteor shields you have to wait
#define METEOR_SHIELD_EMAG_COOLDOWN 1 MINUTES
//Station Shield
// A chain of satellites encircles the station
// Satellites be actived to generate a shield that will block unorganic matter from passing it.
/datum/station_goal/station_shield
name = "Station Shield"
requires_space = TRUE
var/coverage_goal = 500
VAR_PRIVATE/cached_coverage_length
/datum/station_goal/station_shield/get_report()
return list(
"<blockquote>The station is located in a zone full of space debris.",
"We have a prototype shielding system you must deploy to reduce collision-related accidents.",
"",
"You can order the satellites and control systems at cargo.</blockquote>",
).Join("\n")
/datum/station_goal/station_shield/on_report()
//Unlock
var/datum/supply_pack/P = SSshuttle.supply_packs[/datum/supply_pack/engineering/shield_sat]
P.special_enabled = TRUE
P = SSshuttle.supply_packs[/datum/supply_pack/engineering/shield_sat_control]
P.special_enabled = TRUE
/datum/station_goal/station_shield/check_completion()
if(..())
return TRUE
update_coverage()
if(cached_coverage_length >= coverage_goal)
return TRUE
return FALSE
/datum/station_goal/station_shield/proc/get_coverage()
return cached_coverage_length
/// Gets the coverage of all active meteor shield satellites
/// Can be expensive, ensure you need this before calling it
/datum/station_goal/station_shield/proc/update_coverage()
var/list/coverage = list()
for(var/obj/machinery/satellite/meteor_shield/shield_satt as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/satellite/meteor_shield))
if(!shield_satt.active || !is_station_level(shield_satt.z))
continue
for(var/turf/covered in view(shield_satt.kill_range, shield_satt))
coverage |= covered
cached_coverage_length = length(coverage)
/obj/machinery/satellite/meteor_shield
name = "\improper Meteor Shield Satellite"
desc = "A meteor point-defense satellite."
mode = "M-SHIELD"
/// the range a meteor shield sat can destroy meteors
var/kill_range = 14
//emag behavior dark matt-eor stuff
/// Proximity monitor associated with this atom, needed for it to work.
var/datum/proximity_monitor/proximity_monitor
/// amount of emagged active meteor shields
var/static/emagged_active_meteor_shields = 0
/// the highest amount of shields you've ever emagged
var/static/highest_emagged_threshold_reached = 0
/// cooldown on emagging meteor shields because instantly summoning a dark matt-eor is very unfun
STATIC_COOLDOWN_DECLARE(shared_emag_cooldown)
/obj/machinery/satellite/meteor_shield/examine(mob/user)
. = ..()
if(active)
. += span_notice("It is currently active. You can interact with it to shut it down.")
if(obj_flags & EMAGGED)
. += span_warning("Rather than the usual sounds of beeps and pings, it produces a weird and constant hiss of white noise…")
else
. += span_notice("It emits periodic beeps and pings as it communicates with the satellite network.")
else
. += span_notice("It is currently disabled. You can interact with it to set it up.")
if(obj_flags & EMAGGED)
. += span_warning("But something seems off about it...?")
/obj/machinery/satellite/meteor_shield/proc/space_los(meteor)
for(var/turf/T in get_line(src,meteor))
if(!isspaceturf(T))
return FALSE
return TRUE
/obj/machinery/satellite/meteor_shield/Initialize(mapload)
. = ..()
proximity_monitor = new(src, /* range = */ 0)
/obj/machinery/satellite/meteor_shield/HasProximity(atom/movable/proximity_check_mob)
. = ..()
if(!istype(proximity_check_mob, /obj/effect/meteor))
return
var/obj/effect/meteor/meteor_to_destroy = proximity_check_mob
if(space_los(meteor_to_destroy))
var/turf/beam_from = get_turf(src)
beam_from.Beam(get_turf(meteor_to_destroy), icon_state="sat_beam", time = 5)
if(meteor_to_destroy.shield_defense(src))
qdel(meteor_to_destroy)
/obj/machinery/satellite/meteor_shield/toggle(user)
if(user)
balloon_alert(user, "looking for [active ? "off" : "on"] button")
if(user && !do_after(user, 2 SECONDS, src, IGNORE_HELD_ITEM))
return FALSE
if(!..(user))
return FALSE
if(obj_flags & EMAGGED)
update_emagged_meteor_sat(user)
if(active)
proximity_monitor.set_range(kill_range)
else
proximity_monitor.set_range(0)
var/datum/station_goal/station_shield/goal = SSstation.get_station_goal(/datum/station_goal/station_shield)
goal?.update_coverage()
/obj/machinery/satellite/meteor_shield/Destroy()
. = ..()
QDEL_NULL(proximity_monitor)
if(obj_flags & EMAGGED)
//satellites that are destroying are not active, this will count down the number of emagged sats
update_emagged_meteor_sat()
/obj/machinery/satellite/meteor_shield/emag_act(mob/user, obj/item/card/emag/emag_card)
if(obj_flags & EMAGGED)
balloon_alert(user, "already emagged!")
return FALSE
if(!COOLDOWN_FINISHED(src, shared_emag_cooldown))
balloon_alert(user, "on cooldown!")
to_chat(user, span_warning("The last satellite emagged needs [DisplayTimeText(COOLDOWN_TIMELEFT(src, shared_emag_cooldown))] to recalibrate first. Emagging another so soon could damage the satellite network."))
return FALSE
var/cooldown_applied = METEOR_SHIELD_EMAG_COOLDOWN
COOLDOWN_START(src, shared_emag_cooldown, cooldown_applied)
obj_flags |= EMAGGED
to_chat(user, span_notice("You access the satellite's debug mode and it begins emitting a strange signal, increasing the chance of meteor strikes."))
AddComponent(/datum/component/gps, "Corrupted Meteor Shield Attraction Signal")
say("Recalibrating... ETA:[DisplayTimeText(cooldown_applied)].")
if(active) //if we allowed inactive updates a sat could be worth -1 active meteor shields on first emag
update_emagged_meteor_sat(user)
return TRUE
/obj/machinery/satellite/meteor_shield/proc/update_emagged_meteor_sat(mob/user)
if(!active)
change_meteor_chance(0.5)
emagged_active_meteor_shields--
if(user)
balloon_alert(user, "meteor probability halved")
return
change_meteor_chance(2)
emagged_active_meteor_shields++
if(user)
balloon_alert(user, "meteor probability doubled")
if(emagged_active_meteor_shields > highest_emagged_threshold_reached)
highest_emagged_threshold_reached = emagged_active_meteor_shields
handle_new_emagged_shield_threshold()
/obj/machinery/satellite/meteor_shield/proc/handle_new_emagged_shield_threshold()
switch(highest_emagged_threshold_reached)
if(EMAGGED_METEOR_SHIELD_THRESHOLD_ONE)
say("Warning. Meteor strike probability entering dangerous ranges for more exotic meteors.")
if(EMAGGED_METEOR_SHIELD_THRESHOLD_TWO)
say("Warning. Risk of dark matter congealment entering existent ranges. Further tampering will be reported.")
if(EMAGGED_METEOR_SHIELD_THRESHOLD_THREE)
say("Warning. Further tampering has been reported.")
priority_announce("Warning. Tampering of meteor satellites puts the station at risk of exotic, deadly meteor collisions. Please intervene by checking your GPS devices for strange signals, and dismantling the tampered meteor shields.", "Strange Meteor Signal Warning")
if(EMAGGED_METEOR_SHIELD_THRESHOLD_FOUR)
say("Warning. Warning. Dark Matt-eor on course for station.")
force_event_async(/datum/round_event_control/dark_matteor, "an array of tampered meteor satellites")
/obj/machinery/satellite/meteor_shield/proc/change_meteor_chance(mod)
// Update the weight of all meteor events
for(var/datum/round_event_control/meteor_wave/meteors in SSevents.control)
meteors.weight *= mod
for(var/datum/round_event_control/stray_meteor/stray_meteor in SSevents.control)
stray_meteor.weight *= mod
#undef EMAGGED_METEOR_SHIELD_THRESHOLD_ONE
#undef EMAGGED_METEOR_SHIELD_THRESHOLD_TWO
#undef EMAGGED_METEOR_SHIELD_THRESHOLD_THREE
#undef EMAGGED_METEOR_SHIELD_THRESHOLD_FOUR
#undef METEOR_SHIELD_EMAG_COOLDOWN