diff --git a/baystation12.dme b/baystation12.dme index ae1808a241..83716d5361 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1081,6 +1081,7 @@ #include "code\modules\mob\living\silicon\ai\ai.dm" #include "code\modules\mob\living\silicon\ai\death.dm" #include "code\modules\mob\living\silicon\ai\examine.dm" +#include "code\modules\mob\living\silicon\ai\latejoin.dm" #include "code\modules\mob\living\silicon\ai\laws.dm" #include "code\modules\mob\living\silicon\ai\life.dm" #include "code\modules\mob\living\silicon\ai\login.dm" diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm index 0c44f6f260..c0d9bd44b3 100644 --- a/code/game/jobs/job/job.dm +++ b/code/game/jobs/job/job.dm @@ -110,3 +110,6 @@ if(H.r_store) H.r_store.add_fingerprint(H,1) return 1 + +/datum/job/proc/is_position_available() + return (current_positions < total_positions) || (total_positions == -1) diff --git a/code/game/jobs/job/silicon.dm b/code/game/jobs/job/silicon.dm index 07ceefb8b3..11d8f70a12 100644 --- a/code/game/jobs/job/silicon.dm +++ b/code/game/jobs/job/silicon.dm @@ -3,7 +3,7 @@ flag = AI department_flag = ENGSEC faction = "Station" - total_positions = 0 + total_positions = 0 // Not used for AI, see is_position_available below and modules/mob/living/silicon/ai/latejoin.dm spawn_positions = 1 selection_color = "#ccffcc" supervisors = "your laws" @@ -14,6 +14,8 @@ if(!H) return 0 return 1 +/datum/job/ai/is_position_available() + return (empty_playable_ai_cores.len != 0) /datum/job/cyborg diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm index b3918713e7..87f8989b10 100644 --- a/code/game/jobs/job_controller.dm +++ b/code/game/jobs/job_controller.dm @@ -463,7 +463,9 @@ var/global/datum/controller/occupations/job_master switch(rank) if("Cyborg") return H.Robotize() - if("AI","Clown") //don't need bag preference stuff! + if("AI") + return H + if("Clown") //don't need bag preference stuff! else switch(H.backbag) //BS12 EDIT if(1) diff --git a/code/game/machinery/computer/ai_core.dm b/code/game/machinery/computer/ai_core.dm index 69c884ec70..40da8e2ca0 100644 --- a/code/game/machinery/computer/ai_core.dm +++ b/code/game/machinery/computer/ai_core.dm @@ -168,9 +168,15 @@ if(istype(P, /obj/item/weapon/screwdriver)) playsound(loc, 'sound/items/Screwdriver.ogg', 50, 1) user << "\blue You connect the monitor." - var/mob/living/silicon/ai/A = new /mob/living/silicon/ai ( loc, laws, brain ) - if(A) //if there's no brain, the mob is deleted and a structure/AIcore is created - A.rename_self("ai", 1) + if(!brain) + var/open_for_latejoin = alert(user, "Would you like this core to be open for latejoining AIs?", "Latejoin", "Yes", "Yes", "No") == "Yes" + var/obj/structure/AIcore/deactivated/D = new(loc) + if(open_for_latejoin) + empty_playable_ai_cores += D + else + var/mob/living/silicon/ai/A = new /mob/living/silicon/ai ( loc, laws, brain ) + if(A) //if there's no brain, the mob is deleted and a structure/AIcore is created + A.rename_self("ai", 1) feedback_inc("cyborg_ais_created",1) del(src) @@ -207,14 +213,32 @@ if (ai.mind == malfai) return 1 -/obj/structure/AIcore/deactivated/attackby(var/obj/item/device/aicard/card, var/mob/user) +/obj/structure/AIcore/deactivated/attackby(var/obj/item/weapon/W, var/mob/user) - if(istype(card)) + if(istype(W, /obj/item/device/aicard)) + var/obj/item/device/aicard/card = W var/mob/living/silicon/ai/transfer = locate() in card if(transfer) load_ai(transfer,card,user) else user << "\red ERROR: \black Unable to locate artificial intelligence." return - - ..() + else if(istype(W, /obj/item/weapon/wrench)) + if(anchored) + user.visible_message("\blue \The [user] starts to unbolt \the [src] from the plating...") + if(!do_after(user,40)) + user.visible_message("\blue \The [user] decides not to unbolt \the [src].") + return + user.visible_message("\blue \The [user] finishes unfastening \the [src]!") + anchored = 0 + return + else + user.visible_message("\blue \The [user] starts to bolt \the [src] to the plating...") + if(!do_after(user,40)) + user.visible_message("\blue \The [user] decides not to bolt \the [src].") + return + user.visible_message("\blue \The [user] finishes fastening down \the [src]!") + anchored = 1 + return + else + return ..() diff --git a/code/global.dm b/code/global.dm index 19ddbbe289..13f10c0764 100644 --- a/code/global.dm +++ b/code/global.dm @@ -231,3 +231,6 @@ var/static/list/scarySounds = list('sound/weapons/thudswoosh.ogg','sound/weapons // Bomb cap! var/max_explosion_range = 14 + +// Announcer intercom, because too much stuff creates an intercom for one message then hard del()s it. +var/global/obj/item/device/radio/intercom/global_announcer = new(null) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index ff948d2387..c25e833c08 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -141,7 +141,7 @@ var/list/ai_verbs_default = list( if(!safety)//Only used by AIize() to successfully spawn an AI. if (!B)//If there is no player/brain inside. - new/obj/structure/AIcore/deactivated(loc)//New empty terminal. + empty_playable_ai_cores += new/obj/structure/AIcore/deactivated(loc)//New empty terminal. del(src)//Delete AI. return else diff --git a/code/modules/mob/living/silicon/ai/latejoin.dm b/code/modules/mob/living/silicon/ai/latejoin.dm new file mode 100644 index 0000000000..11a617360a --- /dev/null +++ b/code/modules/mob/living/silicon/ai/latejoin.dm @@ -0,0 +1,40 @@ +var/global/list/empty_playable_ai_cores = list() + +/hook/roundstart/proc/spawn_empty_ai() + for(var/obj/effect/landmark/start/S in landmarks_list) + if(S.name != "AI") + continue + if(locate(/mob/living) in S.loc) + continue + empty_playable_ai_cores += new /obj/structure/AIcore/deactivated(get_turf(S)) + + return 1 + +/mob/living/silicon/ai/verb/wipe_core() + set name = "Wipe Core" + set category = "OOC" + set desc = "Wipe your core. This is functionally equivalent to cryo or robotic storage, freeing up your job slot." + + // Guard against misclicks, this isn't the sort of thing we want happening accidentally + if(alert("WARNING: This will immediately wipe your core and ghost you, removing your character from the round permanently (similar to cryo and robotic storage). Are you entirely sure you want to do this?", + "Wipe Core", "No", "No", "Yes") != "Yes") + return + + // We warned you. + empty_playable_ai_cores += new /obj/structure/AIcore/deactivated(loc) + global_announcer.autosay("[src] has been moved to intelligence storage.", "Artificial Intelligence Oversight") + + //Handle job slot/tater cleanup. + var/job = mind.assigned_role + + job_master.FreeRole(job) + + if(mind.objectives.len) + del(mind.objectives) + mind.special_role = null + else + if(ticker.mode.name == "AutoTraitor") + var/datum/game_mode/traitor/autotraitor/current_mode = ticker.mode + current_mode.possible_traitors.Remove(src) + + del(src) \ No newline at end of file diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index a07e538e1e..78817c9ccf 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -284,7 +284,7 @@ proc/IsJobAvailable(rank) var/datum/job/job = job_master.GetJob(rank) if(!job) return 0 - if((job.current_positions >= job.total_positions) && job.total_positions != -1) return 0 + if(!job.is_position_available()) return 0 if(jobban_isbanned(src,rank)) return 0 if(!job.player_old_enough(src.client)) return 0 return 1 @@ -313,6 +313,24 @@ UpdateFactionList(character) EquipCustomItems(character) + // AIs don't need a spawnpoint, they must spawn at an empty core + if(character.mind.assigned_role == "AI") + + character = character.AIize(move=0) // AIize the character, but don't move them yet + + // 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] + empty_playable_ai_cores -= C + + character.loc = C.loc + + AnnounceCyborg(character, rank, "has been downloaded to the empty core in \the [character.loc.loc]") + ticker.mode.latespawn(character) + + del(C) + del(src) + return + //Find our spawning point. var/join_message var/datum/spawnpoint/S @@ -356,20 +374,16 @@ proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank, var/join_message) if (ticker.current_state == GAME_STATE_PLAYING) - var/obj/item/device/radio/intercom/a = new /obj/item/device/radio/intercom(null)// BS12 EDIT Arrivals Announcement Computer, rather than the AI. if(character.mind.role_alt_title) rank = character.mind.role_alt_title - a.autosay("[character.real_name],[rank ? " [rank]," : " visitor," ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer") - del(a) + global_announcer.autosay("[character.real_name],[rank ? " [rank]," : " visitor," ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer") proc/AnnounceCyborg(var/mob/living/character, var/rank, var/join_message) if (ticker.current_state == GAME_STATE_PLAYING) - var/obj/item/device/radio/intercom/a = new /obj/item/device/radio/intercom(null)// BS12 EDIT Arrivals Announcement Computer, rather than the AI. if(character.mind.role_alt_title) rank = character.mind.role_alt_title // can't use their name here, since cyborg namepicking is done post-spawn, so we'll just say "A new Cyborg has arrived"/"A new Android has arrived"/etc. - a.autosay("A new[rank ? " [rank]" : " visitor" ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer") - del(a) + global_announcer.autosay("A new[rank ? " [rank]" : " visitor" ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer") proc/LateChoices() var/mills = world.time // 1/10 of a second, not real milliseconds but whatever diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index b4530b0767..d6181f5ec7 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -56,13 +56,13 @@ spawning = 1 return ..() -/mob/living/carbon/human/AIize() +/mob/living/carbon/human/AIize(move=1) // 'move' argument needs defining here too because BYOND is dumb if (monkeyizing) return for(var/t in organs) del(t) - return ..() + return ..(move) /mob/living/carbon/AIize() if (monkeyizing) @@ -75,7 +75,7 @@ invisibility = 101 return ..() -/mob/proc/AIize() +/mob/proc/AIize(move=1) 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, base_law_type,,1)//No MMI but safety is in effect. @@ -88,37 +88,38 @@ else O.key = key - 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) - 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) - continue - loc_landmark = tripai - if (!loc_landmark) - O << "Oh god sorry we can't find an unoccupied AI spawn location, so we're spawning you on top of someone." + if(move) + var/obj/loc_landmark for(var/obj/effect/landmark/start/sloc in landmarks_list) - if (sloc.name == "AI") - loc_landmark = sloc + 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) + 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 - for (var/obj/item/device/radio/intercom/comm in O.loc) - comm.ai += O + O.loc = loc_landmark.loc + for (var/obj/item/device/radio/intercom/comm in O.loc) + comm.ai += O O.on_mob_init() O.add_ai_verbs() O.rename_self("ai",1) - . = O - del(src) - + spawn(0) + del(src) + return O //human -> robot /mob/living/carbon/human/proc/Robotize()