diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm
index 5424eaa6a7c..563a469e821 100644
--- a/code/game/jobs/job/job.dm
+++ b/code/game/jobs/job/job.dm
@@ -115,4 +115,7 @@
H.l_store.add_fingerprint(H,1)
if(H.r_store)
H.r_store.add_fingerprint(H,1)
- return 1
\ No newline at end of file
+ return 1
+
+/datum/job/proc/is_position_available()
+ return (current_positions < total_positions) || (total_positions == -1)
\ No newline at end of file
diff --git a/code/game/jobs/job/silicon.dm b/code/game/jobs/job/silicon.dm
index 67d10b876a5..7bb2b006f87 100644
--- a/code/game/jobs/job/silicon.dm
+++ b/code/game/jobs/job/silicon.dm
@@ -2,7 +2,7 @@
title = "AI"
flag = AI
department_flag = ENGSEC
- 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"
@@ -13,7 +13,9 @@
if(!H) return 0
return 1
-
+/datum/job/ai/is_position_available()
+ return (empty_playable_ai_cores.len != 0)
+
/datum/job/cyborg
title = "Cyborg"
diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm
index 647a405bf18..1e7dfe99d5f 100644
--- a/code/game/jobs/job_controller.dm
+++ b/code/game/jobs/job_controller.dm
@@ -443,7 +443,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!
if(rank=="Clown") // Clowns DO need to breathe, though - N3X
H.species.equip(H)
else
diff --git a/code/game/machinery/bots/bots.dm b/code/game/machinery/bots/bots.dm
index 626f6aad274..539dd775b0f 100644
--- a/code/game/machinery/bots/bots.dm
+++ b/code/game/machinery/bots/bots.dm
@@ -565,7 +565,7 @@ obj/machinery/bot/proc/start_patrol()
send_status()
if(awaiting_beacon) // awaiting beacon response
awaiting_beacon++
- if(awaiting_beacon > 20) // wait 20 secs for beacon response
+ if(awaiting_beacon > 40) // wait 40 secs for beacon response
find_nearest_beacon() // then go to nearest instead
return
if(next_destination)
@@ -583,7 +583,7 @@ obj/machinery/bot/proc/start_patrol()
new_destination = "__nearest__"
post_signal(beacon_freq, "findbeacon", "patrol")
awaiting_beacon = 1
- spawn(100)
+ spawn(150)
awaiting_beacon = 0
if(nearest_beacon)
set_destination(nearest_beacon)
diff --git a/code/game/machinery/bots/ed209bot.dm b/code/game/machinery/bots/ed209bot.dm
index 21fb72573e4..6b1514391bd 100644
--- a/code/game/machinery/bots/ed209bot.dm
+++ b/code/game/machinery/bots/ed209bot.dm
@@ -69,6 +69,7 @@
spawn(3)
var/datum/job/detective/J = new/datum/job/detective
botcard.access = J.get_access()
+ botcard.access += 2
prev_access = botcard.access
diff --git a/code/game/machinery/bots/secbot.dm b/code/game/machinery/bots/secbot.dm
index 5da885441de..663646cb999 100644
--- a/code/game/machinery/bots/secbot.dm
+++ b/code/game/machinery/bots/secbot.dm
@@ -88,6 +88,7 @@
var/datum/job/detective/J = new/datum/job/detective
botcard.access = J.get_access()
+ botcard.access += 2
prev_access = botcard.access
add_to_beacons(bot_filter)
diff --git a/code/game/machinery/computer/ai_core.dm b/code/game/machinery/computer/ai_core.dm
index 6b5d3abe41f..597386eff5a 100644
--- a/code/game/machinery/computer/ai_core.dm
+++ b/code/game/machinery/computer/ai_core.dm
@@ -156,11 +156,15 @@
if(istype(P, /obj/item/weapon/screwdriver))
playsound(loc, 'sound/items/Screwdriver.ogg', 50, 1)
user << "You connect the monitor."
- if(!laws.inherent.len) //If laws isn't set to null but nobody supplied a board, the AI would normally be created lawless. We don't want that.
- laws = null
- 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)
@@ -171,10 +175,53 @@
anchored = 1
state = 20//So it doesn't interact based on the above. Not really necessary.
- attackby(var/obj/item/device/aicard/A as obj, var/mob/user as mob)
- if(istype(A, /obj/item/device/aicard))//Is it?
- A.transfer_ai("INACTIVE","AICARD",src,user)
- return
+/obj/structure/AIcore/deactivated/attackby(var/obj/item/weapon/W, var/mob/user)
+ if(istype(W, /obj/item/device/aicard))//Is it?
+ var/obj/item/device/aicard/card = W
+ card.transfer_ai("INACTIVE","AICARD",src,user)
+ 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 ..()
+
+/client/proc/empty_ai_core_toggle_latejoin()
+ set name = "Toggle AI Core Latejoin"
+ set category = "Admin"
+
+ var/list/cores = list()
+ for(var/obj/structure/AIcore/deactivated/D in world)
+ cores["[D] ([D.loc.loc])"] = D
+
+ if(!cores.len)
+ src << "No deactivated AI cores were found."
+
+ var/id = input("Which core?", "Toggle AI Core Latejoin", null) as null|anything in cores
+ if(!id) return
+
+ var/obj/structure/AIcore/deactivated/D = cores[id]
+ if(!D) return
+
+ if(D in empty_playable_ai_cores)
+ empty_playable_ai_cores -= D
+ src << "\The [id] is now not available for latejoining AIs."
+ else
+ empty_playable_ai_cores += D
+ src << "\The [id] is now available for latejoining AIs."
/*
diff --git a/code/game/vehicles/spacepods/spacepod.dm b/code/game/vehicles/spacepods/spacepod.dm
index 248868e1957..7d81ba1ebbd 100644
--- a/code/game/vehicles/spacepods/spacepod.dm
+++ b/code/game/vehicles/spacepods/spacepod.dm
@@ -75,6 +75,10 @@
if(P.damage && !P.nodamage)
deal_damage(P.damage)
+/obj/spacepod/blob_act()
+ deal_damage(30)
+ return
+
/obj/spacepod/attack_animal(mob/living/simple_animal/user as mob)
if(user.melee_damage_upper == 0)
user.emote("[user.friendly] [src]")
diff --git a/code/global.dm b/code/global.dm
index fc7bb7511f9..28ed508a5f4 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -332,4 +332,7 @@ var/recall_time_limit=72000
var/global/list/alphabet_uppercase = list("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z")
// AI controlled bots
-var/global/list/aibots = list()
\ No newline at end of file
+var/global/list/aibots = list()
+
+// 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)
\ No newline at end of file
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index ed8989c3302..727dfc58c61 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -64,7 +64,8 @@ var/list/admin_verbs_admin = list(
/client/proc/man_up,
/client/proc/global_man_up,
/client/proc/delbook,
- /client/proc/event_manager_panel
+ /client/proc/event_manager_panel,
+ /client/proc/empty_ai_core_toggle_latejoin
)
var/list/admin_verbs_ban = list(
/client/proc/unban_panel,
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 657713ca60a..f34da6c2e75 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -115,7 +115,7 @@ var/list/ai_list = 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/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 7e7ae9c3f59..c8403d5b671 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -272,7 +272,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(!is_job_whitelisted(src, rank)) return 0
if(!job.player_old_enough(src.client)) return 0
@@ -309,6 +309,24 @@
EquipRacialItems(character)
character = job_master.EquipRank(character, rank, 1) //equips the human
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
@@ -364,13 +382,11 @@
arrivalmessage = replacetext(arrivalmessage,"$rank",rank ? "[rank]" : "visitor")
announcer.say(";[arrivalmessage]")
else
- 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)
if((character.mind.assigned_role != "Cyborg") && (character.mind.special_role != "MODE"))
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)
@@ -384,12 +400,10 @@
var/arrivalmessage = "A new[rank ? " [rank]" : " visitor" ] [join_message ? join_message : "has arrived on the station"]."
announcer.say(";[arrivalmessage]")
else
- 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)
if((character.mind.special_role != "MODE"))
// 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 a5aad6e3999..29baaab6f11 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -61,13 +61,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)
@@ -80,7 +80,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,,,1)//No MMI but safety is in effect.
@@ -93,28 +93,29 @@
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 << "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras)."
O << "To look at other parts of the station, click on yourself to get a camera menu."
@@ -131,9 +132,9 @@
O.job = "AI"
O.rename_self("ai",1)
- . = O
- del(src)
-
+ spawn(0)
+ del(src)
+ return O
/mob/living/carbon/human/make_into_mask(var/should_gib = 0)
for(var/t in organs)
diff --git a/paradise.dme b/paradise.dme
index f059059abf3..1a6063c3eb8 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1255,6 +1255,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"