diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 010c74c404c4..881c41182ab4 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -179,6 +179,8 @@ // HIVE TRAITS /// If the Hive is a Xenonid Hive #define TRAIT_XENONID "t_xenonid" +/// If the hive or xeno can use objects. +#define TRAIT_OPPOSABLE_THUMBS "t_thumbs" /// If the Hive delays round end (this is overridden for some hives). Does not occur naturally. Must be applied in events. #define TRAIT_NO_HIVE_DELAY "t_no_hive_delay" /// If the Hive uses it's colors on the mobs. Does not occur naturally, excepting the Mutated hive. diff --git a/code/controllers/subsystem/minimap.dm b/code/controllers/subsystem/minimap.dm index 478848906047..ff250625043f 100644 --- a/code/controllers/subsystem/minimap.dm +++ b/code/controllers/subsystem/minimap.dm @@ -765,7 +765,7 @@ SUBSYSTEM_DEF(minimaps) data["canDraw"] = FALSE data["canViewTacmap"] = TRUE data["canViewCanvas"] = FALSE - data["isXeno"] = FALSE + data["isxeno"] = FALSE return data @@ -781,7 +781,7 @@ SUBSYSTEM_DEF(minimaps) var/is_xeno = istype(xeno) var/faction = is_xeno ? xeno.hivenumber : user.faction - data["isXeno"] = is_xeno + data["isxeno"] = is_xeno data["canViewTacmap"] = is_xeno data["canViewCanvas"] = faction == FACTION_MARINE || faction == XENO_HIVE_NORMAL @@ -799,7 +799,7 @@ SUBSYSTEM_DEF(minimaps) data["canDraw"] = FALSE data["canViewTacmap"] = FALSE data["canViewCanvas"] = TRUE - data["isXeno"] = FALSE + data["isxeno"] = FALSE return data @@ -811,7 +811,7 @@ SUBSYSTEM_DEF(minimaps) data["canDraw"] = FALSE data["canViewTacmap"] = FALSE data["canViewCanvas"] = TRUE - data["isXeno"] = TRUE + data["isxeno"] = TRUE return data diff --git a/code/datums/action.dm b/code/datums/action.dm index 3a597ad262b1..d1768655a2da 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -191,6 +191,8 @@ var/mob/living/carbon/human/human = owner if(human.body_position == STANDING_UP) return TRUE + if((HAS_TRAIT(owner, TRAIT_OPPOSABLE_THUMBS)) && !owner.is_mob_incapacitated()) + return TRUE /datum/action/item_action/update_button_icon() button.overlays.Cut() diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index 88055a89f82b..a04dcd4d9212 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -254,6 +254,10 @@ Class Procs: return TRUE if(user.is_mob_incapacitated()) return TRUE + if(!(istype(user, /mob/living/carbon/human) || isRemoteControlling(user) || istype(user, /mob/living/carbon/xenomorph))) + if(!HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) + to_chat(usr, SPAN_DANGER("You don't have the dexterity to do this!")) + return TRUE if(!is_valid_user(user)) to_chat(usr, SPAN_DANGER("You don't have the dexterity to do this!")) return TRUE diff --git a/code/game/machinery/vending/cm_vending.dm b/code/game/machinery/vending/cm_vending.dm index bfb539043b26..cbded151c011 100644 --- a/code/game/machinery/vending/cm_vending.dm +++ b/code/game/machinery/vending/cm_vending.dm @@ -373,41 +373,49 @@ GLOBAL_LIST_EMPTY(vending_products) //------------INTERACTION PROCS--------------- -/obj/structure/machinery/cm_vending/attack_alien(mob/living/carbon/xenomorph/M) +/obj/structure/machinery/cm_vending/attack_alien(mob/living/carbon/xenomorph/user) if(stat & TIPPED_OVER || indestructible) - to_chat(M, SPAN_WARNING("There's no reason to bother with that old piece of trash.")) + to_chat(user, SPAN_WARNING("There's no reason to bother with that old piece of trash.")) return XENO_NO_DELAY_ACTION - if(M.a_intent == INTENT_HARM && !unslashable) - M.animation_attack_on(src) - if(prob(M.melee_damage_lower)) + if(user.a_intent == INTENT_HARM && !unslashable) + user.animation_attack_on(src) + if(prob(user.melee_damage_lower)) playsound(loc, 'sound/effects/metalhit.ogg', 25, 1) - M.visible_message(SPAN_DANGER("[M] smashes [src] beyond recognition!"), \ + user.visible_message(SPAN_DANGER("[user] smashes [src] beyond recognition!"), \ SPAN_DANGER("You enter a frenzy and smash [src] apart!"), null, 5, CHAT_TYPE_XENO_COMBAT) malfunction() tip_over() else - M.visible_message(SPAN_DANGER("[M] slashes [src]!"), \ + user.visible_message(SPAN_DANGER("[user] slashes [src]!"), \ SPAN_DANGER("You slash [src]!"), null, 5, CHAT_TYPE_XENO_COMBAT) playsound(loc, 'sound/effects/metalhit.ogg', 25, 1) return XENO_ATTACK_ACTION - if(M.action_busy) + if(user.action_busy) return XENO_NO_DELAY_ACTION - - M.visible_message(SPAN_WARNING("[M] begins to lean against [src]."), \ + if(user.a_intent == INTENT_HELP && user.IsAdvancedToolUser()) + user.set_interaction(src) + tgui_interact(user) + if(!hacked) + to_chat(user, SPAN_WARNING("You slash open [src]'s front panel, revealing the items within.")) + var/datum/effect_system/spark_spread/spark_system = new + spark_system.set_up(5, 5, get_turf(src)) + hacked = TRUE + return XENO_ATTACK_ACTION + user.visible_message(SPAN_WARNING("[user] begins to lean against [src]."), \ SPAN_WARNING("You begin to lean against [src]."), null, 5, CHAT_TYPE_XENO_COMBAT) var/shove_time = 80 - if(M.mob_size >= MOB_SIZE_BIG) + if(user.mob_size >= MOB_SIZE_BIG) shove_time = 30 - if(istype(M,/mob/living/carbon/xenomorph/crusher)) + if(istype(user,/mob/living/carbon/xenomorph/crusher)) shove_time = 15 - xeno_attack_delay(M) //Adds delay here and returns nothing because otherwise it'd cause lag *after* finishing the shove. + xeno_attack_delay(user) //Adds delay here and returns nothing because otherwise it'd cause lag *after* finishing the shove. - if(do_after(M, shove_time, INTERRUPT_ALL, BUSY_ICON_HOSTILE)) - M.animation_attack_on(src) - M.visible_message(SPAN_DANGER("[M] knocks [src] down!"), \ + if(do_after(user, shove_time, INTERRUPT_ALL, BUSY_ICON_HOSTILE)) + user.animation_attack_on(src) + user.visible_message(SPAN_DANGER("[user] knocks [src] down!"), \ SPAN_DANGER("You knock [src] down!"), null, 5, CHAT_TYPE_XENO_COMBAT) tip_over() return XENO_NO_DELAY_ACTION @@ -508,7 +516,12 @@ GLOBAL_LIST_EMPTY(vending_products) if(.) return - var/mob/living/carbon/human/user = usr + var/mob/living/carbon/human/human_user + var/mob/living/carbon/user = ui.user + + if(ishuman(user)) + human_user = usr + switch (action) if ("vend") if(stat & IN_USE) @@ -528,8 +541,11 @@ GLOBAL_LIST_EMPTY(vending_products) to_chat(usr, SPAN_WARNING("The floor is too cluttered, make some space.")) vend_fail() return FALSE - - if((!user.assigned_squad && squad_tag) || (!user.assigned_squad?.omni_squad_vendor && (squad_tag && user.assigned_squad.name != squad_tag))) + if(HAS_TRAIT(user,TRAIT_OPPOSABLE_THUMBS)) // the big monster 7 ft with thumbs does not care for squads + vendor_successful_vend(itemspec, usr) + add_fingerprint(usr) + return TRUE + if((!human_user.assigned_squad && squad_tag) || (!human_user.assigned_squad?.omni_squad_vendor && (squad_tag && human_user.assigned_squad.name != squad_tag))) to_chat(user, SPAN_WARNING("This machine isn't for your squad.")) vend_fail() return FALSE @@ -554,7 +570,7 @@ GLOBAL_LIST_EMPTY(vending_products) to_chat(user, SPAN_WARNING("That set is already taken.")) vend_fail() return FALSE - var/obj/item/card/id/ID = user.wear_id + var/obj/item/card/id/ID = human_user.wear_id if(!istype(ID) || !ID.check_biometrics(user)) to_chat(user, SPAN_WARNING("You must be wearing your [SPAN_INFO("dog tags")] to select a specialization!")) return FALSE @@ -584,7 +600,7 @@ GLOBAL_LIST_EMPTY(vending_products) to_chat(user, SPAN_WARNING("Something bad occurred with [src], tell a Dev.")) vend_fail() return FALSE - ID.set_assignment((user.assigned_squad ? (user.assigned_squad.name + " ") : "") + JOB_SQUAD_SPECIALIST + " ([specialist_assignment])") + ID.set_assignment((human_user.assigned_squad ? (human_user.assigned_squad.name + " ") : "") + JOB_SQUAD_SPECIALIST + " ([specialist_assignment])") GLOB.data_core.manifest_modify(user.real_name, WEAKREF(user), ID.assignment) GLOB.available_specialist_sets -= p_name else if(vendor_role.Find(JOB_SYNTH)) @@ -777,6 +793,8 @@ GLOBAL_LIST_EMPTY(vending_products) return listed_products /obj/structure/machinery/cm_vending/proc/can_access_to_vend(mob/user, display = TRUE, ignore_hack = FALSE) + if(HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) // We're just going to skip the mess of access checks assuming xenos with thumbs are human and just allow them to access because it's funny + return TRUE if(!hacked || ignore_hack) if(!allowed(user)) if(display) diff --git a/code/game/objects/items/explosives/grenades/grenade.dm b/code/game/objects/items/explosives/grenades/grenade.dm index 6b793233678d..b2f95646a966 100644 --- a/code/game/objects/items/explosives/grenades/grenade.dm +++ b/code/game/objects/items/explosives/grenades/grenade.dm @@ -36,7 +36,7 @@ to_chat(user, SPAN_WARNING("You don't have the dexterity to do this!")) return FALSE - if(harmful && !user.allow_gun_usage) + if(harmful && ishuman(user) && !user.allow_gun_usage) to_chat(user, SPAN_WARNING("Your programming prevents you from using this!")) return FALSE diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 13d1bbaefcf7..f589e9c5a52a 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -277,7 +277,7 @@ if (!ismob(M) || (get_dist(src, user) > 1) || user.is_mob_restrained() || user.stat || buckled_mob || M.buckled || !isturf(user.loc)) return - if (isxeno(user)) + if (isxeno(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) to_chat(user, SPAN_WARNING("You don't have the dexterity to do that, try a nest.")) return if (iszombie(user)) @@ -300,12 +300,12 @@ if(M.loc != src.loc) return . = buckle_mob(M) - if (M.mob_size <= MOB_SIZE_XENO && M.stat == DEAD && istype(src, /obj/structure/bed/roller)) - do_buckle(M, user) - return - if (M.mob_size > MOB_SIZE_HUMAN) - to_chat(user, SPAN_WARNING("[M] is too big to buckle in.")) - return + if (M.mob_size <= MOB_SIZE_XENO) + if ((M.stat == DEAD && istype(src, /obj/structure/bed/roller) || HAS_TRAIT(M, TRAIT_OPPOSABLE_THUMBS))) + do_buckle(M, user) + else if ((M.mob_size > MOB_SIZE_HUMAN)) + to_chat(user, SPAN_WARNING("[M] is too big to buckle in.")) + return do_buckle(M, user) // the actual buckling proc diff --git a/code/modules/admin/tabs/event_tab.dm b/code/modules/admin/tabs/event_tab.dm index 801bdcc87e18..ff0e9cc6ebaf 100644 --- a/code/modules/admin/tabs/event_tab.dm +++ b/code/modules/admin/tabs/event_tab.dm @@ -773,6 +773,7 @@ Spawn a nuke
Toggle PMC gun restrictions
Turn everyone into monkies
+ Give or take opposable thumbs and gun permits from xenos

"} diff --git a/code/modules/admin/topic/topic_events.dm b/code/modules/admin/topic/topic_events.dm index da8743d6dead..ddb33a2ccc3f 100644 --- a/code/modules/admin/topic/topic_events.dm +++ b/code/modules/admin/topic/topic_events.dm @@ -93,6 +93,63 @@ message_admins("[key_name_admin(usr)] added [amount] research credits.") GLOB.chemical_data.update_credits(amount) + if("xenothumbs") + var/grant = alert(usr, "Do you wish to grant or revoke Xenomorph firearms permits?", "Give or Take", "Grant", "Revoke", "Cancel") + if(grant == "Cancel") + return + + var/list/mob/living/carbon/xenomorph/permit_recipients = list() + var/list/datum/hive_status/permit_hives = list() + switch(alert(usr, "Do you wish to do this for one Xeno or an entire hive?", "Recipients", "Xeno", "Hive", "All Xenos")) + if("Xeno") + permit_recipients += tgui_input_list(usr, "Select recipient Xenomorph:", "Armed Xenomorph", GLOB.living_xeno_list) + if(isnull(permit_recipients[1])) //Cancel button. + return + if("Hive") + permit_hives += GLOB.hive_datum[tgui_input_list(usr, "Select recipient hive:", "Armed Hive", GLOB.hive_datum)] + if(isnull(permit_hives[1])) //Cancel button. + return + permit_recipients = permit_hives[1].totalXenos.Copy() + if("All Xenos") + permit_recipients = GLOB.living_xeno_list.Copy() + for(var/H in GLOB.hive_datum) + permit_hives += GLOB.hive_datum[H] + + var/list/handled_xenos = list() + + for(var/mob/living/carbon/xenomorph/xeno as anything in permit_recipients) + if(QDELETED(xeno) || xeno.stat == DEAD) //Xenos might die before the admin picks them. + to_chat(usr, SPAN_HIGHDANGER("[xeno] died before her firearms permit could be issued!")) + continue + if(HAS_TRAIT(xeno, TRAIT_OPPOSABLE_THUMBS)) + if(grant == "Revoke") + REMOVE_TRAIT(xeno, TRAIT_OPPOSABLE_THUMBS, TRAIT_SOURCE_HIVE) + to_chat(xeno, SPAN_XENOANNOUNCE("You forget how thumbs work. You feel a terrible sense of loss.")) + handled_xenos += xeno + else if(grant == "Grant") + ADD_TRAIT(xeno, TRAIT_OPPOSABLE_THUMBS, TRAIT_SOURCE_HIVE) + to_chat(xeno, SPAN_XENOANNOUNCE("You suddenly comprehend the magic of opposable thumbs along with surprising kinesthetic intelligence. You could do... so much with this knowledge.")) + handled_xenos += xeno + + for(var/datum/hive_status/permit_hive as anything in permit_hives) + //Give or remove the trait from newly-born xenos in this hive. + if(grant == "Grant") + LAZYADD(permit_hive.hive_inherant_traits, TRAIT_OPPOSABLE_THUMBS) + else + LAZYREMOVE(permit_hive.hive_inherant_traits, TRAIT_OPPOSABLE_THUMBS) + + if(!length(handled_xenos) && !length(permit_hives)) + return + + if(grant == "Grant") + message_admins("[usr] granted 2nd Amendment rights to [length(handled_xenos) > 1 ? "[length(handled_xenos)] xenos" : "[length(handled_xenos) == 1 ? "[handled_xenos[1]]" : "no xenos"]"]\ + [length(permit_hives) > 1 ? " in all hives, and to any new xenos. Quite possibly we will all regret this." : "[length(permit_hives) == 1 ? " in [permit_hives[1]], and to any new xenos in that hive." : "."]"]") + else + message_admins("[usr] revoked 2nd Amendment rights from [length(handled_xenos) > 1 ? "[length(handled_xenos)] xenos" : "[length(handled_xenos) == 1 ? "[handled_xenos[1]]" : "no xenos"]"]\ + [length(permit_hives) > 1 ? " in all hives, and from any new xenos." : "[length(permit_hives) == 1 ? " in [permit_hives[1]], and from any new xenos in that hive." : "."]"]") + + + /datum/admins/proc/create_humans_list(href_list) if(SSticker?.current_state < GAME_STATE_PLAYING) alert("Please wait until the game has started before spawning humans") diff --git a/code/modules/cm_marines/m2c.dm b/code/modules/cm_marines/m2c.dm index dea7d80b50f9..23a9d243b134 100644 --- a/code/modules/cm_marines/m2c.dm +++ b/code/modules/cm_marines/m2c.dm @@ -73,7 +73,7 @@ icon_state = icon_name /obj/item/device/m2c_gun/proc/check_can_setup(mob/user, turf/rotate_check, turf/open/OT, list/ACR) - if(!ishuman(user)) + if(!ishuman(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) return FALSE if(broken_gun) to_chat(user, SPAN_WARNING("You can't set up \the [src], it's completely broken!")) @@ -148,7 +148,7 @@ HMG.try_mount_gun(user) /obj/item/device/m2c_gun/attackby(obj/item/O as obj, mob/user as mob) - if(!ishuman(user)) + if(!ishuman(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) return if(!iswelder(O) || user.action_busy) @@ -330,7 +330,7 @@ update_icon() /obj/structure/machinery/m56d_hmg/auto/attackby(obj/item/O as obj, mob/user as mob) - if(!ishuman(user)) + if(!ishuman(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) return // RELOADING if(istype(O, /obj/item/ammo_magazine/m2c)) @@ -456,13 +456,12 @@ // DISASSEMBLY /obj/structure/machinery/m56d_hmg/auto/MouseDrop(over_object, src_location, over_location) - if(!ishuman(usr)) - return - var/mob/living/carbon/human/user = usr + var/mob/living/carbon/user = usr // If the user is unconscious or dead. if(user.stat) return - + if(!ishuman(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) + return if(over_object == user && in_range(src, user)) if((rounds > 0) && (user.a_intent & (INTENT_GRAB))) playsound(src.loc, 'sound/items/m56dauto_load.ogg', 75, 1) diff --git a/code/modules/cm_marines/smartgun_mount.dm b/code/modules/cm_marines/smartgun_mount.dm index 7cb3b4fa051b..b3035511cab7 100644 --- a/code/modules/cm_marines/smartgun_mount.dm +++ b/code/modules/cm_marines/smartgun_mount.dm @@ -70,7 +70,7 @@ return /obj/item/device/m56d_gun/attackby(obj/item/O as obj, mob/user as mob) - if(!ishuman(user)) + if(!ishuman(user) || !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) return if(QDELETED(O)) @@ -92,7 +92,7 @@ if(istype(machine, /obj/structure/machinery/m56d_hmg) || istype(machine, /obj/structure/machinery/m56d_post)) to_chat(user, SPAN_WARNING("This is too close to [machine]!")) return - if(!ishuman(user)) + if(!ishuman(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) return if(!has_mount) return @@ -188,7 +188,7 @@ /obj/item/device/m56d_post/attack_self(mob/user) ..() - if(!ishuman(usr)) + if(!ishuman(usr) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) return if(SSinterior.in_interior(user)) to_chat(usr, SPAN_WARNING("It's too cramped in here to deploy \a [src].")) @@ -296,9 +296,9 @@ return XENO_ATTACK_ACTION /obj/structure/machinery/m56d_post/MouseDrop(over_object, src_location, over_location) //Drag the tripod onto you to fold it. - if(!ishuman(usr)) + var/mob/living/carbon/user = usr //this is us + if(!ishuman(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) return - var/mob/living/carbon/human/user = usr //this is us if(over_object == user && in_range(src, user)) if(anchored && gun_mounted) to_chat(user, SPAN_WARNING("\The [src] can't be folded while there's an unsecured gun mounted on it. Either complete the assembly or take the gun off with a crowbar.")) @@ -313,7 +313,7 @@ qdel(src) /obj/structure/machinery/m56d_post/attackby(obj/item/O, mob/user) - if(!ishuman(user)) //first make sure theres no funkiness + if(!ishuman(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) //first make sure theres no funkiness return if(HAS_TRAIT(O, TRAIT_TOOL_WRENCH)) //rotate the mount @@ -549,7 +549,7 @@ /obj/structure/machinery/m56d_hmg/get_examine_text(mob/user) //Let us see how much ammo we got in this thing. . = ..() - if(ishuman(user)) + if(ishuman(user) || HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) if(rounds) . += SPAN_NOTICE("It has [rounds] round\s out of [rounds_max].") else @@ -568,7 +568,7 @@ return /obj/structure/machinery/m56d_hmg/attackby(obj/item/O as obj, mob/user as mob) //This will be how we take it apart. - if(!ishuman(user)) + if(!ishuman(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) return ..() if(QDELETED(O)) @@ -692,16 +692,18 @@ update_health(round(P.damage / 10)) //Universal low damage to what amounts to a post with a gun. return 1 -/obj/structure/machinery/m56d_hmg/attack_alien(mob/living/carbon/xenomorph/M) // Those Ayy lmaos. - if(islarva(M)) +/obj/structure/machinery/m56d_hmg/attack_alien(mob/living/carbon/xenomorph/xeno) // Those Ayy lmaos. + if(islarva(xeno)) return //Larvae can't do shit - - M.visible_message(SPAN_DANGER("[M] has slashed [src]!"), + if(xeno.IsAdvancedToolUser() && xeno.a_intent == INTENT_HELP) + try_mount_gun(xeno) + return XENO_NO_DELAY_ACTION + xeno.visible_message(SPAN_DANGER("[xeno] has slashed [src]!"), SPAN_DANGER("You slash [src]!")) - M.animation_attack_on(src) - M.flick_attack_overlay(src, "slash") + xeno.animation_attack_on(src) + xeno.flick_attack_overlay(src, "slash") playsound(loc, "alien_claw_metal", 25) - update_health(rand(M.melee_damage_lower,M.melee_damage_upper)) + update_health(rand(xeno.melee_damage_lower,xeno.melee_damage_upper)) return XENO_ATTACK_ACTION /obj/structure/machinery/m56d_hmg/proc/load_into_chamber() @@ -841,17 +843,21 @@ // Try to man the gun try_mount_gun(usr) -/obj/structure/machinery/m56d_hmg/proc/try_mount_gun(mob/living/carbon/human/user) +/obj/structure/machinery/m56d_hmg/proc/try_mount_gun(mob/living/carbon/user) // If the user isn't a human. if(!istype(user)) return // If the user is unconscious or dead. if(user.stat) return - + if(ishuman(user)) + var/mob/living/carbon/human/human = user + if(!human.allow_gun_usage) + to_chat(user, SPAN_WARNING("You aren't allowed to use firearms!")) + return // If the user isn't actually allowed to use guns. - if(!user.allow_gun_usage) - to_chat(user, SPAN_WARNING("You aren't allowed to use firearms!")) + else if (!HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) + to_chat(user, SPAN_WARNING("You don't know what to do with [src]!")) return // If the user is invisible. diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index c25b52d1dd37..10f2af57b719 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -649,6 +649,10 @@ tracked_marker = null + ///This permits xenos with thumbs to fire guns and arm grenades. God help us all. +/mob/living/carbon/xenomorph/IsAdvancedToolUser() + return HAS_TRAIT(src, TRAIT_OPPOSABLE_THUMBS) + /mob/living/carbon/xenomorph/proc/do_nesting_host(mob/current_mob, nest_structural_base) var/list/xeno_hands = list(get_active_hand(), get_inactive_hand()) diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm index 69ab18431237..16cfeca8f87e 100644 --- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm +++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm @@ -52,6 +52,8 @@ gender = NEUTER icon_size = 48 black_market_value = KILL_MENDOZA + ///How much to horizontally adjust the sprites of held item onmobs by. Based on icon size. Most xenos have hands about the same height as a human's. + var/xeno_inhand_item_offset dead_black_market_value = 50 light_system = MOVABLE_LIGHT var/obj/item/clothing/suit/wear_suit = null @@ -429,7 +431,7 @@ GLOB.living_xeno_list += src GLOB.xeno_mob_list += src - + xeno_inhand_item_offset = (icon_size - 32) * 0.5 // More setup stuff for names, abilities etc update_icon_source() generate_name() diff --git a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm index 7479fb356e43..2ab5f6ae703d 100644 --- a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm +++ b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm @@ -314,7 +314,10 @@ //This proc is here to prevent Xenomorphs from picking up objects (default attack_hand behaviour) //Note that this is overridden by every proc concerning a child of obj unless inherited -/obj/item/attack_alien(mob/living/carbon/xenomorph/M) +/obj/item/attack_alien(mob/living/carbon/xenomorph/xeno) + if(HAS_TRAIT(xeno, TRAIT_OPPOSABLE_THUMBS)) + attack_hand(xeno) + return XENO_NONCOMBAT_ACTION return /obj/attack_larva(mob/living/carbon/xenomorph/larva/M) @@ -381,10 +384,16 @@ //If we sent it to monkey we'd get some weird shit happening. /obj/structure/attack_alien(mob/living/carbon/xenomorph/M) // fuck off dont destroy my unslashables - if(unslashable || health <= 0) + if(unslashable || health <= 0 && !HAS_TRAIT(usr, TRAIT_OPPOSABLE_THUMBS)) to_chat(M, SPAN_WARNING("We stare at \the [src] cluelessly.")) return XENO_NO_DELAY_ACTION +/obj/structure/magazine_box/attack_alien(mob/living/carbon/xenomorph/xeno) + if(HAS_TRAIT(usr, TRAIT_OPPOSABLE_THUMBS)) + attack_hand(xeno) + return XENO_NONCOMBAT_ACTION + else + . = ..() //Beds, nests and chairs - unbuckling /obj/structure/bed/attack_alien(mob/living/carbon/xenomorph/M) @@ -664,7 +673,7 @@ //Xenomorphs can't use machinery, not even the "intelligent" ones //Exception is Queen and shuttles, because plot power /obj/structure/machinery/attack_alien(mob/living/carbon/xenomorph/M) - if(unslashable || health <= 0) + if(unslashable || health <= 0 && !HAS_TRAIT(usr, TRAIT_OPPOSABLE_THUMBS)) to_chat(M, SPAN_WARNING("We stare at \the [src] cluelessly.")) return XENO_NO_DELAY_ACTION @@ -683,7 +692,7 @@ // Destroying reagent dispensers /obj/structure/reagent_dispensers/attack_alien(mob/living/carbon/xenomorph/M) - if(unslashable || health <= 0) + if(unslashable || health <= 0 && !HAS_TRAIT(usr, TRAIT_OPPOSABLE_THUMBS)) to_chat(M, SPAN_WARNING("We stare at \the [src] cluelessly.")) return XENO_NO_DELAY_ACTION diff --git a/code/modules/mob/living/carbon/xenomorph/update_icons.dm b/code/modules/mob/living/carbon/xenomorph/update_icons.dm index 571f261ab981..cf84312657a3 100644 --- a/code/modules/mob/living/carbon/xenomorph/update_icons.dm +++ b/code/modules/mob/living/carbon/xenomorph/update_icons.dm @@ -148,7 +148,12 @@ var/t_state = r_hand.item_state if(!t_state) t_state = r_hand.icon_state - overlays_standing[X_R_HAND_LAYER] = r_hand.get_mob_overlay(src, WEAR_R_HAND) + /*Move inhand image to the center of the sprite. Strictly speaking this should probably be like monkey get_offset_overlay_image() and tailor item icon + positions to the hands of the xeno, but outside of special occasions xenos can't really pick items up and this tends to look better than human default.*/ + var/image/inhand_image = r_hand.get_mob_overlay(src, WEAR_R_HAND) + inhand_image.pixel_x = xeno_inhand_item_offset + overlays_standing[X_R_HAND_LAYER] = inhand_image + apply_overlay(X_R_HAND_LAYER) /mob/living/carbon/xenomorph/update_inv_l_hand() @@ -161,7 +166,13 @@ var/t_state = l_hand.item_state if(!t_state) t_state = l_hand.icon_state - overlays_standing[X_L_HAND_LAYER] = l_hand.get_mob_overlay(src, WEAR_L_HAND) + + /*Move inhand image overlay to the center of the sprite. Strictly speaking this should probably be like monkey get_offset_overlay_image() and tailor item icon + positions to the hands of the xeno, but outside of special occasions xenos can't really pick items up and this tends to look better than human default.*/ + var/image/inhand_image = l_hand.get_mob_overlay(src, WEAR_L_HAND) + inhand_image.pixel_x = xeno_inhand_item_offset + overlays_standing[X_L_HAND_LAYER] = inhand_image + apply_overlay(X_L_HAND_LAYER) /mob/living/carbon/xenomorph/update_inv_back() diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 68f169fa2b25..a9000eec5c28 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -1525,7 +1525,7 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed return //We just put the gun up. Can't do it that fast if(ismob(user)) //Could be an object firing the gun. - if(!user.IsAdvancedToolUser()) + if(!user.IsAdvancedToolUser() && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) to_chat(user, SPAN_WARNING("You don't have the dexterity to do this!")) return @@ -1755,7 +1755,7 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed else total_recoil -= user.skills.get_skill_level(SKILL_FIREARMS)*RECOIL_AMOUNT_TIER_5 - if(total_recoil > 0 && ishuman(user)) + if(total_recoil > 0 && (ishuman(user) || HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS))) if(total_recoil >= 4) shake_camera(user, total_recoil * 0.5, total_recoil) else @@ -1767,7 +1767,7 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed /obj/item/weapon/gun/proc/muzzle_flash(angle,mob/user) if(!muzzle_flash || flags_gun_features & GUN_SILENCED || isnull(angle)) return //We have to check for null angle here, as 0 can also be an angle. - if(!istype(user) || !istype(user.loc,/turf)) + if(!istype(user) || !isturf(user.loc)) return var/prev_light = light_range @@ -1776,12 +1776,12 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed set_light_on(TRUE) addtimer(CALLBACK(src, PROC_REF(reset_light_range), prev_light), 0.5 SECONDS) - var/image_layer = (user && user.dir == SOUTH) ? MOB_LAYER+0.1 : MOB_LAYER-0.1 - var/offset = 5 - - var/image/I = image('icons/obj/items/weapons/projectiles.dmi',user,muzzle_flash,image_layer) + var/image/I = image('icons/obj/items/weapons/projectiles.dmi', user, muzzle_flash, user.dir == NORTH ? ABOVE_LYING_MOB_LAYER : FLOAT_LAYER) var/matrix/rotate = matrix() //Change the flash angle. - rotate.Translate(0, offset) + if(iscarbonsizexeno(user)) + var/mob/living/carbon/xenomorph/xeno = user + I.pixel_x = xeno.xeno_inhand_item_offset //To center it on the xeno sprite without being thrown off by rotation. + rotate.Translate(0, 5) //Y offset to push the flash overlay outwards. rotate.Turn(angle) I.transform = rotate I.flick_overlay(user, 3) diff --git a/code/modules/projectiles/gun_helpers.dm b/code/modules/projectiles/gun_helpers.dm index 1f407081f033..e5d0ef1c0ef8 100644 --- a/code/modules/projectiles/gun_helpers.dm +++ b/code/modules/projectiles/gun_helpers.dm @@ -460,9 +460,9 @@ DEFINES in setup.dm, referenced here. else attack_verb = list("slashed", "stabbed", "speared", "torn", "punctured", "pierced", "gored") //Greater than 35 /obj/item/weapon/gun/proc/get_active_firearm(mob/user, restrictive = TRUE) - if(!ishuman(usr)) - return if(user.is_mob_incapacitated() || !isturf(usr.loc)) + return + if(!ishuman(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) to_chat(user, SPAN_WARNING("Not right now.")) return @@ -782,7 +782,7 @@ DEFINES in setup.dm, referenced here. if(flags_gun_features & GUN_BURST_FIRING) return - if(!ishuman(usr)) + if(!ishuman(usr) && !HAS_TRAIT(usr, TRAIT_OPPOSABLE_THUMBS)) return if(usr.is_mob_incapacitated() || !usr.loc || !isturf(usr.loc)) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 3aaeff519456..edb565158185 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1062,7 +1062,7 @@ bullet_message(P, damaging = FALSE) return - if(isxeno(P.firer)) + if(isxeno(P.firer) && ammo_flags & (AMMO_ACIDIC|AMMO_XENO)) //Xenomorph shooting spit. Xenos with thumbs and guns can fully FF. var/mob/living/carbon/xenomorph/X = P.firer if(X.can_not_harm(src)) bullet_ping(P) diff --git a/code/modules/vehicles/interior/interactable/seats.dm b/code/modules/vehicles/interior/interactable/seats.dm index 253b4a066b4f..8abbf9f1190d 100644 --- a/code/modules/vehicles/interior/interactable/seats.dm +++ b/code/modules/vehicles/interior/interactable/seats.dm @@ -108,7 +108,9 @@ to_chat(user, SPAN_WARNING("You are unable to use heavy weaponry.")) return - for(var/obj/item/I in user.contents) //prevents shooting while zoomed in, but zoom can still be activated and used without shooting + if(!HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) + return + for(var/obj/item/I in user.contents) //prevents shooting while zoomed in, but zoom can still be activated and used without shooting if(I.zoom) I.zoom(user) diff --git a/tgui/packages/tgui/interfaces/TacticalMap.tsx b/tgui/packages/tgui/interfaces/TacticalMap.tsx index 934589a0850e..c3a386cc6f92 100644 --- a/tgui/packages/tgui/interfaces/TacticalMap.tsx +++ b/tgui/packages/tgui/interfaces/TacticalMap.tsx @@ -13,7 +13,7 @@ interface TacMapProps { svgData: any; canViewTacmap: number; canDraw: number; - isXeno: boolean; + isxeno: boolean; canViewCanvas: number; newCanvasFlatImage: string; oldCanvasFlatImage: string; @@ -104,7 +104,7 @@ export const TacticalMap = (props) => { + theme={data.isxeno ? 'hive_status' : 'crtblue'}>
{ return (