diff --git a/code/__DEFINES/conflict.dm b/code/__DEFINES/conflict.dm
index 0e915bf200bf..cf6d0a2cf8a2 100644
--- a/code/__DEFINES/conflict.dm
+++ b/code/__DEFINES/conflict.dm
@@ -50,6 +50,8 @@
#define PROJECTILE_SHRAPNEL (1<<0)
/// Apply additional effects upon hitting clicked target
#define PROJECTILE_BULLSEYE (1<<1)
+/// Reflected projectiles
+#define PROJECTILE_REFLECTED (1<<2)
//Gun defines for gun related thing. More in the projectile folder.
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 40c38d270b89..7c55f81361ff 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -169,6 +169,8 @@
#define TRAIT_VALKYRIE_ARMORED "trait_valkyrie_armored"
/// Prevents mob from riding mobs when buckled onto something
#define TRAIT_CANT_RIDE "cant_ride"
+/// Makes mob immune to dir lock slowdown.
+#define TRAIT_NO_DIR_LOCK_SLOWDOWN "no_dir_lock_slowdown"
// SPECIES TRAITS
/// Knowledge of Yautja technology
@@ -265,6 +267,11 @@
#define TRAIT_ABILITY_BURROWED "t_ability_burrowed"
/// Xenos with this trait can toggle long sight while resting.
#define TRAIT_ABILITY_SIGHT_IGNORE_REST "t_ability_sight_ignore_rest"
+/// Used by shielder to check stance.
+#define TRAIT_ABILITY_ENCLOSED_PLATES "t_ability_enclosed_plates"
+/// Used by shielder for reflective plates.
+#define TRAIT_ABILITY_REFLECTIVE_PLATES "t_ability_reflective_plates"
+
//-- item traits --
// TOOL TRAITS
@@ -344,6 +351,8 @@ GLOBAL_LIST_INIT(mob_traits, list(
TRAIT_DEXTROUS,
TRAIT_REAGENT_SCANNER,
TRAIT_ABILITY_BURROWED,
+ TRAIT_ABILITY_ENCLOSED_PLATES,
+ TRAIT_ABILITY_REFLECTIVE_PLATES,
TRAIT_VULTURE_USER,
TRAIT_IN_TUTORIAL,
TRAIT_SPEC_KIT,
diff --git a/code/__DEFINES/typecheck/items.dm b/code/__DEFINES/typecheck/items.dm
index 5c4d099b8112..4438cd918aa0 100644
--- a/code/__DEFINES/typecheck/items.dm
+++ b/code/__DEFINES/typecheck/items.dm
@@ -1,9 +1,7 @@
#define iswelder(O) (istype(O, /obj/item/tool/weldingtool))
-#define iscoil(O) (istype(O, /obj/item/stack/cable_coil))
#define iswire(O) (istype(O, /obj/item/stack/cable_coil))
#define isweapon(O) (O && is_type_in_list(O, GLOB.weapons))
#define isgun(O) (istype(O, /obj/item/weapon/gun))
-#define isbanana(O) (istype(O, /obj/item/reagent_container/food/snacks/grown/banana))
#define istool(O) (O && is_type_in_list(O, GLOB.common_tools))
#define ispowerclamp(O) (istype(O, /obj/item/powerloader_clamp))
#define isstorage(O) (istype(O, /obj/item/storage))
@@ -27,7 +25,3 @@ GLOBAL_LIST_INIT(common_tools, list(
/obj/item/device/multitool,
/obj/item/tool/crowbar
))
-
-/obj/item/proc/can_pry()
- if(pry_capable > IS_PRY_CAPABLE_SIMPLE || HAS_TRAIT(src, TRAIT_TOOL_CROWBAR))
- return TRUE
diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm
index b6377a47337b..9af7e845818b 100644
--- a/code/__DEFINES/xeno.dm
+++ b/code/__DEFINES/xeno.dm
@@ -421,6 +421,9 @@
// Lurker strain flags
#define LURKER_VAMPIRE "Vampire"
+// Warrior strain flags
+#define WARRIOR_BULWARK "Bulwark"
+
// Ravager strain flags
#define RAVAGER_HEDGEHOG "Hedgehog"
#define RAVAGER_BERSERKER "Berserker"
@@ -449,6 +452,7 @@
// Damage - this is applied as a flat nerf/buff to the xeno's average damage
#define XENO_DAMAGE_MOD_VERY_SMALL 5
+#define XENO_DAMAGE_MOD_BULWARK 8
#define XENO_DAMAGE_MOD_SMALL 10
#define XENO_DAMAGE_MOD_MED 15
#define XENO_DAMAGE_MOD_LARGE 20
@@ -747,6 +751,8 @@
// dancer defines
#define DANCER_DODGE_TIME 7 SECONDS
+// bulwark defines
+#define BULWARK_REFLECTIVE_TIME 6 SECONDS
// drone fruits
diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm
index e853760b103a..41ccad15fe3d 100644
--- a/code/_onclick/adjacent.dm
+++ b/code/_onclick/adjacent.dm
@@ -273,7 +273,7 @@ Quick adjacency (to turf):
var/list/cur_dense_blockers = list()
for(var/atom/blocker in blockers["fd1"])
if(blocker.flags_barrier & HANDLE_BARRIER_CHANCE)
- if(blocker.handle_barrier_chance())
+ if(blocker.handle_barrier_chance(attacker))
return blocker
else
guaranteed_hit = 1
@@ -282,7 +282,7 @@ Quick adjacency (to turf):
for(var/atom/blocker in blockers["fd2"])
if(blocker.flags_barrier & HANDLE_BARRIER_CHANCE)
- if(blocker.handle_barrier_chance())
+ if(blocker.handle_barrier_chance(attacker))
return blocker
else
guaranteed_hit++
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 1c180ba16b25..13b08bf3672f 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -105,6 +105,10 @@
if(isxeno(M))
var/mob/living/carbon/xenomorph/X = M
power = armor_damage_reduction(GLOB.xeno_melee, power, X.armor_deflection + X.armor_deflection_buff - X.armor_deflection_debuff, 20, 0, 0, X.armor_integrity)
+
+ if(X.melee_vulnerability_mult != 0)
+ power *= X.melee_vulnerability_mult
+
var/armor_punch = armor_break_calculation(GLOB.xeno_melee, power, X.armor_deflection + X.armor_deflection_buff - X.armor_deflection_debuff, 20, 0, 0, X.armor_integrity)
X.apply_armorbreak(armor_punch)
if(hitsound)
diff --git a/code/controllers/subsystem/cmtv.dm b/code/controllers/subsystem/cmtv.dm
index 03104a886720..73c644fb00e2 100644
--- a/code/controllers/subsystem/cmtv.dm
+++ b/code/controllers/subsystem/cmtv.dm
@@ -484,27 +484,44 @@ SUBSYSTEM_DEF(cmtv)
camera_operator.view = "32x24"
/datum/controller/subsystem/cmtv/proc/is_subscriber(client/potential_subscriber)
- if(!CONFIG_GET(string/cmtv_api) || !CONFIG_GET(string/cmtv_api_key))
- return FALSE
+ var/static/lookup_cache = list()
+
+ var/cmtv_subscriber_api = CONFIG_GET(string/cmtv_subscriber_api)
+ var/cmtv_subscriber_api_key = CONFIG_GET(string/cmtv_subscriber_api_key)
- WAIT_DB_READY
+ if(!CONFIG_GET(string/cmtv_api) || !CONFIG_GET(string/cmtv_api_key) || !cmtv_subscriber_api || !cmtv_subscriber_api_key)
+ return FALSE
UNTIL(initialized)
if(!potential_subscriber)
return FALSE
- var/list/datum/view_record/twitch_link/links = DB_VIEW(/datum/view_record/twitch_link, DB_AND(
- DB_COMP("ckey", DB_EQUALS, potential_subscriber.ckey),
- DB_COMP("twitch_id", DB_ISNOT)
- ))
+ var/twitch_id = lookup_cache[potential_subscriber.ckey]
+ if(!twitch_id)
+ var/datum/http_request/request = new
+ request.prepare(RUSTG_HTTP_METHOD_GET, "[cmtv_subscriber_api]?ckey=[potential_subscriber.ckey]", null, list("Authorization" = "Bearer [cmtv_subscriber_api_key]"))
+ request.begin_async()
- if(!length(links))
- return FALSE
+ UNTIL(request.is_complete())
+
+ var/datum/http_response/response = request.into_response()
+
+ var/decoded
+ try
+ decoded = json_decode(response.body)
+ catch
+ log_debug("cmtv_subscriber_api returned an invalid response.")
+ return FALSE
+
+ twitch_id = decoded["twitch_id"]
+ if(!twitch_id)
+ return FALSE
- for(var/datum/view_record/twitch_link/link as anything in links)
- if(link.twitch_id in subscribers)
- return TRUE
+ lookup_cache[potential_subscriber.ckey] = twitch_id
+
+ if(twitch_id in subscribers)
+ return TRUE
return FALSE
@@ -657,6 +674,13 @@ SUBSYSTEM_DEF(cmtv)
/datum/config_entry/string/cmtv_api_key
protection = CONFIG_ENTRY_HIDDEN | CONFIG_ENTRY_LOCKED
+/datum/config_entry/string/cmtv_subscriber_api
+ protection = CONFIG_ENTRY_LOCKED
+
+/datum/config_entry/string/cmtv_subscriber_api_key
+ protection = CONFIG_ENTRY_HIDDEN | CONFIG_ENTRY_LOCKED
+
+
/atom/movable/screen/cmtv
plane = ESCAPE_MENU_PLANE
clear_with_screen = FALSE
diff --git a/code/controllers/subsystem/hijack.dm b/code/controllers/subsystem/hijack.dm
index 9c9a4447b1c6..b9daf9c2e9ca 100644
--- a/code/controllers/subsystem/hijack.dm
+++ b/code/controllers/subsystem/hijack.dm
@@ -797,7 +797,7 @@ SUBSYSTEM_DEF(hijack)
var/cause_data = create_cause_data("ship explosion")
for(var/obj/structure/machinery/power/apc/apc as anything in apcs)
var/turf/apc_turf = get_turf(apc)
- if(apc_turf && prob(chance))
+ if(apc_turf && apc.crash_break_probability && prob(chance))
cell_explosion(apc_turf, 30, 5, explosion_cause_data=cause_data, enviro=TRUE)
CHECK_TICK
diff --git a/code/datums/ammo/bullet/shotgun.dm b/code/datums/ammo/bullet/shotgun.dm
index b06d42ce42b9..345152076e28 100644
--- a/code/datums/ammo/bullet/shotgun.dm
+++ b/code/datums/ammo/bullet/shotgun.dm
@@ -209,6 +209,10 @@
/datum/ammo/bullet/shotgun/buckshot/on_hit_mob(mob/M,obj/projectile/P)
knockback(M,P)
+/datum/ammo/bullet/shotgun/buckshot/turret
+ flags_ammo_behavior = AMMO_NO_DEFLECT //New Exclusive ammo for shotgun turrets.
+ bonus_projectiles_type = /datum/ammo/bullet/shotgun/spread/turret
+
//buckshot variant only used by the masterkey shotgun attachment.
/datum/ammo/bullet/shotgun/buckshot/masterkey
bonus_projectiles_type = /datum/ammo/bullet/shotgun/spread/masterkey
@@ -238,6 +242,9 @@
/datum/ammo/bullet/shotgun/spread/masterkey
damage = 20
+/datum/ammo/bullet/shotgun/spread/turret
+ flags_ammo_behavior = AMMO_NO_DEFLECT //New Exclusive buckshot pellets for shotgun turrets.
+
/*
8 GAUGE SHOTGUN AMMO
*/
diff --git a/code/datums/ammo/bullet/special_ammo.dm b/code/datums/ammo/bullet/special_ammo.dm
index 19abf007c2e7..7f5d4d9dd1d8 100644
--- a/code/datums/ammo/bullet/special_ammo.dm
+++ b/code/datums/ammo/bullet/special_ammo.dm
@@ -151,7 +151,7 @@
/datum/ammo/bullet/turret
name = "autocannon bullet"
icon_state = "redbullet" //Red bullets to indicate friendly fire restriction
- flags_ammo_behavior = AMMO_BALLISTIC|AMMO_IGNORE_COVER
+ flags_ammo_behavior = AMMO_BALLISTIC|AMMO_IGNORE_COVER|AMMO_NO_DEFLECT
accurate_range = 22
accuracy_var_low = PROJECTILE_VARIANCE_TIER_8
diff --git a/code/datums/ammo/misc.dm b/code/datums/ammo/misc.dm
index 516faadeb21b..8f7659dd9871 100644
--- a/code/datums/ammo/misc.dm
+++ b/code/datums/ammo/misc.dm
@@ -66,7 +66,7 @@
landingsmoke = null
/datum/ammo/flamethrower/sentry_flamer
- flags_ammo_behavior = AMMO_IGNORE_ARMOR|AMMO_IGNORE_COVER|AMMO_FLAME
+ flags_ammo_behavior = AMMO_IGNORE_ARMOR|AMMO_IGNORE_COVER|AMMO_FLAME|AMMO_NO_DEFLECT
flamer_reagent_id = "napalmx"
accuracy = HIT_ACCURACY_TIER_8
diff --git a/code/datums/entities/twitch_link.dm b/code/datums/entities/twitch_link.dm
deleted file mode 100644
index 5c1ccc4e30a3..000000000000
--- a/code/datums/entities/twitch_link.dm
+++ /dev/null
@@ -1,73 +0,0 @@
-
-/datum/entity/twitch_link
- var/ckey
- var/access_code
- var/twitch_id
-
-/datum/entity_meta/twitch_link
- entity_type = /datum/entity/twitch_link
- table_name = "twitch_link"
- field_types = list(
- "ckey" = DB_FIELDTYPE_STRING_LARGE,
- "access_code" = DB_FIELDTYPE_STRING_MEDIUM,
- "twitch_id" = DB_FIELDTYPE_STRING_LARGE,
- )
-
-/datum/view_record/twitch_link
- var/ckey
- var/access_code
- var/twitch_id
- var/id
-
-/datum/entity_view_meta/twitch_link
- root_record_type = /datum/entity/twitch_link
- destination_entity = /datum/view_record/twitch_link
-
- fields = list(
- "ckey",
- "access_code",
- "twitch_id",
- "id",
- )
-
-/datum/config_entry/string/twitch_link_url
- protection = CONFIG_ENTRY_LOCKED
-
-CLIENT_VERB(link_twitch)
- set name = "Twitch Link"
- set category = "OOC"
-
- var/url = CONFIG_GET(string/twitch_link_url)
- if(!url)
- to_chat(src, SPAN_WARNING("Twitch linking is not enabled on this server."))
- return
-
- if(IsGuestKey(key, TRUE))
- to_chat(src, SPAN_WARNING("You must be connected as a BYOND key to connect to Twitch."))
- return
-
- if(length(DB_VIEW(/datum/view_record/twitch_link,
- DB_AND(
- DB_COMP("ckey", DB_EQUALS, ckey),
- DB_COMP("twitch_id", DB_IS)
- ))
- ))
- to_chat(src, SPAN_WARNING("You have already linked this CKEY to Twitch. Contact support to remove this."))
- return
-
- var/datum/view_record/twitch_link/existing_link = locate() in DB_VIEW(
- DB_COMP("ckey", DB_EQUALS, ckey)
- )
-
- if(existing_link)
- to_chat(src, SPAN_LARGE(SPAN_NOTICE("Please click here to link your CKEY to Twitch.")))
- return
-
- var/datum/entity/twitch_link/new_link = DB_ENTITY(/datum/entity/twitch_link)
- new_link.access_code = generate_access_code()
- new_link.ckey = ckey
-
- new_link.save()
- new_link.detach()
-
- to_chat(src, SPAN_LARGE(SPAN_NOTICE("Please click here to link your CKEY to Twitch.")))
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 2310024e96fc..68ea2b137818 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -376,6 +376,9 @@
* Called from [/atom/movable/proc/keyLoop], this exists to be overwritten by living mobs with a check to see if we're actually alive enough to change directions
*/
/atom/movable/proc/keybind_face_direction(direction)
+ if(HAS_TRAIT(src, TRAIT_ABILITY_REFLECTIVE_PLATES))
+ if(!do_after(src, 3 DECISECONDS, INTERRUPT_INCAPACITATED, BUSY_ICON_GENERIC))
+ setDir(direction)
setDir(direction)
/atom/movable/proc/onTransitZ(old_z,new_z)
diff --git a/code/game/gamemodes/colonialmarines/colonialmarines.dm b/code/game/gamemodes/colonialmarines/colonialmarines.dm
index abe9b0e10913..d23cc82505a4 100644
--- a/code/game/gamemodes/colonialmarines/colonialmarines.dm
+++ b/code/game/gamemodes/colonialmarines/colonialmarines.dm
@@ -469,7 +469,7 @@
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(marine_announcement), "Almayer, this is the Tyrargo Museum civilian evacuation site. We are under assault by a XX-121 cluster, but we are holding our own.\n\nWe have heavy XX-121 waves inbound from the north-east and are under heavy suppression, our evacuation craft are pinned by long range boiler strikes and the western city exits are too dangerous to move towards with ground based evacuation vehicles, we’re requesting you secure the western approach so you can suppress the enemy forces to allow civilian evacuation, over.", "Tyrargo Civilian Evac, 1st Air Cav Headquarters", 'sound/AI/commandreport.ogg'), 15 MINUTES)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(xeno_announcement), "Be on guard my children. I have sensed that the petrid sewers of this so called city could be flooded by the hosts at a moments notice if the hosts restore power to the area. The button to release this putrid water is found in the metal structure the hosts call the sewer treatement plant.", "everything", QUEEN_MOTHER_ANNOUNCE), 15 MINUTES)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(marine_announcement), "Attention: Analysis of city layout plans have identified a possible tactical advantage. A release valve can be triggered within the City Sewer Treatment Plant, this valve will flood the lower sewer tunnels with water, expunging a significant amount of xenobiological growth.\n\nHowever, this valve must be powered by repairing a special APC located within the underground power-substation, located east of the underground sewer treatment plant.", "ARES 3.2 Strategic Notice", 'sound/AI/commandreport.ogg'), 20 MINUTES)
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(marine_announcement), "Almayer. We’re seeing increased XX-121 activity at the Tyrargo evac site. Additional strains are inbound from the north.\n\nEnemy Boiler’s have moved close enough to suppress our air support, we’re re-orienting the Longstreet tanks to cover our flanks. Requesting immediate suppression of enemy forces near our location via the western city entrance, over. ", "Tyrargo Civilian Evac, 1st Air Cav Headquarters", 'sound/AI/commandreport.ogg'), 35 MINUTES)
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(marine_announcement), "Almayer. We’re seeing increased XX-121 activity at the Tyrargo evac site. Additional strains are inbound from the north.\n\nEnemy Boilers have moved close enough to suppress our air support, we’re re-orienting the Longstreet tanks to cover our flanks. Requesting immediate suppression of enemy forces near our location via the western city entrance, over. ", "Tyrargo Civilian Evac, 1st Air Cav Headquarters", 'sound/AI/commandreport.ogg'), 35 MINUTES)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(marine_announcement), "All elements, more XX-121 clusters are encroaching from our east. We’re under heavy attack from all quarters and have lost half of our Longstreet tank support to Crushers.\n\nWe’ve exhausted our HEAP munitions and have had to switch to soft-point munitions. We can’t take this for much longer, requesting urgent support from Almayer forces, over.", "Tyrargo Civilian Evac, 1st Air Cav Headquarters", 'sound/AI/commandreport.ogg'), 60 MINUTES)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(marine_announcement), "This is Tyrargo. The xenos have begun to encroach from our southern flank. We only have a single tank left. We’re withdrawing to the middle corridor and have relocated the civilians to the inner perimeter.\n\nSituation is dire, we’re getting wasted. We need that support, over.", "Tyrargo Civilian Evac, 1st Air Cav Headquarters", 'sound/AI/commandreport.ogg'), 80 MINUTES)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(marine_announcement), "All elements! This is the Tyrargo evac site, our situation is critical. The bugs have us surrounded on all fronts, our armoured support is destroyed and we’re now being pinned by enemy Ravagers.\n\nWe need urgent fire support, we can’t take it much longer.", "Tyrargo Civilian Evac, 1st Air Cav Headquarters", 'sound/AI/commandreport.ogg'), 100 MINUTES)
diff --git a/code/game/machinery/air_alarm.dm b/code/game/machinery/air_alarm.dm
index 9189936be0ff..3b917baf393c 100644
--- a/code/game/machinery/air_alarm.dm
+++ b/code/game/machinery/air_alarm.dm
@@ -984,7 +984,7 @@ table tr:first-child th:first-child { border: none;}
return
if(1)
- if(iscoil(W))
+ if(iswire(W))
var/obj/item/stack/cable_coil/C = W
if(C.use(5))
to_chat(user, SPAN_NOTICE("You wire \the [src]."))
diff --git a/code/game/machinery/colony_floodlights.dm b/code/game/machinery/colony_floodlights.dm
index c899b643453d..f6f02fefc737 100644
--- a/code/game/machinery/colony_floodlights.dm
+++ b/code/game/machinery/colony_floodlights.dm
@@ -283,7 +283,7 @@ GLOBAL_LIST_INIT(all_breaker_switches, list())
to_chat(user, SPAN_WARNING("You need more welding fuel to complete this task."))
return TRUE
- else if(iscoil(I))
+ else if(iswire(I))
var/obj/item/stack/cable_coil/coil = I
if(!skillcheck(user, SKILL_ENGINEER, SKILL_ENGINEER_TRAINED))
to_chat(user, SPAN_WARNING("You have no clue how to repair [src]."))
diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm
index f6ae18e6a339..6b6f9558c339 100644
--- a/code/game/machinery/constructable_frame.dm
+++ b/code/game/machinery/constructable_frame.dm
@@ -53,7 +53,7 @@
return
switch(state)
if(CONSTRUCTION_STATE_BEGIN)
- if(iscoil(P))
+ if(iswire(P))
if(!skillcheck(user, SKILL_CONSTRUCTION, required_skill))
to_chat(user, SPAN_WARNING("You are not trained to build machines..."))
return
diff --git a/code/game/objects/effects/effect_system/smoke.dm b/code/game/objects/effects/effect_system/smoke.dm
index df33523d468a..25a1ce72d83a 100644
--- a/code/game/objects/effects/effect_system/smoke.dm
+++ b/code/game/objects/effects/effect_system/smoke.dm
@@ -47,7 +47,7 @@
src.time_to_live += rand(-1,1)
var/area/my_area = get_area(src)
- if(my_area.flags_area & AREA_HEAVILY_VENTILATED)
+ if(my_area?.flags_area & AREA_HEAVILY_VENTILATED)
var/new_amount = rand(1,3)
src.time_to_live = min(new_amount, src.time_to_live)
diff --git a/code/game/objects/items/frames/camera.dm b/code/game/objects/items/frames/camera.dm
index c951e8dfeb5b..67f5765aa2d9 100644
--- a/code/game/objects/items/frames/camera.dm
+++ b/code/game/objects/items/frames/camera.dm
@@ -58,7 +58,7 @@
if(2)
// State 2
- if(iscoil(W))
+ if(iswire(W))
var/obj/item/stack/cable_coil/C = W
if(C.use(2))
to_chat(user, SPAN_NOTICE("You add wires to the assembly."))
diff --git a/code/game/objects/items/tools/maintenance_tools.dm b/code/game/objects/items/tools/maintenance_tools.dm
index 95437a42524c..a6f377e22c52 100644
--- a/code/game/objects/items/tools/maintenance_tools.dm
+++ b/code/game/objects/items/tools/maintenance_tools.dm
@@ -591,7 +591,12 @@
if(attacked_door.locked) //Bolted
to_chat(user, SPAN_DANGER("You can't pry open [attacked_door] while it is bolted shut."))
return
-
+ if(!attacked_door.density && !attacked_door.arePowerSystemsOn()) //If its open and unpowered
+ attacked_door.close(TRUE)
+ return
+ if(attacked_door.density && !attacked_door.arePowerSystemsOn()) // if its closed and unpowered
+ attacked_door.open(TRUE)
+ return
if(requires_superstrength_pry)
if(!HAS_TRAIT(user, TRAIT_SUPER_STRONG)) //basically IS_PRY_CAPABLE_CROWBAR
return
@@ -600,15 +605,8 @@
return
if(user.action_busy)
return
- if(!attacked_door.density && !attacked_door.arePowerSystemsOn()) //If its open and unpowered
- attacked_door.close(TRUE)
- return
- if(attacked_door.density && !attacked_door.arePowerSystemsOn()) // if its closed and unpowered
- attacked_door.open(TRUE)
- return
if(!attacked_door.density) //If its open
return
-
user.visible_message(SPAN_DANGER("[user] jams [src] into [attacked_door] and starts to pry it open."),
SPAN_DANGER("You jam [src] into [attacked_door] and start to pry it open."))
playsound(src, "pry", 15, TRUE)
diff --git a/code/game/objects/structures/barricade/barricade.dm b/code/game/objects/structures/barricade/barricade.dm
index 20285725c564..38c0916ea634 100644
--- a/code/game/objects/structures/barricade/barricade.dm
+++ b/code/game/objects/structures/barricade/barricade.dm
@@ -221,9 +221,15 @@
return ..()
-/obj/structure/barricade/handle_barrier_chance()
+/obj/structure/barricade/handle_barrier_chance(mob/living/attacker)
if(!anchored)
return FALSE
+
+ if(isxeno(attacker))
+ var/mob/living/carbon/xenomorph/xeno = attacker
+ if(xeno.strain && istype(xeno.strain, /datum/xeno_strain/bulwark))
+ return prob(25) //Bulwark can attack through wired cade with 75% chance.
+
return prob(max(30,(100.0*health)/maxhealth))
/obj/structure/barricade/attack_animal(mob/user as mob)
diff --git a/code/game/turfs/walls/wall_types.dm b/code/game/turfs/walls/wall_types.dm
index 0b3a6b272860..ee1240f917dd 100644
--- a/code/game/turfs/walls/wall_types.dm
+++ b/code/game/turfs/walls/wall_types.dm
@@ -1477,8 +1477,11 @@
var/explosive_multiplier = 0.3
var/reflection_multiplier = 0.5
-/turf/closed/wall/resin/reflective/bullet_act(obj/projectile/P)
- if(src in P.permutated)
+/turf/closed/wall/resin/reflective/bullet_act(obj/projectile/proj_bullet)
+ if(proj_bullet.projectile_flags & PROJECTILE_REFLECTED)
+ return
+
+ if(proj_bullet.ammo.flags_ammo_behavior & AMMO_NO_DEFLECT)
return
//Ineffective if someone is sitting on the wall
@@ -1486,26 +1489,15 @@
return ..()
if(!prob(chance_to_reflect))
- if(P.ammo.damage_type == BRUTE)
- P.damage *= brute_multiplier
+ if(proj_bullet.ammo.damage_type == BRUTE)
+ proj_bullet.damage *= brute_multiplier
return ..()
- if(P.runtime_iff_group || P.ammo.flags_ammo_behavior & AMMO_NO_DEFLECT)
- // Bullet gets absorbed if it has IFF or can't be reflected.
- return
- var/obj/projectile/new_proj = new(src, construction_data ? construction_data : create_cause_data(initial(name)))
- new_proj.generate_bullet(P.ammo)
- new_proj.damage = P.damage * reflection_multiplier // don't make it too punishing
- new_proj.accuracy = HIT_ACCURACY_TIER_7 // 35% chance to hit something
-
- // Move back to who fired you.
- RegisterSignal(new_proj, COMSIG_BULLET_PRE_HANDLE_TURF, PROC_REF(bullet_ignore_turf))
- new_proj.permutated |= src
+ var/atom/target = proj_bullet.firer
+ if(!target)
+ return ..()
- var/angle = Get_Angle(src, P.firer) + rand(30, -30)
- var/atom/target = get_angle_target_turf(src, angle, get_dist(src, P.firer))
- new_proj.projectile_flags |= PROJECTILE_SHRAPNEL
- new_proj.fire_at(target, P.firer, src, reflect_range, speed = P.ammo.shell_speed)
+ proj_bullet.reflect_projectile_at_firer(src, proj_bullet, proj_bullet.firer, target, damage_multiplier = reflection_multiplier, accuracy_override = HIT_ACCURACY_TIER_7, range_override = reflect_range, angle_variance = 30)
return TRUE
diff --git a/code/modules/client/preferences_gear.dm b/code/modules/client/preferences_gear.dm
index a51820fc72c9..0594decac440 100644
--- a/code/modules/client/preferences_gear.dm
+++ b/code/modules/client/preferences_gear.dm
@@ -224,6 +224,36 @@ GLOBAL_LIST_EMPTY(roles_with_gear)
display_name = "Sunglasses"
path = /obj/item/clothing/glasses/sunglasses
+// Hippie Shades
+
+/datum/gear/eyewear/sunglasses/hippie_shades
+ display_name = "Suntex-Sightware rounded shades, pink"
+ path = /obj/item/clothing/glasses/sunglasses/hippie
+
+/datum/gear/eyewear/sunglasses/hippie_shades/green
+ display_name = "Suntex-Sightware rounded shades, green"
+ path = /obj/item/clothing/glasses/sunglasses/hippie/green
+
+/datum/gear/eyewear/sunglasses/hippie_shades/sunrise
+ display_name = "Suntex-Sightware rounded shades, sunrise"
+ path = /obj/item/clothing/glasses/sunglasses/hippie/sunrise
+
+/datum/gear/eyewear/sunglasses/hippie_shades/sunset
+ display_name = "Suntex-Sightware rounded shades, sunset"
+ path = /obj/item/clothing/glasses/sunglasses/hippie/sunset
+
+/datum/gear/eyewear/sunglasses/hippie_shades/nightblue
+ display_name = "Suntex-Sightware rounded shades, nightblue"
+ path = /obj/item/clothing/glasses/sunglasses/hippie/nightblue
+
+/datum/gear/eyewear/sunglasses/hippie_shades/midnight
+ display_name = "Suntex-Sightware rounded shades, midnight"
+ path = /obj/item/clothing/glasses/sunglasses/hippie/midnight
+
+/datum/gear/eyewear/sunglasses/hippie_shades/bloodred
+ display_name = "Suntex-Sightware rounded shades, bloodred"
+ path = /obj/item/clothing/glasses/sunglasses/hippie/bloodred
+
/datum/gear/mask
category = "Masks and scarves"
slot = WEAR_FACE
@@ -1362,36 +1392,6 @@ GLOBAL_LIST_EMPTY(roles_with_gear)
display_name = "cowboy hat, light-brown"
path = /obj/item/clothing/head/cowboy/light
-// Hippie Shades
-
-/datum/gear/eyewear/sunglasses/hippie_shades/pink
- display_name = "Suntex-Sightware rounded shades, pink"
- path = /obj/item/clothing/glasses/sunglasses/hippie
-
-/datum/gear/eyewear/sunglasses/hippie_shades/green
- display_name = "Suntex-Sightware rounded shades, green"
- path = /obj/item/clothing/glasses/sunglasses/hippie/green
-
-/datum/gear/eyewear/sunglasses/hippie_shades/sunrise
- display_name = "Suntex-Sightware rounded shades, sunrise"
- path = /obj/item/clothing/glasses/sunglasses/hippie/sunrise
-
-/datum/gear/eyewear/sunglasses/hippie_shades/sunset
- display_name = "Suntex-Sightware rounded shades, sunset"
- path = /obj/item/clothing/glasses/sunglasses/hippie/sunset
-
-/datum/gear/eyewear/sunglasses/hippie_shades/nightblue
- display_name = "Suntex-Sightware rounded shades, nightblue"
- path = /obj/item/clothing/glasses/sunglasses/hippie/nightblue
-
-/datum/gear/eyewear/sunglasses/hippie_shades/midnight
- display_name = "Suntex-Sightware rounded shades, midnight"
- path = /obj/item/clothing/glasses/sunglasses/hippie/midnight
-
-/datum/gear/eyewear/sunglasses/hippie_shades/bloodred
- display_name = "Suntex-Sightware rounded shades, bloodred"
- path = /obj/item/clothing/glasses/sunglasses/hippie/bloodred
-
// Civilian shoes
/datum/gear/civilian/shoes
diff --git a/code/modules/cm_marines/dropship_ammo.dm b/code/modules/cm_marines/dropship_ammo.dm
index c4048302bdb5..2a46e12a2b44 100644
--- a/code/modules/cm_marines/dropship_ammo.dm
+++ b/code/modules/cm_marines/dropship_ammo.dm
@@ -261,12 +261,12 @@
/obj/structure/ship_ammo/laser_battery/detonate_on(turf/impact, obj/structure/dropship_equipment/weapon/fired_from)
set waitfor = 0
- var/list/turf_list = RANGE_TURFS(3, impact) //This is its area of effect
+ var/list/turf_list = shuffle(RANGE_TURFS(3, impact)) //This is its area of effect
playsound(impact, 'sound/effects/pred_vision.ogg', 20, 1)
- for(var/i=1 to 16) //This is how many tiles within that area of effect will be randomly ignited
- var/turf/U = pick(turf_list)
- turf_list -= U
- fire_spread_recur(U, create_cause_data(fired_from.name, source_mob), 1, null, 5, 75, "#EE6515")//Very, very intense, but goes out very quick
+ var/datum/reagent/fire_reagent = create_fire_reagent(5, 75, "#EE6515")
+ for(var/i in 1 to 16) //This is how many tiles within that area of effect will be randomly ignited
+ var/turf/target_turf = turf_list[i]
+ fire_spread(target_turf, create_cause_data(fired_from.name, source_mob), 1, fire_reagent)//Very, very intense, but goes out very quick
if(!ammo_count && !QDELETED(src))
qdel(src) //deleted after last laser beam is fired and impact the ground.
@@ -316,8 +316,9 @@
/obj/structure/ship_ammo/rocket/banshee/detonate_on(turf/impact, obj/structure/dropship_equipment/weapon/fired_from)
impact.ceiling_debris_check(3)
+ var/datum/reagent/fire_reagent = create_fire_reagent(15, 50, "#00b8ff")
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cell_explosion), impact, 175, 20, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name), source_mob)), 0.5 SECONDS) //Small explosive power with a small fall off for a big explosion range
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(fire_spread), impact, create_cause_data(initial(name), source_mob), 4, 15, 50, "#00b8ff"), 0.5 SECONDS) //Very intense but the fire doesn't last very long
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(fire_spread), impact, create_cause_data(initial(name), source_mob), 4, fire_reagent), 0.5 SECONDS) //Very intense but the fire doesn't last very long
QDEL_IN(src, 0.5 SECONDS)
/obj/structure/ship_ammo/rocket/keeper
@@ -357,8 +358,9 @@
/obj/structure/ship_ammo/rocket/napalm/detonate_on(turf/impact, obj/structure/dropship_equipment/weapon/fired_from)
impact.ceiling_debris_check(3)
+ var/datum/reagent/fire_reagent = create_fire_reagent(60, 30, "#EE6515")
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(cell_explosion), impact, 200, 25, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data(initial(name), source_mob)), 0.5 SECONDS)
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(fire_spread), impact, create_cause_data(initial(name), source_mob), 6, 60, 30, "#EE6515"), 0.5 SECONDS) //Color changed into napalm's color to better convey how intense the fire actually is.
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(fire_spread), impact, create_cause_data(initial(name), source_mob), 6, fire_reagent), 0.5 SECONDS) //Color changed into napalm's color to better convey how intense the fire actually is.
QDEL_IN(src, 0.5 SECONDS)
/obj/structure/ship_ammo/rocket/thermobaric
@@ -371,7 +373,8 @@
/obj/structure/ship_ammo/rocket/thermobaric/detonate_on(turf/impact, obj/structure/dropship_equipment/weapon/fired_from)
impact.ceiling_debris_check(3)
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(fire_spread), impact, create_cause_data(initial(name), source_mob), 4, 25, 50, "#c96500"), 0.5 SECONDS) //Very intense but the fire doesn't last very long
+ var/datum/reagent/fire_reagent = create_fire_reagent(25, 50, "#c96500")
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(fire_spread), impact, create_cause_data(initial(name), source_mob), 4, fire_reagent), 0.5 SECONDS) //Very intense but the fire doesn't last very long
for(var/mob/living/carbon/victim in orange(5, impact))
victim.throw_atom(impact, 3, 15, src, TRUE) // Implosion throws affected towards center of vacuum
QDEL_IN(src, 0.5 SECONDS)
@@ -425,7 +428,8 @@
/obj/structure/ship_ammo/minirocket/incendiary/detonate_on(turf/impact, obj/structure/dropship_equipment/weapon/fired_from)
..()
spawn(5)
- fire_spread(impact, create_cause_data(initial(name), source_mob), 3, 25, 20, "#EE6515")
+ var/datum/reagent/fire_reagent = create_fire_reagent(25, 20, "#EE6515")
+ fire_spread(impact, create_cause_data(initial(name), source_mob), 3, fire_reagent)
/obj/structure/ship_ammo/sentry
name = "\improper A/C-49-P Air Deployable Sentry"
diff --git a/code/modules/cm_marines/orbital_cannon.dm b/code/modules/cm_marines/orbital_cannon.dm
index c9de051fc7a6..0bd03ff08f60 100644
--- a/code/modules/cm_marines/orbital_cannon.dm
+++ b/code/modules/cm_marines/orbital_cannon.dm
@@ -532,7 +532,8 @@ GLOBAL_LIST_EMPTY(orbital_cannon_cancellation)
handle_ob_shake(target)
sleep(clear_delay)
- fire_spread(target, cause_data, distance, fire_level, burn_level, fire_color, fire_type, TURF_PROTECTION_OB)
+ var/datum/reagent/fire_reagent = create_fire_reagent(fire_level, burn_level, fire_color, fire_type)
+ fire_spread(target, cause_data, distance, fire_reagent, TURF_PROTECTION_OB)
qdel(src)
/obj/structure/ob_ammo/warhead/cluster
diff --git a/code/modules/cm_preds/yaut_items.dm b/code/modules/cm_preds/yaut_items.dm
index 2d25953291cf..820d6105d6b7 100644
--- a/code/modules/cm_preds/yaut_items.dm
+++ b/code/modules/cm_preds/yaut_items.dm
@@ -1598,6 +1598,11 @@ GLOBAL_VAR_INIT(youngblood_timer_yautja, 0)
icon = 'icons/obj/items/hunter/prey_items.dmi'
unacidable = TRUE
+/obj/item/skull/Initialize(mapload, ...)
+ . = ..()
+ if(!icon_state)
+ return INITIALIZE_HINT_QDEL
+
/obj/item/skull/queen
name = "Queen skull"
desc = "Skull of a prime hive ruler, mother to many."
@@ -1699,6 +1704,16 @@ GLOBAL_VAR_INIT(youngblood_timer_yautja, 0)
desc = "Skull of a highly acidic xenomorph, a venomous ranged attacker."
icon_state = "spitter_skull"
+/obj/item/skull/abomination
+ name = "Abomination skull"
+ desc = "Skull of a mysterious hybrid xenomorph, a horror on the field."
+ icon_state = "predalien_skull"
+
+/obj/item/skull/abomination/get_examine_text(mob/user)
+ . = ..()
+ if(isyautja(user))
+ . += SPAN_RED("Not even this relic can be tolerated. Destroy it.")
+
// PELTS
/obj/item/pelt
@@ -1706,6 +1721,11 @@ GLOBAL_VAR_INIT(youngblood_timer_yautja, 0)
icon = 'icons/obj/items/hunter/prey_items.dmi'
unacidable = TRUE
+/obj/item/pelt/Initialize(mapload, ...)
+ . = ..()
+ if(!icon_state)
+ return INITIALIZE_HINT_QDEL
+
/obj/item/pelt/queen
name = "Queen pelt"
desc = "The pelt of a prime hive ruler, mother to many."
@@ -1811,6 +1831,16 @@ GLOBAL_VAR_INIT(youngblood_timer_yautja, 0)
desc = "The hide of a juvenile Xenomorph, a grim trophy from a fledgling that never reached its full potential."
icon_state = "larva_pelt"
+/obj/item/pelt/abomination
+ name = "Abomination pelt"
+ desc = "The pelt of a mysterious hybrid xenomorph, a horror on the field."
+ icon_state = "predalien_pelt"
+
+/obj/item/pelt/abomination/get_examine_text(mob/user)
+ . = ..()
+ if(isyautja(user))
+ . += SPAN_RED("Not even this relic can be tolerated. Destroy it.")
+
/// TOOLS
/obj/item/tool/crowbar/yautja
diff --git a/code/modules/mob/living/carbon/xenomorph/Embryo.dm b/code/modules/mob/living/carbon/xenomorph/Embryo.dm
index f29b97fc8fd9..51be269699d2 100644
--- a/code/modules/mob/living/carbon/xenomorph/Embryo.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Embryo.dm
@@ -300,8 +300,10 @@
notify_ghosts(header = "Burst Imminent", message = "A [new_xeno.hive.prefix]Larva is about to chestburst out of [affected_mob][area_text]!", source = affected_mob)
stage = 7 // Begin the autoburst countdown
+/mob/living/carbon/xenomorph/proc/cause_unbearable_pain(mob/living/carbon/victim)
+ return
-/mob/living/carbon/xenomorph/larva/proc/cause_unbearable_pain(mob/living/carbon/victim)
+/mob/living/carbon/xenomorph/larva/cause_unbearable_pain(mob/living/carbon/victim)
if(loc != victim)
return
victim.emote("scream")
@@ -311,7 +313,10 @@
to_chat(victim, message)
addtimer(CALLBACK(src, PROC_REF(cause_unbearable_pain), victim), rand(1, 3) SECONDS, TIMER_UNIQUE|TIMER_NO_HASH_WAIT)
-/mob/living/carbon/xenomorph/larva/proc/chest_burst(mob/living/carbon/victim)
+/mob/living/carbon/xenomorph/proc/chest_burst(mob/living/carbon/victim)
+ return
+
+/mob/living/carbon/xenomorph/larva/chest_burst(mob/living/carbon/victim)
set waitfor = 0
if(victim.chestburst || loc != victim)
return
diff --git a/code/modules/mob/living/carbon/xenomorph/Evolution.dm b/code/modules/mob/living/carbon/xenomorph/Evolution.dm
index 2b3aa1ea2ebe..83d058a76002 100644
--- a/code/modules/mob/living/carbon/xenomorph/Evolution.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Evolution.dm
@@ -66,6 +66,12 @@ GLOBAL_LIST_EMPTY(deevolved_ckeys)
to_chat(src, SPAN_WARNING("The Hive cannot support this caste yet! ([floor((caste_datum.minimum_evolve_time - ROUND_TIME) / 10)] seconds remaining)"))
return
+ if(hive.restricted_castes && (castepick in hive.restricted_castes))
+ var/max_num = hive.restricted_castes[castepick]
+ if(hive.get_caste_count(castepick) >= max_num)
+ to_chat(src, SPAN_WARNING("The Hive has reached capacity for this caste!"))
+ return
+
if(!evolve_checks())
return
@@ -279,7 +285,7 @@ GLOBAL_LIST_EMPTY(deevolved_ckeys)
to_chat(src, SPAN_WARNING("We must be at full health to evolve."))
return FALSE
- if(agility || fortify || crest_defense || stealth)
+ if(agility || fortify || crest_defense || stealth || HAS_TRAIT(src, TRAIT_ABILITY_ENCLOSED_PLATES) || HAS_TRAIT(src, TRAIT_ABILITY_REFLECTIVE_PLATES))
to_chat(src, SPAN_WARNING("We cannot evolve while in this stance."))
return FALSE
@@ -518,10 +524,10 @@ GLOBAL_LIST_EMPTY(deevolved_ckeys)
if(xeno.counts_for_slots)
totalXenos++
- if(tier == 1 && (((used_tier_2_slots + used_tier_3_slots) / totalXenos) * hive.tier_slot_multiplier) >= 0.5 && castepick != XENO_CASTE_QUEEN)
+ if(tier == 1 && (((used_tier_2_slots + used_tier_3_slots) / totalXenos) * hive.tier_slot_divisor) >= 0.5 && castepick != XENO_CASTE_QUEEN)
to_chat(src, SPAN_WARNING("The hive cannot support another Tier 2, wait for either more aliens to be born or someone to die."))
return FALSE
- else if(tier == 2 && ((used_tier_3_slots / totalXenos) * hive.tier_slot_multiplier) >= 0.20 && castepick != XENO_CASTE_QUEEN)
+ else if(tier == 2 && ((used_tier_3_slots / totalXenos) * hive.tier_slot_divisor) >= 0.20 && castepick != XENO_CASTE_QUEEN)
to_chat(src, SPAN_WARNING("The hive cannot support another Tier 3, wait for either more aliens to be born or someone to die."))
return FALSE
diff --git a/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm b/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm
index cf25fadeef89..816ba1fb8c7f 100644
--- a/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm
+++ b/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm
@@ -109,7 +109,7 @@
//Hot hot Aliens on Aliens action.
//Actually just used for eating people.
/mob/living/carbon/xenomorph/attack_alien(mob/living/carbon/xenomorph/xeno)
- if (xeno.fortify || HAS_TRAIT(xeno, TRAIT_ABILITY_BURROWED))
+ if(xeno.fortify || HAS_TRAIT(xeno, TRAIT_ABILITY_BURROWED) || HAS_TRAIT(xeno, TRAIT_ABILITY_REFLECTIVE_PLATES))
return XENO_NO_DELAY_ACTION
if(HAS_TRAIT(src, TRAIT_ABILITY_BURROWED))
diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm
index 7896d44e0e7d..a8d19062ff71 100644
--- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm
+++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm
@@ -782,6 +782,10 @@
to_chat(src, SPAN_XENOBOLDNOTICE("There are no weeds here! Nesting hosts requires hive weeds."))
return
+ if(supplier_weeds.hivenumber != hivenumber)
+ to_chat(src, SPAN_XENOBOLDNOTICE("The weeds here do not belong to us!"))
+ return
+
if(supplier_weeds.weed_strength < WEED_LEVEL_HIVE)
to_chat(src, SPAN_XENOBOLDNOTICE("The weeds here are not strong enough for nesting hosts."))
return
@@ -807,7 +811,7 @@
to_chat(src, SPAN_XENONOTICE("There is already a host nested here!"))
return
- var/obj/structure/bed/nest/applicable_nest = new(get_turf(host_to_nest))
+ var/obj/structure/bed/nest/applicable_nest = new(get_turf(host_to_nest), hivenumber)
applicable_nest.dir = dir_to_nest
if(!applicable_nest.buckle_mob(host_to_nest, src))
qdel(applicable_nest)
diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
index 0f330a9bee1d..f04a028b62cf 100644
--- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
@@ -136,6 +136,8 @@
var/armor_integrity_last_damage_time = 0
var/armor_integrity_immunity_time = 0
+ var/melee_vulnerability_mult = 0
+
var/pull_multiplier = 1
var/aura_strength = 0 // Pheromone strength
var/weed_level = WEED_LEVEL_STANDARD
@@ -211,6 +213,8 @@
var/plasmapool_modifier = 1
var/plasmagain_modifier = 0
var/tackle_chance_modifier = 0
+ var/tackle_min_modifier = 0
+ var/tackle_max_modifier = 0
var/regeneration_multiplier = 1
var/speed_modifier = 0
var/phero_modifier = 0
@@ -221,6 +225,9 @@
var/attack_speed_modifier = 0
var/armor_integrity_modifier = 0
+ ///Used to add plasma to strain if caste have 0 plasma_max
+ var/add_plasma = 0
+
var/list/modifier_sources
COOLDOWN_DECLARE(next_strain_reset)
@@ -248,6 +255,7 @@
/// Caste-based spit windup
var/spit_windup = FALSE
/// Caste-based spit windup duration (if applicable)
+ var/spit_delay = 0
var/tileoffset = 0 // How much your view will be offset in the direction that you zoom?
var/viewsize = 0 //What size your view will be changed to when you zoom?
var/banished = FALSE // Banished xenos can be attacked by all other xenos
@@ -318,13 +326,16 @@
var/obj/effect/alien/resin/fruit/selected_fruit = null
var/list/built_structures = list()
- // Designer stuff
+ /// Designer related
var/obj/effect/alien/resin/design/selected_design = null
var/list/available_design = list()
var/list/current_design = list()
var/max_design_nodes = 0
var/selected_design_mark
+ var/front_armor
+ var/side_armor
+
var/icon_xeno
var/icon_xenonid
var/xenonid_pixel_x
@@ -607,6 +618,9 @@
if(fire_immunity & FIRE_IMMUNITY_XENO_FRENZY)
. |= COMPONENT_XENO_FRENZY
+/mob/living/carbon/xenomorph/proc/get_reflection_chance(obj/projectile/bullet)
+ return
+
//Off-load this proc so it can be called freely
//Since Xenos change names like they change shoes, we need somewhere to hammer in all those legos
//We set their name first, then update their real_name AND their mind name
@@ -939,8 +953,8 @@
recalculate_tackle()
/mob/living/carbon/xenomorph/proc/recalculate_tackle()
- tackle_min = caste.tackle_min
- tackle_max = caste.tackle_max
+ tackle_min = caste.tackle_min + tackle_min_modifier
+ tackle_max = caste.tackle_max + tackle_max_modifier
tackle_chance = caste.tackle_chance + tackle_chance_modifier
tacklestrength_min = caste.tacklestrength_min
tacklestrength_max = caste.tacklestrength_max
@@ -961,19 +975,23 @@
health = maxHealth
/mob/living/carbon/xenomorph/proc/recalculate_plasma()
- if(!plasma_max)
+ var/new_plasma_max = (plasmapool_modifier * caste.plasma_max) + add_plasma
+ if(!plasma_max && new_plasma_max <= 0)
return
- var/new_plasma_max = plasmapool_modifier * caste.plasma_max
plasma_gain = plasmagain_modifier + caste.plasma_gain
if(hive)
new_plasma_max += hive.hive_stat_modifier_flat["plasmapool"]
new_plasma_max *= hive.hive_stat_modifier_multiplier["plasmapool"]
plasma_gain += hive.hive_stat_modifier_flat["plasmagain"]
plasma_gain *= hive.hive_stat_modifier_multiplier["plasmagain"]
- if (new_plasma_max == plasma_max)
+ if(new_plasma_max == plasma_max)
return
- var/plasma_ratio = plasma_stored / plasma_max
+
+ var/plasma_ratio = 0
+ if(plasma_max > 0)
+ plasma_ratio = plasma_stored / plasma_max
+
plasma_max = new_plasma_max
plasma_stored = floor(plasma_max * plasma_ratio + 0.5) //Restore our plasma ratio, so if we're full, we continue to be full, etc. Rounding up (hence the +0.5)
if(plasma_stored > plasma_max)
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm
index 9c22bf65e5c8..04fa1f7fe9b9 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm
@@ -570,3 +570,289 @@
seethroughComp.toggle_active()
apply_cooldown()
+
+
+
+/mob/living/carbon/xenomorph/proc/set_orders()
+ set category = "Alien.Hivemind-Control"
+ set name = "Set Hive Orders (50)"
+ set desc = "Give some specific orders to the hive. They can see this on the status pane."
+
+ if(!check_state())
+ return
+ if(last_special > world.time)
+ return
+ if(!check_plasma(50))
+ return
+ use_plasma(50)
+
+ var/txt = strip_html(input("Set the hive's orders to what? Leave blank to clear it.", "Hive Orders",""))
+ if(txt)
+ xeno_message("The Queen's will overwhelms your instincts...", 3, hivenumber)
+ xeno_message("\""+txt+"\"", 3, hivenumber)
+ xeno_maptext(txt, "Hive Orders Updated", hivenumber)
+ hive.hive_orders = txt
+ log_hiveorder("[key_name(usr)] has set the Hive Order to: [txt]")
+ else
+ hive.hive_orders = ""
+
+ last_special = world.time + 15 SECONDS
+
+/mob/living/carbon/xenomorph/proc/hive_message()
+ set category = "Alien.Hivemind"
+ set name = "Word of the Queen (50)"
+ set desc = "Send a message to all aliens in the hive that is big and visible."
+ if(client.prefs.muted & MUTE_IC)
+ to_chat(src, SPAN_DANGER("You cannot send Announcements (muted)."))
+ return
+ if(health <= 0)
+ to_chat(src, SPAN_WARNING("You can't do that while unconscious."))
+ return FALSE
+ if(!check_plasma(50))
+ return FALSE
+
+ // Get a reference to the ability to utilize cooldowns
+ var/datum/action/xeno_action/onclick/queen_word/word_ability
+ for(var/datum/action/xeno_action/action in actions)
+ if(istype(action, /datum/action/xeno_action/onclick/queen_word))
+ word_ability = action
+ if(!word_ability.action_cooldown_check())
+ return FALSE
+ break
+
+ var/input = stripped_multiline_input(src, "This message will be broadcast throughout the hive.", "Word of the Queen", "")
+ if(!input)
+ return FALSE
+
+ use_plasma(50)
+ if(word_ability)
+ word_ability.apply_cooldown()
+
+ xeno_announcement(input, hivenumber, "The words of the [name] reverberate in our head...")
+
+ message_admins("[key_name_admin(src)] has created a Word of the Queen report:")
+ log_admin("[key_name_admin(src)] Word of the Queen: [input]")
+ return TRUE
+
+/mob/living/carbon/xenomorph/proc/claw_toggle()
+ set name = "Permit/Disallow Harming"
+ set desc = "Allows you to permit the hive to harm/slash."
+ set category = "Alien.Hivemind-Control"
+
+ if(stat)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ return
+
+ if(!hive)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ CRASH("[src] attempted to toggle slashing without a linked hive")
+
+ if(hive.hive_flags_locked)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ return
+
+ if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_TOGGLE_SLASH))
+ to_chat(src, SPAN_WARNING("You must wait a bit before you can toggle this again."))
+ return
+
+ var/current_setting = null
+ if(CHECK_MULTIPLE_BITFIELDS(hive.hive_flags, XENO_SLASH_ALLOW_ALL))
+ current_setting = "Allowed"
+ else if(!(hive.hive_flags & XENO_SLASH_INFECTED) && (hive.hive_flags & XENO_SLASH_NORMAL))
+ current_setting = "Restricted - Infected Hosts"
+ else if(!(hive.hive_flags & XENO_SLASH_ALLOW_ALL))
+ current_setting = "Forbidden"
+
+ var/choice = tgui_input_list(src, "Choose which level of harming hosts to permit to your hive.", "Harming", list("Forbidden", "Restricted - Infected Hosts", "Allowed"), theme="hive_status", default=current_setting)
+ if(!choice)
+ return
+
+ if(choice == "Allowed")
+ if(current_setting == choice)
+ to_chat(src, SPAN_XENOWARNING("You already allow harming."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You allow harming."))
+ xeno_message(SPAN_XENOANNOUNCE("The Queen has permitted the harming of hosts! Go hog wild!"), hivenumber=hivenumber)
+ hive.hive_flags |= XENO_SLASH_ALLOW_ALL
+ else if(choice == "Restricted - Infected Hosts")
+ if(current_setting == choice)
+ to_chat(src, SPAN_XENOWARNING("You already forbid harming of infected hosts."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You forbid harming of infected hosts."))
+ xeno_message(SPAN_XENOANNOUNCE("The Queen has restricted the harming of hosts. You can no longer slash infected hosts."), hivenumber=hivenumber)
+ hive.hive_flags &= ~XENO_SLASH_INFECTED
+ hive.hive_flags |= XENO_SLASH_NORMAL
+ else if(choice == "Forbidden")
+ if(current_setting == choice)
+ to_chat(src, SPAN_XENOWARNING("You already forbid harming entirely."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You forbid harming entirely."))
+ xeno_message(SPAN_XENOANNOUNCE("The Queen has forbidden the harming of hosts. You can no longer slash your enemies."), hivenumber=hivenumber)
+ hive.hive_flags &= ~XENO_SLASH_ALLOW_ALL
+
+ TIMER_COOLDOWN_START(src, COOLDOWN_TOGGLE_SLASH, 30 SECONDS)
+
+/mob/living/carbon/xenomorph/proc/construction_toggle()
+ set name = "Permit/Disallow Construction Placement"
+ set desc = "Allows you to permit the hive to place construction nodes freely."
+ set category = "Alien.Hivemind-Control"
+
+ if(stat)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ return
+
+ if(!hive)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ CRASH("[src] attempted to toggle construction without a linked hive")
+
+ if(hive.hive_flags_locked)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ return
+
+ if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_TOGGLE_CONSTRUCTION))
+ to_chat(src, SPAN_WARNING("You must wait a bit before you can toggle this again."))
+ return
+
+ var/current_setting = null
+ if(CHECK_MULTIPLE_BITFIELDS(hive.hive_flags, XENO_CONSTRUCTION_ALLOW_ALL))
+ current_setting = "Anyone"
+ else if(!(hive.hive_flags & XENO_CONSTRUCTION_NORMAL) && CHECK_MULTIPLE_BITFIELDS(hive.hive_flags, XENO_CONSTRUCTION_QUEEN|XENO_CONSTRUCTION_LEADERS))
+ current_setting = "Leaders"
+ else if(!(hive.hive_flags & (XENO_CONSTRUCTION_LEADERS|XENO_CONSTRUCTION_NORMAL)) && (hive.hive_flags & XENO_CONSTRUCTION_QUEEN))
+ current_setting = "Queen"
+
+ var/choice = tgui_input_list(src, "Choose which level of construction placement freedom to permit to your hive.", "Construction", list("Queen", "Leaders", "Anyone"), theme="hive_status", default=current_setting)
+ if(!choice)
+ return
+
+ if(choice == "Anyone")
+ if(current_setting == choice)
+ to_chat(src, SPAN_XENOWARNING("You already allow construction placement to all builder castes."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You allow construction placement to all builder castes."))
+ xeno_message("The Queen has permitted the placement of construction nodes to all builder castes!", hivenumber=hivenumber)
+ hive.hive_flags |= XENO_CONSTRUCTION_ALLOW_ALL
+ else if(choice == "Leaders")
+ if(current_setting == choice)
+ to_chat(src, SPAN_XENOWARNING("You already restrict construction placement to leaders only."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You restrict construction placement to leaders only."))
+ xeno_message("The Queen has restricted the placement of construction nodes to leading builder castes only.", hivenumber=hivenumber)
+ hive.hive_flags &= ~XENO_CONSTRUCTION_NORMAL
+ hive.hive_flags |= XENO_CONSTRUCTION_QUEEN|XENO_CONSTRUCTION_LEADERS
+ else if(choice == "Queen")
+ if(current_setting == choice)
+ to_chat(src, SPAN_XENOWARNING("You already forbid construction placement entirely."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You forbid construction placement entirely."))
+ xeno_message("The Queen has forbidden the placement of construction nodes to all but herself.", hivenumber=hivenumber)
+ hive.hive_flags &= ~(XENO_CONSTRUCTION_LEADERS|XENO_CONSTRUCTION_NORMAL)
+ hive.hive_flags |= XENO_CONSTRUCTION_QUEEN
+
+ TIMER_COOLDOWN_START(src, COOLDOWN_TOGGLE_CONSTRUCTION, 30 SECONDS)
+
+/mob/living/carbon/xenomorph/proc/destruction_toggle()
+ set name = "Permit/Disallow Special Structure Destruction"
+ set desc = "Allows you to permit the hive to destroy special structures freely."
+ set category = "Alien.Hivemind-Control"
+
+ if(stat)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ return
+
+ if(!hive)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ CRASH("[src] attempted to toggle deconstruction without a linked hive")
+
+ if(hive.hive_flags_locked)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ return
+
+ if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_TOGGLE_DECONSTRUCTION))
+ to_chat(src, SPAN_WARNING("You must wait a bit before you can toggle this again."))
+ return
+
+ var/current_setting = null
+ if(CHECK_MULTIPLE_BITFIELDS(hive.hive_flags, XENO_DECONSTRUCTION_ALLOW_ALL))
+ current_setting = "Anyone"
+ else if(!(hive.hive_flags & XENO_DECONSTRUCTION_NORMAL) && CHECK_MULTIPLE_BITFIELDS(hive.hive_flags, XENO_DECONSTRUCTION_QUEEN|XENO_DECONSTRUCTION_LEADERS))
+ current_setting = "Leaders"
+ else if(!(hive.hive_flags & (XENO_DECONSTRUCTION_LEADERS|XENO_DECONSTRUCTION_NORMAL)) && (hive.hive_flags & XENO_DECONSTRUCTION_QUEEN))
+ current_setting = "Queen"
+
+ var/choice = tgui_input_list(src, "Choose which level of destruction freedom to permit to your hive.", "Deconstruction", list("Queen", "Leaders", "Anyone"), theme="hive_status", default=current_setting)
+ if(!choice)
+ return
+
+ if(choice == "Anyone")
+ if(current_setting == choice)
+ to_chat(src, SPAN_XENOWARNING("You already allow special structure destruction to all builder castes and leaders."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You allow special structure destruction to all builder castes and leaders."))
+ xeno_message("The Queen has permitted the destruction of special structures to all builder castes and leaders!", hivenumber=hivenumber)
+ hive.hive_flags |= XENO_DECONSTRUCTION_ALLOW_ALL
+ else if(choice == "Leaders")
+ if(current_setting == choice)
+ to_chat(src, SPAN_XENOWARNING("You already restrict special structure destruction to leaders only."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You restrict special structure destruction to leaders only."))
+ xeno_message("The Queen has restricted the destruction of special structures to leaders only.", hivenumber=hivenumber)
+ hive.hive_flags &= ~XENO_DECONSTRUCTION_NORMAL
+ hive.hive_flags |= XENO_DECONSTRUCTION_QUEEN|XENO_DECONSTRUCTION_LEADERS
+ else if(choice == "Queen")
+ if(current_setting == choice)
+ to_chat(src, SPAN_XENOWARNING("You already forbid special structure destruction entirely."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You forbid special structure destruction entirely."))
+ xeno_message("The Queen has forbidden the destruction of special structures to all but herself.", hivenumber=hivenumber)
+ hive.hive_flags &= ~(XENO_DECONSTRUCTION_LEADERS|XENO_DECONSTRUCTION_NORMAL)
+ hive.hive_flags |= XENO_DECONSTRUCTION_QUEEN
+
+ TIMER_COOLDOWN_START(src, COOLDOWN_TOGGLE_DECONSTRUCTION, 30 SECONDS)
+
+/mob/living/carbon/xenomorph/proc/unnesting_toggle()
+ set name = "Permit/Disallow Unnesting"
+ set desc = "Allows you to restrict unnesting to drones."
+ set category = "Alien.Hivemind-Control"
+
+ if(stat)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+
+ if(!hive)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ CRASH("[src] attempted to toggle unnesting without a linked hive")
+
+ if(hive.hive_flags_locked)
+ to_chat(src, SPAN_WARNING("You can't do that now."))
+ return
+
+ if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_TOGGLE_UNNESTING))
+ to_chat(src, SPAN_WARNING("You must wait a bit before you can toggle this again."))
+ return
+
+ var/current_setting = null
+ if(!(hive.hive_flags & XENO_UNNESTING_RESTRICTED))
+ current_setting = "Anyone"
+ else if(hive.hive_flags & XENO_UNNESTING_RESTRICTED)
+ current_setting = "Drone castes"
+
+ var/choice = tgui_input_list(src, "Choose which level of unnesting freedom to permit to your hive.", "Unnesting", list("Drone castes", "Anyone"), theme="hive_status", default=current_setting)
+ if(!choice)
+ return
+
+ if(choice == "Anyone")
+ if(!(hive.hive_flags & XENO_UNNESTING_RESTRICTED))
+ to_chat(src, SPAN_XENOWARNING("You have already allowed everyone to unnest hosts."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You have allowed everyone to unnest hosts."))
+ xeno_message("The Queen has allowed everyone to unnest hosts.", hivenumber=hivenumber)
+ hive.hive_flags &= ~XENO_UNNESTING_RESTRICTED
+ else
+ if(hive.hive_flags & XENO_UNNESTING_RESTRICTED)
+ to_chat(src, SPAN_XENOWARNING("You have already forbidden anyone to unnest hosts, except for the drone caste."))
+ return
+ to_chat(src, SPAN_XENONOTICE("You have forbidden anyone to unnest hosts, except for the drone caste."))
+ xeno_message("The Queen has forbidden anyone to unnest hosts, except for the drone caste.", hivenumber=hivenumber)
+ hive.hive_flags |= XENO_UNNESTING_RESTRICTED
+
+ TIMER_COOLDOWN_START(src, COOLDOWN_TOGGLE_UNNESTING, 30 SECONDS)
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm
index b570e4f068db..8bbbdd50c027 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm
@@ -110,6 +110,10 @@
to_chat(src, SPAN_WARNING("We cannot rest while our crest is down!"))
return
+ if(HAS_TRAIT(src, TRAIT_ABILITY_ENCLOSED_PLATES))
+ to_chat(src, SPAN_WARNING("We cannot rest when we are encased in plates!"))
+ return
+
return ..()
/mob/living/carbon/xenomorph/set_lying_down()
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_abilities.dm
index 2e175bbc2482..458cb3d3489a 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_abilities.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_abilities.dm
@@ -40,3 +40,56 @@
var/base_punch_damage_synth = 30
var/base_punch_damage_pred = 25
var/damage_variance = 5
+
+///Bulwark Strain
+
+/datum/action/xeno_action/onclick/toggle_plates
+ name = "Toggle Encasing Plates"
+ action_icon_state = "encased_plates"
+ macro_path = /datum/action/xeno_action/verb/verb_toggle_plates
+ action_type = XENO_ACTION_ACTIVATE
+ xeno_cooldown = 1 SECONDS
+ ability_primacy = XENO_PRIMARY_ACTION_1
+
+ var/speed_debuff = 1.35
+
+/datum/action/xeno_action/activable/plate_bash
+ name = "Plate Bash"
+ action_icon_state = "plate_bash"
+ macro_path = /datum/action/xeno_action/verb/verb_plate_bash
+ action_type = XENO_ACTION_CLICK
+ ability_primacy = XENO_PRIMARY_ACTION_2
+ xeno_cooldown = 4 SECONDS
+
+ var/base_damage = 20
+
+/datum/action/xeno_action/onclick/tail_swing
+ name = "Tail Swing"
+ action_icon_state = "tail_swing"
+ macro_path = /datum/action/xeno_action/verb/verb_tail_swing
+ action_type = XENO_ACTION_ACTIVATE
+ ability_primacy = XENO_PRIMARY_ACTION_3
+ plasma_cost = 20
+ xeno_cooldown = 10 SECONDS
+
+ var/swing_range = 1
+
+/datum/action/xeno_action/onclick/reflective_shield
+ name = "Reflective Shield"
+ action_icon_state = "reflective_shield"
+ macro_path = /datum/action/xeno_action/verb/verb_reflective_shield
+ action_type = XENO_ACTION_CLICK
+ ability_primacy = XENO_PRIMARY_ACTION_4
+
+ /// used to calculate reflective plates refunding.
+ var/duration = BULWARK_REFLECTIVE_TIME
+ /// reflective plates addtimer ID (for deletion)
+ var/reflective_shield_timer_id = TIMER_ID_NULL
+ /// Used to countdown BULWARK_REFLECTIVE_TIME.
+ var/reflective_start_time = -1
+ /// How much refund we want to get back? 1 is 1s used to 1s cooldown, 2 is 1s used 2s cooldown.
+ var/reflective_refund_multiplier = 2
+ /// Used in calculation, finalized number will be displayed as cooldown.
+ var/reflective_recharge_time = null
+ /// Cooldown after activation to prevent accidental double click.
+ var/reflective_safe_click_cooldown = 0
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_macros.dm b/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_macros.dm
index 63d97bb69aa2..f6bd989d7c46 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_macros.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/warrior/warrior_macros.dm
@@ -19,3 +19,31 @@
set hidden = TRUE
var/action_name = "Punch"
handle_xeno_macro(src, action_name)
+
+/datum/action/xeno_action/verb/verb_toggle_plates()
+ set category = "Alien"
+ set name = "Toggle Encasing Plates"
+ set hidden = TRUE
+ var/action_name = "Toggle Plates Defense"
+ handle_xeno_macro(src, action_name)
+
+/datum/action/xeno_action/verb/verb_plate_bash()
+ set category = "Alien"
+ set name = "Plate Bash"
+ set hidden = TRUE
+ var/action_name = "Plate Bash"
+ handle_xeno_macro(src, action_name)
+
+/datum/action/xeno_action/verb/verb_tail_swing()
+ set category = "Alien"
+ set name = "Tail Swing"
+ set hidden = TRUE
+ var/action_name = "Tail Swing"
+ handle_xeno_macro(src, action_name)
+
+/datum/action/xeno_action/verb/verb_reflective_shield()
+ set category = "Alien"
+ set name = "Reflective Shield"
+ set hidden = TRUE
+ var/action_name = "Reflective Plates"
+ handle_xeno_macro(src, action_name)
diff --git a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm
index 9af59c6201dc..41e43e8cc72f 100644
--- a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm
+++ b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm
@@ -9,7 +9,7 @@
// this proc could use refactoring at some point
/mob/living/carbon/human/attack_alien(mob/living/carbon/xenomorph/attacking_xeno, dam_bonus, unblockable = FALSE)
- if(attacking_xeno.fortify || HAS_TRAIT(attacking_xeno, TRAIT_ABILITY_BURROWED))
+ if(attacking_xeno.fortify || HAS_TRAIT(attacking_xeno, TRAIT_ABILITY_BURROWED) || HAS_TRAIT(attacking_xeno, TRAIT_ABILITY_REFLECTIVE_PLATES))
return XENO_NO_DELAY_ACTION
if(HAS_TRAIT(src, TRAIT_HAULED))
@@ -403,7 +403,10 @@
if(is_wired)
xeno.visible_message(SPAN_DANGER("The barbed wire slices into [xeno]!"),
SPAN_DANGER("The barbed wire slices into us!"), null, 5, CHAT_TYPE_XENO_COMBAT)
- xeno.apply_damage(10, enviro=TRUE)
+ if(istype(xeno.strain, /datum/xeno_strain/bulwark))
+ xeno.apply_damage(5, enviro=TRUE)
+ else
+ xeno.apply_damage(10, enviro=TRUE)
return XENO_ATTACK_ACTION
/obj/structure/barricade/handle_tail_stab(mob/living/carbon/xenomorph/xeno, blunt_stab)
@@ -1141,8 +1144,7 @@
to_chat(xeno, SPAN_WARNING("It's already damaged."))
return XENO_NO_DELAY_ACTION
xeno.animation_attack_on(src)
- xeno.visible_message(SPAN_DANGER("[xeno] slashes away at [src]!"),
- SPAN_DANGER("We slash and claw at the bright light!"), max_distance = 5, message_flags = CHAT_TYPE_XENO_COMBAT)
+ xeno.visible_message("[xeno] slashes away at [src]!","We slash and claw at the bright light!", max_distance = 5, message_flags = CHAT_TYPE_XENO_COMBAT)
health = max(health - rand(xeno.melee_damage_lower, xeno.melee_damage_upper), 0)
if(!health)
set_damaged()
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm b/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm
index 1a1a63a4bb3e..8a3ad059710f 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm
@@ -67,6 +67,9 @@
/datum/action/xeno_action/onclick/toggle_gut_targeting,
)
+ skull = /obj/item/skull/abomination
+ pelt = /obj/item/pelt/abomination
+
weed_food_icon = 'icons/mob/xenos/weeds_64x64.dmi'
weed_food_states = list("Predalien_1","Predalien_2","Predalien_3")
weed_food_states_flipped = list("Predalien_1","Predalien_2","Predalien_3")
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm
index af3bf4cd6ca8..d0d141e3125a 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm
@@ -335,8 +335,8 @@
/mob/living/carbon/xenomorph/proc/construction_toggle,
/mob/living/carbon/xenomorph/proc/destruction_toggle,
/mob/living/carbon/xenomorph/proc/unnesting_toggle,
- /mob/living/carbon/xenomorph/queen/proc/set_orders,
- /mob/living/carbon/xenomorph/queen/proc/hive_message,
+ /mob/living/carbon/xenomorph/proc/set_orders,
+ /mob/living/carbon/xenomorph/proc/hive_message,
/mob/living/carbon/xenomorph/proc/rename_tunnel,
/mob/living/carbon/xenomorph/proc/set_hugger_reserve_for_morpher,
)
@@ -493,13 +493,13 @@
if(XENO_NORMAL)
name = "[name_prefix]Queen" //Regular
if(XENO_MATURE)
- name = "[name_prefix]Elder Queen" //Mature
+ name = "[name_prefix]Empress" //Mature
if(XENO_ELDER)
name = "[name_prefix]Elder Empress" //Elite
if(XENO_ANCIENT)
name = "[name_prefix]Ancient Empress" //Ancient
if(XENO_PRIME)
- name = "[name_prefix]Prime Empress" //Primordial
+ name = "[name_prefix]Prime Empress" //Prime
else
age = XENO_NORMAL
if(client)
@@ -674,290 +674,6 @@
if(queen_age_temp_timer_id != TIMER_ID_NULL)
. += "Temporary Maturity: [time2text(timeleft(queen_age_temp_timer_id), "mm:ss")] remaining"
-/mob/living/carbon/xenomorph/queen/proc/set_orders()
- set category = "Alien.Hivemind-Control"
- set name = "Set Hive Orders (50)"
- set desc = "Give some specific orders to the hive. They can see this on the status pane."
-
- if(!check_state())
- return
- if(last_special > world.time)
- return
- if(!check_plasma(50))
- return
- use_plasma(50)
-
- var/txt = strip_html(input("Set the hive's orders to what? Leave blank to clear it.", "Hive Orders",""))
- if(txt)
- xeno_message("The Queen's will overwhelms your instincts...", 3, hivenumber)
- xeno_message("\""+txt+"\"", 3, hivenumber)
- xeno_maptext(txt, "Hive Orders Updated", hivenumber)
- hive.hive_orders = txt
- log_hiveorder("[key_name(usr)] has set the Hive Order to: [txt]")
- else
- hive.hive_orders = ""
-
- last_special = world.time + 15 SECONDS
-
-/mob/living/carbon/xenomorph/queen/proc/hive_message()
- set category = "Alien.Hivemind"
- set name = "Word of the Queen (50)"
- set desc = "Send a message to all aliens in the hive that is big and visible."
- if(client.prefs.muted & MUTE_IC)
- to_chat(src, SPAN_DANGER("You cannot send Announcements (muted)."))
- return
- if(health <= 0)
- to_chat(src, SPAN_WARNING("You can't do that while unconscious."))
- return FALSE
- if(!check_plasma(50))
- return FALSE
-
- // Get a reference to the ability to utilize cooldowns
- var/datum/action/xeno_action/onclick/queen_word/word_ability
- for(var/datum/action/xeno_action/action in actions)
- if(istype(action, /datum/action/xeno_action/onclick/queen_word))
- word_ability = action
- if(!word_ability.action_cooldown_check())
- return FALSE
- break
-
- var/input = stripped_multiline_input(src, "This message will be broadcast throughout the hive.", "Word of the Queen", "")
- if(!input)
- return FALSE
-
- use_plasma(50)
- if(word_ability)
- word_ability.apply_cooldown()
-
- xeno_announcement(input, hivenumber, "The words of the [name] reverberate in our head...")
-
- message_admins("[key_name_admin(src)] has created a Word of the Queen report:")
- log_admin("[key_name_admin(src)] Word of the Queen: [input]")
- return TRUE
-
-/mob/living/carbon/xenomorph/proc/claw_toggle()
- set name = "Permit/Disallow Harming"
- set desc = "Allows you to permit the hive to harm/slash."
- set category = "Alien.Hivemind-Control"
-
- if(stat)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- return
-
- if(!hive)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- CRASH("[src] attempted to toggle slashing without a linked hive")
-
- if(hive.hive_flags_locked)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- return
-
- if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_TOGGLE_SLASH))
- to_chat(src, SPAN_WARNING("You must wait a bit before you can toggle this again."))
- return
-
- var/current_setting = null
- if(CHECK_MULTIPLE_BITFIELDS(hive.hive_flags, XENO_SLASH_ALLOW_ALL))
- current_setting = "Allowed"
- else if(!(hive.hive_flags & XENO_SLASH_INFECTED) && (hive.hive_flags & XENO_SLASH_NORMAL))
- current_setting = "Restricted - Infected Hosts"
- else if(!(hive.hive_flags & XENO_SLASH_ALLOW_ALL))
- current_setting = "Forbidden"
-
- var/choice = tgui_input_list(src, "Choose which level of harming hosts to permit to your hive.", "Harming", list("Forbidden", "Restricted - Infected Hosts", "Allowed"), theme="hive_status", default=current_setting)
- if(!choice)
- return
-
- if(choice == "Allowed")
- if(current_setting == choice)
- to_chat(src, SPAN_XENOWARNING("You already allow harming."))
- return
- to_chat(src, SPAN_XENONOTICE("You allow harming."))
- xeno_message(SPAN_XENOANNOUNCE("The Queen has permitted the harming of hosts! Go hog wild!"), hivenumber=hivenumber)
- hive.hive_flags |= XENO_SLASH_ALLOW_ALL
- else if(choice == "Restricted - Infected Hosts")
- if(current_setting == choice)
- to_chat(src, SPAN_XENOWARNING("You already forbid harming of infected hosts."))
- return
- to_chat(src, SPAN_XENONOTICE("You forbid harming of infected hosts."))
- xeno_message(SPAN_XENOANNOUNCE("The Queen has restricted the harming of hosts. You can no longer slash infected hosts."), hivenumber=hivenumber)
- hive.hive_flags &= ~XENO_SLASH_INFECTED
- hive.hive_flags |= XENO_SLASH_NORMAL
- else if(choice == "Forbidden")
- if(current_setting == choice)
- to_chat(src, SPAN_XENOWARNING("You already forbid harming entirely."))
- return
- to_chat(src, SPAN_XENONOTICE("You forbid harming entirely."))
- xeno_message(SPAN_XENOANNOUNCE("The Queen has forbidden the harming of hosts. You can no longer slash your enemies."), hivenumber=hivenumber)
- hive.hive_flags &= ~XENO_SLASH_ALLOW_ALL
-
- TIMER_COOLDOWN_START(src, COOLDOWN_TOGGLE_SLASH, 30 SECONDS)
-
-/mob/living/carbon/xenomorph/proc/construction_toggle()
- set name = "Permit/Disallow Construction Placement"
- set desc = "Allows you to permit the hive to place construction nodes freely."
- set category = "Alien.Hivemind-Control"
-
- if(stat)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- return
-
- if(!hive)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- CRASH("[src] attempted to toggle construction without a linked hive")
-
- if(hive.hive_flags_locked)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- return
-
- if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_TOGGLE_CONSTRUCTION))
- to_chat(src, SPAN_WARNING("You must wait a bit before you can toggle this again."))
- return
-
- var/current_setting = null
- if(CHECK_MULTIPLE_BITFIELDS(hive.hive_flags, XENO_CONSTRUCTION_ALLOW_ALL))
- current_setting = "Anyone"
- else if(!(hive.hive_flags & XENO_CONSTRUCTION_NORMAL) && CHECK_MULTIPLE_BITFIELDS(hive.hive_flags, XENO_CONSTRUCTION_QUEEN|XENO_CONSTRUCTION_LEADERS))
- current_setting = "Leaders"
- else if(!(hive.hive_flags & (XENO_CONSTRUCTION_LEADERS|XENO_CONSTRUCTION_NORMAL)) && (hive.hive_flags & XENO_CONSTRUCTION_QUEEN))
- current_setting = "Queen"
-
- var/choice = tgui_input_list(src, "Choose which level of construction placement freedom to permit to your hive.", "Construction", list("Queen", "Leaders", "Anyone"), theme="hive_status", default=current_setting)
- if(!choice)
- return
-
- if(choice == "Anyone")
- if(current_setting == choice)
- to_chat(src, SPAN_XENOWARNING("You already allow construction placement to all builder castes."))
- return
- to_chat(src, SPAN_XENONOTICE("You allow construction placement to all builder castes."))
- xeno_message("The Queen has permitted the placement of construction nodes to all builder castes!", hivenumber=hivenumber)
- hive.hive_flags |= XENO_CONSTRUCTION_ALLOW_ALL
- else if(choice == "Leaders")
- if(current_setting == choice)
- to_chat(src, SPAN_XENOWARNING("You already restrict construction placement to leaders only."))
- return
- to_chat(src, SPAN_XENONOTICE("You restrict construction placement to leaders only."))
- xeno_message("The Queen has restricted the placement of construction nodes to leading builder castes only.", hivenumber=hivenumber)
- hive.hive_flags &= ~XENO_CONSTRUCTION_NORMAL
- hive.hive_flags |= XENO_CONSTRUCTION_QUEEN|XENO_CONSTRUCTION_LEADERS
- else if(choice == "Queen")
- if(current_setting == choice)
- to_chat(src, SPAN_XENOWARNING("You already forbid construction placement entirely."))
- return
- to_chat(src, SPAN_XENONOTICE("You forbid construction placement entirely."))
- xeno_message("The Queen has forbidden the placement of construction nodes to all but herself.", hivenumber=hivenumber)
- hive.hive_flags &= ~(XENO_CONSTRUCTION_LEADERS|XENO_CONSTRUCTION_NORMAL)
- hive.hive_flags |= XENO_CONSTRUCTION_QUEEN
-
- TIMER_COOLDOWN_START(src, COOLDOWN_TOGGLE_CONSTRUCTION, 30 SECONDS)
-
-/mob/living/carbon/xenomorph/proc/destruction_toggle()
- set name = "Permit/Disallow Special Structure Destruction"
- set desc = "Allows you to permit the hive to destroy special structures freely."
- set category = "Alien.Hivemind-Control"
-
- if(stat)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- return
-
- if(!hive)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- CRASH("[src] attempted to toggle deconstruction without a linked hive")
-
- if(hive.hive_flags_locked)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- return
-
- if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_TOGGLE_DECONSTRUCTION))
- to_chat(src, SPAN_WARNING("You must wait a bit before you can toggle this again."))
- return
-
- var/current_setting = null
- if(CHECK_MULTIPLE_BITFIELDS(hive.hive_flags, XENO_DECONSTRUCTION_ALLOW_ALL))
- current_setting = "Anyone"
- else if(!(hive.hive_flags & XENO_DECONSTRUCTION_NORMAL) && CHECK_MULTIPLE_BITFIELDS(hive.hive_flags, XENO_DECONSTRUCTION_QUEEN|XENO_DECONSTRUCTION_LEADERS))
- current_setting = "Leaders"
- else if(!(hive.hive_flags & (XENO_DECONSTRUCTION_LEADERS|XENO_DECONSTRUCTION_NORMAL)) && (hive.hive_flags & XENO_DECONSTRUCTION_QUEEN))
- current_setting = "Queen"
-
- var/choice = tgui_input_list(src, "Choose which level of destruction freedom to permit to your hive.", "Deconstruction", list("Queen", "Leaders", "Anyone"), theme="hive_status", default=current_setting)
- if(!choice)
- return
-
- if(choice == "Anyone")
- if(current_setting == choice)
- to_chat(src, SPAN_XENOWARNING("You already allow special structure destruction to all builder castes and leaders."))
- return
- to_chat(src, SPAN_XENONOTICE("You allow special structure destruction to all builder castes and leaders."))
- xeno_message("The Queen has permitted the destruction of special structures to all builder castes and leaders!", hivenumber=hivenumber)
- hive.hive_flags |= XENO_DECONSTRUCTION_ALLOW_ALL
- else if(choice == "Leaders")
- if(current_setting == choice)
- to_chat(src, SPAN_XENOWARNING("You already restrict special structure destruction to leaders only."))
- return
- to_chat(src, SPAN_XENONOTICE("You restrict special structure destruction to leaders only."))
- xeno_message("The Queen has restricted the destruction of special structures to leaders only.", hivenumber=hivenumber)
- hive.hive_flags &= ~XENO_DECONSTRUCTION_NORMAL
- hive.hive_flags |= XENO_DECONSTRUCTION_QUEEN|XENO_DECONSTRUCTION_LEADERS
- else if(choice == "Queen")
- if(current_setting == choice)
- to_chat(src, SPAN_XENOWARNING("You already forbid special structure destruction entirely."))
- return
- to_chat(src, SPAN_XENONOTICE("You forbid special structure destruction entirely."))
- xeno_message("The Queen has forbidden the destruction of special structures to all but herself.", hivenumber=hivenumber)
- hive.hive_flags &= ~(XENO_DECONSTRUCTION_LEADERS|XENO_DECONSTRUCTION_NORMAL)
- hive.hive_flags |= XENO_DECONSTRUCTION_QUEEN
-
- TIMER_COOLDOWN_START(src, COOLDOWN_TOGGLE_DECONSTRUCTION, 30 SECONDS)
-
-/mob/living/carbon/xenomorph/proc/unnesting_toggle()
- set name = "Permit/Disallow Unnesting"
- set desc = "Allows you to restrict unnesting to drones."
- set category = "Alien.Hivemind-Control"
-
- if(stat)
- to_chat(src, SPAN_WARNING("You can't do that now."))
-
- if(!hive)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- CRASH("[src] attempted to toggle unnesting without a linked hive")
-
- if(hive.hive_flags_locked)
- to_chat(src, SPAN_WARNING("You can't do that now."))
- return
-
- if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_TOGGLE_UNNESTING))
- to_chat(src, SPAN_WARNING("You must wait a bit before you can toggle this again."))
- return
-
- var/current_setting = null
- if(!(hive.hive_flags & XENO_UNNESTING_RESTRICTED))
- current_setting = "Anyone"
- else if(hive.hive_flags & XENO_UNNESTING_RESTRICTED)
- current_setting = "Drone castes"
-
- var/choice = tgui_input_list(src, "Choose which level of unnesting freedom to permit to your hive.", "Unnesting", list("Drone castes", "Anyone"), theme="hive_status", default=current_setting)
- if(!choice)
- return
-
- if(choice == "Anyone")
- if(!(hive.hive_flags & XENO_UNNESTING_RESTRICTED))
- to_chat(src, SPAN_XENOWARNING("You have already allowed everyone to unnest hosts."))
- return
- to_chat(src, SPAN_XENONOTICE("You have allowed everyone to unnest hosts."))
- xeno_message("The Queen has allowed everyone to unnest hosts.", hivenumber=hivenumber)
- hive.hive_flags &= ~XENO_UNNESTING_RESTRICTED
- else
- if(hive.hive_flags & XENO_UNNESTING_RESTRICTED)
- to_chat(src, SPAN_XENOWARNING("You have already forbidden anyone to unnest hosts, except for the drone caste."))
- return
- to_chat(src, SPAN_XENONOTICE("You have forbidden anyone to unnest hosts, except for the drone caste."))
- xeno_message("The Queen has forbidden anyone to unnest hosts, except for the drone caste.", hivenumber=hivenumber)
- hive.hive_flags |= XENO_UNNESTING_RESTRICTED
-
- TIMER_COOLDOWN_START(src, COOLDOWN_TOGGLE_UNNESTING, 30 SECONDS)
-
/mob/living/carbon/xenomorph/queen/handle_screech_act(mob/self, mob/living/carbon/xenomorph/queen/queen)
return COMPONENT_SCREECH_ACT_CANCEL
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm
index 50a2a8e1d05e..0a79fbf32e9a 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm
@@ -15,6 +15,8 @@
behavior_delegate_type = /datum/behavior_delegate/warrior_base
+ available_strains = list(/datum/xeno_strain/bulwark)
+
evolves_to = list(XENO_CASTE_PRAETORIAN, XENO_CASTE_CRUSHER)
deevolves_to = list(XENO_CASTE_DEFENDER)
caste_desc = "A powerful front line combatant."
@@ -78,12 +80,19 @@
var/emote_cooldown = 0
var/lunging = FALSE // whether or not the warrior is currently lunging (holding) a target
+/mob/living/carbon/xenomorph/warrior/handle_special_state()
+ return HAS_TRAIT(src, TRAIT_ABILITY_ENCLOSED_PLATES)
+
+/mob/living/carbon/xenomorph/warrior/handle_special_wound_states(severity)
+ if(HAS_TRAIT(src, TRAIT_ABILITY_ENCLOSED_PLATES))
+ return "Warrior_plates_[severity]"
+
/mob/living/carbon/xenomorph/warrior/throw_item(atom/target)
toggle_throw_mode(THROW_MODE_OFF)
/mob/living/carbon/xenomorph/warrior/stop_pulling()
var/datum/behavior_delegate/warrior_base/warrior_delegate = behavior_delegate
- if(isliving(pulling) && warrior_delegate.lunging)
+ if(isliving(pulling) && istype(warrior_delegate) && warrior_delegate.lunging)
warrior_delegate.lunging = FALSE // To avoid extreme cases of stopping a lunge then quickly pulling and stopping to pull someone else
var/mob/living/lunged = pulling
lunged.set_effect(0, STUN)
diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm
index ec58bd42843f..0b0e295c43c5 100644
--- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm
+++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm
@@ -21,9 +21,9 @@
var/color = null
var/ui_color = null // Color for hive status collapsible buttons and xeno count list
var/prefix = ""
- var/queen_leader_limit = 2
- var/list/open_xeno_leader_positions = list(1, 2) // Ordered list of xeno leader positions (indexes in xeno_leader_list) that are not occupied
- var/list/xeno_leader_list[2] // Ordered list (i.e. index n holds the nth xeno leader)
+ var/queen_leader_limit = 4
+ var/list/open_xeno_leader_positions = list(1, 2, 3, 4) // Ordered list of xeno leader positions (indexes in xeno_leader_list) that are not occupied
+ var/list/xeno_leader_list[4] // Ordered list (i.e. index n holds the nth xeno leader)
var/stored_larva = 0
///used by /datum/hive_status/proc/increase_larva_after_burst() to support non-integer increases to larva
@@ -49,7 +49,8 @@
var/allowed_nest_distance = 15 //How far away do we allow nests from an ovied Queen. Default 15 tiles.
var/obj/effect/alien/resin/special/pylon/core/hive_location = null //Set to ref every time a core is built, for defining the hive location
- var/tier_slot_multiplier = 1
+ /// Slots are divided by this value to reach final value.
+ var/tier_slot_divisor = 1
var/larva_gestation_multiplier = 1
var/bonus_larva_spawn_chance = 1
var/hijack_burrowed_surge = FALSE //at hijack, start spawning lots of burrowed
@@ -87,7 +88,7 @@
XENO_STRUCTURE_PYLON = 2,
)
- var/global/list/hive_structure_types = list(
+ var/list/hive_structure_types = list(
XENO_STRUCTURE_CORE = /datum/construction_template/xenomorph/core,
XENO_STRUCTURE_CLUSTER = /datum/construction_template/xenomorph/cluster,
XENO_STRUCTURE_EGGMORPH = /datum/construction_template/xenomorph/eggmorph,
@@ -100,6 +101,8 @@
/// Lazylist of possible caste defines the hive disallows evolution to
var/list/blacklisted_castes = null
+ /// List of caste defines associated with a maximum capacity number.
+ var/list/restricted_castes = null
var/datum/hive_status_ui/hive_ui
var/datum/mark_menu_ui/mark_ui
@@ -377,7 +380,7 @@
/datum/hive_status/proc/recalculate_hive()
//No leaders for a Hive without a Queen!
- queen_leader_limit = living_xeno_queen ? 4 : 0
+ queen_leader_limit = living_xeno_queen ? initial(queen_leader_limit) : 0
if (length(xeno_leader_list) > queen_leader_limit)
var/diff = 0
@@ -517,6 +520,23 @@
return xeno_counts
+/// Returns number of xenos in the given hive that are the searched caste.
+/datum/hive_status/proc/get_caste_count(caste_to_check)
+ if(!caste_to_check)
+ return
+ var/caste_count = 0
+ for(var/mob/living/carbon/xenomorph/xeno as anything in totalXenos)
+ //don't show xenos in the thunderdome when admins test stuff.
+ if(should_block_game_interaction(xeno))
+ var/area/cur_area = get_area(xeno)
+ if(!(cur_area.flags_atom & AREA_ALLOW_XENO_JOIN))
+ continue
+
+ if(xeno.caste && xeno.counts_for_slots && (xeno.caste.caste_type == caste_to_check))
+ caste_count++
+
+ return caste_count
+
/// Returns the full minimap icon as base64 string.
/datum/hive_status/proc/get_xeno_icons()
// Must match hardcoded xeno counts order.
@@ -743,10 +763,10 @@
effective_total++
// Tier 3 slots are always 20% of the total xenos in the hive
- slots[TIER_3][OPEN_SLOTS] = max(0, ceil(0.20*effective_total/tier_slot_multiplier) - used_tier_3_slots)
+ slots[TIER_3][OPEN_SLOTS] = max(0, ceil(0.20*effective_total/tier_slot_divisor) - used_tier_3_slots)
// Tier 2 slots are between 30% and 50% of the hive, depending
// on how many T3s there are.
- slots[TIER_2][OPEN_SLOTS] = max(0, ceil(0.5*effective_total/tier_slot_multiplier) - used_tier_2_slots - used_tier_3_slots)
+ slots[TIER_2][OPEN_SLOTS] = max(0, ceil(0.5*effective_total/tier_slot_divisor) - used_tier_2_slots - used_tier_3_slots)
return slots
@@ -815,6 +835,8 @@
/datum/hive_status/proc/abandon_on_hijack()
var/area/hijacked_dropship = get_area(living_xeno_queen)
+ if(!hijacked_dropship)
+ return FALSE
var/shipside_humans_weighted_count = 0
var/xenos_count = 0
for(var/name_ref in hive_structures)
diff --git a/code/modules/mob/living/carbon/xenomorph/life.dm b/code/modules/mob/living/carbon/xenomorph/life.dm
index 706dbdf36e8e..c23a39ece7e2 100644
--- a/code/modules/mob/living/carbon/xenomorph/life.dm
+++ b/code/modules/mob/living/carbon/xenomorph/life.dm
@@ -599,6 +599,6 @@ Make sure their actual health updates immediately.*/
return TRUE //weeds, yes!
if(need_weeds)
return FALSE //needs weeds, doesn't have any
- if(hive && hive.living_xeno_queen && !is_mainship_level(hive.living_xeno_queen.loc.z) && is_mainship_level(loc.z))
+ if((hive && !hive.allow_no_queen_actions) && hive.living_xeno_queen && (!is_mainship_level(hive.living_xeno_queen.loc.z) && is_mainship_level(loc.z)))
return FALSE //We are on the ship, but the Queen isn't
return TRUE //we have off-weed healing, and either we're on Almayer with the Queen, or we're on non-Almayer, or the Queen is dead, good enough!
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm
index 80e265d2fedd..d33475028b00 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm
@@ -18,7 +18,7 @@
behavior_delegate_type = /datum/behavior_delegate/ravager_berserker
/datum/xeno_strain/berserker/apply_strain(mob/living/carbon/xenomorph/ravager/ravager)
- ravager.plasma_max = 0
+ ravager.plasmapool_modifier = XENO_NO_PLASMA
ravager.health_modifier -= XENO_HEALTH_MOD_MED
ravager.armor_modifier += XENO_ARMOR_MOD_VERY_SMALL
ravager.speed_modifier += XENO_SPEED_FASTMOD_TIER_3
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm
index 1497e50ef22d..8632830e2172 100644
--- a/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/hedgehog.dm
@@ -18,7 +18,7 @@
behavior_delegate_type = /datum/behavior_delegate/ravager_hedgehog
/datum/xeno_strain/hedgehog/apply_strain(mob/living/carbon/xenomorph/ravager/ravager)
- ravager.plasma_max = 0
+ ravager.plasmapool_modifier = XENO_NO_PLASMA
ravager.small_explosives_stun = TRUE
ravager.explosivearmor_modifier += XENO_EXPOSIVEARMOR_MOD_SMALL
ravager.damage_modifier -= XENO_DAMAGE_MOD_SMALL
diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/warrior/bulwark.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/warrior/bulwark.dm
new file mode 100644
index 000000000000..6bbecc671e41
--- /dev/null
+++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/warrior/bulwark.dm
@@ -0,0 +1,393 @@
+/datum/xeno_strain/bulwark
+ name = WARRIOR_BULWARK
+ description = "You give up all of your normal abilities, as well as some damage, speed, tackle reliability, and you take 50% more melee damage, in exchange for plasma, slightly stronger explosive resistance, and directional defenses. You take 50% less damage from wired cades, have a 75% chance to strike enemies behind wired cades, and gain bonus directional armor with no directional-lock slowdown. Encasing Plates lets you enter a defensive stance that slows your movement and reduces tackle efficiency, but increases directional armor, makes you immune to knockbacks, and allows you to tear openings in walls. Plate Bash dashes up to 3 tiles and strikes a target; while encased, it instead launches the target up to 3 tiles away and knocks them down. Tail Swing trips enemies around you; if used on a grenade instead, it reflects it up to 3 tiles away with a reduced cooldown. Reflective Shield allows you to reflect bullets coming from the front back toward enemies for up to 6 seconds with a 100% reflection chance. While active, it locks your facing direction to the direction it was activated in. You can stop this ability at any time, but its minimum cooldown is 6 seconds, and each additional 1 second of use adds 2 seconds to the cooldown."
+ flavor_description = "Where there's a sword, there's a shield."
+ icon_state_prefix = "Bulwark"
+
+ actions_to_remove = list(
+ /datum/action/xeno_action/activable/warrior_punch,
+ /datum/action/xeno_action/activable/lunge,
+ /datum/action/xeno_action/activable/fling,
+ )
+ actions_to_add = list(
+ /datum/action/xeno_action/onclick/toggle_plates, //1st
+ /datum/action/xeno_action/activable/plate_bash, //2nd
+ /datum/action/xeno_action/onclick/tail_swing, //3rd
+ /datum/action/xeno_action/onclick/reflective_shield, //4th
+ )
+
+ behavior_delegate_type = /datum/behavior_delegate/warrior_bulwark
+
+/datum/xeno_strain/bulwark/apply_strain(mob/living/carbon/xenomorph/warrior/warrior)
+ warrior.explosivearmor_modifier += XENO_EXPLOSIVE_ARMOR_TIER_1
+ warrior.health_modifier += XENO_HEALTH_MOD_VERY_LARGE
+ warrior.armor_modifier += XENO_ARMOR_MOD_SMALL
+ warrior.add_plasma += XENO_PLASMA_TIER_2
+ warrior.speed += XENO_SPEED_TIER_1
+ warrior.tackle_max_modifier += 1
+ warrior.melee_vulnerability_mult += 1.5
+
+ warrior.recalculate_everything()
+
+//
+// bulwark config
+//
+
+#define BULWARK_DIR_ARMOR 10
+#define BULWARK_GRENADE_SWEEP_THROW 3
+#define BULWARK_REFLECTION_CHANCE_FRONT 100
+#define BULWARK_REFLECTED_BULLET_DAMAGE 0.5
+#define BULWARK_REFLECTED_BULLET_ACCURACY 80
+
+//
+// Passive benefits
+//
+
+/datum/behavior_delegate/warrior_bulwark
+ name = "Bulwark Warrior Behavior Delegate"
+
+ var/frontal_armor = BULWARK_DIR_ARMOR
+ var/sided_armor = BULWARK_DIR_ARMOR
+
+/datum/behavior_delegate/warrior_bulwark/append_to_stat()
+ . = list()
+ . += "Front Armor: +[frontal_armor]"
+ . += "Side Armor: +[sided_armor]"
+ if(HAS_TRAIT(bound_xeno, TRAIT_ABILITY_ENCLOSED_PLATES))
+ . += "Encased Plates: -[XENO_DAMAGE_MOD_BULWARK] Claws Damage."
+ var/datum/action/xeno_action/onclick/reflective_shield/ability_used = get_action(bound_xeno, /datum/action/xeno_action/onclick/reflective_shield)
+ if(ability_used.reflective_start_time != -1)
+ var/time_left = null
+ time_left = (BULWARK_REFLECTIVE_TIME - (world.time - ability_used.reflective_start_time)) / 10
+ . += "Reflective Plates Remaining Time: [time_left] second\s."
+ return
+
+/datum/behavior_delegate/warrior_bulwark/add_to_xeno()
+ RegisterSignal(bound_xeno, COMSIG_XENO_PRE_CALCULATE_ARMOURED_DAMAGE_PROJECTILE, PROC_REF(apply_directional_armor))
+ ADD_TRAIT(bound_xeno, TRAIT_NO_DIR_LOCK_SLOWDOWN, TRAIT_SOURCE_ABILITY("no_dir_lock_slowdown"))
+
+/datum/behavior_delegate/warrior_bulwark/proc/apply_directional_armor(mob/living/carbon/xenomorph/xeno_player, list/damagedata)
+ SIGNAL_HANDLER
+ var/projectile_direction = damagedata["direction"]
+ if(xeno_player.dir & REVERSE_DIR(projectile_direction))
+ damagedata["armor"] += frontal_armor
+ else
+ for(var/side_direction in get_perpen_dir(xeno_player.dir))
+ if(projectile_direction == side_direction)
+ damagedata["armor"] += sided_armor
+ return
+
+/datum/behavior_delegate/warrior_bulwark/on_update_icons()
+ if(bound_xeno.stat == DEAD)
+ return
+
+ if(!HAS_TRAIT(bound_xeno, TRAIT_ABILITY_REFLECTIVE_PLATES))
+ if(HAS_TRAIT(bound_xeno, TRAIT_ABILITY_ENCLOSED_PLATES) && bound_xeno.health > 0)
+ bound_xeno.icon_state = "[bound_xeno.get_strain_icon()] Warrior Shield"
+ return TRUE
+
+ if(HAS_TRAIT(bound_xeno, TRAIT_ABILITY_REFLECTIVE_PLATES) && bound_xeno.health > 0)
+ bound_xeno.icon_state = "[bound_xeno.get_strain_icon()] Warrior Shield Reflective"
+ return TRUE
+
+/datum/behavior_delegate/warrior_bulwark/melee_attack_additional_effects_target(mob/living/carbon/carbon_target)
+ if(HAS_TRAIT(bound_xeno, TRAIT_ABILITY_ENCLOSED_PLATES))
+ bound_xeno.flick_attack_overlay(carbon_target, "punch") // We shmwack them with plates!
+
+//
+// 1st ability
+//
+
+/datum/action/xeno_action/onclick/toggle_plates/use_ability()
+ var/mob/living/carbon/xenomorph/xeno_player = owner
+ if(!istype(xeno_player))
+ return
+
+ XENO_ACTION_CHECK(xeno_player)
+
+ var/datum/action/xeno_action/onclick/reflective_shield/ability_used = get_action(xeno_player, /datum/action/xeno_action/onclick/reflective_shield)
+ if(HAS_TRAIT(xeno_player, TRAIT_ABILITY_ENCLOSED_PLATES))
+ if(HAS_TRAIT(xeno_player, TRAIT_ABILITY_REFLECTIVE_PLATES))
+ to_chat(xeno_player, SPAN_WARNING("We break our reflective stance!"))
+ ability_used.reflective_safe_click_cooldown = -1
+ ability_used.remove_reflective_shield()
+ disengage_plates()
+ else
+ engage_plates()
+
+ return ..()
+
+/datum/action/xeno_action/onclick/toggle_plates/proc/engage_plates()
+ var/mob/living/carbon/xenomorph/xeno_player = owner
+ if(!istype(xeno_player))
+ return
+
+ var/datum/behavior_delegate/warrior_bulwark/behavior = xeno_player.behavior_delegate
+ if(!istype(behavior))
+ return
+
+ ADD_TRAIT(xeno_player, TRAIT_ABILITY_ENCLOSED_PLATES, TRAIT_SOURCE_ABILITY("enclosed_plates"))
+ to_chat(xeno_player, SPAN_XENOWARNING("We raise our plates and form a shield."))
+ xeno_player.ability_speed_modifier += speed_debuff
+ xeno_player.mob_size = MOB_SIZE_BIG //knockback immune
+ button.icon_state = "template_active"
+ behavior.frontal_armor += BULWARK_DIR_ARMOR
+ behavior.sided_armor -= BULWARK_DIR_ARMOR
+ xeno_player.damage_modifier -= XENO_DAMAGE_MOD_BULWARK
+ xeno_player.tackle_min_modifier += 2
+
+ xeno_player.recalculate_tackle()
+ xeno_player.update_icons()
+ apply_cooldown()
+
+/datum/action/xeno_action/onclick/toggle_plates/proc/disengage_plates()
+ var/mob/living/carbon/xenomorph/xeno_player = owner
+ if(!istype(xeno_player))
+ return
+
+ var/datum/behavior_delegate/warrior_bulwark/behavior = xeno_player.behavior_delegate
+ if(!istype(behavior))
+ return
+
+ REMOVE_TRAIT(xeno_player, TRAIT_ABILITY_ENCLOSED_PLATES, TRAIT_SOURCE_ABILITY("enclosed_plates"))
+ to_chat(xeno_player, SPAN_XENOWARNING("We lower our plates."))
+ xeno_player.ability_speed_modifier -= speed_debuff
+ xeno_player.mob_size = MOB_SIZE_XENO //no longer knockback immune
+ button.icon_state = "template_xeno"
+ behavior.frontal_armor -= BULWARK_DIR_ARMOR
+ behavior.sided_armor += BULWARK_DIR_ARMOR
+ xeno_player.damage_modifier += XENO_DAMAGE_MOD_BULWARK
+ xeno_player.tackle_min_modifier -= 2
+
+ xeno_player.recalculate_tackle()
+ xeno_player.update_icons()
+ apply_cooldown()
+
+//
+// 2nd ability
+//
+
+/datum/action/xeno_action/activable/plate_bash/use_ability(atom/target_atom)
+ var/mob/living/carbon/xenomorph/xeno_player = owner
+
+ if(!iscarbon(target_atom))
+ return
+
+ if(!isxeno_human(target_atom) || xeno_player.can_not_harm(target_atom))
+ return
+
+ XENO_ACTION_CHECK_USE_PLASMA(xeno_player)
+
+ var/mob/living/carbon/carbon_target = target_atom
+ if(carbon_target.stat == DEAD)
+ return
+
+ var/distance = get_dist(xeno_player, carbon_target)
+ var/max_distance = 2
+ if(distance > max_distance)
+ return
+
+ if(!HAS_TRAIT(xeno_player, TRAIT_ABILITY_ENCLOSED_PLATES))
+ xeno_player.throw_atom(get_step_towards(carbon_target, xeno_player), 2, SPEED_SLOW, xeno_player, tracking=TRUE)
+ if(!xeno_player.Adjacent(carbon_target))
+ on_cooldown_end()
+ return
+
+ carbon_target.last_damage_data = create_cause_data(xeno_player.caste_type, xeno_player)
+ var/facing = get_dir(xeno_player, carbon_target)
+
+ if(HAS_TRAIT(xeno_player, TRAIT_ABILITY_ENCLOSED_PLATES))
+ xeno_player.throw_carbon(carbon_target, facing, 3, SPEED_VERY_FAST, shake_camera = TRUE, immobilize = TRUE)
+ carbon_target.KnockDown(1)
+ else
+ xeno_player.throw_carbon(carbon_target, facing, 1, SPEED_SLOW, shake_camera = TRUE, immobilize = FALSE)
+
+ apply_custom_cooldown()
+
+ if(carbon_target.stat != DEAD && (!(carbon_target.status_flags & XENO_HOST) || !HAS_TRAIT(carbon_target, TRAIT_NESTED)))
+ carbon_target.apply_armoured_damage(get_xeno_damage_slash(carbon_target, base_damage), ARMOR_MELEE, BRUTE, "chest", 5)
+
+ xeno_player.visible_message(SPAN_XENOWARNING("[xeno_player] dashes at [carbon_target] with its armored plates!"),
+ SPAN_XENOWARNING("We dash at [carbon_target] with our armored plates!"))
+
+ xeno_player.face_atom(carbon_target)
+ xeno_player.animation_attack_on(carbon_target)
+ xeno_player.flick_attack_overlay(carbon_target, "punch")
+ playsound(carbon_target,'sound/weapons/alien_claw_block.ogg', 50, 1)
+
+ return ..()
+
+//
+// 3rd ability
+//
+
+/datum/action/xeno_action/onclick/tail_swing/use_ability()
+ var/mob/living/carbon/xenomorph/xeno_player = owner
+
+ var/datum/action/xeno_action/onclick/reflective_shield/ability_used = get_action(xeno_player, /datum/action/xeno_action/onclick/reflective_shield)
+ var/datum/action/xeno_action/onclick/toggle_plates/plates_used = get_action(xeno_player, /datum/action/xeno_action/onclick/toggle_plates)
+
+ if(HAS_TRAIT(xeno_player, TRAIT_ABILITY_ENCLOSED_PLATES))
+ if(HAS_TRAIT(xeno_player, TRAIT_ABILITY_REFLECTIVE_PLATES))
+ to_chat(xeno_player, SPAN_WARNING("We break our reflective stance!"))
+ ability_used.reflective_safe_click_cooldown = -1
+ ability_used.remove_reflective_shield()
+ to_chat(xeno_player, SPAN_WARNING("We break our defensive stance!"))
+ plates_used.disengage_plates()
+
+ xeno_player.visible_message(SPAN_XENOWARNING("[xeno_player] swings its tail in a wide circle!"),
+ SPAN_XENOWARNING("We swing our tail in a wide circle!"))
+
+ XENO_ACTION_CHECK_USE_PLASMA(xeno_player)
+
+ xeno_player.spin_circle()
+ playsound(xeno_player,'sound/effects/tail_swing.ogg', 25, 1)
+
+ var/hit_enemy = FALSE
+ for(var/mob/living/carbon/carbon_target in orange(swing_range, get_turf(xeno_player)))
+ if(!isxeno_human(carbon_target) || xeno_player.can_not_harm(carbon_target))
+ continue
+ if(carbon_target.stat == DEAD)
+ continue
+ if(HAS_TRAIT(carbon_target, TRAIT_NESTED))
+ continue
+
+ hit_enemy = TRUE
+ xeno_player.flick_attack_overlay(carbon_target, "punch")
+ carbon_target.last_damage_data = create_cause_data(xeno_player.caste_type, xeno_player)
+ carbon_target.apply_armoured_damage(get_xeno_damage_slash(xeno_player, 15), ARMOR_MELEE, BRUTE)
+ shake_camera(carbon_target, 2, 1)
+
+ if(carbon_target.mob_size < MOB_SIZE_BIG)
+ carbon_target.apply_effect(get_xeno_stun_duration(carbon_target, 1), WEAKEN)
+
+ to_chat(carbon_target, SPAN_XENOWARNING("You are tripped by [xeno_player]'s tail swing!"))
+ playsound(carbon_target,'sound/weapons/alien_claw_block.ogg', 50, 1)
+
+ var/hit_grenade = FALSE
+ for(var/obj/item/explosive/grenade/grenade in orange(swing_range, get_turf(xeno_player)))
+ hit_grenade = TRUE
+ var/direction = get_dir(xeno_player, grenade)
+ var/turf/target_destination = get_ranged_target_turf(grenade, direction, BULWARK_GRENADE_SWEEP_THROW)
+ if(target_destination)
+ grenade.throw_atom(target_destination, BULWARK_GRENADE_SWEEP_THROW, SPEED_FAST, grenade)
+ playsound(xeno_player,'sound/effects/grenade_hit.ogg', 50, 1)
+
+ if(!hit_enemy && hit_grenade)
+ xeno_cooldown *= 0.3
+
+ if(!hit_enemy && !hit_grenade)
+ xeno_cooldown *= 0.3
+
+ apply_custom_cooldown()
+
+ return ..()
+
+//
+// 4th ability
+//
+
+/datum/action/xeno_action/onclick/reflective_shield/use_ability()
+ var/mob/living/carbon/xenomorph/warrior/xeno_player = owner
+
+ XENO_ACTION_CHECK(xeno_player)
+
+ if(HAS_TRAIT(xeno_player, TRAIT_ABILITY_REFLECTIVE_PLATES))
+ remove_reflective_shield()
+ return
+
+ if(!HAS_TRAIT(xeno_player, TRAIT_ABILITY_ENCLOSED_PLATES))
+ xeno_player.balloon_alert(xeno_player, "we need to tense up our plates!", text_color = "#7d32bb", delay = 1 SECONDS)
+ return
+
+ if(!check_and_use_plasma_owner(80))
+ return
+
+ xeno_player.stop_pulling()
+ ADD_TRAIT(xeno_player, TRAIT_ABILITY_REFLECTIVE_PLATES, TRAIT_SOURCE_ABILITY("reflective_plates"))
+ xeno_player.flags_atom |= DIRLOCK
+ xeno_player.update_icons()
+ xeno_player.create_shield(BULWARK_REFLECTIVE_TIME, "shield2")
+ button.icon_state = "template_active"
+ reflective_start_time = world.time
+ reflective_safe_click_cooldown = world.time + 1 SECONDS
+
+ to_chat(xeno_player, SPAN_XENOWARNING("We adjust our plates and prepare for incoming frontal attacks!"))
+ xeno_player.visible_message(SPAN_XENOWARNING("[xeno_player] locks its stance, focusing on incoming frontal attacks!"))
+
+ if(reflective_shield_timer_id != TIMER_ID_NULL)
+ deltimer(reflective_shield_timer_id)
+
+ reflective_shield_timer_id = addtimer(CALLBACK(src, PROC_REF(remove_reflective_shield)), BULWARK_REFLECTIVE_TIME, TIMER_STOPPABLE)
+
+ apply_cooldown()
+ return ..()
+
+/datum/action/xeno_action/onclick/reflective_shield/proc/remove_reflective_shield()
+ var/mob/living/carbon/xenomorph/warrior/xeno_player = owner
+
+ var/datum/action/xeno_action/onclick/reflective_shield/ability_used = get_action(xeno_player, /datum/action/xeno_action/onclick/reflective_shield)
+ if(!istype(ability_used))
+ return
+
+ if(!HAS_TRAIT(xeno_player, TRAIT_ABILITY_REFLECTIVE_PLATES))
+ return
+
+ if(world.time < ability_used.reflective_safe_click_cooldown)
+ to_chat(xeno_player, SPAN_XENOWARNING("We need a moment before breaking our reflective stance!"))
+ return
+
+ REMOVE_TRAIT(xeno_player, TRAIT_ABILITY_REFLECTIVE_PLATES, TRAIT_SOURCE_ABILITY("reflective_plates"))
+ xeno_player.update_icons()
+ xeno_player.remove_suit_layer()
+ button.icon_state = "template_xeno"
+ to_chat(xeno_player, SPAN_XENOWARNING("We adjust our plates and stance back to normal."))
+
+ if(ability_used.reflective_shield_timer_id != TIMER_ID_NULL)
+ deltimer(ability_used.reflective_shield_timer_id)
+ ability_used.reflective_shield_timer_id = TIMER_ID_NULL
+
+ if(ability_used.reflective_start_time > 0)
+ var/used_ratio = round((world.time - ability_used.reflective_start_time) / ability_used.duration, 0.1)
+ ability_used.reflective_recharge_time = (BULWARK_REFLECTIVE_TIME * used_ratio * ability_used.reflective_refund_multiplier) + 6 SECONDS
+
+ ability_used.reflective_start_time = -1
+ apply_cooldown_override(ability_used.reflective_recharge_time)
+
+/mob/living/carbon/xenomorph/warrior/get_reflection_chance(obj/projectile/bullet)
+ var/datum/behavior_delegate/warrior_bulwark/behavior = src.behavior_delegate
+ if(!istype(behavior))
+ return
+
+ if(!HAS_TRAIT(src, TRAIT_ABILITY_REFLECTIVE_PLATES))
+ return
+
+ if(body_position == LYING_DOWN || stat == UNCONSCIOUS)
+ return //we don't want to reflect bullets when we are laying down/unconscious.
+
+ if(bullet.ammo.flags_ammo_behavior & (AMMO_SNIPER|AMMO_ROCKET|AMMO_XENO|AMMO_NO_DEFLECT|AMMO_SKIPS_ALIENS))
+ return //we don't want to reflect sniper bullets, rockets, anti-reflection bullets, xeno bullets and friendly bullets.
+
+ var/projectile_dir = 0
+
+ if(!bullet.firer)
+ return
+
+ projectile_dir = bullet.dir
+
+ if(src.dir & REVERSE_DIR(projectile_dir))
+ return BULWARK_REFLECTION_CHANCE_FRONT
+
+ for(var/side_dir in get_perpen_dir(src.dir))
+ if(projectile_dir == side_dir)
+ return 0
+
+ return
+
+//
+// Custom Proc(s)
+//
+
+/datum/action/xeno_action/proc/apply_custom_cooldown()
+ apply_cooldown()
+ xeno_cooldown = initial(xeno_cooldown) //We revert cooldown back to original value (after it got applied)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 779969ddf073..324a1f61bd27 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -725,6 +725,8 @@ note dizziness decrements automatically in the mob's Life() proc.
if(!canface())
return 0
if(dir != ndir)
+ if(HAS_TRAIT(src, TRAIT_ABILITY_REFLECTIVE_PLATES))
+ return
flags_atom &= ~DIRLOCK
setDir(ndir)
if(buckled && !buckled.anchored)
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index f180e11be404..c8c3d6c08d4c 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -186,7 +186,8 @@ CLIENT_VERB(drop_item)
move_delay += mob.next_move_slowdown
mob.next_move_slowdown = 0
if((mob.flags_atom & DIRLOCK) && mob.dir != direct)
- move_delay += MOVE_REDUCTION_DIRECTION_LOCKED // by Geeves
+ if(!HAS_TRAIT(mob, TRAIT_NO_DIR_LOCK_SLOWDOWN))
+ move_delay += MOVE_REDUCTION_DIRECTION_LOCKED // by Geeves
mob.cur_speed = clamp(10/(move_delay + 0.5), MIN_SPEED, MAX_SPEED)
next_movement = world.time + MINIMAL_MOVEMENT_INTERVAL // We pre-set this now for the crawling case. If crawling do_after fails, next_movement would be set after the attempt end instead of now.
diff --git a/code/modules/projectiles/guns/flamer/flamer.dm b/code/modules/projectiles/guns/flamer/flamer.dm
index b4928ff6c07f..8a1147ef368b 100644
--- a/code/modules/projectiles/guns/flamer/flamer.dm
+++ b/code/modules/projectiles/guns/flamer/flamer.dm
@@ -328,6 +328,7 @@
else
var/obj/effect/particle_effect/smoke/chem/checker = new()
var/atom/blocked = LinkBlocked(checker, source_turf, turf)
+ qdel(checker)
if(blocked)
break
@@ -899,38 +900,30 @@ GLOBAL_LIST_EMPTY(flamer_particles)
else
weather_smothering_strength = 0
-/proc/fire_spread_recur(turf/target, datum/cause_data/cause_data, remaining_distance, direction, fire_lvl, burn_lvl, f_color, burn_sprite = "dynamic", aerial_flame_level)
- var/direction_angle = dir2angle(direction)
+/proc/create_fire_reagent(fire_lvl, burn_lvl, f_color, burn_sprite = "dynamic")
+ var/datum/reagent/fire_reag = new()
+ fire_reag.intensityfire = burn_lvl
+ fire_reag.durationfire = fire_lvl
+ fire_reag.burn_sprite = burn_sprite
+ fire_reag.burncolor = f_color
+ return fire_reag
+
+/proc/fire_spread_recur(turf/target, datum/cause_data/cause_data, remaining_distance, direction, datum/reagent/fire_reagent, aerial_flame_level)
+ set waitfor = FALSE
var/obj/flamer_fire/foundflame = locate() in target
if(!foundflame)
- var/datum/reagent/fire_reag = new()
- fire_reag.intensityfire = burn_lvl
- fire_reag.durationfire = fire_lvl
- fire_reag.burn_sprite = burn_sprite
- fire_reag.burncolor = f_color
- new/obj/flamer_fire(target, cause_data, fire_reag)
+ new/obj/flamer_fire(target, cause_data, fire_reagent)
if(target.density)
return
- for(var/spread_direction in GLOB.alldirs)
-
+ for(var/spread_angle in list(-45, 0, 45))
+ var/spread_direction = turn(direction, spread_angle)
var/spread_power = remaining_distance
-
- var/spread_direction_angle = dir2angle(spread_direction)
-
- var/angle = 180 - abs( abs( direction_angle - spread_direction_angle ) - 180 ) // the angle difference between the spread direction and initial direction
-
- switch(angle) //this reduces power when the explosion is going around corners
- if (45)
- spread_power *= 0.75
- if (90 to 180) //turns out angles greater than 90 degrees almost never happen. This bit also prevents trying to spread backwards
- continue
-
- switch(spread_direction)
- if(NORTH,SOUTH,EAST,WEST)
- spread_power--
- else
- spread_power -= 1.414 //diagonal spreading
+ if(abs(spread_angle) == 45) // diagonal
+ spread_power -= sqrt(2)
+ spread_power *= 0.75 //this reduces power when the explosion is going around corners
+ else // cardinal
+ spread_power -= 1
if (spread_power < 1)
continue
@@ -942,37 +935,33 @@ GLOBAL_LIST_EMPTY(flamer_particles)
if(aerial_flame_level)
if(picked_turf.get_pylon_protection_level() >= aerial_flame_level)
- break
- var/area/picked_area = get_area(picked_turf)
+ continue
+ var/area/picked_area = picked_turf.loc // get_area() is slower here when we know we have a turf
if(CEILING_IS_PROTECTED(picked_area?.ceiling, get_ceiling_protection_level(aerial_flame_level)))
- break
-
- spawn(0)
- fire_spread_recur(picked_turf, cause_data, spread_power, spread_direction, fire_lvl, burn_lvl, f_color, burn_sprite, aerial_flame_level)
+ continue
-/proc/fire_spread(turf/target, datum/cause_data/cause_data, range, fire_lvl, burn_lvl, f_color, burn_sprite = "dynamic", aerial_flame_level = TURF_PROTECTION_NONE)
- var/datum/reagent/fire_reag = new()
- fire_reag.intensityfire = burn_lvl
- fire_reag.durationfire = fire_lvl
- fire_reag.burn_sprite = burn_sprite
- fire_reag.burncolor = f_color
+ CHECK_TICK // before the recursive call
+ fire_spread_recur(picked_turf, cause_data, spread_power, spread_direction, fire_reagent, aerial_flame_level)
- new/obj/flamer_fire(target, cause_data, fire_reag)
- for(var/direction in GLOB.alldirs)
+/proc/fire_spread(turf/target, datum/cause_data/cause_data, range, fire_reagent, aerial_flame_level = TURF_PROTECTION_NONE)
+ set waitfor = FALSE
+ new/obj/flamer_fire(target, cause_data, fire_reagent)
+ for(var/turf/picked_turf in orange(1, target))
+ var/direction = get_dir(target, picked_turf)
var/spread_power = range
switch(direction)
if(NORTH,SOUTH,EAST,WEST)
spread_power--
else
spread_power -= 1.414 //diagonal spreading
- var/turf/picked_turf = get_step(target, direction)
if(aerial_flame_level)
if(picked_turf.get_pylon_protection_level() >= aerial_flame_level)
continue
var/area/picked_area = get_area(picked_turf)
if(CEILING_IS_PROTECTED(picked_area?.ceiling, get_ceiling_protection_level(aerial_flame_level)))
continue
- fire_spread_recur(picked_turf, cause_data, spread_power, direction, fire_lvl, burn_lvl, f_color, burn_sprite, aerial_flame_level)
+ fire_spread_recur(picked_turf, cause_data, spread_power, direction, fire_reagent, aerial_flame_level)
+ CHECK_TICK // don't overrun spreading in just one direction
// So it doens't do the spinny animation
/obj/flamer_fire/onZImpact(turf/impact_turf, height)
diff --git a/code/modules/projectiles/magazines/sentries.dm b/code/modules/projectiles/magazines/sentries.dm
index 7287b71e16f0..199516286a7d 100644
--- a/code/modules/projectiles/magazines/sentries.dm
+++ b/code/modules/projectiles/magazines/sentries.dm
@@ -34,7 +34,7 @@
desc = "An ammo drum of 50 12g buckshot drums for the UA 12-G Shotgun Sentry. Just feed it into the sentry gun's ammo port when its ammo is depleted."
caliber = "12g"
max_rounds = 50
- default_ammo = /datum/ammo/bullet/shotgun/buckshot
+ default_ammo = /datum/ammo/bullet/shotgun/buckshot/turret
/obj/item/ammo_magazine/sentry/wy
name = "H20 ammo drum (10x42mm Caseless)"
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 98852846bba6..f9fc7ec260e4 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -360,7 +360,7 @@
var/turf/current_turf = get_turf(src)
var/turf/next_turf = popleft(path)
- // Terminal projectiles (about to hit) are handled first for retarget logic
+ // Terminal projectiles (about to hit) are handled firer for retarget logic
if((speed * world.tick_lag) >= get_dist(current_turf, target_turf))
SEND_SIGNAL(src, COMSIG_BULLET_TERMINAL)
@@ -420,6 +420,8 @@
return FALSE
if(turf.density) // Handle wall hit
+ if(turf in permutated)
+ return FALSE
var/ammo_flags = ammo.flags_ammo_behavior | projectile_override_flags
if(SEND_SIGNAL(src, COMSIG_BULLET_PRE_HANDLE_TURF, turf) & COMPONENT_BULLET_PASS_THROUGH)
@@ -506,19 +508,33 @@
if(SEND_SIGNAL(src, COMSIG_BULLET_POST_HANDLE_OBJ, obj, .) & COMPONENT_BULLET_PASS_THROUGH)
return FALSE
-/obj/projectile/proc/handle_mob(mob/living/living)
+/obj/projectile/proc/handle_mob(mob/living/target_living)
// If we've already handled this atom, don't do it again
- if(SEND_SIGNAL(src, COMSIG_BULLET_PRE_HANDLE_MOB, living, .) & COMPONENT_BULLET_PASS_THROUGH)
+ if(SEND_SIGNAL(src, COMSIG_BULLET_PRE_HANDLE_MOB, target_living, .) & COMPONENT_BULLET_PASS_THROUGH)
return FALSE
- if((MODE_HAS_MODIFIER(/datum/gamemode_modifier/disable_attacking_corpses) && living.stat == DEAD) || (living in permutated))
+ if((MODE_HAS_MODIFIER(/datum/gamemode_modifier/disable_attacking_corpses) && target_living.stat == DEAD) || (target_living in permutated))
return FALSE
- permutated |= living
- if((ammo.flags_ammo_behavior & AMMO_XENO) && (isfacehugger(living) || living.stat == DEAD)) //xeno ammo is NEVER meant to hit or damage dead people. If you want to add a xeno ammo that DOES then make a new flag that makes it ignore this check.
+
+ if(isxeno(target_living))
+ var/mob/living/carbon/xenomorph/xeno = target_living
+ var/directional_chance = xeno.get_reflection_chance(src)
+ if(directional_chance > 0 && prob(directional_chance))
+ src.reflect_projectile_at_firer(
+ reflector = xeno,
+ new_firer = xeno,
+ damage_multiplier = BULWARK_REFLECTED_BULLET_DAMAGE,
+ accuracy_override = BULWARK_REFLECTED_BULLET_ACCURACY,
+ angle_variance = 25
+ )
+ return TRUE
+
+ permutated |= target_living
+ if((ammo.flags_ammo_behavior & AMMO_XENO) && (isfacehugger(target_living) || target_living.stat == DEAD)) //xeno ammo is NEVER meant to hit or damage dead people. If you want to add a xeno ammo that DOES then make a new flag that makes it ignore this check.
return FALSE
- var/hit_chance = living.get_projectile_hit_chance(src)
+ var/hit_chance = target_living.get_projectile_hit_chance(src)
if(hit_chance) // Calculated from combination of both ammo accuracy and gun accuracy
@@ -526,86 +542,86 @@
var/direct_hit = FALSE
// Wasn't the clicked target
- if(original != living)
+ if(original != target_living)
def_zone = rand_zone()
// Xenos get a RNG limb miss chance regardless of being clicked target or not, see below
- else if(isxeno(living) && hit_roll > hit_chance - 20)
+ else if(isxeno(target_living) && hit_roll > hit_chance - 20)
def_zone = rand_zone()
// Other targets do the same roll with penalty - a near hit will hit but redirected to another limb
- else if(!isxeno(living) && hit_roll > hit_chance - 20 - GLOB.base_miss_chance[def_zone])
+ else if(!isxeno(target_living) && hit_roll > hit_chance - 20 - GLOB.base_miss_chance[def_zone])
def_zone = rand_zone()
else
direct_hit = TRUE
if(firer)
- SEND_SIGNAL(firer, COMSIG_BULLET_DIRECT_HIT, living)
+ SEND_SIGNAL(firer, COMSIG_BULLET_DIRECT_HIT, target_living)
// At present, Xenos have no inherent effects or localized damage stemming from limb targeting
// Therefore we exempt the shooter from direct hit accuracy penalties as well,
// simply to avoid them from resetting target to chest every time they want to shoot a xeno
- if(!direct_hit || !isxeno(living)) // For normal people or direct hits we apply the limb accuracy penalty
+ if(!direct_hit || !isxeno(target_living)) // For normal people or direct hits we apply the limb accuracy penalty
hit_chance -= GLOB.base_miss_chance[def_zone]
// else for direct hits on xenos, we skip it, pretending it's a chest shot with zero penalty
#if DEBUG_HIT_CHANCE
- to_world(SPAN_DEBUG("([living]) Hit chance: [hit_chance] | Roll: [hit_roll]"))
+ to_world(SPAN_DEBUG("([target_living]) Hit chance: [hit_chance] | Roll: [hit_roll]"))
#endif
- if(hit_chance > hit_roll && !(living.status_flags & RECENTSPAWN))
+ if(hit_chance > hit_roll && !(target_living.status_flags & RECENTSPAWN))
#if DEBUG_HIT_CHANCE
- to_world(SPAN_DEBUG("([living]) Hit."))
+ to_world(SPAN_DEBUG("([target_living]) Hit."))
#endif
var/ammo_flags = ammo.flags_ammo_behavior | projectile_override_flags
// If the ammo should hit the surface of the target and there is a mob blocking
// The current turf is the "surface" of the target
if(ammo_flags & AMMO_STRIKES_SURFACE)
- var/turf/turf = get_turf(living)
+ var/turf/turf = get_turf(target_living)
// We "hit" the current turf but strike the actual blockage
ammo.on_hit_turf(get_turf(src),src)
turf.bullet_act(src)
- else if(living && living.loc && (living.bullet_act(src) != -1))
- ammo.on_hit_mob(living,src, firer)
+ else if(target_living && target_living.loc && (target_living.bullet_act(src) != -1))
+ ammo.on_hit_mob(target_living,src, firer)
// If we are a xeno shooting something
- if(istype(ammo, /datum/ammo/xeno) && isxeno(firer) && living.stat != DEAD && ammo.apply_delegate)
+ if(istype(ammo, /datum/ammo/xeno) && isxeno(firer) && target_living.stat != DEAD && ammo.apply_delegate)
var/mob/living/carbon/xenomorph/xeno = firer
if(xeno.behavior_delegate)
var/datum/behavior_delegate/MD = xeno.behavior_delegate
- MD.ranged_attack_additional_effects_target(living)
- MD.ranged_attack_additional_effects_self(living)
+ MD.ranged_attack_additional_effects_target(target_living)
+ MD.ranged_attack_additional_effects_self(target_living)
// If the thing we're hitting is a Xeno
- if(istype(living, /mob/living/carbon/xenomorph))
- var/mob/living/carbon/xenomorph/xeno = living
+ if(istype(target_living, /mob/living/carbon/xenomorph))
+ var/mob/living/carbon/xenomorph/xeno = target_living
if(xeno.behavior_delegate)
xeno.behavior_delegate.on_hitby_projectile(ammo)
. = TRUE
- else if(living.body_position != LYING_DOWN)
- animatation_displace_reset(living)
+ else if(target_living.body_position != LYING_DOWN)
+ animatation_displace_reset(target_living)
if(ammo.sound_miss)
- playsound_client(living.client, ammo.sound_miss, get_turf(living), 75, TRUE)
- living.visible_message(SPAN_AVOIDHARM("[src] misses [living]!"),
+ playsound_client(target_living.client, ammo.sound_miss, get_turf(target_living), 75, TRUE)
+ target_living.visible_message(SPAN_AVOIDHARM("[src] misses [target_living]!"),
SPAN_AVOIDHARM("[src] narrowly misses you!"), null, 4, CHAT_TYPE_TAKING_HIT)
- var/log_message = "[src] narrowly missed [key_name(living)]"
+ var/log_message = "[src] narrowly missed [key_name(target_living)]"
var/mob/living/carbon/shotby = firer
if(istype(shotby))
- living.attack_log += "\[[time_stamp()]\] [src], fired by [key_name(firer)], narrowly missed [key_name(living)]"
- shotby.attack_log += "\[[time_stamp()]\] [src], fired by [key_name(shotby)], narrowly missed [key_name(living)]"
- log_message = "[src], fired by [key_name(firer)], narrowly missed [key_name(living)]"
+ target_living.attack_log += "\[[time_stamp()]\] [src], fired by [key_name(firer)], narrowly missed [key_name(target_living)]"
+ shotby.attack_log += "\[[time_stamp()]\] [src], fired by [key_name(shotby)], narrowly missed [key_name(target_living)]"
+ log_message = "[src], fired by [key_name(firer)], narrowly missed [key_name(target_living)]"
log_attack(log_message)
#if DEBUG_HIT_CHANCE
- to_world(SPAN_DEBUG("([living]) Missed."))
+ to_world(SPAN_DEBUG("([target_living]) Missed."))
#endif
- if(SEND_SIGNAL(src, COMSIG_BULLET_POST_HANDLE_MOB, living, .) & COMPONENT_BULLET_PASS_THROUGH)
+ if(SEND_SIGNAL(src, COMSIG_BULLET_POST_HANDLE_MOB, target_living, .) & COMPONENT_BULLET_PASS_THROUGH)
return FALSE
/obj/projectile/proc/check_canhit(turf/current_turf, turf/next_turf, list/ignore_list)
@@ -1365,6 +1381,60 @@
. = ..()
source_pill = null
+/**
+ * explained:
+ * * reflector - what shoots projectiles back.
+ * * new_firer - who shoots projectiles back.
+ * * damage_multiplier - how much bullet damage get reflected, default is 0.5 (50%).
+ * * accuracy_override - used to override shoot bullet accuracy.
+ * * angle_variance - how big reflection cone should be, default is 25, final cone will be 50 degrees (-25 and 25)
+ * * range_override - how far reflected bullet will travel, overridies original projectile value with new one.
+ * * projectile_flag_override - should only be used if you want to change bullet to other type than shrapnel.
+ * * ignore_safety - if set to TRUE, allows "target" to reflect reflected bullets.
+ */
+/obj/projectile/proc/reflect_projectile_at_firer(atom/reflector, atom/new_firer, damage_multiplier = 0.5, accuracy_override, angle_variance = 25, range_override, projectile_flag_override = NONE, ignore_safety = FALSE)
+ if(!ammo)
+ return
+
+ if(!firer)
+ return
+
+ if(!ignore_safety)
+ if(projectile_flags & PROJECTILE_REFLECTED) // So we cannot reflect reflected, could create infinite* loop.
+ return
+
+ var/turf/source_turf = get_turf(reflector)
+ if(!source_turf)
+ return
+
+ var/obj/projectile/new_proj = new(source_turf,create_cause_data("[reflector]"))
+ new_proj.generate_bullet(ammo)
+ new_proj.damage = damage * damage_multiplier
+
+ if(!isnull(accuracy_override))
+ new_proj.accuracy = accuracy_override
+ else
+ new_proj.accuracy = accuracy
+
+ if(projectile_flag_override)
+ new_proj.projectile_flags |= projectile_flag_override
+ else
+ new_proj.projectile_flags |= PROJECTILE_SHRAPNEL //we make it shrapnel unless overrided.
+
+ new_proj.projectile_flags |= PROJECTILE_REFLECTED
+ new_proj.permutated |= src
+ new_proj.permutated |= reflector
+
+ var/angle = Get_Angle(source_turf, firer)
+ angle += rand(-angle_variance, angle_variance)
+ var/atom/target = get_angle_target_turf(source_turf,angle,get_dist_sqrd(source_turf, firer))
+
+ if(!range_override)
+ range_override = max(ammo.max_range, 1)
+
+ new_proj.fire_at(target,new_firer ? new_firer : firer,reflector,range_override,speed = ammo.shell_speed)
+ return new_proj
+
#undef DEBUG_HIT_CHANCE
#undef DEBUG_HUMAN_DEFENSE
#undef DEBUG_XENO_DEFENSE
diff --git a/colonialmarines.dme b/colonialmarines.dme
index ef428f6aba0c..af6e0cf6ead6 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -618,7 +618,6 @@
#include "code\datums\entities\player_sticky_ban.dm"
#include "code\datums\entities\player_times.dm"
#include "code\datums\entities\ticket.dm"
-#include "code\datums\entities\twitch_link.dm"
#include "code\datums\entities\logs\player_times_log.dm"
#include "code\datums\factions\clf.dm"
#include "code\datums\factions\cmb.dm"
@@ -2302,6 +2301,7 @@
#include "code\modules\mob\living\carbon\xenomorph\strains\castes\ravager\berserker.dm"
#include "code\modules\mob\living\carbon\xenomorph\strains\castes\ravager\hedgehog.dm"
#include "code\modules\mob\living\carbon\xenomorph\strains\castes\runner\acider.dm"
+#include "code\modules\mob\living\carbon\xenomorph\strains\castes\warrior\bulwark.dm"
#include "code\modules\mob\living\silicon\death.dm"
#include "code\modules\mob\living\silicon\login.dm"
#include "code\modules\mob\living\silicon\say.dm"
diff --git a/html/changelogs/AutoChangeLog-pr-12274.yml b/html/changelogs/AutoChangeLog-pr-12274.yml
deleted file mode 100644
index 275f0e8abf01..000000000000
--- a/html/changelogs/AutoChangeLog-pr-12274.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "cuberound"
-delete-after: True
-changes:
- - qol: "xenos can see reamining durability of special structures on examine"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-12373.yml b/html/changelogs/AutoChangeLog-pr-12373.yml
new file mode 100644
index 000000000000..665345299bb5
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-12373.yml
@@ -0,0 +1,4 @@
+author: "Ammoniacres"
+delete-after: True
+changes:
+ - bugfix: "fixed unpowered doors being unable to be opened by maintenance jacks (for humans)"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-12420.yml b/html/changelogs/AutoChangeLog-pr-12420.yml
new file mode 100644
index 000000000000..169690e97921
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-12420.yml
@@ -0,0 +1,4 @@
+author: "BlitzArde, Blundir"
+delete-after: True
+changes:
+ - soundtweak: "Replaced the disturbances hazmat joe sound file"
\ No newline at end of file
diff --git a/html/changelogs/archive/2026-06.yml b/html/changelogs/archive/2026-06.yml
index eba1ae22092f..14c4a8236610 100644
--- a/html/changelogs/archive/2026-06.yml
+++ b/html/changelogs/archive/2026-06.yml
@@ -246,3 +246,59 @@
2026-06-20:
Antlers:
- bugfix: Hyperdyne corporate survivors no longer get aired out by LZ turrets
+2026-06-22:
+ Drathek:
+ - bugfix: Fixed a runtime with custom smoke spawning
+ MoondancerPony:
+ - refactor: made fire spread (from OB, flamers, etc) much faster
+ NHC:
+ - bugfix: Removed duped sunglasses from loudout menu
+ Venuska1117:
+ - rscadd: Add new Warrior Strain "Bulwark".
+ - rscadd: Bulwark have 600 health, tackles of 2 min - 5max, gains 200 plasma, +10
+ explosive and +10 normal armor for decreased speed and takes 50% more melee
+ damage.
+ - rscadd: Bulwark have 4 passive abilities, 50% less damage from wired cades (5
+ damage instead of 10), 75% for slashes or tailstab to go through wired cades
+ and hit target behind them, +10 front armor and +10 side armor and no direct
+ lock slowdown.
+ - rscadd: Encased Plates, this ability when activated it will reduce your movement
+ speed by 1.35, increases minimum tackles needed to tackle someone down by +2
+ and decreased slash damage by 8, in exchange you gain +10 front armor and lose
+ -10 side armor (stacks with passive), immunity to knockdowns and ability to
+ open holes in walls.
+ - rscadd: Plate Bash, this ability will launch you toward targets if its in range
+ of 2 tiles (3 tiles), it will hit them 1 tile away and shake their screen, if
+ encased plates are active, this ability changes to only work on adjecent targets
+ but in exchange you send them 3 tiles away, knockdown and stun target.
+ - rscadd: Tail Sweep, this ability will hit all adjecent tiles (including corners)
+ around you and trip enemies down and briefly stun them, if grenade is present
+ instead of target, you will send grenade 4 tiles away and your cooldown will
+ be decreased to 1/3rd of full cooldown, if both target and grenade are hit,
+ normal cooldown value will be applied.
+ - rscadd: Reflective Shield, this ability will lock your looking direction when
+ casted to reflect bullets towards target in 50 degree cone (-25 to 25 angle),
+ reflected bullets will only deal half of their original damage, your reflection
+ chance is 100% from front ONLY but you lose ability to attack or help (you can
+ still use abilities) and drop pulled targets, this ability can be disabled early
+ like how lurker invisibility works 1s used is 2s cooldown but you gain +6s to
+ cooldown, minimum 6s cooldown and 18s max, this ability CANNOT reflects sniper
+ bullets, rockets and anti-reflective ammo (like SHARP darts), xeno projectiles
+ and turrets.
+ - balance: Fix Reflective walls, now they reflect bullets properly (uses new bulwark
+ reflective proc).
+ cuberound:
+ - qol: xenos can see reamining durability of special structures on examine
+ - code_imp: isbanana is no longer
+ hry-gh:
+ - server: twitch linking functionality is deprecated in game
+ realforest2001:
+ - rscadd: Added Abomination pelt and skull items.
+ - code_imp: Added code to delete instances of skull and pelt that have no icon.
+ - rscadd: Almayer crash no longer breaks hardened APCs.
+ - code_imp: Changed some queen procs to xenomorph base.
+ - rscadd: Changed Queen naming convention (thought I did this ages ago)
+ - code_imp: Changed the tier_slot_multiplier var name to divisor because it was
+ stupidly named.
+ virtualgirlie:
+ - spellcheck: changed "enemy boiler's" to "enemy boilers"
diff --git a/icons/mob/hud/actions_xeno.dmi b/icons/mob/hud/actions_xeno.dmi
index 4409d45a1470..1bb981218850 100644
Binary files a/icons/mob/hud/actions_xeno.dmi and b/icons/mob/hud/actions_xeno.dmi differ
diff --git a/icons/mob/hud/hud.dmi b/icons/mob/hud/hud.dmi
index bde26859a159..6f909a4d05ec 100644
Binary files a/icons/mob/hud/hud.dmi and b/icons/mob/hud/hud.dmi differ
diff --git a/icons/mob/xenos/castes/tier_2/warrior.dmi b/icons/mob/xenos/castes/tier_2/warrior.dmi
index af3000a3dd5a..189ebac3e1ab 100644
Binary files a/icons/mob/xenos/castes/tier_2/warrior.dmi and b/icons/mob/xenos/castes/tier_2/warrior.dmi differ
diff --git a/icons/obj/items/hunter/prey_items.dmi b/icons/obj/items/hunter/prey_items.dmi
index 654120767a8f..da22076cd39d 100644
Binary files a/icons/obj/items/hunter/prey_items.dmi and b/icons/obj/items/hunter/prey_items.dmi differ
diff --git a/sound/effects/grenade_hit.ogg b/sound/effects/grenade_hit.ogg
new file mode 100644
index 000000000000..f6a4afd789b9
Binary files /dev/null and b/sound/effects/grenade_hit.ogg differ
diff --git a/sound/effects/tail_swing.ogg b/sound/effects/tail_swing.ogg
new file mode 100644
index 000000000000..36912acfe647
Binary files /dev/null and b/sound/effects/tail_swing.ogg differ
diff --git a/sound/voice/joe/disturbance_haz.ogg b/sound/voice/joe/disturbance_haz.ogg
index cfe4110d5dfc..366cd7d6d537 100644
Binary files a/sound/voice/joe/disturbance_haz.ogg and b/sound/voice/joe/disturbance_haz.ogg differ