diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 802abe2c349..2b253c90aad 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -46,6 +46,7 @@ #define COLOR_SOFT_RED "#FA8282" #define COLOR_CULT_RED "#960000" #define COLOR_BUBBLEGUM_RED "#950A0A" +#define COLOR_CARP_RIFT_RED "#ff330030" #define COLOR_YELLOW "#FFFF00" #define COLOR_VIVID_YELLOW "#FBFF23" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm index e1811a24af3..7a02dbddabb 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm @@ -2,6 +2,10 @@ // When the signal is called: (signal arguments) // All signals send the source datum of the signal as the first argument +///from the [EX_ACT] wrapper macro: (severity, target) +#define COMSIG_ATOM_PRE_EX_ACT "atom_pre_ex_act" + /// if returned, don't let the explosion act on this atom + #define COMPONENT_CANCEL_EX_ACT (1<<0) ///from the [EX_ACT] wrapper macro: (severity, target) #define COMSIG_ATOM_EX_ACT "atom_ex_act" ///from base of atom/emp_act(): (severity). return EMP protection flags diff --git a/code/__DEFINES/explosions.dm b/code/__DEFINES/explosions.dm index 0ce9644af1c..a1645b659d1 100644 --- a/code/__DEFINES/explosions.dm +++ b/code/__DEFINES/explosions.dm @@ -23,8 +23,10 @@ if(!(target.flags_1 & PREVENT_CONTENTS_EXPLOSION_1)) { \ target.contents_explosion(##args);\ };\ - SEND_SIGNAL(target, COMSIG_ATOM_EX_ACT, ##args);\ - target.ex_act(##args); + if(!(SEND_SIGNAL(target, COMSIG_ATOM_PRE_EX_ACT, ##args) & COMPONENT_CANCEL_EX_ACT)) { \ + SEND_SIGNAL(target, COMSIG_ATOM_EX_ACT, ##args);\ + target.ex_act(##args);\ + } // Internal explosion argument list keys. // Must match the arguments to [/datum/controller/subsystem/explosions/proc/propagate_blastwave] diff --git a/code/__DEFINES/megafauna.dm b/code/__DEFINES/megafauna.dm new file mode 100644 index 00000000000..981b8d49f52 --- /dev/null +++ b/code/__DEFINES/megafauna.dm @@ -0,0 +1,4 @@ +/// Temperature of drake fire hotspots +#define DRAKE_FIRE_TEMP 500 +/// Volume of drake fire hotspots +#define DRAKE_FIRE_EXPOSURE 50 diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 4f838691d4b..d43f897382c 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -1326,3 +1326,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///Trait given by /datum/element/relay_attacker #define TRAIT_RELAYING_ATTACKER "relaying_attacker" + +/// Trait given while using /datum/action/cooldown/mob_cooldown/wing_buffet +#define TRAIT_WING_BUFFET "wing_buffet" +/// Trait given while tired after using /datum/action/cooldown/mob_cooldown/wing_buffet +#define TRAIT_WING_BUFFET_TIRED "wing_buffet_tired" +/// Trait given to a dragon who fails to defend their rifts +#define TRAIT_RIFT_FAILURE "fail_dragon_loser" diff --git a/code/datums/actions/mobs/fire_breath.dm b/code/datums/actions/mobs/fire_breath.dm index 254a6081425..45b6538c018 100644 --- a/code/datums/actions/mobs/fire_breath.dm +++ b/code/datums/actions/mobs/fire_breath.dm @@ -2,63 +2,121 @@ name = "Fire Breath" button_icon = 'icons/effects/magic.dmi' button_icon_state = "fireball" - desc = "Allows you to shoot fire towards a target." + desc = "Breathe a line of flames towards the target." cooldown_time = 3 SECONDS /// The range of the fire var/fire_range = 15 /// The sound played when you use this ability var/fire_sound = 'sound/magic/fireball.ogg' - /// If the fire should be icey fire - var/ice_breath = FALSE + /// Time to wait between spawning each fire turf + var/fire_delay = 1.5 DECISECONDS + /// How hot is our fire + var/fire_temperature = DRAKE_FIRE_TEMP + /// 'How much' fire do we expose the turf to? + var/fire_volume = DRAKE_FIRE_EXPOSURE + /// How much damage do you take when engulfed? + var/fire_damage = 20 + /// How much damage to mechs take when engulfed? + var/mech_damage = 45 /datum/action/cooldown/mob_cooldown/fire_breath/Activate(atom/target_atom) - StartCooldown(360 SECONDS, 360 SECONDS) attack_sequence(target_atom) StartCooldown() return TRUE +/// Apply our specific fire breathing shape, in proc form so we can override it in subtypes /datum/action/cooldown/mob_cooldown/fire_breath/proc/attack_sequence(atom/target) playsound(owner.loc, fire_sound, 200, TRUE) - fire_line(target, 0) + fire_line(target) +/// Breathe fire in a line towards the target, optionally rotated at an offset from the target /datum/action/cooldown/mob_cooldown/fire_breath/proc/fire_line(atom/target, offset) - SLEEP_CHECK_DEATH(0, owner) - var/list/turfs = line_target(offset, fire_range, target) - // This proc sleeps - INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(dragon_fire_line), owner, /* burn_turfs = */ turfs, /* frozen = */ ice_breath) - -/datum/action/cooldown/mob_cooldown/fire_breath/proc/line_target(offset, range, atom/target) - if(!target) + if (isnull(target)) return - var/turf/T = get_ranged_target_turf_direct(owner, target, range, offset) - return (get_line(owner, T) - get_turf(owner)) + var/turf/target_turf = get_ranged_target_turf_direct(owner, target, fire_range, offset) + var/list/turfs = get_line(owner, target_turf) - get_turf(owner) + INVOKE_ASYNC(src, PROC_REF(progressive_fire_line), turfs) +/// Creates fire with a delay on the list of targetted turfs +/datum/action/cooldown/mob_cooldown/fire_breath/proc/progressive_fire_line(list/burn_turfs) + if (QDELETED(owner) || owner.stat == DEAD) + return + // Guys we have already hit, no double dipping + var/list/hit_list = list(owner) // also don't burn ourselves + for(var/turf/target_turf in burn_turfs) + if (target_turf.is_blocked_turf(exclude_mobs = TRUE)) + return + burn_turf(target_turf, hit_list, owner) + sleep(fire_delay) + +/// Finally spawn the actual fire, spawns the fire hotspot in case you want to recolour it or something +/datum/action/cooldown/mob_cooldown/fire_breath/proc/burn_turf(turf/fire_turf, list/hit_list, mob/living/source) + var/obj/effect/hotspot/fire_hotspot = new /obj/effect/hotspot(fire_turf) + fire_turf.hotspot_expose(fire_temperature, fire_volume, TRUE) + + for(var/mob/living/barbecued in fire_turf.contents) + if(barbecued in hit_list) + continue + hit_list |= barbecued + on_burn_mob(barbecued, source) + + for(var/obj/vehicle/sealed/mecha/robotron in fire_turf.contents) + if(robotron in hit_list) + continue + hit_list |= robotron + robotron.take_damage(mech_damage, BURN, FIRE) + + return fire_hotspot + +/// Do something unpleasant to someone we set on fire +/datum/action/cooldown/mob_cooldown/fire_breath/proc/on_burn_mob(mob/living/barbecued, mob/living/source) + to_chat(barbecued, span_userdanger("You are burned by [source]'s fire breath!")) + barbecued.adjustFireLoss(fire_damage) + +/// Shoot three lines of fire in a sort of fork pattern approximating a cone /datum/action/cooldown/mob_cooldown/fire_breath/cone name = "Fire Cone" - desc = "Allows you to shoot fire towards a target with surrounding lines of fire." + desc = "Breathe several lines of fire directed at a target." /// The angles relative to the target that shoot lines of fire var/list/angles = list(-40, 0, 40) /datum/action/cooldown/mob_cooldown/fire_breath/cone/attack_sequence(atom/target) playsound(owner.loc, fire_sound, 200, TRUE) for(var/offset in angles) - INVOKE_ASYNC(src, PROC_REF(fire_line), target, offset) + fire_line(target, offset) +/// Shoot fire in a whole bunch of directions /datum/action/cooldown/mob_cooldown/fire_breath/mass_fire name = "Mass Fire" button_icon = 'icons/effects/fire.dmi' button_icon_state = "1" - desc = "Allows you to shoot fire in all directions." + desc = "Breathe flames in all directions." cooldown_time = 3 SECONDS + click_to_activate = FALSE + /// How many fire lines do we produce to turn a full circle? + var/sectors = 12 + /// How long do we wait between each spin? + var/breath_delay = 2.5 SECONDS + /// How many full circles do we perform? + var/total_spins = 3 + +/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/Activate(atom/target_atom) + target_atom = get_step(owner, owner.dir) // Just shoot it forwards, we don't need to click on someone for this one + return ..() /datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/attack_sequence(atom/target) - shoot_mass_fire(target, 12, 2.5 SECONDS, 3) + var/queued_spins = 0 + for (var/i in 1 to total_spins) + var/delay = queued_spins * breath_delay + queued_spins++ + addtimer(CALLBACK(src, PROC_REF(fire_spin), target, queued_spins), delay) -/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/proc/shoot_mass_fire(atom/target, spiral_count, delay_time, times) - SLEEP_CHECK_DEATH(0, owner) - for(var/i = 1 to times) - playsound(owner.loc, fire_sound, 200, TRUE) - var/increment = 360 / spiral_count - for(var/j = 1 to spiral_count) - INVOKE_ASYNC(src, PROC_REF(fire_line), target, j * increment + i * increment / 2) - SLEEP_CHECK_DEATH(delay_time, owner) +/// Breathe fire in a circle, with a slight angle offset based on which of our several circles it is +/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/proc/fire_spin(target, spin_count) + if (QDELETED(owner) || owner.stat == DEAD) + return // Too dead to spin + playsound(owner.loc, fire_sound, 200, TRUE) + var/angle_increment = 360 / sectors + var/additional_offset = spin_count * angle_increment / 2 + for (var/i in 1 to sectors) + fire_line(target, (angle_increment * i) + (additional_offset)) diff --git a/code/datums/actions/mobs/mobcooldown.dm b/code/datums/actions/mobs/mobcooldown.dm index 17561f2f45a..a495859494f 100644 --- a/code/datums/actions/mobs/mobcooldown.dm +++ b/code/datums/actions/mobs/mobcooldown.dm @@ -3,7 +3,7 @@ button_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" desc = "Click this ability to attack." - check_flags = AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED cooldown_time = 5 SECONDS text_cooldown = TRUE click_to_activate = TRUE diff --git a/code/datums/actions/mobs/sneak.dm b/code/datums/actions/mobs/sneak.dm index 3fed4f4d500..738bb7b70cf 100644 --- a/code/datums/actions/mobs/sneak.dm +++ b/code/datums/actions/mobs/sneak.dm @@ -5,7 +5,6 @@ button_icon_state = "sniper_zoom" background_icon_state = "bg_alien" overlay_icon_state = "bg_alien_border" - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED | AB_CHECK_INCAPACITATED cooldown_time = 0.5 SECONDS melee_cooldown_time = 0 SECONDS click_to_activate = FALSE diff --git a/code/datums/components/pry_open_door.dm b/code/datums/components/pry_open_door.dm deleted file mode 100644 index 17e445d25ca..00000000000 --- a/code/datums/components/pry_open_door.dm +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Attached to a basic mob. - * Causes attacks on doors to attempt to open them. - */ -/datum/component/pry_open_door - /// Odds the attack opens the door - var/open_chance - /// Time it takes to open a door with force - var/force_wait - -/datum/component/pry_open_door/Initialize(open_chance = 100, force_wait = 10 SECONDS) - . = ..() - - if(!isbasicmob(parent)) - return COMPONENT_INCOMPATIBLE - src.open_chance = open_chance - src.force_wait = force_wait - -/datum/component/pry_open_door/RegisterWithParent() - RegisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(hostile_attackingtarget)) - -/datum/component/pry_open_door/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET) - -/datum/component/pry_open_door/proc/hostile_attackingtarget(mob/living/basic/attacker, atom/target, success) - SIGNAL_HANDLER - - if(!success) - return - if(istype(target, /obj/machinery/door/airlock) && prob(open_chance)) - var/obj/machinery/door/airlock/airlock_target = target - INVOKE_ASYNC(src, PROC_REF(open_door), attacker, airlock_target) - -/datum/component/pry_open_door/proc/open_door(mob/living/basic/attacker, obj/machinery/door/airlock/airlock_target) - if(airlock_target.locked) - to_chat(attacker, span_warning("The airlock's bolts prevent it from being forced!")) - return - else if(!airlock_target.allowed(attacker) && airlock_target.hasPower()) - attacker.visible_message(span_warning("We start forcing the [airlock_target] open."), \ - span_hear("You hear a metal screeching sound.")) - playsound(airlock_target, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) - if(!do_after(attacker, force_wait, airlock_target)) - return - if(airlock_target.locked) - return - attacker.visible_message(span_warning("We force the [airlock_target] to open.")) - airlock_target.open(BYPASS_DOOR_CHECKS) - else if(!airlock_target.hasPower()) - attacker.visible_message(span_warning("We force the [airlock_target] to open.")) - airlock_target.open(FORCING_DOOR_CHECKS) - else - airlock_target.open(DEFAULT_DOOR_CHECKS) diff --git a/code/datums/components/torn_wall.dm b/code/datums/components/torn_wall.dm new file mode 100644 index 00000000000..ebe0fe9a0a6 --- /dev/null +++ b/code/datums/components/torn_wall.dm @@ -0,0 +1,99 @@ + +#define TORN_WALL_RUINED 2 +#define TORN_WALL_DAMAGED 1 +#define TORN_WALL_INITIAL 0 + +/** + * Component applied to a wall to progressively destroy it. + * If component is applied to something which already has it, stage increases. + * Wall is destroyed on third application. + * Can be fixed using a welder + */ +/datum/component/torn_wall + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + var/current_stage = TORN_WALL_INITIAL + +/datum/component/torn_wall/Initialize() + . = ..() + if (!iswallturf(parent) || isindestructiblewall(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/torn_wall/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) + RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_WELDER), PROC_REF(on_welded)) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays)) + RegisterSignal(parent, COMSIG_TURF_CHANGE, PROC_REF(on_turf_changed)) + apply_visuals() + +/datum/component/torn_wall/UnregisterFromParent() + var/atom/atom_parent = parent + UnregisterSignal(parent, list( + COMSIG_ATOM_EXAMINE, + COMSIG_ATOM_TOOL_ACT(TOOL_WELDER), + COMSIG_ATOM_UPDATE_OVERLAYS, + COMSIG_TURF_CHANGE, + )) + atom_parent.update_appearance(UPDATE_ICON) + +/datum/component/torn_wall/InheritComponent(datum/component/C, i_am_original) + increase_stage() + +/// Play a fun animation and make our wall look damaged +/datum/component/torn_wall/proc/apply_visuals() + var/atom/atom_parent = parent + playsound(atom_parent, 'sound/effects/bang.ogg', 50, vary = TRUE) + atom_parent.update_appearance(UPDATE_ICON) + atom_parent.Shake(shake_interval = 0.1 SECONDS, duration = 0.5 SECONDS) + +/// Make the effect more dramatic +/datum/component/torn_wall/proc/increase_stage() + current_stage++ + if (current_stage != TORN_WALL_RUINED) + apply_visuals() + return + var/turf/closed/wall/attached_wall = parent + playsound(attached_wall, 'sound/effects/meteorimpact.ogg', 100, vary = TRUE) + attached_wall.dismantle_wall(devastated = TRUE) + +/// Fix it up on weld +/datum/component/torn_wall/proc/on_welded(atom/source, mob/user, obj/item/tool) + SIGNAL_HANDLER + INVOKE_ASYNC(src, PROC_REF(try_repair), source, user, tool) + return COMPONENT_BLOCK_TOOL_ATTACK + +/// Fix us up +/datum/component/torn_wall/proc/try_repair(atom/source, mob/user, obj/item/tool) + source.balloon_alert(user, "repairing...") + if(!tool.use_tool(source, user, 5 SECONDS, amount = 2, volume = 50)) + source.balloon_alert(user, "interrupted!") + return + current_stage-- + if (current_stage < TORN_WALL_INITIAL) + qdel(src) + return + source.update_appearance(UPDATE_ICON) + source.tool_act(user, tool, TOOL_WELDER, is_right_clicking = FALSE) // Keep going + +/// Give them a hint +/datum/component/torn_wall/proc/on_examined(atom/source, mob/user, list/examine_list) + SIGNAL_HANDLER + var/intensity = (current_stage == TORN_WALL_INITIAL) ? "slightly" : "badly" + examine_list += span_notice("It looks [intensity] damaged.") + examine_list += span_info("You may be able to repair it using a welding tool.") + +/// Show a little crack on here +/datum/component/torn_wall/proc/on_update_overlays(turf/source, list/overlays) + SIGNAL_HANDLER + var/mutable_appearance/crack = mutable_appearance('icons/turf/overlays.dmi', "explodable", source.layer + 0.1) + if (current_stage == TORN_WALL_INITIAL) + crack.alpha *= 0.5 + overlays += crack + +/// If the wall becomes any other turf, delete us. Transforming into a different works fine as a fix. +/datum/component/torn_wall/proc/on_turf_changed() + SIGNAL_HANDLER + qdel(src) + +#undef TORN_WALL_RUINED +#undef TORN_WALL_DAMAGED +#undef TORN_WALL_INITIAL diff --git a/code/datums/elements/door_pryer.dm b/code/datums/elements/door_pryer.dm new file mode 100644 index 00000000000..5d807434e04 --- /dev/null +++ b/code/datums/elements/door_pryer.dm @@ -0,0 +1,70 @@ +/** + * Attached to a basic mob. + * Causes attacks on doors to attempt to open them. + */ +/datum/element/door_pryer + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// Time it takes to open a door with force + var/pry_time + /// Interaction key for if we force a door open + var/interaction_key + +/datum/element/door_pryer/Attach(datum/target, pry_time = 10 SECONDS, interaction_key = null) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + src.pry_time = pry_time + src.interaction_key = interaction_key + RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK), PROC_REF(on_attack)) + +/datum/element/door_pryer/Detach(datum/source) + . = ..() + UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK)) + +/// If we're targetting an airlock, open it +/datum/element/door_pryer/proc/on_attack(mob/living/basic/attacker, atom/target) + SIGNAL_HANDLER + if(!istype(target, /obj/machinery/door/airlock)) + return + var/obj/machinery/door/airlock/airlock_target = target + if (!airlock_target.density) + return // It's already open numbnuts + + if(DOING_INTERACTION_WITH_TARGET(attacker, target) || (!isnull(interaction_key) && DOING_INTERACTION(attacker, interaction_key))) + attacker.balloon_alert(attacker, "busy!") + return COMPONENT_CANCEL_ATTACK_CHAIN + + if (airlock_target.locked || airlock_target.welded || airlock_target.seal) + if (!attacker.combat_mode) + airlock_target.balloon_alert(attacker, "it's sealed!") + return COMPONENT_CANCEL_ATTACK_CHAIN + return // Attack the door + + INVOKE_ASYNC(src, PROC_REF(open_door), attacker, airlock_target) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/// Try opening the door, and if we can't then try forcing it +/datum/element/door_pryer/proc/open_door(mob/living/basic/attacker, obj/machinery/door/airlock/airlock_target) + if (!airlock_target.hasPower()) + attacker.visible_message(span_warning("[attacker] forces the [airlock_target] to open.")) + airlock_target.open(FORCING_DOOR_CHECKS) + return + + if (airlock_target.allowed(attacker)) + airlock_target.open(DEFAULT_DOOR_CHECKS) + return + + attacker.visible_message(\ + message = span_warning("[attacker] starts forcing the [airlock_target] open!"), + blind_message = span_hear("You hear a metal screeching sound."), + ) + playsound(airlock_target, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) + airlock_target.balloon_alert(attacker, "prying...") + if(!do_after(attacker, pry_time, airlock_target)) + airlock_target.balloon_alert(attacker, "interrupted!") + return + if(airlock_target.locked) + return + attacker.visible_message(span_warning("[attacker] forces the [airlock_target] to open.")) + airlock_target.open(BYPASS_DOOR_CHECKS) diff --git a/code/datums/elements/wall_tearer.dm b/code/datums/elements/wall_tearer.dm new file mode 100644 index 00000000000..8499a161705 --- /dev/null +++ b/code/datums/elements/wall_tearer.dm @@ -0,0 +1,83 @@ +/// Returned if we can rip up this target +#define WALL_TEAR_ALLOWED TRUE +/// Returned if we can't rip up this target +#define WALL_TEAR_INVALID FALSE +/// Returned if we can't rip up the target but still don't want to attack it +#define WALL_TEAR_FAIL_CANCEL_CHAIN -1 + +/** + * Allows attached mobs to destroy walls over time, a little less unreasonable than the instant wall deletion of wall_smasher + */ +/datum/element/wall_tearer + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// Whether we can break reinforced walls + var/allow_reinforced + /// How long it takes for us to destroy a wall completely (its a 3 step process so this will be divided by three) + var/tear_time + /// How much longer it takes to break reinforced walls + var/reinforced_multiplier + /// What interaction key do we use for our interaction + var/do_after_key + +/datum/element/wall_tearer/Attach(datum/target, allow_reinforced = TRUE, tear_time = 4 SECONDS, reinforced_multiplier = 3, do_after_key = null) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + src.allow_reinforced = allow_reinforced + src.tear_time = tear_time + src.reinforced_multiplier = reinforced_multiplier + src.do_after_key = do_after_key + RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK), PROC_REF(on_attacked_wall)) + +/datum/element/wall_tearer/Detach(datum/source) + . = ..() + UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK)) + +/// Try to tear up a wall +/datum/element/wall_tearer/proc/on_attacked_wall(mob/living/tearer, atom/target, proximity_flag) + SIGNAL_HANDLER + if (!proximity_flag) + return + if (DOING_INTERACTION_WITH_TARGET(tearer, target) || (!isnull(do_after_key) && DOING_INTERACTION(tearer, do_after_key))) + tearer.balloon_alert(tearer, "busy!") + return COMPONENT_HOSTILE_NO_ATTACK + var/is_valid = validate_target(target, tearer) + if (is_valid != WALL_TEAR_ALLOWED) + return is_valid == WALL_TEAR_FAIL_CANCEL_CHAIN ? COMPONENT_HOSTILE_NO_ATTACK : NONE + INVOKE_ASYNC(src, PROC_REF(rip_and_tear), tearer, target) + return COMPONENT_HOSTILE_NO_ATTACK + +/datum/element/wall_tearer/proc/rip_and_tear(mob/living/tearer, atom/target) + // We need to do this three times to actually destroy it + var/rip_time = (istype(target, /turf/closed/wall/r_wall) ? tear_time * reinforced_multiplier : tear_time) / 3 + if (rip_time > 0) + tearer.visible_message(span_warning("[tearer] begins tearing through [target]!")) + playsound(tearer, 'sound/machines/airlock_alien_prying.ogg', vol = 100, vary = TRUE) + target.balloon_alert(tearer, "tearing...") + if (!do_after(tearer, delay = rip_time, target = target, interaction_key = do_after_key)) + tearer.balloon_alert(tearer, "interrupted!") + return + // Might have been replaced, removed, or reinforced during our do_after + var/is_valid = validate_target(target, tearer) + if (is_valid != WALL_TEAR_ALLOWED) + return + target.AddComponent(/datum/component/torn_wall) + is_valid = validate_target(target, tearer) // And now we might have just destroyed it + if (is_valid == WALL_TEAR_ALLOWED) + tearer.UnarmedAttack(target, proximity_flag = TRUE) + +/// Check if the target atom is a wall we can actually rip up +/datum/element/wall_tearer/proc/validate_target(atom/target, mob/living/tearer) + if (!isclosedturf(target) || isindestructiblewall(target)) + return WALL_TEAR_INVALID + + var/reinforced = istype(target, /turf/closed/wall/r_wall) + if (!allow_reinforced && reinforced) + target.balloon_alert(tearer, "it's too strong!") + return WALL_TEAR_FAIL_CANCEL_CHAIN + return WALL_TEAR_ALLOWED + +#undef WALL_TEAR_ALLOWED +#undef WALL_TEAR_INVALID +#undef WALL_TEAR_FAIL_CANCEL_CHAIN diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index dda198db431..5a85b41b270 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -581,7 +581,7 @@ var/datum/mind/player_mind = new /datum/mind(applicant.key) player_mind.active = TRUE - var/mob/living/simple_animal/hostile/space_dragon/S = new (pick(spawn_locs)) + var/mob/living/basic/space_dragon/S = new (pick(spawn_locs)) player_mind.transfer_to(S) player_mind.add_antag_datum(/datum/antagonist/space_dragon) diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 444eff17006..f99b9f3cc94 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -738,7 +738,7 @@ return update_last_used(user) -/obj/machinery/tool_act(mob/living/user, obj/item/tool, tool_type) +/obj/machinery/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) if(SEND_SIGNAL(user, COMSIG_TRY_USE_MACHINE, src) & COMPONENT_CANT_USE_MACHINE_TOOLS) return TOOL_ACT_MELEE_CHAIN_BLOCKING . = ..() diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index ccb7cf702c7..c56dec6c74f 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -71,7 +71,7 @@ diode = null return TRUE -/obj/item/laser_pointer/tool_act(mob/living/user, obj/item/tool) +/obj/item/laser_pointer/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) . = ..() if(isnull(crystal_lens) || !(tool.tool_behaviour == TOOL_WIRECUTTER || tool.tool_behaviour == TOOL_HEMOSTAT)) return diff --git a/code/game/objects/items/dna_probe.dm b/code/game/objects/items/dna_probe.dm index 4ea89d0a95e..6c9651a3142 100644 --- a/code/game/objects/items/dna_probe.dm +++ b/code/game/objects/items/dna_probe.dm @@ -135,7 +135,7 @@ to_chat(user, span_notice("You pull out the needle from [src] and flip the switch, and start injecting yourself with it.")) if(!do_after(user, CARP_MIX_DNA_TIMER)) return - var/mob/living/simple_animal/hostile/space_dragon/new_dragon = user.change_mob_type(/mob/living/simple_animal/hostile/space_dragon, location = loc, delete_old_mob = TRUE) + var/mob/living/basic/space_dragon/new_dragon = user.change_mob_type(/mob/living/basic/space_dragon, location = loc, delete_old_mob = TRUE) new_dragon.add_filter("anger_glow", 3, list("type" = "outline", "color" = "#ff330030", "size" = 5)) new_dragon.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) priority_announce("A large organic energy flux has been recorded near of [station_name()], please stand-by.", "Lifesign Alert") diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm index ad4be8d6e40..0d845b5254e 100644 --- a/code/game/objects/items/storage/secure.dm +++ b/code/game/objects/items/storage/secure.dm @@ -40,7 +40,7 @@ . = ..() icon_state = "[initial(icon_state)][atom_storage?.locked ? "_locked" : null]" -/obj/item/storage/secure/tool_act(mob/living/user, obj/item/tool) +/obj/item/storage/secure/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) if(can_hack_open && atom_storage.locked) return ..() else diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm index a400771bc0c..49ce549abd5 100644 --- a/code/game/objects/structures/false_walls.dm +++ b/code/game/objects/structures/false_walls.dm @@ -83,7 +83,7 @@ qdel(src) return T -/obj/structure/falsewall/tool_act(mob/living/user, obj/item/tool) +/obj/structure/falsewall/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) if(!opening) return ..() to_chat(user, span_warning("You must wait until the door has stopped moving!")) diff --git a/code/game/turfs/open/floor/plating.dm b/code/game/turfs/open/floor/plating.dm index eee4797887b..a38338010d4 100644 --- a/code/game/turfs/open/floor/plating.dm +++ b/code/game/turfs/open/floor/plating.dm @@ -177,7 +177,7 @@ ScrapeAway(flags = CHANGETURF_INHERIT_AIR) return TRUE -/turf/open/floor/plating/foam/tool_act(mob/living/user, obj/item/I, tool_type) +/turf/open/floor/plating/foam/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) return //reinforced plating deconstruction states diff --git a/code/modules/antagonists/space_dragon/space_dragon.dm b/code/modules/antagonists/space_dragon/space_dragon.dm index 7c49bed6b95..574e458faca 100644 --- a/code/modules/antagonists/space_dragon/space_dragon.dm +++ b/code/modules/antagonists/space_dragon/space_dragon.dm @@ -157,6 +157,7 @@ if(objective_complete) return rifts_charged = 0 + ADD_TRAIT(owner.current, TRAIT_RIFT_FAILURE, REF(src)) owner.current.add_movespeed_modifier(/datum/movespeed_modifier/dragon_depression) riftTimer = -1 SEND_SOUND(owner.current, sound('sound/vehicles/rocketlaunch.ogg')) diff --git a/code/modules/events/ghost_role/space_dragon.dm b/code/modules/events/ghost_role/space_dragon.dm index 379b5d7e07b..dd75a97a869 100644 --- a/code/modules/events/ghost_role/space_dragon.dm +++ b/code/modules/events/ghost_role/space_dragon.dm @@ -31,7 +31,7 @@ if(isnull(spawn_location)) return MAP_ERROR - var/mob/living/simple_animal/hostile/space_dragon/dragon = new (spawn_location) + var/mob/living/basic/space_dragon/dragon = new (spawn_location) dragon.key = key dragon.mind.add_antag_datum(/datum/antagonist/space_dragon) playsound(dragon, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) diff --git a/code/modules/holodeck/turfs.dm b/code/modules/holodeck/turfs.dm index 28482873aeb..1e89052af59 100644 --- a/code/modules/holodeck/turfs.dm +++ b/code/modules/holodeck/turfs.dm @@ -8,7 +8,7 @@ /turf/open/floor/holofloor/attackby(obj/item/I, mob/living/user) return // HOLOFLOOR DOES NOT GIVE A FUCK -/turf/open/floor/holofloor/tool_act(mob/living/user, obj/item/I, tool_type) +/turf/open/floor/holofloor/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) return /turf/open/floor/holofloor/burn_tile() diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm index f89009cc2d9..8fb02ec83df 100644 --- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm @@ -43,12 +43,12 @@ AddElement(/datum/element/footstep, FOOTSTEP_MOB_HEAVY) AddComponent(/datum/component/basic_mob_ability_telegraph) AddComponent(/datum/component/basic_mob_attack_telegraph, telegraph_duration = 0.6 SECONDS) - var/datum/action/cooldown/mob_cooldown/ice_breath/flamethrower = new(src) - var/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions/wide_flames = new(src) + var/datum/action/cooldown/mob_cooldown/fire_breath/ice/flamethrower = new(src) flamethrower.Grant(src) + ai_controller.set_blackboard_key(BB_WHELP_STRAIGHTLINE_FIRE, flamethrower) + var/datum/action/cooldown/mob_cooldown/fire_breath/ice/cross/wide_flames = new(src) wide_flames.Grant(src) ai_controller.set_blackboard_key(BB_WHELP_WIDESPREAD_FIRE, wide_flames) - ai_controller.set_blackboard_key(BB_WHELP_STRAIGHTLINE_FIRE, flamethrower) RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm index d5dc50d0a69..026106516fb 100644 --- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm @@ -1,23 +1,26 @@ -/datum/action/cooldown/mob_cooldown/ice_breath +/// Breathe "fire" in a line (it's freezing cold) +/datum/action/cooldown/mob_cooldown/fire_breath/ice name = "Ice Breath" desc = "Fire a cold line of fire towards the enemy!" button_icon = 'icons/effects/magic.dmi' button_icon_state = "fireball" cooldown_time = 3 SECONDS melee_cooldown_time = 0 SECONDS - click_to_activate = TRUE - ///the range of fire - var/fire_range = 4 + fire_range = 4 + fire_damage = 10 -/datum/action/cooldown/mob_cooldown/ice_breath/Activate(atom/target_atom) - var/turf/target_fire_turf = get_ranged_target_turf_direct(owner, target_atom, fire_range) - var/list/burn_turfs = get_line(owner, target_fire_turf) - get_turf(owner) - // This proc sleeps - INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(dragon_fire_line), owner, /* burn_turfs = */ burn_turfs, /* frozen = */ TRUE) - StartCooldown() - return TRUE +/datum/action/cooldown/mob_cooldown/fire_breath/ice/burn_turf(turf/fire_turf, list/hit_list, atom/source) + var/obj/effect/hotspot/fire_hotspot = ..() + fire_hotspot.add_atom_colour(COLOR_BLUE_LIGHT, FIXED_COLOUR_PRIORITY) // You're blue now, that's my attack + return fire_hotspot -/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions +/datum/action/cooldown/mob_cooldown/fire_breath/ice/on_burn_mob(mob/living/barbecued, mob/living/source) + barbecued.apply_status_effect(/datum/status_effect/ice_block_talisman, 2 SECONDS) + to_chat(barbecued, span_userdanger("You're frozen solid by [source]'s icy breath!")) + barbecued.adjustFireLoss(fire_damage) + +/// Breathe really cold fire in a plus shape, like bomberman +/datum/action/cooldown/mob_cooldown/fire_breath/ice/cross name = "Fire all directions" desc = "Unleash lines of cold fire in all directions" button_icon = 'icons/effects/fire.dmi' @@ -25,13 +28,10 @@ cooldown_time = 4 SECONDS melee_cooldown_time = 0 SECONDS click_to_activate = FALSE - ///the range of fire - var/fire_range = 6 + fire_range = 6 -/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions/Activate(atom/target_atom) +/datum/action/cooldown/mob_cooldown/fire_breath/ice/cross/attack_sequence(atom/target) + playsound(owner.loc, fire_sound, 200, TRUE) for(var/direction in GLOB.cardinals) var/turf/target_fire_turf = get_ranged_target_turf(owner, direction, fire_range) - var/list/burn_turfs = get_line(owner, target_fire_turf) - get_turf(owner) - INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(dragon_fire_line), owner, burn_turfs, frozen = TRUE) - StartCooldown() - return TRUE + fire_line(target_fire_turf) diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm index 29a3de60029..b8e2bb68c69 100644 --- a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm @@ -9,7 +9,6 @@ click_to_activate = TRUE cooldown_time = 5 SECONDS melee_cooldown_time = 0 - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED /// How far does our beam go? var/beam_range = 10 /// How long does our beam last? diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm index bb8adaf61c3..b484afa0cea 100644 --- a/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm @@ -9,7 +9,6 @@ click_to_activate = TRUE cooldown_time = 12 SECONDS melee_cooldown_time = 0 - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED shared_cooldown = NONE /// Furthest range we can activate ability at var/max_range = 7 @@ -44,7 +43,6 @@ overlay_icon_state = "bg_demon_border" cooldown_time = 24 SECONDS melee_cooldown_time = 0 - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED shared_cooldown = NONE click_to_activate = FALSE @@ -69,7 +67,6 @@ click_to_activate = TRUE cooldown_time = 12 SECONDS melee_cooldown_time = 0 - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED shared_cooldown = NONE /datum/action/cooldown/mob_cooldown/tentacle_grasp/Activate(atom/target) diff --git a/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm index 3fee2a003f3..63a66d4c4e3 100644 --- a/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm +++ b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm @@ -9,7 +9,6 @@ click_to_activate = TRUE cooldown_time = 2 SECONDS melee_cooldown_time = 0 - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED shared_cooldown = NONE /// If a mob is not clicked directly, inherit targetting data from this blackboard key and setting it upon this target key var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET diff --git a/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm index 1ffcafecd56..f68c5f7fafe 100644 --- a/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm +++ b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm @@ -9,7 +9,6 @@ click_to_activate = TRUE cooldown_time = 2 SECONDS melee_cooldown_time = 0 - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED shared_cooldown = NONE /// If a mob is not clicked directly, inherit targetting data from this blackboard key and setting it upon this target key var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm index 4b322c220ed..06a221db2dc 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm @@ -9,7 +9,6 @@ background_icon_state = "bg_demon" overlay_icon_state = "bg_demon_border" cooldown_time = 20 SECONDS - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED click_to_activate = FALSE shared_cooldown = NONE melee_cooldown_time = 0 SECONDS diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm index 0c8194c524a..541a4ed9e1c 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm @@ -10,7 +10,6 @@ background_icon_state = "bg_demon" overlay_icon_state = "bg_demon_border" cooldown_time = 20 SECONDS - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED click_to_activate = TRUE shared_cooldown = NONE /// Furthest range we can activate ability at diff --git a/code/modules/mob/living/basic/space_fauna/faithless.dm b/code/modules/mob/living/basic/space_fauna/faithless.dm index c1dc297ea46..88696b3a3c7 100644 --- a/code/modules/mob/living/basic/space_fauna/faithless.dm +++ b/code/modules/mob/living/basic/space_fauna/faithless.dm @@ -38,9 +38,9 @@ /mob/living/basic/faithless/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + AddElement(/datum/element/door_pryer) AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE) AddElement(/datum/element/mob_grabber, steal_from_others = FALSE) - AddComponent(/datum/component/pry_open_door) /mob/living/basic/faithless/melee_attack(atom/target, list/modifiers, ignore_cooldown) . = ..() diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm index 164c25fb896..3bff85bec8f 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm @@ -54,6 +54,7 @@ AddElement(/datum/element/waddling) AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/door_pryer, pry_time = 5 SECONDS, interaction_key = REGALRAT_INTERACTION) AddComponent(\ /datum/component/ghost_direct_control,\ poll_candidates = poll_ghosts,\ @@ -174,10 +175,6 @@ if(isnull(mind)) return - if(istype(target, /obj/machinery/door/airlock)) - INVOKE_ASYNC(src, PROC_REF(pry_door), target) - return COMPONENT_HOSTILE_NO_ATTACK - if(!combat_mode) INVOKE_ASYNC(src, PROC_REF(poison_target), target) return COMPONENT_HOSTILE_NO_ATTACK @@ -235,43 +232,7 @@ heal_bodypart_damage(amount) qdel(target) -/** - * Allows rat king to pry open an airlock if it isn't locked. - * - * A proc used for letting the rat king pry open airlocks instead of just attacking them. - * This allows the rat king to traverse the station when there is a lack of vents or - * accessible doors, something which is common in certain rat king spawn points. - * - * Returns TRUE if the door opens, FALSE otherwise. - */ -/mob/living/basic/regal_rat/proc/pry_door(target) - if(DOING_INTERACTION(src, REGALRAT_INTERACTION)) - return FALSE - - var/obj/machinery/door/airlock/prying_door = target - if(!prying_door.density || prying_door.locked || prying_door.welded || prying_door.seal) - return FALSE - - visible_message( - span_warning("[src] begins prying open the airlock..."), - span_notice("You begin digging your claws into the airlock..."), - span_warning("You hear groaning metal..."), - ) - var/time_to_open = 0.5 SECONDS - - if(prying_door.hasPower()) - time_to_open = 5 SECONDS - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, vary = TRUE) - - if(!do_after(src, time_to_open, prying_door, interaction_key = REGALRAT_INTERACTION)) - return FALSE - - if(!prying_door.open(BYPASS_DOOR_CHECKS)) - balloon_alert(src, "failed to open!") - return FALSE - - return TRUE - +/// Regal rat subtype which can be possessed by ghosts /mob/living/basic/regal_rat/controlled poll_ghosts = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_breath.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_breath.dm new file mode 100644 index 00000000000..c5825f2e2e2 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_breath.dm @@ -0,0 +1,32 @@ +/// A space dragon's fire breath, toasts lunch AND buffs your friends +/datum/action/cooldown/mob_cooldown/fire_breath/carp + desc = "A Space Dragon's burning breath not only chars its foes, but invigorates Space Carp as well." + fire_damage = 30 + mech_damage = 50 + fire_range = 20 + fire_temperature = 700 // Even hotter than a megafauna for some reason + shared_cooldown = NONE + melee_cooldown_time = 0 SECONDS + +/datum/action/cooldown/mob_cooldown/fire_breath/carp/on_burn_mob(mob/living/barbecued, mob/living/source) + if (!source.faction_check_mob(barbecued)) + return ..() + to_chat(barbecued, span_notice("[source]'s fiery breath fills you with energy!")) + barbecued.apply_status_effect(/datum/status_effect/carp_invigoration) + +/// Makes you run faster for the duration +/datum/status_effect/carp_invigoration + id = "carp_invigorated" + alert_type = null + duration = 8 SECONDS + +/datum/status_effect/carp_invigoration/on_apply() + . = ..() + if (!.) + return + owner.add_filter("anger_glow", 3, list("type" = "outline", "color" = COLOR_CARP_RIFT_RED, "size" = 2)) + owner.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) + +/datum/status_effect/carp_invigoration/on_remove() + owner.remove_filter("anger_glow") + owner.remove_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_gust.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_gust.dm new file mode 100644 index 00000000000..a1b9a466bb4 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_gust.dm @@ -0,0 +1,94 @@ +/// Default additional time to spend stunned per usage of ability +#define DEFAULT_ACTIVATED_ENDLAG 3 DECISECONDS + +/// Rise into the air and slam down, knocking people away. No real cooldown but has escalating endlag if used in quick succession. +/datum/action/cooldown/mob_cooldown/wing_buffet + name = "Wing Buffet" + desc = "Rise into the air and release a powerful gust from your wings, blowing attackers away. Becomes more tiring if used in quick succession." + button_icon = 'icons/effects/magic.dmi' + button_icon_state = "tornado" + cooldown_time = 1 SECONDS + melee_cooldown_time = 0 + click_to_activate = FALSE + shared_cooldown = NONE + /// Timer we use to track our current action + var/active_timer + /// How far away can we reach people? + var/gust_distance = 4 + /// How long to animate for before we start? + var/windup_time = 1.2 SECONDS + /// Minimum amount of stun time following use of wing buffet + var/minimum_endlag = 4 DECISECONDS + /// Amount of extra time to stay stunned after the end of the ability + var/additional_endlag = 0 DECISECONDS + /// Amount of time to add to endlag after each successful use of the ability + var/endlag_per_activation = DEFAULT_ACTIVATED_ENDLAG + /// How much accumulated stun time do we subtract every second? Takes a full minute to regen off a single use :( + var/endlag_decay_per_second = DEFAULT_ACTIVATED_ENDLAG / 60 + /// Increase the effect of our accumulated additional stun time by this much if space dragon has lost some rifts + var/exhaustion_multiplier = 5 + /// List of traits we apply while the ability is ongoing, stops us from moving around and such + var/static/list/applied_traits = list( + TRAIT_IMMOBILIZED, + TRAIT_INCAPACITATED, + TRAIT_NO_FLOATING_ANIM, + TRAIT_WING_BUFFET, + ) + +/datum/action/cooldown/mob_cooldown/wing_buffet/Grant(mob/granted_to) + . = ..() + RegisterSignal(granted_to, COMSIG_LIVING_LIFE, PROC_REF(on_life)) + +/datum/action/cooldown/mob_cooldown/wing_buffet/Remove(mob/removed_from) + . = ..() + deltimer(active_timer) + UnregisterSignal(removed_from, COMSIG_LIVING_LIFE) + removed_from.remove_traits(applied_traits + TRAIT_WING_BUFFET_TIRED, REF(src)) + +/// Decay our accumulated additional tiredness +/datum/action/cooldown/mob_cooldown/wing_buffet/proc/on_life(mob/living/liver, seconds_per_tick, times_fired) + SIGNAL_HANDLER + if (liver.stat == DEAD) + return // not so life now buddy + additional_endlag = max(0, additional_endlag - (endlag_decay_per_second * seconds_per_tick)) + +/datum/action/cooldown/mob_cooldown/wing_buffet/Activate(atom/target) + begin_sequence() + StartCooldown() + return TRUE + +/// Rise up into the air +/datum/action/cooldown/mob_cooldown/wing_buffet/proc/begin_sequence() + owner.add_traits(applied_traits, REF(src)) // No moving till we're done + owner.update_appearance(UPDATE_ICON) + animate(owner, pixel_y = 20, time = windup_time) + active_timer = addtimer(CALLBACK(src, PROC_REF(ground_pound)), windup_time, TIMER_DELETE_ME | TIMER_STOPPABLE) + +/// Slam into the ground +/datum/action/cooldown/mob_cooldown/wing_buffet/proc/ground_pound() + if (QDELETED(owner)) + return + owner.pixel_y = 0 + playsound(owner, 'sound/effects/gravhit.ogg', 100, TRUE) + for (var/mob/living/candidate in view(gust_distance, owner)) + if(candidate == owner || candidate.faction_check_mob(owner)) + continue + owner.visible_message(span_boldwarning("[candidate] is knocked back by the gust!")) + to_chat(candidate, span_userdanger("You're knocked back by the gust!")) + var/dir_to_target = get_dir(get_turf(owner), get_turf(candidate)) + var/throwtarget = get_edge_target_turf(target, dir_to_target) + candidate.safe_throw_at(throwtarget, range = 10, speed = 1, thrower = owner) + candidate.Paralyze(5 SECONDS) + + var/endlag_multiplier = HAS_TRAIT(owner, TRAIT_RIFT_FAILURE) ? exhaustion_multiplier : 1 + var/stun_time = minimum_endlag + (additional_endlag * endlag_multiplier) + additional_endlag += endlag_per_activation * endlag_multiplier // double dips, rough + ADD_TRAIT(owner, TRAIT_WING_BUFFET_TIRED, REF(src)) + owner.update_appearance(UPDATE_ICON) + active_timer = addtimer(CALLBACK(src, PROC_REF(complete_ability)), stun_time, TIMER_DELETE_ME | TIMER_STOPPABLE) + +/datum/action/cooldown/mob_cooldown/wing_buffet/proc/complete_ability() + owner.remove_traits(applied_traits + TRAIT_WING_BUFFET_TIRED, REF(src)) + owner.update_appearance(UPDATE_ICON) + +#undef DEFAULT_ACTIVATED_ENDLAG diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm new file mode 100644 index 00000000000..50c54da6c9b --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm @@ -0,0 +1,230 @@ +/// You can't make a dragon darker than this, it'd be hard to see +#define REJECT_DARK_COLOUR_THRESHOLD 50 +/// Any interactions executed by the space dragon +#define DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION "space dragon interaction" + +/** + * Advanced stage of the space carp life cycle, spawned as a midround antagonist or via traitor transformation. + * Can eat corpses to heal, blow people back with its wings, and obviously as a dragon it breathes fire. It can even tear through walls. + * The midround even version also creates rifts which summon carp, and heals when near them. + */ +/mob/living/basic/space_dragon + name = "Space Dragon" + desc = "A serpentine leviathan whose flight defies all modern understanding of physics. Said to be the ultimate stage in the life cycle of the Space Carp." + icon = 'icons/mob/nonhuman-player/spacedragon.dmi' + icon_state = "spacedragon" + icon_living = "spacedragon" + icon_dead = "spacedragon_dead" + health_doll_icon = "spacedragon" + faction = list(FACTION_CARP) + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + gender = NEUTER + maxHealth = 400 + health = 400 + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 + unsuitable_atmos_damage = 0 + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0.5, OXY = 1) + combat_mode = TRUE + speed = 0 + movement_type = FLYING + attack_verb_continuous = "chomps" + attack_verb_simple = "chomp" + attack_sound = 'sound/magic/demon_attack1.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + obj_damage = 50 + melee_damage_upper = 35 + melee_damage_lower = 35 + melee_attack_cooldown = CLICK_CD_MELEE + mob_size = MOB_SIZE_LARGE + armour_penetration = 30 + pixel_x = -16 + base_pixel_x = -16 + maptext_height = 64 + maptext_width = 64 + mouse_opacity = MOUSE_OPACITY_ICON + death_sound = 'sound/creatures/space_dragon_roar.ogg' + death_message = "screeches in agony as it collapses to the floor, its life extinguished." + butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30) + + /// The colour of the space dragon + var/chosen_colour + /// Minimum devastation damage dealt coefficient based on max health + var/devastation_damage_min_percentage = 0.4 + /// Maximum devastation damage dealt coefficient based on max health + var/devastation_damage_max_percentage = 0.75 + /// Our fire breath action + var/datum/action/cooldown/mob_cooldown/fire_breath/carp/fire_breath + /// Our wing flap action + var/datum/action/cooldown/mob_cooldown/wing_buffet/buffet + +/mob/living/basic/space_dragon/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_SPACEWALK, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_NO_FLOATING_ANIM, TRAIT_HEALS_FROM_CARP_RIFTS), INNATE_TRAIT) + AddElement(/datum/element/simple_flying) + AddElement(/datum/element/content_barfer) + AddElement(/datum/element/wall_tearer, do_after_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION) + AddElement(/datum/element/door_pryer, pry_time = 4 SECONDS, interaction_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION) + AddComponent(/datum/component/seethrough_mob) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + RegisterSignal(src, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_changed)) + RegisterSignal(src, COMSIG_ATOM_PRE_EX_ACT, PROC_REF(on_exploded)) + + fire_breath = new(src) + fire_breath.Grant(src) + + buffet = new(src) + buffet.Grant(src) + +/mob/living/basic/space_dragon/Destroy() + QDEL_NULL(fire_breath) + QDEL_NULL(buffet) + return ..() + +/mob/living/basic/space_dragon/Login() + . = ..() + if(!isnull(chosen_colour)) + return + rename_dragon() + select_colour() + +/// Allows the space dragon to pick a funny name +/mob/living/basic/space_dragon/proc/rename_dragon() + var/chosen_name = sanitize_name(reject_bad_text(tgui_input_text(src, "What would you like your name to be?", "Choose Your Name", real_name, MAX_NAME_LEN))) + if(!chosen_name) // Null or empty or rejected + to_chat(src, span_warning("Not a valid name, please try again.")) + rename_dragon() + return + to_chat(src, span_notice("Your name is now [span_name("[chosen_name]")], the feared Space Dragon.")) + fully_replace_character_name(null, chosen_name) + +/// Select scale colour with the colour picker +/mob/living/basic/space_dragon/proc/select_colour() + chosen_colour = input(src, "What colour would you like to be?" ,"Colour Selection", COLOR_WHITE) as color|null + if(!chosen_colour) // Redo proc until we get a color + to_chat(src, span_warning("Not a valid colour, please try again.")) + select_colour() + return + var/temp_hsv = RGBtoHSV(chosen_colour) + if(ReadHSV(temp_hsv)[3] < REJECT_DARK_COLOUR_THRESHOLD) + to_chat(src, span_danger("Invalid colour. Your colour is not bright enough.")) + select_colour() + return + add_atom_colour(chosen_colour, FIXED_COLOUR_PRIORITY) + update_appearance(UPDATE_OVERLAYS) + +/mob/living/basic/space_dragon/update_icon_state() + . = ..() + if (stat == DEAD) + return + if (!HAS_TRAIT(src, TRAIT_WING_BUFFET)) + icon_state = icon_living + return + if (HAS_TRAIT(src, TRAIT_WING_BUFFET_TIRED)) + icon_state = "spacedragon_gust_2" + return + icon_state = "spacedragon_gust" + +/mob/living/basic/space_dragon/update_overlays() + . = ..() + var/overlay_state = "overlay_base" + if (stat == DEAD) + overlay_state = "overlay_dead" + else if (HAS_TRAIT(src, TRAIT_WING_BUFFET_TIRED)) + overlay_state = "overlay_gust_2" + else if (HAS_TRAIT(src, TRAIT_WING_BUFFET)) + overlay_state = "overlay_gust" + + var/mutable_appearance/overlay = mutable_appearance(icon, overlay_state) + overlay.appearance_flags = RESET_COLOR + . += overlay + +/mob/living/basic/space_dragon/melee_attack(obj/vehicle/sealed/mecha/target, list/modifiers, ignore_cooldown) + . = ..() + if (!. || !ismecha(target)) + return + target.take_damage(obj_damage, BRUTE, MELEE) + +/// Before we attack something, check if we want to do something else instead +/mob/living/basic/space_dragon/proc/pre_attack(mob/living/source, atom/target) + SIGNAL_HANDLER + if (target == src) + return COMPONENT_HOSTILE_NO_ATTACK // Easy to misclick yourself, let's not + if (DOING_INTERACTION(source, DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)) + balloon_alert(source, "busy!") + return COMPONENT_HOSTILE_NO_ATTACK + if (!isliving(target)) + return + var/mob/living/living_target = target + if (living_target.stat != DEAD) + return + INVOKE_ASYNC(src, PROC_REF(try_eat), living_target) + return COMPONENT_HOSTILE_NO_ATTACK + +/// Try putting something inside us +/mob/living/basic/space_dragon/proc/try_eat(mob/living/food) + balloon_alert(src, "swallowing...") + if (do_after(src, 3 SECONDS, target = food)) + eat(food) + +/// Succeed in putting something inside us +/mob/living/basic/space_dragon/proc/eat(mob/living/food) + adjust_health(-food.maxHealth * 0.25) + if (QDELETED(food) || food.loc == src) + return FALSE + playsound(src, 'sound/magic/demon_attack1.ogg', 60, TRUE) + visible_message(span_boldwarning("[src] swallows [food] whole!")) + food.extinguish_mob() // It's wet in there, and our food is likely to be on fire. Let's be decent and not husk them. + food.forceMove(src) + return TRUE + +/mob/living/basic/space_dragon/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if (isliving(arrived)) + RegisterSignal(arrived, COMSIG_MOB_STATCHANGE, PROC_REF(eaten_stat_changed)) + +/mob/living/basic/space_dragon/Exited(atom/movable/gone, direction) + . = ..() + if (isliving(gone)) + UnregisterSignal(gone, COMSIG_MOB_STATCHANGE) + +/// Release consumed mobs if they transition from dead to alive +/mob/living/basic/space_dragon/proc/eaten_stat_changed(mob/living/eaten) + SIGNAL_HANDLER + if (eaten.stat == DEAD) + return + new /obj/effect/decal/cleanable/vomit(loc) + playsound(src, 'sound/effects/splat.ogg', vol = 50, vary = TRUE) + visible_message(span_danger("[src] vomits up [eaten]!")) + eaten.forceMove(loc) + eaten.Paralyze(5 SECONDS) + +/mob/living/basic/space_dragon/RangedAttack(atom/target, modifiers) + fire_breath.Trigger(target = target) + +/mob/living/basic/space_dragon/ranged_secondary_attack(atom/target, modifiers) + buffet.Trigger() + +/// When our stat changes, make sure we are using the correct overlay +/mob/living/basic/space_dragon/proc/on_stat_changed() + SIGNAL_HANDLER + update_appearance(UPDATE_OVERLAYS) + +/// We take devastating bomb damage as a random percentage of our maximum health instead of being gibbed +/mob/living/basic/space_dragon/proc/on_exploded(mob/living/source, severity, target, origin) + SIGNAL_HANDLER + if (severity != EXPLODE_DEVASTATE) + return + var/damage_coefficient = rand(devastation_damage_min_percentage, devastation_damage_max_percentage) + adjustBruteLoss(initial(maxHealth)*damage_coefficient) + return COMPONENT_CANCEL_EX_ACT // we handled it + +/// Subtype used by the midround/event +/mob/living/basic/space_dragon/spawn_with_antag + +/mob/living/basic/space_dragon/spawn_with_antag/mind_initialize() + . = ..() + mind.add_antag_datum(/datum/antagonist/space_dragon) + +#undef REJECT_DARK_COLOUR_THRESHOLD +#undef DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm index 28a543be3ac..01477cd8435 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm @@ -5,7 +5,6 @@ button_icon_state = "lay_eggs" background_icon_state = "bg_alien" overlay_icon_state = "bg_alien_border" - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED cooldown_time = 0 melee_cooldown_time = 0 shared_cooldown = NONE diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm index b7062a2dc17..d4f587c9386 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm @@ -6,7 +6,6 @@ button_icon_state = "lay_web" background_icon_state = "bg_alien" overlay_icon_state = "bg_alien_border" - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED cooldown_time = 0 SECONDS melee_cooldown_time = 0 shared_cooldown = NONE diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm index 536f09cef8d..e7771f075a8 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm @@ -8,7 +8,6 @@ overlay_icon_state = "bg_alien_border" button_icon = 'icons/mob/actions/actions_animal.dmi' button_icon_state = "wrap_0" - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED click_to_activate = TRUE ranged_mousepointer = 'icons/effects/mouse_pointers/wrap_target.dmi' shared_cooldown = NONE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index f516a52525c..ea77687bc1c 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -1,7 +1,3 @@ -///used whenever the drake generates a hotspot -#define DRAKE_FIRE_TEMP 500 -///used whenever the drake generates a hotspot -#define DRAKE_FIRE_EXPOSURE 50 ///used to see if the drake is enraged or not #define DRAKE_ENRAGED (health < maxHealth*0.5) @@ -175,36 +171,6 @@ remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) set_light_range(initial(light_range)) -//fire line keeps going even if dragon is deleted -/proc/dragon_fire_line(atom/source, list/turfs, frozen = FALSE) - var/list/hit_list = list() - for(var/turf/T in turfs) - if(isclosedturf(T)) - break - var/obj/effect/hotspot/drake_fire_hotspot = new /obj/effect/hotspot(T) - if(frozen) - drake_fire_hotspot.add_atom_colour(COLOR_BLUE_LIGHT, FIXED_COLOUR_PRIORITY) - T.hotspot_expose(DRAKE_FIRE_TEMP,DRAKE_FIRE_EXPOSURE,1) - for(var/mob/living/L in T.contents) - if(L in hit_list || istype(L, source.type)) - continue - hit_list += L - if(!frozen) - L.adjustFireLoss(20) - to_chat(L, span_userdanger("You're hit by [source]'s fire breath!")) - continue - L.adjustFireLoss(10) - L.apply_status_effect(/datum/status_effect/ice_block_talisman, 20) - to_chat(L, span_userdanger("You're hit by [source]'s freezing breath!")) - - // deals damage to mechs - for(var/obj/vehicle/sealed/mecha/M in T.contents) - if(M in hit_list) - continue - hit_list += M - M.take_damage(45, BRUTE, MELEE, 1) - sleep(0.15 SECONDS) - /mob/living/simple_animal/hostile/megafauna/dragon/ex_act(severity, target) if(severity <= EXPLODE_LIGHT) return FALSE @@ -371,8 +337,5 @@ return #undef DRAKE_ENRAGED -#undef DRAKE_FIRE_EXPOSURE -#undef DRAKE_FIRE_TEMP - #undef SWOOP_DAMAGEABLE #undef SWOOP_INVULNERABLE diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm deleted file mode 100644 index c536da22cd1..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ /dev/null @@ -1,444 +0,0 @@ -/// The darkness threshold for space dragon when choosing a color -#define DARKNESS_THRESHOLD 50 -/// Any interactions executed by the space dragon -#define DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION "space dragon interaction" - -/** - * # Space Dragon - * - * A space-faring leviathan-esque monster which breathes fire and summons carp. Spawned during its respective midround antagonist event. - * - * A space-faring monstrosity who has the ability to breathe dangerous fire breath and uses its powerful wings to knock foes away. - * Normally spawned as an antagonist during the Space Dragon event, Space Dragon's main goal is to open three rifts from which to pull a great tide of carp onto the station. - * Space Dragon can summon only one rift at a time, and can do so anywhere a blob is allowed to spawn. In order to trigger his victory condition, Space Dragon must summon and defend three rifts while they charge. - * Space Dragon, when spawned, has five minutes to summon the first rift. Failing to do so will cause Space Dragon to return from whence he came. - * When the rift spawns, ghosts can interact with it to spawn in as space carp to help complete the mission. One carp is granted when the rift is first summoned, with an extra one every 30 seconds. - * Once the victory condition is met, all current rifts become invulnerable to damage, are allowed to spawn infinite sentient space carp, and Space Dragon gets unlimited rage. - * Alternatively, if the shuttle arrives while Space Dragon is still active, their victory condition will automatically be met and all the rifts will immediately become fully charged. - * If a charging rift is destroyed, Space Dragon will be incredibly slowed, and the endlag on his gust attack is greatly increased on each use. - * Space Dragon has the following abilities to assist him with his objective: - * - Can shoot fire in straight line, dealing 30 burn damage and setting those suseptible on fire. - * - Can use his wings to temporarily stun and knock back any nearby mobs. This attack has no cooldown, but instead has endlag after the attack where Space Dragon cannot act. This endlag's time decreases over time, but is added to every time he uses the move. - * - Can swallow mob corpses to heal for half their max health. Any corpses swallowed are stored within him, and will be regurgitated on death. - * - Can tear through any type of wall. This takes 4 seconds for most walls, and 12 seconds for reinforced walls. - */ -/mob/living/simple_animal/hostile/space_dragon - name = "Space Dragon" - desc = "A vile, leviathan-esque creature that flies in the most unnatural way. Looks slightly similar to a space carp." - gender = NEUTER - maxHealth = 400 - health = 400 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0.5, OXY = 1) - combat_mode = TRUE - speed = 0 - movement_type = FLYING - attack_verb_continuous = "chomps" - attack_verb_simple = "chomp" - attack_sound = 'sound/magic/demon_attack1.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE - death_sound = 'sound/creatures/space_dragon_roar.ogg' - icon = 'icons/mob/nonhuman-player/spacedragon.dmi' - icon_state = "spacedragon" - icon_living = "spacedragon" - icon_dead = "spacedragon_dead" - health_doll_icon = "spacedragon" - obj_damage = 50 - environment_smash = ENVIRONMENT_SMASH_NONE - flags_1 = PREVENT_CONTENTS_EXPLOSION_1 - melee_damage_upper = 35 - melee_damage_lower = 35 - mob_size = MOB_SIZE_LARGE - armour_penetration = 30 - pixel_x = -16 - base_pixel_x = -16 - maptext_height = 64 - maptext_width = 64 - turns_per_move = 5 - ranged = TRUE - mouse_opacity = MOUSE_OPACITY_ICON - butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30) - death_message = "screeches as its wings turn to dust and it collapses on the floor, its life extinguished." - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = 1500 - faction = list(FACTION_CARP) - pressure_resistance = 200 - /// How much endlag using Wing Gust should apply. Each use of wing gust increments this, and it decreases over time. - var/tiredness = 0 - /// A multiplier to how much each use of wing gust should add to the tiredness variable. Set to 5 if the current rift is destroyed. - var/tiredness_mult = 1 - /// The distance Space Dragon's gust reaches - var/gust_distance = 4 - /// The amount of tiredness to add to Space Dragon per use of gust - var/gust_tiredness = 30 - /// Determines whether or not Space Dragon is in the middle of using wing gust. If set to true, prevents him from moving and doing certain actions. - var/using_special = FALSE - /// The color of the space dragon. - var/chosen_color - /// Minimum devastation damage dealt coefficient based on max health - var/devastation_damage_min_percentage = 0.4 - /// Maximum devastation damage dealt coefficient based on max health - var/devastation_damage_max_percentage = 0.75 - -/mob/living/simple_animal/hostile/space_dragon/Initialize(mapload) - . = ..() - AddComponent(/datum/component/seethrough_mob) - AddElement(/datum/element/simple_flying) - add_traits(list(TRAIT_SPACEWALK, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_NO_FLOATING_ANIM, TRAIT_HEALS_FROM_CARP_RIFTS), INNATE_TRAIT) - AddElement(/datum/element/content_barfer) - RegisterSignal(src, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(before_attack)) - -/mob/living/simple_animal/hostile/space_dragon/Login() - . = ..() - if(!chosen_color) - dragon_name() - color_selection() - -/mob/living/simple_animal/hostile/space_dragon/ex_act_devastate() - var/damage_coefficient = rand(devastation_damage_min_percentage, devastation_damage_max_percentage) - adjustBruteLoss(initial(maxHealth)*damage_coefficient) - -/mob/living/simple_animal/hostile/space_dragon/Life(seconds_per_tick = SSMOBS_DT, times_fired) - . = ..() - tiredness = max(tiredness - (0.5 * seconds_per_tick), 0) - -/mob/living/simple_animal/hostile/space_dragon/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) - . = ..() - if (isliving(arrived)) - RegisterSignal(arrived, COMSIG_MOB_STATCHANGE, PROC_REF(eaten_stat_changed)) - -/mob/living/simple_animal/hostile/space_dragon/Exited(atom/movable/gone, direction) - . = ..() - if (isliving(gone)) - UnregisterSignal(gone, COMSIG_MOB_STATCHANGE) - -/// Release consumed mobs if they transition from dead to alive -/mob/living/simple_animal/hostile/space_dragon/proc/eaten_stat_changed(mob/living/eaten) - SIGNAL_HANDLER - if (eaten.stat == DEAD) - return - playsound(src, 'sound/effects/splat.ogg', vol = 50, vary = TRUE) - visible_message(span_danger("[src] vomits up [eaten]!")) - eaten.forceMove(loc) - eaten.Paralyze(5 SECONDS) - -/mob/living/simple_animal/hostile/space_dragon/proc/before_attack(datum/source, atom/target) - SIGNAL_HANDLER - if(using_special) - return COMPONENT_CANCEL_ATTACK_CHAIN - - if(target == src) - to_chat(src, span_warning("You almost bite yourself, but then decide against it.")) - return COMPONENT_CANCEL_ATTACK_CHAIN - - if(DOING_INTERACTION(src, DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)) // patience grasshopper - target.balloon_alert(src, "finish current action first!") - return COMPONENT_CANCEL_ATTACK_CHAIN - - if(ismecha(target)) - target.take_damage(50, BRUTE, MELEE, 1) - return // don't block the rest of the attack chain - - if(iswallturf(target)) - INVOKE_ASYNC(src, PROC_REF(tear_down_wall), target) - return COMPONENT_CANCEL_ATTACK_CHAIN - - if(isliving(target)) //Swallows corpses like a snake to regain health. - var/mob/living/living_target = target - if(living_target.stat != DEAD) - return // go ham on slapping the shit out of them buddy - - INVOKE_ASYNC(src, PROC_REF(eat_this_corpse), living_target) - return COMPONENT_CANCEL_ATTACK_CHAIN - -/// Handles tearing down a wall. Returns TRUE if successful, FALSE otherwise. -/mob/living/simple_animal/hostile/space_dragon/proc/tear_down_wall(turf/closed/wall/wall_target) - to_chat(src, span_warning("You begin tearing through the wall...")) - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) - - var/time_to_tear = 4 SECONDS - if(istype(wall_target, /turf/closed/wall/r_wall)) - time_to_tear = 12 SECONDS - - if(!do_after(src, time_to_tear, target = wall_target, interaction_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)) - return FALSE - - if(isopenturf(wall_target)) - return FALSE // well the thing was destroyed while we were sleeping so that's nice, but we didn't successfully tear it down. whatever - - wall_target.dismantle_wall(devastated = TRUE) - playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) - return TRUE - -/// Handles eating a corpse, giving us a bit of health back. Returns TRUE if we were sucessful in eating, FALSE otherwise. -/mob/living/simple_animal/hostile/space_dragon/proc/eat_this_corpse(mob/living/corpse) - to_chat(src, span_warning("You begin to swallow the body of [corpse] whole...")) - - if(!do_after(src, 3 SECONDS, target = corpse, interaction_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)) - return FALSE - if(!eat(corpse)) - return FALSE - - adjustHealth(-(corpse.maxHealth * 0.25)) - return TRUE - -/mob/living/simple_animal/hostile/space_dragon/ranged_secondary_attack(atom/target, modifiers) - if(using_special) - return - using_special = TRUE - icon_state = "spacedragon_gust" - add_dragon_overlay() - useGust(0) - -/mob/living/simple_animal/hostile/space_dragon/Move() - if(!using_special) - ..() - -/mob/living/simple_animal/hostile/space_dragon/OpenFire() - if(using_special) - return - ranged_cooldown = world.time + ranged_cooldown_time - fire_stream() - -/mob/living/simple_animal/hostile/space_dragon/death(gibbed) - . = ..() - add_dragon_overlay() - -/mob/living/simple_animal/hostile/space_dragon/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE) - . = ..() - add_dragon_overlay() - -/** - * Allows space dragon to choose its own name. - * - * Prompts the space dragon to choose a name, which it will then apply to itself. - * If the name is invalid, will re-prompt the dragon until a proper name is chosen. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/dragon_name() - var/chosen_name = sanitize_name(reject_bad_text(tgui_input_text(src, "What would you like your name to be?", "Choose Your Name", real_name, MAX_NAME_LEN))) - if(!chosen_name) - to_chat(src, span_warning("Not a valid name, please try again.")) - dragon_name() - return - to_chat(src, span_notice("Your name is now [span_name("[chosen_name]")], the feared Space Dragon.")) - fully_replace_character_name(null, chosen_name) - -/** - * Allows space dragon to choose a color for itself. - * - * Prompts the space dragon to choose a color, from which it will then apply to itself. - * If an invalid color is given, will re-prompt the dragon until a proper color is chosen. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/color_selection() - chosen_color = input(src,"What would you like your color to be?","Choose Your Color", COLOR_WHITE) as color|null - if(!chosen_color) //redo proc until we get a color - to_chat(src, span_warning("Not a valid color, please try again.")) - color_selection() - return - var/temp_hsv = RGBtoHSV(chosen_color) - if(ReadHSV(temp_hsv)[3] < DARKNESS_THRESHOLD) - to_chat(src, span_danger("Invalid color. Your color is not bright enough.")) - color_selection() - return - add_atom_colour(chosen_color, FIXED_COLOUR_PRIORITY) - add_dragon_overlay() - -/** - * Adds the proper overlay to the space dragon. - * - * Clears the current overlay on space dragon and adds a proper one for whatever animation he's in. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/add_dragon_overlay() - cut_overlays() - if(stat == DEAD) - var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_dead") - overlay.appearance_flags = RESET_COLOR - add_overlay(overlay) - return - if(!using_special) - var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_base") - overlay.appearance_flags = RESET_COLOR - add_overlay(overlay) - return - if(using_special) - var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_gust") - overlay.appearance_flags = RESET_COLOR - add_overlay(overlay) - -/** - * Determines a line of turfs from sources's position to the target with length range. - * - * Determines a line of turfs from the source's position to the target with length range. - * The line will extend on past the target if the range is large enough, and not reach the target if range is small enough. - * Arguments: - * * offset - whether or not to aim slightly to the left or right of the target - * * range - how many turfs should we go out for - * * atom/at - The target - */ -/mob/living/simple_animal/hostile/space_dragon/proc/line_target(offset, range, atom/at = target) - if(!at) - return - var/angle = ATAN2(at.x - src.x, at.y - src.y) + offset - var/turf/T = get_turf(src) - for(var/i in 1 to range) - var/turf/check = locate(src.x + cos(angle) * i, src.y + sin(angle) * i, src.z) - if(!check) - break - T = check - return (get_line(src, T) - get_turf(src)) - -/** - * Spawns fire at each position in a line from the source to the target. - * - * Spawns fire at each position in a line from the source to the target. - * Stops if it comes into contact with a solid wall, a window, or a door. - * Delays the spawning of each fire by 1.5 deciseconds. - * Arguments: - * * atom/at - The target - */ -/mob/living/simple_animal/hostile/space_dragon/proc/fire_stream(atom/at = target) - playsound(get_turf(src),'sound/magic/fireball.ogg', 200, TRUE) - var/range = 20 - var/list/turfs = list() - var/list/hit_list_parameter = list(src) - turfs = line_target(0, range, at) - var/delayFire = -1.0 - for(var/turf/T in turfs) - if(isclosedturf(T)) - return - for(var/obj/structure/window/W in T.contents) - return - for(var/obj/machinery/door/D in T.contents) - if(D.density) - return - delayFire += 1.0 - addtimer(CALLBACK(src, PROC_REF(dragon_fire_line), T, hit_list_parameter), delayFire) - -/** - * What occurs on each tile to actually create the fire. - * - * Creates a fire on the given turf. - * It creates a hotspot on the given turf, damages any living mob with 30 burn damage, and damages mechs by 50. - * It can only hit any given target once. - * Arguments: - * * turf/T - The turf to trigger the effects on. - * * list/hit_list - The list of targets that have already been hit in the fire_stream. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/dragon_fire_line(turf/fire_turf, list/hit_list) - new /obj/effect/hotspot(fire_turf) - fire_turf.hotspot_expose(700,50,1) - for(var/mob/living/living_target in fire_turf.contents) - if(living_target.faction_check_mob(src) && living_target != src) - hit_list += living_target - start_carp_speedboost(living_target) - if(living_target in hit_list) - continue - if(living_target.mind?.has_antag_datum(/datum/antagonist/space_carp)) - continue - hit_list += living_target - living_target.adjustFireLoss(30) - to_chat(living_target, span_userdanger("You're hit by [src]'s fire breath!")) - // deals damage to mechs - for(var/obj/vehicle/sealed/mecha/mech_target in fire_turf.contents) - if(mech_target in hit_list) - continue - hit_list += mech_target - mech_target.take_damage(50, BRUTE, MELEE, 1) - -/** - * Applies the speed boost to carps when hit by space dragon's flame breath - * - * Applies the dragon rage effect to carps temporarily, giving them a glow and a speed boost. - * This lasts for 8 seconds. - * Arguments: - * * mob/living/target - The carp being affected. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/start_carp_speedboost(mob/living/target) - target.add_filter("anger_glow", 3, list("type" = "outline", "color" = "#ff330030", "size" = 2)) - target.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) - addtimer(CALLBACK(src, PROC_REF(end_carp_speedboost), target), 8 SECONDS) - -/** - * Remove the speed boost from carps when hit by space dragon's flame breath - * - * Removes the dragon rage effect from carps, removing their glow and speed boost. - * Arguments: - * * mob/living/target - The carp being affected. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/end_carp_speedboost(mob/living/target) - target.remove_filter("anger_glow") - target.remove_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) - -/** - * Handles consuming and storing consumed things inside Space Dragon - * - * Plays a sound and then stores the consumed thing inside Space Dragon. - * Used in AttackingTarget(), paired with a heal should it succeed. - * Arguments: - * * atom/movable/A - The thing being consumed - */ -/mob/living/simple_animal/hostile/space_dragon/proc/eat(atom/movable/A) - if(A && A.loc != src) - playsound(src, 'sound/magic/demon_attack1.ogg', 60, TRUE) - visible_message(span_warning("[src] swallows [A] whole!")) - to_chat(src, span_notice("Your acids cleanse the flames off [A] on the way down. Delicious!")) - A.extinguish() - A.forceMove(src) - return TRUE - return FALSE - -/** - * Resets Space Dragon's status after using wing gust. - * - * Resets Space Dragon's status after using wing gust. - * If it isn't dead by the time it calls this method, reset the sprite back to the normal living sprite. - * Also sets the using_special variable to FALSE, allowing Space Dragon to move and attack freely again. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/reset_status() - if(stat != DEAD) - icon_state = "spacedragon" - using_special = FALSE - add_dragon_overlay() - -/** - * Handles wing gust from the windup all the way to the endlag at the end. - * - * Handles the wing gust attack from start to finish, based on the timer. - * When intially triggered, starts at 0. Until the timer reaches 10, increase Space Dragon's y position by 2 and call back to the function in 1.5 deciseconds. - * When the timer is at 10, trigger the attack. Change Space Dragon's sprite. reset his y position, and push all living creatures back in a 3 tile radius and stun them for 5 seconds. - * Stay in the ending state for how much our tiredness dictates and add to our tiredness. - * Arguments: - * * timer - The timer used for the windup. - */ -/mob/living/simple_animal/hostile/space_dragon/proc/useGust(timer) - if(timer != 10) - pixel_y = pixel_y + 2; - addtimer(CALLBACK(src, PROC_REF(useGust), timer + 1), 1.2) - return - pixel_y = 0 - icon_state = "spacedragon_gust_2" - cut_overlays() - var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_gust_2") - overlay.appearance_flags = RESET_COLOR - add_overlay(overlay) - playsound(src, 'sound/effects/gravhit.ogg', 100, TRUE) - for (var/mob/living/candidate in view(gust_distance, src)) - if(candidate == src || candidate.faction_check_mob(src)) - continue - visible_message(span_boldwarning("[candidate] is knocked back by the gust!")) - to_chat(candidate, span_userdanger("You're knocked back by the gust!")) - var/dir_to_target = get_dir(get_turf(src), get_turf(candidate)) - var/throwtarget = get_edge_target_turf(target, dir_to_target) - candidate.safe_throw_at(throwtarget, 10, 1, src) - candidate.Paralyze(50) - addtimer(CALLBACK(src, PROC_REF(reset_status)), 4 + ((tiredness * tiredness_mult) / 10)) - tiredness = tiredness + (gust_tiredness * tiredness_mult) - -/mob/living/simple_animal/hostile/space_dragon/spawn_with_antag - -/mob/living/simple_animal/hostile/space_dragon/spawn_with_antag/mind_initialize() - . = ..() - mind.add_antag_datum(/datum/antagonist/space_dragon) - -#undef DARKNESS_THRESHOLD -#undef DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm index 45a0a520fa0..56be6864b54 100644 --- a/code/modules/research/server.dm +++ b/code/modules/research/server.dm @@ -154,7 +154,7 @@ if(HDD_OVERLOADED) . += "The front panel is dangling open. The hdd inside is destroyed and the wires are all burned." -/obj/machinery/rnd/server/master/tool_act(mob/living/user, obj/item/tool, tool_type) +/obj/machinery/rnd/server/master/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking) // Only antags are given the training and knowledge to disassemble this thing. if(is_special_character(user)) return ..() diff --git a/code/modules/unit_tests/dragon_expiration.dm b/code/modules/unit_tests/dragon_expiration.dm index 7b36b576291..45262dc9d60 100644 --- a/code/modules/unit_tests/dragon_expiration.dm +++ b/code/modules/unit_tests/dragon_expiration.dm @@ -2,9 +2,12 @@ /datum/unit_test/contents_barfer /datum/unit_test/contents_barfer/Run() - var/mob/living/simple_animal/hostile/space_dragon/dragon_time = allocate(/mob/living/simple_animal/hostile/space_dragon) + var/mob/living/basic/space_dragon/dragon_time = allocate(/mob/living/basic/space_dragon) var/mob/living/carbon/human/to_be_consumed = allocate(/mob/living/carbon/human/consistent) + to_be_consumed.adjust_fire_stacks(5) + to_be_consumed.ignite_mob() TEST_ASSERT(dragon_time.eat(to_be_consumed), "The space dragon failed to consume the dummy!") + TEST_ASSERT(!to_be_consumed.has_status_effect(/datum/status_effect/fire_handler/fire_stacks), "The space dragon failed to extinguish the dummy!") TEST_ASSERT_EQUAL(to_be_consumed.loc, dragon_time, "The dummy's location, after being successfuly consumed, was not within the space dragon's contents!") dragon_time.death() TEST_ASSERT(isturf(to_be_consumed.loc), "After dying, the space dragon did not eject the consumed dummy content barfer element.") @@ -13,7 +16,7 @@ /datum/unit_test/space_dragon_expiration /datum/unit_test/space_dragon_expiration/Run() - var/mob/living/simple_animal/hostile/space_dragon/dragon_time = allocate(/mob/living/simple_animal/hostile/space_dragon) + var/mob/living/basic/space_dragon/dragon_time = allocate(/mob/living/basic/space_dragon) var/mob/living/carbon/human/to_be_consumed = allocate(/mob/living/carbon/human/consistent) dragon_time.mind_initialize() diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index a4ef1a1a282..6b233b30799 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -163,8 +163,6 @@ /mob/living/simple_animal/hostile/skeleton/plasmaminer, /mob/living/simple_animal/hostile/skeleton/plasmaminer/jackhammer, /mob/living/simple_animal/hostile/skeleton/templar, - /mob/living/simple_animal/hostile/space_dragon, - /mob/living/simple_animal/hostile/space_dragon/spawn_with_antag, /mob/living/simple_animal/hostile/vatbeast, /mob/living/simple_animal/hostile/wizard, /mob/living/simple_animal/hostile/zombie, diff --git a/tgstation.dme b/tgstation.dme index bb2da82fed3..366143b7aa0 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -138,6 +138,7 @@ #include "code\__DEFINES\MC.dm" #include "code\__DEFINES\mecha.dm" #include "code\__DEFINES\medical.dm" +#include "code\__DEFINES\megafauna.dm" #include "code\__DEFINES\melee.dm" #include "code\__DEFINES\memory_defines.dm" #include "code\__DEFINES\mergers.dm" @@ -1056,7 +1057,6 @@ #include "code\datums\components\phylactery.dm" #include "code\datums\components\pinata.dm" #include "code\datums\components\pricetag.dm" -#include "code\datums\components\pry_open_door.dm" #include "code\datums\components\punchcooldown.dm" #include "code\datums\components\puzzgrid.dm" #include "code\datums\components\radiation_countdown.dm" @@ -1127,6 +1127,7 @@ #include "code\datums\components\tippable.dm" #include "code\datums\components\toggle_attached_clothing.dm" #include "code\datums\components\toggle_suit.dm" +#include "code\datums\components\torn_wall.dm" #include "code\datums\components\transforming.dm" #include "code\datums\components\trapdoor.dm" #include "code\datums\components\tree_climber.dm" @@ -1303,6 +1304,7 @@ #include "code\datums\elements\dextrous.dm" #include "code\datums\elements\diggable.dm" #include "code\datums\elements\digitalcamo.dm" +#include "code\datums\elements\door_pryer.dm" #include "code\datums\elements\drag_pickup.dm" #include "code\datums\elements\dryable.dm" #include "code\datums\elements\earhealing.dm" @@ -1390,6 +1392,7 @@ #include "code\datums\elements\waddling.dm" #include "code\datums\elements\wall_engraver.dm" #include "code\datums\elements\wall_smasher.dm" +#include "code\datums\elements\wall_tearer.dm" #include "code\datums\elements\wall_walker.dm" #include "code\datums\elements\weapon_description.dm" #include "code\datums\elements\weather_listener.dm" @@ -4530,6 +4533,9 @@ #include "code\modules\mob\living\basic\space_fauna\revenant\revenant_objectives.dm" #include "code\modules\mob\living\basic\space_fauna\snake\snake.dm" #include "code\modules\mob\living\basic\space_fauna\snake\snake_ai.dm" +#include "code\modules\mob\living\basic\space_fauna\space_dragon\dragon_breath.dm" +#include "code\modules\mob\living\basic\space_fauna\space_dragon\dragon_gust.dm" +#include "code\modules\mob\living\basic\space_fauna\space_dragon\space_dragon.dm" #include "code\modules\mob\living\basic\space_fauna\spider\spider.dm" #include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_ai.dm" #include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_subtrees.dm" @@ -4756,7 +4762,6 @@ #include "code\modules\mob\living\simple_animal\hostile\ooze.dm" #include "code\modules\mob\living\simple_animal\hostile\pirate.dm" #include "code\modules\mob\living\simple_animal\hostile\skeleton.dm" -#include "code\modules\mob\living\simple_animal\hostile\space_dragon.dm" #include "code\modules\mob\living\simple_animal\hostile\vatbeast.dm" #include "code\modules\mob\living\simple_animal\hostile\wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\zombie.dm" diff --git a/tools/UpdatePaths/Scripts/78979_simple_to_basic_space_dragon.txt b/tools/UpdatePaths/Scripts/78979_simple_to_basic_space_dragon.txt new file mode 100644 index 00000000000..31210a5ea29 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78979_simple_to_basic_space_dragon.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/space_dragon : /mob/living/basic/space_dragon/@SUBTYPES{@OLD}