diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 39c462c012c..1e39cb0d8e4 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -89,6 +89,8 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list( //More carbon mobs #define isalien(A) (istype(A, /mob/living/carbon/alien)) +#define isalienhumanoid(A) (istype(A, /mob/living/carbon/alien/humanoid)) + #define islarva(A) (istype(A, /mob/living/carbon/alien/larva)) #define isalienadult(A) (istype(A, /mob/living/carbon/alien/humanoid) || istype(A, /mob/living/simple_animal/hostile/alien)) diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm index 9d67466cf5b..46d1ef3333b 100644 --- a/code/__HELPERS/maths.dm +++ b/code/__HELPERS/maths.dm @@ -143,3 +143,15 @@ return "centuple" else //It gets too tedious to use latin prefixes from here. return "[number]-tuple" + +/// Takes a value, and a threshold it has to at least match +/// returns the correctly signed value max'd to the threshold +/proc/at_least(new_value, threshold) + var/sign = SIGN(new_value) + // SIGN will return 0 if the value is 0, so we just go to the positive threshold + if(!sign) + return threshold + if(sign == 1) + return max(new_value, threshold) + if(sign == -1) + return min(new_value, threshold * -1) diff --git a/code/__HELPERS/spatial_info.dm b/code/__HELPERS/spatial_info.dm index f8e5282f081..448b7bcba99 100644 --- a/code/__HELPERS/spatial_info.dm +++ b/code/__HELPERS/spatial_info.dm @@ -280,8 +280,7 @@ return turfs ///Returns a list of turfs around a center based on view() -/proc/circle_view_turfs(center=usr,radius=3) //Is there even a diffrence between this proc and circle_range_turfs()? - +/proc/circle_view_turfs(center=usr,radius=3) //Is there even a diffrence between this proc and circle_range_turfs()? // Yes var/turf/center_turf = get_turf(center) var/list/turfs = new/list() var/rsq = radius * (radius + 0.5) @@ -293,6 +292,31 @@ turfs += checked_turf return turfs +///Returns the list of turfs around the outside of a center based on RANGE_TURFS() +/proc/border_diamond_range_turfs(atom/center = usr, radius = 3) + var/turf/center_turf = get_turf(center) + var/list/turfs = list() + + for(var/turf/checked_turf as anything in RANGE_TURFS(radius, center_turf)) + var/dx = checked_turf.x - center_turf.x + var/dy = checked_turf.y - center_turf.y + var/abs_sum = abs(dx) + abs(dy) + if(abs_sum == radius) + turfs += checked_turf + return turfs + +///Returns a slice of a list of turfs, defined by the ones that are inside the inner/outer angle's bounds +/proc/slice_off_turfs(atom/center, list/turf/turfs, inner_angle, outer_angle) + var/turf/center_turf = get_turf(center) + var/list/sliced_turfs = list() + + for(var/turf/checked_turf as anything in turfs) + var/angle_to = get_angle(center_turf, checked_turf) + if(angle_to < inner_angle || angle_to > outer_angle) + continue + sliced_turfs += checked_turf + return sliced_turfs + /** * Get a bounding box of a list of atoms. * diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 95e157007c2..eba31944215 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -378,18 +378,22 @@ /mob/living/carbon/human/CtrlClick(mob/user) - - if(!ishuman(user) || !user.CanReach(src) || user.incapacitated()) + if(!iscarbon(user) || !user.CanReach(src) || user.incapacitated()) return ..() if(world.time < user.next_move) return FALSE - var/mob/living/carbon/human/human_user = user - if(human_user.dna.species.grab(human_user, src, human_user.mind.martial_art)) - human_user.changeNext_move(CLICK_CD_MELEE) - return TRUE - + if (ishuman(user)) + var/mob/living/carbon/human/human_user = user + if(human_user.dna.species.grab(human_user, src, human_user.mind.martial_art)) + human_user.changeNext_move(CLICK_CD_MELEE) + return TRUE + else if(isalien(user)) + var/mob/living/carbon/alien/humanoid/alien_boy = user + if(alien_boy.grab(src)) + alien_boy.changeNext_move(CLICK_CD_MELEE) + return TRUE return ..() /mob/proc/CtrlMiddleClickOn(atom/A) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 244bf680222..a01d50ac396 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -65,6 +65,8 @@ var/atom/movable/pulling var/grab_state = 0 + /// The strongest grab we can acomplish + var/max_grab = GRAB_KILL var/throwforce = 0 var/datum/component/orbiter/orbiting diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 70065e0beed..d8466c87e77 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -126,7 +126,7 @@ if (isnull(held_item)) if(density) - if(isalienadult(living_user) || issilicon(living_user)) + if(isalienhumanoid(living_user) || issilicon(living_user)) context[SCREENTIP_CONTEXT_LMB] = "Open" return CONTEXTUAL_SCREENTIP_SET if(!living_user.combat_mode) diff --git a/code/game/objects/effects/effect_system/effects_water.dm b/code/game/objects/effects/effect_system/effects_water.dm index 4141788552f..7a09a422eb0 100644 --- a/code/game/objects/effects/effect_system/effects_water.dm +++ b/code/game/objects/effects/effect_system/effects_water.dm @@ -7,7 +7,6 @@ var/life = 15 mouse_opacity = MOUSE_OPACITY_TRANSPARENT - /obj/effect/particle_effect/water/Initialize(mapload) . = ..() QDEL_IN(src, 70) @@ -58,6 +57,17 @@ QDEL_IN(src, engine.delay) //Gotta let it stop drifting animate(src, alpha = 0, time = engine.delay) +/obj/effect/particle_effect/water/extinguisher/stomach_acid + name = "acid" + icon_state = "xenobarf" + +// Stomach acid doesn't use legacy because it's not "targeted", and we instead want the circular sorta look +/obj/effect/particle_effect/water/extinguisher/stomach_acid/move_at(atom/target, delay, lifetime) + var/datum/move_loop/loop = SSmove_manager.move_towards(src, target, delay, timeout = delay * lifetime, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY) + RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, .proc/post_forcemove) + RegisterSignal(loop, COMSIG_PARENT_QDELETING, .proc/movement_stopped) + return loop + ///////////////////////////////////////////// // GENERIC STEAM SPREAD SYSTEM diff --git a/code/modules/mob/living/carbon/alien/emote.dm b/code/modules/mob/living/carbon/alien/emote.dm index edf529858f3..94ea98e3d7e 100644 --- a/code/modules/mob/living/carbon/alien/emote.dm +++ b/code/modules/mob/living/carbon/alien/emote.dm @@ -14,7 +14,7 @@ emote_type = EMOTE_AUDIBLE /datum/emote/living/alien/hiss/get_sound(mob/living/user) - if(isalienadult(user)) + if(isalienhumanoid(user)) return SFX_HISS /datum/emote/living/alien/roar @@ -26,5 +26,5 @@ vary = TRUE /datum/emote/living/alien/roar/get_sound(mob/living/user) - if(isalienadult(user)) + if(isalienhumanoid(user)) return 'sound/voice/hiss5.ogg' diff --git a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm index 1ff88c419d0..4c4ae7e7c1e 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm @@ -362,6 +362,33 @@ Doesn't work on other aliens/AI.*/ return TRUE +/datum/action/cooldown/alien/regurgitate + name = "Regurgitate" + desc = "Empties the contents of your stomach." + button_icon_state = "alien_barf" + var/angle_delta = 45 + var/mob_speed = 1.5 + var/spit_speed = 1 + +/datum/action/cooldown/alien/regurgitate/Activate(atom/target) + if(!iscarbon(owner)) + return + var/mob/living/carbon/alien/humanoid/alieninated_owner = owner + var/obj/item/organ/internal/stomach/alien/melting_pot = alieninated_owner.getorganslot(ORGAN_SLOT_STOMACH) + if(!melting_pot) + owner.visible_message(span_clown("[src] gags, and spits up a bit of purple liquid. Ewwww."), \ + span_alien("You feel a pain in your... chest? There's nothing there there's nothing there no no n-")) + return + + if(!length(melting_pot.stomach_contents)) + to_chat(owner, span_alien("There's nothing in your stomach, what exactly do you plan on spitting up?")) + return + owner.visible_message(span_danger("[owner] hurls out the contents of their stomach!")) + var/dir_angle = dir2angle(owner.dir) + + playsound(owner, 'sound/creatures/alien_york.ogg', 100) + melting_pot.eject_stomach(slice_off_turfs(owner, border_diamond_range_turfs(owner, 9), dir_angle - angle_delta, dir_angle + angle_delta), 4, mob_speed, spit_speed) + /// Gets the plasma level of this carbon's plasma vessel, or -1 if they don't have one /mob/living/carbon/proc/getPlasma() var/obj/item/organ/internal/alien/plasmavessel/vessel = getorgan(/obj/item/organ/internal/alien/plasmavessel) @@ -383,3 +410,4 @@ Doesn't work on other aliens/AI.*/ /mob/living/carbon/alien/adjustPlasma(amount) . = ..() updatePlasmaDisplay() + diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index 174d06bf4c9..6dbf3998d56 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -7,6 +7,7 @@ hud_type = /datum/hud/alien melee_damage_lower = 20 //Refers to unarmed damage, aliens do unarmed attacks. melee_damage_upper = 20 + max_grab = GRAB_AGGRESSIVE var/caste = "" var/alt_icon = 'icons/mob/alienleap.dmi' //used to switch between the two alien icon files. var/leap_on_click = 0 @@ -34,6 +35,10 @@ GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list( AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW, 0.5, -11) AddElement(/datum/element/strippable, GLOB.strippable_alien_humanoid_items) +/mob/living/carbon/alien/humanoid/create_internal_organs() + internal_organs += new /obj/item/organ/internal/stomach/alien() + return ..() + /mob/living/carbon/alien/humanoid/cuff_resist(obj/item/I) playsound(src, 'sound/voice/hiss5.ogg', 40, TRUE, TRUE) //Alien roars when starting to break free ..(I, cuff_break = INSTANT_CUFFBREAK) @@ -68,3 +73,81 @@ GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list( if(numba) name = "[name] ([numba])" real_name = name + +/mob/living/carbon/alien/humanoid/proc/grab(mob/living/carbon/human/target) + if(target.check_block()) + target.visible_message(span_warning("[target] blocks [src]'s grab!"), \ + span_userdanger("You block [src]'s grab!"), span_hear("You hear a swoosh!"), COMBAT_MESSAGE_RANGE, src) + to_chat(src, span_warning("Your grab at [target] was blocked!")) + return FALSE + target.grabbedby(src) + return TRUE + +/mob/living/carbon/alien/humanoid/setGrabState(newstate) + if(newstate == grab_state) + return + if(newstate > GRAB_AGGRESSIVE) + newstate = GRAB_AGGRESSIVE + SEND_SIGNAL(src, COMSIG_MOVABLE_SET_GRAB_STATE, newstate) + . = grab_state + grab_state = newstate + switch(grab_state) // Current state. + if(GRAB_PASSIVE) + REMOVE_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) + if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher. + REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + if(GRAB_AGGRESSIVE) + if(. >= GRAB_NECK) // Grab got downgraded. + REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + else // Grab got upgraded from a passive one. + ADD_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) + if(GRAB_NECK, GRAB_KILL) + if(. <= GRAB_AGGRESSIVE) + ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + +/mob/living/carbon/alien/humanoid/MouseDrop_T(atom/dropping, atom/user) + if(devour_lad(dropping)) + return + return ..() + +/// Returns FALSE if we're not allowed to eat it, true otherwise +/mob/living/carbon/alien/humanoid/proc/can_consume(atom/movable/poor_soul) + if(!isliving(poor_soul) || pulling != poor_soul) + return FALSE + if(incapacitated() || grab_state < GRAB_AGGRESSIVE || stat != CONSCIOUS) + return FALSE + if(get_dir(src, poor_soul) != dir) // Gotta face em 4head + return FALSE + return TRUE + +/// Attempts to devour the passed in thing in devour_time seconds +/// The mob needs to be consumable, as decided by [/mob/living/carbon/alien/humanoid/proc/can_consume] +/// Returns FALSE if the attempt never even started, TRUE otherwise +/mob/living/carbon/alien/humanoid/proc/devour_lad(atom/movable/candidate, devour_time = 13.5 SECONDS) + setDir(get_dir(src, candidate)) + if(!can_consume(candidate)) + return FALSE + var/mob/living/lucky_winner = candidate + + lucky_winner.audible_message(span_danger("You hear a great snapping, like the disjointing of muscle and bone.")) + lucky_winner.visible_message(span_danger("[src] is attempting to devour [lucky_winner]!"), \ + span_userdanger("[src] is attempting to devour you!")) + + playsound(lucky_winner, 'sound/creatures/alien_eat.ogg', 100) + if(!do_mob(src, lucky_winner, devour_time, extra_checks = CALLBACK(src, .proc/can_consume, lucky_winner))) + return TRUE + if(!can_consume(lucky_winner)) + return TRUE + + var/obj/item/organ/internal/stomach/alien/melting_pot = getorganslot(ORGAN_SLOT_STOMACH) + if(!istype(melting_pot)) + visible_message(span_clown("[src] can't seem to consume [lucky_winner]!"), \ + span_alien("You feel a pain in your... chest? You can't get [lucky_winner] down.")) + return TRUE + + lucky_winner.audible_message(span_danger("You hear a deep groan, and a harsh snap like a mantrap.")) + lucky_winner.visible_message(span_danger("[src] devours [lucky_winner]!"), \ + span_userdanger("[lucky_winner] devours you!")) + melting_pot.consume_thing(lucky_winner) + log_combat(src, lucky_winner, "devoured") + return TRUE diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm index 9bf9a243e2e..a520ed1ff21 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm @@ -147,7 +147,7 @@ if(!promotion) CRASH("[type] was created and handled by a mob ([queen]) that didn't have a promotion action associated.") - if(!isalienadult(to_promote) || isalienroyal(to_promote)) + if(!isalienhumanoid(to_promote) || isalienroyal(to_promote)) to_chat(queen, span_noticealien("You may only use this with your adult, non-royal children!")) return diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm index ca33aa826e7..3e7ab19ff33 100644 --- a/code/modules/mob/living/carbon/alien/life.dm +++ b/code/modules/mob/living/carbon/alien/life.dm @@ -41,6 +41,10 @@ //BREATH TEMPERATURE handle_breath_temperature(breath) +/mob/living/carbon/alien/humanoid/Life(delta_time, times_fired) + . = ..() + handle_organs(delta_time, times_fired) + /mob/living/carbon/alien/handle_status_effects(delta_time, times_fired) ..() //natural reduction of movement delay due to stun. diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm index 33cb540787a..b04572cad3e 100644 --- a/code/modules/mob/living/carbon/alien/organs.dm +++ b/code/modules/mob/living/carbon/alien/organs.dm @@ -138,7 +138,7 @@ /obj/item/organ/internal/alien/resinspinner name = "resin spinner" - icon_state = "stomach-x" + icon_state = "spinner-x" zone = BODY_ZONE_PRECISE_MOUTH slot = ORGAN_SLOT_XENO_RESINSPINNER actions_types = list(/datum/action/cooldown/alien/make_structure/resin) @@ -167,3 +167,174 @@ slot = ORGAN_SLOT_XENO_EGGSAC w_class = WEIGHT_CLASS_BULKY actions_types = list(/datum/action/cooldown/alien/make_structure/lay_egg) + +/// The stomach that lets aliens eat people/things +/obj/item/organ/internal/stomach/alien + name = "alien stomach" + icon_state = "stomach-x" + w_class = WEIGHT_CLASS_BULKY + actions_types = list(/datum/action/cooldown/alien/regurgitate) + var/list/atom/movable/stomach_contents = list() + +/obj/item/organ/internal/stomach/alien/Destroy() + QDEL_LIST(stomach_contents) + return ..() + +/obj/item/organ/internal/stomach/alien/on_life(delta_time, times_fired) + . = ..() + if(!owner || SSmobs.times_fired % 3 != 0) + return + // Digest the stuff in our stomach, just a bit + var/static/list/digestable_cache = typecacheof(list(/mob/living, /obj/item/food, /obj/item/reagent_containers)) + for(var/atom/movable/thing as anything in stomach_contents) + if(!digestable_cache[thing.type]) + continue + thing.reagents.trans_to(src, 4) + + if(isliving(thing)) + var/mob/living/lad = thing + lad.adjustBruteLoss(6) + else if(!thing.reagents.total_volume) // Mobs can't get dusted like this, too important + qdel(thing) + +/obj/item/organ/internal/stomach/alien/proc/consume_thing(atom/movable/thing) + RegisterSignal(thing, COMSIG_MOVABLE_MOVED, .proc/content_moved) + if(isliving(thing)) + RegisterSignal(thing, COMSIG_LIVING_DEATH, .proc/content_died) + stomach_contents += thing + thing.forceMove(owner || src) // We assert that if we have no owner, we will not be nullspaced + +/obj/item/organ/internal/stomach/alien/proc/content_died(atom/movable/source) + SIGNAL_HANDLER + qdel(source) + +/obj/item/organ/internal/stomach/alien/proc/content_moved(atom/movable/source) + SIGNAL_HANDLER + if(source.loc == src || source.loc == owner) // not in us? out da list then + return + stomach_contents -= source + UnregisterSignal(source, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_DEATH)) + +/obj/item/organ/internal/stomach/alien/Insert(mob/living/carbon/stomach_owner, special) + RegisterSignal(stomach_owner, COMSIG_ATOM_RELAYMOVE, .proc/something_moved) + return ..() + +/obj/item/organ/internal/stomach/alien/Remove(mob/living/carbon/stomach_owner, special) + UnregisterSignal(stomach_owner, COMSIG_ATOM_RELAYMOVE) + return ..() + +/obj/item/organ/internal/stomach/alien/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + if(loc == null && owner) + for(var/atom/movable/thing as anything in contents) + thing.forceMove(owner) + else if(loc != null) + for(var/atom/movable/thing as anything in contents) + thing.forceMove(src) + +/obj/item/organ/internal/stomach/alien/proc/something_moved(mob/living/source, mob/living/user, direction) + SIGNAL_HANDLER + relaymove(user, direction) + return COMSIG_BLOCK_RELAYMOVE + +/obj/item/organ/internal/stomach/alien/relaymove(mob/living/user, direction) + if(!(user in src.stomach_contents)) + return + if(!prob(40)) + return + var/atom/play_from = owner || src + var/stomach_text = owner ? "\the [owner]'s stomach" : "\the [src]" + if(prob(25)) + play_from.audible_message(span_warning("You hear something rumbling inside [stomach_text]..."), \ + span_warning("You hear something rumbling."), 4,\ + self_message = span_userdanger("Something is rumbling inside your stomach!")) + + if(user.client) + user.client.move_delay = world.time + 1.5 SECONDS + + var/attack_name = "" + var/attack_verb = "" + var/impact = 0 + var/obj/item/pokie = user.get_active_held_item() + if(pokie) + var/dmg = pokie.force || 0 + var/list/attack_verbs = pokie.attack_verb_continuous + impact = rand(round(dmg / 4), dmg) + attack_name = pokie.name + attack_verb = length(attack_verbs) ? "[pick(attack_verbs)]" : "attacks" + + if(!impact) + return + + applyOrganDamage(impact) + + var/damage_ratio = damage / max(maxHealth, 1) + if(owner) + var/obj/item/bodypart/part = owner.get_bodypart(BODY_ZONE_CHEST) + // Brute damage to the mob is less then to the organ, so there's a higher chance of the explosion happening before xeno death + part.receive_damage(impact / 2) + // We choose the option that's best for the check + var/part_dam_ratio = part.brute_dam / max(part.max_damage, 1) + if(damage_ratio < part_dam_ratio) + damage_ratio = part_dam_ratio + + play_from.visible_message(span_danger("[user] [attack_verb] [stomach_text] wall with the [attack_name]!"), \ + span_userdanger("[user] [attack_verb] your stomach wall with the [attack_name]!")) + + // At 100% damage, the stomach burts + // Otherwise, we give them a -50% -> 50% chance scaling with damage dealt + if(!prob((damage_ratio * 100) - 50) && damage_ratio != 1) + playsound(play_from, 'sound/creatures/alien_organ_cut.ogg', 100, 1) + // We try and line up the "jump" here with the sound of the hit + var/oldx = play_from.pixel_x + var/oldy = play_from.pixel_y + animate(play_from, pixel_x = oldx, pixel_y = oldx, 0.1 SECONDS) + var/newx = oldx + at_least(rand(-8, 8), 2) + var/newy = oldy + at_least(rand(-8, 8), 2) + // Here's a bit before the hit + animate(pixel_x = newx, pixel_y = newx, 0.15 SECONDS, easing = SINE_EASING | EASE_IN) + newx += at_least(rand(-4, 4), 1) + newy += at_least(rand(-4, 4), 1) + // Here's a bit after the hit, we've got maybe 2 ticks to add a bit more juice + animate(pixel_x = newy, pixel_y = newx, 0.1 SECONDS) + // Now we're gonna walk back to rest in maybe 3 ticks? + animate(pixel_x = oldx, pixel_y = oldx, 0.5 SECONDS) + + shake_camera(user, 0.1 SECONDS, 0.5) + if(owner) + shake_camera(owner, 0.3 SECONDS, 1.5) + return + // Failure condition + if(isalienhumanoid(user)) + play_from.visible_message(span_danger("[user] blows a hole in [stomach_text] and escapes!"), \ + span_userdanger("As your hive's food bursts out of your stomach, one thought fills your mind. \"Oh, so this is how the other side feels\"")) + else // Just to be safe ya know? + play_from.visible_message(span_danger("[user] blows a hole in [stomach_text] and escapes!"), \ + span_userdanger("[user] escapes from your [stomach_text]. Hell, that hurts.")) + + playsound(get_turf(play_from), 'sound/creatures/alien_explode.ogg', 100, extrarange = 4) + eject_stomach(border_diamond_range_turfs(play_from, 6), 5, 1.5, 1, 8) + shake_camera(user, 1 SECONDS, 3) + if(owner) + shake_camera(owner, 2, 5) + owner.gib() + qdel(src) + +/obj/item/organ/internal/stomach/alien/proc/eject_stomach(list/turf/targets, spit_range, content_speed, particle_delay, particle_count=4) + var/atom/spit_as = owner || src + /// Throw out the stuff in our stomach + for(var/atom/movable/thing as anything in stomach_contents) + thing.forceMove(spit_as.drop_location()) + if(length(targets)) + thing.throw_at(pick(targets), spit_range, content_speed, thrower = spit_as, spin = TRUE) + + for(var/a in 1 to particle_count) + if(!length(targets)) + break + var/obj/effect/particle_effect/water/extinguisher/stomach_acid/acid = new (get_turf(spit_as)) + var/turf/my_target = pick_n_take(targets) + var/datum/reagents/acid_reagents = new /datum/reagents(5) + acid.reagents = acid_reagents + acid_reagents.my_atom = acid + acid_reagents.add_reagent(/datum/reagent/toxin/acid, 30) + acid.move_at(my_target, particle_delay, spit_range) diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm index bac5a8dcc38..27ffce5a8ae 100644 --- a/code/modules/mob/living/carbon/damage_procs.dm +++ b/code/modules/mob/living/carbon/damage_procs.dm @@ -61,7 +61,6 @@ amount += BP.burn_dam return amount - /mob/living/carbon/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_status) if(!forced && (status_flags & GODMODE)) return FALSE diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm index 9cb6eef50fe..e5077c4442d 100644 --- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm @@ -109,7 +109,7 @@ /obj/item/organ/internal/heart/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "stomach-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") /obj/item/organ/internal/heart/fly/update_icon_state() SHOULD_CALL_PARENT(FALSE) @@ -121,7 +121,7 @@ /obj/item/organ/internal/lungs/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "stomach-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") /obj/item/organ/internal/liver/fly desc = "You have no idea what the hell this is, or how it manages to keep something alive in any capacity." @@ -130,7 +130,7 @@ /obj/item/organ/internal/liver/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "stomach-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") /obj/item/organ/internal/stomach/fly desc = "You have no idea what the hell this is, or how it manages to keep something alive in any capacity." @@ -138,7 +138,7 @@ /obj/item/organ/internal/stomach/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "stomach-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") /obj/item/organ/internal/stomach/fly/on_life(delta_time, times_fired) if(locate(/datum/reagent/consumable) in reagents.reagent_list) @@ -156,7 +156,7 @@ /obj/item/organ/internal/appendix/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "stomach-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") /obj/item/organ/internal/appendix/fly/update_appearance(updates=ALL) return ..(updates & ~(UPDATE_NAME|UPDATE_ICON)) //don't set name or icon thank you @@ -169,7 +169,7 @@ /obj/item/organ/internal/fly/Initialize(mapload) . = ..() name = odd_organ_name() - icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "stomach-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") + icon_state = pick("brain-x-d", "liver-x", "kidneys-x", "spinner-x", "lungs-x", "random_fly_1", "random_fly_2", "random_fly_3", "random_fly_4", "random_fly_5") /obj/item/organ/internal/fly/groin //appendix is the only groin organ so we gotta have one of these too lol zone = BODY_ZONE_PRECISE_GROIN diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 738fcf3c6c5..e080475e1af 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -150,62 +150,62 @@ //proc to upgrade a simple pull into a more aggressive grab. /mob/living/proc/grippedby(mob/living/carbon/user, instant = FALSE) - if(user.grab_state < GRAB_KILL) - user.changeNext_move(CLICK_CD_GRABBING) - var/sound_to_play = 'sound/weapons/thudswoosh.ogg' - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna.species.grab_sound) - sound_to_play = H.dna.species.grab_sound - playsound(src.loc, sound_to_play, 50, TRUE, -1) + if(user.grab_state >= user.max_grab) + return + user.changeNext_move(CLICK_CD_GRABBING) + var/sound_to_play = 'sound/weapons/thudswoosh.ogg' + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna.species.grab_sound) + sound_to_play = H.dna.species.grab_sound + playsound(src.loc, sound_to_play, 50, TRUE, -1) - if(user.grab_state) //only the first upgrade is instantaneous - var/old_grab_state = user.grab_state - var/grab_upgrade_time = instant ? 0 : 30 - visible_message(span_danger("[user] starts to tighten [user.p_their()] grip on [src]!"), \ - span_userdanger("[user] starts to tighten [user.p_their()] grip on you!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You start to tighten your grip on [src]!")) - switch(user.grab_state) - if(GRAB_AGGRESSIVE) - log_combat(user, src, "attempted to neck grab", addition="neck grab") - if(GRAB_NECK) - log_combat(user, src, "attempted to strangle", addition="kill grab") - if(!do_mob(user, src, grab_upgrade_time)) - return FALSE - if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state) - return FALSE - user.setGrabState(user.grab_state + 1) + if(user.grab_state) //only the first upgrade is instantaneous + var/old_grab_state = user.grab_state + var/grab_upgrade_time = instant ? 0 : 30 + visible_message(span_danger("[user] starts to tighten [user.p_their()] grip on [src]!"), \ + span_userdanger("[user] starts to tighten [user.p_their()] grip on you!"), span_hear("You hear aggressive shuffling!"), null, user) + to_chat(user, span_danger("You start to tighten your grip on [src]!")) switch(user.grab_state) if(GRAB_AGGRESSIVE) - var/add_log = "" - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - visible_message(span_danger("[user] firmly grips [src]!"), - span_danger("[user] firmly grips you!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You firmly grip [src]!")) - add_log = " (pacifist)" - else - visible_message(span_danger("[user] grabs [src] aggressively!"), \ - span_userdanger("[user] grabs you aggressively!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You grab [src] aggressively!")) - drop_all_held_items() - stop_pulling() - log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") + log_combat(user, src, "attempted to neck grab", addition="neck grab") if(GRAB_NECK) - log_combat(user, src, "grabbed", addition="neck grab") - visible_message(span_danger("[user] grabs [src] by the neck!"),\ - span_userdanger("[user] grabs you by the neck!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You grab [src] by the neck!")) - if(!buckled && !density) - Move(user.loc) - if(GRAB_KILL) - log_combat(user, src, "strangled", addition="kill grab") - visible_message(span_danger("[user] is strangling [src]!"), \ - span_userdanger("[user] is strangling you!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You're strangling [src]!")) - if(!buckled && !density) - Move(user.loc) - user.set_pull_offsets(src, grab_state) - return TRUE + log_combat(user, src, "attempted to strangle", addition="kill grab") + if(!do_mob(user, src, grab_upgrade_time)) + return FALSE + if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state) + return FALSE + user.setGrabState(user.grab_state + 1) + switch(user.grab_state) + if(GRAB_AGGRESSIVE) + var/add_log = "" + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + visible_message(span_danger("[user] firmly grips [src]!"), + span_danger("[user] firmly grips you!"), span_hear("You hear aggressive shuffling!"), null, user) + to_chat(user, span_danger("You firmly grip [src]!")) + add_log = " (pacifist)" + else + visible_message(span_danger("[user] grabs [src] aggressively!"), \ + span_userdanger("[user] grabs you aggressively!"), span_hear("You hear aggressive shuffling!"), null, user) + to_chat(user, span_danger("You grab [src] aggressively!")) + stop_pulling() + log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") + if(GRAB_NECK) + log_combat(user, src, "grabbed", addition="neck grab") + visible_message(span_danger("[user] grabs [src] by the neck!"),\ + span_userdanger("[user] grabs you by the neck!"), span_hear("You hear aggressive shuffling!"), null, user) + to_chat(user, span_danger("You grab [src] by the neck!")) + if(!buckled && !density) + Move(user.loc) + if(GRAB_KILL) + log_combat(user, src, "strangled", addition="kill grab") + visible_message(span_danger("[user] is strangling [src]!"), \ + span_userdanger("[user] is strangling you!"), span_hear("You hear aggressive shuffling!"), null, user) + to_chat(user, span_danger("You're strangling [src]!")) + if(!buckled && !density) + Move(user.loc) + user.set_pull_offsets(src, grab_state) + return TRUE /mob/living/attack_slime(mob/living/simple_animal/slime/M) diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm index 5afb8ac74a0..41b8c7733c1 100644 --- a/code/modules/mob/living/simple_animal/slime/life.dm +++ b/code/modules/mob/living/simple_animal/slime/life.dm @@ -348,7 +348,7 @@ else for(var/mob/living/carbon/C in targets) if(!Discipline && DT_PROB(2.5, delta_time)) - if(ishuman(C) || isalienadult(C)) + if(ishuman(C) || isalienhumanoid(C)) set_target(C) break diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm index 6b286b9faaf..1f76691ec43 100644 --- a/code/modules/paperwork/photocopier.dm +++ b/code/modules/paperwork/photocopier.dm @@ -313,7 +313,7 @@ temp_img = icon(spec.ass_image) else temp_img = icon(ass.gender == FEMALE ? 'icons/ass/assfemale.png' : 'icons/ass/assmale.png') - else if(isalienadult(ass) || istype(ass, /mob/living/simple_animal/hostile/alien)) //Xenos have their own asses, thanks to Pybro. + else if(isalienadult(ass)) //Xenos have their own asses, thanks to Pybro. temp_img = icon('icons/ass/assalien.png') else if(issilicon(ass)) temp_img = icon('icons/ass/assmachine.png') diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index cf355f7cfc7..ced291043c4 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -249,6 +249,7 @@ wound_resistance = -10 px_x = 5 px_y = -3 + /obj/item/bodypart/r_arm/alien icon = 'icons/mob/animal_parts.dmi' icon_state = "alien_r_arm" diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index c5252487376..207fb4a0aea 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi index 410780b105f..f30d5f2b603 100755 Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ diff --git a/sound/attributions.txt b/sound/attributions.txt index 75751919e19..7f10a08b94d 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -31,3 +31,7 @@ mbox_full.ogg and mbox_end.ogg make use of The Ragtime Drummer by James Lent, in growl1.ogg and growl2.ogg in /sound/creatures/dog are adapted from Glitchedtones's Freesound shih-tzu uploads https://freesound.org/people/Glitchedtones/ eject.ogg is by magedu, adapted from https://freesound.org/people/magedu/sounds/267832/ + +alien_explode.ogg is adapted from zimbot's Frozen Lettuce upload https://freesound.org/people/zimbot/sounds/89378/ and little_dog4000's balloon pop https://freesound.org/people/little_dog4000/sounds/478764/ + +alien_organ_cut.ogg is adapted from some CC 0 flesh sound, lesaucisson's swoosh https://freesound.org/people/lesaucisson/sounds/585256/, and a snippet of EminYILDIRIM's tree cutting clip https://freesound.org/people/EminYILDIRIM/sounds/608343/ diff --git a/sound/creatures/alien_eat.ogg b/sound/creatures/alien_eat.ogg new file mode 100644 index 00000000000..55351fbeb64 Binary files /dev/null and b/sound/creatures/alien_eat.ogg differ diff --git a/sound/creatures/alien_explode.ogg b/sound/creatures/alien_explode.ogg new file mode 100644 index 00000000000..4f0991ab5b4 Binary files /dev/null and b/sound/creatures/alien_explode.ogg differ diff --git a/sound/creatures/alien_organ_cut.ogg b/sound/creatures/alien_organ_cut.ogg new file mode 100644 index 00000000000..33a699fa2ea Binary files /dev/null and b/sound/creatures/alien_organ_cut.ogg differ diff --git a/sound/creatures/alien_york.ogg b/sound/creatures/alien_york.ogg new file mode 100644 index 00000000000..a8e88eef28f Binary files /dev/null and b/sound/creatures/alien_york.ogg differ