diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index b6d60216970..b3e1436ea72 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -212,7 +212,7 @@
if(!R.robot_modules_background)
return
- var/display_rows = CEILING(length(R.module.get_inactive_modules()) / 8, 1)
+ var/display_rows = max(CEILING(length(R.module.get_inactive_modules()) / 8, 1),1)
R.robot_modules_background.screen_loc = "CENTER-4:16,SOUTH+1:7 to CENTER+3:16,SOUTH+[display_rows]:7"
screenmob.client.screen += R.robot_modules_background
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 8a04d6dae3e..54219cdb222 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -363,6 +363,19 @@ GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list
return FALSE
return SSshuttle.emergency.is_hijacked()
+/datum/objective/hijack/highlander
+ name="highlander hijack"
+ explanation_text = "Escape on the shuttle alone. Ensure that nobody else makes it out."
+
+/datum/objective/hijack/check_completion()
+ if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME)
+ return FALSE
+ var/list/datum/mind/owners = get_owners()
+ for(var/datum/mind/M in owners)
+ if(!considered_alive(M, enforce_human = FALSE) || !SSshuttle.emergency.shuttle_areas[get_area(M.current)])
+ return FALSE
+ return SSshuttle.emergency.is_hijacked(filter_by_human = FALSE, solo_hijack = TRUE)
+
/datum/objective/block
name = "no organics on shuttle"
explanation_text = "Do not allow any organic lifeforms to escape on the shuttle alive."
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 46da249a9a1..78fe4e8035d 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -134,7 +134,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/attack(mob/living/target, mob/living/user)
. = ..()
- if(!QDELETED(target) && iscarbon(target) && target.stat == DEAD && target.mind && target.mind.special_role == "highlander")
+ if(!QDELETED(target) && target.stat == DEAD && target.mind && target.mind.special_role == "highlander")
user.fully_heal(admin_revive = FALSE) //STEAL THE LIFE OF OUR FALLEN FOES
add_notch(user)
target.visible_message("[target] crumbles to dust beneath [user]'s blows!", "As you fall, your body crumbles to dust!")
@@ -143,9 +143,13 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/attack_self(mob/living/user)
var/closest_victim
var/closest_distance = 255
- for(var/mob/living/carbon/human/H in GLOB.player_list - user)
- if(H.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance))
- closest_victim = H
+ for(var/mob/living/carbon/human/scot in GLOB.player_list - user)
+ if(scot.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance))
+ closest_victim = scot
+ for(var/mob/living/silicon/robot/siliscot in GLOB.player_list - user)
+ if(siliscot.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance))
+ closest_victim = siliscot
+
if(!closest_victim)
to_chat(user, "[src] thrums for a moment and falls dark. Perhaps there's nobody nearby.")
return
@@ -207,6 +211,21 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
name = new_name
playsound(user, 'sound/items/screwdriver2.ogg', 50, TRUE)
+/obj/item/claymore/highlander/robot //BLOODTHIRSTY BORGS NOW COME IN PLAID
+ icon = 'icons/obj/items_cyborg.dmi'
+ icon_state = "claymore_cyborg"
+ var/mob/living/silicon/robot/robot
+
+/obj/item/claymore/highlander/robot/Initialize()
+ var/obj/item/robot_module/kiltkit = loc
+ robot = kiltkit.loc
+ if(!istype(robot))
+ qdel(src)
+ return ..()
+
+/obj/item/claymore/highlander/robot/process()
+ loc.layer = LARGE_MOB_LAYER
+
/obj/item/katana
name = "katana"
desc = "Woefully underpowered in D20."
diff --git a/code/modules/admin/verbs/onlyone.dm b/code/modules/admin/verbs/onlyone.dm
index 3bbaca9529f..40d2fb9f14d 100644
--- a/code/modules/admin/verbs/onlyone.dm
+++ b/code/modules/admin/verbs/onlyone.dm
@@ -17,6 +17,22 @@ GLOBAL_VAR_INIT(highlander, FALSE)
continue
H.make_scottish()
+ for(var/mob/living/silicon/ai/AI in GLOB.player_list)
+ if(!istype(AI) || AI.stat == DEAD)
+ continue
+ if(AI.deployed_shell)
+ AI.deployed_shell.undeploy()
+ AI.change_mob_type(/mob/living/silicon/robot , null, null)
+ AI.gib()
+
+ for(var/mob/living/silicon/robot/robot in GLOB.player_list)
+ if(!istype(robot) || robot.stat == DEAD)
+ continue
+ if(robot.shell)
+ robot.gib()
+ continue
+ robot.make_scottish()
+
message_admins("[key_name_admin(usr)] used THERE CAN BE ONLY ONE!")
log_admin("[key_name(usr)] used THERE CAN BE ONLY ONE.")
addtimer(CALLBACK(SSshuttle.emergency, /obj/docking_port/mobile/emergency.proc/request, null, 1), 50)
@@ -29,3 +45,6 @@ GLOBAL_VAR_INIT(highlander, FALSE)
/mob/living/carbon/human/proc/make_scottish()
mind.add_antag_datum(/datum/antagonist/highlander)
+
+/mob/living/silicon/robot/proc/make_scottish()
+ mind.add_antag_datum(/datum/antagonist/highlander/robot)
diff --git a/code/modules/antagonists/highlander/highlander.dm b/code/modules/antagonists/highlander/highlander.dm
index 1d5c85d664a..4d82a5f3d91 100644
--- a/code/modules/antagonists/highlander/highlander.dm
+++ b/code/modules/antagonists/highlander/highlander.dm
@@ -9,22 +9,24 @@
var/mob/living/L = owner.current || mob_override
ADD_TRAIT(L, TRAIT_NOGUNS, "highlander")
ADD_TRAIT(L, TRAIT_NODISMEMBER, "highlander")
+ ADD_TRAIT(L, TRAIT_SHOCKIMMUNE, "highlander")
REMOVE_TRAIT(L, TRAIT_PACIFISM, ROUNDSTART_TRAIT)
/datum/antagonist/highlander/remove_innate_effects(mob/living/mob_override)
var/mob/living/L = owner.current || mob_override
REMOVE_TRAIT(L, TRAIT_NOGUNS, "highlander")
REMOVE_TRAIT(L, TRAIT_NODISMEMBER, "highlander")
+ REMOVE_TRAIT(L, TRAIT_SHOCKIMMUNE, "highlander")
if(L.has_quirk(/datum/quirk/nonviolent))
ADD_TRAIT(L, TRAIT_PACIFISM, ROUNDSTART_TRAIT)
+
/datum/antagonist/highlander/proc/forge_objectives()
var/datum/objective/steal/steal_objective = new
steal_objective.owner = owner
steal_objective.set_target(new /datum/objective_item/steal/nukedisc)
objectives += steal_objective
- var/datum/objective/hijack/hijack_objective = new
- hijack_objective.explanation_text = "Escape on the shuttle alone. Ensure that nobody else makes it out."
+ var/datum/objective/hijack/highlander/hijack_objective = new
hijack_objective.owner = owner
objectives += hijack_objective
@@ -54,7 +56,7 @@
H.regenerate_icons()
H.revive(full_heal = TRUE, admin_revive = TRUE)
H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/kilt/highlander(H), ITEM_SLOT_ICLOTHING)
- H.equip_to_slot_or_del(new /obj/item/radio/headset/heads/captain(H), ITEM_SLOT_EARS)
+ H.equip_to_slot_or_del(new /obj/item/radio/headset/syndicate(H), ITEM_SLOT_EARS)
H.equip_to_slot_or_del(new /obj/item/clothing/head/beret/highlander(H), ITEM_SLOT_HEAD)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(H), ITEM_SLOT_FEET)
H.equip_to_slot_or_del(new /obj/item/pinpointer/nuke(H), ITEM_SLOT_LPOCKET)
@@ -81,3 +83,22 @@
antiwelder.desc = "You are unable to hold anything in this hand until you're the last one left!"
antiwelder.icon_state = "bloodhand_right"
H.put_in_hands(antiwelder)
+
+/datum/antagonist/highlander/robot
+ name="highlander"
+
+/datum/antagonist/highlander/robot/greet()
+ to_chat(owner, "Your integrated claymore cries out for blood. Claim the lives of others, and your own will be restored!\n\
+ Activate it in your hand, and it will lead to the nearest target. Attack the nuclear authentication disk with it, and you will store it.")
+
+/datum/antagonist/highlander/robot/give_equipment()
+ var/mob/living/silicon/robot/robotlander = owner.current
+ if(!istype(robotlander))
+ return ..()
+ robotlander.revive(full_heal = TRUE, admin_revive = TRUE)
+ robotlander.set_connected_ai() //DISCONNECT FROM AI
+ robotlander.laws.clear_inherent_laws()
+ robotlander.laws.set_zeroth_law("THERE CAN BE ONLY ONE")
+ robotlander.laws.show_laws(robotlander)
+ robotlander.module.transform_to(/obj/item/robot_module/syndicate/kiltborg)
+ sword = locate(/obj/item/claymore/highlander/robot) in robotlander.module.basic_modules
diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm
index d2fa26831b4..3740192c9ff 100644
--- a/code/modules/mob/living/silicon/robot/inventory.dm
+++ b/code/modules/mob/living/silicon/robot/inventory.dm
@@ -51,6 +51,10 @@
* * module_num - the slot number being equipped to.
*/
/mob/living/silicon/robot/proc/equip_module_to_slot(obj/item/item_module, module_num)
+ var/storage_was_closed = FALSE //Just to be consistant and all
+ if(!shown_robot_modules) //Tools may be invisible if the collection is hidden
+ hud_used.toggle_show_robot_modules()
+ storage_was_closed = TRUE
switch(module_num)
if(1)
item_module.screen_loc = inv1.screen_loc
@@ -72,6 +76,9 @@
update_sight()
observer_screen_update(item_module, TRUE)
+
+ if(storage_was_closed)
+ hud_used.toggle_show_robot_modules()
return TRUE
/**
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index b240f6a65d7..ecc1933cf30 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -73,7 +73,7 @@
var/lawupdate = 1 //Cyborgs will sync their laws with their AI by default
var/scrambledcodes = FALSE // Used to determine if a borg shows up on the robotics console. Setting to TRUE hides them.
- var/lockcharge //Boolean of whether the borg is locked down or not
+ var/lockcharge = FALSE //Boolean of whether the borg is locked down or not
var/toner = 0
var/tonermax = 40
@@ -729,6 +729,8 @@
/mob/living/silicon/robot/updatehealth()
..()
+ if(!module.breakable_modules)
+ return
/// the current percent health of the robot (-1 to 1)
var/percent_hp = health/maxHealth
diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index 065e57288ea..c5a6e2e05fc 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -100,6 +100,9 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
return
if(W.slot_flags & ITEM_SLOT_HEAD && hat_offset != INFINITY && user.a_intent == INTENT_HELP && !is_type_in_typecache(W, GLOB.blacklisted_borg_hats))
+ if(HAS_TRAIT(hat, TRAIT_NODROP))
+ to_chat(user, "You can't seem to remove [src]'s existing headwear!")
+ return
to_chat(user, "You begin to place [W] on [src]'s head...")
to_chat(src, "[user] is placing [W] on your head...")
if(do_after(user, 30, target = src))
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index d5541591c50..8e6d1b6b4ea 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -7,6 +7,8 @@
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
flags_1 = CONDUCT_1
+ ///Host of this module
+ var/mob/living/silicon/robot/robot
var/list/basic_modules = list() //a list of paths, converted to a list of instances on New()
var/list/emag_modules = list() //ditto
@@ -24,6 +26,8 @@
var/can_be_pushed = TRUE
var/magpulsing = FALSE
var/clean_on_move = FALSE
+ var/breakable_modules = TRUE //Whether the borg loses tool slots with damage.
+ var/locked_transform = TRUE //Whether swapping to this module should lockcharge the borg
var/did_feedback = FALSE
@@ -183,6 +187,7 @@
/obj/item/robot_module/proc/transform_to(new_module_type)
var/mob/living/silicon/robot/R = loc
var/obj/item/robot_module/RM = new new_module_type(R)
+ RM.robot = R
if(!RM.be_transformed_to(src))
qdel(RM)
return
@@ -217,15 +222,15 @@
sleep(1)
flick("[cyborg_base_icon]_transform", R)
R.notransform = TRUE
- R.SetLockdown(1)
- R.set_anchored(TRUE)
+ if(locked_transform)
+ R.SetLockdown(TRUE)
+ R.set_anchored(TRUE)
R.logevent("Chassis configuration has been set to [name].")
sleep(1)
for(var/i in 1 to 4)
playsound(R, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, TRUE, -1)
sleep(7)
- if(!prev_lockcharge)
- R.SetLockdown(0)
+ R.SetLockdown(prev_lockcharge)
R.setDir(SOUTH)
R.set_anchored(FALSE)
R.notransform = FALSE
@@ -643,6 +648,34 @@
hat_offset = -4
canDispose = TRUE
+/obj/item/robot_module/syndicate/kiltborg
+ name = "Highlander"
+ basic_modules = list(
+ /obj/item/claymore/highlander/robot,
+ /obj/item/pinpointer/nuke,)
+ moduleselect_icon = "kilt"
+ cyborg_base_icon = "kilt"
+ hat_offset = -2
+ breakable_modules = FALSE
+ locked_transform = FALSE //GO GO QUICKLY AND SLAUGHTER THEM ALL
+
+/obj/item/robot_module/syndicate/kiltborg/be_transformed_to(obj/item/robot_module/old_module)
+ . = ..()
+ qdel(robot.radio)
+ robot.radio = new /obj/item/radio/borg/syndicate(robot)
+ robot.scrambledcodes = TRUE
+ robot.maxHealth = 50 //DIE IN THREE HITS, LIKE A REAL SCOT
+ robot.break_cyborg_slot(3) //YOU ONLY HAVE TWO ITEMS ANYWAY
+ var/obj/item/pinpointer/nuke/diskyfinder = locate(/obj/item/pinpointer/nuke) in basic_modules
+ diskyfinder.attack_self(robot)
+
+/obj/item/robot_module/syndicate/kiltborg/do_transform_delay() //AUTO-EQUIPPING THESE TOOLS ANY EARLIER CAUSES RUNTIMES OH YEAH
+ . = ..()
+ robot.equip_module_to_slot(locate(/obj/item/claymore/highlander/robot) in basic_modules, 1)
+ robot.equip_module_to_slot(locate(/obj/item/pinpointer/nuke) in basic_modules, 2)
+ robot.place_on_head(new /obj/item/clothing/head/beret/highlander(robot)) //THE ONLY PART MORE IMPORTANT THAN THE SWORD IS THE HAT
+ ADD_TRAIT(robot.hat, TRAIT_NODROP, HIGHLANDER)
+
/datum/robot_energy_storage
var/name = "Generic energy storage"
var/max_energy = 30000
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index a26be90d9f1..37ce3fc364c 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -265,15 +265,26 @@
SSticker.emergency_reason = null
-/obj/docking_port/mobile/emergency/proc/is_hijacked()
+
+/**
+ * Proc that handles checking if the emergency shuttle was successfully hijacked
+ *
+ * Checks for all mobs on the shuttle, checks their status, and checks if they're
+ * borgs or simple animals. Depending on the args, certain mobs may be ignored,
+ * and the presence of other antags may or may not invalidate a hijack.
+ * Args:
+ * filter_by_human, default TRUE, tells the proc that only humans should block a hijack. Borgs and animals are ignored and will not block if this is TRUE.
+ * solo_hijack, default FALSE, tells the proc to fail with multiple hijackers, such as for Highlander mode.
+ */
+/obj/docking_port/mobile/emergency/proc/is_hijacked(filter_by_human = TRUE, solo_hijack = FALSE)
var/has_people = FALSE
- var/hijacker_present = FALSE
+ var/hijacker_count = 0
for(var/mob/living/player in GLOB.player_list)
if(player.mind)
if(player.stat != DEAD)
- if(issilicon(player)) //Borgs are technically dead anyways
+ if(issilicon(player) && filter_by_human) //Borgs are technically dead anyways
continue
- if(isanimal(player)) //animals don't count
+ if(isanimal(player) && filter_by_human) //animals don't count
continue
if(isbrain(player)) //also technically dead
continue
@@ -287,7 +298,7 @@
var/prevent = FALSE
for(var/datum/antagonist/A in player.mind.antag_datums)
if(A.can_hijack == HIJACK_HIJACKER)
- hijacker_present = TRUE
+ hijacker_count += 1
prevent = FALSE
break //If we have both prevent and hijacker antags assume we want to hijack.
else if(A.can_hijack == HIJACK_PREVENT)
@@ -295,8 +306,8 @@
if(prevent)
return FALSE
-
- return has_people && hijacker_present
+ //has people AND either there's only one hijacker or there's any but solo_hijack is disabled
+ return has_people && ((hijacker_count == 1) || (hijacker_count && !solo_hijack))
/obj/docking_port/mobile/emergency/proc/ShuttleDBStuff()
set waitfor = FALSE
diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi
index 1ff82985af9..f8ef748273f 100644
Binary files a/icons/mob/robots.dmi and b/icons/mob/robots.dmi differ
diff --git a/icons/mob/screen_cyborg.dmi b/icons/mob/screen_cyborg.dmi
index 219d6978560..04de1b140ea 100644
Binary files a/icons/mob/screen_cyborg.dmi and b/icons/mob/screen_cyborg.dmi differ
diff --git a/icons/obj/items_cyborg.dmi b/icons/obj/items_cyborg.dmi
index a4bd75f7e51..7669c190c14 100644
Binary files a/icons/obj/items_cyborg.dmi and b/icons/obj/items_cyborg.dmi differ