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, }); } }