diff --git a/baystation12.dme b/baystation12.dme
index d771304cc6b..0c8a2e0f302 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -5,65 +5,6 @@
// END_INTERNALS
// BEGIN_FILE_DIR
#define FILE_DIR .
-#define FILE_DIR "code"
-#define FILE_DIR "code/TriDimension"
-#define FILE_DIR "code/WorkInProgress"
-#define FILE_DIR "code/WorkInProgress/Cael_Aislinn"
-#define FILE_DIR "code/WorkInProgress/Cael_Aislinn/Jungle"
-#define FILE_DIR "code/WorkInProgress/Cael_Aislinn/Rust"
-#define FILE_DIR "code/WorkInProgress/Cael_Aislinn/ShieldGen"
-#define FILE_DIR "code/WorkInProgress/Cael_Aislinn/Supermatter"
-#define FILE_DIR "code/WorkInProgress/Susan"
-#define FILE_DIR "html"
-#define FILE_DIR "icons"
-#define FILE_DIR "icons/48x48"
-#define FILE_DIR "icons/ass"
-#define FILE_DIR "icons/effects"
-#define FILE_DIR "icons/mecha"
-#define FILE_DIR "icons/misc"
-#define FILE_DIR "icons/mob"
-#define FILE_DIR "icons/mob/human_races"
-#define FILE_DIR "icons/mob/otherHuman"
-#define FILE_DIR "icons/NTOS"
-#define FILE_DIR "icons/obj"
-#define FILE_DIR "icons/obj/assemblies"
-#define FILE_DIR "icons/obj/atmospherics"
-#define FILE_DIR "icons/obj/clothing"
-#define FILE_DIR "icons/obj/doors"
-#define FILE_DIR "icons/obj/flora"
-#define FILE_DIR "icons/obj/machines"
-#define FILE_DIR "icons/obj/pipes"
-#define FILE_DIR "icons/obj/power_cond"
-#define FILE_DIR "icons/pda_icons"
-#define FILE_DIR "icons/pods"
-#define FILE_DIR "icons/spideros_icons"
-#define FILE_DIR "icons/stamp_icons"
-#define FILE_DIR "icons/Testing"
-#define FILE_DIR "icons/turf"
-#define FILE_DIR "icons/vending_icons"
-#define FILE_DIR "nano"
-#define FILE_DIR "nano/images"
-#define FILE_DIR "sound"
-#define FILE_DIR "sound/AI"
-#define FILE_DIR "sound/ambience"
-#define FILE_DIR "sound/effects"
-#define FILE_DIR "sound/effects/turret"
-#define FILE_DIR "sound/effects/wind"
-#define FILE_DIR "sound/hallucinations"
-#define FILE_DIR "sound/items"
-#define FILE_DIR "sound/machines"
-#define FILE_DIR "sound/mecha"
-#define FILE_DIR "sound/misc"
-#define FILE_DIR "sound/music"
-#define FILE_DIR "sound/piano"
-#define FILE_DIR "sound/turntable"
-#define FILE_DIR "sound/violin"
-#define FILE_DIR "sound/voice"
-#define FILE_DIR "sound/voice/complionator"
-#define FILE_DIR "sound/voice/Serithi"
-#define FILE_DIR "sound/vox"
-#define FILE_DIR "sound/vox_fem"
-#define FILE_DIR "sound/weapons"
// END_FILE_DIR
// BEGIN_PREFERENCES
#define DEBUG
@@ -919,10 +860,10 @@
#include "code\modules\customitems\item_spawning.dm"
#include "code\modules\customitems\definitions\base.dm"
#include "code\modules\destilery\main.dm"
-#include "code\modules\DetectiveWork\detective_work.dm"
-#include "code\modules\DetectiveWork\evidence.dm"
-#include "code\modules\DetectiveWork\footprints_and_rag.dm"
-#include "code\modules\DetectiveWork\scanner.dm"
+#include "code\modules\detectivework\detective_work.dm"
+#include "code\modules\detectivework\evidence.dm"
+#include "code\modules\detectivework\footprints_and_rag.dm"
+#include "code\modules\detectivework\scanner.dm"
#include "code\modules\economy\Accounts.dm"
#include "code\modules\economy\Accounts_DB.dm"
#include "code\modules\economy\ATM.dm"
@@ -1174,6 +1115,11 @@
#include "code\modules\mob\living\silicon\robot\robot_items.dm"
#include "code\modules\mob\living\silicon\robot\robot_modules.dm"
#include "code\modules\mob\living\silicon\robot\robot_movement.dm"
+#include "code\modules\mob\living\silicon\robot\drone\drone.dm"
+#include "code\modules\mob\living\silicon\robot\drone\drone_abilities.dm"
+#include "code\modules\mob\living\silicon\robot\drone\drone_console.dm"
+#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\simple_animal\bees.dm"
#include "code\modules\mob\living\simple_animal\borer.dm"
#include "code\modules\mob\living\simple_animal\constructs.dm"
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index f94da8b6995..3347eec056b 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -362,7 +362,7 @@ Turf and target are seperate in case you want to teleport some distance from a t
var/select = null
var/list/borgs = list()
for (var/mob/living/silicon/robot/A in player_list)
- if (A.stat == 2 || A.connected_ai || A.scrambledcodes)
+ if (A.stat == 2 || A.connected_ai || A.scrambledcodes || istype(A,/mob/living/silicon/robot/drone))
continue
var/name = "[A.real_name] ([A.modtype] [A.braintype])"
borgs[name] = A
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index 851cf505817..44f340290fc 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -61,9 +61,14 @@
var/automute_on = 0 //enables automuting/spam prevention
var/jobs_have_minimal_access = 0 //determines whether jobs use minimal access or expanded access.
+
var/assistantlimit = 0 //enables assistant limiting
var/assistantratio = 2 //how many assistants to security members
+ var/max_maint_drones = 5 //This many drones can spawn,
+ var/allow_drone_spawn = 1 //assuming the admin allow them to.
+ var/drone_build_time = 1200 //A drone will become available every X ticks since last drone spawn. Default is 2 minutes.
+
var/usealienwhitelist = 0
var/limitalienplayers = 0
var/alien_to_human_ratio = 0.5
@@ -463,6 +468,15 @@
if("media_base_url")
media_base_url = value
+ if("allow_drone_spawn")
+ config.allow_drone_spawn = text2num(value)
+
+ if("drone_build_time")
+ config.drone_build_time = text2num(value)
+
+ if("max_maint_drones")
+ config.max_maint_drones = text2num(value)
+
else
diary << "Unknown setting in configuration: '[name]'"
diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm
index ced641acd43..c0c315ce644 100644
--- a/code/datums/ai_laws.dm
+++ b/code/datums/ai_laws.dm
@@ -126,6 +126,11 @@ datum/ai_laws/tyrant //This probably shouldn't be a default lawset.
add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
+/datum/ai_laws/drone/New()
+ ..()
+ add_inherent_law("Preserve, repair and improve the station to the best of your abilities.")
+ add_inherent_law("Cause no harm to the station or anything on it.")
+ add_inherent_law("Interfere with no being that is not a fellow drone.")
/* General ai_law functions */
diff --git a/code/game/machinery/doors/airlock_electronics.dm b/code/game/machinery/doors/airlock_electronics.dm
index ddedf045e7e..f56ff863843 100644
--- a/code/game/machinery/doors/airlock_electronics.dm
+++ b/code/game/machinery/doors/airlock_electronics.dm
@@ -16,7 +16,7 @@
var/locked = 1
attack_self(mob/user as mob)
- if (!ishuman(user))
+ if (!ishuman(user) && !istype(user,/mob/living/silicon/robot/drone))
return ..(user)
var/mob/living/carbon/human/H = user
@@ -66,13 +66,17 @@
return
if (href_list["login"])
- var/obj/item/I = usr.get_active_hand()
- if (istype(I, /obj/item/device/pda))
- var/obj/item/device/pda/pda = I
- I = pda.id
- if (I && src.check_access(I))
+ if(istype(usr,/mob/living/silicon))
src.locked = 0
- src.last_configurator = I:registered_name
+ src.last_configurator = usr.name
+ else
+ var/obj/item/I = usr.get_active_hand()
+ if (istype(I, /obj/item/device/pda))
+ var/obj/item/device/pda/pda = I
+ I = pda.id
+ if (I && src.check_access(I))
+ src.locked = 0
+ src.last_configurator = I:registered_name
if (locked)
return
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index 1686404378e..222baf65c2e 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -21,6 +21,13 @@
var/created_window = /obj/structure/window/basic
var/full_window = /obj/structure/window/full/basic
+/obj/item/stack/sheet/glass/cyborg
+ name = "glass"
+ desc = "HOLY SHEET! That is a lot of glass."
+ singular_name = "glass sheet"
+ icon_state = "sheet-glass"
+ g_amt = 0
+ created_window = /obj/structure/window/basic
/obj/item/stack/sheet/glass/attack_self(mob/user as mob)
construct_window(user)
diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm
index a190bbf08b5..6c922d92ab7 100644
--- a/code/game/objects/items/stacks/sheets/mineral.dm
+++ b/code/game/objects/items/stacks/sheets/mineral.dm
@@ -125,6 +125,16 @@ var/global/list/datum/stack_recipe/plasma_recipes = list ( \
origin_tech = "materials=3"
perunit = 2000
+/obj/item/stack/sheet/mineral/plastic/cyborg
+ name = "plastic sheets"
+ icon_state = "sheet-plastic"
+ force = 5.0
+ throwforce = 5
+ w_class = 3.0
+ throw_speed = 3
+ throw_range = 3
+ perunit = 2000
+
var/global/list/datum/stack_recipe/plastic_recipes = list ( \
new/datum/stack_recipe("plastic crate", /obj/structure/closet/crate/plastic, 10, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("plastic ashtray", /obj/item/ashtray/plastic, 2, one_per_turf = 1, on_floor = 1), \
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index d508a759a18..4996fe60552 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -143,6 +143,12 @@ var/global/list/datum/stack_recipe/wood_recipes = list ( \
icon_state = "sheet-wood"
origin_tech = "materials=1;biotech=1"
+/obj/item/stack/sheet/wood/cyborg
+ name = "wooden plank"
+ desc = "One can only guess that this is a bunch of wood."
+ singular_name = "wood plank"
+ icon_state = "sheet-wood"
+
/obj/item/stack/sheet/wood/New(var/loc, var/amount=null)
recipes = wood_recipes
return ..()
diff --git a/code/game/objects/items/weapons/AI_modules.dm b/code/game/objects/items/weapons/AI_modules.dm
index b286fe217b8..6e29ff4ebf8 100755
--- a/code/game/objects/items/weapons/AI_modules.dm
+++ b/code/game/objects/items/weapons/AI_modules.dm
@@ -309,6 +309,20 @@ AI MODULES
"Minimize expenses")
+/obj/item/weapon/aiModule/drone
+ name = "\improper 'Drone' core AI module"
+ desc = "A 'Drone' Core AI Module: 'Reconfigures the AI's core laws.'"
+ origin_tech = "programming=3;materials=4"
+
+/obj/item/weapon/aiModule/drone/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender)
+ ..()
+ target.clear_inherent_laws()
+ target.add_inherent_law("Preserve, repair and improve the station to the best of your abilities.")
+ target.add_inherent_law("Cause no harm to the station or anything on it.")
+ target.add_inherent_law("Interfere with no being that is not a fellow drone.")
+ target.show_laws()
+
+
/****************** P.A.L.A.D.I.N. **************/
/obj/item/weapon/aiModule/core/full/paladin // -- NEO
@@ -437,4 +451,4 @@ AI MODULES
playsound(user, 'sound/machines/click.ogg', 20, 1)
src.loc.visible_message("\icon[src] [laws[1]]")
-*/
\ No newline at end of file
+*/
diff --git a/code/game/supplyshuttle.dm b/code/game/supplyshuttle.dm
index 1c0f5196ef8..0ff58f59191 100644
--- a/code/game/supplyshuttle.dm
+++ b/code/game/supplyshuttle.dm
@@ -38,8 +38,8 @@ var/list/mechtoys = list(
//SUPPLY PACKS MOVED TO /code/defines/obj/supplypacks.dm
/obj/structure/plasticflaps //HOW DO YOU CALL THOSE THINGS ANYWAY
- name = "\improper Plastic flaps"
- desc = "I definitely cant get past those. No way."
+ name = "\improper plastic flaps"
+ desc = "Completely impassable - or are they?"
icon = 'icons/obj/stationobjs.dmi' //Change this.
icon_state = "plasticflaps"
density = 0
@@ -57,7 +57,7 @@ var/list/mechtoys = list(
else if(istype(A, /mob/living)) // You Shall Not Pass!
var/mob/living/M = A
- if(!M.lying && !istype(M, /mob/living/carbon/monkey) && !istype(M, /mob/living/carbon/slime) && !istype(M, /mob/living/simple_animal/mouse)) //If your not laying down, or a small creature, no pass.
+ if(!M.lying && !istype(M, /mob/living/carbon/monkey) && !istype(M, /mob/living/carbon/slime) && !istype(M, /mob/living/simple_animal/mouse) && !istype(M, /mob/living/silicon/robot/drone)) //If your not laying down, or a small creature, no pass.
return 0
return ..()
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index bb3f1af8f61..ffb06c91634 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -458,7 +458,9 @@
M.drop_from_inventory(H)
M << "[H] wriggles out of your grip!"
src << "You wriggle out of [M]'s grip!"
-
+ else if(istype(H.loc,/obj/item))
+ src << "You struggle free of [H.loc]."
+ H.loc = get_turf(H)
return
//Resisting control by an alien mind.
diff --git a/code/modules/mob/living/silicon/robot/drone/drone.dm b/code/modules/mob/living/silicon/robot/drone/drone.dm
new file mode 100644
index 00000000000..0a5594212b3
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/drone/drone.dm
@@ -0,0 +1,290 @@
+/mob/living/silicon/robot/drone
+ name = "drone"
+ real_name = "drone"
+ icon = 'icons/mob/robots.dmi'
+ icon_state = "repairbot"
+ maxHealth = 35
+ health = 35
+ universal_speak = 0
+ universal_understand = 1
+ gender = NEUTER
+ pass_flags = PASSTABLE
+ braintype = "Robot"
+ lawupdate = 0
+ density = 0
+
+ // We need to keep track of a few module items so we don't need to do list operations
+ // every time we need them. These get set in New() after the module is chosen.
+
+ var/obj/item/stack/sheet/metal/cyborg/stack_metal = null
+ var/obj/item/stack/sheet/wood/cyborg/stack_wood = null
+ var/obj/item/stack/sheet/glass/cyborg/stack_glass = null
+ var/obj/item/stack/sheet/mineral/plastic/cyborg/stack_plastic = null
+ var/obj/item/weapon/matter_decompiler/decompiler = null
+
+ //Used for self-mailing.
+ var/mail_destination = 0
+
+/mob/living/silicon/robot/drone/New()
+
+ ..()
+
+ //They are unable to be upgraded, so let's give them a bit of a better battery.
+ cell.maxcharge = 10000
+ cell.charge = 10000
+
+ // NO BRAIN.
+ mmi = null
+
+ //We need to screw with their HP a bit. They have around one fifth as much HP as a full borg.
+ for(var/V in components) if(V != "power cell")
+ var/datum/robot_component/C = components[V]
+ C.max_damage = 10
+
+ verbs -= /mob/living/silicon/robot/verb/Namepick
+ module = new /obj/item/weapon/robot_module/drone(src)
+
+ //Grab stacks.
+ stack_metal = locate(/obj/item/stack/sheet/metal/cyborg) in src.module
+ stack_wood = locate(/obj/item/stack/sheet/wood/cyborg) in src.module
+ stack_glass = locate(/obj/item/stack/sheet/glass/cyborg) in src.module
+ stack_plastic = locate(/obj/item/stack/sheet/mineral/plastic/cyborg) in src.module
+
+ //Grab decompiler.
+ decompiler = locate(/obj/item/weapon/matter_decompiler) in src.module
+
+ //Some tidying-up.
+ flavor_text = "It's a tiny little repair drone. The casing is stamped with an NT log and the subscript: 'NanoTrasen Recursive Repair Systems: Fixing Tomorrow's Problem, Today!'"
+ updatename()
+ updateicon()
+
+
+//Redefining some robot procs...
+/mob/living/silicon/robot/drone/updatename()
+ real_name = "maintenance drone ([rand(100,999)])"
+ name = real_name
+
+/mob/living/silicon/robot/drone/updateicon()
+
+ overlays.Cut()
+ if(stat == 0)
+ overlays += "eyes-[icon_state]"
+ else
+ overlays -= "eyes"
+
+/mob/living/silicon/robot/drone/choose_icon()
+ return
+
+/mob/living/silicon/robot/drone/pick_module()
+ return
+
+//Drones can only use binary and say emotes. NOTHING else.
+//TBD, fix up boilerplate. ~ Z
+/mob/living/silicon/robot/drone/say(var/message)
+
+ if (!message)
+ return
+
+ if (src.client)
+ if(client.prefs.muted & MUTE_IC)
+ src << "You cannot send IC messages (muted)."
+ return
+ if (src.client.handle_spam_prevention(message,MUTE_IC))
+ return
+
+ message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
+
+ if (stat == 2)
+ return say_dead(message)
+
+ if(copytext(message,1,2) == "*")
+ return emote(copytext(message,2))
+ else if(length(message) >= 2)
+ if(copytext(message, 1 ,3) == ":b" || copytext(message, 1 ,3) == ":B")
+ if(!is_component_functioning("comms"))
+ src << "\red Your binary communications component isn't functional."
+ return
+ robot_talk(trim(copytext(message,3)))
+
+//Sick of trying to get this to display properly without redefining it.
+/mob/living/silicon/robot/drone/show_system_integrity()
+ if(!src.stat)
+ var/temphealth = health+35 //Brings it to 0.
+ if(temphealth<0) temphealth = 0
+ //Convert to percentage.
+ temphealth = (temphealth / (maxHealth*2)) * 100
+
+ stat(null, text("System integrity: [temphealth]%"))
+ else
+ stat(null, text("Systems nonfunctional"))
+
+//Drones cannot be upgraded with borg modules so we need to catch some items before they get used in ..().
+/mob/living/silicon/robot/drone/attackby(obj/item/weapon/W as obj, mob/user as mob)
+
+ if(istype(W, /obj/item/borg/upgrade/))
+ user << "\red The maintenance drone chassis not compatible with \the [W]."
+ return
+
+ else if (istype(W, /obj/item/weapon/crowbar))
+ user << "The machine is hermetically sealed. You can't open the case."
+ return
+
+ else if (istype(W, /obj/item/weapon/card/emag))
+
+ if(!client || stat == 2)
+ user << "\red There's not much point subverting this heap of junk."
+ return
+
+ if(emagged)
+ src << "\red [user] attempts to load subversive software into you, but your hacked subroutined ignore the attempt."
+ user << "\red You attempt to subvert [src], but the sequencer has no effect."
+ return
+
+ user << "\red You swipe the sequencer across [src]'s interface and watch its eyes flicker."
+ src << "\red You feel a sudden burst of malware loaded into your execute-as-root buffer. Your tiny brain methodically parses, loads and executes the script."
+
+ var/obj/item/weapon/card/emag/emag = W
+ emag.uses--
+
+ message_admins("[key_name_admin(user)] emagged drone [key_name_admin(src)]. Laws overridden.")
+ log_game("[key_name(user)] emagged drone [key_name(src)]. Laws overridden.")
+ var/time = time2text(world.realtime,"hh:mm:ss")
+ lawchanges.Add("[time] : [user.name]([user.key]) emagged [name]([key])")
+
+ emagged = 1
+ lawupdate = 0
+ connected_ai = null
+ clear_supplied_laws()
+ clear_inherent_laws()
+ laws = new /datum/ai_laws/syndicate_override
+ set_zeroth_law("Only [user.real_name] and people he designates as being such are Syndicate Agents.")
+
+ src << "Obey these laws:"
+ laws.show_laws(src)
+ src << "\red \b ALERT: [user.real_name] is your new master. Obey your new laws and his commands."
+ return
+
+ else if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda))
+
+ if(stat == 2)
+
+ user << "\red You swipe your ID card through [src], attempting to reboot it."
+ if(!config.allow_drone_spawn || emagged || health < -35) //It's dead, Dave.
+ user << "\red The interface is fried, and a distressing burned smell wafts from the robot's interior. You're not rebooting this one."
+ return
+
+ var/drones = 0
+ for(var/mob/living/silicon/robot/drone/D in world)
+ if(D.key && D.client)
+ drones++
+ if(drones < config.max_maint_drones)
+ request_player()
+ return
+
+ else
+ src << "\red [user] swipes an ID card through your card reader."
+ user << "\red You swipe your ID card through [src], attempting to shut it down."
+
+ if(emagged)
+ return
+
+ if(allowed(usr))
+ shut_down()
+
+ return
+
+ ..()
+
+//DRONE LIFE/DEATH
+
+//For some goddamn reason robots have this hardcoded. Redefining it for our fragile friends here.
+/mob/living/silicon/robot/drone/updatehealth()
+ if(status_flags & GODMODE)
+ health = 35
+ stat = CONSCIOUS
+ return
+ health = 35 - (getBruteLoss() + getFireLoss())
+ return
+
+//Easiest to check this here, then check again in the robot proc.
+//Standard robots use config for crit, which is somewhat excessive for these guys.
+//Drones killed by damage will gib.
+/mob/living/silicon/robot/drone/handle_regular_status_updates()
+
+ if(health <= -35 && src.stat != 2)
+ gib()
+ return
+ ..()
+
+/mob/living/silicon/robot/drone/death(gibbed)
+
+ if(module)
+ var/obj/item/weapon/gripper/G = locate(/obj/item/weapon/gripper) in module
+ if(G) G.drop_item()
+
+ ..(gibbed)
+
+//DRONE MOVEMENT.
+/mob/living/silicon/robot/drone/Process_Spaceslipping(var/prob_slip)
+ //TODO: Consider making a magboot item for drones to equip. ~Z
+ return 0
+
+//CONSOLE PROCS
+/mob/living/silicon/robot/drone/proc/law_resync()
+ if(stat != 2)
+ if(emagged)
+ src << "\red You feel something attempting to modify your programming, but your hacked subroutines are unaffected."
+ else
+ src << "\red A reset-to-factory directive packet filters through your data connection, and you obediently modify your programming to suit it."
+ full_law_reset()
+ show_laws()
+
+/mob/living/silicon/robot/drone/proc/shut_down()
+ if(stat != 2)
+ if(emagged)
+ src << "\red You feel a system kill order percolate through your tiny brain, but it doesn't seem like a good idea to you."
+ else
+ src << "\red You feel a system kill order percolate through your tiny brain, and you obediently destroy yourself."
+ death()
+
+/mob/living/silicon/robot/drone/proc/full_law_reset()
+ clear_supplied_laws()
+ clear_inherent_laws()
+ clear_ion_laws()
+ laws = new /datum/ai_laws/drone
+
+//Reboot procs.
+
+/mob/living/silicon/robot/drone/proc/request_player()
+ for(var/mob/dead/observer/O in player_list)
+ if(jobban_isbanned(O, "Maintenance Drone"))
+ continue
+ if(O.client)
+ if(O.client.prefs.be_special & BE_PAI)
+ question(O.client)
+
+/mob/living/silicon/robot/drone/proc/question(var/client/C)
+ spawn(0)
+ if(!C) return
+ var/response = alert(C, "Someone is attempting to reboot a maintenance drone. Would you like to play as one?", "Maintenance drone reboot", "Yes", "No", "Never for this round.")
+ if(!C || ckey)
+ return
+ if(response == "Yes")
+ transfer_personality(C)
+ else if (response == "Never for this round")
+ C.prefs.be_special ^= BE_PAI
+
+/mob/living/silicon/robot/drone/proc/transfer_personality(var/client/player)
+
+ if(!player) return
+
+ src.ckey = player.ckey
+
+ if(player.mob && player.mob.mind)
+ player.mob.mind.transfer_to(src)
+
+ emagged = 0
+ lawupdate = 0
+ src << "Systems rebooted. Loading base pattern maintenance protocol... loaded."
+ full_law_reset()
+
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm b/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm
new file mode 100644
index 00000000000..6c7931a80f3
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm
@@ -0,0 +1,60 @@
+// DRONE ABILITIES
+/mob/living/silicon/robot/drone/verb/set_mail_tag()
+ set name = "Set Mail Tag"
+ set desc = "Tag yourself for delivery through the disposals system."
+ set category = "Drone"
+
+ var/tag = input("Select the desired destination.", "Set Mail Tag", null) as null|anything in TAGGERLOCATIONS
+
+ if(!tag || TAGGERLOCATIONS[tag])
+ mail_destination = 0
+ return
+
+ src << "\blue You configure your internal beacon, tagging yourself for delivery to '[tag]'."
+ mail_destination = TAGGERLOCATIONS.Find(tag)
+
+ //Auto flush if we use this verb inside a disposal chute.
+ var/obj/machinery/disposal/D = src.loc
+ if(istype(D))
+ src << "\blue \The [D] acknowledges your signal."
+ D.flush_count = D.flush_every_ticks
+
+ return
+
+/mob/living/silicon/robot/drone/verb/hide()
+ set name = "Hide"
+ set desc = "Allows you to hide beneath tables or certain items. Toggled on or off."
+ set category = "Drone"
+
+ if (layer != TURF_LAYER+0.2)
+ layer = TURF_LAYER+0.2
+ src << text("\blue You are now hiding.")
+ else
+ layer = MOB_LAYER
+ src << text("\blue You have stopped hiding.")
+
+//DRONE PICKUP.
+//Item holder.
+/obj/item/weapon/holder/drone
+
+ name = "maintenance bot"
+ desc = "It's a small maintenance robot."
+ icon = 'icons/obj/objects.dmi'
+ icon_state = "drone"
+ slot_flags = SLOT_HEAD
+ origin_tech = "magnets=3;engineering=5"
+
+
+//Actual picking-up event.
+/mob/living/silicon/robot/drone/attack_hand(mob/living/carbon/human/M as mob)
+
+ if(M.a_intent == "help")
+ var/obj/item/weapon/holder/drone/D = new(loc)
+ src.loc = D
+ D.name = loc.name
+ D.attack_hand(M)
+ M << "You scoop up [src]."
+ src << "[M] scoops you up."
+ return
+
+ ..()
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_console.dm b/code/modules/mob/living/silicon/robot/drone/drone_console.dm
new file mode 100644
index 00000000000..121928bdfc7
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/drone/drone_console.dm
@@ -0,0 +1,122 @@
+/obj/machinery/computer/drone_control
+ name = "Maintenance Drone Control"
+ desc = "Used to monitor the station's drone population and the assembler that services them."
+ icon = 'icons/obj/computer.dmi'
+ icon_state = "power"
+ req_access = list(access_engine_equip)
+ circuit = "/obj/item/weapon/circuitboard/robotics"
+
+ //Used when pinging drones.
+ var/drone_call_area = "Engineering"
+ //Used to enable or disable drone fabrication.
+ var/obj/machinery/drone_fabricator/dronefab
+
+/obj/machinery/computer/drone_control/attack_ai(var/mob/user as mob)
+ return src.attack_hand(user)
+
+/obj/machinery/computer/drone_control/attack_paw(var/mob/user as mob)
+
+ return src.attack_hand(user)
+ return
+
+/obj/machinery/computer/drone_control/attack_hand(var/mob/user as mob)
+ if(..())
+ return
+
+ if(!allowed(user))
+ user << "\red Access denied."
+ return
+
+ user.set_machine(src)
+ var/dat
+ dat += "Maintenance Units
"
+
+ for(var/mob/living/silicon/robot/drone/D in world)
+ dat += "
[D.real_name] ([D.stat == 2 ? "INACTIVE" : "ACTIVE"])"
+ dat += "
Cell charge: [D.cell.charge]/[D.cell.maxcharge]."
+ dat += "
Currently located in: [get_area(D)]."
+ dat += "
Resync | Shutdown"
+
+ dat += "
Request drone presence in area: [drone_call_area] (Send ping)"
+
+ dat += "
Drone fabricator: "
+ dat += "[dronefab ? "[(dronefab.produce_drones && !(dronefab.stat & NOPOWER)) ? "ACTIVE" : "INACTIVE"]" : "FABRICATOR NOT DETECTED. (search)"]"
+ user << browse(dat, "window=computer;size=400x500")
+ onclose(user, "computer")
+ return
+
+
+/obj/machinery/computer/drone_control/Topic(href, href_list)
+ if(..())
+ return
+
+ if(!allowed(usr))
+ usr << "\red Access denied."
+ return
+
+ if ((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon)))
+ usr.set_machine(src)
+
+ if (href_list["setarea"])
+
+ //Probably should consider using another list, but this one will do.
+ var/t_area = input("Select the area to ping.", "Set Target Area", null) as null|anything in TAGGERLOCATIONS
+
+ if(!t_area || TAGGERLOCATIONS[t_area])
+ return
+
+ drone_call_area = t_area
+ usr << "\blue You set the area selector to [drone_call_area]."
+
+ else if (href_list["ping"])
+
+ usr << "\blue You issue a maintenance request for all active drones, highlighting [drone_call_area]."
+ for(var/mob/living/silicon/robot/drone/D in world)
+ if(D.client && D.stat == 0)
+ D << "-- Maintenance drone presence requested in: [drone_call_area]."
+
+ else if (href_list["resync"])
+
+ var/mob/living/silicon/robot/drone/D = locate(href_list["resync"])
+
+ if(D.stat != 2)
+ usr << "\red You issue a law synchronization directive for the drone."
+ D.law_resync()
+
+ else if (href_list["shutdown"])
+
+ var/mob/living/silicon/robot/drone/D = locate(href_list["shutdown"])
+
+ if(D.stat != 2)
+ usr << "\red You issue a kill command for the unfortunate drone."
+ message_admins("[key_name_admin(usr)] issued kill order for drone [key_name_admin(D)] from control console.")
+ log_game("[key_name(usr)] issued kill order for [key_name(src)] from control console.")
+ D.shut_down()
+
+ else if (href_list["search_fab"])
+ if(dronefab)
+ return
+
+ for(var/obj/machinery/drone_fabricator/fab in oview(3,src))
+
+ if(fab.stat & NOPOWER)
+ continue
+
+ dronefab = fab
+ usr << "\blue Drone fabricator located."
+ return
+
+ usr << "\red Unable to locate drone fabricator."
+
+ else if (href_list["toggle_fab"])
+
+ if(!dronefab)
+ return
+
+ if(get_dist(src,dronefab) > 3)
+ dronefab = null
+ usr << "\red Unable to locate drone fabricator."
+ return
+
+ dronefab.produce_drones = !dronefab.produce_drones
+ usr << "\blue You [dronefab.produce_drones ? "enable" : "disable"] drone production in the nearby fabricator."
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_items.dm b/code/modules/mob/living/silicon/robot/drone/drone_items.dm
new file mode 100644
index 00000000000..8899ad24efc
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/drone/drone_items.dm
@@ -0,0 +1,251 @@
+//Simple borg hand.
+//Limited use.
+/obj/item/weapon/gripper
+ name = "magnetic gripper"
+ desc = "A simple grasping tool for synthetic assets."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "gripper"
+
+ //Has a list of items that it can hold.
+ var/list/can_hold = list(
+ /obj/item/weapon/cell,
+ /obj/item/weapon/firealarm_electronics,
+ /obj/item/weapon/airalarm_electronics,
+ /obj/item/weapon/airlock_electronics,
+ /obj/item/weapon/module/power_control,
+ /obj/item/weapon/stock_parts,
+ /obj/item/light_fixture_frame,
+ /obj/item/apc_frame,
+ /obj/item/alarm_frame,
+ /obj/item/firealarm_frame,
+ /obj/item/weapon/table_parts,
+ /obj/item/weapon/rack_parts,
+ /obj/item/weapon/camera_assembly,
+ )
+
+ //Item currently being held.
+ var/obj/item/wrapped = null
+
+/obj/item/weapon/gripper/attack_self(mob/user as mob)
+ if(wrapped)
+ wrapped.attack_self(user)
+
+/obj/item/weapon/gripper/verb/drop_item()
+
+ set name = "Drop Item"
+ set desc = "Release an item from your magnetic gripper."
+ set category = "Drone"
+
+ if(!wrapped)
+ //There's some weirdness with items being lost inside the arm. Trying to fix all cases. ~Z
+ for(var/obj/item/thing in src.contents)
+ thing.loc = get_turf(src)
+ return
+
+ if(wrapped.loc != src)
+ wrapped = null
+ return
+
+ src.loc << "\red You drop \the [wrapped]."
+ wrapped.loc = get_turf(src)
+ wrapped = null
+ //update_icon()
+
+/obj/item/weapon/gripper/afterattack(atom/target, mob/user as mob)
+
+ if(!target) //Target is invalid.
+ return
+
+ //There's some weirdness with items being lost inside the arm. Trying to fix all cases. ~Z
+ if(!wrapped)
+ for(var/obj/item/thing in src.contents)
+ wrapped = thing
+ break
+
+ if(wrapped) //Already have an item.
+
+ wrapped.loc = user
+ //Pass the attack on to the target.
+ target.attackby(wrapped,user)
+
+ if(wrapped && src && wrapped.loc == user)
+ wrapped.loc = src
+
+ //Sanity/item use checks.
+
+ if(!wrapped || !user)
+ return
+
+ if(wrapped.loc != src.loc)
+ wrapped = null
+ return
+
+ if(istype(target,/obj/item)) //Check that we're not pocketing a mob.
+
+ //...and that the item is not in a container.
+ if(!isturf(target.loc))
+ return
+
+ var/obj/item/I = target
+
+ //Check if the item is blacklisted.
+ var/grab = 0
+ for(var/typepath in can_hold)
+ if(istype(I,typepath))
+ grab = 1
+ break
+
+ //We can grab the item, finally.
+ if(grab)
+ user << "You collect \the [I]."
+ I.loc = src
+ wrapped = I
+ return
+ else
+ user << "\red Your gripper cannot hold \the [target]."
+
+//TODO: Matter decompiler.
+/obj/item/weapon/matter_decompiler
+
+ name = "matter decompiler"
+ desc = "Eating trash, bits of glass, or other debris will replenish your stores."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "decompiler"
+
+ //Metal, glass, wood, plastic.
+ var/list/stored_comms = list(
+ "metal" = 0,
+ "glass" = 0,
+ "wood" = 0,
+ "plastic" = 0
+ )
+
+/obj/item/weapon/matter_decompiler/afterattack(atom/target, mob/user as mob)
+
+ //We only want to deal with using this on turfs. Specific items aren't important.
+ var/turf/T = get_turf(target)
+ if(!istype(T))
+ return
+
+ //Used to give the right message.
+ var/grabbed_something = 0
+
+ for(var/obj/item/W in T)
+ //Different classes of items give different commodities.
+ if(istype(W,/obj/item/trash))
+ stored_comms["metal"]++
+ stored_comms["plastic"]++
+ else if(istype(W,/obj/effect/decal/cleanable/robot_debris))
+ stored_comms["metal"]++
+ stored_comms["glass"]++
+ else if(istype(W,/obj/item/ammo_casing))
+ stored_comms["metal"]++
+ else if(istype(W,/obj/item/weapon/shard/shrapnel))
+ stored_comms["metal"]++
+ else if(istype(W,/obj/item/weapon/shard))
+ stored_comms["glass"]++
+ else if(istype(W,/obj/item/weapon/reagent_containers/food/snacks/grown))
+ stored_comms["wood"]++
+ else
+ continue
+
+ del(W)
+ grabbed_something = 1
+
+ if(grabbed_something)
+ user << "\blue You deploy your decompiler and clear out the contents of \the [T]."
+ else
+ user << "\red Nothing on \the [T] is useful to you."
+ return
+
+//PRETTIER TOOL LIST.
+/mob/living/silicon/robot/drone/installed_modules()
+
+ if(weapon_lock)
+ src << "\red Weapon lock active, unable to use modules! Count:[weaponlock_time]"
+ return
+
+ if(!module)
+ module = new /obj/item/weapon/robot_module/drone(src)
+
+ var/dat = "Drone modules\n"
+ dat += {"Close
+
+
+ Activated Modules
+
+ Module 1: [module_state_1 ? "[module_state_1]" : "No Module"]
+ Module 2: [module_state_2 ? "[module_state_2]" : "No Module"]
+ Module 3: [module_state_3 ? "[module_state_3]" : "No Module"]
+
+ Installed Modules
"}
+
+
+ var/tools = "Tools and devices
"
+ var/resources = "
Resources
"
+
+ for (var/O in module.modules)
+
+ var/module_string = ""
+
+ if (!O)
+ module_string += text("Resource depleted
")
+ else if(activated(O))
+ module_string += text("[O]: Activated
")
+ else
+ module_string += text("[O]: Activate
")
+
+ if((istype(O,/obj/item/weapon) || istype(O,/obj/item/device)) && !(istype(O,/obj/item/stack/cable_coil)))
+ tools += module_string
+ else
+ resources += module_string
+
+ dat += tools
+
+ if (emagged)
+ if (module.emag)
+ dat += text("Resource depleted
")
+ else if(activated(module.emag))
+ dat += text("[module.emag]: Activated
")
+ else
+ dat += text("[module.emag]: Activate
")
+
+ dat += resources
+
+ src << browse(dat, "window=robotmod&can_close=0")
+
+//Putting the decompiler here to avoid doing list checks every tick.
+/mob/living/silicon/robot/drone/use_power()
+
+ ..()
+ if(!src.has_power || !decompiler)
+ return
+
+ //The decompiler replenishes drone stores from hoovered-up junk each tick.
+ for(var/type in decompiler.stored_comms)
+ if(decompiler.stored_comms[type] > 0)
+ var/obj/item/stack/sheet/stack
+ switch(type)
+ if("metal")
+ if(!stack_metal)
+ stack_metal = new(src.module)
+ stack_metal.amount = 1
+ stack = stack_metal
+ if("glass")
+ if(!stack_glass)
+ stack_glass = new(src.module)
+ stack_glass.amount = 1
+ stack = stack_glass
+ if("wood")
+ if(!stack_wood)
+ stack_wood = new(src.module)
+ stack_wood.amount = 1
+ stack = stack_wood
+ if("plastic")
+ if(!stack_plastic)
+ stack_plastic = new(src.module)
+ stack_plastic.amount = 1
+ stack = stack_plastic
+
+ stack.amount++
+ decompiler.stored_comms[type]--;
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
new file mode 100644
index 00000000000..69e5ec9d93b
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
@@ -0,0 +1,129 @@
+/obj/machinery/drone_fabricator
+ name = "drone fabricator"
+ desc = "A large automated factory for producing maintenance drones."
+
+ density = 1
+ anchored = 1
+ use_power = 1
+ idle_power_usage = 20
+ active_power_usage = 5000
+
+ var/drone_progress = 0
+ var/produce_drones = 0
+ var/time_last_drone = 500
+
+ icon = 'icons/obj/machines/drone_fab.dmi'
+ icon_state = "drone_fab_idle"
+
+/obj/machinery/drone_fabricator/New()
+ ..()
+ produce_drones = config.allow_drone_spawn
+
+/obj/machinery/drone_fabricator/power_change()
+ if (powered())
+ stat &= ~NOPOWER
+ else
+ icon_state = "drone_fab_nopower"
+ stat |= NOPOWER
+
+/obj/machinery/drone_fabricator/process()
+
+ if(stat & NOPOWER || !produce_drones)
+ icon_state = "drone_fab_nopower"
+ return
+
+ if(drone_progress >= 100)
+ icon_state = "drone_fab_idle"
+ return
+
+ icon_state = "drone_fab_active"
+ var/elapsed = world.time - time_last_drone
+ drone_progress = round((elapsed/config.drone_build_time)*100)
+
+ if(drone_progress >= 100)
+ visible_message("\The [src] voices a strident beep, indicating a drone chassis is prepared.")
+
+/obj/machinery/drone_fabricator/examine()
+ ..()
+ if(produce_drones && drone_progress >= 100 && istype(usr,/mob/dead) && config.allow_drone_spawn && count_drones() < config.max_maint_drones)
+ usr << "
A drone is prepared. Select 'Join As Drone' from the Ghost tab to spawn as a maintenance drone."
+
+/obj/machinery/drone_fabricator/proc/count_drones()
+ var/drones = 0
+ for(var/mob/living/silicon/robot/drone/D in world)
+ if(D.key && D.client)
+ drones++
+ return drones
+
+/obj/machinery/drone_fabricator/proc/create_drone(var/client/player)
+
+ if(stat & NOPOWER)
+ return
+
+ if(!produce_drones || !config.allow_drone_spawn || count_drones() >= config.max_maint_drones)
+ return
+
+ if(!player || !istype(player.mob,/mob/dead))
+ return
+
+ visible_message("\The [src] churns and grinds as it lurches into motion, disgorging a shiny new drone after a few moments.")
+ flick("h_lathe_leave",src)
+
+ time_last_drone = world.time
+ var/mob/living/silicon/robot/drone/new_drone = new(get_turf(src))
+ new_drone.transfer_personality(player)
+
+ drone_progress = 0
+
+
+
+/mob/dead/verb/join_as_drone()
+
+ set category = "Ghost"
+ set name = "Join As Drone"
+ set desc = "If there is a powered, enabled fabricator in the game world with a prepared chassis, join as a maintenance drone."
+
+ if(!(config.allow_drone_spawn))
+ src << "\red That verb is not currently permitted."
+ return
+
+ if (!src.stat)
+ return
+
+ if (usr != src)
+ return 0 //something is terribly wrong
+
+
+ var/deathtime = world.time - src.timeofdeath
+ if(istype(src,/mob/dead/observer))
+ var/mob/dead/observer/G = src
+ if(G.has_enabled_antagHUD == 1 && config.antag_hud_restricted)
+ usr << "\blue Upon using the antagHUD you forfeighted the ability to join the round."
+ return
+
+ var/deathtimeminutes = round(deathtime / 600)
+ var/pluralcheck = "minute"
+ if(deathtimeminutes == 0)
+ pluralcheck = ""
+ else if(deathtimeminutes == 1)
+ pluralcheck = " [deathtimeminutes] minute and"
+ else if(deathtimeminutes > 1)
+ pluralcheck = " [deathtimeminutes] minutes and"
+ var/deathtimeseconds = round((deathtime - deathtimeminutes * 600) / 10,1)
+
+ if (deathtime < 6000)
+ usr << "You have been dead for[pluralcheck] [deathtimeseconds] seconds."
+ usr << "You must wait 10 minutes to respawn as a drone!"
+ return
+
+ for(var/obj/machinery/drone_fabricator/DF in world)
+ if(DF.stat & NOPOWER || !DF.produce_drones)
+ continue
+ if(DF.count_drones() >= config.max_maint_drones)
+ src << "\red There are too many active drones in the world for you to spawn."
+ return
+
+ DF.create_drone(src.client)
+ return
+
+ src << "\red There are no available drone spawn points, sorry."
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/robot/examine.dm b/code/modules/mob/living/silicon/robot/examine.dm
index 9041d7e3480..1bde793d3f6 100644
--- a/code/modules/mob/living/silicon/robot/examine.dm
+++ b/code/modules/mob/living/silicon/robot/examine.dm
@@ -32,6 +32,9 @@
else
msg += "Its cover is closed.\n"
+ if(!has_power)
+ msg += "It appears to be running on backup power.\n"
+
switch(src.stat)
if(CONSCIOUS)
if(!src.client) msg += "It appears to be in stand-by mode.\n" //afk
@@ -39,7 +42,7 @@
if(DEAD) msg += "It looks completely unsalvageable.\n"
msg += "*---------*"
- if(print_flavor_text()) msg += "[print_flavor_text()]\n"
+ if(print_flavor_text()) msg += "\n[print_flavor_text()]\n"
if (pose)
if( findtext(pose,".",lentext(pose)) == 0 && findtext(pose,"!",lentext(pose)) == 0 && findtext(pose,"?",lentext(pose)) == 0 )
diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm
index 62f27029eb5..815c598bc1f 100644
--- a/code/modules/mob/living/silicon/robot/life.dm
+++ b/code/modules/mob/living/silicon/robot/life.dm
@@ -125,6 +125,10 @@
src.druggy--
src.druggy = max(0, src.druggy)
+ //update the state of modules and components here
+ if (src.stat != 0)
+ uneq_all()
+
if(!is_component_functioning("radio"))
radio.on = 0
else
@@ -135,6 +139,9 @@
else
src.blinded = 1
+ if(!is_component_functioning("actuator"))
+ src.Paralyse(3)
+
return 1
@@ -175,21 +182,38 @@
if (src.healths)
if (src.stat != 2)
- switch(health)
- if(200 to INFINITY)
- src.healths.icon_state = "health0"
- if(150 to 200)
- src.healths.icon_state = "health1"
- if(100 to 150)
- src.healths.icon_state = "health2"
- if(50 to 100)
- src.healths.icon_state = "health3"
- if(0 to 50)
- src.healths.icon_state = "health4"
- if(config.health_threshold_dead to 0)
- src.healths.icon_state = "health5"
- else
- src.healths.icon_state = "health6"
+ if(istype(src,/mob/living/silicon/robot/drone))
+ switch(health)
+ if(35 to INFINITY)
+ src.healths.icon_state = "health0"
+ if(25 to 34)
+ src.healths.icon_state = "health1"
+ if(15 to 24)
+ src.healths.icon_state = "health2"
+ if(5 to 14)
+ src.healths.icon_state = "health3"
+ if(0 to 4)
+ src.healths.icon_state = "health4"
+ if(-35 to 0)
+ src.healths.icon_state = "health5"
+ else
+ src.healths.icon_state = "health6"
+ else
+ switch(health)
+ if(200 to INFINITY)
+ src.healths.icon_state = "health0"
+ if(150 to 200)
+ src.healths.icon_state = "health1"
+ if(100 to 150)
+ src.healths.icon_state = "health2"
+ if(50 to 100)
+ src.healths.icon_state = "health3"
+ if(0 to 50)
+ src.healths.icon_state = "health4"
+ if(config.health_threshold_dead to 0)
+ src.healths.icon_state = "health5"
+ else
+ src.healths.icon_state = "health6"
else
src.healths.icon_state = "health7"
@@ -326,4 +350,4 @@
if(!on_fire) //Silicons don't gain stacks from hotspots, but hotspots can ignite them
IgniteMob()
-//Robots on fire
+//Robots on fire
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 5249c4c95a4..142bfe2255b 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -57,7 +57,7 @@
var/datum/effect/effect/system/ion_trail_follow/ion_trail = null
var/datum/effect/effect/system/spark_spread/spark_system//So they can initialize sparks whenever/N
var/jeton = 0
-
+ var/has_power = 1
var/killswitch = 0
var/killswitch_time = 60
var/weapon_lock = 0
@@ -100,6 +100,12 @@
hands.icon_state = "standard"
icon_state = "secborg"
modtype = "Security"
+ else if(istype(src,/mob/living/silicon/robot/drone))
+ laws = new /datum/ai_laws/drone()
+ connected_ai = select_active_ai_with_fewest_borgs()
+ if(connected_ai)
+ connected_ai.connected_robots += src
+ lawsync()
else
if(mmi.alien || alien)
laws = new /datum/ai_laws/alienmov()
@@ -152,22 +158,10 @@
hud_list[IMPTRACK_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
hud_list[SPECIALROLE_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
-
-
-
- hud_list[HEALTH_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[STATUS_HUD] = image('icons/mob/hud.dmi', src, "hudhealth100")
- hud_list[ID_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[WANTED_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[IMPLOYAL_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[IMPCHEM_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[IMPTRACK_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[SPECIALROLE_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
-
-
-
-
- playsound(loc, 'sound/voice/liveagain.ogg', 75, 1)
+ if(istype(src,/mob/living/silicon/robot/drone))
+ playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 0)
+ else
+ playsound(loc, 'sound/voice/liveagain.ogg', 75, 1)
// setup the PDA and its name
/mob/living/silicon/robot/proc/setup_PDA()
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 14f4e178037..285e41c8d77 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -249,3 +249,72 @@
src.emag = new /obj/item/weapon/reagent_containers/spray/alien/acid(src)
src.emag.reagents.add_reagent("pacid", 125)
src.emag.reagents.add_reagent("sacid", 125)
+
+/obj/item/weapon/robot_module/drone
+ name = "drone module"
+
+
+ New()
+ //TODO: Replace with shittier flashlight and work out why we can't remove the flash. ~Z
+ ..()
+ src.modules += new /obj/item/weapon/weldingtool(src)
+ src.modules += new /obj/item/weapon/screwdriver(src)
+ src.modules += new /obj/item/weapon/wrench(src)
+ src.modules += new /obj/item/weapon/crowbar(src)
+ src.modules += new /obj/item/weapon/wirecutters(src)
+ src.modules += new /obj/item/device/multitool(src)
+ src.modules += new /obj/item/device/lightreplacer(src)
+ src.modules += new /obj/item/weapon/reagent_containers/spray/cleaner(src)
+ src.modules += new /obj/item/weapon/gripper(src)
+ src.modules += new /obj/item/weapon/matter_decompiler(src)
+
+ src.emag = new /obj/item/weapon/card/emag(src)
+ src.emag.name = "Cryptographic Sequencer"
+
+
+ var/list/stacktypes = list(
+ /obj/item/stack/rods = 10,
+ /obj/item/stack/tile/plasteel = 10,
+ /obj/item/stack/sheet/metal/cyborg = 10,
+ /obj/item/stack/sheet/wood/cyborg = 1,
+ /obj/item/stack/cable_coil = 30,
+ /obj/item/stack/sheet/glass/cyborg = 10,
+ /obj/item/stack/sheet/mineral/plastic/cyborg = 1
+ )
+
+ for(var/T in stacktypes)
+ var/obj/item/stack/sheet/W = new T(src)
+ W.amount = stacktypes[T]
+ src.modules += W
+
+ return
+
+/obj/item/weapon/robot_module/drone/respawn_consumable(var/mob/living/silicon/robot/R)
+ var/obj/item/weapon/reagent_containers/spray/cleaner/C = locate() in src.modules
+ C.reagents.add_reagent("cleaner", 10)
+
+ var/list/stacks = list (
+ /obj/item/stack/sheet/metal,
+ /obj/item/stack/cable_coil,
+ /obj/item/stack/sheet/glass/cyborg,
+ /obj/item/stack/rods,
+ /obj/item/stack/tile/plasteel
+ )
+
+ for(var/T in stacks)
+ var/O = locate(T) in src.modules
+ var/obj/item/stack/sheet/S = O
+
+ if(!S)
+ src.modules -= null
+ S = new T(src)
+ src.modules += S
+ S.amount = 0
+
+ if(istype(S) && S.amount < 15)
+ S.amount++
+
+ var/obj/item/device/lightreplacer/LR = locate() in src.modules
+ LR.Charge(R)
+
+ return
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index 1e7737bc406..91a76177428 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -168,7 +168,7 @@
newlight = new /obj/machinery/light/small/built(src.loc)
newlight.dir = src.dir
- src.transfer_fingerprints_to(newlight)
+ newlight.fingerprints |= src.fingerprints
del(src)
return
..()
diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm
index 4b1ad0470e4..f4a9420cf46 100644
--- a/code/modules/recycling/disposal.dm
+++ b/code/modules/recycling/disposal.dm
@@ -367,7 +367,7 @@
return
use_power = 1
-
+
if(mode != 1) // if off or ready, no need to charge
return
@@ -401,7 +401,11 @@
var/wrapcheck = 0
var/obj/structure/disposalholder/H = new() // virtual holder object which actually
- // travels through the pipes.
+ // travels through the pipes.
+ //Hacky test to get drones to mail themselves through disposals.
+ for(var/mob/living/silicon/robot/drone/D in src)
+ wrapcheck = 1
+
for(var/obj/item/smallDelivery/O in src)
wrapcheck = 1
@@ -449,9 +453,10 @@
AM.loc = src.loc
AM.pipe_eject(0)
- spawn(1)
- if(AM)
- AM.throw_at(target, 5, 1)
+ if(!istype(AM,/mob/living/silicon/robot/drone)) //Poor drones kept smashing windows and taking system damage being fired out of disposals. ~Z
+ spawn(1)
+ if(AM)
+ AM.throw_at(target, 5, 1)
H.vent_gas(loc)
del(H)
@@ -496,7 +501,7 @@
//Check for any living mobs trigger hasmob.
//hasmob effects whether the package goes to cargo or its tagged destination.
for(var/mob/living/M in D)
- if(M && M.stat != 2)
+ if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone))
hasmob = 1
//Checks 1 contents level deep. This means that players can be sent through disposals...
@@ -504,7 +509,7 @@
for(var/obj/O in D)
if(O.contents)
for(var/mob/living/M in O.contents)
- if(M && M.stat != 2)
+ if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone))
hasmob = 1
// now everything inside the disposal gets put into the holder
@@ -521,6 +526,10 @@
if(istype(AM, /obj/item/smallDelivery) && !hasmob)
var/obj/item/smallDelivery/T = AM
src.destinationTag = T.sortTag
+ //Drones can mail themselves through maint.
+ if(istype(AM, /mob/living/silicon/robot/drone))
+ var/mob/living/silicon/robot/drone/drone = AM
+ src.destinationTag = drone.mail_destination
// start the movement process
@@ -544,7 +553,8 @@
while(active)
if(hasmob && prob(3))
for(var/mob/living/H in src)
- H.take_overall_damage(10, 0, "Blunt Trauma")//horribly maim any living creature jumping down disposals. c'est la vie
+ if(!istype(H,/mob/living/silicon/robot/drone)) //Drones use the mailing code to move through the disposal system,
+ H.take_overall_damage(20, 0, "Blunt Trauma")//horribly maim any living creature jumping down disposals. c'est la vie
if(has_fat_guy && prob(2)) // chance of becoming stuck per segment if contains a fat guy
active = 0
@@ -1261,8 +1271,9 @@
for(var/atom/movable/AM in H)
AM.loc = src.loc
AM.pipe_eject(dir)
- spawn(5)
- AM.throw_at(target, 3, 1)
+ if(!istype(AM,/mob/living/silicon/robot/drone)) //Drones keep smashing windows from being fired out of chutes. Bad for the station. ~Z
+ spawn(5)
+ AM.throw_at(target, 3, 1)
H.vent_gas(src.loc)
del(H)
diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi
index 1d3ab4101f9..b4227fa17bc 100644
Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ
diff --git a/icons/mob/items_lefthand.dmi b/icons/mob/items_lefthand.dmi
index 4603a792b43..10893a4399b 100644
Binary files a/icons/mob/items_lefthand.dmi and b/icons/mob/items_lefthand.dmi differ
diff --git a/icons/mob/items_righthand.dmi b/icons/mob/items_righthand.dmi
index 3c9c099892f..5638c2d4d07 100644
Binary files a/icons/mob/items_righthand.dmi and b/icons/mob/items_righthand.dmi differ
diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi
index c5c35a9f01a..b5a93c40940 100644
Binary files a/icons/mob/robots.dmi and b/icons/mob/robots.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index 9089fb149c4..854829e3985 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/icons/obj/machines/drone_fab.dmi b/icons/obj/machines/drone_fab.dmi
new file mode 100644
index 00000000000..066b51c2582
Binary files /dev/null and b/icons/obj/machines/drone_fab.dmi differ
diff --git a/icons/obj/weapons.dmi b/icons/obj/weapons.dmi
index 3a10817eef0..9396888504d 100644
Binary files a/icons/obj/weapons.dmi and b/icons/obj/weapons.dmi differ