diff --git a/code/game/objects/items/RCD.dm b/code/game/objects/items/RCD.dm
index a1f663986577..8e53ec65c58f 100644
--- a/code/game/objects/items/RCD.dm
+++ b/code/game/objects/items/RCD.dm
@@ -413,6 +413,7 @@ RLD
no_ammo_message = "Insufficient charge."
desc = "A device used to rapidly build walls and floors."
canRturf = TRUE
+ var/energyfactor = 72
/obj/item/construction/rcd/borg/useResource(amount, mob/user)
@@ -423,7 +424,7 @@ RLD
if(user)
to_chat(user, no_ammo_message)
return 0
- . = borgy.cell.use(amount * 72) //borgs get 1.3x the use of their RCDs
+ . = borgy.cell.use(amount * energyfactor) //borgs get 1.3x the use of their RCDs
if(!. && user)
to_chat(user, no_ammo_message)
return .
@@ -436,11 +437,16 @@ RLD
if(user)
to_chat(user, no_ammo_message)
return 0
- . = borgy.cell.charge >= (amount * 72)
+ . = borgy.cell.charge >= (amount * energyfactor)
if(!. && user)
to_chat(user, no_ammo_message)
return .
+/obj/item/construction/rcd/borg/syndicate
+ icon_state = "ircd"
+ item_state = "ircd"
+ energyfactor = 66
+
/obj/item/construction/rcd/loaded
matter = 160
diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm
index a5cb83c3a93c..5a42b8504c40 100644
--- a/code/modules/antagonists/_common/antag_spawner.dm
+++ b/code/modules/antagonists/_common/antag_spawner.dm
@@ -182,6 +182,10 @@
name = "syndicate medical teleporter"
borg_to_spawn = "Medical"
+/obj/item/antag_spawner/nuke_ops/borg_tele/saboteur
+ name = "syndicate saboteur teleporter"
+ borg_to_spawn = "Saboteur"
+
/obj/item/antag_spawner/nuke_ops/borg_tele/spawn_antag(client/C, turf/T, kind, datum/mind/user)
var/mob/living/silicon/robot/R
var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE)
@@ -191,6 +195,8 @@
switch(borg_to_spawn)
if("Medical")
R = new /mob/living/silicon/robot/modules/syndicate/medical(T)
+ if("Saboteur")
+ R = new /mob/living/silicon/robot/modules/syndicate/saboteur(T)
else
R = new /mob/living/silicon/robot/modules/syndicate(T) //Assault borg by default
diff --git a/code/modules/antagonists/nukeop/equipment/borgchameleon.dm b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
new file mode 100644
index 000000000000..01e18c752bcf
--- /dev/null
+++ b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
@@ -0,0 +1,103 @@
+/obj/item/borg_chameleon
+ name = "cyborg chameleon projector"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "shield0"
+ flags_1 = CONDUCT_1
+ item_flags = NOBLUDGEON
+ item_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ w_class = WEIGHT_CLASS_SMALL
+ var/friendlyName
+ var/savedName
+ var/active = FALSE
+ var/activationCost = 300
+ var/activationUpkeep = 50
+ var/disguise = "engineer"
+ var/datum/component/mobhook // need this to deal with unregistration properly
+ var/mob/living/silicon/robot/user // needed for process()
+
+/obj/item/borg_chameleon/Initialize()
+ . = ..()
+ friendlyName = pick(GLOB.ai_names)
+
+/obj/item/borg_chameleon/Destroy()
+ QDEL_NULL(mobhook)
+ return ..()
+
+/obj/item/borg_chameleon/dropped(mob/user)
+ . = ..()
+ disrupt(user)
+
+/obj/item/borg_chameleon/equipped(mob/user)
+ . = ..()
+ disrupt(user)
+
+/obj/item/borg_chameleon/attack_self(mob/living/silicon/robot/user)
+ if (user && user.cell && user.cell.charge > activationCost)
+ if (isturf(user.loc))
+ toggle(user)
+ else
+ to_chat(user, "You can't use [src] while inside something!")
+ else
+ to_chat(user, "You need at least [activationCost] charge in your cell to use [src]!")
+
+/obj/item/borg_chameleon/proc/toggle(mob/living/silicon/robot/user)
+ if(active)
+ playsound(src, 'sound/effects/pop.ogg', 100, 1, -6)
+ to_chat(user, "You deactivate \the [src].")
+ deactivate(user)
+ else
+ to_chat(user, "You activate \the [src].")
+ playsound(src, 'sound/effects/seedling_chargeup.ogg', 100, 1, -6)
+ if (do_after(user, 50, target=user) && user.cell.use(activationCost))
+ playsound(src, 'sound/effects/bamf.ogg', 100, 1, -6)
+ to_chat(user, "You are now disguised as the Nanotrasen engineering borg \"[friendlyName]\".")
+ activate(user)
+ else
+ to_chat(user, "The chameleon field fizzles.")
+ do_sparks(3, FALSE, user)
+
+/obj/item/borg_chameleon/process()
+ if (user)
+ if (!user.cell || !user.cell.use(activationUpkeep))
+ disrupt(user)
+ else
+ return PROCESS_KILL
+
+/obj/item/borg_chameleon/proc/activate(mob/living/silicon/robot/user)
+ START_PROCESSING(SSobj, src)
+ src.user = user
+ savedName = user.name
+ user.name = friendlyName
+ user.module.cyborg_base_icon = disguise
+ active = TRUE
+ if (mobhook && mobhook.parent != user)
+ QDEL_NULL(mobhook)
+ if (!mobhook)
+ var/callback = CALLBACK(src, .proc/disrupt, user) // push user into the callback so that it's guaranteed to be the first arg
+ mobhook = user.AddComponent(/datum/component/redirect, list( // list here all signals that should break the camouflage
+ COMSIG_PARENT_ATTACKBY = callback,
+ COMSIG_ATOM_ATTACK_HAND = callback,
+ COMSIG_MOVABLE_IMPACT_ZONE = callback,
+ COMSIG_ATOM_BULLET_ACT = callback,
+ COMSIG_ATOM_EX_ACT = callback,
+ COMSIG_ATOM_FIRE_ACT = callback,
+ COMSIG_ATOM_EMP_ACT = callback,
+ ))
+ user.update_icons()
+
+/obj/item/borg_chameleon/proc/deactivate(mob/living/silicon/robot/user)
+ STOP_PROCESSING(SSobj, src)
+ QDEL_NULL(mobhook)
+ do_sparks(5, FALSE, user)
+ user.name = savedName
+ user.module.cyborg_base_icon = initial(user.module.cyborg_base_icon)
+ active = FALSE
+ user.update_icons()
+ src.user = user
+
+/obj/item/borg_chameleon/proc/disrupt(mob/living/silicon/robot/user)
+ if(active)
+ to_chat(user, "Your chameleon field deactivates.")
+ deactivate(user)
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 86fb68807f2c..846768c3ab73 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -853,6 +853,17 @@
Help the operatives secure the disk at all costs!"
set_module = /obj/item/robot_module/syndicate_medical
+/mob/living/silicon/robot/modules/syndicate/saboteur
+ icon_state = "synd_engi"
+ playstyle_string = "You are a Syndicate saboteur cyborg!
\
+ You are armed with robust engineering tools to aid you in your mission: help the operatives secure the nuclear authentication disk. \
+ Your destination tagger will allow you to stealthily traverse the disposal network across the station \
+ Your welder will allow you to repair the operatives' exosuits, but also yourself and your fellow cyborgs \
+ Your cyborg chameleon projector allows you to assume the appearance and registered name of a Nanotrasen engineering borg, and undertake covert actions on the station \
+ Be aware that almost any physical contact or incidental damage will break your camouflage \
+ Help the operatives secure the disk at all costs!"
+ set_module = /obj/item/robot_module/saboteur
+
/mob/living/silicon/robot/proc/notify_ai(notifytype, oldname, newname)
if(!connected_ai)
return
diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index c90c719e8a85..037b2819e993 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -177,8 +177,8 @@
if (stat != DEAD)
adjustBruteLoss(30)
-/mob/living/silicon/robot/bullet_act(var/obj/item/projectile/Proj)
- ..(Proj)
+/mob/living/silicon/robot/bullet_act(var/obj/item/projectile/Proj, def_zone)
+ ..()
updatehealth()
if(prob(75) && Proj.damage > 0)
spark_system.start()
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 3f3071bf0c20..b57fa101b0e3 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -32,6 +32,7 @@
var/list/ride_offset_y = list("north" = 4, "south" = 4, "east" = 3, "west" = 3)
var/ride_allow_incapacitated = FALSE
var/allow_riding = TRUE
+ var/canDispose = FALSE // Whether the borg can stuff itself into disposal
/obj/item/robot_module/Initialize()
. = ..()
@@ -596,6 +597,40 @@
can_be_pushed = FALSE
hat_offset = 3
+/obj/item/robot_module/saboteur
+ name = "Syndicate Saboteur"
+ basic_modules = list(
+ /obj/item/assembly/flash/cyborg,
+ /obj/item/borg/sight/thermal,
+ /obj/item/construction/rcd/borg/syndicate,
+ /obj/item/pipe_dispenser,
+ /obj/item/extinguisher,
+ /obj/item/weldingtool/largetank/cyborg,
+ /obj/item/screwdriver/nuke,
+ /obj/item/wrench/cyborg,
+ /obj/item/crowbar/cyborg,
+ /obj/item/wirecutters/cyborg,
+ /obj/item/multitool/cyborg,
+ /obj/item/stack/sheet/metal/cyborg,
+ /obj/item/stack/sheet/glass/cyborg,
+ /obj/item/stack/sheet/rglass/cyborg,
+ /obj/item/stack/rods/cyborg,
+ /obj/item/stack/tile/plasteel/cyborg,
+ /obj/item/destTagger/borg,
+ /obj/item/stack/cable_coil/cyborg,
+ /obj/item/pinpointer/syndicate_cyborg,
+ /obj/item/borg_chameleon,
+ )
+ ratvar_modules = list(
+ /obj/item/clockwork/slab/cyborg/engineer,
+ /obj/item/clockwork/replica_fabricator/cyborg)
+ cyborg_base_icon = "synd_engi"
+ moduleselect_icon = "malf"
+ can_be_pushed = FALSE
+ magpulsing = TRUE
+ hat_offset = -4
+ canDispose = TRUE
+
/datum/robot_energy_storage
var/name = "Generic energy storage"
var/max_energy = 30000
diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm
index 073a2eec2b28..d898fadc5fcc 100644
--- a/code/modules/mob/living/silicon/silicon_defense.dm
+++ b/code/modules/mob/living/silicon/silicon_defense.dm
@@ -65,6 +65,9 @@
//ATTACK HAND IGNORING PARENT RETURN VALUE
/mob/living/silicon/attack_hand(mob/living/carbon/human/M)
+ . = FALSE
+ if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, M) & COMPONENT_NO_ATTACK_HAND)
+ . = TRUE
switch(M.a_intent)
if ("help")
M.visible_message("[M] pets [src].", \
@@ -76,7 +79,6 @@
playsound(src.loc, 'sound/effects/bang.ogg', 10, 1)
visible_message("[M] punches [src], but doesn't leave a dent.", \
"[M] punches [src], but doesn't leave a dent.", null, COMBAT_MESSAGE_RANGE)
- return 0
/mob/living/silicon/attack_drone(mob/living/simple_animal/drone/M)
if(M.a_intent == INTENT_HARM)
@@ -108,7 +110,8 @@
M.visible_message("[M] is thrown off of [src]!")
flash_act(affect_silicon = 1)
-/mob/living/silicon/bullet_act(obj/item/projectile/Proj)
+/mob/living/silicon/bullet_act(obj/item/projectile/Proj, def_zone)
+ SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, Proj, def_zone)
if((Proj.damage_type == BRUTE || Proj.damage_type == BURN))
adjustBruteLoss(Proj.damage)
if(prob(Proj.damage*1.5))
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index 8c9769f27852..3ac50c8e721e 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -153,6 +153,7 @@
var/robot = pick(200;/mob/living/silicon/robot,
/mob/living/silicon/robot/modules/syndicate,
/mob/living/silicon/robot/modules/syndicate/medical,
+ /mob/living/silicon/robot/modules/syndicate/saboteur,
200;/mob/living/simple_animal/drone/polymorphed)
new_mob = new robot(M.loc)
if(issilicon(new_mob))
@@ -510,5 +511,3 @@
var/turf/T = get_turf(target)
for(var/i=0, i<50, i+=10)
addtimer(CALLBACK(GLOBAL_PROC, .proc/explosion, T, -1, exp_heavy, exp_light, exp_flash, FALSE, FALSE, exp_fire), i)
-
-
diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm
index fd98c54b81cf..6ac9ac19c092 100644
--- a/code/modules/recycling/disposal/bin.dm
+++ b/code/modules/recycling/disposal/bin.dm
@@ -115,7 +115,12 @@
/obj/machinery/disposal/proc/stuff_mob_in(mob/living/target, mob/living/user)
if(!iscarbon(user) && !user.ventcrawler) //only carbon and ventcrawlers can climb into disposal by themselves.
- return
+ if (iscyborg(user))
+ var/mob/living/silicon/robot/borg = user
+ if (!borg.module || !borg.module.canDispose)
+ return
+ else
+ return
if(!isturf(user.loc)) //No magically doing it from inside closets
return
if(target.buckled || target.has_buckled_mobs())
diff --git a/code/modules/recycling/disposal/holder.dm b/code/modules/recycling/disposal/holder.dm
index 2c0a6aa646b6..639df2e36406 100644
--- a/code/modules/recycling/disposal/holder.dm
+++ b/code/modules/recycling/disposal/holder.dm
@@ -52,9 +52,13 @@
if(istype(AM, /obj/structure/bigDelivery) && !hasmob)
var/obj/structure/bigDelivery/T = AM
src.destinationTag = T.sortTag
- if(istype(AM, /obj/item/smallDelivery) && !hasmob)
+ else if(istype(AM, /obj/item/smallDelivery) && !hasmob)
var/obj/item/smallDelivery/T = AM
src.destinationTag = T.sortTag
+ else if(istype(AM, /mob/living/silicon/robot))
+ var/obj/item/destTagger/borg/tagger = locate() in AM
+ if (tagger)
+ src.destinationTag = tagger.currTag
// start the movement process
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index fa2eaa22d4bf..052d12407046 100644
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -154,6 +154,10 @@
flags_1 = CONDUCT_1
slot_flags = ITEM_SLOT_BELT
+/obj/item/destTagger/borg
+ name = "cyborg destination tagger"
+ desc = "Used to fool the disposal mail network into thinking that you're a harmless parcel. Does actually work as a regular destination tagger as well."
+
/obj/item/destTagger/suicide_act(mob/living/user)
user.visible_message("[user] begins tagging [user.p_their()] final destination! It looks like [user.p_theyre()] trying to commit suicide!")
if (islizard(user))
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 8077e59b4de4..6d47a86b81c1 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -201,7 +201,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 10
surplus = 40
include_modes = list(/datum/game_mode/nuclear)
-
+
/datum/uplink_item/dangerous/carbine
name = "M-90gl Carbine"
desc = "A fully-loaded, specialized three-round burst carbine that fires 5.56mm ammunition from a 30 round magazine \
@@ -526,7 +526,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/storage/backpack/duffelbag/syndie/ammo/smg
cost = 20
include_modes = list(/datum/game_mode/nuclear)
-
+
/datum/uplink_item/ammo/carbine
name = "5.56mm Toploader Magazine"
desc = "An additional 30-round 5.56mm magazine; suitable for use with the M-90gl carbine. \
@@ -534,7 +534,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/ammo_box/magazine/m556
cost = 4
include_modes = list(/datum/game_mode/nuclear)
-
+
/datum/uplink_item/ammo/a40mm
name = "40mm Grenade"
desc = "A 40mm HE grenade for use with the M-90gl's under-barrel grenade launcher. \
@@ -642,6 +642,14 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 35
restricted = TRUE
+/datum/uplink_item/support/reinforcement/saboteur_borg
+ name = "Syndicate Saboteur Cyborg"
+ desc = "A streamlined engineering cyborg, equipped with thermal vision and the ability to traverse Nanotrasen's disposal network. Also incapable of leaving the welder in the shuttle."
+ item = /obj/item/antag_spawner/nuke_ops/borg_tele/saboteur
+ refundable = TRUE
+ cost = 35
+ restricted = TRUE
+
/datum/uplink_item/support/gygax
name = "Gygax Exosuit"
desc = "A lightweight exosuit, painted in a dark scheme. Its speed and equipment selection make it excellent \
diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi
index ea0d10e67d64..1ead2a2afad2 100644
Binary files a/icons/mob/robots.dmi and b/icons/mob/robots.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 2b2018dc3f10..128b5605dcbc 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1254,6 +1254,7 @@
#include "code\modules\antagonists\ninja\ninja.dm"
#include "code\modules\antagonists\nukeop\clownop.dm"
#include "code\modules\antagonists\nukeop\nukeop.dm"
+#include "code\modules\antagonists\nukeop\equipment\borgchameleon.dm"
#include "code\modules\antagonists\nukeop\equipment\nuclear_challenge.dm"
#include "code\modules\antagonists\nukeop\equipment\nuclearbomb.dm"
#include "code\modules\antagonists\nukeop\equipment\pinpointer.dm"