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()