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