diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 145055206c..ea380cf49b 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -1,3 +1,6 @@
+#define TRAITOR_HUMAN /datum/traitor_class/human/freeform
+#define TRAITOR_AI /datum/traitor_class/ai
+
#define NUKE_RESULT_FLUKE 0
#define NUKE_RESULT_NUKE_WIN 1
#define NUKE_RESULT_CREW_WIN 2
diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm
index a8e03946b6..e73465d785 100644
--- a/code/datums/components/uplink.dm
+++ b/code/datums/components/uplink.dm
@@ -28,7 +28,7 @@ GLOBAL_LIST_EMPTY(uplinks)
var/compact_mode = FALSE
var/debug = FALSE
-/datum/component/uplink/Initialize(_owner, _lockable = TRUE, _enabled = FALSE, datum/game_mode/_gamemode, starting_tc = 20, datum/ui_state/_checkstate)
+/datum/component/uplink/Initialize(_owner, _lockable = TRUE, _enabled = FALSE, datum/game_mode/_gamemode, starting_tc = 20, datum/ui_state/_checkstate, datum/traitor_class/traitor_class)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
@@ -47,7 +47,11 @@ GLOBAL_LIST_EMPTY(uplinks)
RegisterSignal(parent, COMSIG_PEN_ROTATED, .proc/pen_rotation)
GLOB.uplinks += src
- uplink_items = get_uplink_items(gamemode, TRUE, allow_restricted)
+ var/list/filters = list()
+ if(istype(traitor_class))
+ filters = traitor_class.uplink_filters
+ starting_tc = traitor_class.TC
+ uplink_items = get_uplink_items(gamemode, TRUE, allow_restricted, filters)
if(_owner)
owner = _owner
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 29bb694a6f..edd5c25e2e 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -256,9 +256,11 @@
remove_rev()
SSticker.mode.update_cult_icons_removed(src)
-/datum/mind/proc/equip_traitor(employer = "The Syndicate", silent = FALSE, datum/antagonist/uplink_owner)
+/datum/mind/proc/equip_traitor(datum/traitor_class/traitor_class, silent = FALSE, datum/antagonist/uplink_owner)
if(!current)
return
+ if(!traitor_class)
+ traitor_class = GLOB.traitor_classes[TRAITOR_HUMAN]
var/mob/living/carbon/human/traitor_mob = current
if (!istype(traitor_mob))
return
@@ -306,21 +308,21 @@
if (!uplink_loc)
if(!silent)
- to_chat(traitor_mob, "Unfortunately, [employer] wasn't able to get you an Uplink.")
+ to_chat(traitor_mob, "Unfortunately, [traitor_class.employer] wasn't able to get you an Uplink.")
. = 0
else
. = uplink_loc
- var/datum/component/uplink/U = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key)
+ var/datum/component/uplink/U = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key,traitor_class)
if(!U)
CRASH("Uplink creation failed.")
U.setup_unlock_code()
if(!silent)
if(uplink_loc == R)
- to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(U.unlock_code)] to unlock its hidden features.")
+ to_chat(traitor_mob, "[traitor_class.employer] has cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(U.unlock_code)] to unlock its hidden features.")
else if(uplink_loc == PDA)
- to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [PDA.name]. Simply enter the code \"[U.unlock_code]\" into the ringtone select to unlock its hidden features.")
+ to_chat(traitor_mob, "[traitor_class.employer] has cunningly disguised a Syndicate Uplink as your [PDA.name]. Simply enter the code \"[U.unlock_code]\" into the ringtone select to unlock its hidden features.")
else if(uplink_loc == P)
- to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [P.name]. Simply twist the top of the pen [U.unlock_code] from its starting position to unlock its hidden features.")
+ to_chat(traitor_mob, "[traitor_class.employer] has cunningly disguised a Syndicate Uplink as your [P.name]. Simply twist the top of the pen [U.unlock_code] from its starting position to unlock its hidden features.")
if(uplink_owner)
uplink_owner.antag_memory += U.unlock_note + "
"
diff --git a/code/modules/antagonists/traitor/IAA/internal_affairs.dm b/code/modules/antagonists/traitor/IAA/internal_affairs.dm
index caf2f644df..19144d67c9 100644
--- a/code/modules/antagonists/traitor/IAA/internal_affairs.dm
+++ b/code/modules/antagonists/traitor/IAA/internal_affairs.dm
@@ -120,7 +120,7 @@
/datum/antagonist/traitor/internal_affairs/reinstate_escape_objective()
..()
- var/objtype = traitor_kind == TRAITOR_HUMAN ? /datum/objective/escape : /datum/objective/survive
+ var/objtype = !istype(traitor_kind,TRAITOR_AI) ? /datum/objective/escape : /datum/objective/survive
var/datum/objective/escape_objective = new objtype
escape_objective.owner = owner
add_objective(escape_objective)
@@ -215,20 +215,12 @@
kill_objective.target = target_mind
kill_objective.update_explanation_text()
add_objective(kill_objective)
-
- //Optional traitor objective
- if(prob(PROB_ACTUAL_TRAITOR))
- employer = "The Syndicate"
- owner.special_role = TRAITOR_AGENT_ROLE
- special_role = TRAITOR_AGENT_ROLE
- syndicate = TRUE
- forge_single_objective()
return
/datum/antagonist/traitor/internal_affairs/forge_traitor_objectives()
forge_iaa_objectives()
- var/objtype = traitor_kind == TRAITOR_HUMAN ? /datum/objective/escape : /datum/objective/survive
+ var/objtype = !istype(traitor_kind,TRAITOR_AI) ? /datum/objective/escape : /datum/objective/survive
var/datum/objective/escape_objective = new objtype
escape_objective.owner = owner
add_objective(escape_objective)
diff --git a/code/modules/antagonists/traitor/classes/ai.dm b/code/modules/antagonists/traitor/classes/ai.dm
new file mode 100644
index 0000000000..fac1efeb49
--- /dev/null
+++ b/code/modules/antagonists/traitor/classes/ai.dm
@@ -0,0 +1,68 @@
+/datum/traitor_class/ai // this one is special, so has no weight
+ name = "Malfunctioning AI"
+
+/datum/traitor_class/ai/forge_objectives(datum/antagonist/traitor/T)
+ var/objective_count = 0
+
+ if(prob(30))
+ objective_count += forge_single_objective()
+
+ for(var/i = objective_count, i < CONFIG_GET(number/traitor_objectives_amount), i++)
+ var/datum/objective/assassinate/kill_objective = new
+ kill_objective.owner = T.owner
+ kill_objective.find_target()
+ T.add_objective(kill_objective)
+
+ var/datum/objective/survive/exist/exist_objective = new
+ exist_objective.owner = T.owner
+ T.add_objective(exist_objective)
+
+/datum/traitor_class/ai/forge_single_objective(datum/antagonist/traitor/T)
+ .=1
+ var/special_pick = rand(1,4)
+ switch(special_pick)
+ if(1)
+ var/datum/objective/block/block_objective = new
+ block_objective.owner = T.owner
+ T.add_objective(block_objective)
+ if(2)
+ var/datum/objective/purge/purge_objective = new
+ purge_objective.owner = T.owner
+ T.add_objective(purge_objective)
+ if(3)
+ var/datum/objective/robot_army/robot_objective = new
+ robot_objective.owner = T.owner
+ T.add_objective(robot_objective)
+ if(4) //Protect and strand a target
+ var/datum/objective/protect/yandere_one = new
+ yandere_one.owner = T.owner
+ T.add_objective(yandere_one)
+ yandere_one.find_target()
+ var/datum/objective/maroon/yandere_two = new
+ yandere_two.owner = T.owner
+ yandere_two.target = yandere_one.target
+ yandere_two.update_explanation_text() // normally called in find_target()
+ T.add_objective(yandere_two)
+ .=2
+
+/datum/traitor_class/ai/on_removal(datum/antagonist/traitor/T)
+ var/mob/living/silicon/ai/A = T.owner.current
+ A.set_zeroth_law("")
+ A.verbs -= /mob/living/silicon/ai/proc/choose_modules
+ A.malf_picker.remove_malf_verbs(A)
+ qdel(A.malf_picker)
+
+
+/datum/traitor_class/ai/apply_innate_effects(mob/living/M)
+ var/mob/living/silicon/ai/A = M
+ A.hack_software = TRUE
+
+/datum/traitor_class/ai/remove_innate_effects(mob/living/M)
+ var/mob/living/silicon/ai/A = M
+ A.hack_software = FALSE
+
+/datum/traitor_class/ai/finalize_traitor(datum/antagonist/traitor/T)
+ T.add_law_zero()
+ T.owner.current.playsound_local(get_turf(T.owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE)
+ T.owner.current.grant_language(/datum/language/codespeak)
+ return FALSE
diff --git a/code/modules/antagonists/traitor/classes/assassin.dm b/code/modules/antagonists/traitor/classes/assassin.dm
new file mode 100644
index 0000000000..0c1d950f57
--- /dev/null
+++ b/code/modules/antagonists/traitor/classes/assassin.dm
@@ -0,0 +1,37 @@
+/datum/traitor_class/human/assassin
+ name = "Donk Co Operative"
+ employer = "Donk Corporation"
+ weight = 0
+ chaos = 1
+ cost = 2
+
+/datum/traitor_class/human/assassin/forge_single_objective(datum/antagonist/traitor/T)
+ .=1
+ var/permakill_prob = 20
+ var/is_dynamic = FALSE
+ var/datum/game_mode/dynamic/mode
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ mode = SSticker.mode
+ is_dynamic = TRUE
+ permakill_prob = max(0,mode.threat_level-50)
+ var/list/active_ais = active_ais()
+ if(active_ais.len && prob(100/GLOB.joined_player_list.len))
+ var/datum/objective/destroy/destroy_objective = new
+ destroy_objective.owner = T.owner
+ destroy_objective.find_target()
+ T.add_objective(destroy_objective)
+ else if(prob(30) || (is_dynamic && (mode.storyteller.flags & NO_ASSASSIN)))
+ var/datum/objective/maroon/maroon_objective = new
+ maroon_objective.owner = T.owner
+ maroon_objective.find_target()
+ T.add_objective(maroon_objective)
+ else if(prob(permakill_prob))
+ var/datum/objective/assassinate/kill_objective = new
+ kill_objective.owner = T.owner
+ kill_objective.find_target()
+ T.add_objective(kill_objective)
+ else
+ var/datum/objective/assassinate/once/kill_objective = new
+ kill_objective.owner = T.owner
+ kill_objective.find_target()
+ T.add_objective(kill_objective)
diff --git a/code/modules/antagonists/traitor/classes/freeform.dm b/code/modules/antagonists/traitor/classes/freeform.dm
new file mode 100644
index 0000000000..f84eadb9f6
--- /dev/null
+++ b/code/modules/antagonists/traitor/classes/freeform.dm
@@ -0,0 +1,12 @@
+/datum/traitor_class/human/freeform
+ name = "Waffle Co Agent"
+ employer = "Waffle Company"
+ weight = 16
+ chaos = 0
+
+/datum/traitor_class/human/freeform/forge_objectives(datum/antagonist/traitor/T)
+ var/datum/objective/escape/O = new
+ O.explanation_text = "You have no goals! Whatever you can do do antagonize Nanotrasen, do it! The gimmickier, the better! Make sure to escape alive, though!"
+ O.owner = T.owner
+ T.add_objective(O)
+ return
diff --git a/code/modules/antagonists/traitor/classes/hijack.dm b/code/modules/antagonists/traitor/classes/hijack.dm
new file mode 100644
index 0000000000..e89eda1dcf
--- /dev/null
+++ b/code/modules/antagonists/traitor/classes/hijack.dm
@@ -0,0 +1,18 @@
+/datum/traitor_class/human/hijack
+ name = "Gorlex Marauder"
+ employer = "The Gorlex Marauders"
+ weight = 3
+ chaos = 5
+ cost = 5
+ uplink_filters = list(/datum/uplink_item/stealthy_weapons/romerol_kit)
+
+/datum/traitor_class/human/hijack/forge_objectives(datum/antagonist/traitor/T)
+ var/datum/objective/hijack/O = new
+ O.explanation_text = "The Gorlex Marauders are letting you do what you want, with one condition: the shuttle must be hijacked by hacking its navigational protocols through the control console (alt click emergency shuttle console)."
+ O.owner = T.owner
+ T.add_objective(O)
+ return
+
+/datum/traitor_class/human/hijack/finalize_traitor(datum/antagonist/traitor/T)
+ T.hijack_speed=1
+ return TRUE
diff --git a/code/modules/antagonists/traitor/classes/human.dm b/code/modules/antagonists/traitor/classes/human.dm
new file mode 100644
index 0000000000..4e096390c0
--- /dev/null
+++ b/code/modules/antagonists/traitor/classes/human.dm
@@ -0,0 +1,82 @@
+/datum/traitor_class/human
+ name = "Syndicate Agent"
+ chaos = 0
+
+/datum/traitor_class/human/forge_objectives(datum/antagonist/traitor/T)
+ var/objective_count = 0 //Hijacking counts towards number of objectives
+ if(!SSticker.mode.exchange_blue && SSticker.mode.traitors.len >= 8) //Set up an exchange if there are enough traitors
+ if(!SSticker.mode.exchange_red)
+ SSticker.mode.exchange_red = T.owner
+ else
+ SSticker.mode.exchange_blue = T.owner
+ T.assign_exchange_role(SSticker.mode.exchange_red)
+ T.assign_exchange_role(SSticker.mode.exchange_blue)
+ objective_count += 1 //Exchange counts towards number of objectives
+ var/toa = CONFIG_GET(number/traitor_objectives_amount)
+ for(var/i = objective_count, i < toa, i++)
+ forge_single_objective(T)
+ if(!(locate(/datum/objective/escape) in T.objectives))
+ var/datum/objective/escape/escape_objective = new
+ escape_objective.owner = T.owner
+ T.add_objective(escape_objective)
+ return
+
+/datum/traitor_class/human/forge_single_objective(datum/antagonist/traitor/T)
+ .=1
+ var/assassin_prob = 50
+ var/is_dynamic = FALSE
+ var/datum/game_mode/dynamic/mode
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ mode = SSticker.mode
+ is_dynamic = TRUE
+ assassin_prob = max(0,mode.threat_level-20)
+ if(prob(assassin_prob))
+ if(is_dynamic)
+ var/threat_spent = CONFIG_GET(number/dynamic_assassinate_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("[T.owner.name] spent [threat_spent] on an assassination target.")
+ var/list/active_ais = active_ais()
+ if(active_ais.len && prob(100/GLOB.joined_player_list.len))
+ var/datum/objective/destroy/destroy_objective = new
+ destroy_objective.owner = T.owner
+ destroy_objective.find_target()
+ T.add_objective(destroy_objective)
+ else if(prob(30) || (is_dynamic && (mode.storyteller.flags & NO_ASSASSIN)))
+ var/datum/objective/maroon/maroon_objective = new
+ maroon_objective.owner = T.owner
+ maroon_objective.find_target()
+ T.add_objective(maroon_objective)
+ else if(prob(max(0,assassin_prob-20)))
+ var/datum/objective/assassinate/kill_objective = new
+ kill_objective.owner = T.owner
+ kill_objective.find_target()
+ T.add_objective(kill_objective)
+ else
+ var/datum/objective/assassinate/once/kill_objective = new
+ kill_objective.owner = T.owner
+ kill_objective.find_target()
+ T.add_objective(kill_objective)
+ else
+ if(prob(15) && !(locate(/datum/objective/download) in T.objectives) && !(T.owner.assigned_role in list("Research Director", "Scientist", "Roboticist")))
+ var/datum/objective/download/download_objective = new
+ download_objective.owner = T.owner
+ download_objective.gen_amount_goal()
+ T.add_objective(download_objective)
+ else if(prob(40)) // cum. not counting download: 40%.
+ var/datum/objective/steal/steal_objective = new
+ steal_objective.owner = T.owner
+ steal_objective.find_target()
+ T.add_objective(steal_objective)
+ else if(prob(100/3)) // cum. not counting download: 20%.
+ var/datum/objective/sabotage/sabotage_objective = new
+ sabotage_objective.owner = T.owner
+ sabotage_objective.find_target()
+ T.add_objective(sabotage_objective)
+ else // cum. not counting download: 40%
+ var/datum/objective/flavor/traitor/flavor_objective = new
+ flavor_objective.owner = T.owner
+ flavor_objective.forge_objective()
+ T.add_objective(flavor_objective)
+
+/datum/traitor_class/human/greet(datum/antagonist/traitor/T)
+ to_chat(T.owner.current, "You are under contract with [employer]. They have given you your objectives.")
diff --git a/code/modules/antagonists/traitor/classes/martyr.dm b/code/modules/antagonists/traitor/classes/martyr.dm
new file mode 100644
index 0000000000..78f8bf9b0c
--- /dev/null
+++ b/code/modules/antagonists/traitor/classes/martyr.dm
@@ -0,0 +1,14 @@
+/datum/traitor_class/human/martyr
+ name = "Tiger Cooperator"
+ employer = "The Tiger Cooperative"
+ weight = 2
+ chaos = 5
+ cost = 5
+ uplink_filters = list(/datum/uplink_item/stealthy_weapons/romerol_kit,/datum/uplink_item/bundles_TC/contract_kit)
+
+/datum/traitor_class/human/martyr/forge_objectives(datum/antagonist/traitor/T)
+ var/datum/objective/martyr/O = new
+ O.explanation_text = "The tiger cooperative have given you free reign. You may do as you wish, as long as you die a glorious death!"
+ O.owner = T.owner
+ T.add_objective(O)
+ return
diff --git a/code/modules/antagonists/traitor/classes/subterfuge.dm b/code/modules/antagonists/traitor/classes/subterfuge.dm
new file mode 100644
index 0000000000..ae43f11fe7
--- /dev/null
+++ b/code/modules/antagonists/traitor/classes/subterfuge.dm
@@ -0,0 +1,40 @@
+/datum/traitor_class/human/subterfuge
+ name = "MI13 Operative"
+ employer = "MI13"
+ weight = 20
+ chaos = -5
+
+/datum/traitor_class/human/subterfuge/forge_single_objective(datum/antagonist/traitor/T)
+ .=1
+ var/assassin_prob = 30
+ var/datum/game_mode/dynamic/mode
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ mode = SSticker.mode
+ assassin_prob = max(0,mode.threat_level-40)
+ if(prob(assassin_prob))
+ if(prob(assassin_prob))
+ var/datum/objective/assassinate/once/kill_objective = new
+ kill_objective.owner = T.owner
+ kill_objective.find_target()
+ T.add_objective(kill_objective)
+ else
+ var/datum/objective/maroon/maroon_objective = new
+ maroon_objective.owner = T.owner
+ maroon_objective.find_target()
+ T.add_objective(maroon_objective)
+ else
+ if(prob(15) && !(locate(/datum/objective/download) in T.objectives) && !(T.owner.assigned_role in list("Research Director", "Scientist", "Roboticist")))
+ var/datum/objective/download/download_objective = new
+ download_objective.owner = T.owner
+ download_objective.gen_amount_goal()
+ T.add_objective(download_objective)
+ else if(prob(70)) // cum. not counting download: 40%.
+ var/datum/objective/steal/steal_objective = new
+ steal_objective.owner = T.owner
+ steal_objective.find_target()
+ T.add_objective(steal_objective)
+ else
+ var/datum/objective/sabotage/sabotage_objective = new
+ sabotage_objective.owner = T.owner
+ sabotage_objective.find_target()
+ T.add_objective(sabotage_objective)
diff --git a/code/modules/antagonists/traitor/classes/traitor_class.dm b/code/modules/antagonists/traitor/classes/traitor_class.dm
new file mode 100644
index 0000000000..3df9dec929
--- /dev/null
+++ b/code/modules/antagonists/traitor/classes/traitor_class.dm
@@ -0,0 +1,40 @@
+GLOBAL_LIST_EMPTY(traitor_classes)
+
+/datum/traitor_class
+ var/name = "Bad Coders Ltd."
+ var/employer = "The Syndicate"
+ var/weight = 0
+ var/chaos = 0
+ var/cost = 0
+ var/TC = 20
+ var/list/uplink_filters
+
+/datum/traitor_class/New()
+ ..()
+ if(src.type in GLOB.traitor_classes)
+ qdel(src)
+ else
+ GLOB.traitor_classes += src.type
+ GLOB.traitor_classes[src.type] = src
+
+/datum/traitor_class/proc/forge_objectives(datum/antagonist/traitor/T)
+ // Like the old forge_human_objectives. Makes all the objectives for this traitor class.
+
+/datum/traitor_class/proc/forge_single_objective(datum/antagonist/traitor/T)
+ // As forge_single_objective.
+
+/datum/traitor_class/proc/on_removal(datum/antagonist/traitor/T)
+ // What this does to the antag datum on removal. Called before proper removal, obviously.
+
+/datum/traitor_class/proc/apply_innate_effects(mob/living/M)
+ // What innate effects it should have. See: AI.
+
+/datum/traitor_class/proc/remove_innate_effects(mob/living/M)
+ // Cleaning up the innate effects.
+
+/datum/traitor_class/proc/greet(datum/antagonist/traitor/T)
+ // Message upon creation. Not necessary, but can be useful.
+
+/datum/traitor_class/proc/finalize_traitor(datum/antagonist/traitor/T)
+ // Finalization. Return TRUE if should play standard traitor sound/equip, return FALSE if both are special case
+ return TRUE
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 0352272f70..a9dee81a6c 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -1,6 +1,3 @@
-#define TRAITOR_HUMAN "human"
-#define TRAITOR_AI "AI"
-
/datum/antagonist/traitor
name = "Traitor"
roundend_category = "traitors"
@@ -12,43 +9,52 @@
var/give_objectives = TRUE
var/should_give_codewords = TRUE
var/should_equip = TRUE
- var/traitor_kind = TRAITOR_HUMAN //Set on initial assignment
+ var/datum/traitor_class/traitor_kind
var/datum/contractor_hub/contractor_hub
hijack_speed = 0.5 //10 seconds per hijack stage by default
+/datum/antagonist/traitor/New()
+ ..()
+ if(!GLOB.traitor_classes.len)//Only need to fill the list when it's needed.
+ for(var/I in subtypesof(/datum/traitor_class))
+ new I
+
+/datum/antagonist/traitor/proc/set_traitor_kind(var/kind)
+ traitor_kind = GLOB.traitor_classes[kind]
+ if(istype(SSticker.mode, /datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(traitor_kind.cost)
+ mode.spend_threat(traitor_kind.cost)
+ mode.log_threat("[traitor_kind.cost] was spent due to [owner.name] being a [traitor_kind.name].")
+
/datum/antagonist/traitor/on_gain()
if(owner.current && isAI(owner.current))
- traitor_kind = TRAITOR_AI
-
+ set_traitor_kind(TRAITOR_AI)
+ else
+ var/chaos_weight = 0
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ chaos_weight = (mode.threat - 50)/50
+ var/list/weights = list()
+ for(var/C in GLOB.traitor_classes)
+ var/datum/traitor_class/class = GLOB.traitor_classes[C]
+ var/weight = (1.5*class.weight)/(0.5+NUM_E**(-chaos_weight*class.chaos)) // just a logistic function
+ weights[C] = weight
+ var/choice = pickweightAllowZero(weights)
+ if(!choice)
+ choice = GLOB.traitor_classes[TRAITOR_HUMAN]
+ set_traitor_kind(pickweightAllowZero(weights))
+ traitor_kind.weight *= 0.8 // less likely this round
SSticker.mode.traitors += owner
owner.special_role = special_role
if(give_objectives)
- forge_traitor_objectives()
+ traitor_kind.forge_objectives(src)
finalize_traitor()
..()
-/datum/antagonist/traitor/apply_innate_effects()
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/traitor_mob = owner.current
- if(traitor_mob && istype(traitor_mob))
- if(!silent)
- to_chat(traitor_mob, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
- traitor_mob.dna.remove_mutation(CLOWNMUT)
-
-/datum/antagonist/traitor/remove_innate_effects()
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/traitor_mob = owner.current
- if(traitor_mob && istype(traitor_mob))
- traitor_mob.dna.add_mutation(CLOWNMUT)
-
/datum/antagonist/traitor/on_removal()
//Remove malf powers.
- if(traitor_kind == TRAITOR_AI && owner.current && isAI(owner.current))
- var/mob/living/silicon/ai/A = owner.current
- A.set_zeroth_law("")
- A.verbs -= /mob/living/silicon/ai/proc/choose_modules
- A.malf_picker.remove_malf_verbs(A)
- qdel(A.malf_picker)
+ traitor_kind.on_removal(src)
SSticker.mode.traitors -= owner
if(!silent && owner.current)
to_chat(owner.current," You are no longer the [special_role]! ")
@@ -69,192 +75,11 @@
objectives -= O
/datum/antagonist/traitor/proc/forge_traitor_objectives()
- switch(traitor_kind)
- if(TRAITOR_AI)
- forge_ai_objectives()
- else
- forge_human_objectives()
-
-/datum/antagonist/traitor/proc/forge_human_objectives()
- var/is_hijacker = FALSE
- var/datum/game_mode/dynamic/mode
- var/is_dynamic = FALSE
- var/hijack_prob = 0
- if(istype(SSticker.mode,/datum/game_mode/dynamic))
- mode = SSticker.mode
- is_dynamic = TRUE
- if(mode.threat >= CONFIG_GET(number/dynamic_hijack_cost))
- hijack_prob = CLAMP(mode.threat_level-50,0,20)
- if(GLOB.joined_player_list.len>=GLOB.dynamic_high_pop_limit)
- is_hijacker = (prob(hijack_prob) && mode.threat_level > CONFIG_GET(number/dynamic_hijack_high_population_requirement))
- else
- var/indice_pop = min(10,round(GLOB.joined_player_list.len/mode.pop_per_requirement)+1)
- is_hijacker = (prob(hijack_prob) && (mode.threat_level >= CONFIG_GET(number_list/dynamic_hijack_requirements)[indice_pop]))
- if(mode.storyteller.flags & NO_ASSASSIN)
- is_hijacker = FALSE
- else if (GLOB.joined_player_list.len >= 30) // Less murderboning on lowpop thanks
- hijack_prob = 10
- is_hijacker = prob(10)
- var/martyr_chance = prob(hijack_prob*2)
- var/objective_count = is_hijacker //Hijacking counts towards number of objectives
- if(!SSticker.mode.exchange_blue && SSticker.mode.traitors.len >= 8) //Set up an exchange if there are enough traitors
- if(!SSticker.mode.exchange_red)
- SSticker.mode.exchange_red = owner
- else
- SSticker.mode.exchange_blue = owner
- assign_exchange_role(SSticker.mode.exchange_red)
- assign_exchange_role(SSticker.mode.exchange_blue)
- objective_count += 1 //Exchange counts towards number of objectives
- var/toa = CONFIG_GET(number/traitor_objectives_amount)
- for(var/i = objective_count, i < toa, i++)
- forge_single_objective()
-
- if(is_hijacker && objective_count <= toa) //Don't assign hijack if it would exceed the number of objectives set in config.traitor_objectives_amount
- if (!(locate(/datum/objective/hijack) in objectives))
- var/datum/objective/hijack/hijack_objective = new
- hijack_objective.owner = owner
- add_objective(hijack_objective)
- if(is_dynamic)
- var/threat_spent = CONFIG_GET(number/dynamic_hijack_cost)
- mode.spend_threat(threat_spent)
- mode.log_threat("[owner.name] spent [threat_spent] on hijack.")
- return
-
-
- var/martyr_compatibility = 1 //You can't succeed in stealing if you're dead.
- for(var/datum/objective/O in objectives)
- if(!O.martyr_compatible)
- martyr_compatibility = 0
- break
-
- if(martyr_compatibility && martyr_chance)
- var/datum/objective/martyr/martyr_objective = new
- martyr_objective.owner = owner
- add_objective(martyr_objective)
- if(is_dynamic)
- var/threat_spent = CONFIG_GET(number/dynamic_hijack_cost)
- mode.spend_threat(threat_spent)
- mode.log_threat("[owner.name] spent [threat_spent] on glorious death.")
- return
-
- else
- if(!(locate(/datum/objective/escape) in objectives))
- var/datum/objective/escape/escape_objective = new
- escape_objective.owner = owner
- add_objective(escape_objective)
- return
-
-/datum/antagonist/traitor/proc/forge_ai_objectives()
- var/objective_count = 0
-
- if(prob(30))
- objective_count += forge_single_objective()
-
- for(var/i = objective_count, i < CONFIG_GET(number/traitor_objectives_amount), i++)
- var/datum/objective/assassinate/kill_objective = new
- kill_objective.owner = owner
- kill_objective.find_target()
- add_objective(kill_objective)
-
- var/datum/objective/survive/exist/exist_objective = new
- exist_objective.owner = owner
- add_objective(exist_objective)
-
-
-/datum/antagonist/traitor/proc/forge_single_objective()
- switch(traitor_kind)
- if(TRAITOR_AI)
- return forge_single_AI_objective()
- else
- return forge_single_human_objective()
-
-/datum/antagonist/traitor/proc/forge_single_human_objective() //Returns how many objectives are added
- .=1
- var/assassin_prob = 50
- var/is_dynamic = FALSE
- var/datum/game_mode/dynamic/mode
- if(istype(SSticker.mode,/datum/game_mode/dynamic))
- mode = SSticker.mode
- is_dynamic = TRUE
- assassin_prob = max(0,mode.threat_level-20)
- if(prob(assassin_prob))
- if(is_dynamic)
- var/threat_spent = CONFIG_GET(number/dynamic_assassinate_cost)
- mode.spend_threat(threat_spent)
- mode.log_threat("[owner.name] spent [threat_spent] on an assassination target.")
- var/list/active_ais = active_ais()
- if(active_ais.len && prob(100/GLOB.joined_player_list.len))
- var/datum/objective/destroy/destroy_objective = new
- destroy_objective.owner = owner
- destroy_objective.find_target()
- add_objective(destroy_objective)
- else if(prob(30) || (is_dynamic && (mode.storyteller.flags & NO_ASSASSIN)))
- var/datum/objective/maroon/maroon_objective = new
- maroon_objective.owner = owner
- maroon_objective.find_target()
- add_objective(maroon_objective)
- else if(prob(max(0,assassin_prob-20)))
- var/datum/objective/assassinate/kill_objective = new
- kill_objective.owner = owner
- kill_objective.find_target()
- add_objective(kill_objective)
- else
- var/datum/objective/assassinate/once/kill_objective = new
- kill_objective.owner = owner
- kill_objective.find_target()
- add_objective(kill_objective)
- else
- if(prob(15) && !(locate(/datum/objective/download) in objectives) && !(owner.assigned_role in list("Research Director", "Scientist", "Roboticist")))
- var/datum/objective/download/download_objective = new
- download_objective.owner = owner
- download_objective.gen_amount_goal()
- add_objective(download_objective)
- else if(prob(40)) // cum. not counting download: 40%.
- var/datum/objective/steal/steal_objective = new
- steal_objective.owner = owner
- steal_objective.find_target()
- add_objective(steal_objective)
- else if(prob(100/3)) // cum. not counting download: 20%.
- var/datum/objective/sabotage/sabotage_objective = new
- sabotage_objective.owner = owner
- sabotage_objective.find_target()
- add_objective(sabotage_objective)
- else // cum. not counting download: 40%
- var/datum/objective/flavor/traitor/flavor_objective = new
- flavor_objective.owner = owner
- flavor_objective.forge_objective()
- add_objective(flavor_objective)
-
-/datum/antagonist/traitor/proc/forge_single_AI_objective()
- .=1
- var/special_pick = rand(1,4)
- switch(special_pick)
- if(1)
- var/datum/objective/block/block_objective = new
- block_objective.owner = owner
- add_objective(block_objective)
- if(2)
- var/datum/objective/purge/purge_objective = new
- purge_objective.owner = owner
- add_objective(purge_objective)
- if(3)
- var/datum/objective/robot_army/robot_objective = new
- robot_objective.owner = owner
- add_objective(robot_objective)
- if(4) //Protect and strand a target
- var/datum/objective/protect/yandere_one = new
- yandere_one.owner = owner
- add_objective(yandere_one)
- yandere_one.find_target()
- var/datum/objective/maroon/yandere_two = new
- yandere_two.owner = owner
- yandere_two.target = yandere_one.target
- yandere_two.update_explanation_text() // normally called in find_target()
- add_objective(yandere_two)
- .=2
+ traitor_kind.forge_objectives(src)
/datum/antagonist/traitor/greet()
to_chat(owner.current, "You are the [owner.special_role].")
+ traitor_kind.greet(src)
owner.announce_objectives()
if(should_give_codewords)
give_codewords()
@@ -270,32 +95,33 @@
set_antag_hud(owner.current, null)
/datum/antagonist/traitor/proc/finalize_traitor()
- switch(traitor_kind)
- if(TRAITOR_AI)
- add_law_zero()
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE)
- owner.current.grant_language(/datum/language/codespeak)
- if(TRAITOR_HUMAN)
- if(should_equip)
- equip(silent)
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE)
+ if(traitor_kind.finalize_traitor(src))
+ if(should_equip)
+ equip(silent)
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE)
/datum/antagonist/traitor/apply_innate_effects(mob/living/mob_override)
. = ..()
update_traitor_icons_added()
var/mob/M = mob_override || owner.current
- if(isAI(M) && traitor_kind == TRAITOR_AI)
- var/mob/living/silicon/ai/A = M
- A.hack_software = TRUE
+ traitor_kind.apply_innate_effects(M)
+ if(owner.assigned_role == "Clown")
+ var/mob/living/carbon/human/H = M
+ if(istype(H))
+ if(!silent)
+ to_chat(H, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
+ H.dna.remove_mutation(CLOWNMUT)
RegisterSignal(M, COMSIG_MOVABLE_HEAR, .proc/handle_hearing)
/datum/antagonist/traitor/remove_innate_effects(mob/living/mob_override)
. = ..()
update_traitor_icons_removed()
var/mob/M = mob_override || owner.current
- if(isAI(M) && traitor_kind == TRAITOR_AI)
- var/mob/living/silicon/ai/A = M
- A.hack_software = FALSE
+ traitor_kind.remove_innate_effects(M)
+ if(owner.assigned_role == "Clown")
+ var/mob/living/carbon/human/H = M
+ if(istype(H))
+ H.dna.add_mutation(CLOWNMUT)
UnregisterSignal(M, COMSIG_MOVABLE_HEAR)
/datum/antagonist/traitor/proc/give_codewords()
@@ -326,8 +152,7 @@
killer.add_malf_picker()
/datum/antagonist/traitor/proc/equip(var/silent = FALSE)
- if(traitor_kind == TRAITOR_HUMAN)
- owner.equip_traitor(employer, silent, src)
+ owner.equip_traitor(traitor_kind, silent, src)
/datum/antagonist/traitor/proc/assign_exchange_role()
//set faction
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 52f6efd3df..bbfa186a48 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -1,4 +1,4 @@
-/proc/get_uplink_items(datum/game_mode/gamemode, allow_sales = TRUE, allow_restricted = TRUE)
+/proc/get_uplink_items(datum/game_mode/gamemode, allow_sales = TRUE, allow_restricted = TRUE, other_filter = list())
var/list/filtered_uplink_items = GLOB.uplink_categories.Copy() // list of uplink categories without associated values.
var/list/sale_items = list()
@@ -18,7 +18,8 @@
continue
if (I.restricted && !allow_restricted)
continue
-
+ if (I.type in other_filter)
+ continue
LAZYSET(filtered_uplink_items[I.category], I.name, I)
if(I.limited_stock < 0 && !I.cant_discount && I.item && I.cost > 1)
diff --git a/tgstation.dme b/tgstation.dme
old mode 100755
new mode 100644
index 21f9f98d65..06c841e021
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1498,6 +1498,14 @@
#include "code\modules\antagonists\swarmer\swarmer.dm"
#include "code\modules\antagonists\swarmer\swarmer_event.dm"
#include "code\modules\antagonists\traitor\datum_traitor.dm"
+#include "code\modules\antagonists\traitor\classes\ai.dm"
+#include "code\modules\antagonists\traitor\classes\assassin.dm"
+#include "code\modules\antagonists\traitor\classes\freeform.dm"
+#include "code\modules\antagonists\traitor\classes\hijack.dm"
+#include "code\modules\antagonists\traitor\classes\human.dm"
+#include "code\modules\antagonists\traitor\classes\martyr.dm"
+#include "code\modules\antagonists\traitor\classes\subterfuge.dm"
+#include "code\modules\antagonists\traitor\classes\traitor_class.dm"
#include "code\modules\antagonists\traitor\syndicate_contract.dm"
#include "code\modules\antagonists\traitor\equipment\contractor.dm"
#include "code\modules\antagonists\traitor\equipment\Malf_Modules.dm"