diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index 84d0ac1307..20695d6534 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -372,3 +372,8 @@ var/global/list/##LIST_NAME = list();\
// Used by radios to indicate that they have sent a message via something other than subspace
#define RADIO_CONNECTION_FAIL 0
#define RADIO_CONNECTION_NON_SUBSPACE 1
+
+#define JOB_CARBON 0x1
+#define JOB_SILICON_ROBOT 0x2
+#define JOB_SILICON_AI 0x4
+#define JOB_SILICON 0x6 // 2|4, probably don't set jobs to this, but good for checking
diff --git a/code/controllers/subsystems/ticker.dm b/code/controllers/subsystems/ticker.dm
index b17640430b..0fd2d4398c 100644
--- a/code/controllers/subsystems/ticker.dm
+++ b/code/controllers/subsystems/ticker.dm
@@ -404,16 +404,25 @@ var/global/datum/controller/subsystem/ticker/ticker
/datum/controller/subsystem/ticker/proc/create_characters()
for(var/mob/new_player/player in player_list)
- if(player && player.ready && player.mind)
- if(player.mind.assigned_role=="AI")
+ if(player && player.ready && player.mind?.assigned_role)
+ var/datum/job/J = SSjob.get_job(player.mind.assigned_role)
+
+ // Snowflakey AI treatment
+ if(J.mob_type & JOB_SILICON_AI)
player.close_spawn_windows()
- player.AIize()
- else if(!player.mind.assigned_role)
+ player.AIize(move = TRUE)
continue
- else
- player.create_character()
+
+ // Ask their new_player mob to spawn them
+ var/mob/living/carbon/human/new_char = player.create_character()
+
+ // Created their playable character, delete their /mob/new_player
+ if(new_char)
qdel(player)
+ // If they're a carbon, they can get manifested
+ if(J.mob_type & JOB_CARBON)
+ data_core.manifest_inject(new_char)
/datum/controller/subsystem/ticker/proc/collect_minds()
for(var/mob/living/player in player_list)
diff --git a/code/game/antagonist/antagonist.dm b/code/game/antagonist/antagonist.dm
index 962c91a621..e00a4a99df 100644
--- a/code/game/antagonist/antagonist.dm
+++ b/code/game/antagonist/antagonist.dm
@@ -4,6 +4,7 @@
var/list/restricted_jobs = list() // Jobs that cannot be this antagonist (depending on config)
var/list/protected_jobs = list() // As above.
var/list/roundstart_restricted = list() //Jobs that can be this antag, but not at roundstart
+ var/avoid_silicons = FALSE // If we won't hand this antag role to silicons (AI, borg, etc)
// Strings.
var/welcome_text = "Cry havoc and let slip the dogs of war!"
diff --git a/code/game/antagonist/antagonist_helpers.dm b/code/game/antagonist/antagonist_helpers.dm
index c8f2928ced..10f7a75b15 100644
--- a/code/game/antagonist/antagonist_helpers.dm
+++ b/code/game/antagonist/antagonist_helpers.dm
@@ -10,6 +10,10 @@
return FALSE
if(config.protect_roles_from_antagonist && (player.assigned_role in protected_jobs))
return FALSE
+ if(avoid_silicons)
+ var/datum/job/J = SSjob.get_job(player.assigned_role)
+ if(J.mob_type & JOB_SILICON)
+ return FALSE
return TRUE
/datum/antagonist/proc/antags_are_dead()
diff --git a/code/game/antagonist/station/changeling.dm b/code/game/antagonist/station/changeling.dm
index 860e73a052..694c4a3634 100644
--- a/code/game/antagonist/station/changeling.dm
+++ b/code/game/antagonist/station/changeling.dm
@@ -5,7 +5,7 @@
role_text_plural = "Changelings"
bantype = "changeling"
feedback_tag = "changeling_objective"
- restricted_jobs = list("AI", "Cyborg")
+ avoid_silicons = TRUE
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Colony Director")
welcome_text = "Use say \"#g message\" to communicate with your fellow changelings. Remember: you get all of their absorbed DNA if you absorb them."
antag_sound = 'sound/effects/antag_notice/ling_alert.ogg'
diff --git a/code/game/antagonist/station/cultist.dm b/code/game/antagonist/station/cultist.dm
index dc98622ba7..06aab654b4 100644
--- a/code/game/antagonist/station/cultist.dm
+++ b/code/game/antagonist/station/cultist.dm
@@ -11,7 +11,8 @@ var/datum/antagonist/cultist/cult
role_text = "Cultist"
role_text_plural = "Cultists"
bantype = "cultist"
- restricted_jobs = list("Chaplain","AI", "Cyborg")
+ restricted_jobs = list("Chaplain")
+ avoid_silicons = TRUE
protected_jobs = list("Security Officer", "Warden", "Detective", "Internal Affairs Agent", "Head of Security", "Colony Director")
roundstart_restricted = list("Internal Affairs Agent", "Head of Security", "Colony Director")
role_type = BE_CULTIST
diff --git a/code/game/antagonist/station/loyalist.dm b/code/game/antagonist/station/loyalist.dm
index 9b2333b491..ec27584426 100644
--- a/code/game/antagonist/station/loyalist.dm
+++ b/code/game/antagonist/station/loyalist.dm
@@ -29,7 +29,7 @@ var/datum/antagonist/loyalists/loyalists
faction_welcome = "Preserve NanoTrasen's interests against the traitorous recidivists amongst the crew. Protect the heads of staff with your life."
faction_indicator = "loyal"
faction_invisible = 1
- restricted_jobs = list("AI", "Cyborg")
+ avoid_silicons = TRUE
/datum/antagonist/loyalists/New()
..()
diff --git a/code/game/antagonist/station/renegade.dm b/code/game/antagonist/station/renegade.dm
index ed3c804506..cdd4efb8f3 100644
--- a/code/game/antagonist/station/renegade.dm
+++ b/code/game/antagonist/station/renegade.dm
@@ -6,7 +6,7 @@ var/datum/antagonist/renegade/renegades
role_text = "Renegade"
role_text_plural = "Renegades"
bantype = "renegade"
- restricted_jobs = list("AI", "Cyborg")
+ avoid_silicons = TRUE
welcome_text = "Something's going to go wrong today, you can just feel it. You're paranoid, you've got a gun, and you're going to survive."
antag_sound = 'sound/effects/antag_notice/general_goodie_alert.ogg'
antag_text = "You are a minor antagonist! Within the rules, \
diff --git a/code/game/antagonist/station/revolutionary.dm b/code/game/antagonist/station/revolutionary.dm
index 17c519b7a0..1db40f396a 100644
--- a/code/game/antagonist/station/revolutionary.dm
+++ b/code/game/antagonist/station/revolutionary.dm
@@ -29,7 +29,7 @@ var/datum/antagonist/revolutionary/revs
faction_indicator = "rev"
faction_invisible = 1
- restricted_jobs = list("AI", "Cyborg")
+ avoid_silicons = TRUE
protected_jobs = list("Security Officer", "Warden", "Detective", "Internal Affairs Agent", "Colony Director", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer")
roundstart_restricted = list("Internal Affairs Agent", "Colony Director", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer")
diff --git a/code/game/antagonist/station/stowaway.dm b/code/game/antagonist/station/stowaway.dm
index dc3814b3b1..574fda0bca 100644
--- a/code/game/antagonist/station/stowaway.dm
+++ b/code/game/antagonist/station/stowaway.dm
@@ -6,7 +6,7 @@ var/datum/antagonist/stowaway/stowaways
role_text = "Stowaway"
role_text_plural = "Stowaways"
bantype = "renegade"
- restricted_jobs = list("AI")
+ avoid_silicons = TRUE // This was previously allowing cyborgs to be stowaways, but given that they would just connect to the AI, it didn't make much sense
welcome_text = "People are known to run from many things, or to many things, for many different reasons. You happen to be one of those people."
antag_text = "You are a minor antagonist! Within the server rules, do whatever it is \
that you came to the station to do. Espionage, thievery, or just running from the law are all examples. \
diff --git a/code/game/antagonist/station/thug.dm b/code/game/antagonist/station/thug.dm
index 61d92e1646..f26d6441a8 100644
--- a/code/game/antagonist/station/thug.dm
+++ b/code/game/antagonist/station/thug.dm
@@ -6,7 +6,7 @@ var/datum/antagonist/thug/thugs
role_text = "Thug"
role_text_plural = "Thugs"
bantype = "renegade"
- restricted_jobs = list("AI", "Cyborg")
+ avoid_silicons = TRUE
welcome_text = "Sometimes, people just need to get messed up. Luckily, that's what you're here to do."
antag_text = "You are a minor antagonist! Within the server rules, do whatever it is \
that you came to the station to do, be it violence, theft, or just extreme self-defense. \
diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm
index 2ded8a4cbe..b78fc5f74a 100644
--- a/code/game/jobs/job/job.dm
+++ b/code/game/jobs/job/job.dm
@@ -31,6 +31,7 @@
var/outfit_type // What outfit datum does this job use in its default title?
var/offmap_spawn = FALSE // Do we require weird and special spawning and datacore handling?
+ var/mob_type = JOB_CARBON // Bitflags representing mob type this job spawns
// Description of the job's role and minimum responsibilities.
var/job_description = "This Job doesn't have a description! Please report it!"
diff --git a/code/game/jobs/job/silicon.dm b/code/game/jobs/job/silicon.dm
index 6f89adcd07..1619569c9d 100644
--- a/code/game/jobs/job/silicon.dm
+++ b/code/game/jobs/job/silicon.dm
@@ -18,6 +18,7 @@
economic_modifier = 0
has_headset = FALSE
assignable = FALSE
+ mob_type = JOB_SILICON_AI
outfit_type = /decl/hierarchy/outfit/job/silicon/ai
job_description = "The AI oversees the operation of the station and its crew, but has no real authority over them. \
The AI is required to follow its Laws, and Lawbound Synthetics that are linked to it are expected to follow \
@@ -54,6 +55,7 @@
economic_modifier = 0
has_headset = FALSE
assignable = FALSE
+ mob_type = JOB_SILICON_ROBOT
outfit_type = /decl/hierarchy/outfit/job/silicon/cyborg
job_description = "A Cyborg is a mobile station synthetic, piloted by a cybernetically preserved brain. It is considered a person, but is still required \
to follow its Laws."
diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm
index 806d0fe794..182382578a 100644
--- a/code/game/jobs/job_controller.dm
+++ b/code/game/jobs/job_controller.dm
@@ -360,54 +360,64 @@ var/global/datum/controller/occupations/job_master
//Equip custom gear loadout.
var/list/custom_equip_slots = list() //If more than one item takes the same slot, all after the first one spawn in storage.
var/list/custom_equip_leftovers = list()
- if(H.client.prefs.gear && H.client.prefs.gear.len && job.title != "Cyborg" && job.title != "AI")
+ if(H.client.prefs.gear && H.client.prefs.gear.len && !(job.mob_type & JOB_SILICON))
for(var/thing in H.client.prefs.gear)
var/datum/gear/G = gear_datums[thing]
- if(G)
- var/permitted
- if(G.allowed_roles)
- for(var/job_name in G.allowed_roles)
- if(job.title == job_name)
- permitted = 1
+ if(!G) //Not a real gear datum (maybe removed, as this is loaded from their savefile)
+ continue
+
+ var/permitted
+ // Check if it is restricted to certain roles
+ if(G.allowed_roles)
+ for(var/job_name in G.allowed_roles)
+ if(job.title == job_name)
+ permitted = 1
+ else
+ permitted = 1
+
+ // Check if they're whitelisted for this gear (in alien whitelist? seriously?)
+ if(G.whitelisted && !is_alien_whitelisted(H, GLOB.all_species[G.whitelisted]))
+ permitted = 0
+
+ // If they aren't, tell them
+ if(!permitted)
+ to_chat(H, "Your current species, job or whitelist status does not permit you to spawn with [thing]!")
+ continue
+
+ // Implants get special treatment
+ if(G.slot == "implant")
+ var/obj/item/weapon/implant/I = G.spawn_item(H)
+ I.invisibility = 100
+ I.implant_loadout(H)
+ continue
+
+ // Try desperately (and sorta poorly) to equip the item
+ if(G.slot && !(G.slot in custom_equip_slots))
+ var/metadata = H.client.prefs.gear[G.display_name]
+ if(G.slot == slot_wear_mask || G.slot == slot_wear_suit || G.slot == slot_head)
+ custom_equip_leftovers += thing
+ else if(H.equip_to_slot_or_del(G.spawn_item(H, metadata), G.slot))
+ to_chat(H, "Equipping you with \the [thing]!")
+ custom_equip_slots.Add(G.slot)
else
- permitted = 1
+ custom_equip_leftovers.Add(thing)
+ else
+ spawn_in_storage += thing
- if(G.whitelisted && !is_alien_whitelisted(H, GLOB.all_species[G.whitelisted]))
-
- //if(G.whitelisted && (G.whitelisted != H.species.name || !is_alien_whitelisted(H, G.whitelisted)))
- permitted = 0
-
- if(!permitted)
- to_chat(H, "Your current species, job or whitelist status does not permit you to spawn with [thing]!")
- continue
-
- if(G.slot == "implant")
- var/obj/item/weapon/implant/I = G.spawn_item(H)
- I.invisibility = 100
- I.implant_loadout(H)
- continue
-
- if(G.slot && !(G.slot in custom_equip_slots))
- // This is a miserable way to fix the loadout overwrite bug, but the alternative requires
- // adding an arg to a bunch of different procs. Will look into it after this merge. ~ Z
- var/metadata = H.client.prefs.gear[G.display_name]
- if(G.slot == slot_wear_mask || G.slot == slot_wear_suit || G.slot == slot_head)
- custom_equip_leftovers += thing
- else if(H.equip_to_slot_or_del(G.spawn_item(H, metadata), G.slot))
- to_chat(H, "Equipping you with \the [thing]!")
- custom_equip_slots.Add(G.slot)
- else
- custom_equip_leftovers.Add(thing)
- else
- spawn_in_storage += thing
- //Equip job items.
+ // Set up their account
job.setup_account(H)
+
+ // Equip job items.
job.equip(H, H.mind ? H.mind.role_alt_title : "")
+
+ // Stick their fingerprints on literally everything
job.apply_fingerprints(H)
- if(job.title != "Cyborg" && job.title != "AI")
+
+ // Only non-silicons get post-job-equip equipment
+ if(!(job.mob_type & JOB_SILICON))
H.equip_post_job()
- //If some custom items could not be equipped before, try again now.
+ // If some custom items could not be equipped before, try again now.
for(var/thing in custom_equip_leftovers)
var/datum/gear/G = gear_datums[thing]
if(G.slot in custom_equip_slots)
@@ -442,14 +452,16 @@ var/global/datum/controller/occupations/job_master
H.mind.assigned_role = rank
alt_title = H.mind.role_alt_title
- switch(rank)
- if("Cyborg")
- return H.Robotize()
- if("AI")
- return H
- if("Colony Director")
- var/sound/announce_sound = (ticker.current_state <= GAME_STATE_SETTING_UP) ? null : sound('sound/misc/boatswain.ogg', volume=20)
- captain_announcement.Announce("All hands, [alt_title ? alt_title : "Colony Director"] [H.real_name] on deck!", new_sound = announce_sound, zlevel = H.z)
+ // If we're a silicon, we may be done at this point
+ if(job.mob_type & JOB_SILICON_ROBOT)
+ return H.Robotize()
+ if(job.mob_type & JOB_SILICON_AI)
+ return H
+
+ // TWEET PEEP
+ if(rank == "Colony Director")
+ var/sound/announce_sound = (ticker.current_state <= GAME_STATE_SETTING_UP) ? null : sound('sound/misc/boatswain.ogg', volume=20)
+ captain_announcement.Announce("All hands, [alt_title ? alt_title : "Colony Director"] [H.real_name] on deck!", new_sound = announce_sound, zlevel = H.z)
//Deferred item spawning.
if(spawn_in_storage && spawn_in_storage.len)
@@ -559,7 +571,7 @@ var/global/datum/controller/occupations/job_master
if(!J) continue
J.total_positions = text2num(value)
J.spawn_positions = text2num(value)
- if(name == "AI" || name == "Cyborg")//I dont like this here but it will do for now
+ if(J.mob_type & JOB_SILICON)
J.total_positions = 0
return 1
diff --git a/code/modules/client/preferences_spawnpoints.dm b/code/modules/client/preferences_spawnpoints.dm
index 6df4334835..1736c9ca7d 100644
--- a/code/modules/client/preferences_spawnpoints.dm
+++ b/code/modules/client/preferences_spawnpoints.dm
@@ -13,6 +13,7 @@ var/list/spawntypes = list()
var/list/restrict_job = null
var/list/disallow_job = null
var/announce_channel = "Common"
+ var/allowed_mob_types = JOB_SILICON|JOB_CARBON
proc/check_job_spawning(job)
if(restrict_job && !(job in restrict_job))
@@ -22,9 +23,15 @@ var/list/spawntypes = list()
return 0
var/datum/job/J = SSjob.get_job(job)
- if(J?.offmap_spawn && !(job in restrict_job))
+ if(!J) // Couldn't find, admin shenanigans? Allow it
+ return 1
+
+ if(J.offmap_spawn && !(job in restrict_job))
return 0
+ if(!(J.mob_type & allowed_mob_types))
+ return 0
+
return 1
/datum/spawnpoint/proc/get_spawn_position()
@@ -57,7 +64,7 @@ var/list/spawntypes = list()
/datum/spawnpoint/cryo
display_name = "Cryogenic Storage"
msg = "has completed cryogenic revival"
- disallow_job = list("Cyborg")
+ allowed_mob_types = JOB_CARBON
/datum/spawnpoint/cryo/New()
..()
@@ -66,7 +73,7 @@ var/list/spawntypes = list()
/datum/spawnpoint/cyborg
display_name = "Cyborg Storage"
msg = "has been activated from storage"
- restrict_job = list("Cyborg")
+ allowed_mob_types = JOB_SILICON
/datum/spawnpoint/cyborg/New()
..()
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 064f6f9e99..d7432c8611 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -378,10 +378,10 @@
character = job_master.EquipRank(character, rank, 1) //equips the human
UpdateFactionList(character)
- // AIs don't need a spawnpoint, they must spawn at an empty core
- if(character.mind.assigned_role == "AI")
+ var/datum/job/J = SSjob.get_job(rank)
- character = character.AIize(move=0) // AIize the character, but don't move them yet
+ // AIs don't need a spawnpoint, they must spawn at an empty core
+ if(J.mob_type & JOB_SILICON_AI)
// IsJobAvailable for AI checks that there is an empty core available in this list
var/obj/structure/AIcore/deactivated/C = empty_playable_ai_cores[1]
@@ -389,11 +389,14 @@
character.loc = C.loc
+ // AIize the character, but don't move them yet
+ character = character.AIize(move = FALSE) // Dupe of code in /datum/controller/subsystem/ticker/proc/create_characters() for non-latespawn, unify?
+
AnnounceCyborg(character, rank, "has been transferred to the empty core in \the [character.loc.loc]")
ticker.mode.latespawn(character)
- qdel(C)
- qdel(src)
+ qdel(C) //Deletes empty core (really?)
+ qdel(src) //Deletes new_player
return
// Equip our custom items only AFTER deploying to spawn points eh?
@@ -407,18 +410,15 @@
character.buckled.set_dir(character.dir)
ticker.mode.latespawn(character)
-
- if(character.mind.assigned_role != "Cyborg")
+
+ if(J.mob_type & JOB_SILICON)
+ AnnounceCyborg(character, rank, join_message, announce_channel, character.z)
+ else
+ AnnounceArrival(character, rank, join_message, announce_channel, character.z)
data_core.manifest_inject(character)
ticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn
-
- //Grab some data from the character prefs for use in random news procs.
-
- AnnounceArrival(character, rank, join_message, announce_channel, character.z)
- else
- AnnounceCyborg(character, rank, join_message, announce_channel, character.z)
-
- qdel(src)
+
+ qdel(src) // Delete new_player mob
/mob/new_player/proc/AnnounceCyborg(var/mob/living/character, var/rank, var/join_message, var/channel, var/zlevel)
if (ticker.current_state == GAME_STATE_PLAYING)
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index d44ffca31e..9afbcf14e0 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -64,10 +64,34 @@
invisibility = 101
return ..()
-/mob/proc/AIize(move=1)
+/mob/proc/AIize(var/move = TRUE)
if(client)
src << sound(null, repeat = 0, wait = 0, volume = 85, channel = 1) // stop the jams for AIs
- var/mob/living/silicon/ai/O = new (loc, using_map.default_law_type,,1)//No MMI but safety is in effect.
+
+ var/newloc = loc
+ if(move)
+ var/obj/loc_landmark
+ for(var/obj/effect/landmark/start/sloc in landmarks_list)
+ if (sloc.name != "AI")
+ continue
+ if ((locate(/mob/living) in sloc.loc) || (locate(/obj/structure/AIcore) in sloc.loc))
+ continue
+ loc_landmark = sloc
+ if (!loc_landmark)
+ for(var/obj/effect/landmark/tripai in landmarks_list)
+ if (tripai.name == "tripai")
+ if((locate(/mob/living) in tripai.loc) || (locate(/obj/structure/AIcore) in tripai.loc))
+ continue
+ loc_landmark = tripai
+ if (!loc_landmark)
+ to_chat(src, "Oh god sorry we can't find an unoccupied AI spawn location, so we're spawning you on top of someone.")
+ for(var/obj/effect/landmark/start/sloc in landmarks_list)
+ if (sloc.name == "AI")
+ loc_landmark = sloc
+
+ newloc = loc_landmark.loc
+
+ var/mob/living/silicon/ai/O = new (newloc, using_map.default_law_type,,1)//No MMI but safety is in effect.
O.invisibility = 0
O.aiRestorePowerRoutine = 0
@@ -101,28 +125,6 @@
if(LANGUAGE_ROOTLOCAL in B.alternate_languages)
O.add_language(LANGUAGE_ROOTLOCAL, 1)
- if(move)
- var/obj/loc_landmark
- for(var/obj/effect/landmark/start/sloc in landmarks_list)
- if (sloc.name != "AI")
- continue
- if ((locate(/mob/living) in sloc.loc) || (locate(/obj/structure/AIcore) in sloc.loc))
- continue
- loc_landmark = sloc
- if (!loc_landmark)
- for(var/obj/effect/landmark/tripai in landmarks_list)
- if (tripai.name == "tripai")
- if((locate(/mob/living) in tripai.loc) || (locate(/obj/structure/AIcore) in tripai.loc))
- continue
- loc_landmark = tripai
- if (!loc_landmark)
- to_chat(O, "Oh god sorry we can't find an unoccupied AI spawn location, so we're spawning you on top of someone.")
- for(var/obj/effect/landmark/start/sloc in landmarks_list)
- if (sloc.name == "AI")
- loc_landmark = sloc
-
- O.loc = loc_landmark.loc
-
O.on_mob_init()
O.add_ai_verbs()