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'}>