diff --git a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm index f4df347c62db..9d3d7549dca9 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm @@ -45,9 +45,9 @@ #define COMSIG_MOB_ATTEMPTING_EQUIP "mob_attempting_equip" #define COMPONENT_MOB_CANCEL_ATTEMPT_EQUIP (1<<0) -/// For when a mob is devoured by a Xeno -#define COMSIG_MOB_DEVOURED "mob_devoured" - #define COMPONENT_CANCEL_DEVOUR (1<<0) +/// For when a mob is hauled by a Xeno +#define COMSIG_MOB_HAULED "mob_hauled" + #define COMPONENT_CANCEL_HAUL (1<<0) // Reserved for tech trees #define COMSIG_MOB_ENTER_TREE "mob_enter_tree" #define COMPONENT_CANCEL_TREE_ENTRY (1<<0) diff --git a/code/__DEFINES/job.dm b/code/__DEFINES/job.dm index b99bce50519d..c33c8be76873 100644 --- a/code/__DEFINES/job.dm +++ b/code/__DEFINES/job.dm @@ -63,6 +63,7 @@ GLOBAL_LIST_INIT(job_squad_roles, JOB_SQUAD_ROLES_LIST) #define JOB_RESEARCHER "Researcher" #define JOB_MEDIC_ROLES /datum/timelock/medic #define JOB_MEDIC_ROLES_LIST list(JOB_SQUAD_MEDIC, JOB_CMO, JOB_DOCTOR, JOB_FIELD_DOCTOR, JOB_NURSE, JOB_RESEARCHER, JOB_SURGEON) +#define JOB_DOCTOR_ROLES_LIST list(JOB_CMO, JOB_DOCTOR, JOB_SURGEON) #define JOB_CORPORATE_LIAISON "Corporate Liaison" diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm index 26567be26255..dc58682d9483 100644 --- a/code/__DEFINES/text.dm +++ b/code/__DEFINES/text.dm @@ -24,3 +24,5 @@ #define MAX_PAPER_MESSAGE_LEN 3072 #define MAX_BOOK_MESSAGE_LEN 9216 #define MAX_NAME_LEN 28 + +#define MAX_MESSAGE_CHUNKS 20 diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 12e105c14655..cf48dabcb9b9 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -163,6 +163,8 @@ #define TRAIT_TEMPORARILY_MUTED "temporarily_muted" /// Mob wont get hit by stray projectiles #define TRAIT_NO_STRAY "trait_no_stray" +/// When a Xeno hauls us. We can take out our knife or gun if hauled though we are immobilized, also shielded from most damage. +#define TRAIT_HAULED "hauled" // only used by valkyrie #define TRAIT_VALKYRIE_ARMORED "trait_valkyrie_armored" @@ -439,6 +441,8 @@ GLOBAL_LIST(trait_name_map) #define TRAIT_SOURCE_STRAIN "t_s_strain" ///Status trait coming from being buckled. #define TRAIT_SOURCE_BUCKLE "t_s_buckle" +//Status trait coming from being hauled by a xeno. +#define TRAIT_SOURCE_XENO_HAUL "t_s_xeno_haul" ///Status trait coming from being assigned as [acting] squad leader. #define TRAIT_SOURCE_SQUAD_LEADER "t_s_squad_leader" ///Status trait coming from their job diff --git a/code/__DEFINES/typecheck/xenos.dm b/code/__DEFINES/typecheck/xenos.dm index 34b70ac92f45..c84aa1e30426 100644 --- a/code/__DEFINES/typecheck/xenos.dm +++ b/code/__DEFINES/typecheck/xenos.dm @@ -41,6 +41,9 @@ var/datum/hive_status/corrupted/renegade/renegade_hive = hive return renegade_hive.iff_protection_check(src, attempt_harm_mob) + if(HAS_TRAIT(attempt_harm_mob, TRAIT_HAULED)) + return TRUE + return hive.is_ally(attempt_harm_mob) // need this to set the data for walls/eggs/huggers when they are initialized diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm index e160a334bce5..a0b957c05a75 100644 --- a/code/__DEFINES/xeno.dm +++ b/code/__DEFINES/xeno.dm @@ -8,8 +8,10 @@ #define TUNNEL_ENTER_BIG_XENO_DELAY 120 #define TUNNEL_ENTER_LARVA_DELAY 10 -/// The duration it takes a player controlled facehugger to leap or hug adjacently -#define FACEHUGGER_WINDUP_DURATION 1 SECONDS +/// The duration it takes a player controlled facehugger to leap +#define FACEHUGGER_LEAP_DURATION 2 SECONDS +/// The duration it takes a player controlled facehugger to hug a target lying down by clicking on it +#define FACEHUGGER_CLIMB_DURATION 1 SECONDS // Defines for action types and click delays used by xenomorph/unarmedattack() and attack_alien(). diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index dcdabe1068a8..fae9d1a82fe6 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -438,22 +438,37 @@ return target -/proc/urange(dist=0, atom/center=usr, orange=0, areas=0) +/** + * An alternative to orange() that should perform better for distances >= 5 because + * of ORANGE_TURFS utilizing RECT_TURFS. + * + * Returns a list of turfs and those turf's contents, excluding center (and + * its contents). Ignores visibility (like orange). + */ +/proc/long_orange(dist=0, atom/center) if(!dist) - if(!orange) - return list(center) - else - return list() + return list() + var/list/turfs = ORANGE_TURFS(dist, center) + . = list() + for(var/turf/cur_turf as anything in turfs) + . += cur_turf + . += cur_turf.contents + . -= center // If center isn't a turf + +/** + * An alternative to range() that should perform better for distances >= 5 because + * of RANGE_TURFS utilizing RECT_TURFS. + * + * Returns a list of turfs and those turf's contents, including center (and its + * contents if a turf). Ignores visibility (like range). + */ +/proc/long_range(dist=0, atom/center) var/list/turfs = RANGE_TURFS(dist, center) - if(orange) - turfs -= get_turf(center) . = list() - for(var/turf/T as anything in turfs) - . += T - . += T.contents - if(areas) - . |= T.loc + for(var/turf/cur_turf as anything in turfs) + . += cur_turf + . += cur_turf.contents // returns turf relative to A in given direction at set range // result is bounded to map size diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 9c1d39ebbf25..31e26bd0498d 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -145,6 +145,9 @@ return /mob/proc/click_adjacent(atom/targeted_atom, obj/item/used_item, mods) + if(HAS_TRAIT(src, TRAIT_HAULED)) + if(!isstorage(targeted_atom) && !isclothing(targeted_atom) && !isweapon(targeted_atom) && !isgun(targeted_atom)) + return if(used_item) var/attackby_result = targeted_atom.attackby(used_item, src, mods) var/afterattack_result diff --git a/code/_onclick/human.dm b/code/_onclick/human.dm index ffa7b376acca..d30fe12992b0 100644 --- a/code/_onclick/human.dm +++ b/code/_onclick/human.dm @@ -70,7 +70,7 @@ /mob/living/carbon/human/UnarmedAttack(atom/A, proximity, click_parameters) - if(body_position == LYING_DOWN) //No attacks while laying down + if(body_position == LYING_DOWN && !HAS_TRAIT(src, TRAIT_HAULED)) //No attacks while laying down return 0 var/obj/item/clothing/gloves/G = gloves // not typecast specifically enough in defines diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 1631bc7bd12e..d1208fb45bb9 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -1,6 +1,8 @@ // Called when the item is in the active hand, and clicked; alternately, there is an 'activate held object' verb or you can hit pagedown. /obj/item/proc/attack_self(mob/user) + if(HAS_TRAIT(user, TRAIT_HAULED)) + return SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SELF, user) SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK_SELF, src) @@ -35,6 +37,8 @@ else if(initiate_surgery_moment(I, src, null, user)) return TRUE */ + if(HAS_TRAIT(user, TRAIT_HAULED)) + return if(istype(I) && ismob(user)) return I.attack(src, user) diff --git a/code/datums/ammo/shrapnel.dm b/code/datums/ammo/shrapnel.dm index 02a57202ff53..f98c9723f1b1 100644 --- a/code/datums/ammo/shrapnel.dm +++ b/code/datums/ammo/shrapnel.dm @@ -6,26 +6,24 @@ /datum/ammo/bullet/shrapnel name = "shrapnel" icon_state = "buckshot" + accurate_range_min = 5 flags_ammo_behavior = AMMO_BALLISTIC|AMMO_STOPPED_BY_COVER - accuracy = HIT_ACCURACY_TIER_4 - accurate_range = 7 - max_range = 10 - damage = 30 - penetration = ARMOR_PENETRATION_TIER_2 + accuracy = HIT_ACCURACY_TIER_3 + accurate_range = 32 + max_range = 8 + damage = 25 + damage_var_low = -PROJECTILE_VARIANCE_TIER_6 + damage_var_high = PROJECTILE_VARIANCE_TIER_6 + penetration = ARMOR_PENETRATION_TIER_4 shell_speed = AMMO_SPEED_TIER_2 + shrapnel_chance = 5 /datum/ammo/bullet/shrapnel/on_hit_obj(obj/O, obj/projectile/P) if(istype(O, /obj/structure/barricade)) var/obj/structure/barricade/B = O - B.health -= rand(5, 10) + B.health -= rand(2, 5) B.update_health(1) -/datum/ammo/bullet/shrapnel/on_hit_mob(mob/living/carbon/xeno, obj/projectile/projectile, mob/user) - if(!shrapnel_chance) // no shrapnell , no special effects - return - if(isxeno(xeno)) - xeno.apply_effect(4, SLOW) // multiple hits dont stack they just renew the duration - xeno.apply_armoured_damage(damage * 0.6, ARMOR_BULLET, BRUTE, , penetration) // xenos have a lot of HP /datum/ammo/bullet/shrapnel/breaching/set_bullet_traits() . = ..() @@ -49,7 +47,7 @@ name = ".22 hornet round" icon_state = "hornet_round" flags_ammo_behavior = AMMO_BALLISTIC - damage = 10 + damage = 8 shrapnel_chance = 0 shell_speed = AMMO_SPEED_TIER_3//she fast af boi penetration = ARMOR_PENETRATION_TIER_5 @@ -70,7 +68,7 @@ icon_state = "beanbag" // looks suprisingly a lot like flaming shrapnel chunks flags_ammo_behavior = AMMO_STOPPED_BY_COVER shell_speed = AMMO_SPEED_TIER_1 - damage = 30 + damage = 20 penetration = ARMOR_PENETRATION_TIER_4 /datum/ammo/bullet/shrapnel/incendiary/set_bullet_traits() @@ -95,6 +93,7 @@ /datum/ammo/bullet/shrapnel/metal name = "metal shrapnel" icon_state = "shrapnelshot_bit" + flags_ammo_behavior = AMMO_STOPPED_BY_COVER|AMMO_BALLISTIC shell_speed = AMMO_SPEED_TIER_1 damage = 30 shrapnel_chance = 15 diff --git a/code/datums/ammo/xeno.dm b/code/datums/ammo/xeno.dm index 653beba573e9..11873a7202d5 100644 --- a/code/datums/ammo/xeno.dm +++ b/code/datums/ammo/xeno.dm @@ -162,7 +162,7 @@ /datum/ammo/xeno/acid/on_hit_mob(mob/M, obj/projectile/P) if(iscarbon(M)) var/mob/living/carbon/C = M - if(C.status_flags & XENO_HOST && HAS_TRAIT(C, TRAIT_NESTED) || C.stat == DEAD) + if(C.status_flags & XENO_HOST && HAS_TRAIT(C, TRAIT_NESTED) || C.stat == DEAD || HAS_TRAIT(C, TRAIT_HAULED)) return FALSE ..() @@ -252,7 +252,7 @@ /datum/ammo/xeno/boiler_gas/on_hit_mob(mob/moob, obj/projectile/proj) if(iscarbon(moob)) var/mob/living/carbon/carbon = moob - if(carbon.status_flags & XENO_HOST && HAS_TRAIT(carbon, TRAIT_NESTED) || carbon.stat == DEAD) + if(carbon.status_flags & XENO_HOST && HAS_TRAIT(carbon, TRAIT_NESTED) || carbon.stat == DEAD || HAS_TRAIT(carbon, TRAIT_HAULED)) return var/datum/effects/neurotoxin/neuro_effect = locate() in moob.effects_list if(!neuro_effect) @@ -302,7 +302,7 @@ /datum/ammo/xeno/boiler_gas/acid/on_hit_mob(mob/moob, obj/projectile/proj) if(iscarbon(moob)) var/mob/living/carbon/carbon = moob - if(carbon.status_flags & XENO_HOST && HAS_TRAIT(carbon, TRAIT_NESTED) || carbon.stat == DEAD) + if(carbon.status_flags & XENO_HOST && HAS_TRAIT(carbon, TRAIT_NESTED) || carbon.stat == DEAD || HAS_TRAIT(carbon, TRAIT_HAULED)) return to_chat(moob,SPAN_HIGHDANGER("Acid covers your body! Oh fuck!")) playsound(moob,"acid_strike",75,1) @@ -330,7 +330,7 @@ /datum/ammo/xeno/bone_chips/on_hit_mob(mob/living/M, obj/projectile/P) if(iscarbon(M)) var/mob/living/carbon/C = M - if((HAS_FLAG(C.status_flags, XENO_HOST) && HAS_TRAIT(C, TRAIT_NESTED)) || C.stat == DEAD) + if((HAS_FLAG(C.status_flags, XENO_HOST) && HAS_TRAIT(C, TRAIT_NESTED)) || C.stat == DEAD || HAS_TRAIT(C, TRAIT_HAULED)) return if(ishuman_strict(M) || isxeno(M)) playsound(M, 'sound/effects/spike_hit.ogg', 25, 1, 1) @@ -360,7 +360,7 @@ /datum/ammo/xeno/bone_chips/spread/runner/on_hit_mob(mob/living/M, obj/projectile/P) if(iscarbon(M)) var/mob/living/carbon/C = M - if((HAS_FLAG(C.status_flags, XENO_HOST) && HAS_TRAIT(C, TRAIT_NESTED)) || C.stat == DEAD) + if((HAS_FLAG(C.status_flags, XENO_HOST) && HAS_TRAIT(C, TRAIT_NESTED)) || C.stat == DEAD || HAS_TRAIT(C, TRAIT_HAULED)) return if(ishuman_strict(M) || isxeno(M)) playsound(M, 'sound/effects/spike_hit.ogg', 25, 1, 1) diff --git a/code/datums/diseases/xeno_transformation.dm b/code/datums/diseases/xeno_transformation.dm index 9bd5b965c2cc..9f49c2f1259a 100644 --- a/code/datums/diseases/xeno_transformation.dm +++ b/code/datums/diseases/xeno_transformation.dm @@ -31,13 +31,13 @@ if (prob(4)) to_chat(affected_mob, SPAN_DANGER("You can feel something move...inside.")) if (prob(5)) - to_chat(affected_mob, "\italic " + pick("Soon we will be one...", "Must... devour...", "Capture...", "We are perfect.")) + to_chat(affected_mob, "\italic " + pick("Soon we will be one...", "Must... evolve...", "Capture...", "We are perfect.")) if(4) if (prob(10)) to_chat(affected_mob, pick(SPAN_DANGER("Your skin feels very tight."), SPAN_DANGER("Your blood boils!"))) affected_mob.take_limb_damage(3) if (prob(5)) - affected_mob.whisper(pick("Soon we will be one...", "Must... devour...", "Capture...", "We are perfect.", "Hsssshhhhh!")) + affected_mob.whisper(pick("Soon we will be one...", "Must... evolve...", "Capture...", "We are perfect.", "Hsssshhhhh!")) if (prob(8)) to_chat(affected_mob, SPAN_DANGER("You can feel... something...inside you.")) if(5) diff --git a/code/datums/effects/acid.dm b/code/datums/effects/acid.dm index 8d800ef0bfa0..f98afe012ef4 100644 --- a/code/datums/effects/acid.dm +++ b/code/datums/effects/acid.dm @@ -41,7 +41,7 @@ if(ishuman(A)) var/mob/living/carbon/human/H = A - if(H.status_flags & XENO_HOST && HAS_TRAIT(H, TRAIT_NESTED) || H.stat == DEAD) + if(H.status_flags & XENO_HOST && HAS_TRAIT(H, TRAIT_NESTED) || H.stat == DEAD || HAS_TRAIT(H, TRAIT_HAULED)) return FALSE . = ..() diff --git a/code/datums/pain/_pain.dm b/code/datums/pain/_pain.dm index 6c494fc65ed2..a648eb8cf427 100644 --- a/code/datums/pain/_pain.dm +++ b/code/datums/pain/_pain.dm @@ -195,7 +195,7 @@ if(new_level >= PAIN_LEVEL_SEVERE && feels_pain) RegisterSignal(source_mob, COMSIG_MOB_DRAGGED, PROC_REF(oxyloss_drag), override = TRUE) - RegisterSignal(source_mob, COMSIG_MOB_DEVOURED, PROC_REF(handle_devour), override = TRUE) + RegisterSignal(source_mob, COMSIG_MOB_HAULED, PROC_REF(handle_haul), override = TRUE) RegisterSignal(source_mob, COMSIG_MOVABLE_PRE_THROW, PROC_REF(oxy_kill), override = TRUE) last_level = new_level @@ -232,7 +232,7 @@ if(new_level < PAIN_LEVEL_SEVERE) UnregisterSignal(source_mob, list( COMSIG_MOB_DRAGGED, - COMSIG_MOB_DEVOURED, + COMSIG_MOB_HAULED, COMSIG_MOVABLE_PRE_THROW )) @@ -294,14 +294,14 @@ if(H.species.flags & HAS_HARDCRIT) source.apply_damage(20, OXY) -/datum/pain/proc/handle_devour(mob/living/source, mob/living/carbon/xenomorph/devourer) +/datum/pain/proc/handle_haul(mob/living/source, mob/living/carbon/xenomorph/hauler) SIGNAL_HANDLER if(source.chestburst) return - if(source.ally_of_hivenumber(devourer.hivenumber)) + if(source.ally_of_hivenumber(hauler.hivenumber)) return oxy_kill(source) - return COMPONENT_CANCEL_DEVOUR + return COMPONENT_CANCEL_HAUL /datum/pain/proc/oxy_kill(mob/living/source) SIGNAL_HANDLER diff --git a/code/datums/pain/pain_yautja.dm b/code/datums/pain/pain_yautja.dm index 94f5e8d33650..e2acce0ea2e4 100644 --- a/code/datums/pain/pain_yautja.dm +++ b/code/datums/pain/pain_yautja.dm @@ -17,7 +17,7 @@ /datum/pain/yautja/oxyloss_drag(mob/living/source, mob/puller) return -/datum/pain/yautja/handle_devour(mob/living/source) +/datum/pain/yautja/handle_haul(mob/living/source) return /datum/pain/yautja/oxy_kill(mob/living/source) diff --git a/code/datums/tutorial/xenomorph/xenomorph_basic.dm b/code/datums/tutorial/xenomorph/xenomorph_basic.dm index ed9a9b810b45..c5e62d543a18 100644 --- a/code/datums/tutorial/xenomorph/xenomorph_basic.dm +++ b/code/datums/tutorial/xenomorph/xenomorph_basic.dm @@ -209,15 +209,15 @@ SIGNAL_HANDLER TUTORIAL_ATOM_FROM_TRACKING(/mob/living/carbon/human, human_dummy) UnregisterSignal(human_dummy, COMSIG_MOVABLE_XENO_START_PULLING) - message_to_player("Well done. Now devour the human by clicking on your character with the grab selected in your hand. You must not move during this process.") - update_objective("Devour the grabbed human by clicking on them with the grab in-hand.") - RegisterSignal(human_dummy, COMSIG_MOB_DEVOURED, PROC_REF(nest_cap_phase_five)) + message_to_player("Well done. Now haul the human by clicking on your character with the grab selected in your hand. You must not move during this process.") + update_objective("Haul the grabbed human by clicking on them with the grab in-hand.") + RegisterSignal(human_dummy, COMSIG_MOB_HAULED, PROC_REF(nest_cap_phase_five)) /datum/tutorial/xenomorph/basic/proc/nest_cap_phase_five() SIGNAL_HANDLER - message_to_player("Well done, you can reguritate the human using the new ability you have gained.") - message_to_player("Be careful. Real humans may put up a fight and can try to cut out of you from inside!") - give_action(xeno, /datum/action/xeno_action/onclick/regurgitate) + message_to_player("Well done, you can release the human using the new ability you have gained.") + message_to_player("Be careful. Real humans may put up a fight and can try to cut out of your grip, killing you!") + give_action(xeno, /datum/action/xeno_action/onclick/release_haul) addtimer(CALLBACK(src, PROC_REF(nest_cap_phase_six)), 15 SECONDS) /datum/tutorial/xenomorph/basic/proc/nest_cap_phase_six() @@ -227,7 +227,7 @@ /datum/tutorial/xenomorph/basic/proc/nest_cap_phase_seven() TUTORIAL_ATOM_FROM_TRACKING(/mob/living/carbon/human, human_dummy) - UnregisterSignal(human_dummy, COMSIG_MOB_DEVOURED) + UnregisterSignal(human_dummy, COMSIG_MOB_HAULED) RegisterSignal(human_dummy, COMSIG_MOB_NESTED, PROC_REF(on_mob_nested)) message_to_player("Nest the captive human!") update_objective("Nest the captive human!") diff --git a/code/game/jobs/job/civilians/other/liaison.dm b/code/game/jobs/job/civilians/other/liaison.dm index 765235233e85..26799242cfd2 100644 --- a/code/game/jobs/job/civilians/other/liaison.dm +++ b/code/game/jobs/job/civilians/other/liaison.dm @@ -6,7 +6,7 @@ selection_class = "job_cl" flags_startup_parameters = ROLE_ADD_TO_DEFAULT gear_preset = /datum/equipment_preset/uscm_ship/liaison - entry_message_body = "As a representative of Weyland-Yutani Corporation, your job requires you to stay in character at all times. While in the AO (Area of Operation), you are subject to orders given by military personnel. On ship, you are subject to orders only by the Command and Security departments. You are not required to follow any orders but you can be arrested if you do not. Your primary job is to observe and report back your findings to Weyland-Yutani. Follow regular game rules unless told otherwise by your superiors. Use your office fax machine to communicate with corporate headquarters or to acquire new directives. You may not receive anything back, and this is normal." + entry_message_body = "As a representative of Weyland-Yutani Corporation from the Corporate Relations Department, your job requires you to stay in character at all times. While in the AO (Area of Operation), you are subject to orders given by military personnel. On ship, you are subject to orders only by the Command and Security departments. You are not required to follow any orders but you can be arrested if you do not. Your primary job is to observe and report back your findings to Weyland-Yutani. Follow regular game rules unless told otherwise by your superiors. Use your office fax machine to communicate with corporate headquarters or to acquire new directives. You may not receive anything back, and this is normal." var/mob/living/carbon/human/active_liaison /datum/job/civilian/liaison/generate_entry_conditions(mob/living/liaison, whitelist_status) diff --git a/code/game/jobs/job/civilians/support/field_doctor.dm b/code/game/jobs/job/civilians/support/field_doctor.dm index 0dfa442ca3ea..9c715bb31884 100644 --- a/code/game/jobs/job/civilians/support/field_doctor.dm +++ b/code/game/jobs/job/civilians/support/field_doctor.dm @@ -12,7 +12,7 @@ entry_message_body = "You are a Field Doctor tasked with keeping the marines healthy and strong in the field, usually in the form of surgery. You must stay onboard the Almayer medical bay if there are no other doctors present and until the FOB is secured. Your superiors may also delay your deployment to the field." AddTimelock(/datum/job/civilian/field_doctor, list( - JOB_MEDIC_ROLES = 5 HOURS + JOB_DOCTOR_ROLES = 5 HOURS )) /obj/effect/landmark/start/field_doctor diff --git a/code/game/machinery/autolathe_datums.dm b/code/game/machinery/autolathe_datums.dm index fcff34a86f8e..a9eee084b34c 100644 --- a/code/game/machinery/autolathe_datums.dm +++ b/code/game/machinery/autolathe_datums.dm @@ -67,6 +67,11 @@ path = /obj/item/tool/wrench category = AUTOLATHE_CATEGORY_TOOLS +/datum/autolathe/recipe/lightreplacer + name = "light replacer" + path = /obj/item/device/lightreplacer/empty + category = AUTOLATHE_CATEGORY_TOOLS + /datum/autolathe/recipe/mop name = "mop" path = /obj/item/tool/mop diff --git a/code/game/machinery/vending/cm_vending.dm b/code/game/machinery/vending/cm_vending.dm index 6940fbdd5355..5f4515167995 100644 --- a/code/game/machinery/vending/cm_vending.dm +++ b/code/game/machinery/vending/cm_vending.dm @@ -844,7 +844,7 @@ GLOBAL_LIST_EMPTY(vending_products) icon_state = "gear" use_points = TRUE vendor_theme = VENDOR_THEME_USCM - vend_flags = VEND_CLUTTER_PROTECTION|VEND_CATEGORY_CHECK|VEND_TO_HAND + vend_flags = VEND_CLUTTER_PROTECTION|VEND_CATEGORY_CHECK|VEND_UNIFORM_AUTOEQUIP /obj/structure/machinery/cm_vending/gear/ui_static_data(mob/user) . = ..(user) diff --git a/code/game/machinery/vending/vendor_types/engineering.dm b/code/game/machinery/vending/vendor_types/engineering.dm index 35e78e5cc962..1fa773f9cf65 100644 --- a/code/game/machinery/vending/vendor_types/engineering.dm +++ b/code/game/machinery/vending/vendor_types/engineering.dm @@ -40,7 +40,8 @@ list("ME3 Hand Welder", floor(scale * 2), /obj/item/tool/weldingtool/simple, VENDOR_ITEM_REGULAR), list("Screwdriver", floor(scale * 4), /obj/item/tool/screwdriver, VENDOR_ITEM_REGULAR), list("Wirecutters", floor(scale * 4), /obj/item/tool/wirecutters, VENDOR_ITEM_REGULAR), - list("Wrench", floor(scale * 4), /obj/item/tool/wrench, VENDOR_ITEM_REGULAR) + list("Wrench", floor(scale * 4), /obj/item/tool/wrench, VENDOR_ITEM_REGULAR), + list("Light Replacer", floor(scale * 4), /obj/item/device/lightreplacer, VENDOR_ITEM_REGULAR) ) /obj/structure/machinery/cm_vending/sorted/tech/comtech_tools @@ -65,6 +66,7 @@ list("Wrench", floor(scale * 4), /obj/item/tool/wrench, VENDOR_ITEM_REGULAR), list("Multitool", floor(scale * 4), /obj/item/device/multitool, VENDOR_ITEM_REGULAR), list("ME3 Hand Welder", floor(scale * 2), /obj/item/tool/weldingtool/simple, VENDOR_ITEM_REGULAR), + list("Light Replacer", floor(scale * 4), /obj/item/device/lightreplacer, VENDOR_ITEM_REGULAR), list("UTILITY", -1, null, null), list("Sentry Gun Network Laptop", 4, /obj/item/device/sentry_computer, VENDOR_ITEM_REGULAR), diff --git a/code/game/machinery/vending/vendor_types/intelligence_officer.dm b/code/game/machinery/vending/vendor_types/intelligence_officer.dm index cddebc9562bf..49554f4c78c4 100644 --- a/code/game/machinery/vending/vendor_types/intelligence_officer.dm +++ b/code/game/machinery/vending/vendor_types/intelligence_officer.dm @@ -28,6 +28,9 @@ GLOBAL_LIST_INIT(cm_vending_gear_intelligence_officer, list( list("VP78 Pistol", 15, /obj/item/storage/box/guncase/vp78, null, VENDOR_ITEM_REGULAR), list("SU-6 Smart Pistol", 15, /obj/item/storage/box/guncase/smartpistol, null, VENDOR_ITEM_REGULAR), + list("RESTRICTED GEAR", 0, null, null, null), + list("M276 Pattern Combat Toolbelt Rig", 15, /obj/item/storage/belt/gun/utility, null, VENDOR_ITEM_RECOMMENDED), + list("POUCHES", 0, null, null, null), list("Large Magazine Pouch", 10, /obj/item/storage/pouch/magazine/large, null, VENDOR_ITEM_REGULAR), list("Large Shotgun Shell Pouch", 10, /obj/item/storage/pouch/shotgun/large, null, VENDOR_ITEM_REGULAR), diff --git a/code/game/objects/effects/effect_system/smoke.dm b/code/game/objects/effects/effect_system/smoke.dm index 4c7752538515..1ad4ed34127a 100644 --- a/code/game/objects/effects/effect_system/smoke.dm +++ b/code/game/objects/effects/effect_system/smoke.dm @@ -543,7 +543,7 @@ return FALSE if(isyautja(affected_mob) && prob(75)) return FALSE - if(HAS_TRAIT(affected_mob, TRAIT_NESTED) && affected_mob.status_flags & XENO_HOST) + if(HAS_TRAIT(affected_mob, TRAIT_NESTED) && affected_mob.status_flags & XENO_HOST || HAS_TRAIT(affected_mob, TRAIT_HAULED)) return FALSE affected_mob.last_damage_data = cause_data @@ -600,7 +600,7 @@ return FALSE if(isyautja(moob)) return FALSE - if(HAS_TRAIT(moob, TRAIT_NESTED) && moob.status_flags & XENO_HOST) + if(HAS_TRAIT(moob, TRAIT_NESTED) && moob.status_flags & XENO_HOST || HAS_TRAIT(moob, TRAIT_HAULED)) return FALSE var/mob/living/carbon/human/human_moob @@ -658,7 +658,7 @@ return FALSE if(isyautja(moob) && prob(75)) return FALSE - if(HAS_TRAIT(moob, TRAIT_NESTED) && moob.status_flags & XENO_HOST) + if(HAS_TRAIT(moob, TRAIT_NESTED) && moob.status_flags & XENO_HOST || HAS_TRAIT(moob, TRAIT_HAULED)) return FALSE var/mob/living/carbon/human/human_moob diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm index 42af8347a5ee..dcf2ad5c17e4 100644 --- a/code/game/objects/items/devices/lightreplacer.dm +++ b/code/game/objects/items/devices/lightreplacer.dm @@ -41,7 +41,7 @@ /obj/item/device/lightreplacer name = "light replacer" - desc = "A device to automatically replace lights. Refill with working lightbulbs." + desc = "A device to automatically replace lights. Can be refill with working lightbulbs and sheets of glass, and can recycle broken lightbulbs." icon = 'icons/obj/janitor.dmi' icon_state = "lightreplacer0" @@ -54,51 +54,59 @@ flags_atom = FPRINT|CONDUCT flags_equip_slot = SLOT_WAIST + matter = list("metal" = 20,"glass" = 50) var/max_uses = 50 - var/uses = 0 + var/uses = 50 var/failmsg = "" var/charge = 1 + var/recycle = 0 + var/max_recycle = 3 + +/obj/item/device/lightreplacer/empty + uses = 0 /obj/item/device/lightreplacer/Initialize() . = ..() - uses = max_uses failmsg = "The [name]'s refill light blinks red." /obj/item/device/lightreplacer/get_examine_text(mob/user) . = ..() - . += "It has [uses] lights remaining." + . += "It has [uses] lights remaining, and [recycle] broken lights stored." /obj/item/device/lightreplacer/attackby(obj/item/W, mob/user) if(istype(W, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = W + var/obj/item/stack/sheet/glass/glass = W if(uses >= max_uses) - to_chat(user, SPAN_WARNING("[src.name] is full.")) + to_chat(user, SPAN_WARNING("[src] is full.")) return - else if(G.use(1)) + else if(glass.use(1)) AddUses(5) - to_chat(user, SPAN_NOTICE("You insert a piece of glass into the [src.name]. You have [uses] lights remaining.")) + to_chat(user, SPAN_NOTICE("You insert a piece of glass into the [src]. You have [uses] lights remaining.")) return else to_chat(user, SPAN_WARNING("You need one sheet of glass to replace lights.")) if(istype(W, /obj/item/light_bulb)) - var/obj/item/light_bulb/L = W - if(L.status == 0) // LIGHT OKAY + var/obj/item/light_bulb/bulb = W + if(bulb.status == 0) // LIGHT OKAY if(uses < max_uses) AddUses(1) - to_chat(user, "You insert the [L.name] into the [src.name]. You have [uses] lights remaining.") + to_chat(user, SPAN_NOTICE("You insert the [bulb] into [src]. You have [uses] lights remaining.")) user.drop_held_item() - qdel(L) + qdel(bulb) return else - to_chat(user, "You need a working light.") + Recycle() + to_chat(user, SPAN_NOTICE("You insert the [bulb] into [src] for recycling.")) + user.drop_held_item() + qdel(bulb) return /obj/item/device/lightreplacer/attack_self(mob/user) ..() - to_chat(usr, "It has [uses] lights remaining.") + to_chat(usr, "It has [uses] lights remaining, and has [recycle] broken lights stored.") /obj/item/device/lightreplacer/update_icon() icon_state = "lightreplacer0" @@ -106,12 +114,12 @@ /obj/item/device/lightreplacer/proc/Use(mob/user) - playsound(src.loc, 'sound/machines/click.ogg', 25, 1) AddUses(-1) return 1 // Negative numbers will subtract /obj/item/device/lightreplacer/proc/AddUses(amount = 1) + playsound(src, 'sound/machines/click.ogg', 25, 1) uses = min(max(uses + amount, 0), max_uses) /obj/item/device/lightreplacer/proc/Charge(mob/user) @@ -120,6 +128,16 @@ AddUses(1) charge = 1 +/obj/item/device/lightreplacer/proc/Recycle(mob/living/U) + if(recycle == max_recycle) + recycle = 0 + AddUses(1) + playsound(src, 'sound/machines/ding.ogg', 5, 1) + return + else + playsound(src, 'sound/machines/click.ogg', 25, 1) + recycle += 1 + /obj/item/device/lightreplacer/proc/ReplaceLight(obj/structure/machinery/light/target, mob/living/U) if(target.status != LIGHT_OK) @@ -130,33 +148,28 @@ if(target.status != LIGHT_EMPTY) - var/obj/item/light_bulb/L1 = new target.light_type(target.loc) - L1.status = target.status - L1.rigged = target.rigged - L1.brightness = target.brightness - L1.switchcount = target.switchcount target.switchcount = 0 - L1.update() - target.status = LIGHT_EMPTY target.update() - var/obj/item/light_bulb/L2 = new target.light_type() + Recycle() + + var/obj/item/light_bulb/bulb = new target.light_type() - target.status = L2.status - target.switchcount = L2.switchcount + target.status = bulb.status + target.switchcount = bulb.switchcount target.rigged = FALSE - target.brightness = L2.brightness + target.brightness = bulb.brightness target.on = target.has_power() target.update() - qdel(L2) + qdel(bulb) if(target.on && target.rigged) target.explode() return else - to_chat(U, failmsg) + to_chat(U, SPAN_DANGER(failmsg)) return else to_chat(U, "There is a working [target.fitting] already inserted.") diff --git a/code/game/objects/items/devices/radio/listening_bugs.dm b/code/game/objects/items/devices/radio/listening_bugs.dm index a87c19245ac3..a3a381997795 100644 --- a/code/game/objects/items/devices/radio/listening_bugs.dm +++ b/code/game/objects/items/devices/radio/listening_bugs.dm @@ -279,6 +279,11 @@ subspace_switchable = FALSE broadcasting = TRUE bug_broadcast_level = LISTENING_BUG_NEVER //Don't want fax responder devices broadcasting to ghosts because it will duplicate a lot of messages every round all the time. + alpha = 0 + mouse_opacity = 0 + explo_proof = TRUE + unacidable = TRUE + emp_proof = TRUE /obj/item/device/radio/listening_bug/radio_linked/fax/wy frequency = FAX_WY_FREQ diff --git a/code/game/objects/items/explosives/grenades/grenade.dm b/code/game/objects/items/explosives/grenades/grenade.dm index f157d7f8d931..2ddd588890d1 100644 --- a/code/game/objects/items/explosives/grenades/grenade.dm +++ b/code/game/objects/items/explosives/grenades/grenade.dm @@ -44,6 +44,9 @@ to_chat(user, SPAN_WARNING("Your programming prevents you from using this!")) return FALSE + if(HAS_TRAIT(user, TRAIT_HAULED)) // If somehow they have a grenade in hand while hauled, we don't want them to prime it + return FALSE + return TRUE /obj/item/explosive/grenade/dropped(mob/user) diff --git a/code/game/objects/items/reagent_containers/hypospray.dm b/code/game/objects/items/reagent_containers/hypospray.dm index 36762ba1ea90..b220382ac9ec 100644 --- a/code/game/objects/items/reagent_containers/hypospray.dm +++ b/code/game/objects/items/reagent_containers/hypospray.dm @@ -35,7 +35,8 @@ /obj/item/reagent_container/hypospray/attack_self(mob/user) ..() - + if(HAS_TRAIT(user, TRAIT_HAULED)) + return if(next_inject > world.time) return next_inject = world.time + inject_cd diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 8ed279867678..590c8d68e6ec 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -96,7 +96,7 @@ GLOBAL_LIST_INIT_TYPED(metal_recipes, /datum/stack_recipe, list ( \ * Plasteel */ GLOBAL_LIST_INIT_TYPED(plasteel_recipes, /datum/stack_recipe, list ( \ - new/datum/stack_recipe("folding plasteel barricade", /obj/structure/barricade/plasteel, 8, time = 4 SECONDS, one_per_turf = ONE_TYPE_PER_TURF, on_floor = 1, skill_req = SKILL_CONSTRUCTION, skill_lvl = SKILL_CONSTRUCTION_ENGI, min_time = 2 SECONDS), + new/datum/stack_recipe("folding plasteel barricade", /obj/structure/barricade/plasteel, 8, time = 4 SECONDS, one_per_turf = ONE_TYPE_PER_BORDER, on_floor = 1, skill_req = SKILL_CONSTRUCTION, skill_lvl = SKILL_CONSTRUCTION_ENGI, min_time = 2 SECONDS), new/datum/stack_recipe("plasteel barricade", /obj/structure/barricade/metal/plasteel, 6, time = 8 SECONDS, one_per_turf = ONE_TYPE_PER_BORDER, on_floor = 1, skill_req = SKILL_CONSTRUCTION, skill_lvl = SKILL_CONSTRUCTION_ENGI, min_time = 2 SECONDS), null, \ new/datum/stack_recipe("reinforced window frame", /obj/structure/window_frame/colony/reinforced, 5, time = 40, one_per_turf = ONE_TYPE_PER_TURF, on_floor = 1, skill_req = SKILL_CONSTRUCTION, skill_lvl = SKILL_CONSTRUCTION_ENGI), diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm index 75c067c7f037..b4203f14c95f 100644 --- a/code/game/objects/items/storage/storage.dm +++ b/code/game/objects/items/storage/storage.dm @@ -396,14 +396,21 @@ GLOBAL_LIST_EMPTY_TYPED(item_storage_box_cache, /datum/item_storage_box) ///Returns TRUE if there is room for the given item. W_class_override allows checking for just a generic W_class, meant for checking shotgun handfuls without having to spawn and delete one just to check. /obj/item/storage/proc/has_room(obj/item/new_item, W_class_override = null) for(var/obj/item/cur_item in contents) - if(!istype(cur_item, /obj/item/stack) || !istype(new_item, /obj/item/stack)) - continue + if(istype(cur_item, /obj/item/stack) && istype(new_item, /obj/item/stack)) + var/obj/item/stack/cur_stack = cur_item + var/obj/item/stack/new_stack = new_item - var/obj/item/stack/cur_stack = cur_item - var/obj/item/stack/new_stack = new_item + if(cur_stack.amount < cur_stack.max_amount && new_stack.stack_id == cur_stack.stack_id) + return TRUE - if(cur_stack.amount < cur_stack.max_amount && new_stack.stack_id == cur_stack.stack_id) - return TRUE + else if(istype(cur_item, /obj/item/ammo_magazine/handful) && istype(new_item, /obj/item/ammo_magazine/handful)) + var/obj/item/ammo_magazine/handful/cur_handful = cur_item + var/obj/item/ammo_magazine/handful/new_handful = new_item + + if(cur_handful.is_transferable(new_handful)) + return TRUE + else + continue if(storage_slots != null && length(contents) < storage_slots) return TRUE //At least one open slot. @@ -522,6 +529,21 @@ user can be null, it refers to the potential mob doing the insertion.**/ return FALSE else return TRUE + else if(istype(new_item, /obj/item/ammo_magazine/handful)) + var/obj/item/ammo_magazine/handful/new_handful = new_item + + for(var/obj/item/cur_item in contents) + if(!istype(cur_item, /obj/item/ammo_magazine/handful)) + continue + var/obj/item/ammo_magazine/handful/cur_handful = cur_item + if(cur_handful.is_transferable(new_handful)) + cur_handful.transfer_ammo(new_handful, user, new_handful.current_rounds) + + if(!QDELETED(new_handful) && can_be_inserted(new_handful, user)) + if(!user.drop_inv_item_to_loc(new_handful, src)) + return FALSE + else + return TRUE if(!user.drop_inv_item_to_loc(new_item, src)) return FALSE @@ -616,6 +638,8 @@ W is always an item. stop_warning prevents messaging. user may be null.**/ return handle_item_insertion(W, prevent_warning, user) /obj/item/storage/attack_hand(mob/user, mods) + if(HAS_TRAIT(user, TRAIT_HAULED)) + return if (loc == user) if((mods && mods["alt"] || storage_flags & STORAGE_USING_DRAWING_METHOD) && ishuman(user) && length(contents)) //Alt mod can reach attack_hand through the clicked() override. var/obj/item/I diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm index e72bf008927a..68a5f4ed074e 100644 --- a/code/game/objects/items/weapons/twohanded.dm +++ b/code/game/objects/items/weapons/twohanded.dm @@ -253,7 +253,7 @@ item_icons = list( WEAR_L_HAND = 'icons/mob/humans/onmob/inhands/weapons/melee/spears_lefthand.dmi', WEAR_R_HAND = 'icons/mob/humans/onmob/inhands/weapons/melee/spears_righthand.dmi', - WEAR_BACK = 'icons/mob/humans/onmob/clothing/back/misc.dmi' + WEAR_BACK = 'icons/mob/humans/onmob/clothing/back/melee_weapons.dmi' ) w_class = SIZE_LARGE flags_equip_slot = SLOT_BACK @@ -350,7 +350,7 @@ item_icons = list( WEAR_L_HAND = 'icons/mob/humans/onmob/inhands/equipment/tools_lefthand.dmi', WEAR_R_HAND = 'icons/mob/humans/onmob/inhands/equipment/tools_righthand.dmi', - WEAR_BACK = 'icons/mob/humans/onmob/clothing/back/misc.dmi' + WEAR_BACK = 'icons/mob/humans/onmob/clothing/back/melee_weapons.dmi' ) icon_state = "d2_breacher" item_state = "d2_breacher" diff --git a/code/game/objects/structures/barricade/folding.dm b/code/game/objects/structures/barricade/folding.dm index 1169c6c9129d..3b8336a5d511 100644 --- a/code/game/objects/structures/barricade/folding.dm +++ b/code/game/objects/structures/barricade/folding.dm @@ -219,8 +219,8 @@ if(recentlyflipped) to_chat(user, SPAN_NOTICE("[src] has been flipped too recently!")) return - user.visible_message(SPAN_NOTICE("[user] flips [src] open."), - SPAN_NOTICE("You flip [src] open.")) + user.visible_message(SPAN_NOTICE("[user] flips [src] closed."), + SPAN_NOTICE("You flip [src] closed.")) open(src) recentlyflipped = TRUE spawn(10) @@ -231,8 +231,8 @@ if(recentlyflipped) to_chat(user, SPAN_NOTICE("[src] has been flipped too recently!")) return - user.visible_message(SPAN_NOTICE("[user] flips [src] closed."), - SPAN_NOTICE("You flip [src] closed.")) + user.visible_message(SPAN_NOTICE("[user] flips [src] open."), + SPAN_NOTICE("You flip [src] open.")) close(src) recentlyflipped = TRUE spawn(10) diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm index 8ba5c0a721c6..adb45256a919 100644 --- a/code/game/objects/structures/flora.dm +++ b/code/game/objects/structures/flora.dm @@ -708,6 +708,8 @@ ICEY GRASS. IT LOOKS LIKE IT'S MADE OF ICE. if(ishuman(L)) var/mob/living/carbon/human/H = L var/stuck = rand(0,10) + if(HAS_TRAIT(L, TRAIT_HAULED)) + return switch(stuck) if(0 to 4) var/new_slowdown = H.next_move_slowdown + rand(2,3) diff --git a/code/game/turfs/auto_turf.dm b/code/game/turfs/auto_turf.dm index 8edd13f58612..e965323d86ed 100644 --- a/code/game/turfs/auto_turf.dm +++ b/code/game/turfs/auto_turf.dm @@ -235,12 +235,13 @@ slow_amount = 0.15 can_stuck = 0 var/new_slowdown = C.next_move_slowdown + (slow_amount * bleed_layer) - if(prob(2)) - to_chat(C, SPAN_WARNING("Moving through [src] slows you down.")) //Warning only - else if(can_stuck && bleed_layer == 4 && prob(2)) - to_chat(C, SPAN_WARNING("You get stuck in [src] for a moment!")) - new_slowdown += 10 - C.next_move_slowdown = new_slowdown + if(!HAS_TRAIT(C, TRAIT_HAULED)) + if(prob(2)) + to_chat(C, SPAN_WARNING("Moving through [src] slows you down.")) //Warning only + else if(can_stuck && bleed_layer == 4 && prob(2)) + to_chat(C, SPAN_WARNING("You get stuck in [src] for a moment!")) + new_slowdown += 10 + C.next_move_slowdown = new_slowdown ..() /turf/open/auto_turf/snow/layer0 //still have to manually define the layers for the editor diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm index 66770163bf18..862402919ded 100644 --- a/code/game/turfs/open.dm +++ b/code/game/turfs/open.dm @@ -812,7 +812,8 @@ if(world.time % 5) if(ismob(AM)) var/mob/rivermob = AM - to_chat(rivermob, SPAN_WARNING("Moving through the incredibly deep ocean slows you down a lot!")) + if(!HAS_TRAIT(rivermob, TRAIT_HAULED)) + to_chat(rivermob, SPAN_WARNING("Moving through the incredibly deep ocean slows you down a lot!")) /turf/open/gm/coast name = "coastline" diff --git a/code/game/turfs/snow.dm b/code/game/turfs/snow.dm index c8afd734e862..e6954fc9cd38 100644 --- a/code/game/turfs/snow.dm +++ b/code/game/turfs/snow.dm @@ -51,12 +51,13 @@ slow_amount = 0.25 can_stuck = 0 var/new_slowdown = C.next_move_slowdown + (slow_amount * bleed_layer) - if(prob(2)) - to_chat(C, SPAN_WARNING("Moving through [src] slows you down.")) //Warning only - else if(can_stuck && bleed_layer == 3 && prob(2)) - to_chat(C, SPAN_WARNING("You get stuck in [src] for a moment!")) - new_slowdown += 10 - C.next_move_slowdown = new_slowdown + if(!HAS_TRAIT(C, TRAIT_HAULED)) + if(prob(2)) + to_chat(C, SPAN_WARNING("Moving through [src] slows you down.")) //Warning only + else if(can_stuck && bleed_layer == 3 && prob(2)) + to_chat(C, SPAN_WARNING("You get stuck in [src] for a moment!")) + new_slowdown += 10 + C.next_move_slowdown = new_slowdown ..() diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm index 8b516ced2e6e..8ee544e475ee 100644 --- a/code/game/verbs/ooc.dm +++ b/code/game/verbs/ooc.dm @@ -82,27 +82,25 @@ var/byond = icon('icons/effects/effects.dmi', "byondlogo") prefix += "[icon2html(byond, GLOB.clients)]" if(CONFIG_GET(flag/ooc_country_flags) && (prefs.toggle_prefs & TOGGLE_OOC_FLAG)) - prefix += "[country2chaticon(src.country, GLOB.clients)]" + prefix += "[country2chaticon(country, GLOB.clients)]" if(donator) - prefix += "[icon2html('icons/ooc.dmi', GLOB.clients, "Donator")]" + prefix += "[icon2html(GLOB.ooc_rank_dmi, GLOB.clients, "Donator")]" if(isCouncil(src)) - prefix += "[icon2html('icons/ooc.dmi', GLOB.clients, "WhitelistCouncil")]" + prefix += "[icon2html(GLOB.ooc_rank_dmi, GLOB.clients, "WhitelistCouncil")]" var/comm_award = find_community_award_icons() if(comm_award) prefix += comm_award if(admin_holder) - var/list/rank_icons = icon_states('icons/ooc.dmi') - var/rankname = admin_holder.rank - if(rankname in rank_icons) - prefix += "[icon2html('icons/ooc.dmi', GLOB.clients, admin_holder.rank)]" - if(length(admin_holder.extra_titles)) - var/list/extra_rank_icons = icon_states('icons/ooc.dmi') - var/ooc_icon_state - for(var/srank in admin_holder.extra_titles) - ooc_icon_state = trim(srank) - if(ooc_icon_state in extra_rank_icons) - prefix += "[icon2html('icons/ooc.dmi', GLOB.clients, ooc_icon_state)]" + var/extra_title_state + for(var/extra_title in admin_holder.extra_titles) + extra_title_state = ckeyEx(extra_title) + if(extra_title_state in GLOB.ooc_rank_iconstates) + prefix += "[icon2html(GLOB.ooc_rank_dmi, GLOB.clients, extra_title_state)]" + + if(admin_holder.rank in GLOB.ooc_rank_iconstates) + prefix += "[icon2html(GLOB.ooc_rank_dmi, GLOB.clients, admin_holder.rank)]" + if(prefix) prefix = "[prefix] " return prefix diff --git a/code/modules/admin/autoreply.dm b/code/modules/admin/autoreply.dm index 24a67256acd8..ee655f86394d 100644 --- a/code/modules/admin/autoreply.dm +++ b/code/modules/admin/autoreply.dm @@ -144,9 +144,9 @@ ON_CONFIG_LOAD(/datum/autoreply/mentor/macros) Rangefinders allow you to get tile coordinates (longitude and latitude) by lasing it while zoomed in (produces a GREEN laser). Ctrl + Click on any open tile to start lasing. Ctrl + Click on your rangefinders to stop lasing without zooming out. Coordinates can be used by Staff Officers to send supply drops or to perform Orbital Bombardment. You also can use them to call mortar fire if there are engineers with a mortar. \ Laser Designators have a second mode (produces a RED laser) that allows highlighting targets for Close Air Support performed by dropship pilots. They also have a fixed ID number that is shown on the pilot's weaponry console. Examine the laser designator to check its ID. Red laser must be maintained as long as needed in order for the dropship pilot to bomb the designated area. To switch between lasing modes, Alt + Click the laser designator. Alternatively, Right + Click it in hand and click \"Toggle Mode\"." -/datum/autoreply/mentor/devour - title = "X: Devour as Xeno" - message = "Devouring is useful to quickly transport incapacitated hosts from one place to another. In order to devour a host as a Xeno, grab the mob (CTRL+Click) and then click on yourself to begin devouring. The host can break out of your stomach, which will result in your death so make sure your target is incapacitated. After approximately 1 minute host will be automatically regurgitated. To release your target voluntary, click 'Regurgitate' on the HUD to throw them back up." +/datum/autoreply/mentor/haul + title = "X: Haul as Xeno" + message = "Hauling is useful to quickly transport incapacitated hosts from one place to another. In order to haul a host as a Xeno, grab the mob (CTRL+Click) and then click on yourself to begin hauling. The host can break out of your grip, which will result in your death so make sure your target is incapacitated. After approximately 1 minute host will be automatically released. To release your target voluntary, click 'Release' on the HUD to throw them back up." /datum/autoreply/mentor/plasma title = "X: No plasma regen" diff --git a/code/modules/admin/tabs/admin_tab.dm b/code/modules/admin/tabs/admin_tab.dm index b949ef42e653..38abdea4ee4d 100644 --- a/code/modules/admin/tabs/admin_tab.dm +++ b/code/modules/admin/tabs/admin_tab.dm @@ -351,11 +351,11 @@ var/list/subtle_message_options = list(SUBTLE_MESSAGE_IN_HEAD, SUBTLE_MESSAGE_WEYLAND, SUBTLE_MESSAGE_USCM, SUBTLE_MESSAGE_FACTION) var/message_option = tgui_input_list(usr, "Choose the method of subtle messaging", "", subtle_message_options) + if(!message_option) + return if(message_option == SUBTLE_MESSAGE_FACTION) var/faction = input("Choose which faction", "") as text|null - if(!faction) - return message_option = faction var/input = input("Contents of the message", text("Subtle PM to In View")) as text|null diff --git a/code/modules/admin/tabs/event_tab.dm b/code/modules/admin/tabs/event_tab.dm index bf434d1fd142..3dbfed7b38ed 100644 --- a/code/modules/admin/tabs/event_tab.dm +++ b/code/modules/admin/tabs/event_tab.dm @@ -1143,6 +1143,8 @@ var/duration = 5 SECONDS var/message = "ADMIN TEST" var/text_input = tgui_input_text(usr, "Announcement message", "Message Contents", message, timeout = 5 MINUTES) + if(!text_input) + return // Early return here so people dont have to go through the whole process just to cancel it. message = text_input duration = tgui_input_number(usr, "Set the duration of the alert in deci-seconds.", "Duration", 5 SECONDS, 5 MINUTES, 5 SECONDS, 20 SECONDS) var/confirm = tgui_alert(usr, "Are you sure you wish to send '[message]' to all players for [(duration / 10)] seconds?", "Confirm", list("Yes", "No"), 20 SECONDS) diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm b/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm index 64e6bb85bed6..a99d27faf0d8 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm @@ -266,6 +266,13 @@ /proc/_turn(dir, angle) return turn(dir, angle) +/proc/_icon_states(icon/thing, mode) + if(istype(thing)) + return icon_states(thing, mode) + if(isatom(thing)) + var/atom/atom_thing = thing + return icon_states(atom_thing.icon, mode) + /// Auxtools REALLY doesn't know how to handle filters as values; /// when passed as arguments to auxtools-called procs, they aren't simply treated as nulls - /// they don't even count towards the length of args. diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm index 05bde482f504..deef7119c926 100644 --- a/code/modules/admin/verbs/playsound.dm +++ b/code/modules/admin/verbs/playsound.dm @@ -124,7 +124,7 @@ if("Ghosts") targets = GLOB.observer_list + GLOB.dead_mob_list if("All In View Range") - var/list/atom/ranged_atoms = urange(usr.client.view, get_turf(usr)) + var/list/atom/ranged_atoms = long_range(usr.client.view, get_turf(usr)) for(var/mob/receiver in ranged_atoms) targets += receiver if("Single Mob") diff --git a/code/modules/animations/animation_library.dm b/code/modules/animations/animation_library.dm index e7cb49c930d4..777fae6977ad 100644 --- a/code/modules/animations/animation_library.dm +++ b/code/modules/animations/animation_library.dm @@ -177,8 +177,8 @@ Can look good elsewhere as well.*/ if(A.clone) if(src.Adjacent(A.clone)) A = A.clone - if(buckled || anchored) - return //it would look silly. + if(buckled || anchored || HAS_TRAIT(src, TRAIT_HAULED)) //it would look silly. + return var/pixel_x_diff = 0 var/pixel_y_diff = 0 var/direction = get_dir(src, A) @@ -207,7 +207,6 @@ Can look good elsewhere as well.*/ animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2, flags = ANIMATION_PARALLEL) animate(pixel_x = initial(pixel_x), pixel_y = initial(pixel_y), time = 2) - /atom/proc/animation_spin(speed = 5, loop_amount = -1, clockwise = TRUE, sections = 3, angular_offset = 0, pixel_fuzz = 0) if(!sections) return diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 8faba744ea17..953c9f021a75 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -942,8 +942,17 @@ GLOBAL_LIST_INIT(whitelisted_client_procs, list( winset(src, "default.Shift", "is-disabled=true") winset(src, "default.ShiftUp", "is-disabled=true") +GLOBAL_VAR(ooc_rank_dmi) +GLOBAL_LIST_INIT(ooc_rank_iconstates, setup_ooc_rank_icons()) GLOBAL_LIST_INIT(community_awards, get_community_awards()) +/proc/setup_ooc_rank_icons() + var/ooc_dmi_path = "config/ooc.dmi" + if(!fexists(ooc_dmi_path)) + return list() + GLOB.ooc_rank_dmi = icon(file(ooc_dmi_path)) + return icon_states(GLOB.ooc_rank_dmi) + /proc/get_community_awards() var/list/awards_file = file2list("config/community_awards.txt") var/list/processed_awards = list() @@ -978,5 +987,5 @@ GLOBAL_LIST_INIT(community_awards, get_community_awards()) if(GLOB.community_awards[ckey]) var/full_prefix = "" for(var/award in GLOB.community_awards[ckey]) - full_prefix += "[icon2html('icons/ooc.dmi', GLOB.clients, award)]" + full_prefix += "[icon2html(GLOB.ooc_rank_dmi, GLOB.clients, award)]" return full_prefix diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 1ae304b0e271..8a6bc24a3561 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -257,6 +257,7 @@ GLOBAL_LIST_INIT(be_special_flags, list( var/tgui_fancy = TRUE var/tgui_lock = FALSE + var/tgui_scale = TRUE var/hear_vox = TRUE @@ -578,6 +579,7 @@ GLOBAL_LIST_INIT(be_special_flags, list( dat += "Tooltips: [tooltips ? "Enabled" : "Disabled"]
" dat += "tgui Window Mode: [(tgui_fancy) ? "Fancy (default)" : "Compatible (slower)"]
" dat += "tgui Window Placement: [(tgui_lock) ? "Primary monitor" : "Free (default)"]
" + dat += "tgui Scaling: [tgui_scale ? "Larger windows (default)" : "Smaller zoom"]
" dat += "Play Admin Sounds: [(toggles_sound & SOUND_MIDI) ? "Yes" : "No"]
" dat += "Play Announcement Sounds As Ghost: [(toggles_sound & SOUND_OBSERVER_ANNOUNCEMENTS) ? "Yes" : "No"]
" dat += "Play Fax Sounds As Ghost: [(toggles_sound & SOUND_FAX_MACHINE) ? "Yes" : "No"]
" @@ -1988,6 +1990,8 @@ GLOBAL_LIST_INIT(be_special_flags, list( tgui_fancy = !tgui_fancy if("tgui_lock") tgui_lock = !tgui_lock + if("tgui_scale") + tgui_scale = !tgui_scale if("change_menu") current_menu = href_list["menu"] diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 9b1885a71cf4..6bfbf18c53e0 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -361,6 +361,10 @@ S["tooltips"] >> tooltips S["key_bindings"] >> key_bindings + S["tgui_lock"] >> tgui_lock + S["tgui_fancy"] >> tgui_fancy + S["tgui_scale"] >> tgui_scale + var/tutorial_string = "" S["completed_tutorials"] >> tutorial_string tutorial_savestring_to_list(tutorial_string) @@ -440,6 +444,10 @@ yautja_status = sanitize_inlist(yautja_status, GLOB.whitelist_hierarchy + list("Elder"), initial(yautja_status)) synth_status = sanitize_inlist(synth_status, GLOB.whitelist_hierarchy, initial(synth_status)) + tgui_scale = sanitize_integer(tgui_scale, FALSE, TRUE, initial(tgui_scale)) + tgui_lock = sanitize_integer(tgui_lock, FALSE, TRUE, initial(tgui_lock)) + tgui_fancy = sanitize_integer(tgui_fancy, FALSE, TRUE, initial(tgui_fancy)) + fax_name_uscm = fax_name_uscm ? sanitize_text(fax_name_uscm, initial(fax_name_uscm)) : generate_name(FACTION_MARINE) fax_name_pvst = fax_name_pvst ? sanitize_text(fax_name_pvst, initial(fax_name_pvst)) : generate_name(FACTION_MARINE) fax_name_wy = fax_name_wy ? sanitize_text(fax_name_wy, initial(fax_name_wy)) : generate_name(FACTION_WY) @@ -606,6 +614,10 @@ S["job_loadout"] << save_loadout(loadout) S["job_loadout_names"] << loadout_slot_names + S["tgui_fancy"] << tgui_fancy + S["tgui_lock"] << tgui_lock + S["tgui_scale"] << tgui_scale + return TRUE /datum/preferences/proc/load_character(slot) diff --git a/code/modules/cm_aliens/XenoStructures.dm b/code/modules/cm_aliens/XenoStructures.dm index 970d5e043787..1fb5bd2efadc 100644 --- a/code/modules/cm_aliens/XenoStructures.dm +++ b/code/modules/cm_aliens/XenoStructures.dm @@ -225,6 +225,9 @@ if(H.ally_of_hivenumber(hivenumber)) return + if(HAS_TRAIT(H, TRAIT_HAULED)) + return + H.apply_armoured_damage(damage, penetration = penetration, def_zone = pick(target_limbs)) H.last_damage_data = construction_data @@ -445,6 +448,8 @@ /obj/structure/mineral_door/resin/proc/close_blocked() for(var/turf/turf in locs) for(var/mob/living/living_mob in turf) + if(living_mob.stat == DEAD) + continue if(!HAS_TRAIT(living_mob, TRAIT_MERGED_WITH_WEEDS)) return TRUE return FALSE @@ -628,7 +633,7 @@ return var/mob/living/carbon/target = null var/furthest_distance = INFINITY - for(var/mob/living/carbon/C in urange(range, get_turf(loc))) + for(var/mob/living/carbon/C in long_range(range, get_turf(loc))) if(!can_target(C)) continue var/distance_between = get_dist(src, C) @@ -712,7 +717,7 @@ START_PROCESSING(SSshield_pillar, src) /obj/effect/alien/resin/shield_pillar/process() - for(var/mob/living/carbon/xenomorph/X in urange(range, src)) + for(var/mob/living/carbon/xenomorph/X in long_range(range, src)) if((X.hivenumber != hivenumber) || X.stat == DEAD) continue X.add_xeno_shield(shield_to_give, XENO_SHIELD_SOURCE_SHIELD_PILLAR, decay_amount_per_second = 1, add_shield_on = TRUE, duration = 1 SECONDS) @@ -992,7 +997,7 @@ for(var/obj/effect/alien/resin/special/pylon/pylon as anything in hive.active_endgame_pylons) pylon.protection_level = TURF_PROTECTION_OB pylon.update_icon() - + if(hatched) STOP_PROCESSING(SSobj, src) return @@ -1098,7 +1103,7 @@ if(!candidate.client) return FALSE - + return candidate.client.prefs.be_special & BE_KING #undef KING_PLAYTIME_HOURS @@ -1208,7 +1213,7 @@ rolling_candidates = FALSE return message_admins("Failed to find a client for the King, releasing as freed mob.") - + /// Starts the hatching in twenty seconds, otherwise immediately if expedited /obj/effect/alien/resin/king_cocoon/proc/start_hatching(expedite = FALSE) diff --git a/code/modules/cm_marines/dropship_ammo.dm b/code/modules/cm_marines/dropship_ammo.dm index 4ba8c38b8640..2ebb12e1bb34 100644 --- a/code/modules/cm_marines/dropship_ammo.dm +++ b/code/modules/cm_marines/dropship_ammo.dm @@ -442,7 +442,7 @@ qdel(src) /obj/structure/ship_ammo/sentry/can_fire_at(turf/impact, mob/user) - for(var/obj/structure/machinery/defenses/def in urange(4, impact)) + for(var/obj/structure/machinery/defenses/def in range(4, impact)) to_chat(user, SPAN_WARNING("The selected drop site is too close to another deployed defense!")) return FALSE if(istype(impact, /turf/closed)) diff --git a/code/modules/cm_marines/orbital_cannon.dm b/code/modules/cm_marines/orbital_cannon.dm index f73fedaf13da..675d310ed103 100644 --- a/code/modules/cm_marines/orbital_cannon.dm +++ b/code/modules/cm_marines/orbital_cannon.dm @@ -395,7 +395,7 @@ GLOBAL_LIST_EMPTY(orbital_cannon_cancellation) message_admins(FONT_SIZE_XL("CLICK TO CANCEL THIS OB")) var/relative_dir - for(var/mob/M in urange(30, target)) + for(var/mob/M in long_range(30, target)) if(get_turf(M) == target) relative_dir = 0 else @@ -406,7 +406,7 @@ GLOBAL_LIST_EMPTY(orbital_cannon_cancellation) ) sleep(OB_TRAVEL_TIMING/3) - for(var/mob/M in urange(25, target)) + for(var/mob/M in long_range(25, target)) if(get_turf(M) == target) relative_dir = 0 else @@ -417,7 +417,7 @@ GLOBAL_LIST_EMPTY(orbital_cannon_cancellation) ) sleep(OB_TRAVEL_TIMING/3) - for(var/mob/M in urange(15, target)) + for(var/mob/M in long_range(15, target)) M.show_message( \ SPAN_HIGHDANGER("OH GOD THE SKY WILL EXPLODE!!!"), SHOW_MESSAGE_VISIBLE, \ SPAN_HIGHDANGER("YOU SHOULDN'T BE HERE!"), SHOW_MESSAGE_AUDIBLE \ @@ -435,7 +435,7 @@ GLOBAL_LIST_EMPTY(orbital_cannon_cancellation) var/radius_size = 30 - for(var/mob/living/user in urange(radius_size, epicenter)) + for(var/mob/living/user in long_range(radius_size, epicenter)) var/distance = get_accurate_dist(get_turf(user), epicenter) var/distance_percent = ((radius_size - distance) / radius_size) diff --git a/code/modules/cm_marines/smartgun_mount.dm b/code/modules/cm_marines/smartgun_mount.dm index ac113325d1b5..5aa3ff34bcfe 100644 --- a/code/modules/cm_marines/smartgun_mount.dm +++ b/code/modules/cm_marines/smartgun_mount.dm @@ -94,7 +94,7 @@ /obj/item/device/m56d_gun/attack_self(mob/user) ..() - for(var/obj/structure/machinery/machine in urange(defense_check_range, loc)) + for(var/obj/structure/machinery/machine in long_range(defense_check_range, loc)) if(istype(machine, /obj/structure/machinery/m56d_hmg) || istype(machine, /obj/structure/machinery/m56d_post)) to_chat(user, SPAN_WARNING("This is too close to [machine]!")) return @@ -142,7 +142,7 @@ if(!do_after(user, 1 SECONDS, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) return - for(var/obj/structure/machinery/machine in urange(defense_check_range, loc)) + for(var/obj/structure/machinery/machine in long_range(defense_check_range, loc)) if(istype(machine, /obj/structure/machinery/m56d_hmg) || istype(machine, /obj/structure/machinery/m56d_post)) to_chat(user, SPAN_WARNING("This is too close to [machine]!")) return @@ -338,7 +338,7 @@ if(istype(O,/obj/item/device/m56d_gun)) //lets mount the MG onto the mount. var/obj/item/device/m56d_gun/MG = O - for(var/obj/structure/machinery/machine in urange(MG.defense_check_range, loc, TRUE)) + for(var/obj/structure/machinery/machine in long_orange(MG.defense_check_range, loc)) if(istype(machine, /obj/structure/machinery/m56d_hmg) || istype(machine, /obj/structure/machinery/m56d_post)) to_chat(user, SPAN_WARNING("This is too close to [machine]!")) return diff --git a/code/modules/defenses/defenses.dm b/code/modules/defenses/defenses.dm index 5539bbca0dba..834e258db8ea 100644 --- a/code/modules/defenses/defenses.dm +++ b/code/modules/defenses/defenses.dm @@ -387,7 +387,7 @@ if(!turned_on) if(!can_be_near_defense) - for(var/obj/structure/machinery/defenses/def in urange(defense_check_range, loc)) + for(var/obj/structure/machinery/defenses/def in long_range(defense_check_range, loc)) if(def != src && def.turned_on && !def.can_be_near_defense) to_chat(user, SPAN_WARNING("This is too close to \a [def]!")) return diff --git a/code/modules/desert_dam/filtration/filtration.dm b/code/modules/desert_dam/filtration/filtration.dm index 35799e08b5e2..0061d9d2a60e 100644 --- a/code/modules/desert_dam/filtration/filtration.dm +++ b/code/modules/desert_dam/filtration/filtration.dm @@ -127,7 +127,6 @@ Each var depends on others if(isliving(A)) var/mob/living/M = A - // Inside a xeno for example if(!istype(M.loc, /turf)) return @@ -136,6 +135,9 @@ Each var depends on others to_chat(M, SPAN_WARNING("The current forces you to release [M.pulling]!")) M.stop_pulling() + if(HAS_TRAIT(M, TRAIT_HAULED)) + return + cause_damage(M) START_PROCESSING(SSobj, src) return diff --git a/code/modules/gear_presets/_select_equipment.dm b/code/modules/gear_presets/_select_equipment.dm index 720bc410943b..85d57341b8a1 100644 --- a/code/modules/gear_presets/_select_equipment.dm +++ b/code/modules/gear_presets/_select_equipment.dm @@ -93,7 +93,7 @@ /datum/equipment_preset/proc/load_age(mob/living/carbon/human/new_human, client/mob_client) if(minimum_age && new_human.age < minimum_age) - new_human.age = minimum_age + new_human.age = minimum_age + 2 /datum/equipment_preset/proc/load_rank(mob/living/carbon/human/new_human, client/mob_client)//Beagle-Code if(paygrades.len == 1) diff --git a/code/modules/gear_presets/uscm.dm b/code/modules/gear_presets/uscm.dm index 86bca90ffa5f..1c255d0c841b 100644 --- a/code/modules/gear_presets/uscm.dm +++ b/code/modules/gear_presets/uscm.dm @@ -436,7 +436,7 @@ rank = JOB_SQUAD_LEADER paygrades = list(PAY_SHORT_ME5 = JOB_PLAYTIME_TIER_0, PAY_SHORT_ME6 = JOB_PLAYTIME_TIER_1, PAY_SHORT_ME7 = JOB_PLAYTIME_TIER_3) role_comm_title = "SL" - minimum_age = 27 + minimum_age = 25 skills = /datum/skills/SL minimap_icon = "leader" @@ -513,7 +513,7 @@ rank = JOB_SQUAD_LEADER paygrades = list(PAY_SHORT_ME5 = JOB_PLAYTIME_TIER_0, PAY_SHORT_ME6 = JOB_PLAYTIME_TIER_1, PAY_SHORT_ME7 = JOB_PLAYTIME_TIER_3) role_comm_title = "SL" - minimum_age = 27 + minimum_age = 25 skills = /datum/skills/SL minimap_icon = "leader" diff --git a/code/modules/gear_presets/uscm_police.dm b/code/modules/gear_presets/uscm_police.dm index 8322b4e808c0..793c035e4c6f 100644 --- a/code/modules/gear_presets/uscm_police.dm +++ b/code/modules/gear_presets/uscm_police.dm @@ -2,7 +2,7 @@ name = "USCM (police roles)" faction = FACTION_MARINE minimap_background = "background_mp" - minimum_age = 27 + minimum_age = 21 //*****************************************************************************************************/ @@ -88,6 +88,7 @@ rank = JOB_WARDEN paygrades = list(PAY_SHORT_ME5 = JOB_PLAYTIME_TIER_0, PAY_SHORT_ME6 = JOB_PLAYTIME_TIER_1, PAY_SHORT_ME7 = JOB_PLAYTIME_TIER_3) role_comm_title = "MW" + minimum_age = 25 skills = /datum/skills/MW minimap_icon = "warden" @@ -148,6 +149,7 @@ assignment = JOB_CHIEF_POLICE rank = JOB_CHIEF_POLICE paygrades = list(PAY_SHORT_MO1 = JOB_PLAYTIME_TIER_0, PAY_SHORT_MO2 = JOB_PLAYTIME_TIER_1) + minimum_age = 23 role_comm_title = "CMP" skills = /datum/skills/CMP diff --git a/code/modules/gear_presets/uscm_ship.dm b/code/modules/gear_presets/uscm_ship.dm index bfe972d462fc..f914a4f4ef1c 100644 --- a/code/modules/gear_presets/uscm_ship.dm +++ b/code/modules/gear_presets/uscm_ship.dm @@ -180,7 +180,7 @@ rank = JOB_CHIEF_ENGINEER paygrades = list(PAY_SHORT_MO1 = JOB_PLAYTIME_TIER_0, PAY_SHORT_MO2 = JOB_PLAYTIME_TIER_3) role_comm_title = "CE" - minimum_age = 27 + minimum_age = 25 skills = /datum/skills/CE minimap_icon = "ce" @@ -297,7 +297,7 @@ rank = JOB_CHIEF_REQUISITION paygrades = list(PAY_SHORT_ME6 = JOB_PLAYTIME_TIER_0, PAY_SHORT_ME7 = JOB_PLAYTIME_TIER_1, PAY_SHORT_ME8 = JOB_PLAYTIME_TIER_3) role_comm_title = "QM" - minimum_age = 27 + minimum_age = 25 skills = /datum/skills/RO minimap_icon = "cargo" @@ -370,7 +370,7 @@ rank = JOB_XO paygrades = list(PAY_SHORT_MO3 = JOB_PLAYTIME_TIER_0) role_comm_title = "XO" - minimum_age = 35 + minimum_age = 27 skills = /datum/skills/XO minimap_icon = "xo" @@ -409,7 +409,7 @@ rank = JOB_SO paygrades = list(PAY_SHORT_MO1 = JOB_PLAYTIME_TIER_0) role_comm_title = "SO" - minimum_age = 25 + minimum_age = 23 skills = /datum/skills/SO minimap_icon = "so" @@ -488,7 +488,7 @@ rank = JOB_AUXILIARY_OFFICER paygrades = list(PAY_SHORT_MO2 = JOB_PLAYTIME_TIER_0, PAY_SHORT_MO3 = JOB_PLAYTIME_TIER_3) role_comm_title = "ASO" - minimum_age = 27 + minimum_age = 25 skills = /datum/skills/auxiliary_officer minimap_icon = "aso" @@ -547,6 +547,7 @@ rank = JOB_CAS_PILOT paygrades = list(PAY_SHORT_MO1 = JOB_PLAYTIME_TIER_0) role_comm_title = "GP" + minimum_age = 23 skills = /datum/skills/pilot minimap_icon = "gp" @@ -598,6 +599,7 @@ rank = JOB_DROPSHIP_PILOT paygrades = list(PAY_SHORT_MO1 = JOB_PLAYTIME_TIER_0) role_comm_title = "DP" + minimum_age = 23 skills = /datum/skills/pilot minimap_icon = "pilot" @@ -703,7 +705,7 @@ rank = "USCM Officer" paygrades = list(PAY_SHORT_MO3 = JOB_PLAYTIME_TIER_0) role_comm_title = "Cpt" - minimum_age = 40 + minimum_age = 25 skills = /datum/skills/commander utility_hat = list(/obj/item/clothing/head/beret/cm) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 1ca06f709a9b..0d48bb22186f 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -169,6 +169,9 @@ if(is_mob_incapacitated()) return + if(HAS_TRAIT(src, TRAIT_HAULED)) + return + if(pickup_recent_item_on_turf(user_turf)) return diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 81199dc11b54..6e4a4a17abd8 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -15,7 +15,6 @@ remove_all_indicators() /mob/living/carbon/Destroy() - stomach_contents?.Cut() view_change_sources = null active_transfusions = null . = ..() @@ -40,50 +39,30 @@ /mob/living/carbon/relaymove(mob/user, direction) if(user.is_mob_incapacitated(TRUE)) return - if(user in src.stomach_contents) - if(user.client) - user.client.next_movement = world.time + 20 - if(prob(30)) - for(var/mob/mobs_can_hear in hearers(4, src)) - if(mobs_can_hear.client) - mobs_can_hear.show_message(SPAN_DANGER("You hear something rumbling inside [src]'s stomach..."), SHOW_MESSAGE_AUDIBLE) - var/obj/item/item_in_hand = user.get_active_hand() - if(item_in_hand && item_in_hand.force) - var/damage_of_item = rand(floor(item_in_hand.force / 4), item_in_hand.force) - if(istype(src, /mob/living/carbon/human)) - var/mob/living/carbon/human/human_mob = src - var/organ = human_mob.get_limb("chest") - if(istype(organ, /obj/limb)) - var/obj/limb/organs_in_human = organ - if(organs_in_human.take_damage(damage_of_item, 0)) - human_mob.UpdateDamageIcon() - human_mob.updatehealth() - else - src.take_limb_damage(damage_of_item) - for(var/mob/mobs_in_view as anything in viewers(user, null)) - if(mobs_in_view.client) - mobs_in_view.show_message(text(SPAN_DANGER("[user] attacks [src]'s stomach wall with the [item_in_hand.name]!")), SHOW_MESSAGE_AUDIBLE) - user.track_hit(initial(item_in_hand.name)) - playsound(user.loc, 'sound/effects/attackblob.ogg', 25, 1) - - if(prob(max(4*(100*getBruteLoss()/maxHealth - 75),0))) //4% at 24% health, 80% at 5% health - last_damage_data = create_cause_data("chestbursting", user) - gib(last_damage_data) - else if(!chestburst && (status_flags & XENO_HOST) && islarva(user)) + if(!chestburst && (status_flags & XENO_HOST) && islarva(user)) var/mob/living/carbon/xenomorph/larva/larva_burst = user larva_burst.chest_burst(src) /mob/living/carbon/ex_act(severity, direction, datum/cause_data/cause_data) + last_damage_data = istype(cause_data) ? cause_data : create_cause_data(cause_data) + var/gibbing = FALSE + + if(severity >= health && severity >= EXPLOSION_THRESHOLD_GIB) + gibbing = TRUE if(body_position == LYING_DOWN && direction) severity *= EXPLOSION_PRONE_MULTIPLIER + if(HAS_TRAIT(src, TRAIT_HAULED) && !gibbing) // We still probably wanna gib them as well if they were supposed to be gibbed by the explosion in the first place + visible_message(SPAN_WARNING("[src] is shielded from the blast!"), SPAN_WARNING("You are shielded from the blast!")) + return + if(severity >= 30) flash_eyes() last_damage_data = istype(cause_data) ? cause_data : create_cause_data(cause_data) - if(severity >= health && severity >= EXPLOSION_THRESHOLD_GIB) + if(gibbing) gib(last_damage_data) return @@ -98,23 +77,6 @@ /mob/living/carbon/gib(datum/cause_data/cause = create_cause_data("gibbing", src)) if(legcuffed) drop_inv_item_on_ground(legcuffed) - - var/turf/my_turf = get_turf(src) - - for(var/atom/movable/A in stomach_contents) - stomach_contents.Remove(A) - A.forceMove(my_turf) - A.acid_damage = 0 //Reset the acid damage - if(ismob(A)) - visible_message(SPAN_DANGER("[A] bursts out of [src]!")) - - for(var/atom/movable/A in contents_recursive()) - if(isobj(A)) - var/obj/O = A - if(O.unacidable) - O.forceMove(my_turf) - O.throw_atom(pick(RANGE_TURFS(1, src)), 1, SPEED_FAST) - . = ..(cause) /mob/living/carbon/revive() @@ -128,37 +90,69 @@ recalculate_move_delay = TRUE ..() -/mob/living/carbon/human/attackby(obj/item/W, mob/living/user) +/mob/living/carbon/human/attackby(obj/item/weapon, mob/living/user) if(user.mob_flags & SURGERY_MODE_ON) switch(user.a_intent) if(INTENT_HELP) //Attempt to dig shrapnel first, if any. dig_out_shrapnel_check() will fail if user is not human, which may be possible in future. - if(W.flags_item & CAN_DIG_SHRAPNEL && (locate(/obj/item/shard) in src.embedded_items) && W.dig_out_shrapnel_check(src, user)) + if(weapon.flags_item & CAN_DIG_SHRAPNEL && (locate(/obj/item/shard) in src.embedded_items) && weapon.dig_out_shrapnel_check(src, user)) return TRUE var/datum/surgery/current_surgery = active_surgeries[user.zone_selected] if(current_surgery) - if(current_surgery.attempt_next_step(user, W)) + if(current_surgery.attempt_next_step(user, weapon)) return TRUE //Cancel attack. else var/obj/limb/affecting = get_limb(check_zone(user.zone_selected)) - if(initiate_surgery_moment(W, src, affecting, user)) + if(initiate_surgery_moment(weapon, src, affecting, user)) return TRUE if(INTENT_DISARM) //Same as help but without the shrapnel dig attempt. var/datum/surgery/current_surgery = active_surgeries[user.zone_selected] if(current_surgery) - if(current_surgery.attempt_next_step(user, W)) + if(current_surgery.attempt_next_step(user, weapon)) return TRUE else var/obj/limb/affecting = get_limb(check_zone(user.zone_selected)) - if(initiate_surgery_moment(W, src, affecting, user)) + if(initiate_surgery_moment(weapon, src, affecting, user)) return TRUE - else if(W.flags_item & CAN_DIG_SHRAPNEL && W.dig_out_shrapnel_check(src, user)) + else if(weapon.flags_item & CAN_DIG_SHRAPNEL && weapon.dig_out_shrapnel_check(src, user)) return TRUE . = ..() +/mob/living/carbon/human/proc/handle_haul_resist() + if(world.time <= next_haul_resist) + return + + if(is_mob_incapacitated()) + return + + var/mob/living/carbon/xenomorph/xeno = hauling_xeno + next_haul_resist = world.time + 1.4 SECONDS + if(istype(get_active_hand(), /obj/item)) + var/obj/item/item = get_active_hand() + if(item.force) + var/damage_of_item = rand(floor(item.force / 4), item.force) + xeno.take_limb_damage(damage_of_item) + for(var/mob/mobs_in_view as anything in viewers(src, null)) + if(mobs_in_view.client) + mobs_in_view.show_message(text(SPAN_DANGER("[src] attacks [xeno]'s carapace with the [item.name]!")), SHOW_MESSAGE_AUDIBLE) + track_hit(initial(item.name)) + if(item.sharp) + playsound(loc, 'sound/weapons/slash.ogg', 25, 1) + else + var/hit_sound = pick('sound/weapons/genhit1.ogg', 'sound/weapons/genhit2.ogg', 'sound/weapons/genhit3.ogg') + playsound(loc, hit_sound, 25, 1) + if(prob(max(4*(100*xeno.getBruteLoss()/xeno.maxHealth - 75),0))) //4% at 24% health, 80% at 5% health + xeno.last_damage_data = create_cause_data("scuffling", src) + xeno.gib(last_damage_data) + else + for(var/mob/mobs_can_hear in hearers(4, xeno)) + if(mobs_can_hear.client) + mobs_can_hear.show_message(SPAN_DANGER("You hear [src] struggling against [xeno]'s grip..."), SHOW_MESSAGE_AUDIBLE) + return + /mob/living/carbon/attack_hand(mob/target_mob as mob) if(!istype(target_mob, /mob/living/carbon)) return @@ -368,8 +362,8 @@ return if(stat || !target) return - if(!istype(loc, /turf)) // In some mob/object (i.e. devoured or tank) - to_chat(src, SPAN_WARNING("You cannot throw anything while inside of \the [loc.name].")) + if(!istype(loc, /turf) || HAS_TRAIT(src, TRAIT_HAULED)) // In some mob/object (i.e. hauled or tank) + to_chat(src, SPAN_WARNING("You cannot throw anything right now.")) return if(target.type == /atom/movable/screen) return @@ -502,16 +496,51 @@ if(!HAS_TRAIT(src, TRAIT_FLOORED)) // just watch this break in the most horrible way possible break +// Adding traits, etc after xeno restrains and hauls us +/mob/living/carbon/human/proc/handle_haul(mob/living/carbon/xenomorph/xeno) + ADD_TRAIT(src, TRAIT_FLOORED, TRAIT_SOURCE_XENO_HAUL) + ADD_TRAIT(src, TRAIT_HAULED, TRAIT_SOURCE_XENO_HAUL) + ADD_TRAIT(src, TRAIT_NO_STRAY, TRAIT_SOURCE_XENO_HAUL) + + hauling_xeno = xeno + RegisterSignal(xeno, COMSIG_MOB_DEATH, PROC_REF(release_haul_death)) + RegisterSignal(src, COMSIG_LIVING_PREIGNITION, PROC_REF(haul_fire_shield)) + RegisterSignal(src, list(COMSIG_LIVING_FLAMER_CROSSED, COMSIG_LIVING_FLAMER_FLAMED), PROC_REF(haul_fire_shield_callback)) + layer = LYING_BETWEEN_MOB_LAYER + add_filter("hauled_shadow", 1, color_matrix_filter(rgb(95, 95, 95))) + pixel_y = -7 + next_haul_resist = 0 + +/mob/living/carbon/human/proc/release_haul_death() + SIGNAL_HANDLER + handle_unhaul() + +/mob/living/carbon/human/proc/haul_fire_shield(mob/living/burning_mob) //Stealing it from the pyro spec armor, xenos shield us from fire + SIGNAL_HANDLER + return COMPONENT_CANCEL_IGNITION + + +/mob/living/carbon/human/proc/haul_fire_shield_callback(mob/living/burning_mob) + SIGNAL_HANDLER + . = COMPONENT_NO_IGNITE|COMPONENT_NO_BURN + +// Removing traits and other stuff after xeno releases us from haul +/mob/living/carbon/human/proc/handle_unhaul() + var/location = get_turf(loc) + remove_traits(list(TRAIT_HAULED, TRAIT_NO_STRAY, TRAIT_FLOORED, TRAIT_IMMOBILIZED), TRAIT_SOURCE_XENO_HAUL) + pixel_y = 0 + UnregisterSignal(src, list(COMSIG_LIVING_PREIGNITION, COMSIG_LIVING_FLAMER_CROSSED, COMSIG_LIVING_FLAMER_FLAMED)) + UnregisterSignal(hauling_xeno, COMSIG_MOB_DEATH) + hauling_xeno = null + layer = MOB_LAYER + remove_filter("hauled_shadow") + forceMove(location) + for(var/obj/object in location) + if(istype(object, /obj/effect/alien/resin/trap) || istype(object, /obj/effect/alien/egg)) + object.HasProximity(src) + next_haul_resist = 0 -/mob/living/carbon/on_stored_atom_del(atom/movable/AM) - ..() - if(length(stomach_contents) && ismob(AM)) - for(var/X in stomach_contents) - if(AM == X) - stomach_contents -= AM - break - /mob/living/carbon/proc/extinguish_mob(mob/living/carbon/C) adjust_fire_stacks(-5, min_stacks = 0) playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 25, 1, 7) diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 03a8abef22af..f3a1134557a3 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -1,7 +1,6 @@ /mob/living/carbon gender = MALE mobility_flags = MOBILITY_FLAGS_CARBON_DEFAULT - var/list/stomach_contents = list() var/life_tick = 0 // The amount of life ticks that have processed on this mob. diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 289d6afd6e05..1ba66199b950 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -138,8 +138,6 @@ if(body_position == LYING_DOWN && direction) severity *= EXPLOSION_PRONE_MULTIPLIER - - var/b_loss = 0 var/f_loss = 0 @@ -158,6 +156,10 @@ create_shrapnel(oldloc, rand(5, 9), direction, 45, /datum/ammo/bullet/shrapnel/light/human/var2, last_damage_data) return + if(HAS_TRAIT(src, TRAIT_HAULED)) // We still probably wanna gib them as well if they were supposed to be gibbed by the explosion in the first place + visible_message(SPAN_WARNING("[src] is shielded from the blast!"), SPAN_WARNING("You are shielded from the blast!")) + return + if(!HAS_TRAIT(src, TRAIT_EAR_PROTECTION)) ear_damage += severity * 0.15 AdjustEarDeafness(severity * 0.5) diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm index fcd22e5bfa41..2e35209ccd5f 100644 --- a/code/modules/mob/living/carbon/human/human_attackhand.dm +++ b/code/modules/mob/living/carbon/human/human_attackhand.dm @@ -4,6 +4,9 @@ if(..()) return TRUE + if(HAS_TRAIT(attacking_mob, TRAIT_HAULED)) + return + SEND_SIGNAL(attacking_mob, COMSIG_LIVING_ATTACKHAND_HUMAN, src) if((attacking_mob != src) && check_shields(0, attacking_mob.name)) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 2a2661acc500..43c964b9e58a 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -176,6 +176,12 @@ // Are we currently using inherent zoom vision? var/is_zoomed = FALSE + // Xenomorph that is hauling us if we are hauled + var/mob/living/carbon/xenomorph/hauling_xeno + + // Haul resist cooldown + var/next_haul_resist + /client/var/cached_human_playtime /client/proc/get_total_human_playtime(skip_cache = FALSE) diff --git a/code/modules/mob/living/carbon/human/life/life_helpers.dm b/code/modules/mob/living/carbon/human/life/life_helpers.dm index 96f147d52883..a66374dc4680 100644 --- a/code/modules/mob/living/carbon/human/life/life_helpers.dm +++ b/code/modules/mob/living/carbon/human/life/life_helpers.dm @@ -209,6 +209,7 @@ last_damage_data = null statistic_tracked = FALSE tod = null + revive_grace_period = initial(revive_grace_period) set_stat(UNCONSCIOUS) emote("gasp") regenerate_icons() diff --git a/code/modules/mob/living/carbon/xenomorph/Evolution.dm b/code/modules/mob/living/carbon/xenomorph/Evolution.dm index df7e2825ee22..cc5ca0f8beef 100644 --- a/code/modules/mob/living/carbon/xenomorph/Evolution.dm +++ b/code/modules/mob/living/carbon/xenomorph/Evolution.dm @@ -17,6 +17,10 @@ GLOBAL_LIST_EMPTY(deevolved_ckeys) /mob/living/carbon/xenomorph/proc/do_evolve() if(!evolve_checks()) return + var/mob/living/carbon/human/user = hauled_mob?.resolve() + if(user) + to_chat(src, "Release [user] before evolving!") + return var/list/castes_available = caste.evolves_to.Copy() diff --git a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm index 4b37be23fc6c..3fac8948b905 100644 --- a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm +++ b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm @@ -474,7 +474,8 @@ /proc/can_hug(mob/living/carbon/M, hivenumber) if(!istype(M) || isxeno(M) || issynth(M) || iszombie(M) || isHellhound(M) || M.stat == DEAD || !M.huggable) return FALSE - + if(HAS_TRAIT(M, TRAIT_HAULED)) + return FALSE if(M.ally_of_hivenumber(hivenumber)) return FALSE diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index 85574c60319b..a58a8a8be244 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -215,6 +215,31 @@ /mob/living/carbon/xenomorph/proc/gain_armor_percent(value) armor_integrity = min(armor_integrity + value, 100) +/mob/living/carbon/xenomorph/animation_attack_on(atom/A, pixel_offset) + if(hauled_mob?.resolve()) + return + . = ..() + +/mob/living/carbon/xenomorph/Move(NewLoc, direct) + . = ..() + var/mob/user = hauled_mob?.resolve() + if(user) + user.forceMove(loc) + +/mob/living/carbon/xenomorph/forceMove(atom/destination) + . = ..() + var/mob/user = hauled_mob?.resolve() + if(user) + if(!isturf(destination)) + user.forceMove(src) + else + user.forceMove(loc) + +/mob/living/carbon/xenomorph/relaymove(mob/user, direction) + . = ..() + if(HAS_TRAIT(user, TRAIT_HAULED)) + var/mob/living/carbon/human/hauled_mob = user + hauled_mob.handle_haul_resist() //Strip all inherent xeno verbs from your caste. Used in evolution. /mob/living/carbon/xenomorph/proc/remove_inherent_verbs() @@ -368,17 +393,6 @@ /mob/living/carbon/xenomorph/proc/pounced_turf_wrapper(turf/T) pounced_turf(T) -//Bleuugh -/mob/living/carbon/xenomorph/proc/empty_gut() - if(length(stomach_contents)) - for(var/atom/movable/S in stomach_contents) - stomach_contents.Remove(S) - S.acid_damage = 0 //Reset the acid damage - S.forceMove(get_true_turf(src)) - - if(length(contents)) //Get rid of anything that may be stuck inside us as well - for(var/atom/movable/A in contents) - A.forceMove(get_true_turf(src)) /mob/living/carbon/xenomorph/proc/toggle_nightvision() see_in_dark = 12 @@ -393,22 +407,58 @@ lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE update_sight() -/mob/living/carbon/xenomorph/proc/regurgitate(mob/living/victim, stuns = FALSE) - if(length(stomach_contents)) - if(victim) - stomach_contents.Remove(victim) - victim.acid_damage = 0 - victim.forceMove(get_true_turf(loc)) - - visible_message(SPAN_XENOWARNING("[src] hurls out the contents of their stomach!"), - SPAN_XENOWARNING("We hurl out the contents of our stomach!"), null, 5) - playsound(get_true_location(loc), 'sound/voice/alien_drool2.ogg', 50, 1) - log_interact(src, victim, "[key_name(src)] regurgitated [key_name(victim)] at [get_area_name(loc)]") +/mob/living/carbon/xenomorph/proc/haul(mob/living/carbon/human/victim) + visible_message(SPAN_WARNING("[src] restrains [victim], hauling them effortlessly!"), + SPAN_WARNING("We fully restrain [victim] and start hauling them!"), null, 5) + log_interact(src, victim, "[key_name(src)] started hauling [key_name(victim)] at [get_area_name(src)]") + playsound(loc, 'sound/weapons/thudswoosh.ogg', 25, 1, 7) + + if(ishuman(victim)) + var/mob/living/carbon/human/pulled_human = victim + pulled_human.disable_lights() + hauled_mob = WEAKREF(victim) + victim.forceMove(loc, get_dir(victim.loc, loc)) + victim.handle_haul(src) + RegisterSignal(victim, COMSIG_MOB_DEATH, PROC_REF(release_dead_haul)) + haul_timer = addtimer(CALLBACK(src, PROC_REF(about_to_release_hauled)), 40 SECONDS + rand(0 SECONDS, 20 SECONDS), TIMER_STOPPABLE) + +/mob/living/carbon/xenomorph/proc/about_to_release_hauled() + var/mob/living/carbon/human/user = hauled_mob?.resolve() + if(!user) + deltimer(haul_timer) + return + to_chat(src, SPAN_XENOWARNING("We feel our grip loosen on [user], we will have to release them soon.")) + playsound(src, 'sound/voice/alien_hiss2.ogg', 15) + haul_timer = addtimer(CALLBACK(src, PROC_REF(release_haul)), 10 SECONDS, TIMER_STOPPABLE) - if (stuns) - victim.adjust_effect(2, STUN) - else - to_chat(src, SPAN_WARNING("There's nothing in our belly that needs regurgitating.")) +// Releasing a dead hauled mob +/mob/living/carbon/xenomorph/proc/release_dead_haul() + SIGNAL_HANDLER + deltimer(haul_timer) + var/mob/living/carbon/human/user = hauled_mob?.resolve() + to_chat(src, SPAN_XENOWARNING("[user] is dead. No more use for them now.")) + user.handle_unhaul() + UnregisterSignal(user, COMSIG_MOB_DEATH) + UnregisterSignal(src, COMSIG_ATOM_DIR_CHANGE) + hauled_mob = null + +// Releasing a hauled mob +/mob/living/carbon/xenomorph/proc/release_haul(stuns = FALSE) + deltimer(haul_timer) + var/mob/living/carbon/human/user = hauled_mob?.resolve() + if(!user) + to_chat(src, SPAN_WARNING("We are not hauling anyone.")) + return + user.handle_unhaul() + visible_message(SPAN_XENOWARNING("[src] releases [user] from their grip!"), + SPAN_XENOWARNING("We release [user] from our grip!"), null, 5) + playsound(src, 'sound/voice/alien_growl1.ogg', 15) + log_interact(src, user, "[key_name(src)] released [key_name(user)] at [get_area_name(loc)]") + if(stuns) + user.adjust_effect(2, STUN) + UnregisterSignal(user, COMSIG_MOB_DEATH) + UnregisterSignal(src, COMSIG_ATOM_DIR_CHANGE) + hauled_mob = null /mob/living/carbon/xenomorph/proc/check_alien_construction(turf/current_turf, check_blockers = TRUE, silent = FALSE, check_doors = TRUE, ignore_nest = FALSE) var/has_obstacle diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm index fce6ac148acc..513633209c56 100644 --- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm +++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm @@ -60,6 +60,9 @@ var/obj/item/clothing/head/head = null var/obj/item/r_store = null var/obj/item/l_store = null + // Mob we are hauling + var/datum/weakref/hauled_mob + var/haul_timer var/obj/item/iff_tag/iff_tag = null @@ -276,8 +279,6 @@ /// 0/FALSE - upright, 1/TRUE - all fours var/agility = FALSE var/ripping_limb = FALSE - /// The world.time at which we will regurgitate our currently-vored victim - var/devour_timer = 0 /// For drones/hivelords. Extends the maximum build range they have var/extra_build_dist = 0 /// tiles from self you can plant eggs. @@ -394,7 +395,8 @@ //If we're holding things drop them for(var/obj/item/item in old_xeno.contents) //Drop stuff old_xeno.drop_inv_item_on_ground(item) - old_xeno.empty_gut() + if(old_xeno.hauled_mob?.resolve()) + old_xeno.release_haul(old_xeno.hauled_mob.resolve()) if(old_xeno.iff_tag) iff_tag = old_xeno.iff_tag @@ -690,6 +692,10 @@ /mob/living/carbon/xenomorph/Destroy() GLOB.living_xeno_list -= src GLOB.xeno_mob_list -= src + var/mob/living/carbon/human/user = hauled_mob?.resolve() + if(user) + user.handle_unhaul() + hauled_mob = null if(tracked_marker) tracked_marker.xenos_tracking -= src diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm deleted file mode 100644 index 3bef23de8dd0..000000000000 --- a/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm +++ /dev/null @@ -1,304 +0,0 @@ - -/datum/action/xeno_action/activable/pounce/crusher_charge/additional_effects_always() - var/mob/living/carbon/xenomorph/X = owner - if (!istype(X)) - return - - for (var/mob/living/carbon/H in orange(1, get_turf(X))) - if(X.can_not_harm(H)) - continue - - new /datum/effects/xeno_slow(H, X, null, null, 3.5 SECONDS) - to_chat(H, SPAN_XENODANGER("You are slowed as the impact of [X] shakes the ground!")) - -/datum/action/xeno_action/activable/pounce/crusher_charge/additional_effects(mob/living/L) - if (!isxeno_human(L)) - return - - var/mob/living/carbon/H = L - if (H.stat == DEAD) - return - - var/mob/living/carbon/xenomorph/X = owner - if (!istype(X)) - return - - X.emote("roar") - L.apply_effect(2, WEAKEN) - X.visible_message(SPAN_XENODANGER("[X] overruns [H], brutally trampling them underfoot!"), SPAN_XENODANGER("We brutalize [H] as we crush them underfoot!")) - - H.apply_armoured_damage(get_xeno_damage_slash(H, direct_hit_damage), ARMOR_MELEE, BRUTE) - X.throw_carbon(H, X.dir, 3) - - H.last_damage_data = create_cause_data(X.caste_type, X) - return - -/datum/action/xeno_action/activable/pounce/crusher_charge/pre_windup_effects() - RegisterSignal(owner, COMSIG_XENO_PRE_CALCULATE_ARMOURED_DAMAGE_PROJECTILE, PROC_REF(check_directional_armor)) - - var/mob/living/carbon/xenomorph/xeno_owner = owner - if(!istype(xeno_owner)) - return - - var/datum/behavior_delegate/crusher_base/crusher_delegate = xeno_owner.behavior_delegate - if(!istype(crusher_delegate)) - return - - crusher_delegate.is_charging = TRUE - xeno_owner.update_icons() - -/datum/action/xeno_action/activable/pounce/crusher_charge/post_windup_effects(interrupted) - ..() - UnregisterSignal(owner, COMSIG_XENO_PRE_CALCULATE_ARMOURED_DAMAGE_PROJECTILE) - var/mob/living/carbon/xenomorph/xeno_owner = owner - if(!istype(xeno_owner)) - return - - var/datum/behavior_delegate/crusher_base/crusher_delegate = xeno_owner.behavior_delegate - if(!istype(crusher_delegate)) - return - - addtimer(CALLBACK(src, PROC_REF(undo_charging_icon)), 0.5 SECONDS) // let the icon be here for a bit, it looks cool - -/datum/action/xeno_action/activable/pounce/crusher_charge/proc/undo_charging_icon() - var/mob/living/carbon/xenomorph/xeno_owner = owner - if(!istype(xeno_owner)) - return - - var/datum/behavior_delegate/crusher_base/crusher_delegate = xeno_owner.behavior_delegate - if(!istype(crusher_delegate)) - return - - crusher_delegate.is_charging = FALSE - xeno_owner.update_icons() - -/datum/action/xeno_action/activable/pounce/crusher_charge/proc/check_directional_armor(mob/living/carbon/xenomorph/X, list/damagedata) - SIGNAL_HANDLER - var/projectile_direction = damagedata["direction"] - if(X.dir & REVERSE_DIR(projectile_direction)) - // During the charge windup, crusher gets an extra 15 directional armor in the direction its charging - damagedata["armor"] += frontal_armor - - -// This ties the pounce/throwing backend into the old collision backend -/mob/living/carbon/xenomorph/crusher/pounced_obj(obj/O) - var/datum/action/xeno_action/activable/pounce/crusher_charge/CCA = get_action(src, /datum/action/xeno_action/activable/pounce/crusher_charge) - if (istype(CCA) && !CCA.action_cooldown_check() && !(O.type in CCA.not_reducing_objects)) - CCA.reduce_cooldown(50) - - gain_plasma(10) - - if (!handle_collision(O)) // Check old backend - obj_launch_collision(O) - -/mob/living/carbon/xenomorph/crusher/pounced_turf(turf/T) - T.ex_act(EXPLOSION_THRESHOLD_VLOW, , create_cause_data(caste_type, src)) - ..(T) - -/datum/action/xeno_action/onclick/crusher_stomp/use_ability(atom/Atom) - var/mob/living/carbon/xenomorph/xeno = owner - - if (!action_cooldown_check()) - return - - if (!xeno.check_state()) - return - - if(!check_and_use_plasma_owner()) - return - - playsound(xeno, 'sound/effects/bang.ogg', 25) - xeno.visible_message(SPAN_XENODANGER("[xeno] smashes into the ground!"), SPAN_XENODANGER("We smash into the ground!")) - xeno.create_stomp() - - - for (var/mob/living/carbon/targets in orange(distance, xeno)) - - if (targets.stat == DEAD || xeno.can_not_harm(targets)) - continue - - if(targets in get_turf(xeno)) - targets.apply_armoured_damage(get_xeno_damage_slash(targets, damage), ARMOR_MELEE, BRUTE) - - if(targets.mob_size < MOB_SIZE_BIG) - targets.apply_effect(get_xeno_stun_duration(targets, 0.2), WEAKEN) - - new /datum/effects/xeno_slow(targets, xeno, ttl = get_xeno_stun_duration(targets, effect_duration)) - targets.apply_effect(get_xeno_stun_duration(targets, 0.2), WEAKEN) - to_chat(targets, SPAN_XENOHIGHDANGER("You are slowed as [xeno] knocks you off balance!")) - - apply_cooldown() - return ..() - -/datum/action/xeno_action/onclick/crusher_stomp/charger/use_ability() - var/mob/living/carbon/xenomorph/Xeno = owner - var/mob/living/carbon/Targeted - if (!istype(Xeno)) - return - - if (!action_cooldown_check()) - return - - if (!Xeno.check_state()) - return - - if (!check_and_use_plasma_owner()) - return - - playsound(get_turf(Xeno), 'sound/effects/bang.ogg', 25, 0) - Xeno.visible_message(SPAN_XENODANGER("[Xeno] smashes into the ground!"), SPAN_XENODANGER("We smash into the ground!")) - Xeno.create_stomp() - - for (var/mob/living/carbon/Human in get_turf(Xeno)) // MOBS ONTOP - if (Human.stat == DEAD || Xeno.can_not_harm(Human)) - continue - - new effect_type_base(Human, Xeno, , , get_xeno_stun_duration(Human, effect_duration)) - to_chat(Human, SPAN_XENOHIGHDANGER("You are BRUTALLY crushed and stomped on by [Xeno]!!!")) - shake_camera(Human, 10, 2) - if(Human.mob_size < MOB_SIZE_BIG) - Human.apply_effect(get_xeno_stun_duration(Human, 0.2), WEAKEN) - - Human.apply_armoured_damage(get_xeno_damage_slash(Human, damage), ARMOR_MELEE, BRUTE,"chest", 3) - Human.apply_armoured_damage(15, BRUTE) // random - Human.last_damage_data = create_cause_data(Xeno.caste_type, Xeno) - Human.emote("pain") - Targeted = Human - for (var/mob/living/carbon/Human in orange(distance, get_turf(Xeno))) // MOBS AROUND - if (Human.stat == DEAD || Xeno.can_not_harm(Human)) - continue - if(Human.client) - shake_camera(Human, 10, 2) - if(Targeted) - to_chat(Human, SPAN_XENOHIGHDANGER("You watch as [Targeted] gets crushed by [Xeno]!")) - to_chat(Human, SPAN_XENOHIGHDANGER("You are shaken as [Xeno] quakes the earth!")) - - apply_cooldown() - return ..() - -/datum/action/xeno_action/onclick/crusher_shield/use_ability(atom/Target) - var/mob/living/carbon/xenomorph/xeno = owner - - if (!istype(xeno)) - return - - if (!action_cooldown_check()) - return - - if (!xeno.check_state()) - return - - if (!check_and_use_plasma_owner()) - return - - xeno.visible_message(SPAN_XENOWARNING("[xeno] hunkers down and bolsters its defenses!"), SPAN_XENOHIGHDANGER("We hunker down and bolster our defenses!")) - button.icon_state = "template_active" - - xeno.create_crusher_shield() - - xeno.add_xeno_shield(shield_amount, XENO_SHIELD_SOURCE_CRUSHER, /datum/xeno_shield/crusher) - xeno.overlay_shields() - - xeno.explosivearmor_modifier += 1000 - xeno.recalculate_armor() - - addtimer(CALLBACK(src, PROC_REF(remove_explosion_immunity)), explosion_immunity_dur) - addtimer(CALLBACK(src, PROC_REF(remove_shield)), shield_dur) - - apply_cooldown() - return ..() - -/datum/action/xeno_action/onclick/crusher_shield/proc/remove_explosion_immunity() - var/mob/living/carbon/xenomorph/xeno = owner - if (!istype(xeno)) - return - - xeno.explosivearmor_modifier -= 1000 - xeno.recalculate_armor() - to_chat(xeno, SPAN_XENODANGER("Our immunity to explosion damage ends!")) - -/datum/action/xeno_action/onclick/crusher_shield/proc/remove_shield() - var/mob/living/carbon/xenomorph/xeno = owner - if (!istype(xeno)) - return - - var/datum/xeno_shield/found - for (var/datum/xeno_shield/shield in xeno.xeno_shields) - if (shield.shield_source == XENO_SHIELD_SOURCE_CRUSHER) - found = shield - break - - if (istype(found)) - found.on_removal() - qdel(found) - to_chat(xeno, SPAN_XENOHIGHDANGER("We feel our enhanced shield end!")) - button.icon_state = "template" - - xeno.overlay_shields() - -/datum/action/xeno_action/onclick/charger_charge/use_ability(atom/Target) - var/mob/living/carbon/xenomorph/Xeno = owner - - activated = !activated - var/will_charge = "[activated ? "now" : "no longer"]" - to_chat(Xeno, SPAN_XENONOTICE("We will [will_charge] charge when moving.")) - if(activated) - RegisterSignal(Xeno, COMSIG_MOVABLE_MOVED, PROC_REF(handle_movement)) - RegisterSignal(Xeno, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(handle_position_change)) - RegisterSignal(Xeno, COMSIG_ATOM_DIR_CHANGE, PROC_REF(handle_dir_change)) - RegisterSignal(Xeno, COMSIG_XENO_RECALCULATE_SPEED, PROC_REF(update_speed)) - RegisterSignal(Xeno, COMSIG_XENO_STOP_MOMENTUM, PROC_REF(stop_momentum)) - RegisterSignal(Xeno, COMSIG_MOVABLE_ENTERED_RIVER, PROC_REF(handle_river)) - RegisterSignal(Xeno, COMSIG_LIVING_PRE_COLLIDE, PROC_REF(handle_collision)) - RegisterSignal(Xeno, COMSIG_XENO_START_CHARGING, PROC_REF(start_charging)) - button.icon_state = "template_active" - else - stop_momentum() - UnregisterSignal(Xeno, list( - COMSIG_MOVABLE_MOVED, - COMSIG_LIVING_SET_BODY_POSITION, - COMSIG_ATOM_DIR_CHANGE, - COMSIG_XENO_RECALCULATE_SPEED, - COMSIG_MOVABLE_ENTERED_RIVER, - COMSIG_LIVING_PRE_COLLIDE, - COMSIG_XENO_STOP_MOMENTUM, - COMSIG_XENO_START_CHARGING, - )) - button.icon_state = "template" - return ..() - -/datum/action/xeno_action/activable/tumble/use_ability(atom/Target) - if(!action_cooldown_check()) - return - var/mob/living/carbon/xenomorph/Xeno = owner - if (!Xeno.check_state()) - return - if(Xeno.plasma_stored <= plasma_cost) - return - var/target_dist = get_dist(Xeno, Target) - var/dir_between = get_dir(Xeno, Target) - var/target_dir - for(var/perpen_dir in get_perpen_dir(Xeno.dir)) - if(dir_between & perpen_dir) - target_dir = perpen_dir - break - - if(!target_dir) - return - - Xeno.visible_message(SPAN_XENOWARNING("[Xeno] tumbles over to the side!"), SPAN_XENOHIGHDANGER("We tumble over to the side!")) - Xeno.spin(5,1) // note: This spins the sprite and DOES NOT affect directional armor - var/start_charging = HAS_TRAIT(Xeno, TRAIT_CHARGING) - SEND_SIGNAL(Xeno, COMSIG_XENO_STOP_MOMENTUM) - Xeno.flags_atom |= DIRLOCK - playsound(Xeno,"alien_tail_swipe", 50, 1) - - Xeno.use_plasma(plasma_cost) - - var/target = get_step(get_step(Xeno, target_dir), target_dir) - var/list/collision_callbacks = list(/mob/living/carbon/human = CALLBACK(src, PROC_REF(handle_mob_collision))) - var/list/end_throw_callbacks = list(CALLBACK(src, PROC_REF(on_end_throw), start_charging)) - Xeno.throw_atom(target, target_dist, SPEED_FAST, launch_type = LOW_LAUNCH, pass_flags = PASS_CRUSHER_CHARGE, end_throw_callbacks = end_throw_callbacks, collision_callbacks = collision_callbacks) - - apply_cooldown() - return ..() diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm index d5ce19d4a532..5e3c95007733 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm @@ -12,7 +12,7 @@ knockdown = TRUE knockdown_duration = 0.5 windup = TRUE - windup_duration = FACEHUGGER_WINDUP_DURATION + windup_duration = FACEHUGGER_LEAP_DURATION freeze_self = TRUE freeze_time = 5 freeze_play_sound = FALSE 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 fe96e5958a13..f3287592954b 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm @@ -59,12 +59,12 @@ if(X && !X.buckled && !X.is_mob_incapacitated()) return TRUE -// Regurgitate -/datum/action/xeno_action/onclick/regurgitate - name = "Regurgitate" - action_icon_state = "regurgitate" +// release_haul +/datum/action/xeno_action/onclick/release_haul + name = "Release" + action_icon_state = "release_haul" plasma_cost = 0 - macro_path = /datum/action/xeno_action/verb/verb_regurgitate + macro_path = /datum/action/xeno_action/verb/verb_release_haul action_type = XENO_ACTION_CLICK // Choose Resin diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_ability_macros.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_ability_macros.dm index 5e22064cfeb2..ae0736e62c2d 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_ability_macros.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_ability_macros.dm @@ -30,11 +30,11 @@ var/action_name = "Toggle Spit Type" handle_xeno_macro(src, action_name) -/datum/action/xeno_action/verb/verb_regurgitate() +/datum/action/xeno_action/verb/verb_release_haul() set category = "Alien" - set name = "Regurgitate" + set name = "Release" set hidden = TRUE - var/action_name = "Regurgitate" + var/action_name = "Release" handle_xeno_macro(src, action_name) /datum/action/xeno_action/verb/verb_choose_resin_structure() 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 abcae5bf8a60..ccdb42baffaf 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm @@ -123,19 +123,16 @@ button.overlays += image('icons/mob/hud/actions_xeno.dmi', button, "shift_spit_[X.ammo.icon_state]") return ..() -/datum/action/xeno_action/onclick/regurgitate/use_ability(atom/A) +/datum/action/xeno_action/onclick/release_haul/use_ability(atom/A) var/mob/living/carbon/xenomorph/X = owner if(!X.check_state()) return if(!isturf(X.loc)) - to_chat(X, SPAN_WARNING("We cannot regurgitate here.")) + to_chat(X, SPAN_WARNING("We cannot put them down here.")) return - if(length(X.stomach_contents)) - for(var/mob/living/M in X.stomach_contents) - // Also has good reason to be a proc on all Xenos - X.regurgitate(M, TRUE) + X.release_haul(TRUE) return ..() @@ -441,7 +438,7 @@ pre_pounce_effects() X.pounce_distance = get_dist(X, A) - X.throw_atom(A, distance, throw_speed, X, launch_type = LOW_LAUNCH, pass_flags = pounce_pass_flags, collision_callbacks = pounce_callbacks) + X.throw_atom(A, distance, throw_speed, X, launch_type = LOW_LAUNCH, pass_flags = pounce_pass_flags, collision_callbacks = pounce_callbacks, tracking=TRUE) X.update_icons() additional_effects_always() @@ -932,6 +929,8 @@ /datum/action/xeno_action/activable/tail_stab/use_ability(atom/targetted_atom) var/mob/living/carbon/xenomorph/stabbing_xeno = owner + if(HAS_TRAIT(targetted_atom, TRAIT_HAULED)) + return if(HAS_TRAIT(stabbing_xeno, TRAIT_ABILITY_BURROWED) || stabbing_xeno.is_ventcrawling) to_chat(stabbing_xeno, SPAN_XENOWARNING("We must be above ground to do this.")) 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 be3cad44922b..6cba9882d316 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 @@ -235,7 +235,7 @@ apply_cooldown() - predalien_smash.throw_atom(get_step_towards(affected_atom, predalien_smash), grab_range, SPEED_FAST, predalien_smash) + predalien_smash.throw_atom(get_step_towards(affected_atom, predalien_smash), grab_range, SPEED_FAST, predalien_smash, tracking=TRUE) if(predalien_smash.Adjacent(carbon) && predalien_smash.start_pulling(carbon, TRUE)) playsound(carbon.pulledby, 'sound/voice/predalien_growl.ogg', 75, 0, status = 0) // bang and roar for dramatic effect diff --git a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm index b07730340525..ccfdf0702266 100644 --- a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm +++ b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm @@ -12,6 +12,10 @@ if(attacking_xeno.fortify || HAS_TRAIT(attacking_xeno, TRAIT_ABILITY_BURROWED)) return XENO_NO_DELAY_ACTION + if(HAS_TRAIT(src, TRAIT_HAULED)) + to_chat(attacking_xeno, SPAN_WARNING("[src] is being hauled, we cannot do anything to them.")) + return + var/intent = attacking_xeno.a_intent if(attacking_xeno.behavior_delegate) @@ -592,13 +596,19 @@ if(M.is_mob_incapacitated() || M.body_position != STANDING_UP) return XENO_NO_DELAY_ACTION - var/delay + var/delay = 4 SECONDS if(!arePowerSystemsOn()) delay = 1 SECONDS playsound(loc, "alien_doorpry", 25, TRUE) else - delay = 4 SECONDS + switch(M.mob_size) + if(MOB_SIZE_XENO_SMALL, MOB_SIZE_XENO_VERY_SMALL) + delay = 4 SECONDS + if(MOB_SIZE_BIG) + delay = 1 SECONDS + if(MOB_SIZE_XENO) + delay = 3 SECONDS playsound(loc, "alien_doorpry", 25, TRUE) M.visible_message(SPAN_WARNING("[M] digs into [src] and begins to pry it open."), diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm b/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm index c010ec224b97..7ae9b4d61c88 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm @@ -66,7 +66,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab/boiler, /datum/action/xeno_action/activable/corrosive_acid/strong, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm b/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm index daf98a07849e..7b8962e44fc2 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Burrower.dm @@ -56,7 +56,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/corrosive_acid, @@ -146,6 +146,12 @@ invisibility = 101 alpha = 100 anchored = TRUE + + var/mob/living/carbon/human/hauled = hauled_mob?.resolve() + + if(hauled) + hauled.forceMove(src) + if(caste.fire_immunity == FIRE_IMMUNITY_NONE) RegisterSignal(src, COMSIG_LIVING_PREIGNITION, PROC_REF(fire_immune)) RegisterSignal(src, list( @@ -183,6 +189,11 @@ invisibility = FALSE alpha = initial(alpha) anchored = FALSE + + var/mob/living/carbon/human/hauled = hauled_mob?.resolve() + if(hauled) + hauled.forceMove(loc) + playsound(loc, 'sound/effects/burrowoff.ogg', 25) for(var/mob/living/carbon/mob in loc) if(!can_not_harm(mob)) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm index a1b68dc1b09c..21a4c6db0b36 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm @@ -62,7 +62,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/place_construction, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm b/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm index 97f554fbb8b2..18b38fffa029 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm @@ -54,7 +54,7 @@ organ_value = 3000 base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/pounce/crusher_charge, @@ -288,3 +288,238 @@ if(bound_xeno.throwing || is_charging) //Let it build up a bit so we're not changing icons every single turf bound_xeno.icon_state = "[bound_xeno.get_strain_icon()] Crusher Charging" return TRUE + +/datum/action/xeno_action/activable/pounce/crusher_charge/additional_effects_always() + var/mob/living/carbon/xenomorph/xeno = owner + if (!istype(xeno)) + return + + for (var/mob/living/carbon/target in orange(1, get_turf(xeno))) + if(xeno.can_not_harm(target)) + continue + + new /datum/effects/xeno_slow(target, xeno, null, null, 3.5 SECONDS) + to_chat(target, SPAN_XENODANGER("You are slowed as the impact of [xeno] shakes the ground!")) + +/datum/action/xeno_action/activable/pounce/crusher_charge/additional_effects(mob/living/target) + var/mob/living/carbon/xenomorph/xeno = owner + var/mob/living/carbon/target_to_check = target + if (!isxeno_human(target)) + return + + if (target_to_check.stat == DEAD) + return + + xeno.emote("roar") + target.apply_effect(2, WEAKEN) + xeno.visible_message(SPAN_XENODANGER("[xeno] overruns [target_to_check], brutally trampling them underfoot!"), SPAN_XENODANGER("We brutalize [target_to_check] as we crush them underfoot!")) + + target_to_check.apply_armoured_damage(get_xeno_damage_slash(target_to_check, direct_hit_damage), ARMOR_MELEE, BRUTE) + xeno.throw_carbon(target_to_check, xeno.dir, 3) + + target_to_check.last_damage_data = create_cause_data(xeno.caste_type, xeno) + return + +/datum/action/xeno_action/activable/pounce/crusher_charge/pre_windup_effects() + RegisterSignal(owner, COMSIG_XENO_PRE_CALCULATE_ARMOURED_DAMAGE_PROJECTILE, PROC_REF(check_directional_armor)) + + var/mob/living/carbon/xenomorph/xeno_owner = owner + if(!istype(xeno_owner)) + return + + var/datum/behavior_delegate/crusher_base/crusher_delegate = xeno_owner.behavior_delegate + if(!istype(crusher_delegate)) + return + + crusher_delegate.is_charging = TRUE + xeno_owner.update_icons() + +/datum/action/xeno_action/activable/pounce/crusher_charge/post_windup_effects(interrupted) + ..() + UnregisterSignal(owner, COMSIG_XENO_PRE_CALCULATE_ARMOURED_DAMAGE_PROJECTILE) + var/mob/living/carbon/xenomorph/xeno_owner = owner + if(!istype(xeno_owner)) + return + + var/datum/behavior_delegate/crusher_base/crusher_delegate = xeno_owner.behavior_delegate + if(!istype(crusher_delegate)) + return + + addtimer(CALLBACK(src, PROC_REF(undo_charging_icon)), 0.5 SECONDS) // let the icon be here for a bit, it looks cool + +/datum/action/xeno_action/activable/pounce/crusher_charge/proc/undo_charging_icon() + var/mob/living/carbon/xenomorph/xeno_owner = owner + if(!istype(xeno_owner)) + return + + var/datum/behavior_delegate/crusher_base/crusher_delegate = xeno_owner.behavior_delegate + if(!istype(crusher_delegate)) + return + + crusher_delegate.is_charging = FALSE + xeno_owner.update_icons() + +/datum/action/xeno_action/activable/pounce/crusher_charge/proc/check_directional_armor(mob/living/carbon/xenomorph/X, list/damagedata) + SIGNAL_HANDLER + var/projectile_direction = damagedata["direction"] + if(X.dir & REVERSE_DIR(projectile_direction)) + // During the charge windup, crusher gets an extra 15 directional armor in the direction its charging + damagedata["armor"] += frontal_armor + + +// This ties the pounce/throwing backend into the old collision backend +/mob/living/carbon/xenomorph/crusher/pounced_obj(obj/pounced_object) + var/datum/action/xeno_action/activable/pounce/crusher_charge/crusher_charge_action = get_action(src, /datum/action/xeno_action/activable/pounce/crusher_charge) + if (istype(crusher_charge_action) && !crusher_charge_action.action_cooldown_check() && !(pounced_object.type in crusher_charge_action.not_reducing_objects)) + crusher_charge_action.reduce_cooldown(50) + + gain_plasma(10) + + if (!handle_collision(pounced_object)) // Check old backend + obj_launch_collision(pounced_object) + +/mob/living/carbon/xenomorph/crusher/pounced_turf(turf/pounced_turf) + pounced_turf.ex_act(EXPLOSION_THRESHOLD_VLOW, , create_cause_data(caste_type, src)) + ..(pounced_turf) + +/datum/action/xeno_action/onclick/crusher_stomp/use_ability(atom/Atom) + var/mob/living/carbon/xenomorph/xeno = owner + + if (!action_cooldown_check()) + return + + if (!xeno.check_state()) + return + + if(!check_and_use_plasma_owner()) + return + + playsound(xeno, 'sound/effects/bang.ogg', 25) + xeno.visible_message(SPAN_XENODANGER("[xeno] smashes into the ground!"), SPAN_XENODANGER("We smash into the ground!")) + xeno.create_stomp() + + + for (var/mob/living/carbon/targets in orange(distance, xeno)) + + if (targets.stat == DEAD || xeno.can_not_harm(targets)) + continue + + if(targets in get_turf(xeno)) + targets.apply_armoured_damage(get_xeno_damage_slash(targets, damage), ARMOR_MELEE, BRUTE) + + if(targets.mob_size < MOB_SIZE_BIG) + targets.apply_effect(get_xeno_stun_duration(targets, 0.2), WEAKEN) + + new /datum/effects/xeno_slow(targets, xeno, ttl = get_xeno_stun_duration(targets, effect_duration)) + targets.apply_effect(get_xeno_stun_duration(targets, 0.2), WEAKEN) + to_chat(targets, SPAN_XENOHIGHDANGER("You are slowed as [xeno] knocks you off balance!")) + + apply_cooldown() + return ..() + +/datum/action/xeno_action/onclick/crusher_stomp/charger/use_ability() + var/mob/living/carbon/xenomorph/xeno = owner + var/mob/living/carbon/stomped_carbon + + if (!istype(xeno)) + return + + if (!action_cooldown_check()) + return + + if (!xeno.check_state()) + return + + if (!check_and_use_plasma_owner()) + return + + playsound(get_turf(xeno), 'sound/effects/bang.ogg', 25, 0) + xeno.visible_message(SPAN_XENODANGER("[xeno] smashes into the ground!"), SPAN_XENODANGER("We smash into the ground!")) + xeno.create_stomp() + + for (var/mob/living/carbon/target_to_stomp in get_turf(xeno)) // MOBS ONTOP + if (target_to_stomp.stat == DEAD || xeno.can_not_harm(target_to_stomp)) + continue + + new effect_type_base(target_to_stomp, xeno, null, null, get_xeno_stun_duration(target_to_stomp, effect_duration)) + to_chat(target_to_stomp, SPAN_XENOHIGHDANGER("You are BRUTALLY crushed and stomped on by [xeno]!!!")) + shake_camera(target_to_stomp, 10, 2) + if(target_to_stomp.mob_size < MOB_SIZE_BIG) + target_to_stomp.apply_effect(get_xeno_stun_duration(target_to_stomp, 0.2), WEAKEN) + + target_to_stomp.apply_armoured_damage(get_xeno_damage_slash(target_to_stomp, damage), ARMOR_MELEE, BRUTE,"chest", 3) + target_to_stomp.apply_armoured_damage(15, BRUTE) // random + target_to_stomp.last_damage_data = create_cause_data(xeno.caste_type, xeno) + target_to_stomp.emote("pain") + target_to_stomp = stomped_carbon + for (var/mob/living/carbon/targets_to_get in orange(distance, get_turf(xeno))) // MOBS AROUND + if (targets_to_get.stat == DEAD || xeno.can_not_harm(targets_to_get)) + continue + if(targets_to_get.client) + shake_camera(targets_to_get, 10, 2) + if(stomped_carbon) + to_chat(targets_to_get, SPAN_XENOHIGHDANGER("You watch as [stomped_carbon] gets crushed by [xeno]!")) + to_chat(targets_to_get, SPAN_XENOHIGHDANGER("You are shaken as [xeno] quakes the earth!")) + + apply_cooldown() + return ..() + +/datum/action/xeno_action/onclick/crusher_shield/use_ability(atom/Target) + var/mob/living/carbon/xenomorph/xeno = owner + + if (!istype(xeno)) + return + + if (!action_cooldown_check()) + return + + if (!xeno.check_state()) + return + + if (!check_and_use_plasma_owner()) + return + + xeno.visible_message(SPAN_XENOWARNING("[xeno] hunkers down and bolsters its defenses!"), SPAN_XENOHIGHDANGER("We hunker down and bolster our defenses!")) + button.icon_state = "template_active" + + xeno.create_crusher_shield() + + xeno.add_xeno_shield(shield_amount, XENO_SHIELD_SOURCE_CRUSHER, /datum/xeno_shield/crusher) + xeno.overlay_shields() + + xeno.explosivearmor_modifier += 1000 + xeno.recalculate_armor() + + addtimer(CALLBACK(src, PROC_REF(remove_explosion_immunity)), explosion_immunity_dur) + addtimer(CALLBACK(src, PROC_REF(remove_shield)), shield_dur) + + apply_cooldown() + return ..() + +/datum/action/xeno_action/onclick/crusher_shield/proc/remove_explosion_immunity() + var/mob/living/carbon/xenomorph/xeno = owner + if (!istype(xeno)) + return + + xeno.explosivearmor_modifier -= 1000 + xeno.recalculate_armor() + to_chat(xeno, SPAN_XENODANGER("Our immunity to explosion damage ends!")) + +/datum/action/xeno_action/onclick/crusher_shield/proc/remove_shield() + var/mob/living/carbon/xenomorph/xeno = owner + if (!istype(xeno)) + return + + var/datum/xeno_shield/found + for (var/datum/xeno_shield/shield in xeno.xeno_shields) + if (shield.shield_source == XENO_SHIELD_SOURCE_CRUSHER) + found = shield + break + + if (istype(found)) + found.on_removal() + qdel(found) + to_chat(xeno, SPAN_XENOHIGHDANGER("We feel our enhanced shield end!")) + button.icon_state = "template" + + xeno.overlay_shields() diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm b/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm index cf378b241ea9..f810624f1d7e 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Defender.dm @@ -43,7 +43,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab/slam, /datum/action/xeno_action/onclick/toggle_crest, @@ -168,7 +168,7 @@ if(!fendy.crest_defense) apply_cooldown() - fendy.throw_atom(get_step_towards(carbone, fendy), 3, SPEED_SLOW, fendy) + fendy.throw_atom(get_step_towards(carbone, fendy), 3, SPEED_SLOW, fendy, tracking=TRUE) if(!fendy.Adjacent(carbone)) on_cooldown_end() return diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm b/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm index 26c6943cd5df..ff9b3bc862e5 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Drone.dm @@ -58,7 +58,7 @@ old_x = -12 base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/corrosive_acid/weak, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm index 8de39dc98548..1ff8747ff2e0 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm @@ -145,7 +145,7 @@ to_chat(src, SPAN_WARNING("You can't infect \the [human]...")) return visible_message(SPAN_WARNING("\The [src] starts climbing onto \the [human]'s face..."), SPAN_XENONOTICE("You start climbing onto \the [human]'s face...")) - if(!do_after(src, FACEHUGGER_WINDUP_DURATION, INTERRUPT_ALL, BUSY_ICON_HOSTILE, human, INTERRUPT_MOVED, BUSY_ICON_HOSTILE)) + if(!do_after(src, FACEHUGGER_CLIMB_DURATION, INTERRUPT_ALL, BUSY_ICON_HOSTILE, human, INTERRUPT_MOVED, BUSY_ICON_HOSTILE)) return if(human.body_position != LYING_DOWN) to_chat(src, SPAN_WARNING("You can't reach \the [human], they need to be lying down.")) @@ -264,5 +264,5 @@ name = "Base Facehugger Behavior Delegate" /datum/behavior_delegate/facehugger_base/on_life() - if(bound_xeno.body_position == STANDING_UP && !(locate(/obj/effect/alien/weeds) in get_turf(bound_xeno))) - bound_xeno.adjustBruteLoss(1) + if(!(locate(/obj/effect/alien/weeds) in get_turf(bound_xeno))) + bound_xeno.adjustBruteLoss(2) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm b/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm index eb01499a7fb4..a49ff2d96a88 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Hellhound.dm @@ -62,7 +62,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/onclick/xenohide, /datum/action/xeno_action/activable/pounce/gorge, /datum/action/xeno_action/onclick/sense_owner, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm b/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm index cd4f90ce02fe..91b386a7f02f 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Hivelord.dm @@ -60,7 +60,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/corrosive_acid, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/King.dm b/code/modules/mob/living/carbon/xenomorph/castes/King.dm index ca16bc1d628a..46aa1ff78699 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/King.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/King.dm @@ -49,7 +49,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/onclick/rend, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm index 66aeb358b813..e74c162e3927 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm @@ -41,7 +41,7 @@ organ_value = 2000 base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/pounce/lurker, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm b/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm index 43c7c97c7a1f..808a49233d0c 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Praetorian.dm @@ -56,7 +56,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/corrosive_acid, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm b/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm index d658d2e83195..379c5c90b8dc 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Predalien.dm @@ -56,7 +56,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/onclick/feralrush, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm index c34901808437..f86e7d5e31ae 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm @@ -297,7 +297,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/place_construction/not_primary, //normally fifth macro but not as important for queen @@ -327,7 +327,7 @@ var/list/mobile_abilities = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/place_construction/not_primary, //normally fifth macro but not as important for queen @@ -757,7 +757,9 @@ /mob/living/carbon/xenomorph/queen/proc/queen_gut(atom/target) if(!iscarbon(target)) return FALSE - + if(HAS_TRAIT(target, TRAIT_HAULED)) + to_chat(src, SPAN_XENOWARNING("[target] needs to be released first.")) + return FALSE var/mob/living/carbon/victim = target if(get_dist(src, victim) > 1) @@ -849,7 +851,7 @@ var/list/immobile_abilities = list( // These already have their placement locked in: - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/place_construction/not_primary, /datum/action/xeno_action/onclick/emit_pheromones, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm b/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm index f46617cfe838..8a6bc4e0d4c7 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Ravager.dm @@ -53,7 +53,7 @@ organ_value = 3000 base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/pounce/charge, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm b/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm index 7cbe9e8caef7..5be771cea2df 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm @@ -53,7 +53,7 @@ base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/onclick/xenohide, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm b/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm index d0e6ea1c85fe..b4b5ffea998d 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Sentinel.dm @@ -42,7 +42,7 @@ organ_value = 800 base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/corrosive_acid/weak, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm b/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm index 0f2022f38e34..e0990d9c3ed7 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Spitter.dm @@ -44,7 +44,7 @@ tier = 2 base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab/spitter, /datum/action/xeno_action/activable/corrosive_acid, diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm index b0066024b5a2..4faf76b710b4 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm @@ -46,7 +46,7 @@ organ_value = 2000 base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/warrior_punch, @@ -285,7 +285,7 @@ lunge_user.visible_message(SPAN_XENOWARNING("[lunge_user] lunges towards [carbon]!"), SPAN_XENOWARNING("We lunge at [carbon]!")) - lunge_user.throw_atom(get_step_towards(affected_atom, lunge_user), grab_range, SPEED_FAST, lunge_user) + lunge_user.throw_atom(get_step_towards(affected_atom, lunge_user), grab_range, SPEED_FAST, lunge_user, tracking=TRUE) if (lunge_user.Adjacent(carbon)) lunge_user.start_pulling(carbon,1) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm b/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm index 531f7f2d9b14..1ddf4919ae66 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/lesser_drone.dm @@ -59,7 +59,7 @@ acid_blood_damage = 15 base_actions = list( /datum/action/xeno_action/onclick/xeno_resting, - /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/onclick/release_haul, /datum/action/xeno_action/watch_xeno, /datum/action/xeno_action/activable/tail_stab, /datum/action/xeno_action/activable/corrosive_acid/weak, diff --git a/code/modules/mob/living/carbon/xenomorph/damage_procs.dm b/code/modules/mob/living/carbon/xenomorph/damage_procs.dm index a09212a46dd5..5bed5dfd1b59 100644 --- a/code/modules/mob/living/carbon/xenomorph/damage_procs.dm +++ b/code/modules/mob/living/carbon/xenomorph/damage_procs.dm @@ -54,9 +54,6 @@ last_damage_data = istype(cause_data) ? cause_data : create_cause_data(cause_data) - if(severity > EXPLOSION_THRESHOLD_LOW && length(stomach_contents)) - for(var/mob/M in stomach_contents) - M.ex_act(severity - EXPLOSION_THRESHOLD_LOW, last_damage_data, pierce) var/b_loss = 0 var/f_loss = 0 @@ -276,6 +273,8 @@ for(var/mob/living/carbon/human/victim in orange(radius, src)) //Loop through all nearby victims, including the tile. splash_chance = 65 - (i * 5) + if(HAS_TRAIT(victim, TRAIT_HAULED)) + continue if(victim.loc == loc) splash_chance += 30 //Same tile? BURN if(victim.species?.acid_blood_dodge_chance) diff --git a/code/modules/mob/living/carbon/xenomorph/death.dm b/code/modules/mob/living/carbon/xenomorph/death.dm index 1243d3badcdc..3e72aa9fe85d 100644 --- a/code/modules/mob/living/carbon/xenomorph/death.dm +++ b/code/modules/mob/living/carbon/xenomorph/death.dm @@ -107,10 +107,6 @@ GLOBAL_VAR_INIT(total_dead_xenos, 0) if(behavior_delegate) behavior_delegate.handle_death(src) - for(var/atom/movable/A in stomach_contents) - stomach_contents.Remove(A) - A.acid_damage = 0 //Reset the acid damage - A.forceMove(loc) // Banished xeno provide a burrowed larva on death to compensate if(banished && refunds_larva_if_banished) diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm index bddd3bb58c6c..6d21db1cb71f 100644 --- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm +++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm @@ -747,9 +747,6 @@ if(get_area(xeno) != hijacked_dropship && xeno.loc && is_ground_level(xeno.loc.z)) 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(length(xeno.stomach_contents)) - xeno.devour_timer = 0 - xeno.handle_stomach_contents() qdel(xeno) continue if(xeno.hunter_data.hunted && !isqueen(xeno)) @@ -757,9 +754,8 @@ xeno.set_hive_and_update(XENO_HIVE_FORSAKEN) else 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(length(xeno.stomach_contents)) - xeno.devour_timer = 0 - xeno.handle_stomach_contents() + if(xeno.hauled_mob?.resolve()) + xeno.release_haul(xeno.hauled_mob.resolve()) qdel(xeno) stored_larva++ continue diff --git a/code/modules/mob/living/carbon/xenomorph/life.dm b/code/modules/mob/living/carbon/xenomorph/life.dm index 2a3f8ed3bfdb..13a4870f7b48 100644 --- a/code/modules/mob/living/carbon/xenomorph/life.dm +++ b/code/modules/mob/living/carbon/xenomorph/life.dm @@ -25,7 +25,6 @@ handle_xeno_fire() handle_pheromones() handle_regular_status_updates() - handle_stomach_contents() handle_overwatch() // For new Xeno hivewide overwatch - Fourk, 6/24/19 update_icons() handle_luminosity() @@ -221,23 +220,6 @@ return TRUE -/mob/living/carbon/xenomorph/proc/handle_stomach_contents() - //Deal with dissolving/damaging stuff in stomach. - if(length(stomach_contents)) - for(var/atom/movable/M in stomach_contents) - if(ishuman(M)) - if(world.time > devour_timer - 50 && world.time < devour_timer - 30) - to_chat(src, SPAN_WARNING("We're about to regurgitate [M]...")) - playsound(loc, 'sound/voice/alien_drool1.ogg', 50, 1) - var/mob/living/carbon/human/H = M - if(world.time > devour_timer || (H.stat == DEAD && !H.chestburst)) - regurgitate(H) - - M.acid_damage++ - if(M.acid_damage > 300) - to_chat(src, SPAN_XENODANGER("\The [M] is dissolved in our gut with a gurgle.")) - stomach_contents.Remove(M) - qdel(M) /mob/living/carbon/xenomorph/proc/handle_regular_hud_updates() if(!mind) diff --git a/code/modules/mob/living/carbon/xenomorph/resin_constructions.dm b/code/modules/mob/living/carbon/xenomorph/resin_constructions.dm index 1bf817392ab9..c91344c34d4a 100644 --- a/code/modules/mob/living/carbon/xenomorph/resin_constructions.dm +++ b/code/modules/mob/living/carbon/xenomorph/resin_constructions.dm @@ -68,7 +68,7 @@ return FALSE if(range_between_constructions) - for(var/i in urange(range_between_constructions, T)) + for(var/i in long_range(range_between_constructions, T)) var/atom/A = i if(A.type == build_path) to_chat(X, SPAN_WARNING("This is too close to another similar structure!")) diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm index 7eebd3c6ec90..02c659ad06c3 100644 --- a/code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/crusher/charger.dm @@ -652,6 +652,74 @@ charger_ability.stop_momentum() +/datum/action/xeno_action/onclick/charger_charge/use_ability(atom/Target) + var/mob/living/carbon/xenomorph/Xeno = owner + + activated = !activated + var/will_charge = "[activated ? "now" : "no longer"]" + to_chat(Xeno, SPAN_XENONOTICE("We will [will_charge] charge when moving.")) + if(activated) + RegisterSignal(Xeno, COMSIG_MOVABLE_MOVED, PROC_REF(handle_movement)) + RegisterSignal(Xeno, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(handle_position_change)) + RegisterSignal(Xeno, COMSIG_ATOM_DIR_CHANGE, PROC_REF(handle_dir_change)) + RegisterSignal(Xeno, COMSIG_XENO_RECALCULATE_SPEED, PROC_REF(update_speed)) + RegisterSignal(Xeno, COMSIG_XENO_STOP_MOMENTUM, PROC_REF(stop_momentum)) + RegisterSignal(Xeno, COMSIG_MOVABLE_ENTERED_RIVER, PROC_REF(handle_river)) + RegisterSignal(Xeno, COMSIG_LIVING_PRE_COLLIDE, PROC_REF(handle_collision)) + RegisterSignal(Xeno, COMSIG_XENO_START_CHARGING, PROC_REF(start_charging)) + button.icon_state = "template_active" + else + stop_momentum() + UnregisterSignal(Xeno, list( + COMSIG_MOVABLE_MOVED, + COMSIG_LIVING_SET_BODY_POSITION, + COMSIG_ATOM_DIR_CHANGE, + COMSIG_XENO_RECALCULATE_SPEED, + COMSIG_MOVABLE_ENTERED_RIVER, + COMSIG_LIVING_PRE_COLLIDE, + COMSIG_XENO_STOP_MOMENTUM, + COMSIG_XENO_START_CHARGING, + )) + button.icon_state = "template" + return ..() + +/datum/action/xeno_action/activable/tumble/use_ability(atom/Target) + if(!action_cooldown_check()) + return + var/mob/living/carbon/xenomorph/Xeno = owner + if (!Xeno.check_state()) + return + if(Xeno.plasma_stored <= plasma_cost) + return + var/target_dist = get_dist(Xeno, Target) + var/dir_between = get_dir(Xeno, Target) + var/target_dir + for(var/perpen_dir in get_perpen_dir(Xeno.dir)) + if(dir_between & perpen_dir) + target_dir = perpen_dir + break + + if(!target_dir) + return + + Xeno.visible_message(SPAN_XENOWARNING("[Xeno] tumbles over to the side!"), SPAN_XENOHIGHDANGER("We tumble over to the side!")) + Xeno.spin(5,1) // note: This spins the sprite and DOES NOT affect directional armor + var/start_charging = HAS_TRAIT(Xeno, TRAIT_CHARGING) + SEND_SIGNAL(Xeno, COMSIG_XENO_STOP_MOMENTUM) + Xeno.flags_atom |= DIRLOCK + playsound(Xeno,"alien_tail_swipe", 50, 1) + + Xeno.use_plasma(plasma_cost) + + var/target = get_step(get_step(Xeno, target_dir), target_dir) + var/list/collision_callbacks = list(/mob/living/carbon/human = CALLBACK(src, PROC_REF(handle_mob_collision))) + var/list/end_throw_callbacks = list(CALLBACK(src, PROC_REF(on_end_throw), start_charging)) + Xeno.throw_atom(target, target_dist, SPEED_FAST, launch_type = LOW_LAUNCH, pass_flags = PASS_CRUSHER_CHARGE, end_throw_callbacks = end_throw_callbacks, collision_callbacks = collision_callbacks) + + apply_cooldown() + return ..() + + #undef CHARGER_DESTROY #undef CHARGER_DAMAGE_CADE diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm index c5e1cff29c63..86e385bdd9f6 100644 --- a/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/facehugger/watcher.dm @@ -22,10 +22,8 @@ /datum/behavior_delegate/facehugger_watcher/on_life() // Sap health if we're standing, not on weeds, and not zoomed out - if(bound_xeno.body_position != STANDING_UP) - return if(bound_xeno.is_zoomed) return if(locate(/obj/effect/alien/weeds) in get_turf(bound_xeno)) return - bound_xeno.adjustBruteLoss(1) + bound_xeno.adjustBruteLoss(2) diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_helpers.dm b/code/modules/mob/living/carbon/xenomorph/xeno_helpers.dm index 294ea27b6260..2bca2ca224fd 100644 --- a/code/modules/mob/living/carbon/xenomorph/xeno_helpers.dm +++ b/code/modules/mob/living/carbon/xenomorph/xeno_helpers.dm @@ -2,11 +2,11 @@ return (mob_size < MOB_SIZE_BIG && caste.can_vent_crawl) /mob/living/carbon/xenomorph/ventcrawl_carry() - if(length(stomach_contents)) - for(var/mob/living/carbon/human/H in stomach_contents) - if(!isspeciesmonkey(H)) - to_chat(src, SPAN_XENOWARNING("You cannot ventcrawl with [H] inside you!")) - return FALSE + var/mob/living/carbon/human/user = hauled_mob?.resolve() + if(user) + if(!isspeciesmonkey(user)) + to_chat(src, SPAN_XENOWARNING("You cannot ventcrawl while hauling [user]!")) + return FALSE return TRUE /mob/living/carbon/xenomorph/can_inject() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 9bb5357ede55..f5c44160db89 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -415,7 +415,7 @@ ..() -/mob/living/launch_towards(datum/launch_metadata/LM) +/mob/living/launch_towards(datum/launch_metadata/LM, tracking = FALSE) if(src) SEND_SIGNAL(src, COMSIG_MOB_MOVE_OR_LOOK, TRUE, dir, dir) if(!istype(LM) || !LM.target || !src) @@ -694,11 +694,13 @@ // legacy procs /mob/living/put_in_l_hand(obj/item/W) if(body_position == LYING_DOWN) - return + if(!HAS_TRAIT(src, TRAIT_HAULED)) + return return ..() /mob/living/put_in_r_hand(obj/item/W) if(body_position == LYING_DOWN) - return + if(!HAS_TRAIT(src, TRAIT_HAULED)) + return return ..() /mob/living/onZImpact(turf/impact_turf, height) diff --git a/code/modules/mob/living/living_verbs.dm b/code/modules/mob/living/living_verbs.dm index 62c15d12f7f0..e762b2e02376 100644 --- a/code/modules/mob/living/living_verbs.dm +++ b/code/modules/mob/living/living_verbs.dm @@ -1,7 +1,7 @@ /mob/living/can_resist() if(next_move > world.time) return FALSE - if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) + if(HAS_TRAIT(src, TRAIT_INCAPACITATED) || HAS_TRAIT(src, TRAIT_HAULED)) return FALSE return TRUE diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index b15dd898e875..7eb52c3a5df7 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -386,7 +386,7 @@ if(ishuman(mob)) squad = mob.assigned_squad if(!check_improved_pointing()) //Squad Leaders and above have reduced cooldown and get a bigger arrow - recently_pointed_to = world.time + 50 + recently_pointed_to = world.time + 2.5 SECONDS new /obj/effect/overlay/temp/point(T, src, A) else recently_pointed_to = world.time + 10 diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm index 993b4ccfed0f..a768d17f8f9d 100644 --- a/code/modules/mob/mob_grab.dm +++ b/code/modules/mob/mob_grab.dm @@ -118,7 +118,7 @@ if(!istype(pulled)) return if(isxeno(pulled) || issynth(pulled)) - to_chat(xeno, SPAN_WARNING("That wouldn't taste very good.")) + to_chat(xeno, SPAN_WARNING("That wouldn't serve a purpose.")) return 0 if(pulled.buckled) to_chat(xeno, SPAN_WARNING("[pulled] is buckled to something.")) @@ -126,45 +126,34 @@ if(pulled.stat == DEAD && !pulled.chestburst) to_chat(xeno, SPAN_WARNING("Ew, [pulled] is already starting to rot.")) return 0 - if(length(xeno.stomach_contents)) //Only one thing in the stomach at a time, please - to_chat(xeno, SPAN_WARNING("We already have something in our stomach, there's no way that will fit.")) + if(xeno.hauled_mob?.resolve()) // We can't carry more than one mob + to_chat(xeno, SPAN_WARNING("You already are carrying something, there's no way that will work.")) return 0 - /* Saving this in case we want to allow devouring of dead bodies UNLESS their client is still online somewhere + if(HAS_TRAIT(pulled, TRAIT_HAULED)) + to_chat(xeno, SPAN_WARNING("They are already being hauled by someone else.")) + return 0 + /* Saving this in case we want to allow hauling of dead bodies UNLESS their client is still online somewhere if(pulled.client) //The client is still inside the body else // The client is observing for(var/mob/dead/observer/G in player_list) if(ckey(G.mind.original.ckey) == pulled.ckey) - to_chat(src, "You start to devour [pulled] but realize \he is already dead.") + to_chat(src, "You start to haul [pulled] but realize \he is already dead.") return */ if(user.action_busy) to_chat(xeno, SPAN_WARNING("We are already busy with something.")) return SEND_SIGNAL(xeno, COMSIG_MOB_EFFECT_CLOAK_CANCEL) - xeno.visible_message(SPAN_DANGER("[xeno] starts to devour [pulled]!"), - SPAN_DANGER("We start to devour [pulled]!"), null, 5) + xeno.visible_message(SPAN_DANGER("[xeno] starts to restrain [pulled]!"), + SPAN_DANGER("We start restraining [pulled]!"), null, 5) if(HAS_TRAIT(xeno, TRAIT_CLOAKED)) //cloaked don't show the visible message, so we gotta work around - to_chat(pulled, FONT_SIZE_HUGE(SPAN_DANGER("[xeno] is trying to devour you!"))) + to_chat(pulled, FONT_SIZE_HUGE(SPAN_DANGER("[xeno] is trying to restrain you!"))) if(do_after(xeno, 50, INTERRUPT_NO_NEEDHAND, BUSY_ICON_HOSTILE)) - if(isxeno(pulled.loc) && !length(xeno.stomach_contents)) - to_chat(xeno, SPAN_WARNING("Someone already ate \the [pulled].")) + if((isxeno(pulled.loc) && !xeno.hauled_mob) || HAS_TRAIT(pulled, TRAIT_HAULED)) + to_chat(xeno, SPAN_WARNING("Someone already took \the [pulled].")) return 0 - if(xeno.pulling == pulled && !pulled.buckled && (pulled.stat != DEAD || pulled.chestburst) && !length(xeno.stomach_contents)) //make sure you've still got them in your claws, and alive - if(SEND_SIGNAL(pulled, COMSIG_MOB_DEVOURED, xeno) & COMPONENT_CANCEL_DEVOUR) + if(xeno.pulling == pulled && !pulled.buckled && (pulled.stat != DEAD || pulled.chestburst) && !xeno.hauled_mob?.resolve()) //make sure you've still got them in your claws, and alive + if(SEND_SIGNAL(pulled, COMSIG_MOB_HAULED, xeno) & COMPONENT_CANCEL_HAUL) return FALSE + xeno.haul(pulled) + xeno.stop_pulling() - xeno.visible_message(SPAN_WARNING("[xeno] devours [pulled]!"), - SPAN_WARNING("We devour [pulled]!"), null, 5) - log_interact(xeno, pulled, "[key_name(xeno)] devoured [key_name(pulled)] at [get_area_name(xeno)]") - - if(ishuman(pulled)) - var/mob/living/carbon/human/pulled_human = pulled - pulled_human.disable_lights() - - //Then, we place the mob where it ought to be - xeno.stomach_contents.Add(pulled) - xeno.devour_timer = world.time + 500 + rand(0,200) // 50-70 seconds - pulled.forceMove(xeno) - return TRUE - if(!(pulled in xeno.stomach_contents)) - to_chat(xeno, SPAN_WARNING("We stop devouring [pulled]. They probably tasted gross anyways.")) - return 0 diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 5ca6841bd78a..e6d968ed13b3 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -96,6 +96,12 @@ if(isliving(mob)) living_mob = mob + if(ishuman(living_mob)) // Might as well just do it here than set movement delay to 0 + var/mob/living/carbon/human/human = living_mob + if(HAS_TRAIT(human, TRAIT_HAULED)) + human.handle_haul_resist() + return + if(world.time < next_movement) return if(living_mob && living_mob.body_position == LYING_DOWN && mob.crawling) diff --git a/code/modules/movement/launching/launching.dm b/code/modules/movement/launching/launching.dm index bb32a8e14ba3..c598482f3227 100644 --- a/code/modules/movement/launching/launching.dm +++ b/code/modules/movement/launching/launching.dm @@ -130,7 +130,7 @@ return TRUE // Proc for throwing items (should only really be used for throw) -/atom/movable/proc/throw_atom(atom/target, range, speed = 0, atom/thrower, spin, launch_type = NORMAL_LAUNCH, pass_flags = NO_FLAGS, list/end_throw_callbacks, list/collision_callbacks) +/atom/movable/proc/throw_atom(atom/target, range, speed = 0, atom/thrower, spin, launch_type = NORMAL_LAUNCH, pass_flags = NO_FLAGS, list/end_throw_callbacks, list/collision_callbacks, tracking = FALSE) var/temp_pass_flags = pass_flags switch (launch_type) if (NORMAL_LAUNCH) @@ -155,7 +155,7 @@ flags_atom |= NO_ZFALL - launch_towards(LM) + launch_towards(LM, tracking) flags_atom &= ~NO_ZFALL var/turf/end_turf = get_turf(src) @@ -164,7 +164,7 @@ // Proc for throwing or propelling movable atoms towards a target -/atom/movable/proc/launch_towards(datum/launch_metadata/LM) +/atom/movable/proc/launch_towards(datum/launch_metadata/LM, tracking = FALSE) if (!istype(LM)) CRASH("invalid launch_metadata passed to launch_towards") if (!LM.target || !src) @@ -222,6 +222,8 @@ if(A == LM.target) hit_atom = A break + if(!hit_atom && tracking && get_dist(src, LM.target) <= 1 && get_dist(start_turf, LM.target) <= 1) // If we missed, but we are tracking and the target is still next to us and the turf we launched from, then we still count it as a hit + hit_atom = LM.target launch_impact(hit_atom) if (loc) throwing = FALSE diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index 316cd795980d..d5bb62715438 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -577,7 +577,7 @@ /obj/item/paper/prison_station/test_log name = "paper- 'Test Log'" - info = "

TEST LOG

SPECIMEN: Bioweapon candidate Kappa. Individual 3


\n

-

PROCEDURE: Observation

RESULTS: Specimen paces around cell. Appears agitated. Vocalisations.

-

PROCEDURE: Simian test subject

RESULTS: Devoured by specimen. No significant difference from last simian test.

Note: Time to amp it up

-

PROCEDURE: Human test subject (D-1). Instructed to \"pet it like a dog\"

RESULTS: Specimen and D-1 stare at each other for approximately two seconds. D-1 screams and begins pounding on observation window, begging to be released. Specimen pounces on D-1. Specimen kills D-1 with multiple slashes from its foreclaws.

Note: Promising!

-

PROCEDURE: Two human test subjects (D-2, D-3). Instructed to subdue specimen

RESULTS: D-2 and D-3 slowly approach specimen. D-3 punches specimen on forehead to no noticeable effect. Specimen pounces on D-3, then kills him with multiple slashes from its foreclaws. D-2 screams and begins pounding on observation window. Specimen pounces on D-2, then kills him with multiple slashes from its foreclaws.

Specimen begins slashing at observation access doors. Exhibiting an unexpected amount of strength, it is able to d~

" + info = "

TEST LOG

SPECIMEN: Bioweapon candidate Kappa. Individual 3


\n

-

PROCEDURE: Observation

RESULTS: Specimen paces around cell. Appears agitated. Vocalisations.

-

PROCEDURE: Simian test subject

RESULTS: Nested by specimen. No significant difference from last simian test.

Note: Time to amp it up

-

PROCEDURE: Human test subject (D-1). Instructed to \"pet it like a dog\"

RESULTS: Specimen and D-1 stare at each other for approximately two seconds. D-1 screams and begins pounding on observation window, begging to be released. Specimen pounces on D-1. Specimen kills D-1 with multiple slashes from its foreclaws.

Note: Promising!

-

PROCEDURE: Two human test subjects (D-2, D-3). Instructed to subdue specimen

RESULTS: D-2 and D-3 slowly approach specimen. D-3 punches specimen on forehead to no noticeable effect. Specimen pounces on D-3, then kills him with multiple slashes from its foreclaws. D-2 screams and begins pounding on observation window. Specimen pounces on D-2, then kills him with multiple slashes from its foreclaws.

Specimen begins slashing at observation access doors. Exhibiting an unexpected amount of strength, it is able to d~

" /obj/item/paper/prison_station/interrogation_log name = "paper- 'Test Log'" diff --git a/code/modules/paperwork/prefab_papers.dm b/code/modules/paperwork/prefab_papers.dm index 9721de6c4e86..01225f69963d 100644 --- a/code/modules/paperwork/prefab_papers.dm +++ b/code/modules/paperwork/prefab_papers.dm @@ -199,7 +199,7 @@ GLOBAL_REFERENCE_LIST_INDEXED(prefab_papers, /obj/item/paper/prefab, document_ti document_category = PAPER_CATEGORY_LIAISON /obj/item/paper/prefab/liaison/ops_report - document_title = "WY435 - Liaison Operations Report" + document_title = "WY435 - Local Operations Report" doc_datum_type = /datum/prefab_document/wey_yu/liaison/ops_report /obj/item/paper/prefab/liaison/preserve_intent @@ -218,6 +218,10 @@ GLOBAL_REFERENCE_LIST_INDEXED(prefab_papers, /obj/item/paper/prefab, document_ti document_title = "WY442 - Non Disclosure Agreement" doc_datum_type = /datum/prefab_document/wey_yu/liaison/nda_long +/obj/item/paper/prefab/liaison/nda_long/uscm + document_title = "WY442-B - Non Disclosure Agreement (USCM)" + doc_datum_type = /datum/prefab_document/wey_yu/liaison/nda_long/uscm + // ########## Wey-Yu HC Forms ########## \\ /obj/item/paper/prefab/wey_yu diff --git a/code/modules/paperwork/prefab_papers/provost/military_police/appeal_form.dm b/code/modules/paperwork/prefab_papers/provost/military_police/appeal_form.dm index 72d1db7a63ba..767b0bf1b36d 100644 --- a/code/modules/paperwork/prefab_papers/provost/military_police/appeal_form.dm +++ b/code/modules/paperwork/prefab_papers/provost/military_police/appeal_form.dm @@ -166,7 +166,7 @@ dat += "

" dat += "A PR301b Delayed Action Order (DAO) allows Military Police to reprosecute a defendant for charges they are previously released from due to the inability to adhere to the required timeframes.
" dat += "Request of a DAO requires the defendant be released from detainment, the 'PR301b Requested' section on this document completed, and a PR301b form sent by fax to the Provost Office.
" - dat += "Upon receipt of a response from the DOA fax this document must be updated to indicate the Provost Office's decision. Should a DAO be approved the defendant must be presented with the response from the Provost Office upon, or prior to, being detained." + dat += "Upon receipt of a response from the DAO fax this document must be updated to indicate the Provost Office's decision. Should a DAO be approved the defendant must be presented with the response from the Provost Office upon, or prior to, being detained." dat += "

" dat += " " dat += " " diff --git a/code/modules/paperwork/prefab_papers/wey_yu/liaison/liability.dm b/code/modules/paperwork/prefab_papers/wey_yu/liaison/liability.dm index 187d0b1b4cf8..95daec5014af 100644 --- a/code/modules/paperwork/prefab_papers/wey_yu/liaison/liability.dm +++ b/code/modules/paperwork/prefab_papers/wey_yu/liaison/liability.dm @@ -120,7 +120,7 @@ dat += "" dat += "" dat += "
" - dat += "

I, , forfeit all right to bring a suit against the Weyland Yutani (Space) Corporation for any reason on the site. This agreement releases the Weyland Yutani (Space) Corporation from all liability relating to injuries and financial responsibilities for injuries that may occur on the site.

" + dat += "

I, , forfeit all right to bring a suit against the Weyland Yutani Corporation for any reason on the site. This agreement releases the Weyland Yutani Corporation from all liability relating to injuries and financial responsibilities for injuries that may occur on the site.

" dat += "

Signature:
Liaison Signature: Date: [time2text(REALTIMEOFDAY, "Day DD Month [GLOB.game_year]")]

" dat += "
" dat += "" diff --git a/code/modules/paperwork/prefab_papers/wey_yu/liaison/nda_long.dm b/code/modules/paperwork/prefab_papers/wey_yu/liaison/nda_long.dm index 8f07339b1f2f..3ab6260428bd 100644 --- a/code/modules/paperwork/prefab_papers/wey_yu/liaison/nda_long.dm +++ b/code/modules/paperwork/prefab_papers/wey_yu/liaison/nda_long.dm @@ -1,3 +1,7 @@ +/datum/prefab_document/wey_yu/liaison/nda_long + var/preset_recipient = "" + var/doc_code = "WY442" + /datum/prefab_document/wey_yu/liaison/nda_long/New() var/datum/asset/asset = get_asset_datum(/datum/asset/simple/paper) var/dat = "" @@ -113,14 +117,14 @@ dat += "" dat += "
" dat += "

Non Disclosure Agreement

" - dat += "

WY442

" + dat += "

[doc_code]

" dat += "
" dat += "
" dat += "" dat += "" dat += "" dat += "
" - dat += "This Non-disclosure Agreement is made effective as of [time2text(REALTIMEOFDAY, "Day DD Month [GLOB.game_year]")] by and between The Company and the United States Colonial Marines (the \"recipient\") of property and information regarding all operational procedures of .

" + dat += "This Non-disclosure Agreement is made effective as of [time2text(REALTIMEOFDAY, "Day DD Month [GLOB.game_year]")] by and between The Company and [preset_recipient] (the \"recipient\") of property and information regarding all operational procedures of .

" dat += "
" dat += "

I . Confidential Information.
The term \"Confidential Information\" means by any information which is proprietary to the Owner, whether or not owned or developed by the Owner, which is generally known other than by the Owner, which the Recipient may obtain through any direct or indirect contact with the owner, regardless of whether specifically identified as confidential or proprietary, Confidential Information shall include any information provided by the Owner concerning the business, technology and information of the Owner and any third party with which the owner deals, including, without limitation, business records and plans, trade secrets, technical data, product are ideas, contracts, financial information, pricing structure, health discounts, computer programs, listings and unknown wildlife, are all copyright and intellectual property. The nature of the information and the manner of disclosure are such that a reasonable person would understand is confidential.

" dat += "" @@ -152,9 +156,9 @@ dat += "
" dat += "

X . TERM.
The obligations of this agreement shall survive 03/30/2293 from the Effective date or until the Owner sends the Recipient written notice releasing the Recipient from this Agreement. After that, the Recipient must continue to protect the confidential information that was received during the term of this agreement from unauthorized use or disclosure for a additional time of the Owners Choosing.

" dat += "
" - dat += "

XI. Signatories.
This Agreement shall be executed by the named representative, on behalf of The Company and USS Almayer's Commander, on behalf of United States Colonial Marines and delivered in the manner prescribed by law as of the date first written above.

" + dat += "

XI. Signatories.
This Agreement shall be executed by the named corporate representative, on behalf of The Company, and the recipient's named representative, and delivered in the manner prescribed by law as of the date first written above.

" dat += "

A. The Company.
Date: [time2text(REALTIMEOFDAY, "Day DD Month [GLOB.game_year]")]
Representative:
Title:
Signature:

" - dat += "

B. USCM.
Date: [time2text(REALTIMEOFDAY, "Day DD Month [GLOB.game_year]")]
Representative:
Rank:
Signature:

" + dat += "

B. The Recipient.
Date: [time2text(REALTIMEOFDAY, "Day DD Month [GLOB.game_year]")]
Representative:
Rank:
Signature:

" dat += "
" dat += "" dat += "
" @@ -169,3 +173,7 @@ contents = dat + +/datum/prefab_document/wey_yu/liaison/nda_long/uscm + preset_recipient = "the United States Colonial Marines" + doc_code = "WY442-B" diff --git a/code/modules/paperwork/prefab_papers/wey_yu/liaison/nda_short.dm b/code/modules/paperwork/prefab_papers/wey_yu/liaison/nda_short.dm index 32f10f303361..d71a28a2f874 100644 --- a/code/modules/paperwork/prefab_papers/wey_yu/liaison/nda_short.dm +++ b/code/modules/paperwork/prefab_papers/wey_yu/liaison/nda_short.dm @@ -120,7 +120,7 @@ dat += "" dat += "" dat += "
" - dat += "

I, , agree to a Confidentiality Agreement with the Weyland-Yutani (Space) Corporation that no information regarding the events that took place on the facility named and subsequently the USS Almayer is to be delivered publicly. Only qualified Corporate personnel whose identities have been confirmed will receive such information.

" + dat += "

I, , agree to a Confidentiality Agreement with the Weyland-Yutani Corporation that no information regarding the events that took place on the facility named or any directly supporting facility in the immediate region is to be delivered publicly. Only qualified Corporate personnel whose identities have been confirmed will receive such information.

" dat += "

" dat += "Signature:
" dat += "Liaison Signature:
" diff --git a/code/modules/paperwork/prefab_papers/wey_yu/liaison/ops_report.dm b/code/modules/paperwork/prefab_papers/wey_yu/liaison/ops_report.dm index f8419d468267..857ecbcb96a7 100644 --- a/code/modules/paperwork/prefab_papers/wey_yu/liaison/ops_report.dm +++ b/code/modules/paperwork/prefab_papers/wey_yu/liaison/ops_report.dm @@ -111,7 +111,7 @@ dat += "" dat += "" dat += "

" - dat += "

Liaison Operations Report

" + dat += "

Local Operations Report

" dat += "

WY435

" dat += "
" dat += "
" diff --git a/code/modules/paperwork/prefab_papers/wey_yu/liaison/preserve_intent.dm b/code/modules/paperwork/prefab_papers/wey_yu/liaison/preserve_intent.dm index 81af52075b77..cfcc4f3d6472 100644 --- a/code/modules/paperwork/prefab_papers/wey_yu/liaison/preserve_intent.dm +++ b/code/modules/paperwork/prefab_papers/wey_yu/liaison/preserve_intent.dm @@ -120,7 +120,7 @@ dat += "" dat += "" dat += "
" - dat += "

I, , do hereby swear and affirm that they I will do everything within my power to preserve and protect the integrity and value of all proprietary interests of the Weyland Yutani (Space) Corporation on the surface of or above the surface of .

" + dat += "

I, , do hereby swear and affirm that they I will do everything within my power to preserve and protect the integrity and value of all proprietary interests of the Weyland Yutani Corporation on, or above, the surface of .

" dat += "

Signature:
Liaison Signature:
Date: [time2text(REALTIMEOFDAY, "Day DD Month [GLOB.game_year]")]

" dat += "
" dat += "" diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index 8e3435aebf28..a610310f6fc1 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -783,7 +783,7 @@ /obj/item/light_bulb/proc/shatter() if(status == LIGHT_OK || status == LIGHT_BURNED) - src.visible_message(SPAN_DANGER("[name] shatters."),SPAN_DANGER("You hear a small glass object shatter.")) + visible_message(SPAN_DANGER("[src] shatters."), SPAN_DANGER("You hear a small glass object shatter.")) status = LIGHT_BROKEN force = 5 sharp = IS_SHARP_ITEM_SIMPLE diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm index 1914a47b3b7a..82f810413866 100644 --- a/code/modules/projectiles/ammunition.dm +++ b/code/modules/projectiles/ammunition.dm @@ -136,6 +136,16 @@ They're all essentially identical when it comes to getting the job done. else to_chat(user, SPAN_NOTICE("Try holding [src] before you attempt to restock it.")) +//Is the ammo magazine transferrable, silent version +/obj/item/ammo_magazine/proc/is_transferable(obj/item/ammo_magazine/source) + if(default_ammo != source.default_ammo) + return FALSE + if(current_rounds == max_rounds) + return FALSE + if(source.caliber != caliber) + return FALSE + return TRUE + //Generic proc to transfer ammo between ammo mags. Can work for anything, mags, handfuls, etc. /obj/item/ammo_magazine/proc/transfer_ammo(obj/item/ammo_magazine/source, mob/user, transfer_amount = 1) if(current_rounds == max_rounds) //Does the mag actually need reloading? diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 827c17e80684..4b059bdb2982 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -1609,6 +1609,8 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed return TRUE if(user.is_mob_incapacitated()) return + if(HAS_TRAIT(user, TRAIT_HAULED)) + return if(world.time < guaranteed_delay_time) return if((world.time < wield_time || world.time < pull_time) && (delay_style & WEAPON_DELAY_NO_FIRE > 0)) diff --git a/code/modules/projectiles/gun_helpers.dm b/code/modules/projectiles/gun_helpers.dm index e1446ccb10d5..7e39556e82be 100644 --- a/code/modules/projectiles/gun_helpers.dm +++ b/code/modules/projectiles/gun_helpers.dm @@ -537,7 +537,7 @@ DEFINES in setup.dm, referenced here. /mob/living/carbon/human/verb/holster_verb(unholster_number_offset = 1 as num) set name = "holster" set hidden = TRUE - if(usr.is_mob_incapacitated(TRUE) || usr.is_mob_restrained() || IsKnockDown() || HAS_TRAIT_FROM(src, TRAIT_UNDENSE, LYING_DOWN_TRAIT)) + if(usr.is_mob_incapacitated(TRUE) || usr.is_mob_restrained() || IsKnockDown() || HAS_TRAIT_FROM(src, TRAIT_UNDENSE, LYING_DOWN_TRAIT) && !HAS_TRAIT(src, TRAIT_HAULED)) to_chat(src, SPAN_WARNING("You can't draw a weapon in your current state.")) return diff --git a/code/modules/projectiles/guns/flamer/flamer.dm b/code/modules/projectiles/guns/flamer/flamer.dm index 34f5ac254db6..037a1a0ecd3d 100644 --- a/code/modules/projectiles/guns/flamer/flamer.dm +++ b/code/modules/projectiles/guns/flamer/flamer.dm @@ -749,7 +749,10 @@ burn_damage = 0 if(!burn_damage) - to_chat(M, SPAN_DANGER("[isxeno(M) ? "We" : "You"] step over the flames.")) + if(HAS_TRAIT(M, TRAIT_HAULED)) + M.visible_message(SPAN_WARNING("[M] is shielded from the flames!"), SPAN_WARNING("You are shielded from the flames!")) + else + to_chat(M, SPAN_DANGER("[isxeno(M) ? "We" : "You"] step over the flames.")) return M.last_damage_data = weapon_cause_data diff --git a/code/modules/projectiles/magazines/specialist.dm b/code/modules/projectiles/magazines/specialist.dm index 28d6e8edd805..e39e853dfced 100644 --- a/code/modules/projectiles/magazines/specialist.dm +++ b/code/modules/projectiles/magazines/specialist.dm @@ -99,6 +99,7 @@ w_class = SIZE_MEDIUM default_ammo = /datum/ammo/bullet/smartgun gun_type = /obj/item/weapon/gun/smartgun + flags_magazine = AMMUNITION_REFILLABLE|AMMUNITION_SLAP_TRANSFER /obj/item/ammo_magazine/smartgun/dirty name = "irradiated smartgun drum" @@ -107,6 +108,7 @@ icon = 'icons/obj/items/weapons/guns/ammo_by_faction/WY/machineguns.dmi' default_ammo = /datum/ammo/bullet/smartgun/dirty gun_type = /obj/item/weapon/gun/smartgun/dirty + flags_magazine = AMMUNITION_REFILLABLE|AMMUNITION_SLAP_TRANSFER /obj/item/ammo_magazine/smartgun/holo_targetting name = "holotargetting smartgun drum" @@ -114,6 +116,7 @@ icon_state = "m56_drum" //PLACEHOLDER default_ammo = /datum/ammo/bullet/smartgun/holo_target gun_type = /obj/item/weapon/gun/smartgun/rmc + flags_magazine = AMMUNITION_REFILLABLE|AMMUNITION_SLAP_TRANSFER //------------------------------------------------------- //Flare gun. Close enough? /obj/item/ammo_magazine/internal/flare diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index c52508e01a93..02ad18d884c8 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -43,6 +43,9 @@ /// If the window should be closed with other windows when requested var/closeable = TRUE + /// Any partial packets that we have received from TGUI, waiting to be sent + var/partial_packets + /** * public * @@ -268,6 +271,7 @@ "size" = window_size, "fancy" = user.client?.prefs.tgui_fancy, "locked" = user.client?.prefs.tgui_lock, + "scale" = user.client?.prefs.tgui_scale, ), "client" = list( "ckey" = user.client.ckey, @@ -345,6 +349,31 @@ // Pass act type messages to ui_act if(type && copytext(type, 1, 5) == "act/") var/act_type = copytext(type, 5) + + var/id = href_list["packetId"] + if(!isnull(id)) + id = text2num(id) + + var/total = text2num(href_list["totalPackets"]) + + if(total > MAX_MESSAGE_CHUNKS) + return + + if(id == 1) + partial_packets = new /list(total) + + partial_packets[id] = href_list["packet"] + + if(id != total) + return + + var/assembled_payload = "" + for(var/packet in partial_packets) + assembled_payload += packet + + payload = json_decode(assembled_payload) + partial_packets = null + log_tgui(user, "Action: [act_type] [href_list["payload"]]", window = window, src_object = src_object) diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm index 69a352d76fb3..3124149b6a07 100644 --- a/code/modules/tgui_panel/tgui_panel.dm +++ b/code/modules/tgui_panel/tgui_panel.dm @@ -53,6 +53,7 @@ // Other setup request_telemetry() addtimer(CALLBACK(src, PROC_REF(on_initialize_timed_out)), 5 SECONDS) + window.send_message("testTelemetryCommand") /** * private diff --git a/colonialmarines.dme b/colonialmarines.dme index 9305cf0fe0fe..e30e6ab17aa6 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -2114,7 +2114,6 @@ #include "code\modules\mob\living\carbon\xenomorph\abilities\carrier\carrier_macros.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\crusher\crusher_abilities.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\crusher\crusher_macros.dm" -#include "code\modules\mob\living\carbon\xenomorph\abilities\crusher\crusher_powers.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\defender\defender_abilities.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\defender\defender_macros.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\facehugger\facehugger_abilities.dm" diff --git a/html/changelogs/AutoChangeLog-pr-7645.yml b/html/changelogs/AutoChangeLog-pr-7645.yml deleted file mode 100644 index 107f8d5ddcc2..000000000000 --- a/html/changelogs/AutoChangeLog-pr-7645.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "hry-gh" -delete-after: True -changes: - - rscadd: "added a poll button to the lobby screen. engage democratically." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-7819.yml b/html/changelogs/AutoChangeLog-pr-7819.yml new file mode 100644 index 000000000000..d01c2ee8871c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-7819.yml @@ -0,0 +1,4 @@ +author: "JackieEstegado" +delete-after: True +changes: + - balance: "Point blank pounces/lunges can no longer miss" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-7983.yml b/html/changelogs/AutoChangeLog-pr-7983.yml new file mode 100644 index 000000000000..a05c87eea4a8 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-7983.yml @@ -0,0 +1,5 @@ +author: "CapCamIII" +delete-after: True +changes: + - rscadd: "Some roles have lower minimum ages to represent their ranks better, no gameplay changes." + - rscadd: "Being below the minimum age for a role now sets you to that minimum age, plus two years." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8505.yml b/html/changelogs/AutoChangeLog-pr-8505.yml deleted file mode 100644 index 9480a357f385..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8505.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "TrollerNoob" -delete-after: True -changes: - - rscadd: "New shipments of Chestrigs and Technician Welder Chestrigs have arrived from Chinook station. Urban and Snow operations are now covered." - - imageadd: "Added Urban/Snow variants for Chestrig and Welder Chestrig" - - code_imp: "Removed some unneeded code from Chestrig/Welder Chestrig for automatic map variants." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8556.yml b/html/changelogs/AutoChangeLog-pr-8556.yml deleted file mode 100644 index 06ff0d7f870c..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8556.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "lanse12" -delete-after: True -changes: - - spellcheck: "fixed some turret related typos" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8568.yml b/html/changelogs/AutoChangeLog-pr-8568.yml deleted file mode 100644 index 7b37dc8c1aca..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8568.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Syndro101" -delete-after: True -changes: - - code_imp: "changed lots of landmarks to use their new icons" - - imageadd: "new landmark icons" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8581.yml b/html/changelogs/AutoChangeLog-pr-8581.yml deleted file mode 100644 index 2a2015d193d4..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8581.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Drathek" -delete-after: True -changes: - - code_imp: "Removed unused variables in dancer behavior delegate" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8582.yml b/html/changelogs/AutoChangeLog-pr-8582.yml deleted file mode 100644 index f612b3f8ec84..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8582.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "private-tristan" -delete-after: True -changes: - - admin: "Create-Xenos range defaults to 0, instead of 1, hooray!" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8586.yml b/html/changelogs/AutoChangeLog-pr-8586.yml deleted file mode 100644 index 2060a0388dfb..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8586.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MistChristmas" -delete-after: True -changes: - - bugfix: "Fixes setting the amount of belts in the SecTech twice." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8587.yml b/html/changelogs/AutoChangeLog-pr-8587.yml deleted file mode 100644 index 4f717cbe9d75..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8587.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Drathek" -delete-after: True -changes: - - bugfix: "Fixed chat theme always defaulting to dark on a setting change" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8597.yml b/html/changelogs/AutoChangeLog-pr-8597.yml deleted file mode 100644 index 04988d67f88d..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8597.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "JackieEstegado" -delete-after: True -changes: - - bugfix: "Antiparasitic chems can cure stage 2+ infected again." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8599.yml b/html/changelogs/AutoChangeLog-pr-8599.yml deleted file mode 100644 index 1166bf452101..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8599.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Red-byte3D" -delete-after: True -changes: - - bugfix: "Fixes hunting ground spawner code checking for if theres people above the min_mob instead of below" - - rscadd: "Hunting ground prey and youngblood now reset their timers if nobody elligible were found" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8601.yml b/html/changelogs/AutoChangeLog-pr-8601.yml deleted file mode 100644 index 7ad6cd660bd6..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8601.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Drathek" -delete-after: True -changes: - - bugfix: "Fixed UPP supply packs overriding all normal variant supply packs" - - code_imp: "Added a check to ensure all usage of GLOBAL_SUBTYPE_PATH_INDEXED has uniquely indexed entries" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8602.yml b/html/changelogs/AutoChangeLog-pr-8602.yml deleted file mode 100644 index 7bb209020e16..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8602.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Red-byte3D" -delete-after: True -changes: - - bugfix: "Girders are now able to be deconstructed/dismantled etc post repair" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8605.yml b/html/changelogs/AutoChangeLog-pr-8605.yml deleted file mode 100644 index eea28b71eb22..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8605.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Red-byte3D" -delete-after: True -changes: - - bugfix: "Strain resetting no longer gives you an evolution button as facehugger" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8608.yml b/html/changelogs/AutoChangeLog-pr-8608.yml new file mode 100644 index 000000000000..6f3b611fbc14 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-8608.yml @@ -0,0 +1,4 @@ +author: "Red-byte3D" +delete-after: True +changes: + - code_imp: "Some early returns for admin procs" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8611.yml b/html/changelogs/AutoChangeLog-pr-8611.yml deleted file mode 100644 index a4b2d161dd41..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8611.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "NHC" -delete-after: True -changes: - - imageadd: "changed jacket icon name from pilot_flightsuit_dj to pilot_flightsuit_alt_dj" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8615.yml b/html/changelogs/AutoChangeLog-pr-8615.yml deleted file mode 100644 index a0a5d52c44d9..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8615.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Red-byte3D" -delete-after: True -changes: - - bugfix: "Yautja no longer get buffed by orders" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8616.yml b/html/changelogs/AutoChangeLog-pr-8616.yml deleted file mode 100644 index 628d5eb81aea..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8616.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Red-byte3D" -delete-after: True -changes: - - bugfix: "Yautja no longer see the security level in the stat panel" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8617.yml b/html/changelogs/AutoChangeLog-pr-8617.yml deleted file mode 100644 index b4a3c8811077..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8617.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MeH0y" -delete-after: True -changes: - - bugfix: "burrower blip is correctly colored" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8621.yml b/html/changelogs/AutoChangeLog-pr-8621.yml deleted file mode 100644 index 5809ac800a45..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8621.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Danilcus-W" -delete-after: True -changes: - - qol: "Trappers can now see how much \"Insight\" they currently have." - - bugfix: "Trapper abilities empowered overlay is now shown." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8629.yml b/html/changelogs/AutoChangeLog-pr-8629.yml deleted file mode 100644 index 06ebd8492055..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8629.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "remindme" -delete-after: True -changes: - - bugfix: "Some MREs that should've had veggies in them had no veggies (ripoff!)" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8633.yml b/html/changelogs/AutoChangeLog-pr-8633.yml deleted file mode 100644 index f90bce4a3856..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8633.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Drathek" -delete-after: True -changes: - - balance: "Grace timer for being AFK is reduced from 20 mins down to 3 mins after round start" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8639.yml b/html/changelogs/AutoChangeLog-pr-8639.yml deleted file mode 100644 index 4584f57a04da..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8639.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Red-byte3D" -delete-after: True -changes: - - admin: "Added logging for shuttle manipulation." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8647.yml b/html/changelogs/AutoChangeLog-pr-8647.yml deleted file mode 100644 index ce0ed49ac6c0..000000000000 --- a/html/changelogs/AutoChangeLog-pr-8647.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "larentoun" -delete-after: True -changes: - - qol: "Added a visual timer for cooldowns of Xeno Abilities" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8687.yml b/html/changelogs/AutoChangeLog-pr-8687.yml new file mode 100644 index 000000000000..4a5a9a274c98 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-8687.yml @@ -0,0 +1,4 @@ +author: "MeH0y" +delete-after: True +changes: + - balance: "Changes Field Doctor Timelock" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8690.yml b/html/changelogs/AutoChangeLog-pr-8690.yml new file mode 100644 index 000000000000..aa3e16de6a86 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-8690.yml @@ -0,0 +1,4 @@ +author: "blackdragonTOW" +delete-after: True +changes: + - bugfix: "fixed missing sprite for active K9 tracker" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8691.yml b/html/changelogs/AutoChangeLog-pr-8691.yml new file mode 100644 index 000000000000..9ed15073f640 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-8691.yml @@ -0,0 +1,8 @@ +author: "Drathek" +delete-after: True +changes: + - code_imp: "Refactored icon_states usage from handle_ooc_prefix to a glob list" + - bugfix: "Fixed extra_titles not stripping spaces for ooc icons" + - imageadd: "Centered byond logo ooc icon" + - admin: "Admin rank is now the last ooc icon" + - config: "ooc.dmi for rank icons is now expected in the config folder" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8712.yml b/html/changelogs/AutoChangeLog-pr-8712.yml new file mode 100644 index 000000000000..f676d0902a40 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-8712.yml @@ -0,0 +1,4 @@ +author: "hry-gh" +delete-after: True +changes: + - bugfix: "fixes sticky keys (even more)" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8722.yml b/html/changelogs/AutoChangeLog-pr-8722.yml new file mode 100644 index 000000000000..49abf467f337 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-8722.yml @@ -0,0 +1,4 @@ +author: "MistChristmas" +delete-after: True +changes: + - bugfix: "Fixes some UIs CRT theme being left behind when scrolling." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-8723.yml b/html/changelogs/AutoChangeLog-pr-8723.yml new file mode 100644 index 000000000000..952db4126f13 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-8723.yml @@ -0,0 +1,5 @@ +author: "hry-gh" +delete-after: True +changes: + - rscadd: "there's now a new tgui scale option in settings for people with high dpi monitors using windows scaling that allows you to toggle between having windows be larger but their contents the correct size, or having windows be the normal size but their contents shrunk down" + - bugfix: "people with a different windows scaling option than 100% on 516 can drag windows normally again" \ No newline at end of file diff --git a/html/changelogs/archive/2025-03.yml b/html/changelogs/archive/2025-03.yml index 922986b03b07..95e2a1371e2e 100644 --- a/html/changelogs/archive/2025-03.yml +++ b/html/changelogs/archive/2025-03.yml @@ -24,3 +24,113 @@ - imageadd: Added a blank OOC icon for missing sprites. - imageadd: Added a StaffManager OOC icon. - imageadd: Re-Added missing RatKing OOC icon. +2025-03-05: + Danilcus-W: + - qol: Trappers can now see how much "Insight" they currently have. + - bugfix: Trapper abilities empowered overlay is now shown. + Drathek: + - bugfix: Fixed UPP supply packs overriding all normal variant supply packs + - code_imp: Added a check to ensure all usage of GLOBAL_SUBTYPE_PATH_INDEXED has + uniquely indexed entries + - bugfix: Fixed chat theme always defaulting to dark on a setting change + - code_imp: Removed unused variables in dancer behavior delegate + - balance: Grace timer for being AFK is reduced from 20 mins down to 3 mins after + round start + JackieEstegado: + - bugfix: Antiparasitic chems can cure stage 2+ infected again. + MeH0y: + - bugfix: burrower blip is correctly colored + MistChristmas: + - bugfix: Fixes setting the amount of belts in the SecTech twice. + NHC: + - imageadd: changed jacket icon name from pilot_flightsuit_dj to pilot_flightsuit_alt_dj + Red-byte3D: + - bugfix: Yautja no longer see the security level in the stat panel + - bugfix: Yautja no longer get buffed by orders + - bugfix: Girders are now able to be deconstructed/dismantled etc post repair + - admin: Added logging for shuttle manipulation. + - bugfix: Fixes hunting ground spawner code checking for if theres people above + the min_mob instead of below + - rscadd: Hunting ground prey and youngblood now reset their timers if nobody elligible + were found + - bugfix: Strain resetting no longer gives you an evolution button as facehugger + Syndro101: + - code_imp: changed lots of landmarks to use their new icons + - imageadd: new landmark icons + TrollerNoob: + - rscadd: New shipments of Chestrigs and Technician Welder Chestrigs have arrived + from Chinook station. Urban and Snow operations are now covered. + - imageadd: Added Urban/Snow variants for Chestrig and Welder Chestrig + - code_imp: Removed some unneeded code from Chestrig/Welder Chestrig for automatic + map variants. + hry-gh: + - rscadd: added a poll button to the lobby screen. engage democratically. + lanse12: + - spellcheck: fixed some turret related typos + larentoun: + - qol: Added a visual timer for cooldowns of Xeno Abilities + private-tristan: + - admin: Create-Xenos range defaults to 0, instead of 1, hooray! + remindme: + - bugfix: Some MREs that should've had veggies in them had no veggies (ripoff!) +2025-03-06: + TheKillfish: + - rscadd: Added the Light Replacer to tool storage and to the autolathe. The Light + Replacer can now also recycle broken bulbs, either directly when replacing or + when inserted by hand. +2025-03-07: + Mister-moon1: + - balance: 'reverted all direct shrapnel buffs from #7581' + Red-byte3D: + - code_imp: Moves all of crusher abilities to its own files, strains to theirs and + removes some one letter vars +2025-03-09: + Git-Nivrak: + - balance: Increased facehugger leap windup time from 1 second to 2 seconds (crawling + hug time unchanged) + - balance: Increased facehugger health loss off weeds from 1 to 2 + - balance: Facehuggers now lose health off weeds regardless of if resting or not + (exception is watcher in watch mode) + Kashargul: + - bugfix: numberInput works properly again with mouse movement + - bugfix: the chat will no longer init before settings are loaded + - qol: chat settings can be in / exported + - qol: chat tabs can be reordered + hry-gh: + - admin: the audio player works on 516 again - go crazy + - bugfix: tgui windows closing should no longer cause sticky keys +2025-03-11: + BOBAMAx: + - bugfix: fixes not being able to build folding plasteel barricades on the same + tile as other barricades + BasilHerb: + - rscadd: SG drums now have the 'slap mag' property, allowing you to transfer bullets + between SG drums directly, instead of reloading them one handful at a time. + Drathek: + - refactor: Refactored urange into long_range and long_orange + Mikrel0712, Molto, Kugamo: + - rscadd: Replaced xenomorph devour with hauling. + Red-byte3D: + - balance: Xenomorphs now open doors faster depending on their tier / mob size + Sporticusmge: + - balance: IO's can buy combat toolbelt for 15 points in IO vendor + Venuska1117: + - qol: Resin Doors now Ignore living mobs with DEAD state. + Willzadl: + - bugfix: barricades now display the correct open/close message + - qol: halved the untrained pointing cooldown + detectivegoogle: + - bugfix: the backworn sprites for spears and breaching hammers are fixed + efzapa: + - mapadd: Whiskey Outpost CIC has been widened, and a USCM Fax Machine added. + - mapadd: Whiskey Outpost Synth Prep has been remapped to be more streamlined. + ihatethisengine: + - bugfix: fixes revive grace period never resetting + jupyterkat: + - qol: Handfulls of ammos inserted into storages will now try to fill other handfulls + of ammos + realforest2001: + - bugfix: Fixes some paperwork inaccuracies and formatting errors. + - rscadd: Subtypes NDA forms for Wey-Yu to differentiate between USCM and other + parties. + - maptweak: Re-re-re-re-added the fax responder listening bugs to the USS Almayer. diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 9842357a5c0e..9cbf2a9fb69d 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/mob/hud/actions_xeno.dmi b/icons/mob/hud/actions_xeno.dmi index b98194a9cab1..b9605f49fbef 100644 Binary files a/icons/mob/hud/actions_xeno.dmi and b/icons/mob/hud/actions_xeno.dmi differ diff --git a/icons/obj/items/devices.dmi b/icons/obj/items/devices.dmi index aa24a4859de1..63f05f945c58 100644 Binary files a/icons/obj/items/devices.dmi and b/icons/obj/items/devices.dmi differ diff --git a/icons/ooc.dmi b/icons/ooc.dmi deleted file mode 100644 index 861cb7e852d3..000000000000 Binary files a/icons/ooc.dmi and /dev/null differ diff --git a/maps/map_files/USS_Almayer/USS_Almayer.dmm b/maps/map_files/USS_Almayer/USS_Almayer.dmm index 24a6c6f0a565..c1613929b0a0 100644 --- a/maps/map_files/USS_Almayer/USS_Almayer.dmm +++ b/maps/map_files/USS_Almayer/USS_Almayer.dmm @@ -189,10 +189,34 @@ }, /turf/open/floor/almayer/red/north, /area/almayer/shipboard/brig/processing) +"aaB" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/structure/pipes/standard/simple/hidden/supply{ + dir = 4 + }, +/obj/item/device/radio/listening_bug/radio_linked/fax/uscm_pvst{ + nametag = "Brig 2" + }, +/turf/open/floor/plating/plating_catwalk, +/area/almayer/shipboard/brig/processing) "aaC" = ( /obj/structure/lattice, /turf/open/space/basic, /area/space) +"aaD" = ( +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/structure/pipes/standard/simple/hidden/supply{ + dir = 4 + }, +/obj/item/device/radio/listening_bug/radio_linked/fax/uscm_pvst{ + nametag = "Brig 1" + }, +/turf/open/floor/plating/plating_catwalk, +/area/almayer/shipboard/brig/processing) "aaE" = ( /obj/structure/sign/safety/storage{ pixel_x = 8; @@ -361,6 +385,12 @@ }, /turf/open/floor/plating/plating_catwalk, /area/almayer/living/starboard_emb) +"aaT" = ( +/obj/item/device/radio/listening_bug/radio_linked/fax/uscm_pvst{ + nametag = "Brig Perma" + }, +/turf/closed/wall/almayer/reinforced, +/area/almayer/shipboard/brig/perma) "aaU" = ( /obj/structure/machinery/shower{ pixel_y = 16 @@ -375,6 +405,12 @@ }, /turf/open/floor/plating/plating_catwalk, /area/almayer/living/starboard_emb) +"aaV" = ( +/obj/item/device/radio/listening_bug/radio_linked/fax/uscm_pvst{ + nametag = "Interrogation" + }, +/turf/open/floor/plating/plating_catwalk, +/area/almayer/shipboard/brig/interrogation) "aaW" = ( /obj/effect/step_trigger/clone_cleaner, /turf/open/floor/plating/almayer/no_build, @@ -27094,6 +27130,9 @@ /turf/open/floor/almayer/aicore/glowing/no_build, /area/almayer/command/airoom) "euV" = ( +/obj/item/device/radio/listening_bug/radio_linked/fax/uscm_hc{ + nametag = "CIC" + }, /turf/open/floor/almayer/uscm/directional/logo_c/west, /area/almayer/command/cic) "euW" = ( @@ -42121,6 +42160,9 @@ pixel_x = 16; pixel_y = 8 }, +/obj/item/device/radio/listening_bug/radio_linked/fax/uscm_hc{ + nametag = "CO Office" + }, /turf/open/floor/carpet, /area/almayer/living/commandbunks) "kWT" = ( @@ -58882,6 +58924,10 @@ /obj/structure/sign/prop1{ layer = 3.1 }, +/obj/item/device/radio/listening_bug/radio_linked/fax/uscm_hc{ + nametag = "CIC Conf"; + layer = 4 + }, /obj/structure/surface/table/reinforced/black, /turf/open/floor/carpet, /area/almayer/command/cicconference) @@ -82319,7 +82365,7 @@ naB naB naB naB -naB +aaT dcy qPD qPD @@ -83122,7 +83168,7 @@ xkc xyZ hvd vGn -ehl +aaV ehl uXu cQG @@ -83746,7 +83792,7 @@ oRk oRk oRk utZ -cMl +aaB hTt kMH tUx @@ -85979,7 +86025,7 @@ ptq oRk eNR bju -cMl +aaD dEG xkd uvY diff --git a/maps/map_files/Whiskey_Outpost_v2/Whiskey_Outpost_v2.dmm b/maps/map_files/Whiskey_Outpost_v2/Whiskey_Outpost_v2.dmm index 4d087a1d430c..ae723ed138ed 100644 --- a/maps/map_files/Whiskey_Outpost_v2/Whiskey_Outpost_v2.dmm +++ b/maps/map_files/Whiskey_Outpost_v2/Whiskey_Outpost_v2.dmm @@ -25,6 +25,10 @@ }, /turf/open/gm/dirt, /area/whiskey_outpost/inside/caves/tunnel) +"af" = ( +/obj/structure/machinery/cm_vending/own_points/experimental_tools, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) "ag" = ( /obj/structure/machinery/door/poddoor{ desc = "A podlock that leads to the main Marine base on LV-624. It requires power from the outpost to stay shut. Better not let anything get through this..."; @@ -45,12 +49,31 @@ }, /turf/open/gm/dirt, /area/whiskey_outpost/outside/lane/two_south) +"ai" = ( +/obj/structure/machinery/light{ + dir = 2 + }, +/obj/structure/platform/metal/almayer{ + dir = 1 + }, +/obj/structure/platform/metal/almayer{ + dir = 4 + }, +/obj/structure/platform_decoration/metal/almayer/northwest{ + layer = 3.01 + }, +/turf/open/floor/plating/plating_catwalk, +/area/whiskey_outpost/inside/cic) "aj" = ( /turf/closed/wall/r_wall, /area/whiskey_outpost/inside/bunker) "ak" = ( /turf/closed/wall/r_wall, /area/whiskey_outpost/inside/cic) +"al" = ( +/obj/structure/machinery/recharge_station, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) "am" = ( /obj/structure/window/reinforced{ dir = 8; @@ -93,6 +116,10 @@ }, /turf/open/floor/whitegreenfull, /area/whiskey_outpost/inside/hospital) +"ar" = ( +/obj/structure/machinery/cm_vending/clothing/synth, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) "as" = ( /obj/effect/decal/warning_stripes/asteroid{ icon_state = "warning_s" @@ -122,6 +149,10 @@ /obj/structure/machinery/light/small, /turf/open/floor/whitegreen, /area/whiskey_outpost/inside/hospital/triage) +"aw" = ( +/obj/effect/landmark/start/whiskey/synthetic, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) "ax" = ( /obj/structure/surface/table/woodentable/fancy, /obj/item/storage/fancy/cigar, @@ -142,9 +173,19 @@ }, /turf/open/floor/wood, /area/whiskey_outpost/inside/cic) +"aB" = ( +/obj/effect/decal/cleanable/blood/oil, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) "aC" = ( /turf/open/floor/almayer/cargo, /area/whiskey_outpost/inside/cic) +"aD" = ( +/obj/structure/closet/fireaxecabinet{ + pixel_y = -32 + }, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) "aE" = ( /obj/structure/bed, /obj/item/bedsheet/hop, @@ -191,6 +232,13 @@ "aK" = ( /turf/open/gm/dirtgrassborder/west, /area/whiskey_outpost/outside/lane/two_south) +"aL" = ( +/obj/structure/machinery/door/airlock/multi_tile/almayer/generic/solid{ + dir = 1; + name = "\improper Synth Prep" + }, +/turf/open/space/basic, +/area/whiskey_outpost/inside/cic) "aM" = ( /obj/effect/decal/cleanable/blood/writing{ dir = 10 @@ -228,10 +276,24 @@ }, /turf/open/floor/prison/floor_plate, /area/whiskey_outpost/inside/cic) +"aR" = ( +/obj/effect/decal/cleanable/blood/oil, +/obj/effect/landmark/start/whiskey/synthetic, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) "aS" = ( /obj/structure/machinery/optable, /turf/open/floor/whitegreenfull, /area/whiskey_outpost/inside/hospital) +"aT" = ( +/obj/structure/closet/secure_closet/surgical{ + pixel_y = 30 + }, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) +"aU" = ( +/turf/open/space/basic, +/area/whiskey_outpost/inside/cic) "aV" = ( /turf/open/floor/whitegreen/southwest, /area/whiskey_outpost/inside/hospital) @@ -241,6 +303,23 @@ }, /turf/open/floor/prison/kitchen/southwest, /area/whiskey_outpost/inside/living) +"aX" = ( +/obj/structure/machinery/light{ + dir = 1 + }, +/obj/structure/platform/metal/almayer, +/obj/structure/platform/metal/almayer{ + dir = 4 + }, +/obj/structure/platform_decoration/metal/almayer/southeast{ + layer = 3.01 + }, +/turf/open/floor/plating/plating_catwalk, +/area/whiskey_outpost/inside/cic) +"aY" = ( +/obj/structure/machinery/cm_vending/gear/synth, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) "aZ" = ( /obj/structure/cargo_container/watatsumi/rightmid, /turf/open/jungle/impenetrable, @@ -284,6 +363,18 @@ "bf" = ( /turf/open/floor/whitegreen/north, /area/whiskey_outpost/inside/hospital) +"bg" = ( +/obj/structure/machinery/cm_vending/sorted/medical/wall_med{ + name = "Emergency NanoMed"; + pixel_x = -30; + req_access = null + }, +/obj/structure/machinery/light/small{ + dir = 8 + }, +/obj/structure/flora/pottedplant/random, +/turf/open/floor/prison/floor_plate, +/area/whiskey_outpost/inside/cic) "bh" = ( /obj/structure/sign/poster, /turf/closed/wall, @@ -327,6 +418,20 @@ }, /turf/open/floor/almayer/emeraldfull, /area/whiskey_outpost/inside/living) +"bq" = ( +/obj/structure/surface/table/woodentable/fancy, +/obj/structure/machinery/faxmachine/uscm, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) +"br" = ( +/obj/structure/machinery/light/small{ + dir = 8 + }, +/obj/structure/surface/table/woodentable/fancy, +/obj/item/paper_bin, +/obj/item/tool/pen, +/turf/open/floor/prison/floor_plate, +/area/whiskey_outpost/inside/cic) "bs" = ( /turf/open/floor/carpet, /area/whiskey_outpost/inside/cic) @@ -342,6 +447,13 @@ /obj/effect/landmark/start/whiskey/marine, /turf/open/floor/prison/floor_marked/southwest, /area/whiskey_outpost/inside/bunker/bunker/front) +"bv" = ( +/obj/structure/closet/secure_closet/cmdcabinet{ + pixel_y = 24 + }, +/obj/item/device/cotablet, +/turf/open/floor/prison, +/area/whiskey_outpost/inside/cic) "bw" = ( /obj/structure/machinery/cryopod, /turf/open/floor/whitegreenfull, @@ -350,6 +462,13 @@ /obj/item/ammo_box/magazine/misc/mre, /turf/open/floor/prison/floor_marked/southwest, /area/whiskey_outpost/inside/bunker/bunker/front) +"by" = ( +/obj/structure/machinery/light/small{ + dir = 4 + }, +/obj/structure/machinery/power/apc/almayer/north, +/turf/open/floor/prison/floor_plate, +/area/whiskey_outpost/inside/cic) "bz" = ( /obj/structure/machinery/disposal, /obj/structure/disposalpipe/trunk{ @@ -379,6 +498,9 @@ /obj/structure/disposalpipe/segment, /turf/open/gm/dirtgrassborder/north, /area/whiskey_outpost/outside/lane/three_south) +"bE" = ( +/turf/open/floor/prison/kitchen, +/area/whiskey_outpost/inside/cic) "bF" = ( /obj/structure/disposalpipe/segment, /turf/open/jungle, @@ -386,6 +508,15 @@ "bG" = ( /turf/open/floor/prison/kitchen/southwest, /area/whiskey_outpost/inside/living) +"bH" = ( +/obj/structure/mirror{ + pixel_y = 30 + }, +/obj/structure/toilet{ + dir = 8 + }, +/turf/open/floor/prison/kitchen, +/area/whiskey_outpost/inside/cic) "bK" = ( /obj/structure/cargo_container/watatsumi/leftmid, /turf/open/jungle, @@ -612,12 +743,6 @@ }, /turf/open/floor/prison/kitchen, /area/whiskey_outpost/inside/cic) -"cF" = ( -/obj/structure/mirror{ - pixel_y = 30 - }, -/turf/open/floor/prison/kitchen, -/area/whiskey_outpost/inside/cic) "cG" = ( /obj/structure/machinery/door/airlock/almayer/generic{ name = "\improper Bathroom" @@ -807,12 +932,6 @@ /obj/structure/sign/poster, /turf/closed/wall/r_wall, /area/whiskey_outpost/inside/bunker/bunker/front) -"dz" = ( -/obj/structure/toilet{ - dir = 8 - }, -/turf/open/floor/prison/kitchen, -/area/whiskey_outpost/inside/cic) "dA" = ( /obj/structure/surface/table, /obj/item/facepaint/brown, @@ -1037,10 +1156,6 @@ }, /turf/open/jungle, /area/whiskey_outpost/outside/south/far) -"et" = ( -/obj/structure/filingcabinet, -/turf/open/floor/prison, -/area/whiskey_outpost/inside/cic) "eu" = ( /obj/structure/disposalpipe/sortjunction/flipped{ sortType = "Cafeteria" @@ -1076,10 +1191,6 @@ /obj/structure/platform/metal/almayer, /turf/open/gm/coast/west, /area/whiskey_outpost/outside/river/east) -"eF" = ( -/obj/structure/machinery/power/apc/almayer/north, -/turf/open/floor/prison, -/area/whiskey_outpost/inside/cic) "eG" = ( /turf/open/gm/dirtgrassborder/grassdirt_corner2/south_east, /area/whiskey_outpost/outside/lane/two_north) @@ -1904,12 +2015,6 @@ }, /turf/open/floor/whitegreen, /area/whiskey_outpost/inside/hospital) -"ih" = ( -/obj/structure/machinery/light{ - dir = 8 - }, -/turf/open/floor/prison, -/area/whiskey_outpost/inside/cic) "ii" = ( /obj/structure/machinery/door/airlock/almayer/medical/glass{ name = "\improper Medical Storage"; @@ -2800,18 +2905,6 @@ icon_state = "rasputin2" }, /area/whiskey_outpost/outside/lane/four_north) -"lW" = ( -/obj/structure/flora/pottedplant/random, -/obj/structure/machinery/cm_vending/sorted/medical/wall_med{ - name = "Emergency NanoMed"; - pixel_x = -30; - req_access = null - }, -/obj/structure/machinery/light/small{ - dir = 8 - }, -/turf/open/floor/prison/cell_stripe, -/area/whiskey_outpost/inside/cic) "lX" = ( /obj/effect/decal/warning_stripes/asteroid{ dir = 1; @@ -2900,7 +2993,7 @@ /obj/structure/machinery/light/small{ dir = 4 }, -/turf/open/floor/prison/cell_stripe, +/turf/open/floor/prison/floor_plate, /area/whiskey_outpost/inside/cic) "my" = ( /turf/open/gm/dirtgrassborder/grassdirt_corner2/south_west, @@ -2989,11 +3082,6 @@ }, /turf/open/floor/prison/floor_plate, /area/whiskey_outpost/inside/cic) -"mN" = ( -/obj/structure/window/framed/colony/reinforced, -/obj/structure/disposalpipe/segment, -/turf/open/floor/prison/floor_plate, -/area/whiskey_outpost/inside/cic) "mQ" = ( /obj/structure/disposalpipe/segment, /obj/structure/window/framed/colony/reinforced, @@ -3702,13 +3790,6 @@ /obj/item/tool/hand_labeler, /turf/open/floor/plating/plating_catwalk/prison, /area/whiskey_outpost/inside/engineering) -"qe" = ( -/obj/structure/closet/secure_closet/surgical{ - pixel_x = -30 - }, -/obj/effect/landmark/start/whiskey/synthetic, -/turf/open/floor/almayer/cargo, -/area/whiskey_outpost/inside/cic) "qf" = ( /obj/structure/disposalpipe/segment, /turf/open/floor/prison/cell_stripe/north, @@ -3956,16 +4037,6 @@ /obj/structure/sign/prop1, /turf/closed/wall/r_wall, /area/whiskey_outpost/inside/supply) -"rc" = ( -/obj/structure/machinery/light/small{ - dir = 1 - }, -/obj/structure/closet/secure_closet/cmdcabinet{ - pixel_y = 24 - }, -/obj/item/device/cotablet, -/turf/open/floor/prison, -/area/whiskey_outpost/inside/cic) "re" = ( /obj/structure/machinery/power/terminal{ dir = 1 @@ -4809,14 +4880,6 @@ }, /turf/open/gm/dirtgrassborder/grassdirt_corner2/south_east, /area/whiskey_outpost/outside/north) -"uI" = ( -/obj/structure/machinery/door/airlock/almayer/maint{ - name = "\improper Outpost Synthetic Equipment Station"; - req_access = null; - req_one_access = null - }, -/turf/open/floor/prison, -/area/whiskey_outpost/inside/cic) "uJ" = ( /turf/open/gm/dirt, /area/whiskey_outpost/inside/caves/caverns/west) @@ -6771,13 +6834,6 @@ "Ec" = ( /turf/open/floor/plating/warnplate, /area/whiskey_outpost/outside/north/northeast) -"Ed" = ( -/obj/structure/bed/chair{ - dir = 1; - pixel_y = 3 - }, -/turf/open/floor/prison, -/area/whiskey_outpost/inside/cic) "Ee" = ( /turf/open/gm/coast/east, /area/whiskey_outpost/outside/river/east) @@ -6909,10 +6965,6 @@ "EK" = ( /turf/open/jungle/impenetrable, /area/whiskey_outpost/outside/north/northwest) -"EL" = ( -/obj/structure/machinery/cm_vending/gear/synth, -/turf/open/floor/plating/plating_catwalk, -/area/whiskey_outpost/inside/cic) "EN" = ( /obj/structure/largecrate/random/case/double, /turf/open/floor/asteroidfloor/north, @@ -7196,13 +7248,6 @@ }, /turf/open/floor/prison/floor_plate, /area/whiskey_outpost/inside/bunker/bunker/front) -"Gx" = ( -/obj/structure/machinery/light{ - dir = 4 - }, -/obj/effect/decal/cleanable/blood/oil, -/turf/open/floor/prison, -/area/whiskey_outpost/inside/cic) "Gy" = ( /obj/effect/landmark/start/whiskey/leader, /turf/open/floor/almayer/bluefull, @@ -8266,10 +8311,6 @@ icon_state = "diagrasputin" }, /area/whiskey_outpost/outside/lane/four_south) -"Mo" = ( -/obj/structure/machinery/recharge_station, -/turf/open/floor/almayer/cargo, -/area/whiskey_outpost/inside/cic) "Mp" = ( /obj/structure/machinery/m56d_hmg/mg_turret{ dir = 8; @@ -8429,12 +8470,6 @@ /obj/structure/barricade/sandbags/wired, /turf/open/floor/plating/warnplate, /area/whiskey_outpost/outside/north/beach) -"Nr" = ( -/obj/structure/closet/fireaxecabinet{ - pixel_x = -32 - }, -/turf/open/floor/plating/plating_catwalk, -/area/whiskey_outpost/inside/cic) "Nt" = ( /obj/structure/barricade/sandbags/wired, /obj/structure/barricade/sandbags/wired{ @@ -8823,10 +8858,6 @@ }, /turf/open/gm/dirt, /area/whiskey_outpost/outside/lane/three_north) -"PB" = ( -/obj/effect/landmark/start/whiskey/synthetic, -/turf/open/floor/almayer/cargo, -/area/whiskey_outpost/inside/cic) "PC" = ( /obj/structure/flora/bush/ausbushes/var3/leafybush, /turf/open/gm/dirt, @@ -8853,9 +8884,6 @@ "PL" = ( /turf/open/gm/grass/grassbeach/north, /area/whiskey_outpost/outside/lane/one_north) -"PM" = ( -/turf/open/floor/plating/plating_catwalk, -/area/whiskey_outpost/inside/cic) "PO" = ( /turf/open/gm/river, /area/whiskey_outpost/outside/lane/four_north) @@ -9869,11 +9897,6 @@ }, /turf/open/floor/white, /area/whiskey_outpost/inside/hospital/triage) -"Vy" = ( -/obj/structure/machinery/cm_vending/clothing/synth, -/obj/effect/decal/cleanable/cobweb2, -/turf/open/floor/prison, -/area/whiskey_outpost/inside/cic) "Vz" = ( /turf/open/gm/dirtgrassborder/east, /area/whiskey_outpost/outside/south/far) @@ -10157,10 +10180,6 @@ /obj/structure/disposalpipe/segment, /turf/open/floor/asteroidwarning/east, /area/whiskey_outpost/outside/north) -"WK" = ( -/obj/structure/machinery/cm_vending/own_points/experimental_tools, -/turf/open/floor/plating/plating_catwalk, -/area/whiskey_outpost/inside/cic) "WL" = ( /obj/structure/flora/bush/ausbushes/var3/leafybush, /obj/structure/disposalpipe/segment{ @@ -15297,10 +15316,10 @@ mT mT mT mT -mT -mT -mT -mT +ak +ak +ak +ak mT mT mT @@ -15498,12 +15517,12 @@ mT mT mT mT -mT -mT -mT -mT -mT -mT +ak +ak +ar +ar +ak +ak mT mT mT @@ -15699,14 +15718,14 @@ mT mT mT mT -mT -mT -mT -mT -mT -mT -mT -mT +ak +ak +aX +aC +aC +ai +ak +ak mT mT mT @@ -15901,14 +15920,14 @@ mT mT mT mT -mT -mT -mT -mT -mT -mT -mT -mT +ak +aY +aC +aR +fx +aC +af +ak mT mT mT @@ -16103,14 +16122,14 @@ mT mT mT mT -mT -mT -mT -mT -mT -mT -mT -mT +ak +aY +aC +fx +aw +aC +af +ak mT mT mT @@ -16305,14 +16324,14 @@ mT mT mT mT -mT -mT -mT -mT -mT -mT -mT -mT +ak +ak +al +fx +aB +al +ak +ak mT mT mT @@ -16508,12 +16527,12 @@ mT mT mT mT -mT -mT -mT -mT -mT -mT +ak +ak +aT +aD +ak +ak mT mT mT @@ -16709,12 +16728,12 @@ mT mT mT mT -ak -ak -ak -mT mT mT +ak +aU +aL +ak mT mT mT @@ -16899,6 +16918,10 @@ mT mT mT mT +ak +ak +ak +ak mT mT mT @@ -16909,13 +16932,9 @@ mT mT mT mT -mT -mT -ak -WK -ak -ak ak +mh +mh nK nK nK @@ -17101,23 +17120,23 @@ mT mT mT mT +ak +cE +ds +ak +mT mT ak ak ak ak -mT -mT -mT -mT -mT -mT ak -fx -aC -qe -ih -Nr +ak +ak +ak +ak +mh +nI nK uM rq @@ -17303,23 +17322,23 @@ mT mT mT mT -mT ak -cE -ds +bH +bE ak -mT -mT -mT -mT -mT -mT ak -Vy -aC -PB -PM -Ed +ak +ak +ak +hj +iQ +ak +jY +li +ak +ak +mh +mh nK pk sP @@ -17505,23 +17524,23 @@ mT mT mT mT -mT -ak -cF -dz -ak -mT -ak -ak ak ak +cG ak +br +bq +gp +gT +kK +ee +iT +jN +Ez ak ak -EL -Mo -Gx -PM +mh +mh nK qu re @@ -17708,22 +17727,22 @@ mT ak ak ak +bR +cz ak -cG -ak -ak -ak -ak -ak -hj -iQ -ak -jY -li -ak -ak +fx +fx +ea +fx +lm +lm +lm +lm +lm +bg ak -uI +mh +mh nK qx ri @@ -17910,21 +17929,21 @@ mT ak au cz -bR +cz cz dF -ea -ft -gp -gT -kK -ee -iT -jN -Ez -ak -ak -mh +fx +fx +lm +fx +lm +lm +lm +lm +lm +mi +mL +nv mh nK qu @@ -18115,17 +18134,17 @@ bs bs cI ak -eb +bv +fA +gA fx -ea +hk +iS +jo +iS fx -lm -lm -lm -lm -lm -lW -mL +mi +mM nv nI Kr @@ -18317,17 +18336,17 @@ bs bs cL ak -et -fA -gA -fx -hk -iS -jo -iS -fx +eb +fB +gC +gV +hu +iT +jp +kk +lr mi -mM +mL nv mh nK @@ -18520,16 +18539,16 @@ bT cM ex fx -fB -gC -gV -hu -iT -jp -kk -lr +fF +gE +fx +hw +iS +jq +iS +fx mo -mN +mM nw nL nK @@ -18721,17 +18740,17 @@ bs bs cp ak -rc -fF -gE fx -hw -iS -jq -iS fx +lm +fx +lm +lm +lm +lm +lm mi -mM +mL nv mZ nK @@ -18918,12 +18937,12 @@ mT mT mT ak -aN +cq bs bs -cq -ak -eF +cz +dF +fx fx eM fx @@ -18933,8 +18952,8 @@ lm lm lm mx -mL -nv +ak +mh nM nK oy @@ -19124,8 +19143,8 @@ aN cz bV cz -dF -eM +ak +by ft gp gW @@ -19136,7 +19155,7 @@ EZ Vq mz ak -nv +mh nM nK oC diff --git a/strings/memetips.txt b/strings/memetips.txt index 1767d8688953..487236970206 100644 --- a/strings/memetips.txt +++ b/strings/memetips.txt @@ -4,7 +4,7 @@ One shotgun isn't enough. Take six. Don't do the conga, the queen mother knows all. Jones is very important. Protect him at all costs. Install VSC. -Devouring is useful to quickly transport incapacitated hosts from one place to another. In order to devour a host as a Xeno, grab the mob (CTRL + Click) and then click on yourself to begin devouring. The host can break out of your belly, which will result in your death - so make sure your target is incapacitated! After approximately 1 minute the host will be automatically regurgitated. To release your target voluntarily, click 'Regurgitate' on the HUD to throw them back up. +Hauling is useful to quickly transport incapacitated hosts from one place to another. In order to haul a host as a Xeno, grab the mob (CTRL + Click) and then click on yourself to begin restraining them. The host can break out of your grip, which will result in your death - so make sure your target is incapacitated! After approximately 1 minute the host will be automatically be released. To release your target voluntarily, click 'Release' on the HUD to drop them on the ground. As an MP, remember that staff may give you antagonist objectives to help weaken the marines! There's actually a hidden box of HEFA grenades in Requisitions. It can be reached by having one of your buddies throw you down the ASRS elevator. Don't waste crayons by jamming them up your nose - they make excellent emergency rations in a pinch! diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml index 4ff25c1f7fe4..8a6e605e48ea 100644 --- a/tgui/.eslintrc.yml +++ b/tgui/.eslintrc.yml @@ -338,7 +338,7 @@ rules: ## Require or disallow named function expressions # func-names: error ## Enforce the consistent use of either function declarations or expressions - func-style: [error, expression] + # func-style: [error, expression] ## Enforce line breaks between arguments of a function call # function-call-argument-newline: error ## Enforce consistent line breaks inside function parentheses diff --git a/tgui/package.json b/tgui/package.json index 624b43ad7cc5..21139aebc861 100644 --- a/tgui/package.json +++ b/tgui/package.json @@ -27,6 +27,7 @@ "@types/jest": "^29.5.12", "@types/node": "^20.12.3", "@types/webpack-env": "^1.18.4", + "@types/wicg-file-system-access": "^2023.10.5", "@typescript-eslint/parser": "^7.5.0", "@typescript-eslint/utils": "^7.5.0", "css-loader": "^6.10.0", diff --git a/tgui/packages/common/keys.ts b/tgui/packages/common/keys.ts index 34ac9e1614dd..812bbedc6832 100644 --- a/tgui/packages/common/keys.ts +++ b/tgui/packages/common/keys.ts @@ -25,6 +25,7 @@ export enum KEY { Down = 'ArrowDown', End = 'End', Enter = 'Enter', + Esc = 'Esc', Escape = 'Escape', Home = 'Home', Insert = 'Insert', @@ -37,3 +38,18 @@ export enum KEY { Tab = 'Tab', Up = 'ArrowUp', } + +/** + * ### isEscape + * + * Checks if the user has hit the 'ESC' key on their keyboard. + * There's a weirdness in BYOND where this could be either the string + * 'Escape' or 'Esc' depending on the browser. This function handles + * both cases. + * + * @param key - the key to check, typically from event.key + * @returns true if key is Escape or Esc, false otherwise + */ +export function isEscape(key: string): boolean { + return key === KEY.Esc || key === KEY.Escape; +} diff --git a/tgui/packages/common/perf.ts b/tgui/packages/common/perf.ts index 3e8ac2ab050c..9266d88a896a 100644 --- a/tgui/packages/common/perf.ts +++ b/tgui/packages/common/perf.ts @@ -1,4 +1,3 @@ -/* eslint-disable func-style */ /** * Ghetto performance measurement tools. * diff --git a/tgui/packages/common/storage.js b/tgui/packages/common/storage.js index 5b5a3579a8b0..082e41ecf5ee 100644 --- a/tgui/packages/common/storage.js +++ b/tgui/packages/common/storage.js @@ -107,7 +107,7 @@ class IFrameIndexedDbBackend { async get(key) { const promise = new Promise((resolve) => { window.addEventListener('message', (message) => { - if (message.data.key === key) { + if (message.data.key && message.data.key === key) { resolve(message.data.value); } }); @@ -128,6 +128,26 @@ class IFrameIndexedDbBackend { async clear() { this.iframeWindow.postMessage({ type: 'clear' }, '*'); } + + async processChatMessages(messages) { + this.iframeWindow.postMessage( + { type: 'processChatMessages', messages: messages }, + '*', + ); + } + + async getChatMessages() { + const promise = new Promise((resolve) => { + window.addEventListener('message', (message) => { + if (message.data.messages) { + resolve(message.data.messages); + } + }); + }); + + this.iframeWindow.postMessage({ type: 'getChatMessages' }, '*'); + return promise; + } } class IndexedDbBackend { diff --git a/tgui/packages/common/string.ts b/tgui/packages/common/string.ts index 4c93d155e662..d6f328750c42 100644 --- a/tgui/packages/common/string.ts +++ b/tgui/packages/common/string.ts @@ -1,4 +1,3 @@ -/* eslint-disable func-style */ /** * @file * @copyright 2020 Aleksej Komarov diff --git a/tgui/packages/common/type-utils.ts b/tgui/packages/common/type-utils.ts index e6d3fa5cbd4a..a73c0c1d5956 100644 --- a/tgui/packages/common/type-utils.ts +++ b/tgui/packages/common/type-utils.ts @@ -1,4 +1,3 @@ -/* eslint-disable func-style */ /** * Helps visualize highly complex ui data on the fly. * @example diff --git a/tgui/packages/common/uuid.ts b/tgui/packages/common/uuid.ts index 02dab15dd43b..250809ab6a7d 100644 --- a/tgui/packages/common/uuid.ts +++ b/tgui/packages/common/uuid.ts @@ -1,4 +1,3 @@ -/* eslint-disable func-style */ /** * @file * @copyright 2020 Aleksej Komarov diff --git a/tgui/packages/tgui-panel/audio/player.js b/tgui/packages/tgui-panel/audio/player.js deleted file mode 100644 index 50eccee16ba0..000000000000 --- a/tgui/packages/tgui-panel/audio/player.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { createLogger } from 'tgui/logging'; - -const logger = createLogger('AudioPlayer'); - -export class AudioPlayer { - constructor() { - // Set up the HTMLAudioElement node - this.node = document.createElement('audio'); - this.node.style.setProperty('display', 'none'); - document.body.appendChild(this.node); - // Set up other properties - this.playing = false; - this.volume = 1; - this.options = {}; - this.onPlaySubscribers = []; - this.onStopSubscribers = []; - // Listen for playback start events - this.node.addEventListener('canplaythrough', () => { - logger.log('canplaythrough'); - this.playing = true; - this.node.playbackRate = this.options.pitch || 1; - this.node.currentTime = this.options.start || 0; - this.node.volume = this.volume; - this.node.play(); - for (let subscriber of this.onPlaySubscribers) { - subscriber(); - } - }); - // Listen for playback stop events - this.node.addEventListener('ended', () => { - logger.log('ended'); - this.stop(); - }); - // Listen for playback errors - this.node.addEventListener('error', (e) => { - if (this.playing) { - logger.log('playback error', e.error); - this.stop(); - } - }); - // Check every second to stop the playback at the right time - this.playbackInterval = setInterval(() => { - if (!this.playing) { - return; - } - const shouldStop = - this.options.end > 0 && this.node.currentTime >= this.options.end; - if (shouldStop) { - this.stop(); - } - }, 1000); - } - - destroy() { - if (!this.node) { - return; - } - this.node.stop(); - document.removeChild(this.node); - clearInterval(this.playbackInterval); - } - - play(url, options = {}) { - if (!this.node) { - return; - } - logger.log('playing', url, options); - this.options = options; - this.node.src = url; - } - - stop() { - if (!this.node) { - return; - } - if (this.playing) { - for (let subscriber of this.onStopSubscribers) { - subscriber(); - } - } - logger.log('stopping'); - this.playing = false; - this.node.src = ''; - } - - setVolume(volume) { - if (!this.node) { - return; - } - this.volume = volume; - this.node.volume = volume; - } - - onPlay(subscriber) { - if (!this.node) { - return; - } - this.onPlaySubscribers.push(subscriber); - } - - onStop(subscriber) { - if (!this.node) { - return; - } - this.onStopSubscribers.push(subscriber); - } -} diff --git a/tgui/packages/tgui-panel/audio/player.ts b/tgui/packages/tgui-panel/audio/player.ts new file mode 100644 index 000000000000..5ea0716dfbc4 --- /dev/null +++ b/tgui/packages/tgui-panel/audio/player.ts @@ -0,0 +1,101 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { createLogger } from 'tgui/logging'; + +const logger = createLogger('AudioPlayer'); + +type AudioOptions = { + pitch?: number; + start?: number; + end?: number; +}; + +export class AudioPlayer { + element: HTMLAudioElement | null; + options: AudioOptions; + volume: number; + + onPlaySubscribers: { (): void }[]; + onStopSubscribers: { (): void }[]; + + constructor() { + this.element = null; + + this.onPlaySubscribers = []; + this.onStopSubscribers = []; + } + + destroy() { + this.element = null; + } + + play(url: string, options: AudioOptions = {}) { + if (this.element) { + this.stop(); + } + + this.options = options; + + const audio = (this.element = new Audio(url)); + audio.volume = this.volume; + audio.playbackRate = this.options.pitch || 1; + + logger.log('playing', url, options); + + audio.addEventListener('ended', () => { + logger.log('ended'); + this.stop(); + }); + + audio.addEventListener('error', (error) => { + logger.log('playback error', error); + }); + + if (this.options.end) { + audio.addEventListener('timeupdate', () => { + if ( + this.options.end && + this.options.end > 0 && + audio.currentTime >= this.options.end + ) { + this.stop(); + } + }); + } + + audio.play(); + + this.onPlaySubscribers.forEach((subscriber) => subscriber()); + } + + stop() { + if (!this.element) return; + + logger.log('stopping'); + + this.element.pause(); + this.element = null; + + this.onStopSubscribers.forEach((subscriber) => subscriber()); + } + + setVolume(volume: number): void { + this.volume = volume; + + if (!this.element) return; + + this.element.volume = volume; + } + + onPlay(subscriber: () => {}): void { + this.onPlaySubscribers.push(subscriber); + } + + onStop(subscriber: () => {}): void { + this.onStopSubscribers.push(subscriber); + } +} diff --git a/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx b/tgui/packages/tgui-panel/chat/ChatPageSettings.tsx similarity index 64% rename from tgui/packages/tgui-panel/chat/ChatPageSettings.jsx rename to tgui/packages/tgui-panel/chat/ChatPageSettings.tsx index 51c08c05a364..fb36ec150154 100644 --- a/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx +++ b/tgui/packages/tgui-panel/chat/ChatPageSettings.tsx @@ -14,7 +14,13 @@ import { Stack, } from 'tgui/components'; -import { removeChatPage, toggleAcceptedType, updateChatPage } from './actions'; +import { + moveChatPageLeft, + moveChatPageRight, + removeChatPage, + toggleAcceptedType, + updateChatPage, +} from './actions'; import { MESSAGE_TYPES } from './constants'; import { selectCurrentChatPage } from './selectors'; @@ -24,7 +30,23 @@ export const ChatPageSettings = (props) => { return (
- + {!page.isMain && ( + + - + {!page.isMain && ( + + + + )} -
+
{MESSAGE_TYPES.filter( (typeDef) => !typeDef.important && !typeDef.admin, ).map((typeDef) => ( diff --git a/tgui/packages/tgui-panel/chat/actions.js b/tgui/packages/tgui-panel/chat/actions.js index 52582a4e299c..0b6a73f659a7 100644 --- a/tgui/packages/tgui-panel/chat/actions.js +++ b/tgui/packages/tgui-panel/chat/actions.js @@ -21,3 +21,5 @@ export const toggleAcceptedType = createAction('chat/toggleAcceptedType'); export const removeChatPage = createAction('chat/removePage'); export const changeScrollTracking = createAction('chat/changeScrollTracking'); export const saveChatToDisk = createAction('chat/saveToDisk'); +export const moveChatPageLeft = createAction('chat/movePageLeft'); +export const moveChatPageRight = createAction('chat/movePageRight'); diff --git a/tgui/packages/tgui-panel/chat/middleware.js b/tgui/packages/tgui-panel/chat/middleware.js index 512b78532b1a..0e236004f39e 100644 --- a/tgui/packages/tgui-panel/chat/middleware.js +++ b/tgui/packages/tgui-panel/chat/middleware.js @@ -9,6 +9,7 @@ import DOMPurify from 'dompurify'; import { addHighlightSetting, + importSettings, loadSettings, removeHighlightSetting, updateHighlightSetting, @@ -21,6 +22,8 @@ import { changeScrollTracking, clearChat, loadChat, + moveChatPageLeft, + moveChatPageRight, rebuildChat, removeChatPage, saveChatToDisk, @@ -35,27 +38,43 @@ import { selectChat, selectCurrentChatPage } from './selectors'; // List of blacklisted tags const FORBID_TAGS = ['a', 'iframe', 'link', 'video']; -const storage = - Byond.storageCdn === 'tgui:storagecdn' ? realStorage : new StorageProxy(true); +const usingCdnStorage = + !Byond.TRIDENT && Byond.storageCdn !== 'tgui:storagecdn'; +const storage = usingCdnStorage ? new StorageProxy(true) : realStorage; const saveChatToStorage = async (store) => { const state = selectChat(store.getState()); - const fromIndex = Math.max( - 0, - chatRenderer.messages.length - MAX_PERSISTED_MESSAGES, - ); - const messages = chatRenderer.messages - .slice(fromIndex) - .map((message) => serializeMessage(message)); + + if (usingCdnStorage) { + const indexedDbBackend = await storage.backendPromise; + indexedDbBackend.processChatMessages(chatRenderer.storeQueue); + } else { + const fromIndex = Math.max( + 0, + chatRenderer.messages.length - MAX_PERSISTED_MESSAGES, + ); + + const messages = chatRenderer.messages + .slice(fromIndex) + .map((message) => serializeMessage(message)); + + storage.set('chat-messages-cm', messages); + } + + chatRenderer.storeQueue = []; storage.set('chat-state-cm', state); - storage.set('chat-messages-cm', messages); }; const loadChatFromStorage = async (store) => { - const [state, messages] = await Promise.all([ - storage.get('chat-state-cm'), - storage.get('chat-messages-cm'), - ]); + const state = await storage.get('chat-state-cm'); + + let messages; + if (usingCdnStorage) { + messages = await (await storage.backendPromise).getChatMessages(); + } else { + messages = await storage.get('chat-messages-cm'); + } + // Discard incompatible versions if (state && state.version <= 4) { store.dispatch(loadChat()); @@ -98,12 +117,14 @@ export const chatMiddleware = (store) => { chatRenderer.events.on('scrollTrackingChanged', (scrollTracking) => { store.dispatch(changeScrollTracking(scrollTracking)); }); - setInterval(() => { - saveChatToStorage(store); - }, MESSAGE_SAVE_INTERVAL); return (next) => (action) => { const { type, payload } = action; - if (!initialized) { + const settings = selectSettings(store.getState()); + // Load the chat once settings are loaded + if (!initialized && settings.initialized) { + setInterval(() => { + saveChatToStorage(store); + }, MESSAGE_SAVE_INTERVAL); initialized = true; loadChatFromStorage(store); } @@ -111,13 +132,15 @@ export const chatMiddleware = (store) => { let payload_obj; try { payload_obj = JSON.parse(payload); - } catch { + } catch (err) { return; } + const sequence = payload_obj.sequence; if (sequences.includes(sequence)) { return; } + const sequence_count = sequences.length; seq_check: if (sequence_count > 0) { if (sequences_requested.includes(sequence)) { @@ -156,7 +179,9 @@ export const chatMiddleware = (store) => { type === changeChatPage.type || type === addChatPage.type || type === removeChatPage.type || - type === toggleAcceptedType.type + type === toggleAcceptedType.type || + type === moveChatPageLeft.type || + type === moveChatPageRight.type ) { next(action); const page = selectCurrentChatPage(store.getState()); @@ -173,13 +198,14 @@ export const chatMiddleware = (store) => { type === loadSettings.type || type === addHighlightSetting.type || type === removeHighlightSetting.type || - type === updateHighlightSetting.type + type === updateHighlightSetting.type || + type === importSettings.type ) { next(action); - const settings = selectSettings(store.getState()); + const nextSettings = selectSettings(store.getState()); chatRenderer.setHighlight( - settings.highlightSettings, - settings.highlightSettingById, + nextSettings.highlightSettings, + nextSettings.highlightSettingById, ); return; diff --git a/tgui/packages/tgui-panel/chat/model.js b/tgui/packages/tgui-panel/chat/model.js index 6e10a3d02eef..ca2c585d2ec8 100644 --- a/tgui/packages/tgui-panel/chat/model.js +++ b/tgui/packages/tgui-panel/chat/model.js @@ -19,6 +19,7 @@ export const createPage = (obj) => { } return { + isMain: false, id: createUuid(), name: 'New Tab', acceptedTypes: acceptedTypes, @@ -35,6 +36,7 @@ export const createMainPage = () => { acceptedTypes[typeDef.type] = true; } return createPage({ + isMain: true, name: 'Main', acceptedTypes, }); diff --git a/tgui/packages/tgui-panel/chat/reducer.js b/tgui/packages/tgui-panel/chat/reducer.ts similarity index 72% rename from tgui/packages/tgui-panel/chat/reducer.js rename to tgui/packages/tgui-panel/chat/reducer.ts index 7a7681ada741..dda8f860dd2b 100644 --- a/tgui/packages/tgui-panel/chat/reducer.js +++ b/tgui/packages/tgui-panel/chat/reducer.ts @@ -4,17 +4,21 @@ * @license MIT */ +import { importSettings } from '../settings/actions'; import { addChatPage, changeChatPage, changeScrollTracking, loadChat, + moveChatPageLeft, + moveChatPageRight, removeChatPage, toggleAcceptedType, updateChatPage, updateMessageCount, } from './actions'; import { canPageAcceptType, createMainPage } from './model'; +import { Page } from './types'; const mainPage = createMainPage(); @@ -125,6 +129,24 @@ export const chatReducer = (state = initialState, action) => { }, }; } + if (type === importSettings.type) { + const pagesById: Record[] = payload.newPages; + if (!pagesById) { + return state; + } + const newPageIds: string[] = Object.keys(pagesById); + if (!newPageIds) { + return state; + } + + const nextState = { + ...state, + currentPageId: newPageIds[0], + pages: [...newPageIds], + pageById: { ...pagesById }, + }; + return nextState; + } if (type === changeChatPage.type) { const { pageId } = payload; const page = { @@ -188,5 +210,52 @@ export const chatReducer = (state = initialState, action) => { } return nextState; } + if (type === moveChatPageLeft.type) { + const { pageId } = payload; + const nextState = { + ...state, + pages: [...state.pages], + pageById: { + ...state.pageById, + }, + }; + const tmpPage = nextState.pageById[pageId]; + const fromIndex = nextState.pages.indexOf(tmpPage.id); + const toIndex = fromIndex - 1; + // don't ever move leftmost page + if (fromIndex > 0) { + // don't ever move anything to the leftmost page + if (toIndex > 0) { + const tmp = nextState.pages[fromIndex]; + nextState.pages[fromIndex] = nextState.pages[toIndex]; + nextState.pages[toIndex] = tmp; + } + } + return nextState; + } + + if (type === moveChatPageRight.type) { + const { pageId } = payload; + const nextState = { + ...state, + pages: [...state.pages], + pageById: { + ...state.pageById, + }, + }; + const tmpPage = nextState.pageById[pageId]; + const fromIndex = nextState.pages.indexOf(tmpPage.id); + const toIndex = fromIndex + 1; + // don't ever move leftmost page + if (fromIndex > 0) { + // don't ever move anything out of the array + if (toIndex < nextState.pages.length) { + const tmp = nextState.pages[fromIndex]; + nextState.pages[fromIndex] = nextState.pages[toIndex]; + nextState.pages[toIndex] = tmp; + } + } + return nextState; + } return state; }; diff --git a/tgui/packages/tgui-panel/chat/renderer.jsx b/tgui/packages/tgui-panel/chat/renderer.jsx index cda017adcbab..e5d7506abc00 100644 --- a/tgui/packages/tgui-panel/chat/renderer.jsx +++ b/tgui/packages/tgui-panel/chat/renderer.jsx @@ -122,6 +122,7 @@ class ChatRenderer { this.rootNode = null; this.queue = []; this.messages = []; + this.storeQueue = []; this.visibleMessages = []; this.page = null; this.events = new EventEmitter(); @@ -451,6 +452,7 @@ class ChatRenderer { } } } + this.storeQueue.push({ ...message }); // Store the node in the message message.node = node; // Query all possible selectors to find out the message type diff --git a/tgui/packages/tgui-panel/chat/types.ts b/tgui/packages/tgui-panel/chat/types.ts new file mode 100644 index 000000000000..91ab9fd3a7f0 --- /dev/null +++ b/tgui/packages/tgui-panel/chat/types.ts @@ -0,0 +1,9 @@ +export type Page = { + isMain: boolean; + id: string; + name: string; + acceptedTypes: Record; + unreadCount: number; + hideUnreadCount: boolean; + createdAt: number; +}; diff --git a/tgui/packages/tgui-panel/ping/PingIndicator.tsx b/tgui/packages/tgui-panel/ping/PingIndicator.tsx index cd5fb3a0a2a9..9c0e0f9a2430 100644 --- a/tgui/packages/tgui-panel/ping/PingIndicator.tsx +++ b/tgui/packages/tgui-panel/ping/PingIndicator.tsx @@ -7,7 +7,7 @@ import { Color } from 'common/color'; import { toFixed } from 'common/math'; import { useBackend, useSelector } from 'tgui/backend'; -import { Box, Button } from 'tgui/components'; +import { Box, Tooltip } from 'tgui/components'; import { selectPing } from './selectors'; @@ -21,19 +21,16 @@ export const PingIndicator = (props) => { ]).toString(); const roundtrip = ping.roundtrip ? toFixed(ping.roundtrip) : '--'; return ( - + +
act('ping_relays')} + > + + {roundtrip} +
+
); }; diff --git a/tgui/packages/tgui-panel/settings/SettingTabs/SettingsGeneral.tsx b/tgui/packages/tgui-panel/settings/SettingTabs/SettingsGeneral.tsx new file mode 100644 index 000000000000..209691ec29c4 --- /dev/null +++ b/tgui/packages/tgui-panel/settings/SettingTabs/SettingsGeneral.tsx @@ -0,0 +1,192 @@ +import { toFixed } from 'common/math'; +import { capitalize } from 'common/string'; +import { useState } from 'react'; +import { useDispatch, useSelector } from 'tgui/backend'; +import { + Button, + Collapsible, + Divider, + Input, + LabeledList, + Section, + Slider, + Stack, +} from 'tgui/components'; + +import { clearChat, saveChatToDisk } from '../../chat/actions'; +import { THEMES } from '../../themes'; +import { exportSettings, updateSettings } from '../actions'; +import { FONTS } from '../constants'; +import { selectSettings } from '../selectors'; +import { importChatSettings } from '../settingsImExport'; + +export function SettingsGeneral(props) { + const { theme, fontFamily, fontSize, lineHeight } = + useSelector(selectSettings); + const dispatch = useDispatch(); + const [freeFont, setFreeFont] = useState(false); + + return ( +
+ + + {THEMES.map((THEME) => ( + + ))} + + + + {!freeFont ? ( + { + setFreeFont(!freeFont); + }} + > + Custom font + + } + > + {FONTS.map((FONT) => ( + + ))} + + ) : ( + + + dispatch( + updateSettings({ + fontFamily: value, + }), + ) + } + /> + + + )} + + + + + + toFixed(value)} + onChange={(e, value) => + dispatch(updateSettings({ fontSize: value })) + } + /> + + + + + toFixed(value, 2)} + onDrag={(e, value) => + dispatch( + updateSettings({ + lineHeight: value, + }), + ) + } + /> + + + + + + + + + importChatSettings(files)} + > + Import settings + + + + + + + dispatch(clearChat())} + > + Clear chat + + + +
+ ); +} diff --git a/tgui/packages/tgui-panel/settings/SettingTabs/SettingsStatPanel.tsx b/tgui/packages/tgui-panel/settings/SettingTabs/SettingsStatPanel.tsx new file mode 100644 index 000000000000..cf089dad59e5 --- /dev/null +++ b/tgui/packages/tgui-panel/settings/SettingTabs/SettingsStatPanel.tsx @@ -0,0 +1,84 @@ +import { toFixed } from 'common/math'; +import { capitalize } from 'common/string'; +import { useDispatch, useSelector } from 'tgui/backend'; +import { + Button, + LabeledList, + NoticeBox, + Section, + Slider, + Stack, +} from 'tgui/components'; + +import { updateSettings } from '../actions'; +import { selectSettings } from '../selectors'; + +const TabsViews = ['default', 'classic', 'scrollable']; +const LinkedToChat = () => ( + Unlink Stat Panel from chat! +); + +export function SettingsStatPanel(props) { + const { statLinked, statFontSize, statTabsStyle } = + useSelector(selectSettings); + const dispatch = useDispatch(); + + return ( +
+ + + + + {TabsViews.map((view) => ( + + ))} + + + + {statLinked ? ( + + ) : ( + toFixed(value)} + onChange={(e, value) => + dispatch(updateSettings({ statFontSize: value })) + } + /> + )} + + + + + + + + + +
+ ); +} diff --git a/tgui/packages/tgui-panel/settings/SettingTabs/TextHighlight.tsx b/tgui/packages/tgui-panel/settings/SettingTabs/TextHighlight.tsx new file mode 100644 index 000000000000..b1e9cf6daa80 --- /dev/null +++ b/tgui/packages/tgui-panel/settings/SettingTabs/TextHighlight.tsx @@ -0,0 +1,177 @@ +import { useDispatch, useSelector } from 'tgui/backend'; +import { + Box, + Button, + ColorBox, + Divider, + Input, + Section, + Stack, + TextArea, +} from 'tgui/components'; + +import { rebuildChat } from '../../chat/actions'; +import { + addHighlightSetting, + removeHighlightSetting, + updateHighlightSetting, +} from '../actions'; +import { + selectHighlightSettingById, + selectHighlightSettings, +} from '../selectors'; + +export function TextHighlightSettings(props) { + const highlightSettings = useSelector(selectHighlightSettings); + const dispatch = useDispatch(); + + return ( +
+ + {highlightSettings.map((id, i) => ( + + ))} + + + + + + + + + + + Can freeze the chat for a while. + + +
+ ); +} + +function TextHighlightSetting(props) { + const { id, ...rest } = props; + const highlightSettingById = useSelector(selectHighlightSettingById); + const dispatch = useDispatch(); + const { + highlightColor, + highlightText, + highlightWholeMessage, + matchWord, + matchCase, + } = highlightSettingById[id]; + + return ( + + + + + + + + dispatch( + updateHighlightSetting({ + id: id, + highlightWholeMessage: !highlightWholeMessage, + }), + ) + } + > + Whole Message + + + + + dispatch( + updateHighlightSetting({ + id: id, + matchWord: !matchWord, + }), + ) + } + > + Exact + + + + + dispatch( + updateHighlightSetting({ + id: id, + matchCase: !matchCase, + }), + ) + } + > + Case + + + + + + dispatch( + updateHighlightSetting({ + id: id, + highlightColor: value, + }), + ) + } + /> + + +