mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-26 14:39:58 +01:00
Replaces Traitor Greentext Objectives with Assassination/Escape Objectives (#71349)
## About The Pull Request - Traitors now have the old two kill + escape objectives from before progressive traitors. These objectives are for greentext/victory conditions only, and are known as "Primary Objectives". - The Progressive Traitor system remains in place for unlocking dangerous gear. - Completing the Final Objective from the progressive list is an alternate route to victory. - Traitor Uplinks now have a new tab that lists Primary Objectives, which will also indicate when the Traitor has a secured victory by Final Objective. - Also cleaned up the round end report, see below. ## Why It's Good For The Game See https://hackmd.io/WVjTLp6xTJiTFEkhSqqWIQ?view for my complete thoughts. TL;DR: - Getting Rep is too much of a time commitment for Greentext. - Most of ProgTraitor is quiet sabotage that no one cares about, leading to less conflict with the crew. - Losing the Uplink creates an unwinnable state. This PR fixes the above, by bringing back the 2 kill + escape system. Traitors are now required to actually negatively affect at least some crewmembers, and actually survive the round, to gain a victory state. Alternatively, if they reach the Final Objective, they are given a free pass, as an alternate route if they cannot find their Primary Objective targets. Final Objectives will count as a victory even if the traitor dies. The Progressive Traitor system is still used for unlocking gear, and no values for items, or restrictions based on role, have been changed. ## Changelog 🆑 balance: Traitors now have two kill and an escape objective for Greentext purposes, though completing a Final Objective will Greentext too. /🆑 New Primary Objective Tab:  If the Final Objective is done, the traitor's victory condition is secured, and the Primary Objectives no longer matter.  Round-end report, showing one traitor that greentexts through traditional means, and one that doesn't get their kill target or escape, but greentexts via Final Objective anyway. 
This commit is contained in:
@@ -166,20 +166,34 @@
|
||||
|
||||
data["maximum_potential_objectives"] = uplink_handler.maximum_potential_objectives
|
||||
if(uplink_handler.has_objectives)
|
||||
var/list/primary_objectives = list()
|
||||
for(var/datum/objective/task as anything in uplink_handler.primary_objectives)
|
||||
var/list/task_data = list()
|
||||
if(length(primary_objectives) > length(GLOB.phonetic_alphabet))
|
||||
task_data["task_name"] = "DIRECTIVE [length(primary_objectives) + 1]" //The english alphabet is WEAK
|
||||
else
|
||||
task_data["task_name"] = "DIRECTIVE [uppertext(GLOB.phonetic_alphabet[length(primary_objectives) + 1])]"
|
||||
task_data["task_text"] = task.explanation_text
|
||||
primary_objectives += list(task_data)
|
||||
|
||||
var/list/potential_objectives = list()
|
||||
for(var/index in 1 to uplink_handler.potential_objectives.len)
|
||||
var/datum/traitor_objective/objective = uplink_handler.potential_objectives[index]
|
||||
var/list/objective_data = objective.uplink_ui_data(user)
|
||||
objective_data["id"] = index
|
||||
potential_objectives += list(objective_data)
|
||||
|
||||
var/list/active_objectives = list()
|
||||
for(var/index in 1 to uplink_handler.active_objectives.len)
|
||||
var/datum/traitor_objective/objective = uplink_handler.active_objectives[index]
|
||||
var/list/objective_data = objective.uplink_ui_data(user)
|
||||
objective_data["id"] = index
|
||||
active_objectives += list(objective_data)
|
||||
|
||||
data["primary_objectives"] = primary_objectives
|
||||
data["potential_objectives"] = potential_objectives
|
||||
data["active_objectives"] = active_objectives
|
||||
data["completed_final_objective"] = uplink_handler.final_objective
|
||||
|
||||
var/list/stock_list = uplink_handler.item_stock.Copy()
|
||||
var/list/extra_purchasable_stock = list()
|
||||
|
||||
@@ -110,7 +110,7 @@ GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list
|
||||
return target
|
||||
|
||||
//dupe_search_range is a list of antag datums / minds / teams
|
||||
/datum/objective/proc/find_target(dupe_search_range, blacklist)
|
||||
/datum/objective/proc/find_target(dupe_search_range, list/blacklist)
|
||||
var/list/datum/mind/owners = get_owners()
|
||||
if(!dupe_search_range)
|
||||
dupe_search_range = get_owners()
|
||||
@@ -462,7 +462,7 @@ GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list
|
||||
var/target_real_name // Has to be stored because the target's real_name can change over the course of the round
|
||||
var/target_missing_id
|
||||
|
||||
/datum/objective/escape/escape_with_identity/find_target(dupe_search_range)
|
||||
/datum/objective/escape/escape_with_identity/find_target(dupe_search_range, list/blacklist)
|
||||
target = ..()
|
||||
update_explanation_text()
|
||||
|
||||
@@ -576,7 +576,7 @@ GLOBAL_LIST_EMPTY(possible_items)
|
||||
for(var/I in subtypesof(/datum/objective_item/steal))
|
||||
new I
|
||||
|
||||
/datum/objective/steal/find_target(dupe_search_range)
|
||||
/datum/objective/steal/find_target(dupe_search_range, list/blacklist)
|
||||
var/list/datum/mind/owners = get_owners()
|
||||
if(!dupe_search_range)
|
||||
dupe_search_range = get_owners()
|
||||
@@ -659,7 +659,7 @@ GLOBAL_LIST_EMPTY(possible_items_special)
|
||||
for(var/I in subtypesof(/datum/objective_item/special) + subtypesof(/datum/objective_item/stack))
|
||||
new I
|
||||
|
||||
/datum/objective/steal/special/find_target(dupe_search_range)
|
||||
/datum/objective/steal/special/find_target(dupe_search_range, list/blacklist)
|
||||
return set_target(pick(GLOB.possible_items_special))
|
||||
|
||||
/datum/objective/capture
|
||||
@@ -821,8 +821,9 @@ GLOBAL_LIST_EMPTY(possible_items_special)
|
||||
name = "destroy AI"
|
||||
martyr_compatible = TRUE
|
||||
|
||||
/datum/objective/destroy/find_target(dupe_search_range)
|
||||
var/list/possible_targets = active_ais(1)
|
||||
/datum/objective/destroy/find_target(dupe_search_range, list/blacklist)
|
||||
var/list/possible_targets = active_ais(TRUE)
|
||||
possible_targets -= blacklist
|
||||
var/mob/living/silicon/ai/target_ai = pick(possible_targets)
|
||||
target = target_ai.mind
|
||||
update_explanation_text()
|
||||
|
||||
@@ -347,7 +347,7 @@
|
||||
UnregisterSignal(target.current, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO))
|
||||
target = null
|
||||
|
||||
/datum/objective/sacrifice/find_target(dupe_search_range)
|
||||
/datum/objective/sacrifice/find_target(dupe_search_range, list/blacklist)
|
||||
clear_sacrifice()
|
||||
if(!istype(team, /datum/team/cult))
|
||||
return
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
uplink.uplink_handler = uplink_handler
|
||||
else
|
||||
uplink_handler = uplink.uplink_handler
|
||||
uplink_handler.primary_objectives = objectives
|
||||
uplink_handler.has_progression = TRUE
|
||||
SStraitor.register_uplink_handler(uplink_handler)
|
||||
|
||||
@@ -147,71 +148,33 @@
|
||||
employer = pick(possible_employers)
|
||||
traitor_flavor = strings(TRAITOR_FLAVOR_FILE, employer)
|
||||
|
||||
/datum/objective/traitor_progression
|
||||
name = "traitor progression"
|
||||
explanation_text = "Become a living legend by getting a total of %REPUTATION% reputation points"
|
||||
|
||||
var/possible_range = list(40 MINUTES, 90 MINUTES)
|
||||
var/required_total_progression_points
|
||||
|
||||
/datum/objective/traitor_progression/New(text)
|
||||
. = ..()
|
||||
required_total_progression_points = round(rand(possible_range[1], possible_range[2]))
|
||||
explanation_text = replacetext(explanation_text, "%REPUTATION%", DISPLAY_PROGRESSION(required_total_progression_points))
|
||||
|
||||
/datum/objective/traitor_progression/check_completion()
|
||||
if(!owner)
|
||||
return FALSE
|
||||
var/datum/antagonist/traitor/traitor = owner.has_antag_datum(/datum/antagonist/traitor)
|
||||
if(!traitor)
|
||||
return FALSE
|
||||
if(!traitor.uplink_handler)
|
||||
return FALSE
|
||||
if(traitor.uplink_handler.progression_points < required_total_progression_points)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/objective/traitor_objectives
|
||||
name = "traitor objective"
|
||||
explanation_text = "Complete objectives colletively worth more than %REPUTATION% reputation points"
|
||||
|
||||
var/possible_range = list(20 MINUTES, 30 MINUTES)
|
||||
var/required_progression_in_objectives
|
||||
|
||||
/datum/objective/traitor_objectives/New(text)
|
||||
. = ..()
|
||||
required_progression_in_objectives = round(rand(possible_range[1], possible_range[2]))
|
||||
explanation_text = replacetext(explanation_text, "%REPUTATION%", DISPLAY_PROGRESSION(required_progression_in_objectives))
|
||||
|
||||
/datum/objective/traitor_objectives/check_completion()
|
||||
if(!owner)
|
||||
return FALSE
|
||||
var/datum/antagonist/traitor/traitor = owner.has_antag_datum(/datum/antagonist/traitor)
|
||||
if(!traitor)
|
||||
return FALSE
|
||||
if(!traitor.uplink_handler)
|
||||
return FALSE
|
||||
var/total_points = 0
|
||||
for(var/datum/traitor_objective/objective as anything in traitor.uplink_handler.completed_objectives)
|
||||
if(objective.objective_state != OBJECTIVE_STATE_COMPLETED)
|
||||
continue
|
||||
total_points += objective.progression_reward
|
||||
if(total_points < required_progression_in_objectives)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/// Generates a complete set of traitor objectives up to the traitor objective limit, including non-generic objectives such as martyr and hijack.
|
||||
/datum/antagonist/traitor/proc/forge_traitor_objectives()
|
||||
objectives.Cut()
|
||||
|
||||
var/datum/objective/traitor_progression/final_objective = new /datum/objective/traitor_progression()
|
||||
final_objective.owner = owner
|
||||
objectives += final_objective
|
||||
var/list/kill_targets = list() //for blacklisting already-set targets
|
||||
var/objective_limit = CONFIG_GET(number/traitor_objectives_amount)
|
||||
for(var/i in 1 to objective_limit)
|
||||
var/list/ai_targets = active_ais(z = 2) //For multiZ stations, this proc will include AIs on all station levels if the provided arg is 2.
|
||||
ai_targets -= kill_targets
|
||||
if(ai_targets.len)
|
||||
var/diceroll = rand(1, (living_player_count() - 1)) //AI kill and crew kill objectives are different, but I want a rough equal chance for AIs to be targets. Maybe I'll refractor this someday
|
||||
if(diceroll <= ai_targets.len)
|
||||
var/datum/objective/task = new /datum/objective/destroy()
|
||||
task.owner = owner
|
||||
task.find_target(blacklist = kill_targets)
|
||||
kill_targets += task.target
|
||||
objectives += task
|
||||
continue
|
||||
|
||||
var/datum/objective/traitor_objectives/objective_completion = new /datum/objective/traitor_objectives()
|
||||
objective_completion.owner = owner
|
||||
objectives += objective_completion
|
||||
var/datum/objective/task = new /datum/objective/assassinate()
|
||||
task.owner = owner
|
||||
task.find_target(blacklist = kill_targets)
|
||||
kill_targets += task.target
|
||||
objectives += task
|
||||
|
||||
var/datum/objective/escape/bye = new /datum/objective/escape()
|
||||
objectives += bye
|
||||
bye.owner = owner
|
||||
|
||||
/datum/antagonist/traitor/apply_innate_effects(mob/living/mob_override)
|
||||
. = ..()
|
||||
@@ -279,6 +242,9 @@
|
||||
objectives_text += "<br><B>Objective #[count]</B>: [objective.explanation_text] [span_redtext("Fail.")]"
|
||||
traitor_won = FALSE
|
||||
count++
|
||||
if(uplink_handler.final_objective)
|
||||
objectives_text += "<br>[span_greentext("[traitor_won ? "Additionally" : "However"], the final objective \"[uplink_handler.final_objective]\" was completed!")]"
|
||||
traitor_won = TRUE
|
||||
|
||||
result += "<br>[owner.name] <B>[traitor_flavor["roundend_report"]]</B>"
|
||||
|
||||
@@ -292,11 +258,7 @@
|
||||
result += objectives_text
|
||||
|
||||
if(uplink_handler)
|
||||
var/completed_objectives_text = "Completed Uplink Objectives: "
|
||||
for(var/datum/traitor_objective/objective as anything in uplink_handler.completed_objectives)
|
||||
if(objective.objective_state == OBJECTIVE_STATE_COMPLETED)
|
||||
completed_objectives_text += "<br><B>[objective.name]</B> - ([objective.telecrystal_reward] TC, [DISPLAY_PROGRESSION(objective.progression_reward)] Reputation)"
|
||||
result += completed_objectives_text
|
||||
result += "<br>The traitor had a total of [uplink_handler.progression_points] Reputation and [uplink_handler.telecrystals] Unused Telecrystals."
|
||||
|
||||
var/special_role_text = lowertext(name)
|
||||
|
||||
|
||||
@@ -171,6 +171,12 @@
|
||||
|
||||
/datum/traitor_objective/assassinate/generate_objective(datum/mind/generating_for, list/possible_duplicates)
|
||||
|
||||
var/list/already_targeting = list() //List of minds we're already targeting. The possible_duplicates is a list of objectives, so let's not mix things
|
||||
for(var/datum/objective/task as anything in handler.primary_objectives)
|
||||
if(!istype(task.target, /datum/mind))
|
||||
continue
|
||||
already_targeting += task.target //Removing primary objective kill targets from the list
|
||||
|
||||
var/parent_type = type2parent(type)
|
||||
//don't roll head of staff types if you haven't completed the normal version
|
||||
if(heads_of_staff && !handler.get_completion_count(parent_type))
|
||||
@@ -182,6 +188,8 @@
|
||||
if(generating_for.late_joiner)
|
||||
try_target_late_joiners = TRUE
|
||||
for(var/datum/mind/possible_target as anything in get_crewmember_minds())
|
||||
if(possible_target in already_targeting)
|
||||
continue
|
||||
var/target_area = get_area(possible_target.current)
|
||||
if(possible_target == generating_for)
|
||||
continue
|
||||
|
||||
@@ -50,6 +50,13 @@
|
||||
heads_of_staff = TRUE
|
||||
|
||||
/datum/traitor_objective/eyesnatching/generate_objective(datum/mind/generating_for, list/possible_duplicates)
|
||||
|
||||
var/list/already_targeting = list() //List of minds we're already targeting. The possible_duplicates is a list of objectives, so let's not mix things
|
||||
for(var/datum/objective/task as anything in handler.primary_objectives)
|
||||
if(!istype(task.target, /datum/mind))
|
||||
continue
|
||||
already_targeting += task.target //Removing primary objective kill targets from the list
|
||||
|
||||
var/list/possible_targets = list()
|
||||
var/try_target_late_joiners = FALSE
|
||||
if(generating_for.late_joiner)
|
||||
@@ -59,6 +66,9 @@
|
||||
if(possible_target == generating_for)
|
||||
continue
|
||||
|
||||
if(possible_target in already_targeting)
|
||||
continue
|
||||
|
||||
if(!ishuman(possible_target.current))
|
||||
continue
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
for(var/datum/traitor_objective/objective as anything in handler.potential_objectives)
|
||||
objective.fail_objective()
|
||||
user.playsound_local(get_turf(user), 'sound/traitor/final_objective.ogg', vol = 100, vary = FALSE, channel = CHANNEL_TRAITOR)
|
||||
handler.final_objective = name
|
||||
|
||||
/datum/traitor_objective/ultimate/uplink_ui_data(mob/user)
|
||||
. = ..()
|
||||
|
||||
@@ -136,11 +136,21 @@
|
||||
alive_bonus = 2
|
||||
|
||||
/datum/traitor_objective/kidnapping/generate_objective(datum/mind/generating_for, list/possible_duplicates)
|
||||
|
||||
var/list/already_targeting = list() //List of minds we're already targeting. The possible_duplicates is a list of objectives, so let's not mix things
|
||||
for(var/datum/objective/task as anything in handler.primary_objectives)
|
||||
if(!istype(task.target, /datum/mind))
|
||||
continue
|
||||
already_targeting += task.target //Removing primary objective kill targets from the list
|
||||
|
||||
var/list/possible_targets = list()
|
||||
for(var/datum/mind/possible_target as anything in get_crewmember_minds())
|
||||
if(possible_target == generating_for)
|
||||
continue
|
||||
|
||||
if(possible_target in already_targeting)
|
||||
continue
|
||||
|
||||
if(!ishuman(possible_target.current))
|
||||
continue
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
var/list/completed_objectives = list()
|
||||
/// All objectives assigned by type to handle any duplicates
|
||||
var/list/potential_duplicate_objectives = list()
|
||||
/// Text of the final objective, once assigned. Used for uplink data and traitor greentext. Empty string means not yet reached.
|
||||
var/final_objective = ""
|
||||
/// Objectives that must be completed for traitor greentext. Set by the traitor datum.
|
||||
var/list/primary_objectives
|
||||
/// The role that this uplink handler is associated to.
|
||||
var/assigned_role
|
||||
/// The species this uplink handler is associated to.
|
||||
|
||||
@@ -291,6 +291,7 @@ const ObjectiveFunction = (
|
||||
progressionReward={objective.progression_reward}
|
||||
objectiveState={objective.objective_state}
|
||||
originalProgression={objective.original_progression}
|
||||
hideTcRep={objective.final_objective}
|
||||
finalObjective={objective.final_objective}
|
||||
canAbort={
|
||||
!!handleAbort &&
|
||||
@@ -338,18 +339,19 @@ type ObjectiveElementProps = {
|
||||
telecrystalReward: number;
|
||||
progressionReward: number;
|
||||
uiButtons?: JSX.Element;
|
||||
objectiveState: ObjectiveState;
|
||||
objectiveState?: ObjectiveState;
|
||||
originalProgression: number;
|
||||
telecrystalPenalty: number;
|
||||
grow: boolean;
|
||||
hideTcRep: BooleanLike;
|
||||
finalObjective: BooleanLike;
|
||||
canAbort: BooleanLike;
|
||||
|
||||
handleCompletion: (event: MouseEvent) => void;
|
||||
handleAbort: (event: MouseEvent) => void;
|
||||
handleCompletion?: (event: MouseEvent) => void;
|
||||
handleAbort?: (event: MouseEvent) => void;
|
||||
};
|
||||
|
||||
const ObjectiveElement = (props: ObjectiveElementProps, context) => {
|
||||
export const ObjectiveElement = (props: ObjectiveElementProps, context) => {
|
||||
const {
|
||||
name,
|
||||
reputation,
|
||||
@@ -364,6 +366,7 @@ const ObjectiveElement = (props: ObjectiveElementProps, context) => {
|
||||
canAbort,
|
||||
originalProgression,
|
||||
grow,
|
||||
hideTcRep,
|
||||
finalObjective,
|
||||
...rest
|
||||
} = props;
|
||||
@@ -419,9 +422,17 @@ const ObjectiveElement = (props: ObjectiveElementProps, context) => {
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={grow} basis="content">
|
||||
<Box className="UplinkObjective__Content" height="100%">
|
||||
<Box
|
||||
style={{
|
||||
'border-bottom': hideTcRep
|
||||
? '2px solid rgba(0, 0, 0, 0.5)'
|
||||
: undefined,
|
||||
}}
|
||||
className="UplinkObjective__Content"
|
||||
height="100%"
|
||||
mb={hideTcRep ? 2 : 0}>
|
||||
<Box>{description}</Box>
|
||||
{!finalObjective && (
|
||||
{!hideTcRep && (
|
||||
<Box mt={1}>
|
||||
Failing this objective will deduct {telecrystalPenalty} TC.
|
||||
</Box>
|
||||
@@ -436,109 +447,112 @@ const ObjectiveElement = (props: ObjectiveElementProps, context) => {
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Box className="UplinkObjective__Footer">
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<Stack align="center" justify="center">
|
||||
<Box
|
||||
style={{
|
||||
'border': '2px solid rgba(0, 0, 0, 0.5)',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none',
|
||||
'border-bottom': objectiveFinished ? 'none' : undefined,
|
||||
}}
|
||||
className={reputation.gradient}
|
||||
py={0.5}
|
||||
width="100%"
|
||||
textAlign="center">
|
||||
{telecrystalReward} TC,
|
||||
<Box ml={1} as="span">
|
||||
{calculateProgression(progressionReward)} Reputation
|
||||
{Math.abs(progressionDiff) > 10 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<Box>
|
||||
You will get
|
||||
<Box
|
||||
mr={1}
|
||||
ml={1}
|
||||
color={
|
||||
progressionDiff > 0
|
||||
? progressionDiff > 25
|
||||
? 'red'
|
||||
: 'orange'
|
||||
: 'green'
|
||||
}
|
||||
as="span">
|
||||
{Math.abs(progressionDiff)}%
|
||||
</Box>
|
||||
{progressionDiff > 0 ? 'less' : 'more'} reputation
|
||||
from this objective. This is because your reputation
|
||||
is {progressionDiff > 0 ? 'ahead ' : 'behind '}
|
||||
where it normally should be at.
|
||||
</Box>
|
||||
}>
|
||||
<Box
|
||||
ml={1}
|
||||
color={
|
||||
progressionDiff > 0
|
||||
? progressionDiff > 35
|
||||
? 'red'
|
||||
: 'orange'
|
||||
: 'green'
|
||||
}
|
||||
as="span">
|
||||
({progressionDiff > 0 ? '-' : '+'}
|
||||
{Math.abs(progressionDiff)}%)
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
{objectiveFinished ? (
|
||||
<Box
|
||||
inline
|
||||
className={reputation.gradient}
|
||||
style={{
|
||||
'border-radius': '0',
|
||||
'border': '2px solid rgba(0, 0, 0, 0.5)',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none',
|
||||
}}
|
||||
position="relative"
|
||||
width="100%"
|
||||
textAlign="center"
|
||||
bold>
|
||||
{!hideTcRep && (
|
||||
<Box className="UplinkObjective__Footer">
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<Stack align="center" justify="center">
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
backgroundColor={
|
||||
objectiveFailed
|
||||
? 'rgba(255, 0, 0, 0.1)'
|
||||
: 'rgba(0, 255, 0, 0.1)'
|
||||
}
|
||||
position="absolute"
|
||||
left={0}
|
||||
top={0}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleCompletion}
|
||||
color={objectiveFailed ? 'bad' : 'good'}
|
||||
style={{
|
||||
'border': '1px solid rgba(0, 0, 0, 0.65)',
|
||||
'border': '2px solid rgba(0, 0, 0, 0.5)',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none',
|
||||
'border-bottom': objectiveFinished ? 'none' : undefined,
|
||||
}}
|
||||
my={1}>
|
||||
TURN IN
|
||||
</Button>
|
||||
</Box>
|
||||
) : null}
|
||||
</Stack.Item>
|
||||
{!!uiButtons && !objectiveFinished && (
|
||||
<Stack.Item>{uiButtons}</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
className={reputation.gradient}
|
||||
py={0.5}
|
||||
width="100%"
|
||||
textAlign="center">
|
||||
{telecrystalReward} TC,
|
||||
<Box ml={1} as="span">
|
||||
{calculateProgression(progressionReward)} Reputation
|
||||
{Math.abs(progressionDiff) > 10 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<Box>
|
||||
You will get
|
||||
<Box
|
||||
mr={1}
|
||||
ml={1}
|
||||
color={
|
||||
progressionDiff > 0
|
||||
? progressionDiff > 25
|
||||
? 'red'
|
||||
: 'orange'
|
||||
: 'green'
|
||||
}
|
||||
as="span">
|
||||
{Math.abs(progressionDiff)}%
|
||||
</Box>
|
||||
{progressionDiff > 0 ? 'less' : 'more'} reputation
|
||||
from this objective. This is because your
|
||||
reputation is{' '}
|
||||
{progressionDiff > 0 ? 'ahead ' : 'behind '}
|
||||
where it normally should be at.
|
||||
</Box>
|
||||
}>
|
||||
<Box
|
||||
ml={1}
|
||||
color={
|
||||
progressionDiff > 0
|
||||
? progressionDiff > 35
|
||||
? 'red'
|
||||
: 'orange'
|
||||
: 'green'
|
||||
}
|
||||
as="span">
|
||||
({progressionDiff > 0 ? '-' : '+'}
|
||||
{Math.abs(progressionDiff)}%)
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
{objectiveFinished ? (
|
||||
<Box
|
||||
inline
|
||||
className={reputation.gradient}
|
||||
style={{
|
||||
'border-radius': '0',
|
||||
'border': '2px solid rgba(0, 0, 0, 0.5)',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none',
|
||||
}}
|
||||
position="relative"
|
||||
width="100%"
|
||||
textAlign="center"
|
||||
bold>
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
backgroundColor={
|
||||
objectiveFailed
|
||||
? 'rgba(255, 0, 0, 0.1)'
|
||||
: 'rgba(0, 255, 0, 0.1)'
|
||||
}
|
||||
position="absolute"
|
||||
left={0}
|
||||
top={0}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleCompletion}
|
||||
color={objectiveFailed ? 'bad' : 'good'}
|
||||
style={{
|
||||
'border': '1px solid rgba(0, 0, 0, 0.65)',
|
||||
}}
|
||||
my={1}>
|
||||
TURN IN
|
||||
</Button>
|
||||
</Box>
|
||||
) : null}
|
||||
</Stack.Item>
|
||||
{!!uiButtons && !objectiveFinished && (
|
||||
<Stack.Item>{uiButtons}</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Box, Dimmer, Section, Stack } from '../../components';
|
||||
import { ObjectiveElement } from './ObjectiveMenu';
|
||||
|
||||
type PrimaryObjectiveMenuProps = {
|
||||
primary_objectives;
|
||||
final_objective;
|
||||
};
|
||||
|
||||
export const PrimaryObjectiveMenu = (
|
||||
props: PrimaryObjectiveMenuProps,
|
||||
context
|
||||
) => {
|
||||
const { primary_objectives, final_objective } = props;
|
||||
return (
|
||||
<Section fill>
|
||||
<Section>
|
||||
<Box mt={3} mb={3} bold fontSize={1.2} align="center" color="white">
|
||||
{
|
||||
'Agent, your Primary Objectives are as follows. Complete these at all costs.'
|
||||
}
|
||||
</Box>
|
||||
<Box mt={3} mb={5} bold fontSize={1.2} align="center" color="white">
|
||||
{
|
||||
'Completing Secondary Objectives may allow you to aquire additional equipment.'
|
||||
}
|
||||
</Box>
|
||||
</Section>
|
||||
{final_objective && (
|
||||
<Dimmer>
|
||||
<Box
|
||||
color="red"
|
||||
fontFamily={'Bahnschrift'}
|
||||
fontSize={3}
|
||||
align={'top'}
|
||||
as="span">
|
||||
PRIORITY MESSAGE
|
||||
<br />
|
||||
SOURCE: xxx.xxx.xxx.224:41394
|
||||
<br />
|
||||
<br />
|
||||
\\Debrief in progress.
|
||||
<br />
|
||||
\\Final Objective confirmed complete. <br />
|
||||
\\Your work is done here, agent.
|
||||
<br />
|
||||
<br />
|
||||
CONNECTION CLOSED_
|
||||
</Box>
|
||||
</Dimmer>
|
||||
)}
|
||||
<Section>
|
||||
<Stack vertical fill scrollable>
|
||||
{primary_objectives.map((prim_obj, index) => (
|
||||
<Stack.Item key={index}>
|
||||
<ObjectiveElement
|
||||
key={prim_obj.id}
|
||||
name={prim_obj['task_name']}
|
||||
description={prim_obj['task_text']}
|
||||
reputation={{
|
||||
minutesLessThan: 0,
|
||||
title: 'none',
|
||||
gradient:
|
||||
index === primary_objectives.length - 1
|
||||
? 'reputation-good'
|
||||
: 'reputation-very-good',
|
||||
}}
|
||||
telecrystalReward={0}
|
||||
telecrystalPenalty={0}
|
||||
progressionReward={0}
|
||||
originalProgression={0}
|
||||
hideTcRep={1}
|
||||
canAbort={false}
|
||||
grow={false}
|
||||
finalObjective={false}
|
||||
/>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useBackend } from '../../backend';
|
||||
import { Window } from '../../layouts';
|
||||
import { GenericUplink, Item } from './GenericUplink';
|
||||
import { Component } from 'inferno';
|
||||
import { Component, Fragment } from 'inferno';
|
||||
import { fetchRetry } from '../../http';
|
||||
import { resolveAsset } from '../../assets';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { Box, Tabs, Button, Stack, Section, Tooltip } from '../../components';
|
||||
import { PrimaryObjectiveMenu } from './PrimaryObjectiveMenu';
|
||||
import { Objective, ObjectiveMenu } from './ObjectiveMenu';
|
||||
import { calculateProgression, calculateReputationLevel, reputationDefault, reputationLevelsTooltip } from './calculateReputationLevel';
|
||||
|
||||
@@ -46,6 +47,10 @@ type UplinkData = {
|
||||
|
||||
has_objectives: BooleanLike;
|
||||
has_progression: BooleanLike;
|
||||
primary_objectives: {
|
||||
[key: number]: string;
|
||||
};
|
||||
completed_final_objective: string;
|
||||
potential_objectives: Objective[];
|
||||
active_objectives: Objective[];
|
||||
maximum_active_objectives: number;
|
||||
@@ -150,6 +155,8 @@ export class Uplink extends Component<{}, UplinkState> {
|
||||
const {
|
||||
telecrystals,
|
||||
progression_points,
|
||||
primary_objectives,
|
||||
completed_final_objective,
|
||||
active_objectives,
|
||||
potential_objectives,
|
||||
has_objectives,
|
||||
@@ -313,15 +320,22 @@ export class Uplink extends Component<{}, UplinkState> {
|
||||
<Stack.Item grow={1}>
|
||||
<Tabs fluid textAlign="center">
|
||||
{!!has_objectives && (
|
||||
<Tabs.Tab
|
||||
selected={currentTab === 0}
|
||||
onClick={() => this.setState({ currentTab: 0 })}>
|
||||
Objectives
|
||||
</Tabs.Tab>
|
||||
<Fragment>
|
||||
<Tabs.Tab
|
||||
selected={currentTab === 0}
|
||||
onClick={() => this.setState({ currentTab: 0 })}>
|
||||
Primary Objectives
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
selected={currentTab === 1}
|
||||
onClick={() => this.setState({ currentTab: 1 })}>
|
||||
Secondary Objectives
|
||||
</Tabs.Tab>
|
||||
</Fragment>
|
||||
)}
|
||||
<Tabs.Tab
|
||||
selected={currentTab === 1 || !has_objectives}
|
||||
onClick={() => this.setState({ currentTab: 1 })}>
|
||||
selected={currentTab === 2 || !has_objectives}
|
||||
onClick={() => this.setState({ currentTab: 2 })}>
|
||||
Market
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
@@ -341,53 +355,59 @@ export class Uplink extends Component<{}, UplinkState> {
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
{(currentTab === 0 && has_objectives && (
|
||||
<ObjectiveMenu
|
||||
activeObjectives={active_objectives}
|
||||
potentialObjectives={potential_objectives}
|
||||
maximumActiveObjectives={maximum_active_objectives}
|
||||
maximumPotentialObjectives={maximum_potential_objectives}
|
||||
handleObjectiveAction={(objective, action) =>
|
||||
act('objective_act', {
|
||||
check: objective.original_progression,
|
||||
objective_action: action,
|
||||
index: objective.id,
|
||||
})
|
||||
}
|
||||
handleStartObjective={(objective) =>
|
||||
act('start_objective', {
|
||||
check: objective.original_progression,
|
||||
index: objective.id,
|
||||
})
|
||||
}
|
||||
handleObjectiveAbort={(objective) =>
|
||||
act('objective_abort', {
|
||||
check: objective.original_progression,
|
||||
index: objective.id,
|
||||
})
|
||||
}
|
||||
handleObjectiveCompleted={(objective) =>
|
||||
act('finish_objective', {
|
||||
check: objective.original_progression,
|
||||
index: objective.id,
|
||||
})
|
||||
}
|
||||
handleRequestObjectives={() => act('regenerate_objectives')}
|
||||
<PrimaryObjectiveMenu
|
||||
primary_objectives={primary_objectives}
|
||||
final_objective={completed_final_objective}
|
||||
/>
|
||||
)) || (
|
||||
<GenericUplink
|
||||
currency=""
|
||||
categories={allCategories}
|
||||
items={items}
|
||||
handleBuy={(item) => {
|
||||
const extraDataItem = item as Item<ItemExtraData>;
|
||||
if (!extraDataItem.extraData?.ref) {
|
||||
act('buy', { path: item.id });
|
||||
} else {
|
||||
act('buy', { ref: extraDataItem.extraData.ref });
|
||||
)) ||
|
||||
(currentTab === 1 && has_objectives && (
|
||||
<ObjectiveMenu
|
||||
activeObjectives={active_objectives}
|
||||
potentialObjectives={potential_objectives}
|
||||
maximumActiveObjectives={maximum_active_objectives}
|
||||
maximumPotentialObjectives={maximum_potential_objectives}
|
||||
handleObjectiveAction={(objective, action) =>
|
||||
act('objective_act', {
|
||||
check: objective.original_progression,
|
||||
objective_action: action,
|
||||
index: objective.id,
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
handleStartObjective={(objective) =>
|
||||
act('start_objective', {
|
||||
check: objective.original_progression,
|
||||
index: objective.id,
|
||||
})
|
||||
}
|
||||
handleObjectiveAbort={(objective) =>
|
||||
act('objective_abort', {
|
||||
check: objective.original_progression,
|
||||
index: objective.id,
|
||||
})
|
||||
}
|
||||
handleObjectiveCompleted={(objective) =>
|
||||
act('finish_objective', {
|
||||
check: objective.original_progression,
|
||||
index: objective.id,
|
||||
})
|
||||
}
|
||||
handleRequestObjectives={() => act('regenerate_objectives')}
|
||||
/>
|
||||
)) || (
|
||||
<GenericUplink
|
||||
currency=""
|
||||
categories={allCategories}
|
||||
items={items}
|
||||
handleBuy={(item) => {
|
||||
const extraDataItem = item as Item<ItemExtraData>;
|
||||
if (!extraDataItem.extraData?.ref) {
|
||||
act('buy', { path: item.id });
|
||||
} else {
|
||||
act('buy', { ref: extraDataItem.extraData.ref });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
|
||||
Reference in New Issue
Block a user