diff --git a/.github/workflows/conflicts.yml b/.github/workflows/conflicts.yml index 02d9da40342b..b65a5213ab4a 100644 --- a/.github/workflows/conflicts.yml +++ b/.github/workflows/conflicts.yml @@ -12,4 +12,6 @@ jobs: - uses: eps1lon/actions-label-merge-conflict@v2.1.0 with: dirtyLabel: 'Merge Conflict' + commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." + commentOnClean: "Conflicts have been resolved. A maintainer will review the pull request shortly." repoToken: ${{ secrets.BOT_TOKEN_CM || secrets.GITHUB_TOKEN }} diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 60c4116df330..31df07648fc0 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -20,3 +20,18 @@ #define DEFAULT_MESSAGE_RANGE 7 #define BAYONET_DRAW_DELAY (1 SECONDS) + +//Predator decloak multpliers based on the standard. +#define DECLOAK_STANDARD (10 SECONDS) +/// Forced for any unspecified reason. +#define DECLOAK_FORCED 1 +/// Caused by being worn by non humans. +#define DECLOAK_SPECIES 0.75 +/// Caused by fire extinguisher. +#define DECLOAK_EXTINGUISHER 1.5 +/// Caused by predalien screech. +#define DECLOAK_PREDALIEN 2 +/// Caused by being in a body of water. +#define DECLOAK_SUBMERGED 2 +/// Caused by an EMP. +#define DECLOAK_EMP 3 diff --git a/code/__DEFINES/equipment.dm b/code/__DEFINES/equipment.dm index 4212f6a52301..461eae27a2a3 100644 --- a/code/__DEFINES/equipment.dm +++ b/code/__DEFINES/equipment.dm @@ -82,7 +82,6 @@ #define NOTABLEMERGE (1<<13) /// Has heat source but isn't 'on fire' and thus can be stored #define IGNITING_ITEM (1<<14) - //========================================================================================== diff --git a/code/__DEFINES/keybinding.dm b/code/__DEFINES/keybinding.dm index 422edb05508b..1878ca63f34e 100644 --- a/code/__DEFINES/keybinding.dm +++ b/code/__DEFINES/keybinding.dm @@ -189,6 +189,7 @@ #define COMSIG_KB_OBSERVER_JOIN_XENO "keybinding_observer_join_as_xeno" #define COMSIG_KB_OBSERVER_JOIN_ERT "keybinding_observer_join_ert" #define COMSIG_KB_OBSERVER_JOIN_PREDATOR "keybinding_observer_join_pred" +#define COMSIG_KB_OBSERVER_JOIN_LESSER_DRONE "keybinding_observer_join_lesser_drone" #define CATEGORY_CLIENT "CLIENT" #define CATEGORY_EMOTE "EMOTE" diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index bed2ceeced7d..4464a0b16a1d 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -284,6 +284,8 @@ #define COOLDOWN_COMM_CENTRAL 30 SECONDS #define COOLDOWN_COMM_DESTRUCT 5 MINUTES +///Cooldown for pred recharge +#define COOLDOWN_BRACER_CHARGE 3 MINUTES // magic value to use for indicating a proc slept #define PROC_RETURN_SLEEP -1 diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index ba928e202cf0..673bb4fc6d81 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -221,6 +221,7 @@ //Mob sizes #define MOB_SIZE_SMALL 0 #define MOB_SIZE_HUMAN 1 +#define MOB_SIZE_XENO_VERY_SMALL 1.5 #define MOB_SIZE_XENO_SMALL 2 #define MOB_SIZE_XENO 3 #define MOB_SIZE_BIG 4 diff --git a/code/__DEFINES/typecheck/humanoids.dm b/code/__DEFINES/typecheck/humanoids.dm index 58d245c486e4..7076cf67c95c 100644 --- a/code/__DEFINES/typecheck/humanoids.dm +++ b/code/__DEFINES/typecheck/humanoids.dm @@ -25,7 +25,7 @@ #define isspeciessynth(A) (A.species?.group == SPECIES_SYNTHETIC) //Size checks for carbon to use instead of typechecks. (Hellhounds are deprecated) -#define iscarbonsizexeno(A) (A.mob_size >= MOB_SIZE_XENO_SMALL) +#define iscarbonsizexeno(A) (A.mob_size >= MOB_SIZE_XENO_VERY_SMALL) #define iscarbonsizehuman(A) (A.mob_size <= MOB_SIZE_HUMAN) //job/role helpers diff --git a/code/__DEFINES/typecheck/xenos.dm b/code/__DEFINES/typecheck/xenos.dm index 4d1b7819bdf1..d313090e8305 100644 --- a/code/__DEFINES/typecheck/xenos.dm +++ b/code/__DEFINES/typecheck/xenos.dm @@ -14,6 +14,7 @@ #define islarva(A) (istype(A, /mob/living/carbon/xenomorph/larva)) #define ispredalienlarva(A) (istype(A, /mob/living/carbon/xenomorph/larva/predalien)) #define isfacehugger(A) (istype(A, /mob/living/carbon/xenomorph/facehugger)) +#define islesserdrone(A) (istype(A, /mob/living/carbon/xenomorph/lesser_drone)) #define ispraetorian(A) (istype(A, /mob/living/carbon/xenomorph/praetorian)) #define isqueen(A) (istype(A, /mob/living/carbon/xenomorph/queen)) #define isravager(A) (istype(A, /mob/living/carbon/xenomorph/ravager)) diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm index ac783b6f426e..82237cd793b2 100644 --- a/code/__DEFINES/xeno.dm +++ b/code/__DEFINES/xeno.dm @@ -70,6 +70,7 @@ #define HUD_ARMOR_STATES_XENO 10 /// Multiplier for time taken for a xeno to place down a resin structure +#define BUILD_TIME_MULT_LESSER_DRONE 2 #define BUILD_TIME_MULT_XENO 1 #define BUILD_TIME_MULT_BUILDER 1 #define BUILD_TIME_MULT_HIVELORD 0.5 @@ -206,6 +207,7 @@ // Health bands #define XENO_HEALTH_LARVA 35 * XENO_UNIVERSAL_HPMULT +#define XENO_HEALTH_LESSER_DRONE 160 * XENO_UNIVERSAL_HPMULT #define XENO_HEALTH_RUNNER 230 * XENO_UNIVERSAL_HPMULT // Killed by 1 PB #define XENO_HEALTH_TIER_1 250 * XENO_UNIVERSAL_HPMULT #define XENO_HEALTH_TIER_2 300 * XENO_UNIVERSAL_HPMULT @@ -603,7 +605,8 @@ #define XENO_CASTE_LARVA "Bloody Larva" #define XENO_CASTE_PREDALIEN_LARVA "Predalien Larva" #define XENO_CASTE_FACEHUGGER "Facehugger" -#define XENO_T0_CASTES list(XENO_CASTE_LARVA, XENO_CASTE_PREDALIEN_LARVA, XENO_CASTE_FACEHUGGER) +#define XENO_CASTE_LESSER_DRONE "Lesser Drone" +#define XENO_T0_CASTES list(XENO_CASTE_LARVA, XENO_CASTE_PREDALIEN_LARVA, XENO_CASTE_FACEHUGGER, XENO_CASTE_LESSER_DRONE) //t1 #define XENO_CASTE_DRONE "Drone" @@ -631,7 +634,7 @@ #define XENO_CASTE_HELLHOUND "Hellhound" #define XENO_SPECIAL_CASTES list(XENO_CASTE_QUEEN, XENO_CASTE_PREDALIEN, XENO_CASTE_HELLHOUND) -#define ALL_XENO_CASTES list(XENO_CASTE_LARVA, XENO_CASTE_PREDALIEN_LARVA, XENO_CASTE_FACEHUGGER, XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_SENTINEL, XENO_CASTE_DEFENDER, XENO_CASTE_BURROWER, XENO_CASTE_CARRIER, XENO_CASTE_HIVELORD, XENO_CASTE_LURKER, XENO_CASTE_WARRIOR, XENO_CASTE_SPITTER, XENO_CASTE_BOILER, XENO_CASTE_PRAETORIAN, XENO_CASTE_CRUSHER, XENO_CASTE_RAVAGER, XENO_CASTE_QUEEN, XENO_CASTE_PREDALIEN, XENO_CASTE_HELLHOUND) +#define ALL_XENO_CASTES list(XENO_CASTE_LARVA, XENO_CASTE_PREDALIEN_LARVA, XENO_CASTE_FACEHUGGER, XENO_CASTE_LESSER_DRONE, XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_SENTINEL, XENO_CASTE_DEFENDER, XENO_CASTE_BURROWER, XENO_CASTE_CARRIER, XENO_CASTE_HIVELORD, XENO_CASTE_LURKER, XENO_CASTE_WARRIOR, XENO_CASTE_SPITTER, XENO_CASTE_BOILER, XENO_CASTE_PRAETORIAN, XENO_CASTE_CRUSHER, XENO_CASTE_RAVAGER, XENO_CASTE_QUEEN, XENO_CASTE_PREDALIEN, XENO_CASTE_HELLHOUND) // Checks if two hives are allied to each other. // PARAMETERS: @@ -695,3 +698,6 @@ #define TAILSTAB_AIRLOCK_DAMAGE_MULTIPLIER 2 #define FRENZY_DAMAGE_MULTIPLIER 2 + +#define JOIN_AS_FACEHUGGER_DELAY (3 MINUTES) +#define JOIN_AS_LESSER_DRONE_DELAY (30 SECONDS) diff --git a/code/_globalvars/global_lists.dm b/code/_globalvars/global_lists.dm index 586d5e71a92d..7e65cfecd8b0 100644 --- a/code/_globalvars/global_lists.dm +++ b/code/_globalvars/global_lists.dm @@ -50,6 +50,12 @@ GLOBAL_LIST_EMPTY(mainship_pipes) // Resin constructions parameters GLOBAL_LIST_INIT_TYPED(resin_constructions_list, /datum/resin_construction, setup_resin_constructions()) +GLOBAL_LIST_INIT(resin_build_order_lesser_drone, list( + /datum/resin_construction/resin_turf/wall, + /datum/resin_construction/resin_turf/membrane, + /datum/resin_construction/resin_obj/door, +)) + GLOBAL_LIST_INIT(resin_build_order_drone, list( /datum/resin_construction/resin_turf/wall, /datum/resin_construction/resin_turf/membrane, diff --git a/code/game/gamemodes/cm_initialize.dm b/code/game/gamemodes/cm_initialize.dm index a42ff3f22e59..a7e8ab612bb4 100644 --- a/code/game/gamemodes/cm_initialize.dm +++ b/code/game/gamemodes/cm_initialize.dm @@ -548,6 +548,38 @@ Additional game mode variables. return TRUE +/datum/game_mode/proc/attempt_to_join_as_lesser_drone(mob/xeno_candidate) + var/list/active_hives = list() + var/datum/hive_status/hive + var/last_active_hive = 0 + for(var/hivenumber in GLOB.hive_datum) + hive = GLOB.hive_datum[hivenumber] + if(hive.totalXenos.len <= 0) + continue + active_hives[hive.name] = hive.hivenumber + last_active_hive = hive.hivenumber + + if(active_hives.len <= 0) + to_chat(xeno_candidate, SPAN_WARNING("There aren't any Hives active at this point for you to join.")) + return FALSE + + if(active_hives.len > 1) + var/hive_picked = tgui_input_list(xeno_candidate, "Select which Hive to attempt joining.", "Hive Choice", active_hives, theme="hive_status") + if(!hive_picked) + to_chat(xeno_candidate, SPAN_ALERT("Hive choice error. Aborting.")) + return + hive = GLOB.hive_datum[active_hives[hive_picked]] + else + hive = GLOB.hive_datum[last_active_hive] + + if(!hive.hive_location) + to_chat(xeno_candidate, SPAN_WARNING("The selected hive does not have a hive core to spawn from!")) + return + + hive.hive_location.spawn_lesser_drone(xeno_candidate) + + return TRUE + /datum/game_mode/proc/transfer_xeno(xeno_candidate, mob/living/new_xeno) if(!xeno_candidate || !isxeno(new_xeno) || QDELETED(new_xeno)) return FALSE diff --git a/code/game/jobs/role_authority.dm b/code/game/jobs/role_authority.dm index b909c38cd9e6..e7697d54f0de 100644 --- a/code/game/jobs/role_authority.dm +++ b/code/game/jobs/role_authority.dm @@ -762,6 +762,8 @@ I hope it's easier to tell what the heck this proc is even doing, unlike previou M = /mob/living/carbon/xenomorph/larva/predalien if(XENO_CASTE_FACEHUGGER) M = /mob/living/carbon/xenomorph/facehugger + if(XENO_CASTE_LESSER_DRONE) + M = /mob/living/carbon/xenomorph/lesser_drone if(XENO_CASTE_RUNNER) M = /mob/living/carbon/xenomorph/runner if(XENO_CASTE_DRONE) diff --git a/code/game/machinery/vending/vendor_types/requisitions.dm b/code/game/machinery/vending/vendor_types/requisitions.dm index b8e3e55f1d89..f85657e887a8 100644 --- a/code/game/machinery/vending/vendor_types/requisitions.dm +++ b/code/game/machinery/vending/vendor_types/requisitions.dm @@ -92,26 +92,27 @@ list("POUCHES", -1, null, null), list("Autoinjector Pouch", round(scale * 2), /obj/item/storage/pouch/autoinjector, VENDOR_ITEM_REGULAR), - list("Bayonet Pouch", round(scale * 2), /obj/item/storage/pouch/bayonet, VENDOR_ITEM_REGULAR), + list("Medkit Pouch", round(scale * 2), /obj/item/storage/pouch/medkit, VENDOR_ITEM_REGULAR), + list("Medical Pouch", round(scale * 2), /obj/item/storage/pouch/medical, VENDOR_ITEM_REGULAR), + list("First-Aid Pouch (Full)", round(scale * 5), /obj/item/storage/pouch/firstaid/full, VENDOR_ITEM_REGULAR), + list("First Responder Pouch", round(scale * 2), /obj/item/storage/pouch/first_responder, VENDOR_ITEM_REGULAR), + list("Syringe Pouch", round(scale * 2), /obj/item/storage/pouch/syringe, VENDOR_ITEM_REGULAR), + list("Tools Pouch (Full)", round(scale * 2), /obj/item/storage/pouch/tools/full, VENDOR_ITEM_REGULAR), list("Construction Pouch", round(scale * 2), /obj/item/storage/pouch/construction, VENDOR_ITEM_REGULAR), - list("Document Pouch", round(scale * 2), /obj/item/storage/pouch/document/small, VENDOR_ITEM_REGULAR), list("Electronics Pouch", round(scale * 2), /obj/item/storage/pouch/electronics, VENDOR_ITEM_REGULAR), list("Explosive Pouch", round(scale * 2), /obj/item/storage/pouch/explosive, VENDOR_ITEM_REGULAR), - list("First-Aid Pouch (Full)", round(scale * 5), /obj/item/storage/pouch/firstaid/full, VENDOR_ITEM_REGULAR), - list("First Responder Pouch", round(scale * 2), /obj/item/storage/pouch/first_responder, VENDOR_ITEM_REGULAR), list("Flare Pouch (Full)", round(scale * 5), /obj/item/storage/pouch/flare/full, VENDOR_ITEM_REGULAR), - list("Fuel Tank Strap Pouch", round(scale * 4), /obj/item/storage/pouch/flamertank, VENDOR_ITEM_REGULAR), - list("Large Pistol Magazine Pouch", round(scale * 5), /obj/item/storage/pouch/magazine/pistol/large, VENDOR_ITEM_REGULAR), - list("Magazine Pouch", round(scale * 5), /obj/item/storage/pouch/magazine, VENDOR_ITEM_REGULAR), - list("Shotgun Shell Pouch", round(scale * 5), /obj/item/storage/pouch/shotgun, VENDOR_ITEM_REGULAR), + list("Document Pouch", round(scale * 2), /obj/item/storage/pouch/document/small, VENDOR_ITEM_REGULAR), + list("Sling Pouch", round(scale * 2), /obj/item/storage/pouch/sling, VENDOR_ITEM_REGULAR), list("Machete Pouch (Full)", round(scale * 4), /obj/item/storage/pouch/machete/full, VENDOR_ITEM_REGULAR), - list("Medical Pouch", round(scale * 2), /obj/item/storage/pouch/medical, VENDOR_ITEM_REGULAR), + list("Bayonet Pouch", round(scale * 2), /obj/item/storage/pouch/bayonet, VENDOR_ITEM_REGULAR), list("Medium General Pouch", round(scale * 2), /obj/item/storage/pouch/general/medium, VENDOR_ITEM_REGULAR), - list("Medkit Pouch", round(scale * 2), /obj/item/storage/pouch/medkit, VENDOR_ITEM_REGULAR), + list("Magazine Pouch", round(scale * 5), /obj/item/storage/pouch/magazine, VENDOR_ITEM_REGULAR), + list("Shotgun Shell Pouch", round(scale * 5), /obj/item/storage/pouch/shotgun, VENDOR_ITEM_REGULAR), list("Sidearm Pouch", round(scale * 5), /obj/item/storage/pouch/pistol, VENDOR_ITEM_REGULAR), - list("Syringe Pouch", round(scale * 2), /obj/item/storage/pouch/syringe, VENDOR_ITEM_REGULAR), - list("Tools Pouch (Full)", round(scale * 2), /obj/item/storage/pouch/tools/full, VENDOR_ITEM_REGULAR), - list("Sling Pouch", round(scale * 2), /obj/item/storage/pouch/sling, VENDOR_ITEM_REGULAR), + list("Large Pistol Magazine Pouch", round(scale * 5), /obj/item/storage/pouch/magazine/pistol/large, VENDOR_ITEM_REGULAR), + list("Fuel Tank Strap Pouch", round(scale * 4), /obj/item/storage/pouch/flamertank, VENDOR_ITEM_REGULAR), + list("Large General Pouch", round(scale * 2), /obj/item/storage/pouch/general/large, VENDOR_ITEM_REGULAR), list("Large Magazine Pouch", round(scale * 2), /obj/item/storage/pouch/magazine/large, VENDOR_ITEM_REGULAR), list("Large Shotgun Shell Pouch", round(scale * 2), /obj/item/storage/pouch/shotgun/large, VENDOR_ITEM_REGULAR), diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index 55f3c32cebba..3b84d2433e88 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -122,25 +122,27 @@ /obj/structure/closet/bodybag/store_mobs(stored_units) // overriding this var/list/dead_mobs = list() - for(var/mob/living/M in loc) - if(M.buckled) + for(var/mob/living/mob in loc) + if(mob.buckled) continue - if(M.stat != DEAD) // covers alive mobs + if(mob.stat != DEAD) // covers alive mobs continue - if(!ishuman(M)) // all the dead other shit - dead_mobs += M + if(!ishuman(mob)) // all the dead other shit + dead_mobs += mob continue - var/mob/living/carbon/human/H = M - if(H.check_tod() || issynth(H) || H.is_revivable() && H.get_ghost()) // revivable + var/mob/living/carbon/human/human = mob + if(issynth(human)) continue - dead_mobs += M + if(human.check_tod() && human.is_revivable()) // revivable + continue + dead_mobs += mob var/mob/living/mob_to_store if(dead_mobs.len) mob_to_store = pick(dead_mobs) mob_to_store.forceMove(src) stored_units += mob_size - for(var/obj/item/limb/L in loc) - L.forceMove(src) + for(var/obj/item/limb/limb in loc) + limb.forceMove(src) return stored_units /obj/structure/closet/bodybag/attack_hand(mob/living/user) diff --git a/code/game/objects/items/explosives/plastic.dm b/code/game/objects/items/explosives/plastic.dm index 4c2a1774d4db..830df9659070 100644 --- a/code/game/objects/items/explosives/plastic.dm +++ b/code/game/objects/items/explosives/plastic.dm @@ -213,23 +213,6 @@ return TRUE -/obj/item/explosive/plastic/breaching_charge/can_place(mob/user, atom/target) - if(!is_type_in_list(target, breachable))//only items on the list are allowed - to_chat(user, SPAN_WARNING("You cannot plant \the [name] on \the [target]!")) - return FALSE - - if(SSinterior.in_interior(target))// vehicle checks again JUST IN CASE - to_chat(user, SPAN_WARNING("It's too cramped in here to deploy \the [src].")) - return FALSE - - if(istype(target, /obj/structure/window))//no breaching charges on the briefing windows / brig / CIC e.e - var/obj/structure/window/W = target - if(W.not_damageable) - to_chat(user, SPAN_WARNING("[W] is much too tough for you to do anything to it with [src].")) //On purpose to mimic wall message - return FALSE - - return TRUE - /obj/item/explosive/plastic/proc/calculate_pixel_offset(mob/user, atom/target) switch(get_dir(user, target)) if(NORTH) @@ -312,13 +295,6 @@ cell_explosion(target_turf, 120, 30, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, cause_data) qdel(src) -/obj/item/explosive/plastic/breaching_charge/handle_explosion(turf/target_turf, dir, cause_data) - var/explosion_target = get_step(target_turf, dir) - create_shrapnel(explosion_target, 40, dir, angle,/datum/ammo/bullet/shrapnel/metal, cause_data) - sleep(1)// prevents explosion from eating shrapnel - cell_explosion(target_turf, 60, 60, EXPLOSION_FALLOFF_SHAPE_EXPONENTIAL, dir, cause_data) - qdel(src) - /obj/item/explosive/plastic/proc/delayed_prime(turf/target_turf) prime(TRUE) @@ -342,3 +318,65 @@ min_timer = 3 penetration = 0.60 deploying_time = 10 + var/shrapnel_volume = 40 + +/obj/item/explosive/plastic/breaching_charge/can_place(mob/user, atom/target) + if(!is_type_in_list(target, breachable))//only items on the list are allowed + to_chat(user, SPAN_WARNING("You cannot plant [name] on [target]!")) + return FALSE + + if(SSinterior.in_interior(target))// vehicle checks again JUST IN CASE + to_chat(user, SPAN_WARNING("It's too cramped in here to deploy [src].")) + return FALSE + + if(istype(target, /obj/structure/window))//no breaching charges on the briefing windows / brig / CIC e.e + var/obj/structure/window/window = target + if(window.not_damageable) + to_chat(user, SPAN_WARNING("[window] is much too tough for you to do anything to it with [src].")) //On purpose to mimic wall message + return FALSE + + if(istype(target, /turf/closed/wall)) + var/turf/closed/wall/targeted_wall = target + if(targeted_wall.hull) + to_chat(user, SPAN_WARNING("You are unable to stick [src] to [targeted_wall]!")) + return FALSE + + return TRUE + +/obj/item/explosive/plastic/breaching_charge/handle_explosion(turf/target_turf, dir, cause_data) + var/explosion_target = get_step(target_turf, dir) + create_shrapnel(explosion_target, shrapnel_volume, dir, angle,/datum/ammo/bullet/shrapnel/metal, cause_data) + addtimer(CALLBACK(src, PROC_REF(trigger_explosion), target_turf, dir, cause_data), 1) + +/obj/item/explosive/plastic/breaching_charge/proc/trigger_explosion(turf/target_turf, dir, cause_data) + cell_explosion(target_turf, 60, 60, EXPLOSION_FALLOFF_SHAPE_EXPONENTIAL, dir, cause_data) + qdel(src) + +/obj/item/explosive/plastic/breaching_charge/plasma + name = "plasma charge" + desc = "An alien explosive device. Who knows what it might do." + icon_state = "plasma-charge" + overlay_image = "plasma-active" + w_class = SIZE_SMALL + angle = 55 + timer = 5 + min_timer = 5 + penetration = 0.60 + deploying_time = 10 + flags_item = NOBLUDGEON|ITEM_PREDATOR + shrapnel_volume = 10 + +/obj/item/explosive/plastic/breaching_charge/plasma/can_place(mob/user, atom/target) + if(!HAS_TRAIT(user, TRAIT_YAUTJA_TECH)) + to_chat(user, SPAN_WARNING("You don't quite understand how the device works...")) + return FALSE + . = ..() + +/obj/item/explosive/plastic/breaching_charge/plasma/handle_explosion(turf/target_turf, dir, cause_data) + var/explosion_target = get_step(target_turf, dir) + create_shrapnel(explosion_target, shrapnel_volume, dir, angle,/datum/ammo/bullet/shrapnel/plasma, cause_data) + addtimer(CALLBACK(src, PROC_REF(trigger_explosion), target_turf, dir, cause_data), 1) + +/obj/item/explosive/plastic/breaching_charge/plasma/trigger_explosion(turf/target_turf, dir, cause_data) + cell_explosion(target_turf, 90, 90, EXPLOSION_FALLOFF_SHAPE_EXPONENTIAL, dir, cause_data) + qdel(src) diff --git a/code/game/objects/items/storage/pouch.dm b/code/game/objects/items/storage/pouch.dm index 7a49f48cdc92..dc3ee0ba1506 100644 --- a/code/game/objects/items/storage/pouch.dm +++ b/code/game/objects/items/storage/pouch.dm @@ -58,29 +58,12 @@ max_w_class = SIZE_MEDIUM cant_hold = list( //Prevent inventory bloat /obj/item/storage/firstaid, - /obj/item/storage/bible + /obj/item/storage/bible, + /obj/item/storage/box, ) storage_slots = null max_storage_space = 2 -/obj/item/storage/pouch/general/attackby(obj/item/W, mob/user) - if(istype(W, /obj/item/ammo_magazine/shotgun)) - var/obj/item/ammo_magazine/shotgun/M = W - dump_ammo_to(M,user) - else if(istype(W, /obj/item/storage/box/nade_box) || istype(W, /obj/item/storage/box/m94)) - dump_into(W, user) - else - return ..() - -/obj/item/storage/pouch/general/can_be_inserted(obj/item/W, stop_messages) - . = ..() - if(. && W.w_class == SIZE_MEDIUM) - for(var/obj/item/I in return_inv()) - if(I.w_class >= SIZE_MEDIUM) - if(!stop_messages) - to_chat(usr, SPAN_NOTICE("[src] is already too bulky with [I].")) - return FALSE - /obj/item/storage/pouch/general/medium name = "medium general pouch" desc = "A general-purpose pouch used to carry a variety of differently sized items." @@ -468,13 +451,6 @@ for(var/i = 1 to storage_slots) new /obj/item/ammo_magazine/pistol/vp78(src) -/obj/item/storage/pouch/magazine/shotgun/attackby(obj/item/W, mob/living/user) - if(istype(W, /obj/item/ammo_magazine/shotgun)) - var/obj/item/ammo_magazine/shotgun/M = W - dump_ammo_to(M, user, M.transfer_handful_amount) - else - return ..() - /obj/item/storage/pouch/magazine/pulse_rifle/fill_preset_inventory() for(var/i = 1 to storage_slots) new /obj/item/ammo_magazine/rifle(src) diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm index 3f7192b0090b..41ac80bfdc58 100644 --- a/code/game/turfs/open.dm +++ b/code/game/turfs/open.dm @@ -517,7 +517,7 @@ var/obj/item/clothing/gloves/yautja/hunter/Y = H.gloves if(Y && istype(Y) && Y.cloaked) to_chat(H, SPAN_WARNING(" Your bracers hiss and spark as they short out!")) - Y.decloak(H, TRUE) + Y.decloak(H, TRUE, DECLOAK_SUBMERGED) else if(isxeno(C)) river_slowdown -= 0.7 diff --git a/code/modules/admin/player_panel/actions/transform.dm b/code/modules/admin/player_panel/actions/transform.dm index 11dd7525bb07..185165357e05 100644 --- a/code/modules/admin/player_panel/actions/transform.dm +++ b/code/modules/admin/player_panel/actions/transform.dm @@ -38,6 +38,11 @@ GLOBAL_LIST_INIT(pp_transformables, list( name = "Facehugger", key = /mob/living/carbon/xenomorph/facehugger, color = "purple" + ), + list( + name = "Lesser Drone", + key = /mob/living/carbon/xenomorph/lesser_drone, + color = "purple" ) ), diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 5698c30c0acf..16afa8d1b4f2 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -590,7 +590,7 @@ var/const/MAX_SAVE_SLOTS = 10 dat += "
" dat += "

Gameplay Toggles:

" dat += "Toggle Being Able to Hurt Yourself: \ - [toggle_prefs & TOGGLE_IGNORE_SELF ? "On" : "Off"]
" + [toggle_prefs & TOGGLE_IGNORE_SELF ? "Off" : "On"]
" dat += "Toggle Help Intent Safety: \ [toggle_prefs & TOGGLE_HELP_INTENT_SAFETY ? "On" : "Off"]
" dat += "Toggle Middle Mouse Ability Activation: \ diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 0a1b54112f18..0f482fa7f894 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -199,22 +199,22 @@ lastchangelog = sanitize_text(lastchangelog, initial(lastchangelog)) UI_style = sanitize_inlist(UI_style, list("white", "dark", "midnight", "orange", "old"), initial(UI_style)) tgui_say = sanitize_integer(tgui_say, FALSE, TRUE, TRUE) - be_special = sanitize_integer(be_special, 0, 65535, initial(be_special)) + be_special = sanitize_integer(be_special, 0, SHORT_REAL_LIMIT, initial(be_special)) default_slot = sanitize_integer(default_slot, 1, MAX_SAVE_SLOTS, initial(default_slot)) - toggles_chat = sanitize_integer(toggles_chat, 0, 65535, initial(toggles_chat)) - chat_display_preferences = sanitize_integer(chat_display_preferences, 0, 65535, initial(chat_display_preferences)) - toggles_ghost = sanitize_integer(toggles_ghost, 0, 65535, initial(toggles_ghost)) - toggles_langchat = sanitize_integer(toggles_langchat, 0, 65535, initial(toggles_langchat)) - toggles_sound = sanitize_integer(toggles_sound, 0, 65535, initial(toggles_sound)) - toggle_prefs = sanitize_integer(toggle_prefs, 0, 65535, initial(toggle_prefs)) - toggles_flashing= sanitize_integer(toggles_flashing, 0, 65535, initial(toggles_flashing)) - toggles_ert = sanitize_integer(toggles_ert, 0, 65535, initial(toggles_ert)) - toggles_admin = sanitize_integer(toggles_admin, 0, 65535, initial(toggles_admin)) + toggles_chat = sanitize_integer(toggles_chat, 0, SHORT_REAL_LIMIT, initial(toggles_chat)) + chat_display_preferences = sanitize_integer(chat_display_preferences, 0, SHORT_REAL_LIMIT, initial(chat_display_preferences)) + toggles_ghost = sanitize_integer(toggles_ghost, 0, SHORT_REAL_LIMIT, initial(toggles_ghost)) + toggles_langchat = sanitize_integer(toggles_langchat, 0, SHORT_REAL_LIMIT, initial(toggles_langchat)) + toggles_sound = sanitize_integer(toggles_sound, 0, SHORT_REAL_LIMIT, initial(toggles_sound)) + toggle_prefs = sanitize_integer(toggle_prefs, 0, SHORT_REAL_LIMIT, initial(toggle_prefs)) + toggles_flashing= sanitize_integer(toggles_flashing, 0, SHORT_REAL_LIMIT, initial(toggles_flashing)) + toggles_ert = sanitize_integer(toggles_ert, 0, SHORT_REAL_LIMIT, initial(toggles_ert)) + toggles_admin = sanitize_integer(toggles_admin, 0, SHORT_REAL_LIMIT, initial(toggles_admin)) UI_style_color = sanitize_hexcolor(UI_style_color, initial(UI_style_color)) UI_style_alpha = sanitize_integer(UI_style_alpha, 0, 255, initial(UI_style_alpha)) item_animation_pref_level = sanitize_integer(item_animation_pref_level, SHOW_ITEM_ANIMATIONS_NONE, SHOW_ITEM_ANIMATIONS_ALL, SHOW_ITEM_ANIMATIONS_ALL) pain_overlay_pref_level = sanitize_integer(pain_overlay_pref_level, PAIN_OVERLAY_BLURRY, PAIN_OVERLAY_LEGACY, PAIN_OVERLAY_BLURRY) - window_skin = sanitize_integer(window_skin, 0, 65535, initial(window_skin)) + window_skin = sanitize_integer(window_skin, 0, SHORT_REAL_LIMIT, initial(window_skin)) ghost_vision_pref = sanitize_inlist(ghost_vision_pref, list(GHOST_VISION_LEVEL_NO_NVG, GHOST_VISION_LEVEL_MID_NVG, GHOST_VISION_LEVEL_FULL_NVG), GHOST_VISION_LEVEL_MID_NVG) ghost_orbit = sanitize_inlist(ghost_orbit, GLOB.ghost_orbits, initial(ghost_orbit)) playtime_perks = sanitize_integer(playtime_perks, 0, 1, 1) diff --git a/code/modules/cm_aliens/structures/special/pylon_core.dm b/code/modules/cm_aliens/structures/special/pylon_core.dm index a29b49b7745a..993d4f833fa6 100644 --- a/code/modules/cm_aliens/structures/special/pylon_core.dm +++ b/code/modules/cm_aliens/structures/special/pylon_core.dm @@ -327,5 +327,22 @@ // Tell admins that this condition is reached so they know what has happened if it fails somehow return +/obj/effect/alien/resin/special/pylon/core/proc/spawn_lesser_drone(mob/xeno_candidate) + if(!linked_hive.can_spawn_as_lesser_drone(xeno_candidate)) + return FALSE + + var/mob/living/carbon/xenomorph/lesser_drone/new_drone = new /mob/living/carbon/xenomorph/lesser_drone(loc, null, linked_hive.hivenumber) + xeno_candidate.mind.transfer_to(new_drone, TRUE) + new_drone.visible_message(SPAN_XENODANGER("A lesser drone emerges out of [src]!"), SPAN_XENODANGER("You emerge out of [src] and awaken from your slumber. For the Hive!")) + playsound(new_drone, 'sound/effects/xeno_newlarva.ogg', 25, TRUE) + new_drone.generate_name() + + return TRUE + +/obj/effect/alien/resin/special/pylon/core/attack_ghost(mob/dead/observer/user) + . = ..() + if(SSticker.mode.check_xeno_late_join(user)) + SSticker.mode.attempt_to_join_as_lesser_drone(user) + #undef PYLON_REPAIR_TIME #undef PYLON_WEEDS_REGROWTH_TIME diff --git a/code/modules/cm_preds/yaut_bracers.dm b/code/modules/cm_preds/yaut_bracers.dm index 5c4079b2be23..9642b4b9f5a5 100644 --- a/code/modules/cm_preds/yaut_bracers.dm +++ b/code/modules/cm_preds/yaut_bracers.dm @@ -31,6 +31,10 @@ var/notification_sound = TRUE // Whether the bracer pings when a message comes or not var/charge = 1500 var/charge_max = 1500 + /// The amount charged per process + var/charge_rate = 30 + /// Cooldown on draining power from APC + var/charge_cooldown = COOLDOWN_BRACER_CHARGE var/cloaked = 0 var/cloak_timer = 0 var/cloak_malfunction = 0 @@ -41,6 +45,7 @@ var/mob/living/carbon/human/owner //Pred spawned on, or thrall given to. var/obj/item/clothing/gloves/yautja/linked_bracer //Bracer linked to this one (thrall or mentor). + COOLDOWN_DECLARE(bracer_recharge) /obj/item/clothing/gloves/yautja/equipped(mob/user, slot) . = ..() @@ -75,11 +80,27 @@ if(!ishuman(loc)) STOP_PROCESSING(SSobj, src) return - var/mob/living/carbon/human/H = loc + var/mob/living/carbon/human/human_holder = loc - charge = min(charge + 30, charge_max) - var/perc_charge = (charge / charge_max * 100) - H.update_power_display(perc_charge) + if(charge < charge_max) + var/charge_increase = charge_rate + if(is_ground_level(human_holder.z)) + charge_increase = charge_rate / 6 + else if(is_mainship_level(human_holder.z)) + charge_increase = charge_rate / 3 + + charge = min(charge + charge_increase, charge_max) + var/perc_charge = (charge / charge_max * 100) + human_holder.update_power_display(perc_charge) + + //Non-Yautja have a chance to get stunned with each power drain + if(!cloaked) + return + if(human_holder.stat == DEAD) + decloak(human_holder, TRUE) + if(!HAS_TRAIT(human_holder, TRAIT_YAUTJA_TECH) && !human_holder.hunter_data.thralled && prob(15)) + decloak(human_holder) + shock_user(human_holder) /// handles decloaking only on HUNTER gloves /obj/item/clothing/gloves/yautja/proc/decloak() @@ -101,15 +122,6 @@ var/perc = (charge / charge_max * 100) human.update_power_display(perc) - //Non-Yautja have a chance to get stunned with each power drain - if(!HAS_TRAIT(human, TRAIT_YAUTJA_TECH) && !human.hunter_data.thralled) - if(prob(15)) - if(cloaked) - decloak(human) - cloak_timer = world.time + 5 SECONDS - shock_user(human) - return FALSE - return TRUE /obj/item/clothing/gloves/yautja/proc/shock_user(mob/living/carbon/human/M) @@ -242,7 +254,7 @@ if(wearer.gloves == src) wearer.visible_message(SPAN_DANGER("You hear a hiss and crackle!"), SPAN_DANGER("Your bracers hiss and spark!"), SPAN_DANGER("You hear a hiss and crackle!")) if(cloaked) - decloak(wearer) + decloak(wearer, TRUE, DECLOAK_EMP) else var/turf/our_turf = get_turf(src) our_turf.visible_message(SPAN_DANGER("You hear a hiss and crackle!"), SPAN_DANGER("You hear a hiss and crackle!")) @@ -282,29 +294,25 @@ var/mob/living/carbon/human/human = loc - if(cloaked) - charge = max(charge - 10, 0) - if(charge <= 0) - decloak(loc) - //Non-Yautja have a chance to get stunned with each power drain - if(!isyautja(human)) - if(prob(15)) - decloak(human) - shock_user(human) - return + //Non-Yautja have a chance to get stunned with each power drain + if((!HAS_TRAIT(human, TRAIT_YAUTJA_TECH) && !human.hunter_data.thralled) && prob(15)) + if(cloaked) + decloak(human, TRUE, DECLOAK_SPECIES) + shock_user(human) + return ..() /obj/item/clothing/gloves/yautja/hunter/dropped(mob/user) move_chip_to_bracer() if(cloaked) - decloak(user) + decloak(user, TRUE) ..() /obj/item/clothing/gloves/yautja/hunter/on_enter_storage(obj/item/storage/S) if(ishuman(loc)) var/mob/living/carbon/human/human = loc if(cloaked) - decloak(human) + decloak(human, TRUE) . = ..() //We use this to activate random verbs for non-Yautja @@ -421,15 +429,17 @@ var/gear_on_almayer = 0 var/gear_low_orbit = 0 var/closest = 10000 + /// The item itself, to be referenced so Yautja know what to look for. + var/obj/closest_item var/direction = -1 var/atom/areaLoc = null - for(var/obj/item/I as anything in GLOB.loose_yautja_gear) - var/atom/loc = get_true_location(I) - if(I.anchored) + for(var/obj/item/tracked_item as anything in GLOB.loose_yautja_gear) + var/atom/loc = get_true_location(tracked_item) + if(tracked_item.anchored) continue - if(is_honorable_carrier(recursive_holder_check(I))) + if(is_honorable_carrier(recursive_holder_check(tracked_item))) continue - if(istype(get_area(I), /area/yautja)) + if(istype(get_area(tracked_item), /area/yautja)) continue if(is_reserved_level(loc.z)) gear_low_orbit++ @@ -441,6 +451,7 @@ var/dist = get_dist(M,loc) if(dist < closest) closest = dist + closest_item = tracked_item direction = get_dir(M,loc) areaLoc = loc for(var/mob/living/carbon/human/Y as anything in GLOB.yautja_mob_list) @@ -472,9 +483,9 @@ output = TRUE var/areaName = get_area_name(areaLoc) if(closest == 0) - to_chat(M, SPAN_NOTICE("You are directly on top of the closest signature.")) + to_chat(M, SPAN_NOTICE("You are directly on top of the[closest_item ? " [closest_item.name]'s" : ""] signature.")) else - to_chat(M, SPAN_NOTICE("The closest signature is [closest > 10 ? "approximately [round(closest, 10)]" : "[closest]"] paces [dir2text(direction)] in [areaName].")) + to_chat(M, SPAN_NOTICE("The closest signature[closest_item ? ", a [closest_item.name]" : ""], is [closest > 10 ? "approximately [round(closest, 10)]" : "[closest]"] paces [dir2text(direction)] in [areaName].")) if(!output) to_chat(M, SPAN_NOTICE("There are no signatures that require your attention.")) return TRUE @@ -528,7 +539,6 @@ if(true_cloak) M.invisibility = INVISIBILITY_LEVEL_ONE M.see_invisible = SEE_INVISIBLE_LEVEL_ONE - new_alpha = 75 log_game("[key_name_admin(usr)] has enabled their cloaking device.") M.visible_message(SPAN_WARNING("[M] vanishes into thin air!"), SPAN_NOTICE("You are now invisible to normal detection.")) @@ -553,17 +563,18 @@ sparks.set_up(5, 4, src) sparks.start() - decloak(wearer, TRUE) + decloak(wearer, TRUE, DECLOAK_EXTINGUISHER) -/obj/item/clothing/gloves/yautja/hunter/decloak(mob/user, forced) +/obj/item/clothing/gloves/yautja/hunter/decloak(mob/user, forced, force_multipler = DECLOAK_FORCED) if(!user) return UnregisterSignal(user, COMSIG_HUMAN_EXTINGUISH) UnregisterSignal(user, COMSIG_HUMAN_PRE_BULLET_ACT) + var/decloak_timer = (DECLOAK_STANDARD * force_multipler) if(forced) - cloak_malfunction = world.time + 10 SECONDS + cloak_malfunction = world.time + decloak_timer cloaked = FALSE log_game("[key_name_admin(usr)] has disabled their cloaking device.") @@ -573,7 +584,7 @@ if(true_cloak) user.invisibility = initial(user.invisibility) user.see_invisible = initial(user.see_invisible) - cloak_timer = world.time + 5 SECONDS + cloak_timer = world.time + (DECLOAK_STANDARD / 2) var/datum/mob_hud/security/advanced/SA = huds[MOB_HUD_SECURITY_ADVANCED] SA.add_to_hud(user) diff --git a/code/modules/cm_preds/yaut_items.dm b/code/modules/cm_preds/yaut_items.dm index 8a3306817078..47b2408c4f37 100644 --- a/code/modules/cm_preds/yaut_items.dm +++ b/code/modules/cm_preds/yaut_items.dm @@ -327,6 +327,7 @@ unacidable = TRUE ignore_z = TRUE black_market_value = 100 + flags_item = ITEM_PREDATOR /obj/item/device/radio/headset/yautja/talk_into(mob/living/M as mob, message, channel, verb = "commands", datum/language/speaking) if(!isyautja(M)) //Nope. @@ -338,9 +339,6 @@ to_chat(hellhound, "\[Radio\]: [M.real_name] [verb], '[message]'.") ..() -/obj/item/device/radio/headset/yautja/attackby() - return - /obj/item/device/radio/headset/yautja/elder //primarily for use in another MR name = "\improper Elder Communicator" volume_settings = list(RADIO_VOLUME_QUIET_STR, RADIO_VOLUME_RAISED_STR, RADIO_VOLUME_IMPORTANT_STR, RADIO_VOLUME_CRITICAL_STR) @@ -697,6 +695,7 @@ var/tether_range = 5 var/mob/trapped_mob layer = LOWER_ITEM_LAYER + flags_item = ITEM_PREDATOR /obj/item/hunting_trap/Destroy() cleanup_tether() diff --git a/code/modules/cm_preds/yaut_machines.dm b/code/modules/cm_preds/yaut_machines.dm index adba69043dcc..a1782ca22b85 100644 --- a/code/modules/cm_preds/yaut_machines.dm +++ b/code/modules/cm_preds/yaut_machines.dm @@ -5,7 +5,7 @@ icon_state = "globe" breakable = FALSE - minimap_type = MINIMAP_FLAG_XENO|MINIMAP_FLAG_USCM + minimap_type = MINIMAP_FLAG_ALL /obj/structure/machinery/autolathe/yautja name = "yautja autolathe" diff --git a/code/modules/cm_preds/yaut_shield.dm b/code/modules/cm_preds/yaut_shield.dm index 2e036b4a5357..7b84f935f4c5 100644 --- a/code/modules/cm_preds/yaut_shield.dm +++ b/code/modules/cm_preds/yaut_shield.dm @@ -45,10 +45,10 @@ M.apply_effect(3, DAZE) M.apply_effect(5, SLOW) -/obj/item/weapon/shield/riot/yautja/attackby(obj/item/I, mob/user) +/obj/item/weapon/shield/riot/yautja/attackby(obj/item/attacking_item, mob/user) if(cooldown < world.time - 25) - if(istype(I, /obj/item/weapon) && (I.flags_item & ITEM_PREDATOR)) - user.visible_message(SPAN_WARNING("[user] bashes \the [src] with \the [I]!")) + if(istype(attacking_item, /obj/item/weapon) && (attacking_item.flags_item & ITEM_PREDATOR)) + user.visible_message(SPAN_WARNING("[user] bashes [src] with [attacking_item]!")) playsound(user.loc, 'sound/effects/shieldbash.ogg', 25, 1) cooldown = world.time else diff --git a/code/modules/cm_preds/yaut_weapons.dm b/code/modules/cm_preds/yaut_weapons.dm index 40006dafe60b..986b30c27ddf 100644 --- a/code/modules/cm_preds/yaut_weapons.dm +++ b/code/modules/cm_preds/yaut_weapons.dm @@ -891,7 +891,7 @@ /obj/item/weapon/gun/energy/yautja/plasmarifle/load_into_chamber() if(charge_time >= 80) ammo = GLOB.ammo_list[/datum/ammo/energy/yautja/rifle/blast] - charge_time = 0 + charge_time -= 80 else ammo = GLOB.ammo_list[/datum/ammo/energy/yautja/rifle/bolt] charge_time -= 10 @@ -912,6 +912,8 @@ if(refund) charge_time *= 2 return TRUE +#define FIRE_MODE_STANDARD "Standard" +#define FIRE_MODE_INCENDIARY "Incendiary" /obj/item/weapon/gun/energy/yautja/plasmapistol name = "plasma pistol" desc = "A plasma pistol capable of rapid fire. It has an integrated battery. Can be used to set fires, either to braziers or on people." @@ -924,7 +926,12 @@ ammo = /datum/ammo/energy/yautja/pistol muzzle_flash = null // TO DO, add a decent one. w_class = SIZE_MEDIUM + /// Max amount of shots var/charge_time = 40 + /// Amount of charge_time drained per shot + var/shot_cost = 1 + /// standard (sc = 1) or incendiary (sc = 5) + var/mode = FIRE_MODE_STANDARD flags_gun_features = GUN_UNUSUAL_DESIGN flags_item = ITEM_PREDATOR|IGNITING_ITEM|TWOHANDED @@ -965,9 +972,14 @@ if(isyautja(user)) . = ..() . += SPAN_NOTICE("It currently has [charge_time]/40 charge.") + + if(mode == FIRE_MODE_INCENDIARY) + . += SPAN_RED("It is set to fire incendiary plasma bolts.") + else + . += SPAN_ORANGE("It is set to fire plasma bolts.") else . = list() - . += SPAN_NOTICE("This thing looks like an alien rifle of some kind. Strange.") + . += SPAN_NOTICE("This thing looks like an alien gun of some kind. Strange.") /obj/item/weapon/gun/energy/yautja/plasmapistol/able_to_fire(mob/user) @@ -983,7 +995,7 @@ var/obj/item/projectile/projectile = create_bullet(ammo, initial(name)) projectile.SetLuminosity(1) in_chamber = projectile - charge_time-- + charge_time -= shot_cost return in_chamber /obj/item/weapon/gun/energy/yautja/plasmapistol/has_ammunition() @@ -995,9 +1007,30 @@ /obj/item/weapon/gun/energy/yautja/plasmapistol/delete_bullet(obj/item/projectile/projectile_to_fire, refund = 0) qdel(projectile_to_fire) - if(refund) charge_time *= 2 + if(refund) + charge_time += shot_cost + log_debug("Plasma Pistol refunded shot.") return TRUE +/obj/item/weapon/gun/energy/yautja/plasmapistol/use_unique_action() + switch(mode) + if(FIRE_MODE_STANDARD) + mode = FIRE_MODE_INCENDIARY + shot_cost = 5 + fire_delay = FIRE_DELAY_TIER_5 + to_chat(usr, SPAN_NOTICE("[src] will now fire incendiary plasma bolts.")) + ammo = GLOB.ammo_list[/datum/ammo/energy/yautja/pistol/incendiary] + + if(FIRE_MODE_INCENDIARY) + mode = FIRE_MODE_STANDARD + shot_cost = 1 + fire_delay = FIRE_DELAY_TIER_7 + to_chat(usr, SPAN_NOTICE("[src] will now fire plasma bolts.")) + ammo = GLOB.ammo_list[/datum/ammo/energy/yautja/pistol] + +#undef FIRE_MODE_STANDARD +#undef FIRE_MODE_INCENDIARY + /obj/item/weapon/gun/energy/yautja/plasma_caster name = "plasma caster" desc = "A powerful, shoulder-mounted energy weapon." diff --git a/code/modules/mob/dead/observer/actions.dm b/code/modules/mob/dead/observer/actions.dm index 49a3890088f6..ff897db4a3f6 100644 --- a/code/modules/mob/dead/observer/actions.dm +++ b/code/modules/mob/dead/observer/actions.dm @@ -81,6 +81,22 @@ if(SSticker.mode.check_xeno_late_join(owner)) SSticker.mode.attempt_to_join_as_xeno(owner) +/datum/action/observer_action/join_lesser_drone + name = "Join as Lesser Drone" + action_icon_state = "join_lesser_drone" + listen_signal = COMSIG_KB_OBSERVER_JOIN_LESSER_DRONE + +/datum/action/observer_action/join_lesser_drone/action_activate() + if(!owner.client) + return + + if(SSticker.current_state < GAME_STATE_PLAYING || !SSticker.mode) + owner.balloon_alert(owner, "game must start!") + return + + if(SSticker.mode.check_xeno_late_join(owner)) + SSticker.mode.attempt_to_join_as_lesser_drone(owner) + /datum/keybinding/observer category = CATEGORY_OBSERVER weight = WEIGHT_DEAD @@ -108,3 +124,10 @@ name = "join_pred" full_name = "Join the Hunt" keybind_signal = COMSIG_KB_OBSERVER_JOIN_PREDATOR + +/datum/keybinding/observer/join_lesser_drone + hotkey_keys = list("Unbound") + classic_keys = list("Unbound") + name = "join_lesser_drone" + full_name = "Join as Lesser Drone" + keybind_signal = COMSIG_KB_OBSERVER_JOIN_LESSER_DRONE diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 3a35eecd8557..9e8fa264af1d 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -54,7 +54,7 @@ var/datum/health_scan/last_health_display var/ghost_orbit = GHOST_ORBIT_CIRCLE var/own_orbit_size = 0 - var/observer_actions = list(/datum/action/observer_action/join_xeno) + var/observer_actions = list(/datum/action/observer_action/join_xeno, /datum/action/observer_action/join_lesser_drone) var/datum/action/minimap/observer/minimap var/larva_queue_cached_message ///Used to bypass time of death checks such as when being selected for larva. @@ -368,12 +368,13 @@ Works together with spawning an observer, noted above. if(ghost.client.player_data) ghost.client.player_data.load_timestat_data() - // Larva queue: We use the larger of their existing queue time or the new timeofdeath except for facehuggers - // We don't change facehugger timeofdeath because they are still on cooldown if they died as a hugger - var/new_tod = isfacehugger(src) ? 1 : ghost.timeofdeath - // if they died as facehugger, bypass typical TOD checks - ghost.bypass_time_of_death_checks = isfacehugger(src) - ghost.client.player_details.larva_queue_time = max(ghost.client.player_details.larva_queue_time, new_tod) + // Larva queue: We use the larger of their existing queue time or the new timeofdeath except for facehuggers or lesser drone + var/new_tod = (isfacehugger(src) || islesserdrone(src)) ? 1 : ghost.timeofdeath + + // if they died as facehugger or lesser drone, bypass typical TOD checks + ghost.bypass_time_of_death_checks = (isfacehugger(src) || islesserdrone(src)) + + ghost.client?.player_details.larva_queue_time = max(ghost.client.player_details.larva_queue_time, new_tod) ghost.set_huds_from_prefs() @@ -418,9 +419,12 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp if(ghost && !is_admin_level(z)) ghost.timeofdeath = world.time - // Larva queue: We use the larger of their existing queue time or the new timeofdeath except for facehuggers - var/new_tod = isfacehugger(src) ? 1 : world.time - ghost.bypass_time_of_death_checks = isfacehugger(src) + // Larva queue: We use the larger of their existing queue time or the new timeofdeath except for facehuggers or lesser drone + var/new_tod = (isfacehugger(src) || islesserdrone(src)) ? 1 : ghost.timeofdeath + + // if they died as facehugger or lesser drone, bypass typical TOD checks + ghost.bypass_time_of_death_checks = (isfacehugger(src) || islesserdrone(src)) + ghost.client?.player_details.larva_queue_time = max(ghost.client.player_details.larva_queue_time, new_tod) if(is_nested && nest && !QDELETED(nest)) ghost.can_reenter_corpse = FALSE @@ -779,6 +783,21 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp if(SSticker.mode.check_xeno_late_join(src)) SSticker.mode.attempt_to_join_as_facehugger(src) +/mob/dead/verb/join_as_lesser_drone() + set category = "Ghost.Join" + set name = "Join as a Lesser Drone" + set desc = "Try joining as a Lesser Drone to support the hive." + + if (!client) + return + + if(SSticker.current_state < GAME_STATE_PLAYING || !SSticker.mode) + to_chat(src, SPAN_WARNING("The game hasn't started yet!")) + return + + if(SSticker.mode.check_xeno_late_join(src)) + SSticker.mode.attempt_to_join_as_lesser_drone(src) + /mob/dead/verb/join_as_zombie() //Adapted from join as hellhoud set category = "Ghost.Join" set name = "Join as Zombie" diff --git a/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm b/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm index b12ff5d6c3bb..6361ff595b10 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoAttacks.dm @@ -185,7 +185,7 @@ var/is_shover_queen = isqueen(M) var/can_resist_shove = M.hivenumber != src.hivenumber || ((isqueen(src) || IS_XENO_LEADER(src)) && !is_shover_queen) var/can_mega_shove = is_shover_queen || IS_XENO_LEADER(M) - if(can_mega_shove && !can_resist_shove) + if(can_mega_shove && !can_resist_shove || (mob_size < MOB_SIZE_XENO_SMALL && M.mob_size >= MOB_SIZE_XENO_SMALL)) playsound(loc, 'sound/weapons/alien_knockdown.ogg', 25, 1) M.visible_message(SPAN_WARNING("\The [M] shoves \the [src] out of her way!"), \ SPAN_WARNING("You shove \the [src] out of your way!"), null, 5, CHAT_TYPE_XENO_COMBAT) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lesser_drone/lesser_drone_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lesser_drone/lesser_drone_abilities.dm new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/abilities/lesser_drone/lesser_drone_abilities.dm @@ -0,0 +1 @@ + diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lesser_drone/lesser_drone_macros.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lesser_drone/lesser_drone_macros.dm new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/abilities/lesser_drone/lesser_drone_macros.dm @@ -0,0 +1 @@ + diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lesser_drone/lesser_drone_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lesser_drone/lesser_drone_powers.dm new file mode 100644 index 000000000000..515efea23d53 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/abilities/lesser_drone/lesser_drone_powers.dm @@ -0,0 +1,6 @@ +/datum/action/xeno_action/onclick/plant_weeds/lesser/use_ability(atom/A) + if(!(locate(/obj/effect/alien/weeds/node) in orange(4, owner))) + to_chat(owner, SPAN_XENONOTICE("You can only plant resin nodes near other resin nodes!")) + return + + . = ..() diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/predalien/predalien_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/predalien/predalien_powers.dm index 058e643f5c64..5c1584c565c6 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/predalien/predalien_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/predalien/predalien_powers.dm @@ -25,7 +25,7 @@ var/obj/item/clothing/gloves/yautja/hunter/YG = locate(/obj/item/clothing/gloves/yautja/hunter) in human if(isyautja(human) && YG) if(YG.cloaked) - YG.decloak(human) + YG.decloak(human, TRUE, DECLOAK_PREDALIEN) YG.cloak_timer = xeno_cooldown * 0.1 else if(isxeno(carbon) && xeno.can_not_harm(carbon)) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm index ec1697f30081..6b847a6a4fec 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm @@ -82,15 +82,20 @@ var/list/hugger_image_index = list() var/mutable_appearance/hugger_overlays_icon + var/mutable_appearance/eggsac_overlays_icon /mob/living/carbon/xenomorph/carrier/update_icons() . = ..() - - update_hugger_overlays() + if (mutation_type == CARRIER_NORMAL) + update_hugger_overlays() + if (mutation_type == CARRIER_EGGSAC) + update_eggsac_overlays() /mob/living/carbon/xenomorph/carrier/proc/update_hugger_overlays() if(!hugger_overlays_icon) return + if(mutation_type != CARRIER_NORMAL) + return overlays -= hugger_overlays_icon hugger_overlays_icon.overlays.Cut() @@ -99,7 +104,7 @@ hugger_image_index.Cut() return - update_icon_maths(round(( huggers_cur / huggers_max ) * 3.999) + 1) + update_clinger_maths(round(( huggers_cur / huggers_max ) * 3.999) + 1) for(var/i in hugger_image_index) if(stat == DEAD) @@ -114,8 +119,8 @@ overlays += hugger_overlays_icon -/mob/living/carbon/xenomorph/carrier/proc/update_icon_maths(number) - var/funny_list = list(1,2,3,4) +/mob/living/carbon/xenomorph/carrier/proc/update_clinger_maths(number) + var/clinger_list = list(1,2,3,4) if(length(hugger_image_index) != number) if(length(hugger_image_index) > number) while(length(hugger_image_index) != number) @@ -123,20 +128,56 @@ else while(length(hugger_image_index) != number) for(var/i in hugger_image_index) - if(locate(i) in funny_list) - funny_list -= i - hugger_image_index += funny_list[rand(1,length(funny_list))] + if(locate(i) in clinger_list) + clinger_list -= i + hugger_image_index += clinger_list[rand(1,length(clinger_list))] + +/mob/living/carbon/xenomorph/carrier/proc/update_eggsac_overlays() + if(!eggsac_overlays_icon) + return + if(mutation_type != CARRIER_EGGSAC) + return + + overlays -= eggsac_overlays_icon + eggsac_overlays_icon.overlays.Cut() + + if(!eggs_cur) + return + + ///Simplified image index change. + var/i = 0 + if(eggs_cur > 8) + i = 3 + else if (eggs_cur > 4) + i = 2 + else if (eggs_cur > 0) + i = 1 + + if(stat != DEAD) + if(lying) + if((resting || sleeping) && (!knocked_down && !knocked_out && health > 0)) + eggsac_overlays_icon.overlays += icon(icon, "eggsac_[i] Sleeping") + else + eggsac_overlays_icon.overlays +=icon(icon, "eggsac_[i] Knocked Down") + else + eggsac_overlays_icon.overlays +=icon(icon, "eggsac_[i]") + + overlays += eggsac_overlays_icon /mob/living/carbon/xenomorph/carrier/Initialize(mapload, mob/living/carbon/xenomorph/oldxeno, h_number) . = ..() hugger_overlays_icon = mutable_appearance('icons/mob/xenos/overlay_effects64x64.dmi',"empty") + eggsac_overlays_icon = mutable_appearance('icons/mob/xenos/overlay_effects64x64.dmi',"empty") /mob/living/carbon/xenomorph/carrier/death(cause, gibbed) . = ..(cause, gibbed) if(.) - var/chance = 75 + var/chance = 75 //75% to drop an egg or hugger. + if(mutation_type == CARRIER_EGGSAC) + visible_message(SPAN_XENOWARNING("[src] throes as its eggsac bursts into a mess of acid!")) + playsound(src.loc, 'sound/effects/alien_egg_burst.ogg', 25, 1) - if (huggers_cur) + if(huggers_cur) //Hugger explosion, like an egg morpher var/obj/item/clothing/mask/facehugger/hugger visible_message(SPAN_XENOWARNING("The chittering mass of tiny aliens is trying to escape [src]!")) @@ -145,10 +186,15 @@ hugger = new(loc, hivenumber) step_away(hugger, src, 1) - while (eggs_cur > 0) + var/eggs_dropped = FALSE + for(var/i in 1 to eggs_cur) if(prob(chance)) new /obj/item/xeno_egg(loc, hivenumber) - eggs_cur-- + eggs_dropped = TRUE + eggs_cur = 0 + + if(eggs_dropped) //Checks whether or not to announce egg drop. + xeno_message(SPAN_XENOANNOUNCE("[src] has dropped some precious eggs!"), 2, hive.hivenumber) /mob/living/carbon/xenomorph/carrier/get_status_tab_items() . = ..() @@ -272,6 +318,7 @@ if(eggs_cur < eggs_max) if(stat == CONSCIOUS) eggs_cur++ + update_icons() to_chat(src, SPAN_NOTICE("You store the egg and carry it for safekeeping. Now sheltering: [eggs_cur] / [eggs_max].")) qdel(E) else @@ -306,6 +353,7 @@ return E = new(src, hivenumber) eggs_cur-- + update_icons() put_in_active_hand(E) to_chat(src, SPAN_XENONOTICE("You grab one of the eggs in your storage. Now sheltering: [eggs_cur] / [eggs_max].")) return diff --git a/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm b/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm new file mode 100644 index 000000000000..48bf0d95ddcf --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm @@ -0,0 +1,97 @@ +/datum/caste_datum/lesser_drone + caste_type = XENO_CASTE_LESSER_DRONE + tier = 1 + melee_damage_lower = XENO_DAMAGE_TIER_1 + melee_damage_upper = XENO_DAMAGE_TIER_1 + melee_vehicle_damage = XENO_DAMAGE_TIER_1 + max_health = XENO_HEALTH_LESSER_DRONE + plasma_gain = XENO_PLASMA_GAIN_TIER_7 + plasma_max = XENO_PLASMA_TIER_3 + crystal_max = XENO_CRYSTAL_LOW + xeno_explosion_resistance = XENO_NO_EXPLOSIVE_ARMOR + armor_deflection = XENO_NO_ARMOR + evasion = XENO_EVASION_LOW + speed = XENO_SPEED_TIER_6 + + evolution_allowed = FALSE + can_be_revived = FALSE + + build_time_mult = BUILD_TIME_MULT_LESSER_DRONE + + caste_desc = "A builder of hives." + can_hold_facehuggers = 1 + can_hold_eggs = CAN_HOLD_TWO_HANDS + acid_level = 1 + weed_level = WEED_LEVEL_STANDARD + max_build_dist = 1 + + tackle_min = 4 + tackle_max = 5 + + aura_strength = 1 + + minimap_icon = "lesser_drone" + +/datum/caste_datum/lesser_drone/New() + . = ..() + + resin_build_order = GLOB.resin_build_order_lesser_drone + +/mob/living/carbon/xenomorph/lesser_drone + caste_type = XENO_CASTE_LESSER_DRONE + name = XENO_CASTE_LESSER_DRONE + desc = "An alien drone. Looks... smaller." + icon = 'icons/mob/xenos/drone.dmi' + icon_size = 48 + icon_state = "Lesser Drone Walking" + plasma_types = list(PLASMA_PURPLE) + tier = 0 + mob_flags = NOBIOSCAN + mob_size = MOB_SIZE_XENO_VERY_SMALL + life_value = 0 + default_honor_value = 0 + show_only_numbers = TRUE + counts_for_slots = FALSE + counts_for_roundend = FALSE + refunds_larva_if_banished = FALSE + crit_health = 0 + gib_chance = 100 + acid_blood_damage = 15 + base_actions = list( + /datum/action/xeno_action/onclick/xeno_resting, + /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/watch_xeno, + /datum/action/xeno_action/activable/tail_stab, + /datum/action/xeno_action/activable/corrosive_acid/weak, + /datum/action/xeno_action/onclick/emit_pheromones, + /datum/action/xeno_action/onclick/plant_weeds/lesser, //first macro + /datum/action/xeno_action/onclick/choose_resin, //second macro + /datum/action/xeno_action/activable/secrete_resin, //third macro + /datum/action/xeno_action/onclick/tacmap, + ) + inherent_verbs = list( + /mob/living/carbon/xenomorph/proc/vent_crawl, + /mob/living/carbon/xenomorph/proc/rename_tunnel, + /mob/living/carbon/xenomorph/proc/set_hugger_reserve_for_morpher, + ) + + mutation_type = DRONE_NORMAL + + icon_xeno = 'icons/mob/xenos/lesser_drone.dmi' + icon_xenonid = 'icons/mob/xenonids/lesser_drone.dmi' + +/mob/living/carbon/xenomorph/lesser_drone/age_xeno() + if(stat == DEAD || !caste || QDELETED(src) || !client) + return + + age = XENO_NORMAL + + hud_update() + + xeno_jitter(25) + +/mob/living/carbon/xenomorph/lesser_drone/initialize_pass_flags(datum/pass_flags_container/PF) + ..() + if (PF) + PF.flags_pass = PASS_MOB_IS_XENO|PASS_MOB_THRU_XENO + PF.flags_can_pass_all = PASS_MOB_IS_XENO|PASS_MOB_THRU_XENO diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/carrier/eggsac.dm b/code/modules/mob/living/carbon/xenomorph/mutators/strains/carrier/eggsac.dm index c8e6b768f2bc..436adb79e5aa 100644 --- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/carrier/eggsac.dm +++ b/code/modules/mob/living/carbon/xenomorph/mutators/strains/carrier/eggsac.dm @@ -36,6 +36,8 @@ playsound(carrier.loc, 'sound/voice/alien_facehugger_dies.ogg', 25, 1) carrier.huggers_cur = 0 carrier.huggers_max = 0 + carrier.update_hugger_overlays() + carrier.update_eggsac_overlays() carrier.eggs_max = 12 carrier.extra_build_dist = 1 return TRUE @@ -73,3 +75,4 @@ if(egg_generation_progress >= 15) egg_generation_progress = 0 xeno.eggs_cur++ + xeno.update_icons() diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm index 060c3aea2814..eaff5a66309e 100644 --- a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm +++ b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm @@ -1,5 +1,3 @@ -#define JOIN_AS_FACEHUGGER_DELAY 3 MINUTES - // Actual caste datum basedef /datum/caste_datum var/caste_type = "" @@ -352,6 +350,9 @@ /// How many huggers can the hive support var/playable_hugger_limit = 0 + /// How many lesser drones the hive can support + var/lesser_drone_limit = 0 + var/datum/tacmap/xeno/tacmap var/minimap_type = MINIMAP_FLAG_XENO @@ -893,8 +894,11 @@ qdel(S) for(var/mob/living/carbon/xenomorph/xeno as anything in totalXenos) if(get_area(xeno) != hijacked_dropship && xeno.loc && is_ground_level(xeno.loc.z)) - if(isfacehugger(xeno)) + if(isfacehugger(xeno) || islesserdrone(xeno)) to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, you quickly find a hiding place to enter hibernation as you lose touch with the hive mind.")) + if(xeno.stomach_contents.len) + xeno.devour_timer = 0 + xeno.handle_stomach_contents() qdel(xeno) continue if(xeno.hunter_data.hunted && !isqueen(xeno)) @@ -908,7 +912,7 @@ qdel(xeno) stored_larva++ continue - if(!isfacehugger(xeno)) + if(xeno.tier >= 1) xenos_count++ for(var/i in GLOB.alive_mob_list) var/mob/living/potential_host = i @@ -1036,8 +1040,12 @@ to_chat(user, SPAN_WARNING("\The [GLOB.hive_datum[hivenumber]] cannot support more facehuggers! Limit: [current_hugger_count]/[playable_hugger_limit]")) return FALSE - if(alert(user, "Are you sure you want to become a facehugger?", "Confirmation", "Yes", "No") != "Yes") + if(tgui_alert(user, "Are you sure you want to become a facehugger?", "Confirmation", list("Yes", "No")) != "Yes") return FALSE + + if(!user.client) + return FALSE + return TRUE /datum/hive_status/proc/spawn_as_hugger(mob/dead/observer/user, atom/A) @@ -1047,6 +1055,53 @@ playsound(hugger, 'sound/effects/xeno_newlarva.ogg', 25, TRUE) hugger.generate_name() +/datum/hive_status/proc/update_lesser_drone_limit() + lesser_drone_limit = Ceiling(totalXenos.len / 3) + +/datum/hive_status/proc/can_spawn_as_lesser_drone(mob/dead/observer/user) + if(!GLOB.hive_datum || ! GLOB.hive_datum[hivenumber]) + return FALSE + + if(jobban_isbanned(user, JOB_XENOMORPH)) // User is jobbanned + to_chat(user, SPAN_WARNING("You are banned from playing aliens and cannot spawn as a xenomorph.")) + return FALSE + + if(world.time - user.timeofdeath < JOIN_AS_LESSER_DRONE_DELAY) + var/time_left = round((user.timeofdeath + JOIN_AS_LESSER_DRONE_DELAY - world.time) / 10) + to_chat(user, SPAN_WARNING("You ghosted too recently. You cannot become a lesser drone until 30 seconds have passed ([time_left] seconds remaining).")) + return FALSE + + if(totalXenos.len <= 0) + to_chat(user, SPAN_WARNING("The hive has fallen, you can't join it!")) + return FALSE + + if(!living_xeno_queen) + to_chat(user, SPAN_WARNING("The selected hive does not have a Queen!")) + return FALSE + + if(!living_xeno_queen.ovipositor && !SSticker.mode.is_in_endgame) + to_chat(user, SPAN_WARNING("The selected hive does not have a Queen on Ovipositor!")) + return FALSE + + update_lesser_drone_limit() + + var/current_lesser_drone_count = 0 + for(var/mob/mob as anything in totalXenos) + if(islesserdrone(mob)) + current_lesser_drone_count++ + + if(lesser_drone_limit <= current_lesser_drone_count) + to_chat(user, SPAN_WARNING("[GLOB.hive_datum[hivenumber]] cannot support more lesser drones! Limit: [current_lesser_drone_count]/[lesser_drone_limit]")) + return FALSE + + if(tgui_alert(user, "Are you sure you want to become a lesser drone?", "Confirmation", list("Yes", "No")) != "Yes") + return FALSE + + if(!user.client) + return FALSE + + return TRUE + ///Called by /obj/item/alien_embryo when a host is bursting to determine extra larva per burst /datum/hive_status/proc/increase_larva_after_burst() var/extra_per_burst = CONFIG_GET(number/extra_larva_per_burst) diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index 7247e9b87b16..e7a160095705 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -676,9 +676,9 @@ GLOBAL_LIST_INIT(apc_wire_descriptions, list( var/turf/T = get_turf(src) var/obj/structure/cable/N = T.get_cable_node() if(prob(50) && electrocute_mob(usr, N, N)) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(5, 1, src) - s.start() + var/datum/effect_system/spark_spread/spark = new /datum/effect_system/spark_spread + spark.set_up(5, 1, src) + spark.start() return if(C.use(10)) user.visible_message(SPAN_NOTICE("[user] wires [src]'s frame."), @@ -700,9 +700,9 @@ GLOBAL_LIST_INIT(apc_wire_descriptions, list( to_chat(user, SPAN_WARNING("\The [src] lacks a terminal to remove.")) return if (prob(50) && electrocute_mob(user, terminal.powernet, terminal)) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(5, 1, src) - s.start() + var/datum/effect_system/spark_spread/spark = new /datum/effect_system/spark_spread + spark.set_up(5, 1, src) + spark.start() return new /obj/item/stack/cable_coil(loc,10) user.visible_message(SPAN_NOTICE("[user] removes [src]'s wiring and terminal."), @@ -798,41 +798,87 @@ GLOBAL_LIST_INIT(apc_wire_descriptions, list( //Human mob special interaction goes here. if(ishuman(user)) - var/mob/living/carbon/human/H = user + var/mob/living/carbon/human/grabber = user - if(H.species.flags & IS_SYNTHETIC && H.a_intent == INTENT_GRAB) - if(H.action_busy) - return - - if(!do_after(H, 20, INTERRUPT_ALL, BUSY_ICON_GENERIC)) - return + if(grabber.a_intent == INTENT_GRAB) - playsound(src.loc, 'sound/effects/sparks2.ogg', 25, 1) + //Synthpack recharge + if((grabber.species.flags & IS_SYNTHETIC) && istype(grabber.back, /obj/item/storage/backpack/marine/smartpack)) + var/obj/item/storage/backpack/marine/smartpack/s_pack = grabber.back + if(grabber.action_busy) + return - if(stat & BROKEN) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, src) - s.start() - to_chat(H, SPAN_DANGER("The APC's power currents surge eratically, damaging your chassis!")) - H.apply_damage(10,0, BURN) - else if(cell && cell.charge > 0) - if(!istype(H.back, /obj/item/storage/backpack/marine/smartpack)) + if(!do_after(grabber, 20, INTERRUPT_ALL, BUSY_ICON_GENERIC)) return - var/obj/item/storage/backpack/marine/smartpack/S = H.back - if(S.battery_charge < SMARTPACK_MAX_POWER_STORED) - var/charge_to_use = min(cell.charge, SMARTPACK_MAX_POWER_STORED - S.battery_charge) - if(!(cell.use(charge_to_use))) + playsound(src.loc, 'sound/effects/sparks2.ogg', 25, 1) + + if(stat & BROKEN) + var/datum/effect_system/spark_spread/spark = new() + spark.set_up(3, 1, src) + spark.start() + to_chat(grabber, SPAN_DANGER("The APC's power currents surge eratically, damaging your chassis!")) + grabber.apply_damage(10,0, BURN) + else if(cell && cell.charge > 0) + if(!istype(s_pack)) return - S.battery_charge += charge_to_use - to_chat(user, SPAN_NOTICE("You slot your fingers into the APC interface and siphon off some of the stored charge. [S.name] now has [S.battery_charge]/[SMARTPACK_MAX_POWER_STORED]")) - charging = APC_CHARGING + + if(s_pack.battery_charge < SMARTPACK_MAX_POWER_STORED) + var/charge_to_use = min(cell.charge, SMARTPACK_MAX_POWER_STORED - s_pack.battery_charge) + if(!(cell.use(charge_to_use))) + return + s_pack.battery_charge += charge_to_use + to_chat(user, SPAN_NOTICE("You slot your fingers into the APC interface and siphon off some of the stored charge. [s_pack.name] now has [s_pack.battery_charge]/[SMARTPACK_MAX_POWER_STORED]")) + charging = APC_CHARGING + else + to_chat(user, SPAN_WARNING("[s_pack.name] is already fully charged.")) else - to_chat(user, SPAN_WARNING("[S.name] is already fully charged.")) - else - to_chat(user, SPAN_WARNING("There is no charge to draw from that APC.")) - return - else if(H.species.can_shred(H)) + to_chat(user, SPAN_WARNING("There is no charge to draw from that APC.")) + return + + // Yautja Bracer Recharge + var/obj/item/clothing/gloves/yautja/bracer = grabber.gloves + if(istype(bracer)) + if(grabber.action_busy) + return FALSE + if(!COOLDOWN_FINISHED(bracer, bracer_recharge)) + to_chat(user, SPAN_WARNING("It is too soon for [bracer.name] to siphon power again. Wait [COOLDOWN_SECONDSLEFT(bracer, bracer_recharge)] seconds.")) + return FALSE + to_chat(user, SPAN_NOTICE("You rest your bracer against the APC interface and begin to siphon off some of the stored energy.")) + if(!do_after(grabber, 20, INTERRUPT_ALL, BUSY_ICON_HOSTILE)) + return FALSE + + if(stat & BROKEN) + var/datum/effect_system/spark_spread/spark = new() + spark.set_up(3, 1, src) + spark.start() + to_chat(grabber, SPAN_DANGER("The APC's power currents surge eratically, super-heating your bracer!")) + playsound(src.loc, 'sound/effects/sparks2.ogg', 25, 1) + grabber.apply_damage(10,0, BURN) + return FALSE + if(!cell || cell.charge <= 0) + to_chat(user, SPAN_WARNING("There is no charge to draw from that APC.")) + return FALSE + + if(bracer.charge_max <= bracer.charge) + to_chat(user, SPAN_WARNING("[bracer.name] is already fully charged.")) + return FALSE + + var/charge_to_use = min(cell.charge, bracer.charge_max - bracer.charge) + if(!(cell.use(charge_to_use))) + return FALSE + playsound(src.loc, 'sound/effects/sparks2.ogg', 25, 1) + bracer.charge += charge_to_use + COOLDOWN_START(bracer, bracer_recharge, bracer.charge_cooldown) + to_chat(grabber, SPAN_YAUTJABOLD("[icon2html(bracer)] \The [bracer] beep: Power siphon complete. Charge at [bracer.charge]/[bracer.charge_max].")) + if(bracer.notification_sound) + playsound(bracer.loc, 'sound/items/pred_bracer.ogg', 75, 1) + charging = APC_CHARGING + set_broken() // Breaks the APC + + return TRUE + + else if(grabber.species.can_shred(grabber)) var/allcut = TRUE for(var/wire = 1; wire < length(get_wire_descriptions()); wire++) if(!isWireCut(wire)) @@ -1011,9 +1057,9 @@ GLOBAL_LIST_INIT(apc_wire_descriptions, list( smoke.set_up(1, 0, loc) smoke.attach(src) smoke.start() - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(1, 1, src) - s.start() + var/datum/effect_system/spark_spread/spark = new() + spark.set_up(1, 1, src) + spark.start() visible_message(SPAN_WARNING("[src] suddenly lets out a blast of smoke and some sparks!")) /obj/structure/machinery/power/apc/surplus() diff --git a/code/modules/projectiles/ammo_datums.dm b/code/modules/projectiles/ammo_datums.dm index 7114c924fe61..f97195d5ac32 100644 --- a/code/modules/projectiles/ammo_datums.dm +++ b/code/modules/projectiles/ammo_datums.dm @@ -2217,12 +2217,27 @@ damage = 40 shell_speed = AMMO_SPEED_TIER_2 -/datum/ammo/energy/yautja/pistol/set_bullet_traits() +/datum/ammo/energy/yautja/pistol/incendiary + damage = 10 + +/datum/ammo/energy/yautja/pistol/incendiary/set_bullet_traits() . = ..() LAZYADD(traits_to_give, list( BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_incendiary) )) +/datum/ammo/bullet/shrapnel/plasma + name = "plasma wave" + shrapnel_chance = 0 + penetration = ARMOR_PENETRATION_TIER_10 + accuracy = HIT_ACCURACY_TIER_MAX + damage = 15 + icon_state = "shrapnel_plasma" + damage_type = BURN + +/datum/ammo/bullet/shrapnel/plasma/on_hit_mob(mob/hit_mob, obj/item/projectile/hit_projectile) + hit_mob.apply_effect(2, WEAKEN) + /datum/ammo/energy/yautja/caster name = "root caster bolt" icon_state = "ion" diff --git a/code/modules/tgui/tgui_number_input.dm b/code/modules/tgui/tgui_number_input.dm index 9c447ecd5a03..aa189b1d2039 100644 --- a/code/modules/tgui/tgui_number_input.dm +++ b/code/modules/tgui/tgui_number_input.dm @@ -31,7 +31,7 @@ qdel(number_input) ///A clone of tgui_input_number that defaults to accepting negative inputs too. -/proc/tgui_input_real_number(mob/user, message, title = "Number Input", default = 0, max_value = 16777216, min_value = -16777216, timeout = 0, integer_only = FALSE) +/proc/tgui_input_real_number(mob/user, message, title = "Number Input", default = 0, max_value = SHORT_REAL_LIMIT, min_value = -SHORT_REAL_LIMIT, timeout = 0, integer_only = FALSE) return tgui_input_number(user, message, title, default, max_value, min_value, timeout, integer_only) /** * Creates an asynchronous TGUI number input window with an associated callback. diff --git a/colonialmarines.dme b/colonialmarines.dme index b241992cf172..4a5798995693 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -1855,6 +1855,9 @@ #include "code\modules\mob\living\carbon\xenomorph\abilities\facehugger\facehugger_powers.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\hivelord\hivelord_abilities.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\hivelord\hivelord_powers.dm" +#include "code\modules\mob\living\carbon\xenomorph\abilities\lesser_drone\lesser_drone_abilities.dm" +#include "code\modules\mob\living\carbon\xenomorph\abilities\lesser_drone\lesser_drone_macros.dm" +#include "code\modules\mob\living\carbon\xenomorph\abilities\lesser_drone\lesser_drone_powers.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\lurker\lurker_abilities.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\lurker\lurker_macros.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\lurker\lurker_powers.dm" @@ -1891,6 +1894,7 @@ #include "code\modules\mob\living\carbon\xenomorph\castes\Hellhound.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Hivelord.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Larva.dm" +#include "code\modules\mob\living\carbon\xenomorph\castes\lesser_drone.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Lurker.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Praetorian.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Predalien.dm" diff --git a/html/changelogs/AutoChangeLog-pr-3455.yml b/html/changelogs/AutoChangeLog-pr-3455.yml new file mode 100644 index 000000000000..c3189ad233bd --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-3455.yml @@ -0,0 +1,7 @@ +author: "realforest2001" +delete-after: True +changes: + - rscadd: "Adds a plasma breaching charge that detonates a plasma wave stunning those opposite it." + - rscadd: "Adds the name of the tracked item to the Yautja gear tracker." + - rscadd: "Added an alternate mode for the Plasma Pistol and moved the incendiary property to it." + - rscadd: "Added MINIMAP_FLAG_ALL to Yautja globe map." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-3757.yml b/html/changelogs/AutoChangeLog-pr-3757.yml new file mode 100644 index 000000000000..4cc6cc928423 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-3757.yml @@ -0,0 +1,4 @@ +author: "realforest2001" +delete-after: True +changes: + - rscadd: "Made Yautja cloak cost no power to operate, and created multipliers for disabled duration based upon what caused the Yautja to decloak." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-3764.yml b/html/changelogs/AutoChangeLog-pr-3764.yml new file mode 100644 index 000000000000..ecb789daaf91 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-3764.yml @@ -0,0 +1,7 @@ +author: "ghostsheet" +delete-after: True +changes: + - rscadd: "Large General Pouch has stricter restriction against internal boxes." + - rscadd: "Large General Pouch no longer restricted to 1 medium item." + - rscadd: "Large General Pouch added to REQ." + - rscadd: "Shuffled REQ pouch order into Meds, Engi, Misc, Ammo." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-3788.yml b/html/changelogs/AutoChangeLog-pr-3788.yml deleted file mode 100644 index 0efdf59a6d3e..000000000000 --- a/html/changelogs/AutoChangeLog-pr-3788.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "JackieEstegado" -delete-after: True -changes: - - maptweak: "Made LV-624's \"Fully-locked\" Research nightmare insert no longer have indestructible blast doors." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-3868.yml b/html/changelogs/AutoChangeLog-pr-3868.yml new file mode 100644 index 000000000000..4bd11e9a6b0e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-3868.yml @@ -0,0 +1,6 @@ +author: "MarpleJones, ihatethisengine2" +delete-after: True +changes: + - rscadd: "Added new sprites for the Eggsac Carrier. Includes an additional death sound for the eggsac bursting." + - rscadd: "Added a hive announcement for when a Carrier dies with eggs." + - bugfix: "Carrier egg drop chance upon death now works as intended." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-3939.yml b/html/changelogs/AutoChangeLog-pr-3939.yml new file mode 100644 index 000000000000..97bac6e085de --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-3939.yml @@ -0,0 +1,4 @@ +author: "Morrow" +delete-after: True +changes: + - rscadd: "Added lesser drones" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-3949.yml b/html/changelogs/AutoChangeLog-pr-3949.yml new file mode 100644 index 000000000000..eca0cae6dc4a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-3949.yml @@ -0,0 +1,4 @@ +author: "Morrow" +delete-after: True +changes: + - bugfix: "Stops multiple facehuggers spawning from one client" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-3964.yml b/html/changelogs/AutoChangeLog-pr-3964.yml new file mode 100644 index 000000000000..ccd54d9eda5f --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-3964.yml @@ -0,0 +1,4 @@ +author: "Drathek" +delete-after: True +changes: + - bugfix: "Fix bodybags not accepting warm (recent) dead bodies even if unreviveable" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-3975.yml b/html/changelogs/AutoChangeLog-pr-3975.yml new file mode 100644 index 000000000000..6f871b20bbb6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-3975.yml @@ -0,0 +1,5 @@ +author: "Drathek" +delete-after: True +changes: + - bugfix: "Fixed ability deactivation and late join current slot toggles not persisting." + - bugfix: "Toggle the Ability to Hurt Yourself now says On when you can hurt yourself." \ No newline at end of file diff --git a/html/changelogs/archive/2023-07.yml b/html/changelogs/archive/2023-07.yml index 217a2059e0dd..99120b3a13c4 100644 --- a/html/changelogs/archive/2023-07.yml +++ b/html/changelogs/archive/2023-07.yml @@ -403,3 +403,9 @@ system. Their types are also now distinguishable at a glance. - code_imp: Reworked the way blood bag sprites work behind the scenes to use the overlay/underlay system. +2023-07-24: + JackieEstegado: + - maptweak: Made LV-624's "Fully-locked" Research nightmare insert no longer have + indestructible blast doors. + Morrow: + - bugfix: Fixes wood window numbering diff --git a/icons/mob/hud/actions.dmi b/icons/mob/hud/actions.dmi index 4d0697733207..9f885c44f50f 100644 Binary files a/icons/mob/hud/actions.dmi and b/icons/mob/hud/actions.dmi differ diff --git a/icons/mob/xenonids/lesser_drone.dmi b/icons/mob/xenonids/lesser_drone.dmi new file mode 100644 index 000000000000..bff44e659162 Binary files /dev/null and b/icons/mob/xenonids/lesser_drone.dmi differ diff --git a/icons/mob/xenos/carrier.dmi b/icons/mob/xenos/carrier.dmi index 6832bbba8716..4b69cffd5313 100644 Binary files a/icons/mob/xenos/carrier.dmi and b/icons/mob/xenos/carrier.dmi differ diff --git a/icons/mob/xenos/lesser_drone.dmi b/icons/mob/xenos/lesser_drone.dmi new file mode 100644 index 000000000000..134ec0c4ae7c Binary files /dev/null and b/icons/mob/xenos/lesser_drone.dmi differ diff --git a/icons/obj/items/assemblies.dmi b/icons/obj/items/assemblies.dmi index 668d62d23d4e..522e0fb5e55d 100644 Binary files a/icons/obj/items/assemblies.dmi and b/icons/obj/items/assemblies.dmi differ diff --git a/icons/turf/walls/windows.dmi b/icons/turf/walls/windows.dmi index 85f822873e68..a3f2fd1d4198 100644 Binary files a/icons/turf/walls/windows.dmi and b/icons/turf/walls/windows.dmi differ diff --git a/icons/ui_icons/map_blips.dmi b/icons/ui_icons/map_blips.dmi index 85ef9959027f..829d9b8a43b0 100644 Binary files a/icons/ui_icons/map_blips.dmi and b/icons/ui_icons/map_blips.dmi differ