diff --git a/code/__DEFINES/uplink.dm b/code/__DEFINES/uplink.dm
index 3b3621f24c5..3e75278132a 100644
--- a/code/__DEFINES/uplink.dm
+++ b/code/__DEFINES/uplink.dm
@@ -42,3 +42,6 @@
/// Minimal cost for an item to be eligible for a discount
#define TRAITOR_DISCOUNT_MIN_PRICE 4
+
+/// The standard minimum player count for "don't spawn this item on low population rounds"
+#define TRAITOR_POPULATION_LOWPOP 20
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index a81bc480f97..e2dd97935e0 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -62,11 +62,6 @@
integer = FALSE
min_val = 0
-/// Determines the ideal player count for maximum progression per minute.
-/datum/config_entry/number/traitor_ideal_player_count
- default = 20
- min_val = 1
-
/// Determines how fast traitors scale in general.
/datum/config_entry/number/traitor_scaling_multiplier
default = 1
diff --git a/code/controllers/subsystem/traitor.dm b/code/controllers/subsystem/traitor.dm
index 53cf90ecbd0..afa15e7606c 100644
--- a/code/controllers/subsystem/traitor.dm
+++ b/code/controllers/subsystem/traitor.dm
@@ -11,48 +11,33 @@ SUBSYSTEM_DEF(traitor)
/// The coefficient multiplied by the current_global_progression for new joining traitors to calculate their progression
var/newjoin_progression_coeff = 1
- /// The current progression that all traitors should be at in the round
+ /// The current progression that all traitors should be at in the round, you can't have less than this
var/current_global_progression = 0
- /// The amount of deviance from the current global progression before you start getting 2x the current scaling or no scaling at all
- /// Also affects objectives, so -50% progress reduction or 50% progress boost.
- var/progression_scaling_deviance = 20 MINUTES
/// The current uplink handlers being managed
var/list/datum/uplink_handler/uplink_handlers = list()
- /// The current scaling per minute of progression. Has a maximum value of 1 MINUTES.
+ /// The current scaling per minute of progression.
var/current_progression_scaling = 1 MINUTES
/datum/controller/subsystem/traitor/Initialize()
+ current_progression_scaling = 1 MINUTES * CONFIG_GET(number/traitor_scaling_multiplier)
for(var/theft_item in subtypesof(/datum/objective_item/steal))
new theft_item
return SS_INIT_SUCCESS
/datum/controller/subsystem/traitor/fire(resumed)
- var/player_count = length(GLOB.alive_player_list)
- // Has a maximum of 1 minute, however the value can be lower if there are lower players than the ideal
- // player count for a traitor to be threatening. Rounds to the nearest 10% of a minute to prevent weird
- // values from appearing in the UI. Traitor scaling multiplier bypasses the limit and only multiplies the end value.
- // from all of our calculations.
- current_progression_scaling = max(min(
- (player_count / CONFIG_GET(number/traitor_ideal_player_count)) * 1 MINUTES,
- 1 MINUTES
- ), 0.1 MINUTES) * CONFIG_GET(number/traitor_scaling_multiplier)
+ var/previous_progression = current_global_progression
+ current_global_progression = (STATION_TIME_PASSED()) * CONFIG_GET(number/traitor_scaling_multiplier)
+ var/progression_increment = current_global_progression - previous_progression
- var/progression_scaling_delta = (wait / (1 MINUTES)) * current_progression_scaling
- var/previous_global_progression = current_global_progression
-
- current_global_progression += progression_scaling_delta
for(var/datum/uplink_handler/handler in uplink_handlers)
if(!handler.has_progression || QDELETED(handler))
uplink_handlers -= handler
- var/deviance = (previous_global_progression - handler.progression_points) / progression_scaling_deviance
- if(abs(deviance) < 0.01)
- // If deviance is less than 1%, just set them to the current global progression
+ if(handler.progression_points < current_global_progression)
+ // If we got unsynced somehow, just set them to the current global progression
// Prevents problems with precision errors.
handler.progression_points = current_global_progression
else
- var/amount_to_give = progression_scaling_delta + (progression_scaling_delta * deviance)
- amount_to_give = clamp(amount_to_give, 0, progression_scaling_delta * 2)
- handler.progression_points += amount_to_give
+ handler.progression_points += progression_increment // Should only really happen if an admin is messing with an individual's progression value
handler.on_update()
/datum/controller/subsystem/traitor/proc/register_uplink_handler(datum/uplink_handler/uplink_handler)
diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm
index 98faa95b667..31294d586a8 100644
--- a/code/datums/components/uplink.dm
+++ b/code/datums/components/uplink.dm
@@ -168,8 +168,7 @@
var/list/data = list()
data["telecrystals"] = uplink_handler.telecrystals
data["progression_points"] = uplink_handler.progression_points
- data["current_expected_progression"] = SStraitor.current_global_progression
- data["progression_scaling_deviance"] = SStraitor.progression_scaling_deviance
+ data["joined_population"] = length(GLOB.joined_player_list)
data["current_progression_scaling"] = SStraitor.current_progression_scaling
if(uplink_handler.primary_objectives)
@@ -206,6 +205,7 @@
"restricted_roles" = item.restricted_roles,
"restricted_species" = item.restricted_species,
"progression_minimum" = item.progression_minimum,
+ "population_minimum" = item.population_minimum,
"ref" = REF(item),
))
diff --git a/code/modules/antagonists/traitor/uplink_handler.dm b/code/modules/antagonists/traitor/uplink_handler.dm
index 2801ef29aad..84e186bc116 100644
--- a/code/modules/antagonists/traitor/uplink_handler.dm
+++ b/code/modules/antagonists/traitor/uplink_handler.dm
@@ -51,6 +51,10 @@
/datum/uplink_handler/proc/not_enough_reputation(datum/uplink_item/to_purchase)
return has_progression && progression_points < to_purchase.progression_minimum
+/// Checks if there are enough joined players to purchase an item
+/datum/uplink_handler/proc/not_enough_population(datum/uplink_item/to_purchase)
+ return length(GLOB.joined_player_list) < to_purchase.population_minimum
+
/// Checks for uplink flags as well as items restricted to roles and species
/datum/uplink_handler/proc/check_if_restricted(datum/uplink_item/to_purchase)
if(!to_purchase.can_be_bought(src))
@@ -80,9 +84,15 @@
if(!check_if_restricted(to_purchase))
return FALSE
+ if(not_enough_reputation(to_purchase) || not_enough_population(to_purchase))
+ return FALSE
+
+ if(telecrystals < to_purchase.cost)
+ return FALSE
+
var/current_stock = item_stock[to_purchase.stock_key]
var/stock = current_stock != null ? current_stock : INFINITY
- if(telecrystals < to_purchase.cost || stock <= 0 || not_enough_reputation(to_purchase))
+ if(stock <= 0)
return FALSE
return TRUE
diff --git a/code/modules/asset_cache/assets/uplink.dm b/code/modules/asset_cache/assets/uplink.dm
index 35a907a234d..04971c99195 100644
--- a/code/modules/asset_cache/assets/uplink.dm
+++ b/code/modules/asset_cache/assets/uplink.dm
@@ -35,6 +35,7 @@
"restricted_roles" = item.restricted_roles,
"restricted_species" = item.restricted_species,
"progression_minimum" = item.progression_minimum,
+ "population_minimum" = item.population_minimum,
"cost_override_string" = item.cost_override_string,
"lock_other_purchases" = item.lock_other_purchases
))
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 1782f836bea..f0889770f44 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -82,6 +82,8 @@
var/list/restricted_species = list()
/// The minimum amount of progression needed for this item to be added to uplinks.
var/progression_minimum = 0
+ /// The minimum number of joined players (so not observers) needed for this item to be added to uplinks.
+ var/population_minimum = 0
/// Whether this purchase is visible in the purchase log.
var/purchase_log_vis = TRUE // Visible in the purchase log?
/// Whether this purchase is restricted or not (VR/Events related)
diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm
index 0c348d810cc..8cc8b8b6fca 100644
--- a/code/modules/uplink/uplink_items/dangerous.dm
+++ b/code/modules/uplink/uplink_items/dangerous.dm
@@ -63,6 +63,7 @@
desc = "The double-bladed energy sword does slightly more damage than a standard energy sword and will deflect \
energy projectiles it blocks, but requires two hands to wield. It also struggles to protect you from tackles."
progression_minimum = 30 MINUTES
+ population_minimum = TRAITOR_POPULATION_LOWPOP
item = /obj/item/dualsaber
cost = 13
diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm
index 9d357cebfa4..cb10a3aa39f 100644
--- a/code/modules/uplink/uplink_items/job.dm
+++ b/code/modules/uplink/uplink_items/job.dm
@@ -340,6 +340,7 @@
also give them a bit of sentience though."
progression_minimum = 30 MINUTES
item = /obj/item/reagent_containers/syringe/spider_extract
+ population_minimum = TRAITOR_POPULATION_LOWPOP
cost = 10
restricted_roles = list(JOB_RESEARCH_DIRECTOR, JOB_SCIENTIST, JOB_ROBOTICIST)
surplus = 10
diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm
index c7f0bc82a72..677b13dd08f 100644
--- a/code/modules/uplink/uplink_items/nukeops.dm
+++ b/code/modules/uplink/uplink_items/nukeops.dm
@@ -604,6 +604,7 @@
along with slurred speech, aggression, and the ability to infect others with this agent."
item = /obj/item/storage/box/syndie_kit/romerol
cost = 25
+ population_minimum = TRAITOR_POPULATION_LOWPOP
progression_minimum = 30 MINUTES
purchasable_from = UPLINK_ALL_SYNDIE_OPS | UPLINK_TRAITORS // Don't give this to spies
cant_discount = TRUE
diff --git a/code/modules/uplink/uplink_items/stealthy.dm b/code/modules/uplink/uplink_items/stealthy.dm
index 6d8b6b44b9b..53bc37f59e6 100644
--- a/code/modules/uplink/uplink_items/stealthy.dm
+++ b/code/modules/uplink/uplink_items/stealthy.dm
@@ -85,6 +85,7 @@
and gain the ability to swat bullets from the air, but you will also refuse to use dishonorable ranged weaponry."
item = /obj/item/book/granter/martial/carp
progression_minimum = 30 MINUTES
+ population_minimum = TRAITOR_POPULATION_LOWPOP
cost = 17
surplus = 0
purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
diff --git a/tgui/packages/tgui/interfaces/AbductorConsole.tsx b/tgui/packages/tgui/interfaces/AbductorConsole.tsx
index d8d33053627..79063265551 100644
--- a/tgui/packages/tgui/interfaces/AbductorConsole.tsx
+++ b/tgui/packages/tgui/interfaces/AbductorConsole.tsx
@@ -93,6 +93,8 @@ const Abductsoft = (props) => {
disabled: (credits || 0) < item.cost,
icon: item.icon,
icon_state: item.icon_state,
+ population_tooltip: '',
+ insufficient_population: false,
});
}
}
diff --git a/tgui/packages/tgui/interfaces/Uplink/GenericUplink.tsx b/tgui/packages/tgui/interfaces/Uplink/GenericUplink.tsx
index 4165c1049f3..ce460599022 100644
--- a/tgui/packages/tgui/interfaces/Uplink/GenericUplink.tsx
+++ b/tgui/packages/tgui/interfaces/Uplink/GenericUplink.tsx
@@ -133,6 +133,8 @@ export type Item = {
category: string;
cost: JSX.Element | string;
desc: JSX.Element | string;
+ population_tooltip: string;
+ insufficient_population: BooleanLike;
disabled: BooleanLike;
};
@@ -184,9 +186,19 @@ const ItemList = (props: ItemListProps) => {
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
+ opacity: item.insufficient_population ? '0.5' : '1',
}}
>
- {item.name}
+ {item.insufficient_population ? (
+
+
+
+ {item.name}
+
+
+ ) : (
+ item.name
+ )}
@@ -215,6 +227,21 @@ const ItemList = (props: ItemListProps) => {
}
>
+ {item.insufficient_population ? (
+
+ {' '}
+ {item.population_tooltip}
+
+ ) : (
+ ''
+ )}
+
{
const {
telecrystals,
progression_points,
+ joined_population,
primary_objectives,
can_renegotiate,
has_progression,
- current_expected_progression,
- progression_scaling_deviance,
current_progression_scaling,
extra_purchasable,
extra_purchasable_stock,
@@ -202,6 +201,8 @@ export class Uplink extends Component<{}, UplinkState> {
const item = itemsToAdd[i];
const hasEnoughProgression =
progression_points >= item.progression_minimum;
+ const hasEnoughPop =
+ !joined_population || joined_population >= item.population_minimum;
let stock: number | null = current_stock[item.stock_key];
if (item.ref) {
@@ -245,8 +246,14 @@ export class Uplink extends Component<{}, UplinkState> {
)}
),
+ population_tooltip:
+ 'This item is not cleared for operations performed against stations crewed by fewer than ' +
+ item.population_minimum +
+ ' people.',
+ insufficient_population: !hasEnoughPop,
disabled:
!canBuy ||
+ !hasEnoughPop ||
(has_progression && !hasEnoughProgression) ||
(item.lock_other_purchases && purchased_items > 0),
extraData: {
@@ -256,17 +263,7 @@ export class Uplink extends Component<{}, UplinkState> {
},
});
}
- // Get the difference between the current progression and
- // expected progression
- let progressionPercentage =
- current_expected_progression - progression_points;
- // Clamp it down between 0 and 2
- progressionPercentage = Math.min(
- Math.max(progressionPercentage / progression_scaling_deviance, -1),
- 1,
- );
- // Round it and convert it into a percentage
- progressionPercentage = Math.round(progressionPercentage * 1000) / 10;
+
return (
@@ -292,29 +289,6 @@ export class Uplink extends Component<{}, UplinkState> {
every minute
- {Math.abs(progressionPercentage) > 0 && (
-
- Because your threat level is
- {progressionPercentage < 0
- ? ' ahead '
- : ' behind '}
- of where it should be, you are getting
-
- {progressionPercentage}%
-
- {progressionPercentage < 0 ? 'less' : 'more'}{' '}
- threat every minute
-
- )}
{dangerLevelsTooltip}
diff --git a/tgui/packages/tgui/interfaces/common/MalfAiModules.tsx b/tgui/packages/tgui/interfaces/common/MalfAiModules.tsx
index 971dfc3ef17..039b292aa6f 100644
--- a/tgui/packages/tgui/interfaces/common/MalfAiModules.tsx
+++ b/tgui/packages/tgui/interfaces/common/MalfAiModules.tsx
@@ -34,6 +34,8 @@ export function MalfAiModules(props) {
icon: item.icon,
id: item.name,
name: item.name,
+ population_tooltip: '',
+ insufficient_population: false,
});
}
}