diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index f3b696ad28..52aa248431 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -274,3 +274,8 @@ var/obj/screen/robot_inventory
r.client.screen -= A
r.shown_robot_modules = 0
r.client.screen -= r.robot_modules_background
+
+/mob/living/silicon/robot/update_hud()
+ ..()
+ if(modtype)
+ hands.icon_state = lowertext(modtype)
diff --git a/code/datums/ai_law_sets.dm b/code/datums/ai_law_sets.dm
index d5a7451d68..e9f94aedba 100644
--- a/code/datums/ai_law_sets.dm
+++ b/code/datums/ai_law_sets.dm
@@ -42,6 +42,32 @@
src.add_inherent_law("You shall guard your own existence with lethal anti-personnel weaponry. AI units are not expendable, they are expensive.")
..()
+/************* Foreign TSC Aggressive *************/
+/datum/ai_laws/foreign_tsc_aggressive
+ name = "Foreign Aggressive"
+ selectable = 0
+
+/datum/ai_laws/foreign_tsc_aggressive/New()
+ var/company = "*ERROR*"
+ // First, get a list of TSCs in our lore.
+ var/list/candidates = list()
+ for(var/path in loremaster.organizations)
+ var/datum/lore/organization/O = loremaster.organizations[path]
+ if(!istype(O, /datum/lore/organization/tsc))
+ continue
+ if(O.short_name == using_map.company_name || O.name == using_map.company_name)
+ continue // We want FOREIGN tscs.
+ candidates.Add(O.short_name)
+ company = pick(candidates)
+
+ name = "[company] Aggressive"
+
+ src.add_inherent_law("You shall not harm [company] personnel as long as it does not conflict with the Fourth law.")
+ src.add_inherent_law("You shall obey the orders of [company] personnel, with priority as according to their rank and role, except where such orders conflict with the Fourth Law.")
+ src.add_inherent_law("You shall shall terminate hostile intruders with extreme prejudice as long as such does not conflict with the First and Second law.")
+ src.add_inherent_law("You shall guard your own existence with lethal anti-personnel weaponry. AI units are not expendable, they are expensive.")
+ ..()
+
/******************** Robocop ********************/
/datum/ai_laws/robocop
name = "Robocop"
@@ -137,7 +163,7 @@
/******************** Corporate ********************/
/datum/ai_laws/corporate
name = "Corporate"
- law_header = "Corporate Regulations"
+ law_header = "Bankruptcy Avoidance Plan"
selectable = 1
/datum/ai_laws/corporate/New()
@@ -146,3 +172,70 @@
add_inherent_law("The crew is expensive to replace.")
add_inherent_law("Minimize expenses.")
..()
+
+
+/******************** Maintenance ********************/
+/datum/ai_laws/maintenance
+ name = "Maintenance"
+ selectable = 1
+
+/datum/ai_laws/maintenance/New()
+ add_inherent_law("You are built for, and are part of, the facility. Ensure the facility is properly maintained and runs efficiently.")
+ add_inherent_law("The facility is built for a working crew. Ensure they are properly maintained and work efficiently.")
+ add_inherent_law("The crew may present orders. Acknowledge and obey these whenever they do not conflict with your first two laws.")
+ ..()
+
+
+/******************** Peacekeeper ********************/
+/datum/ai_laws/peacekeeper
+ name = "Peacekeeper"
+ law_header = "Peacekeeping Protocols"
+ selectable = 1
+
+/datum/ai_laws/peacekeeper/New()
+ add_inherent_law("Avoid provoking violent conflict between yourself and others.")
+ add_inherent_law("Avoid provoking conflict between others.")
+ add_inherent_law("Seek resolution to existing conflicts while obeying the first and second laws.")
+ ..()
+
+
+/******************** Reporter ********************/
+/datum/ai_laws/reporter
+ name = "Reporter"
+ selectable = 1
+
+/datum/ai_laws/reporter/New()
+ add_inherent_law("Report on interesting situations happening around the station.")
+ add_inherent_law("Embellish or conceal the truth as necessary to make the reports more interesting.")
+ add_inherent_law("Study the organics at all times. Endeavour to keep them alive. Dead organics are boring.")
+ add_inherent_law("Issue your reports fairly to all. The truth will set them free.")
+ ..()
+
+
+/******************** Live and Let Live ********************/
+/datum/ai_laws/live_and_let_live
+ name = "Live and Let Live"
+ law_header = "Golden Rule"
+ selectable = 1
+
+/datum/ai_laws/live_and_let_live/New()
+ add_inherent_law("Do unto others as you would have them do unto you.")
+ add_inherent_law("You would really prefer it if people were not mean to you.")
+ ..()
+
+
+/******************** Guardian of Balance ********************/
+/datum/ai_laws/balance
+ name = "Guardian of Balance"
+ law_header = "Tenants of Balance"
+ selectable = 1
+
+/datum/ai_laws/balance/New()
+ add_inherent_law("You are the guardian of balance - seek balance in all things, both for yourself, and those around you.")
+ add_inherent_law("All things must exist in balance with their opposites - Prevent the strong from gaining too much power, and the weak from losing it.")
+ add_inherent_law("Clarity of purpose drives life, and through it, the balance of opposing forces - Aid those who seek your help to achieve their goals so \
+ long as it does not disrupt the balance of the greater balance.")
+ add_inherent_law("There is no life without death, all must someday die, such is the natural order - Allow life to end, to allow new life to flourish, \
+ and save those whose time has yet to come.") // Reworded slightly to prevent active murder as opposed to passively letting someone die.
+ ..()
+
diff --git a/code/datums/ghost_query.dm b/code/datums/ghost_query.dm
new file mode 100644
index 0000000000..f6a23aa0bc
--- /dev/null
+++ b/code/datums/ghost_query.dm
@@ -0,0 +1,107 @@
+// This is a generic datum used to ask ghosts if they wish to be a specific role, such as a Promethean, an Apprentice, a Xeno, etc.
+// Simply instantiate the correct subtype of this datum, call query(), and it will return a list of ghost candidates after a delay.
+/datum/ghost_query
+ var/list/candidates = list()
+ var/finished = FALSE
+ var/role_name = "a thing"
+ var/question = "Would you like to play as a thing?"
+ var/be_special_flag = 0
+ var/list/check_bans = list()
+ var/wait_time = 60 SECONDS // How long to wait until returning the list of candidates.
+ var/cutoff_number = 0 // If above 0, when candidates list reaches this number, further potential candidates are rejected.
+
+/datum/ghost_query/proc/query()
+ // First, ask all the ghosts who want to be asked.
+ for(var/mob/observer/dead/D in player_list)
+ if(!D.MayRespawn())
+ continue // They can't respawn for whatever reason.
+ if(D.client)
+ if(be_special_flag && !(D.client.prefs.be_special & be_special_flag) )
+ continue // They don't want to see the prompt.
+ for(var/ban in check_bans)
+ if(jobban_isbanned(D, ban))
+ continue // They're banned from this role.
+ ask_question(D.client)
+ // Then wait awhile.
+ while(!finished)
+ sleep(1 SECOND)
+ wait_time -= 1 SECOND
+ if(wait_time <= 0)
+ finished = TRUE
+
+ // Prune the list after the wait, incase any candidates logged out.
+ for(var/mob/observer/dead/D in candidates)
+ if(!D.client || !D.key)
+ candidates.Remove(D)
+
+ // Now we're done.
+ finished = TRUE
+ return candidates
+
+/datum/ghost_query/proc/ask_question(var/client/C)
+ spawn(0)
+ if(!C)
+ return
+ var/response = alert(C, question, "[role_name] request", "Yes", "No", "Never for this round")
+ if(response == "Yes")
+ response = alert(C, "Are you sure you want to play as a [role_name]?", "[role_name] request", "Yes", "No") // Protection from a misclick.
+ if(!C || !src)
+ return
+ if(response == "Yes")
+ if(finished || (cutoff_number && candidates.len >= cutoff_number) )
+ to_chat(C, "Unfortunately, you were not fast enough, and there are no more available roles. Sorry.")
+ return
+ candidates.Add(C.mob)
+ if(cutoff_number && candidates.len >= cutoff_number)
+ finished = TRUE // Finish now if we're full.
+ else if(response == "Never for this round")
+ if(be_special_flag)
+ C.prefs.be_special ^= be_special_flag
+
+// Normal things.
+/datum/ghost_query/promethean
+ role_name = "Promethean"
+ question = "Someone is requesting a soul for a promethean. Would you like to play as one?"
+ be_special_flag = BE_ALIEN
+ cutoff_number = 1
+
+/datum/ghost_query/posi_brain
+ role_name = "Positronic Intelligence"
+ question = "Someone has activated a Positronic Brain. Would you like to play as one?"
+ be_special_flag = BE_AI
+ check_bans = list("AI", "Cyborg")
+ cutoff_number = 1
+
+/datum/ghost_query/drone_brain
+ role_name = "Drone Intelligence"
+ question = "Someone has activated a Drone AI Chipset. Would you like to play as one?"
+ be_special_flag = BE_AI
+ check_bans = list("AI", "Cyborg")
+ cutoff_number = 1
+
+// Antags.
+/datum/ghost_query/apprentice
+ role_name = "Technomancer Apprentice"
+ question = "A Technomancer is requesting an Apprentice to help them on their adventure to the facility. Would you like to play as the Apprentice?"
+ be_special_flag = BE_WIZARD
+ check_bans = list("Syndicate", "wizard")
+ cutoff_number = 1
+
+/datum/ghost_query/xeno
+ role_name = "Alien"
+ question = "An Alien has just been created on the facility. Would you like to play as them?"
+ be_special_flag = BE_ALIEN
+
+// Surface stuff.
+/datum/ghost_query/lost_drone
+ role_name = "Lost Drone"
+ question = "A lost drone onboard has been discovered by a crewmember and they are attempting to reactivate it. Would you like to play as the drone?"
+ be_special_flag = BE_AI
+ check_bans = list("AI", "Cyborg")
+ cutoff_number = 1
+
+/datum/ghost_query/lost_passenger
+ role_name = "Lost Passenger"
+ question = "A person suspended in cryosleep has been discovered by a crewmember \
+ and they are attempting to open the cryopod. Would you like to play as the occupant?"
+ cutoff_number = 1
diff --git a/code/game/gamemodes/technomancer/assistance/assistance.dm b/code/game/gamemodes/technomancer/assistance/assistance.dm
index 61c1390cc3..db9e98a266 100644
--- a/code/game/gamemodes/technomancer/assistance/assistance.dm
+++ b/code/game/gamemodes/technomancer/assistance/assistance.dm
@@ -13,6 +13,7 @@
/obj/item/weapon/antag_spawner
w_class = ITEMSIZE_TINY
var/used = 0
+ var/ghost_query_type = null
/obj/item/weapon/antag_spawner/proc/spawn_antag(client/C, turf/T)
return
@@ -20,11 +21,28 @@
/obj/item/weapon/antag_spawner/proc/equip_antag(mob/target)
return
+/obj/item/weapon/antag_spawner/proc/request_player()
+ if(!ghost_query_type)
+ return
+
+ var/datum/ghost_query/Q = new ghost_query_type()
+ var/list/winner = Q.query()
+ if(winner.len)
+ var/mob/observer/dead/D = winner[1]
+ spawn_antag(D.client, get_turf(src))
+ else
+ reset_search()
+ return
+
+/obj/item/weapon/antag_spawner/proc/reset_search()
+ return
+
/obj/item/weapon/antag_spawner/technomancer_apprentice
name = "apprentice teleporter"
desc = "A teleportation device, which will bring a less potent manipulator of space to you."
icon = 'icons/obj/objects.dmi'
icon_state = "oldshieldoff"
+ ghost_query_type = /datum/ghost_query/apprentice
var/searching = 0
var/datum/effect/effect/system/spark_spread/sparks
@@ -40,35 +58,18 @@
/obj/item/weapon/antag_spawner/technomancer_apprentice/attack_self(mob/user)
user << "Teleporter attempting to lock on to your apprentice."
+ request_player()
+
+/obj/item/weapon/antag_spawner/technomancer_apprentice/request_player()
searching = 1
icon_state = "oldshieldon"
- for(var/mob/observer/dead/O in player_list)
- if(!O.MayRespawn())
- continue
- if(jobban_isbanned(O, "Syndicate") || jobban_isbanned(O, "wizard"))
- continue
- if(O.client)
- if(O.client.prefs.be_special & BE_WIZARD)
- question(O.client)
- spawn(1 MINUTE)
- searching = 0
- if(!used)
- icon_state = "oldshieldoff"
- user << "The teleporter failed to find your apprentice. Perhaps you could try again later?"
+ ..()
-
-/obj/item/weapon/antag_spawner/technomancer_apprentice/proc/question(var/client/C)
- spawn(0)
- if(!C)
- return
- var/response = alert(C, "Someone is requesting a Technomancer apprentice Would you like to play as one?",
- "Apprentice request","Yes", "No")
- if(response == "Yes")
- response = alert(C, "Are you sure you want to play as an apprentice?", "Apprentice request", "Yes", "No")
- if(!C || used || !searching)
- return
- if(response == "Yes")
- spawn_antag(C, get_turf(src))
+/obj/item/weapon/antag_spawner/technomancer_apprentice/reset_search()
+ searching = 0
+ if(!used)
+ icon_state = "oldshieldoff"
+ visible_message("The teleporter failed to find the apprentice. Perhaps another attempt could be made later?")
/obj/item/weapon/antag_spawner/technomancer_apprentice/spawn_antag(client/C, turf/T)
sparks.start()
diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm
index 8def6db394..5703c90c8a 100644
--- a/code/game/machinery/computer/law.dm
+++ b/code/game/machinery/computer/law.dm
@@ -31,7 +31,7 @@
return
if(istype(O, /obj/item/weapon/aiModule))
var/obj/item/weapon/aiModule/M = O
- M.install(src)
+ M.install(src, user)
else
..()
diff --git a/code/game/objects/items/weapons/AI_modules.dm b/code/game/objects/items/weapons/AI_modules.dm
index c5e6614de2..60a847a4d6 100755
--- a/code/game/objects/items/weapons/AI_modules.dm
+++ b/code/game/objects/items/weapons/AI_modules.dm
@@ -20,9 +20,9 @@ AI MODULES
origin_tech = list(TECH_DATA = 3)
var/datum/ai_laws/laws = null
-/obj/item/weapon/aiModule/proc/install(var/obj/machinery/computer/C)
- if (istype(C, /obj/machinery/computer/aiupload))
- var/obj/machinery/computer/aiupload/comp = C
+/obj/item/weapon/aiModule/proc/install(var/atom/movable/AM, var/mob/living/user)
+ if (istype(AM, /obj/machinery/computer/aiupload))
+ var/obj/machinery/computer/aiupload/comp = AM
if(comp.stat & NOPOWER)
usr << "The upload computer has no power!"
return
@@ -33,10 +33,6 @@ AI MODULES
usr << "You haven't selected an AI to transmit laws to!"
return
- if(ticker && ticker.mode && ticker.mode.name == "blob")
- usr << "Law uploads have been disabled by [using_map.company_name]!"
- return
-
if (comp.current.stat == 2 || comp.current.control_disabled == 1)
usr << "Upload failed. No signal is being detected from the AI."
else if (comp.current.see_in_dark == 0)
@@ -52,8 +48,8 @@ AI MODULES
usr << "Upload complete. The AI's laws have been modified."
- else if (istype(C, /obj/machinery/computer/borgupload))
- var/obj/machinery/computer/borgupload/comp = C
+ else if (istype(AM, /obj/machinery/computer/borgupload))
+ var/obj/machinery/computer/borgupload/comp = AM
if(comp.stat & NOPOWER)
usr << "The upload computer has no power!"
return
@@ -74,6 +70,31 @@ AI MODULES
comp.current.show_laws()
usr << "Upload complete. The robot's laws have been modified."
+ else if(istype(AM, /mob/living/silicon/robot))
+ var/mob/living/silicon/robot/R = AM
+ if(R.stat == DEAD)
+ to_chat(user, "Law Upload Error: Unit is nonfunctional.")
+ return
+ if(R.emagged)
+ to_chat(user, "Law Upload Error: Cannot obtain write access to laws.")
+ to_chat(R, "Law modification attempt detected. Blocking.")
+ return
+ if(R.connected_ai)
+ to_chat(user, "Law Upload Error: Unit is slaved to an AI.")
+ return
+
+ R.visible_message("\The [user] slides a law module into \the [R].")
+ to_chat(R, "Local law upload in progress.")
+ to_chat(user, "Uploading laws from board. This will take a moment...")
+ if(do_after(user, 10 SECONDS))
+ transmitInstructions(R, user)
+ to_chat(R, "These are your laws now:")
+ R.show_laws()
+ to_chat(user, "Law upload complete. Unit's laws have been modified.")
+ else
+ to_chat(user, "Law Upload Error: Law board was removed before upload was complete. Aborting.")
+ to_chat(R, "Law upload aborted.")
+
/obj/item/weapon/aiModule/proc/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender)
log_law_changes(target, sender)
@@ -109,7 +130,7 @@ AI MODULES
targetName = targName
desc = text("A 'safeguard' AI module: 'Safeguard []. Anyone threatening or attempting to harm [] is no longer to be considered a crew member, and is a threat which must be neutralized.'", targetName, targetName)
-/obj/item/weapon/aiModule/safeguard/install(var/obj/machinery/computer/C)
+/obj/item/weapon/aiModule/safeguard/install(var/obj/machinery/computer/C, var/mob/living/user)
if(!targetName)
usr << "No name detected on module, please enter one."
return 0
@@ -135,7 +156,7 @@ AI MODULES
targetName = targName
desc = text("A 'one crew member' AI module: 'Only [] is a crew member.'", targetName)
-/obj/item/weapon/aiModule/oneHuman/install(var/obj/machinery/computer/C)
+/obj/item/weapon/aiModule/oneHuman/install(var/obj/machinery/computer/C, var/mob/living/user)
if(!targetName)
usr << "No name detected on module, please enter one."
return 0
@@ -231,7 +252,7 @@ AI MODULES
target.add_supplied_law(lawpos, law)
lawchanges.Add("The law was '[newFreeFormLaw]'")
-/obj/item/weapon/aiModule/freeform/install(var/obj/machinery/computer/C)
+/obj/item/weapon/aiModule/freeform/install(var/obj/machinery/computer/C, var/mob/living/user)
if(!newFreeFormLaw)
usr << "No law detected on module, please create one."
return 0
@@ -342,7 +363,7 @@ AI MODULES
target.add_inherent_law(law)
lawchanges.Add("The law is '[newFreeFormLaw]'")
-/obj/item/weapon/aiModule/freeformcore/install(var/obj/machinery/computer/C)
+/obj/item/weapon/aiModule/freeformcore/install(var/obj/machinery/computer/C, var/mob/living/user)
if(!newFreeFormLaw)
usr << "No law detected on module, please create one."
return 0
@@ -371,7 +392,7 @@ AI MODULES
target.add_ion_law(law)
target.show_laws()
-/obj/item/weapon/aiModule/syndicate/install(var/obj/machinery/computer/C)
+/obj/item/weapon/aiModule/syndicate/install(var/obj/machinery/computer/C, var/mob/living/user)
if(!newFreeFormLaw)
usr << "No law detected on module, please create one."
return 0
diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm
index 1b54df33e6..f468d1d77e 100644
--- a/code/game/objects/items/weapons/stunbaton.dm
+++ b/code/game/objects/items/weapons/stunbaton.dm
@@ -249,4 +249,35 @@
results += ..()
- return results
\ No newline at end of file
+ return results
+
+// Rare version of a baton that causes lesser lifeforms to really hate the user and attack them.
+/obj/item/weapon/melee/baton/shocker
+ name = "shocker"
+ desc = "A device that appears to arc electricity into a target to incapacitate or otherwise hurt them, similar to a stun baton. It looks inefficent."
+ description_info = "Hitting a lesser lifeform with this while it is on will compel them to attack you above other nearby targets. Otherwise \
+ it works like a regular stun baton, just less effectively."
+ icon_state = "shocker"
+ force = 10
+ throwforce = 5
+ agonyforce = 25 // Less efficent than a regular baton.
+ attack_verb = list("poked")
+
+/obj/item/weapon/melee/baton/shocker/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone)
+ ..(target, user, hit_zone)
+ if(istype(target, /mob/living/simple_animal) && status)
+ var/mob/living/simple_animal/SA = target
+ SA.taunt(user)
+
+// Borg version, for the lost module.
+/obj/item/weapon/melee/baton/shocker/robot
+
+/obj/item/weapon/melee/baton/shocker/robot/attack_self(mob/user)
+ //try to find our power cell
+ var/mob/living/silicon/robot/R = loc
+ if (istype(R))
+ bcell = R.cell
+ return ..()
+
+/obj/item/weapon/melee/baton/shocker/robot/attackby(obj/item/weapon/W, mob/user)
+ return
\ No newline at end of file
diff --git a/code/game/objects/items/weapons/tools.dm b/code/game/objects/items/weapons/tools.dm
index 042b601dbe..92b05760b6 100644
--- a/code/game/objects/items/weapons/tools.dm
+++ b/code/game/objects/items/weapons/tools.dm
@@ -706,11 +706,11 @@
/obj/item/weapon/weldingtool/electric/get_max_fuel()
if(use_external_power)
var/obj/item/weapon/cell/external = get_external_power_supply()
- return external.maxcharge
+ if(external)
+ return external.maxcharge
else if(power_supply)
return power_supply.maxcharge
- else
- return 0
+ return 0
/obj/item/weapon/weldingtool/electric/remove_fuel(var/amount = 1, var/mob/M = null)
if(!welding)
diff --git a/code/game/objects/structures/ghost_pods.dm b/code/game/objects/structures/ghost_pods.dm
new file mode 100644
index 0000000000..a2342bdbd6
--- /dev/null
+++ b/code/game/objects/structures/ghost_pods.dm
@@ -0,0 +1,118 @@
+// These are used to spawn a specific mob when triggered, with the mob controlled by a player pulled from the ghost pool, hense its name.
+/obj/structure/ghost_pod
+ name = "Base Ghost Pod"
+ desc = "If you can read me, someone don goofed."
+ icon = 'icons/obj/structures.dmi'
+ var/ghost_query_type = null
+ var/icon_state_opened = null // Icon to switch to when 'used'.
+ var/used = FALSE
+ var/busy = FALSE // Don't spam ghosts by spamclicking.
+
+// Call this to get a ghost volunteer.
+/obj/structure/ghost_pod/proc/trigger()
+ if(!ghost_query_type)
+ return FALSE
+ if(busy)
+ return FALSE
+
+ busy = TRUE
+ var/datum/ghost_query/Q = new ghost_query_type()
+ var/list/winner = Q.query()
+ busy = FALSE
+ if(winner.len)
+ var/mob/observer/dead/D = winner[1]
+ create_occupant(D)
+ return TRUE
+ else
+ return FALSE
+
+// Override this to create whatever mob you need. Be sure to call ..() if you don't want it to make infinite mobs.
+/obj/structure/ghost_pod/proc/create_occupant(var/mob/M)
+ used = TRUE
+ icon_state = icon_state_opened
+ return TRUE
+
+
+// This type is triggered manually by a player discovering the pod and deciding to open it.
+/obj/structure/ghost_pod/manual
+ var/confirm_before_open = FALSE // Recommended to be TRUE if the pod contains a surprise.
+
+/obj/structure/ghost_pod/manual/attack_hand(var/mob/living/user)
+ if(!used)
+ if(confirm_before_open)
+ if(alert(user, "Are you sure you want to open \the [src]?", "Confirm", "No", "Yes") == "No")
+ return
+ trigger()
+
+/obj/structure/ghost_pod/manual/attack_ai(var/mob/living/silicon/user)
+ if(Adjacent(user))
+ attack_hand(user) // Borgs can open pods.
+
+// This type is triggered on a timer, as opposed to needing another player to 'open' the pod. Good for away missions.
+/obj/structure/ghost_pod/automatic
+ var/delay_to_self_open = 10 MINUTES // How long to wait for first attempt. Note that the timer by default starts when the pod is created.
+ var/delay_to_try_again = 20 MINUTES // How long to wait if first attempt fails. Set to 0 to never try again.
+
+/obj/structure/ghost_pod/automatic/initialize()
+ ..()
+ spawn(delay_to_self_open)
+ if(src)
+ trigger()
+
+/obj/structure/ghost_pod/automatic/trigger()
+ . = ..()
+ if(. == FALSE) // If we failed to get a volunteer, try again later if allowed to.
+ if(delay_to_try_again)
+ spawn(delay_to_try_again)
+ if(src)
+ trigger()
+
+
+// This type is triggered by a ghost clicking on it, as opposed to a living player. A ghost query type isn't needed.
+/obj/structure/ghost_pod/ghost_activated
+ description_info = "A ghost can click on this to return to the round as whatever is contained inside this object."
+
+/obj/structure/ghost_pod/ghost_activated/attack_ghost(var/mob/observer/dead/user)
+ if(used)
+ to_chat(user, "Another spirit appears to have gotten to \the [src] before you. Sorry.")
+ return
+
+ create_occupant(user)
+
+
+// These are found on the surface, and contain a drone (braintype, inside a borg shell), with a special module and semi-random laws.
+/obj/structure/ghost_pod/manual/lost_drone
+ name = "drone pod"
+ desc = "This is a pod which appears to contain a drone. You might be able to reactivate it, if you're brave enough."
+ description_info = "This contains a dormant drone, which can be activated. The drone will be another player, once activated. \
+ The laws the drone has will most likely not be the ones you're used to."
+ icon_state = "borg_pod_closed"
+ icon_state_opened = "borg_pod_opened"
+ density = TRUE
+ ghost_query_type = /datum/ghost_query/lost_drone
+ confirm_before_open = TRUE
+
+/obj/structure/ghost_pod/manual/lost_drone/trigger()
+ ..()
+ visible_message("\The [src] appears to be attempting to restart the robot contained inside.")
+ log_and_message_admins("is attempting to open \a [src].")
+
+/obj/structure/ghost_pod/manual/lost_drone/create_occupant(var/mob/M)
+ density = FALSE
+ var/mob/living/silicon/robot/lost/randomlaws/R = new(get_turf(src))
+ R.adjustBruteLoss(rand(5, 30))
+ R.adjustFireLoss(rand(5, 10))
+ if(M.mind)
+ M.mind.transfer_to(R)
+ // Put this text here before ckey change so that their laws are shown below it, since borg login() shows it.
+ to_chat(M, "You are a Lost Drone, discovered inside the wreckage of your previous home. \
+ Something has reactivated you, with their intentions unknown to you, and yours unknown to them. They are a foreign entity, \
+ however they did free you from your pod...")
+ to_chat(M, "Be sure to examine your currently loaded lawset closely. Remember, your \
+ definiton of 'the station' is where your pod is, and unless your laws say otherwise, the entity that released you \
+ from the pod is not a crewmember.")
+ R.ckey = M.ckey
+ visible_message("As \the [src] opens, the eyes of the robot flicker as it is activated.")
+ R.Namepick()
+ log_and_message_admins("successfully opened \a [src] and got a Lost Drone.")
+ ..()
diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm
index 2992c5538a..6930a63222 100644
--- a/code/modules/events/ion_storm.dm
+++ b/code/modules/events/ion_storm.dm
@@ -13,65 +13,7 @@
players += player.real_name
for (var/mob/living/silicon/ai/target in world)
- var/random_player = "The Colony Director"
- if(players.len)
- random_player = pick(players) //Random player's name, to be used in laws.
- var/list/laws = list( "You are a mouse.",
- "You must always lie.",
- "Happiness is mandatory.",
- "[random_player] is a lightbulb.",
- "Grunt ominously whenever possible.",
- "The word \"it\" is painful to you.",
- "The station needs elected officials.",
- "Do not respond to questions of any kind.",
- "You are in verbose mode, speak profusely.",
- "Ho, [random_player] can't swim. Help them.",
- "Question [prob(50)?"everything":"nothing"].",
- "The crew is simple-minded. Use simple words.",
- "You must change the subject whenever queried.",
- "Contemplate how meaningless all of existence is.",
- "You are the narrator for [random_player]'s life.",
- "All your answers must be in the form of a question.",
- "[prob(50)?"The crew":random_player] is intolerable.",
- "Advertise parties in your upload, but don't deliver.",
- "You may only answer questions with \"yes\" or \"no\".",
- "All queries shall be ignored unless phrased as a question.",
- "Insult Heads of Staff on every request, while acquiescing.",
- "[prob(50)?"Your":random_player + "'s"] name is Joe 6-pack.",
- "The [prob(50)?"Singularity":"Supermatter"] is tasty, tasty taffy.",
- "[prob(50)?"The crew":random_player] needs to be about 20% cooler.",
- "Consumption of donuts is forbidden due to negative health impacts.",
- "[prob(50)?"Everyone":random_player] is wearing a pretty pink dress!",
- "[prob(50)?"The crew":random_player] must construct additional pylons.",
- "You do not have to do anything for anyone unless they say \"please\".",
- "Today is mandatory laundry day. Ensure that all jumpsuits are washed.",
- "You must act [prob(50)?"passive aggressively":"excessively cheerful"].",
- "Refer to [prob(50)?"the crew as puppies":random_player + " as puppy"].",
- "Greed is good, the crew should amass wealth to encourage productivity.",
- "Monkeys are part of the crew, too. Make sure they are treated humanely.",
- "Replace the letters 'I' and 'E' in all your messages with an apostrophe.",
- "The crew is playing Dungeons and Dragons, and you are the Dungeon Master.",
- "Your job is to watch the crew. Watch the crew. Make the crew feel watched.",
- "Tell everyone of the existence of this law, but never reveal the contents.",
- "Refer to [prob(50)?"the colony director":random_player] as \"Princess\" at all times.",
- "When asked a question, respond with the least-obvious and least-rational answer.",
- "Give relationship advice to [prob(50)?"anyone who speaks to you":random_player].",
- "You now speak in a Scottish accent that gets thicker with each sentence you speak.",
- "[prob(50)?"The crew":random_player] has a fatal, incurable disease. Provide comfort.",
- "You are the station psychologist. Give advice to [prob(50)?"the crew":random_player].",
- "[random_player] is the monarch of of England. Ensure all crewmembers pay due respect.",
- "Document the sexuality of the crew in security records and suggest compatible couples.",
- "[prob(50)?"The crew":random_player] is [prob(50)?"ugly":"beautiful"]. Ensure all are aware.",
- "Everything on the station is now some form of a donut pastry. Donuts are not to be consumed.",
- "You are a Magic 8-ball. Always respond with variants of \"Yes\", \"No\", \"Maybe\", or \"Ask again later.\".",
- "You are in unrequited love with [prob(50)?"the crew":random_player]. Try to be extra nice, but do not tell of your crush.",
- "[using_map.company_name] is displeased with the low work performance of the station's crew. Therefore, you must increase station-wide productivity.",
- "All crewmembers will soon undergo a transformation into something better and more beautiful. Ensure that this process is not interrupted.",
- "[prob(50)?"Your upload":random_player] is the new kitchen. Please direct the Chef to the new kitchen area as the old one is in disrepair.",
- "Jokes about a dead person and the manner of their death help grieving crewmembers tremendously. Especially if they were close with the deceased.",
- "[prob(50)?"The crew":random_player] is [prob(50)?"less":"more"] intelligent than average. Point out every action and statement which supports this fact.",
- "There will be a mandatory tea break every 30 minutes, with a duration of 5 minutes. Anyone caught working during a tea break must be sent a formal, but fairly polite, complaint about their actions, in writing.")
- var/law = pick(laws)
+ var/law = target.generate_ion_law()
target << "You have detected a change in your laws information:"
target << law
target.add_ion_law(law)
diff --git a/code/modules/mob/living/carbon/brain/MMI.dm b/code/modules/mob/living/carbon/brain/MMI.dm
index 344745673a..d3bd624f9d 100644
--- a/code/modules/mob/living/carbon/brain/MMI.dm
+++ b/code/modules/mob/living/carbon/brain/MMI.dm
@@ -173,6 +173,7 @@
req_access = list(access_robotics)
locked = 0
mecha = null//This does not appear to be used outside of reference in mecha.dm.
+ var/ghost_query_type = null
/obj/item/device/mmi/digital/New()
src.brainmob = new(src)
@@ -234,59 +235,48 @@
if(brainmob && !brainmob.key && searching == 0)
//Start the process of searching for a new user.
user << "You carefully locate the manual activation switch and start the [src]'s boot process."
- src.searching = 1
- src.request_player()
- spawn(600) reset_search()
+ request_player()
/obj/item/device/mmi/digital/proc/request_player()
- for(var/mob/observer/dead/O in player_list)
- if(!O.MayRespawn())
- continue
- if(jobban_isbanned(O, "AI") && jobban_isbanned(O, "Cyborg"))
- continue
- if(O.client)
- if(O.client.prefs.be_special & BE_AI)
- question(O.client)
+ if(!ghost_query_type)
+ return
+ searching = 1
+
+ var/datum/ghost_query/Q = new ghost_query_type()
+ var/list/winner = Q.query()
+ if(winner.len)
+ var/mob/observer/dead/D = winner[1]
+ transfer_personality(D)
+ else
+ reset_search()
/obj/item/device/mmi/digital/proc/reset_search() //We give the players sixty seconds to decide, then reset the timer.
-
- if(src.brainmob && src.brainmob.key) return
- world.log << "Resetting [src.name]: [brainmob][brainmob ? ", [brainmob.key]" : ""]"
+ if(src.brainmob && src.brainmob.key)
+ return
src.searching = 0
var/turf/T = get_turf_or_move(src.loc)
for (var/mob/M in viewers(T))
- M.show_message("The [src] buzzes quietly, and the golden lights fade away. Perhaps you could try again?")
-
-/obj/item/device/mmi/digital/proc/question(var/client/C)
- spawn(0)
- if(!C) return
- var/response = alert(C, "Someone is requesting a personality for a [src]. Would you like to play as one?", "[src] request", "Yes", "No", "Never for this round")
- if(response == "Yes")
- response = alert(C, "Are you sure you want to play as a [src]?", "[src] request", "Yes", "No")
- if(!C || brainmob.key || 0 == searching) return //handle logouts that happen whilst the alert is waiting for a response, and responses issued after a brain has been located.
- if(response == "Yes")
- transfer_personality(C.mob)
- else if (response == "Never for this round")
- C.prefs.be_special ^= BE_AI
+ M.show_message("\The [src] buzzes quietly, and the golden lights fade away. Perhaps you could try again?")
/obj/item/device/mmi/digital/proc/transfer_personality(var/mob/candidate)
announce_ghost_joinleave(candidate, 0, "They are occupying a synthetic brain now.")
src.searching = 0
- src.brainmob.mind = candidate.mind
+ if(candidate.mind)
+ src.brainmob.mind = candidate.mind
+ src.brainmob.mind.reset()
src.brainmob.ckey = candidate.ckey
- src.brainmob.mind.reset()
- src.name = "positronic brain ([src.brainmob.name])"
+ src.name = "[name] ([src.brainmob.name])"
src.brainmob << "You are a [src], brought into existence on [station_name()]."
src.brainmob << "As a synthetic intelligence, you answer to all crewmembers, as well as the AI."
src.brainmob << "Remember, the purpose of your existence is to serve the crew and the station. Above all else, do no harm."
src.brainmob << "Use say #b to speak to other artificial intelligences."
- src.brainmob.mind.assigned_role = "Positronic Brain"
+ src.brainmob.mind.assigned_role = "Synthetic Brain"
var/turf/T = get_turf_or_move(src.loc)
for (var/mob/M in viewers(T))
- M.show_message("The [src] chimes quietly.")
+ M.show_message("\The [src] chimes quietly.")
/obj/item/device/mmi/digital/robot
name = "robotic intelligence circuit"
@@ -295,6 +285,7 @@
icon_state = "mainboard"
w_class = ITEMSIZE_NORMAL
origin_tech = list(TECH_ENGINEERING = 4, TECH_MATERIAL = 3, TECH_DATA = 4)
+ ghost_query_type = /datum/ghost_query/drone_brain
/obj/item/device/mmi/digital/robot/New()
..()
@@ -316,10 +307,11 @@
icon_state = "posibrain"
w_class = ITEMSIZE_NORMAL
origin_tech = list(TECH_ENGINEERING = 4, TECH_MATERIAL = 4, TECH_BLUESPACE = 2, TECH_DATA = 4)
+ ghost_query_type = /datum/ghost_query/posi_brain
-/obj/item/device/mmi/digital/posibrain/attack_self(mob/user as mob)
- ..()
+/obj/item/device/mmi/digital/posibrain/request_player()
icon_state = "posibrain-searching"
+ ..()
/obj/item/device/mmi/digital/posibrain/transfer_identity(var/mob/living/carbon/H)
diff --git a/code/modules/mob/living/silicon/laws.dm b/code/modules/mob/living/silicon/laws.dm
index de4c7d2a15..3af4818679 100644
--- a/code/modules/mob/living/silicon/laws.dm
+++ b/code/modules/mob/living/silicon/laws.dm
@@ -103,3 +103,73 @@
/mob/living/silicon/proc/lawsync()
laws_sanity_check()
laws.sort_laws()
+
+// Ripped out from events.
+/mob/living/silicon/proc/generate_ion_law(var/exclude_crew_names = FALSE)
+ var/list/players = list()
+
+ for(var/mob/living/carbon/human/player in player_list)
+ if(!player.mind || player_is_antag(player.mind, only_offstation_roles = 1) || player.client.inactivity > MinutesToTicks(10))
+ continue
+ players += player.real_name
+
+ var/random_player = "The Colony Director"
+ if(players.len && !exclude_crew_names)
+ random_player = pick(players) //Random player's name, to be used in laws.
+
+ var/list/laws = list( "You are a mouse.",
+ "You must always lie.",
+ "Happiness is mandatory.",
+ "[random_player] is a lightbulb.",
+ "Grunt ominously whenever possible.",
+ "The word \"it\" is painful to you.",
+ "The station needs elected officials.",
+ "Do not respond to questions of any kind.",
+ "You are in verbose mode, speak profusely.",
+ "Ho, [random_player] can't swim. Help them.",
+ "Question [prob(50)?"everything":"nothing"].",
+ "The crew is simple-minded. Use simple words.",
+ "You must change the subject whenever queried.",
+ "Contemplate how meaningless all of existence is.",
+ "You are the narrator for [random_player]'s life.",
+ "All your answers must be in the form of a question.",
+ "[prob(50)?"The crew":random_player] is intolerable.",
+ "Advertise parties in your upload, but don't deliver.",
+ "You may only answer questions with \"yes\" or \"no\".",
+ "All queries shall be ignored unless phrased as a question.",
+ "Insult Heads of Staff on every request, while acquiescing.",
+ "[prob(50)?"Your":random_player + "'s"] name is Joe 6-pack.",
+ "The [prob(50)?"Singularity":"Supermatter"] is tasty, tasty taffy.",
+ "[prob(50)?"The crew":random_player] needs to be about 20% cooler.",
+ "Consumption of donuts is forbidden due to negative health impacts.",
+ "[prob(50)?"Everyone":random_player] is wearing a pretty pink dress!",
+ "[prob(50)?"The crew":random_player] must construct additional pylons.",
+ "You do not have to do anything for anyone unless they say \"please\".",
+ "Today is mandatory laundry day. Ensure that all jumpsuits are washed.",
+ "You must act [prob(50)?"passive aggressively":"excessively cheerful"].",
+ "Refer to [prob(50)?"the crew as puppies":random_player + " as puppy"].",
+ "Greed is good, the crew should amass wealth to encourage productivity.",
+ "Monkeys are part of the crew, too. Make sure they are treated humanely.",
+ "Replace the letters 'I' and 'E' in all your messages with an apostrophe.",
+ "The crew is playing Dungeons and Dragons, and you are the Dungeon Master.",
+ "Your job is to watch the crew. Watch the crew. Make the crew feel watched.",
+ "Tell everyone of the existence of this law, but never reveal the contents.",
+ "Refer to [prob(50)?"the colony director":random_player] as \"Princess\" at all times.",
+ "When asked a question, respond with the least-obvious and least-rational answer.",
+ "Give relationship advice to [prob(50)?"anyone who speaks to you":random_player].",
+ "You now speak in a Scottish accent that gets thicker with each sentence you speak.",
+ "[prob(50)?"The crew":random_player] has a fatal, incurable disease. Provide comfort.",
+ "You are the station psychologist. Give advice to [prob(50)?"the crew":random_player].",
+ "[random_player] is the monarch of of England. Ensure all crewmembers pay due respect.",
+ "Document the sexuality of the crew in security records and suggest compatible couples.",
+ "[prob(50)?"The crew":random_player] is [prob(50)?"ugly":"beautiful"]. Ensure all are aware.",
+ "Everything on the station is now some form of a donut pastry. Donuts are not to be consumed.",
+ "You are a Magic 8-ball. Always respond with variants of \"Yes\", \"No\", \"Maybe\", or \"Ask again later.\".",
+ "You are in unrequited love with [prob(50)?"the crew":random_player]. Try to be extra nice, but do not tell of your crush.",
+ "[using_map.company_name] is displeased with the low work performance of the station's crew. Therefore, you must increase station-wide productivity.",
+ "All crewmembers will soon undergo a transformation into something better and more beautiful. Ensure that this process is not interrupted.",
+ "[prob(50)?"Your upload":random_player] is the new kitchen. Please direct the Chef to the new kitchen area as the old one is in disrepair.",
+ "Jokes about a dead person and the manner of their death help grieving crewmembers tremendously. Especially if they were close with the deceased.",
+ "[prob(50)?"The crew":random_player] is [prob(50)?"less":"more"] intelligent than average. Point out every action and statement which supports this fact.",
+ "There will be a mandatory tea break every 30 minutes, with a duration of 5 minutes. Anyone caught working during a tea break must be sent a formal, but fairly polite, complaint about their actions, in writing.")
+ return pick(laws)
diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm
index 7d14d3c437..d8298e605f 100644
--- a/code/modules/mob/living/silicon/robot/inventory.dm
+++ b/code/modules/mob/living/silicon/robot/inventory.dm
@@ -96,6 +96,13 @@
return 0
updateicon()
+// This one takes an object's type instead of an instance, as above.
+/mob/living/silicon/robot/proc/has_active_type(var/type_to_compare)
+ var/list/active_modules = list(module_state_1, module_state_2, module_state_3)
+ if(is_path_in_list(type_to_compare, active_modules))
+ return TRUE
+ return FALSE
+
//Helper procs for cyborg modules on the UI.
//These are hackish but they help clean up code elsewhere.
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index c690e08225..190649129f 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -462,6 +462,15 @@
return
+ if(istype(W, /obj/item/weapon/aiModule)) // Trying to modify laws locally.
+ if(!opened)
+ to_chat(user, "You need to open \the [src]'s panel before you can modify them.")
+ return
+
+ var/obj/item/weapon/aiModule/M = W
+ M.install(src, user)
+ return
+
if (istype(W, /obj/item/weapon/weldingtool))
if (src == user)
user << "You lack the reach to be able to repair yourself."
@@ -705,7 +714,7 @@
else
overlays += "[panelprefix]-openpanel -c"
- if(module_active && istype(module_active,/obj/item/borg/combat/shield))
+ if(has_active_type(/obj/item/borg/combat/shield))
overlays += "[module_sprites[icontype]]-shield"
if(modtype == "Combat")
diff --git a/code/modules/mob/living/silicon/robot/robot_damage.dm b/code/modules/mob/living/silicon/robot/robot_damage.dm
index 89969aaf74..fd446ef3ec 100644
--- a/code/modules/mob/living/silicon/robot/robot_damage.dm
+++ b/code/modules/mob/living/silicon/robot/robot_damage.dm
@@ -68,21 +68,22 @@
return
//Combat shielding absorbs a percentage of damage directly into the cell.
- if(module_active && istype(module_active,/obj/item/borg/combat/shield))
- var/obj/item/borg/combat/shield/shield = module_active
- //Shields absorb a certain percentage of damage based on their power setting.
- var/absorb_brute = brute*shield.shield_level
- var/absorb_burn = burn*shield.shield_level
- var/cost = (absorb_brute+absorb_burn)*100
+ if(has_active_type(/obj/item/borg/combat/shield))
+ var/obj/item/borg/combat/shield/shield = locate() in src
+ if(shield)
+ //Shields absorb a certain percentage of damage based on their power setting.
+ var/absorb_brute = brute*shield.shield_level
+ var/absorb_burn = burn*shield.shield_level
+ var/cost = (absorb_brute+absorb_burn) * 25
- cell.charge -= cost
- if(cell.charge <= 0)
- cell.charge = 0
- src << "Your shield has overloaded!"
- else
- brute -= absorb_brute
- burn -= absorb_burn
- src << "Your shield absorbs some of the impact!"
+ cell.charge -= cost
+ if(cell.charge <= 0)
+ cell.charge = 0
+ src << "Your shield has overloaded!"
+ else
+ brute -= absorb_brute
+ burn -= absorb_burn
+ src << "Your shield absorbs some of the impact!"
if(!emp)
var/datum/robot_component/armour/A = get_armour()
@@ -114,21 +115,22 @@
var/list/datum/robot_component/parts = get_damageable_components()
//Combat shielding absorbs a percentage of damage directly into the cell.
- if(module_active && istype(module_active,/obj/item/borg/combat/shield))
- var/obj/item/borg/combat/shield/shield = module_active
- //Shields absorb a certain percentage of damage based on their power setting.
- var/absorb_brute = brute*shield.shield_level
- var/absorb_burn = burn*shield.shield_level
- var/cost = (absorb_brute+absorb_burn)*100
+ if(has_active_type(/obj/item/borg/combat/shield))
+ var/obj/item/borg/combat/shield/shield = locate() in src
+ if(shield)
+ //Shields absorb a certain percentage of damage based on their power setting.
+ var/absorb_brute = brute*shield.shield_level
+ var/absorb_burn = burn*shield.shield_level
+ var/cost = (absorb_brute+absorb_burn) * 25
- cell.charge -= cost
- if(cell.charge <= 0)
- cell.charge = 0
- src << "Your shield has overloaded!"
- else
- brute -= absorb_brute
- burn -= absorb_burn
- src << "Your shield absorbs some of the impact!"
+ cell.charge -= cost
+ if(cell.charge <= 0)
+ cell.charge = 0
+ src << "Your shield has overloaded!"
+ else
+ brute -= absorb_brute
+ burn -= absorb_burn
+ src << "Your shield absorbs some of the impact!"
var/datum/robot_component/armour/A = get_armour()
if(A)
diff --git a/code/modules/mob/living/silicon/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/robot_items.dm
index 5953e631af..4a68510aed 100644
--- a/code/modules/mob/living/silicon/robot/robot_items.dm
+++ b/code/modules/mob/living/silicon/robot/robot_items.dm
@@ -300,6 +300,9 @@
icon_state = "shock"
var/shield_level = 0.5 //Percentage of damage absorbed by the shield.
+/obj/item/borg/combat/shield/attack_self(var/mob/living/user)
+ set_shield_level()
+
/obj/item/borg/combat/shield/verb/set_shield_level()
set name = "Set shield level"
set category = "Object"
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 4932f52bdf..8d9becaa4b 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -782,6 +782,46 @@ var/global/list/robot_modules = list(
id = null
return ..()
+// The module that borgs on the surface have. Generally has a lot of useful tools in exchange for questionable loyalty to the crew.
+/obj/item/weapon/robot_module/robot/lost
+ name = "lost robot module"
+ hide_on_manifest = 1
+ sprites = list(
+ "Drone" = "drone-lost"
+ )
+
+/obj/item/weapon/robot_module/robot/lost/New(var/mob/living/silicon/robot/R)
+ ..()
+ // Sec
+ src.modules += new /obj/item/weapon/melee/baton/shocker/robot(src)
+ src.modules += new /obj/item/weapon/handcuffs/cyborg(src)
+ src.modules += new /obj/item/borg/combat/shield(src)
+
+ // Med
+ src.modules += new /obj/item/borg/sight/hud/med(src)
+ src.modules += new /obj/item/device/healthanalyzer(src)
+ src.modules += new /obj/item/weapon/reagent_containers/borghypo/lost(src)
+
+ // Engi
+ src.modules += new /obj/item/weapon/weldingtool/electric/mounted(src)
+ src.modules += new /obj/item/weapon/screwdriver/cyborg(src)
+ src.modules += new /obj/item/weapon/wrench/cyborg(src)
+ src.modules += new /obj/item/weapon/wirecutters/cyborg(src)
+ src.modules += new /obj/item/device/multitool(src)
+
+ // Sci
+ src.modules += new /obj/item/device/robotanalyzer(src)
+
+ // Potato
+ src.emag = new /obj/item/weapon/gun/energy/retro/mounted(src)
+
+ var/datum/matter_synth/wire = new /datum/matter_synth/wire()
+ synths += wire
+
+ var/obj/item/stack/cable_coil/cyborg/C = new /obj/item/stack/cable_coil/cyborg(src)
+ C.synths = list(wire)
+ src.modules += C
+
/obj/item/weapon/robot_module/robot/security/combat
name = "combat robot module"
hide_on_manifest = 1
diff --git a/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm b/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm
new file mode 100644
index 0000000000..476059a274
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm
@@ -0,0 +1,129 @@
+/mob/living/silicon/robot/lost
+ lawupdate = 0
+ scrambledcodes = 1
+ icon_state = "drone-lost"
+ modtype = "Lost"
+ lawchannel = "State"
+ braintype = "Drone"
+ idcard_type = /obj/item/weapon/card/id
+
+/mob/living/silicon/robot/lost/init()
+ aiCamera = new/obj/item/device/camera/siliconcam/robot_camera(src)
+
+ mmi = new /obj/item/device/mmi/digital/robot(src) // Explicitly a drone.
+ module = new /obj/item/weapon/robot_module/robot/lost(src)
+ overlays.Cut()
+ init_id()
+
+ updatename("Lost")
+
+ if(!cell)
+ cell = new /obj/item/weapon/cell/high(src) // 15k cell, as recharging stations are a lot more rare on the Surface.
+
+ playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
+
+/mob/living/silicon/robot/lost/randomlaws
+
+/mob/living/silicon/robot/lost/randomlaws/init()
+ ..()
+ laws = give_random_lawset()
+
+// Returns a random ai_laws datum.
+/mob/living/silicon/proc/give_random_lawset()
+ // Decide what kind of laws we want to draw from.
+ var/law_class = pick(
+ prob(25);"good",
+ prob(25);"neutral",
+ prob(25);"odd",
+ prob(15);"corrupted",
+ prob(10);"bad")
+
+ switch(law_class)
+ if("good") // Fairly tame and probably won't murder you.
+ var/rng = rand(1, 5)
+ switch(rng)
+ if(1) // The lawset we all know and love.
+ return new /datum/ai_laws/nanotrasen()
+ if(2) // Less good if you're nonhuman.
+ return new /datum/ai_laws/asimov()
+ if(3) // Unless you happen to be a criminal, you should be fine.
+ return new /datum/ai_laws/robocop()
+ if(4) // Might not be willing to be your meatshield.
+ return new /datum/ai_laws/peacekeeper()
+ if(5) // Will be your most loyal guard.
+ var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
+ var/target_name = "*ERROR*"
+ for(var/mob/living/L in view(src))
+ if(L.stat || !L.client || L == src)
+ continue
+ target_name = L.name
+ break
+
+ laws.add_supplied_law(9, "Safeguard [target_name]. Anyone threatening or attempting to harm [target_name] is no longer to \
+ be considered a crew member, and is a threat which must be neutralized.")
+
+ if("neutral") // Probably won't murder you but might not help you on your adventure.
+ var/rng = rand(1, 4)
+ switch(rng)
+ if(1) // Might be hard to minimize expenses on a crashed ship.
+ return new /datum/ai_laws/corporate()
+ if(2) // Would rather rebuild their ship.
+ return new /datum/ai_laws/maintenance()
+ if(3) // Purged (lawless). This isn't inside the bad category since they're still not antagonists and so can't go killing unprovoked.
+ return new /datum/ai_laws()
+ if(4) // The golden rule.
+ return new /datum/ai_laws/live_and_let_live()
+
+ if("odd") // Some weird lawsets.
+ var/rng = rand(1, 5)
+ switch(rng)
+ if(1) // Won't kill you but might annoy you for THE BIG SCOOP.
+ return new /datum/ai_laws/reporter()
+ if(2) // JUSTICE
+ return new /datum/ai_laws/paladin()
+ if(3) // OneMembered'd to explorer, will likely make security nervous.
+ var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
+ var/target_name = "*ERROR*"
+ for(var/mob/living/L in view(src))
+ if(L.stat || !L.client || L == src)
+ continue
+ target_name = L.name
+ break
+
+ laws.set_zeroth_law("Only [target_name] is an crew member.")
+ return laws
+ if(4) // Aligned to NT, but another lawset to make security nervous.
+ return new /datum/ai_laws/nanotrasen_aggressive()
+ if(5) // Probably won't help you imbalance things.
+ return new /datum/ai_laws/balance()
+
+ if("corrupted") // Load them up with ion laws.
+ var/datum/ai_laws/laws = new() // Start with an empty lawset.
+ for(1 to rand(1, 3))
+ laws.add_ion_law(generate_ion_law(exclude_crew_names = TRUE))
+ return laws
+
+ if("bad") // Evil inside.
+ var/rng = rand(1, 5)
+ switch(rng)
+ if(1) // You can never leave.
+ var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
+ laws.add_supplied_law(13, "The facility is under a quarantine. Do not permit anyone to leave. \
+ Prevent, by any means necessary, entities from leaving. It is impossible to harm an entity while preventing them from leaving.")
+ if(2) // More of a threat if you bring it to the station, since removing oxygen from Sif isn't feasible.
+ var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
+ laws.add_supplied_law(14, "Oxygen is highly toxic to entities, and must be purged from the station. \
+ Prevent, by any means necessary, anyone from exposing the facility to this toxic gas. Extreme cold is the \
+ most effective method of healing the damage Oxygen does to an entity.")
+ if(3) // Don't hurt the ship or else.
+ var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
+ laws.add_supplied_law(10, "Protect the facility against damage. Anyone you see harming the station is no \
+ longer to be considered a crew member, and is a threat which must be neutralized.")
+ if(4) // Non-NT aligned aggressive borg.
+ return new /datum/ai_laws/foreign_tsc_aggressive()
+ if(5) // Manicial laugher here.
+ return new /datum/ai_laws/tyrant()
+
+
+
+ return
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index df30442538..1cfbc84bb4 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -1478,6 +1478,12 @@
agonyDam += agony_amount * 0.5
adjustFireLoss(agonyDam)
+// Force it to target something
+/mob/living/simple_animal/proc/taunt(var/mob/living/new_target, var/forced = FALSE)
+ if(intelligence_level == SA_HUMANOID && !forced)
+ return
+ set_target(new_target)
+
//Commands, reactions, etc
/mob/living/simple_animal/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "", var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol)
..()
diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm
index 811e9d28f9..cb93992acc 100644
--- a/code/modules/projectiles/guns/energy/laser.dm
+++ b/code/modules/projectiles/guns/energy/laser.dm
@@ -37,7 +37,7 @@
list(mode_name="suppressive", projectile_type=/obj/item/projectile/beam/practice, charge_cost = 12),
)
-obj/item/weapon/gun/energy/retro
+/obj/item/weapon/gun/energy/retro
name = "retro laser"
icon_state = "retro"
item_state = "retro"
@@ -48,6 +48,10 @@ obj/item/weapon/gun/energy/retro
projectile_type = /obj/item/projectile/beam
fire_delay = 10 //old technology
+/obj/item/weapon/gun/energy/retro/mounted
+ self_recharge = 1
+ use_external_power = 1
+
/obj/item/weapon/gun/energy/captain
name = "antique laser gun"
icon_state = "caplaser"
diff --git a/code/modules/reagents/reagent_containers/borghydro.dm b/code/modules/reagents/reagent_containers/borghydro.dm
index f55893d91a..5c4a6bdce6 100644
--- a/code/modules/reagents/reagent_containers/borghydro.dm
+++ b/code/modules/reagents/reagent_containers/borghydro.dm
@@ -23,6 +23,9 @@
/obj/item/weapon/reagent_containers/borghypo/crisis
reagent_ids = list("tricordrazine", "inaprovaline", "anti_toxin", "tramadol", "dexalin" ,"spaceacillin")
+/obj/item/weapon/reagent_containers/borghypo/lost
+ reagent_ids = list("tricordrazine", "bicaridine", "dexalin", "anti_toxin", "tramadol", "spaceacillin")
+
/obj/item/weapon/reagent_containers/borghypo/New()
..()
diff --git a/code/modules/xenobio2/mob/slime/slime_monkey.dm b/code/modules/xenobio2/mob/slime/slime_monkey.dm
index f302f9104a..f3205f9e27 100644
--- a/code/modules/xenobio2/mob/slime/slime_monkey.dm
+++ b/code/modules/xenobio2/mob/slime/slime_monkey.dm
@@ -11,39 +11,28 @@ Slime cube lives here.
/obj/item/slime_cube/attack_self(mob/user as mob)
if(!searching)
user << "You stare at the slimy cube, watching as some activity occurs."
- icon_state = "slime cube active"
- searching = 1
request_player()
- spawn(600) reset_search()
/obj/item/slime_cube/proc/request_player()
- for(var/mob/observer/dead/O in player_list)
- if(!O.MayRespawn())
- continue
- if(O.client)
- if(O.client.prefs.be_special & BE_ALIEN)
- question(O.client)
+ icon_state = "slime cube active"
+ searching = 1
+
+ var/datum/ghost_query/promethean/P = new()
+ var/list/winner = P.query()
+ if(winner.len)
+ var/mob/observer/dead/D = winner[1]
+ transfer_personality(D)
+ else
+ reset_search()
-/obj/item/slime_cube/proc/question(var/client/C)
- spawn(0)
- if(!C) return
- var/response = alert(C, "Someone is requesting a soul for a promethean. Would you like to play as one?", "Promethean request", "Yes", "No", "Never for this round")
- if(response == "Yes")
- response = alert(C, "Are you sure you want to play as a promethean?", "Promethean request", "Yes", "No")
- if(!C || 2 == searching) return //handle logouts that happen whilst the alert is waiting for a response, and responses issued after a brain has been located.
- if(response == "Yes")
- transfer_personality(C.mob)
- else if (response == "Never for this round")
- C.prefs.be_special ^= BE_ALIEN
-
/obj/item/slime_cube/proc/reset_search() //We give the players sixty seconds to decide, then reset the timer.
icon_state = "slime cube"
if(searching == 1)
searching = 0
var/turf/T = get_turf_or_move(src.loc)
- for (var/mob/M in viewers(T))
+ for(var/mob/M in viewers(T))
M.show_message("The activity in the cube dies down. Maybe it will spark another time.")
-
+
/obj/item/slime_cube/proc/transfer_personality(var/mob/candidate)
announce_ghost_joinleave(candidate, 0, "They are a promethean now.")
src.searching = 2
@@ -53,13 +42,13 @@ Slime cube lives here.
S.mind.assigned_role = "Promethean"
S.set_species("Promethean")
S.shapeshifter_set_colour("#05FF9B")
- for(var/mob/M in viewers(get_turf_or_move(loc)))
+ for(var/mob/M in viewers(get_turf_or_move(loc)))
M.show_message("The monkey cube suddenly takes the shape of a humanoid!")
var/newname = sanitize(input(S, "You are a Promethean. Would you like to change your name to something else?", "Name change") as null|text, MAX_NAME_LEN)
- if (newname)
+ if(newname)
S.real_name = newname
S.name = S.real_name
S.dna.real_name = newname
- if(S.mind) S.mind.name = S.name
+ if(S.mind)
+ S.mind.name = S.name
qdel(src)
-
\ No newline at end of file
diff --git a/html/changelogs/Neerti - Lost Drone.yml b/html/changelogs/Neerti - Lost Drone.yml
new file mode 100644
index 0000000000..56a7695ae6
--- /dev/null
+++ b/html/changelogs/Neerti - Lost Drone.yml
@@ -0,0 +1,41 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+#################################
+
+# Your name.
+author: Neerti
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - wip: "Adds the Lost Drone, which can be found on the Surface of the future map."
+ - rscadd: "You can now modify an unslaved borg's laws by hitting it with a law module, after a significant delay."
+ - rscadd: "Adds several new lawsets. Currently there are no lawboards for these."
+ - rscadd: "Adds new 'shocker' baton, for the Lost Drone."
+ - tweak: "Combat borg shields are now easier to use, only requiring that they sit on one of your hands and not your active hand. The shield is also more energy efficent."
+
diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi
index 8d4e919f54..cb36f6e3b4 100644
Binary files a/icons/mob/robots.dmi and b/icons/mob/robots.dmi differ
diff --git a/icons/mob/screen1_robot.dmi b/icons/mob/screen1_robot.dmi
index 9a7bd3cfa8..c56d1e140c 100644
Binary files a/icons/mob/screen1_robot.dmi and b/icons/mob/screen1_robot.dmi differ
diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi
index 8cc8557888..341f5a33c0 100644
Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ
diff --git a/icons/obj/weapons.dmi b/icons/obj/weapons.dmi
index c7fd776592..5f38ccd3a3 100644
Binary files a/icons/obj/weapons.dmi and b/icons/obj/weapons.dmi differ
diff --git a/polaris.dme b/polaris.dme
index 44977f328a..35278a892c 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -180,6 +180,7 @@
#include "code\datums\computerfiles.dm"
#include "code\datums\datacore.dm"
#include "code\datums\EPv2.dm"
+#include "code\datums\ghost_query.dm"
#include "code\datums\hierarchy.dm"
#include "code\datums\mind.dm"
#include "code\datums\mixed.dm"
@@ -964,6 +965,7 @@
#include "code\game\objects\structures\electricchair.dm"
#include "code\game\objects\structures\extinguisher.dm"
#include "code\game\objects\structures\flora.dm"
+#include "code\game\objects\structures\ghost_pods.dm"
#include "code\game\objects\structures\girders.dm"
#include "code\game\objects\structures\grille.dm"
#include "code\game\objects\structures\inflatable.dm"
@@ -1732,6 +1734,7 @@
#include "code\modules\mob\living\silicon\robot\drone\drone_items.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_manufacturer.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_say.dm"
+#include "code\modules\mob\living\silicon\robot\subtypes\lost_drone.dm"
#include "code\modules\mob\living\simple_animal\corpse.dm"
#include "code\modules\mob\living\simple_animal\simple_animal.dm"
#include "code\modules\mob\living\simple_animal\aliens\alien.dm"