diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/_jungle_mobs.dm b/code/modules/mob/living/simple_animal/hostile/jungle/_jungle_mobs.dm new file mode 100644 index 0000000000..c337344c90 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/jungle/_jungle_mobs.dm @@ -0,0 +1,17 @@ +/mob/living/simple_animal/hostile/jungle + vision_range = 5 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + faction = list("jungle") + weather_immunities = list("acid") + obj_damage = 30 + environment_smash = ENVIRONMENT_SMASH_WALLS + minbodytemp = 0 + maxbodytemp = 450 + response_help = "pokes" + response_disarm = "shoves" + response_harm = "strikes" + status_flags = NONE + a_intent = INTENT_HARM + see_in_dark = 4 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + mob_size = MOB_SIZE_LARGE diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm b/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm new file mode 100644 index 0000000000..491aca3233 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm @@ -0,0 +1,263 @@ +#define PLAYER_HOP_DELAY 25 + +//Huge, carnivorous toads that spit an immobilizing toxin at its victims before leaping onto them. +//It has no melee attack, and its damage comes from the toxin in its bubbles and its crushing leap. +//Its eyes will turn red to signal an imminent attack! +/mob/living/simple_animal/hostile/jungle/leaper + name = "leaper" + desc = "Commonly referred to as 'leapers', the Geron Toad is a massive beast that spits out highly pressurized bubbles containing a unique toxin, knocking down its prey and then crushing it with its girth." + icon = 'icons/mob/jungle/leaper.dmi' + icon_state = "leaper" + icon_living = "leaper" + icon_dead = "leaper_dead" + maxHealth = 300 + health = 300 + ranged = TRUE + projectiletype = /obj/item/projectile/leaper + projectilesound = 'sound/weapons/pierce.ogg' + ranged_cooldown_time = 30 + pixel_x = -16 + layer = LARGE_MOB_LAYER + speed = 10 + stat_attack = 1 + robust_searching = 1 + var/hopping = FALSE + var/hop_cooldown = 0 //Strictly for player controlled leapers + var/projectile_ready = FALSE //Stopping AI leapers from firing whenever they want, and only doing it after a hop has finished instead + +/obj/item/projectile/leaper + name = "leaper bubble" + icon_state = "leaper" + knockdown = 50 + damage = 0 + range = 7 + hitsound = 'sound/effects/snap.ogg' + nondirectional_sprite = TRUE + impact_effect_type = /obj/effect/temp_visual/leaper_projectile_impact + +/obj/item/projectile/leaper/on_hit(atom/target, blocked = FALSE) + ..() + if(iscarbon(target)) + var/mob/living/carbon/C = target + C.reagents.add_reagent("leaper_venom", 5) + return + if(isanimal(target)) + var/mob/living/simple_animal/L = target + L.adjustHealth(25) + +/obj/item/projectile/leaper/on_range() + var/turf/T = get_turf(src) + ..() + new /obj/structure/leaper_bubble(T) + +/obj/effect/temp_visual/leaper_projectile_impact + name = "leaper bubble" + icon = 'icons/obj/projectiles.dmi' + icon_state = "leaper_bubble_pop" + layer = ABOVE_ALL_MOB_LAYER + duration = 3 + +/obj/effect/temp_visual/leaper_projectile_impact/Initialize() + . = ..() + new /obj/effect/decal/cleanable/leaper_sludge(get_turf(src)) + +/obj/effect/decal/cleanable/leaper_sludge + name = "leaper sludge" + desc = "A small pool of sludge, containing trace amounts of leaper venom." + icon = 'icons/effects/tomatodecal.dmi' + icon_state = "tomato_floor1" + +/obj/structure/leaper_bubble + name = "leaper bubble" + desc = "A floating bubble containing leaper venom. The contents are under a surprising amount of pressure." + icon = 'icons/obj/projectiles.dmi' + icon_state = "leaper" + max_integrity = 10 + density = FALSE + +/obj/structure/leaper_bubble/Initialize() + . = ..() + float(on = TRUE) + QDEL_IN(src, 100) + +/obj/structure/leaper_bubble/Destroy() + new /obj/effect/temp_visual/leaper_projectile_impact(get_turf(src)) + playsound(src,'sound/effects/snap.ogg',50, 1, -1) + return ..() + +/obj/structure/leaper_bubble/Crossed(atom/movable/AM) + if(isliving(AM)) + var/mob/living/L = AM + if(!istype(L, /mob/living/simple_animal/hostile/jungle/leaper)) + playsound(src,'sound/effects/snap.ogg',50, 1, -1) + L.Knockdown(50) + if(iscarbon(L)) + var/mob/living/carbon/C = L + C.reagents.add_reagent("leaper_venom", 5) + if(isanimal(L)) + var/mob/living/simple_animal/A = L + A.adjustHealth(25) + qdel(src) + return ..() + +/datum/reagent/toxin/leaper_venom + name = "Leaper venom" + id = "leaper_venom" + description = "A toxin spat out by leapers that, while harmless in small doses, quickly creates a toxic reaction if too much is in the body." + color = "#801E28" // rgb: 128, 30, 40 + toxpwr = 0 + taste_description = "french cuisine" + taste_mult = 1.3 + +/datum/reagent/toxin/leaper_venom/on_mob_life(mob/living/M) + if(volume >= 10) + M.adjustToxLoss(5, 0) + ..() + +/obj/effect/temp_visual/leaper_crush + name = "grim tidings" + desc = "Incoming leaper!" + icon = 'icons/effects/96x96.dmi' + icon_state = "lily_pad" + layer = BELOW_MOB_LAYER + pixel_x = -32 + pixel_y = -32 + duration = 30 + +/mob/living/simple_animal/hostile/jungle/leaper/Initialize() + . = ..() + verbs -= /mob/living/verb/pulled + +/mob/living/simple_animal/hostile/jungle/leaper/CtrlClickOn(atom/A) + face_atom(A) + target = A + if(!isturf(loc)) + return + if(next_move > world.time) + return + if(hopping) + return + if(isliving(A)) + var/mob/living/L = A + if(L.incapacitated()) + BellyFlop() + return + if(hop_cooldown <= world.time) + Hop(player_hop = TRUE) + +/mob/living/simple_animal/hostile/jungle/leaper/AttackingTarget() + if(isliving(target)) + return + return ..() + +/mob/living/simple_animal/hostile/jungle/leaper/handle_automated_action() + if(hopping || projectile_ready) + return + . = ..() + if(target) + if(isliving(target)) + var/mob/living/L = target + if(L.incapacitated()) + BellyFlop() + return + if(!hopping) + Hop() + +/mob/living/simple_animal/hostile/jungle/leaper/Life() + . = ..() + update_icons() + +/mob/living/simple_animal/hostile/jungle/leaper/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + if(prob(33) && !ckey) + ranged_cooldown = 0 //Keeps em on their toes instead of a constant rotation + ..() + +/mob/living/simple_animal/hostile/jungle/leaper/OpenFire() + face_atom(target) + if(ranged_cooldown <= world.time) + if(ckey) + if(hopping) + return + if(isliving(target)) + var/mob/living/L = target + if(L.incapacitated()) + return //No stunlocking. Hop on them after you stun them, you donk. + if(AIStatus == AI_ON && !projectile_ready && !ckey) + return + . = ..(target) + projectile_ready = FALSE + update_icons() + +/mob/living/simple_animal/hostile/jungle/leaper/proc/Hop(player_hop = FALSE) + if(z != target.z) + return + hopping = TRUE + density = FALSE + pass_flags |= PASSMOB + notransform = TRUE + var/turf/new_turf = locate((target.x + rand(-3,3)),(target.y + rand(-3,3)),target.z) + if(player_hop) + new_turf = get_turf(target) + hop_cooldown = world.time + PLAYER_HOP_DELAY + if(AIStatus == AI_ON && ranged_cooldown <= world.time) + projectile_ready = TRUE + update_icons() + throw_at(new_turf, max(3,get_dist(src,new_turf)), 1, src, FALSE, callback = CALLBACK(src, .FinishHop)) + +/mob/living/simple_animal/hostile/jungle/leaper/proc/FinishHop() + density = TRUE + notransform = FALSE + pass_flags &= ~PASSMOB + hopping = FALSE + playsound(src.loc, 'sound/effects/meteorimpact.ogg', 100, 1) + if(target && AIStatus == AI_ON && projectile_ready && !ckey) + face_atom(target) + addtimer(CALLBACK(src, .proc/OpenFire, target), 5) + +/mob/living/simple_animal/hostile/jungle/leaper/proc/BellyFlop() + var/turf/new_turf = get_turf(target) + hopping = TRUE + notransform = TRUE + new /obj/effect/temp_visual/leaper_crush(new_turf) + addtimer(CALLBACK(src, .proc/BellyFlopHop, new_turf), 30) + +/mob/living/simple_animal/hostile/jungle/leaper/proc/BellyFlopHop(turf/T) + density = FALSE + throw_at(T, get_dist(src,T),1,src, FALSE, callback = CALLBACK(src, .proc/Crush)) + +/mob/living/simple_animal/hostile/jungle/leaper/proc/Crush() + hopping = FALSE + density = TRUE + notransform = FALSE + playsound(src, 'sound/effects/meteorimpact.ogg', 200, 1) + for(var/mob/living/L in orange(1, src)) + L.adjustBruteLoss(35) + if(!QDELETED(L)) // Some mobs are deleted on death + var/throw_dir = get_dir(src, L) + if(L.loc == loc) + throw_dir = pick(GLOB.alldirs) + var/throwtarget = get_edge_target_turf(src, throw_dir) + L.throw_at(throwtarget, 3, 1) + visible_message("[L] is thrown clear of [src]!") + if(ckey)//Lessens ability to chain stun as a player + ranged_cooldown = ranged_cooldown_time + world.time + update_icons() + +/mob/living/simple_animal/hostile/jungle/leaper/Goto() + return + +/mob/living/simple_animal/hostile/jungle/leaper/throw_impact() + return + +/mob/living/simple_animal/hostile/jungle/leaper/update_icons() + . = ..() + if(stat) + icon_state = "leaper_dead" + return + if(ranged_cooldown <= world.time) + if(AIStatus == AI_ON && projectile_ready || ckey) + icon_state = "leaper_alert" + return + icon_state = "leaper" + +#undef PLAYER_HOP_DELAY diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm b/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm new file mode 100644 index 0000000000..be51502a96 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm @@ -0,0 +1,65 @@ +//Large and powerful, but timid. It won't engage anything above 50 health, or anything without legcuffs. +//It can fire fleshy snares that legcuff anyone that it hits, making them look especially tasty to the arachnid. +/mob/living/simple_animal/hostile/jungle/mega_arachnid + name = "mega arachnid" + desc = "Though physically imposing, it prefers to ambush its prey, and it will only engage with an already crippled opponent." + icon = 'icons/mob/jungle/arachnid.dmi' + icon_state = "arachnid" + icon_living = "arachnid" + icon_dead = "arachnid_dead" + melee_damage_lower = 30 + melee_damage_upper = 30 + maxHealth = 300 + health = 300 + speed = 1 + ranged = 1 + pixel_x = -16 + move_to_delay = 10 + aggro_vision_range = 9 + speak_emote = list("chitters") + attack_sound = 'sound/weapons/bladeslice.ogg' + ranged_cooldown_time = 60 + projectiletype = /obj/item/projectile/mega_arachnid + projectilesound = 'sound/weapons/pierce.ogg' + alpha = 50 + +/mob/living/simple_animal/hostile/jungle/mega_arachnid/Life() + ..() + if(target && ranged_cooldown > world.time && iscarbon(target)) + var/mob/living/carbon/C = target + if(!C.legcuffed && C.health < 50) + retreat_distance = 9 + minimum_distance = 9 + alpha = 125 + return + retreat_distance = 0 + minimum_distance = 0 + alpha = 255 + + +/mob/living/simple_animal/hostile/jungle/mega_arachnid/Aggro() + ..() + alpha = 255 + +/mob/living/simple_animal/hostile/jungle/mega_arachnid/LoseAggro() + ..() + alpha = 50 + +/obj/item/projectile/mega_arachnid + name = "flesh snare" + nodamage = 1 + damage = 0 + icon_state = "tentacle_end" + +/obj/item/projectile/mega_arachnid/on_hit(atom/target, blocked = FALSE) + if(iscarbon(target) && blocked < 100) + var/obj/item/restraints/legcuffs/beartrap/mega_arachnid/B = new /obj/item/restraints/legcuffs/beartrap/mega_arachnid(get_turf(target)) + B.Crossed(target) + ..() + +/obj/item/restraints/legcuffs/beartrap/mega_arachnid + name = "fleshy restraints" + desc = "Used by mega arachnids to immobilize their prey." + flags_1 = DROPDEL_1 + icon_state = "tentacle_end" + icon = 'icons/obj/projectiles.dmi' diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm new file mode 100644 index 0000000000..05896c80b5 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm @@ -0,0 +1,224 @@ +#define MOOK_ATTACK_NEUTRAL 0 +#define MOOK_ATTACK_WARMUP 1 +#define MOOK_ATTACK_ACTIVE 2 +#define MOOK_ATTACK_RECOVERY 3 +#define ATTACK_INTERMISSION_TIME 5 + +//Fragile but highly aggressive wanderers that pose a large threat in numbers. +//They'll attempt to leap at their target from afar using their hatchets. +/mob/living/simple_animal/hostile/jungle/mook + name = "wanderer" + desc = "This unhealthy looking primitive is wielding a rudimentary hatchet, swinging it with wild abandon. One isn't much of a threat, but in numbers they can quickly overwhelm a superior opponent." + icon = 'icons/mob/jungle/mook.dmi' + icon_state = "mook" + icon_living = "mook" + icon_dead = "mook_dead" + pixel_x = -16 + maxHealth = 45 + health = 45 + melee_damage_lower = 30 + melee_damage_upper = 30 + pixel_y = -8 + ranged = TRUE + ranged_cooldown_time = 10 + pass_flags = LETPASSTHROW + robust_searching = TRUE + stat_attack = UNCONSCIOUS + attack_sound = 'sound/weapons/rapierhit.ogg' + death_sound = 'sound/voice/mook_death.ogg' + aggro_vision_range = 15 //A little more aggressive once in combat to balance out their really low HP + var/attack_state = MOOK_ATTACK_NEUTRAL + var/struck_target_leap = FALSE + +/mob/living/simple_animal/hostile/jungle/mook/CanPass(atom/movable/O) + if(istype(O, /mob/living/simple_animal/hostile/jungle/mook)) + var/mob/living/simple_animal/hostile/jungle/mook/M = O + if(M.attack_state == MOOK_ATTACK_ACTIVE && M.throwing) + return TRUE + return ..() + +/mob/living/simple_animal/hostile/jungle/mook/death() + desc = "A deceased primitive. Upon closer inspection, it was suffering from severe cellular degeneration and its garments are machine made..."//Can you guess the twist + return ..() + +/mob/living/simple_animal/hostile/jungle/mook/AttackingTarget() + if(isliving(target)) + if(ranged_cooldown <= world.time && attack_state == MOOK_ATTACK_NEUTRAL) + var/mob/living/L = target + if(L.incapacitated()) + WarmupAttack(forced_slash_combo = TRUE) + return + WarmupAttack() + return + return ..() + +/mob/living/simple_animal/hostile/jungle/mook/Goto() + if(attack_state != MOOK_ATTACK_NEUTRAL) + return + return ..() + +/mob/living/simple_animal/hostile/jungle/mook/Move() + if(attack_state == MOOK_ATTACK_WARMUP || attack_state == MOOK_ATTACK_RECOVERY) + return + return ..() + +/mob/living/simple_animal/hostile/jungle/mook/proc/WarmupAttack(forced_slash_combo = FALSE) + if(attack_state == MOOK_ATTACK_NEUTRAL && target) + attack_state = MOOK_ATTACK_WARMUP + walk(src,0) + update_icons() + if(prob(50) && get_dist(src,target) <= 3 || forced_slash_combo) + addtimer(CALLBACK(src, .proc/SlashCombo), ATTACK_INTERMISSION_TIME) + return + addtimer(CALLBACK(src, .proc/LeapAttack), ATTACK_INTERMISSION_TIME + rand(0,3)) + return + attack_state = MOOK_ATTACK_RECOVERY + ResetNeutral() + +/mob/living/simple_animal/hostile/jungle/mook/proc/SlashCombo() + if(attack_state == MOOK_ATTACK_WARMUP && !stat) + attack_state = MOOK_ATTACK_ACTIVE + update_icons() + SlashAttack() + addtimer(CALLBACK(src, .proc/SlashAttack), 3) + addtimer(CALLBACK(src, .proc/SlashAttack), 6) + addtimer(CALLBACK(src, .proc/AttackRecovery), 9) + +/mob/living/simple_animal/hostile/jungle/mook/proc/SlashAttack() + if(target && !stat && attack_state == MOOK_ATTACK_ACTIVE) + melee_damage_lower = 15 + melee_damage_upper = 15 + var/mob_direction = get_dir(src,target) + if(get_dist(src,target) > 1) + step(src,mob_direction) + if(targets_from && isturf(targets_from.loc) && target.Adjacent(targets_from) && isliving(target)) + var/mob/living/L = target + L.attack_animal(src) + return + var/swing_turf = get_step(src,mob_direction) + new /obj/effect/temp_visual/kinetic_blast(swing_turf) + playsound(src, 'sound/weapons/slashmiss.ogg', 50, 1) + +/mob/living/simple_animal/hostile/jungle/mook/proc/LeapAttack() + if(target && !stat && attack_state == MOOK_ATTACK_WARMUP) + attack_state = MOOK_ATTACK_ACTIVE + density = FALSE + melee_damage_lower = 30 + melee_damage_upper = 30 + update_icons() + new /obj/effect/temp_visual/mook_dust(get_turf(src)) + playsound(src, 'sound/weapons/thudswoosh.ogg', 25, 1) + playsound(src, 'sound/voice/mook_leap_yell.ogg', 100, 1) + var/target_turf = get_turf(target) + throw_at(target_turf, 7, 1, src, FALSE, callback = CALLBACK(src, .proc/AttackRecovery)) + return + attack_state = MOOK_ATTACK_RECOVERY + ResetNeutral() + +/mob/living/simple_animal/hostile/jungle/mook/proc/AttackRecovery() + if(attack_state == MOOK_ATTACK_ACTIVE && !stat) + attack_state = MOOK_ATTACK_RECOVERY + density = TRUE + face_atom(target) + if(!struck_target_leap) + update_icons() + struck_target_leap = FALSE + if(prob(40)) + attack_state = MOOK_ATTACK_NEUTRAL + if(target) + if(isliving(target)) + var/mob/living/L = target + if(L.incapacitated() && L.stat != DEAD) + addtimer(CALLBACK(src, .proc/WarmupAttack, TRUE), ATTACK_INTERMISSION_TIME) + return + addtimer(CALLBACK(src, .proc/WarmupAttack), ATTACK_INTERMISSION_TIME) + return + addtimer(CALLBACK(src, .proc/ResetNeutral), ATTACK_INTERMISSION_TIME) + +/mob/living/simple_animal/hostile/jungle/mook/proc/ResetNeutral() + if(attack_state == MOOK_ATTACK_RECOVERY) + attack_state = MOOK_ATTACK_NEUTRAL + ranged_cooldown = world.time + ranged_cooldown_time + update_icons() + if(target && !stat) + update_icons() + Goto(target, move_to_delay, minimum_distance) + +/mob/living/simple_animal/hostile/jungle/mook/throw_impact(atom/hit_atom, throwingdatum) + . = ..() + if(isliving(hit_atom) && attack_state == MOOK_ATTACK_ACTIVE) + var/mob/living/L = hit_atom + if(CanAttack(L)) + L.attack_animal(src) + struck_target_leap = TRUE + density = TRUE + update_icons() + var/mook_under_us = FALSE + for(var/A in get_turf(src)) + if(struck_target_leap && mook_under_us) + break + if(A == src) + continue + if(isliving(A)) + var/mob/living/ML = A + if(!struck_target_leap && CanAttack(ML))//Check if some joker is attempting to use rest to evade us + struck_target_leap = TRUE + ML.attack_animal(src) + density = TRUE + struck_target_leap = TRUE + update_icons() + continue + if(istype(ML, /mob/living/simple_animal/hostile/jungle/mook) && !mook_under_us)//If we land on the same tile as another mook, spread out so we don't stack our sprite on the same tile + var/mob/living/simple_animal/hostile/jungle/mook/M = ML + if(!M.stat) + mook_under_us = TRUE + var/anydir = pick(GLOB.cardinals) + Move(get_step(src, anydir), anydir) + continue + +/mob/living/simple_animal/hostile/jungle/mook/handle_automated_action() + if(attack_state) + return + return ..() + +/mob/living/simple_animal/hostile/jungle/mook/OpenFire() + if(isliving(target)) + var/mob/living/L = target + if(L.incapacitated()) + return + WarmupAttack() + +/mob/living/simple_animal/hostile/jungle/mook/update_icons() + . = ..() + if(!stat) + switch(attack_state) + if(MOOK_ATTACK_NEUTRAL) + icon_state = "mook" + if(MOOK_ATTACK_WARMUP) + icon_state = "mook_warmup" + if(MOOK_ATTACK_ACTIVE) + if(!density) + icon_state = "mook_leap" + return + if(struck_target_leap) + icon_state = "mook_strike" + return + icon_state = "mook_slash_combo" + if(MOOK_ATTACK_RECOVERY) + icon_state = "mook" + +/obj/effect/temp_visual/mook_dust + name = "dust" + desc = "it's just a dust cloud!" + icon = 'icons/mob/jungle/mook.dmi' + icon_state = "mook_leap_cloud" + layer = BELOW_MOB_LAYER + pixel_x = -16 + pixel_y = -16 + duration = 10 + +#undef MOOK_ATTACK_NEUTRAL +#undef MOOK_ATTACK_WARMUP +#undef MOOK_ATTACK_ACTIVE +#undef MOOK_ATTACK_RECOVERY +#undef ATTACK_INTERMISSION_TIME diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm new file mode 100644 index 0000000000..3e28a789ad --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm @@ -0,0 +1,244 @@ +#define SEEDLING_STATE_NEUTRAL 0 +#define SEEDLING_STATE_WARMUP 1 +#define SEEDLING_STATE_ACTIVE 2 +#define SEEDLING_STATE_RECOVERY 3 + +//A plant rooted in the ground that forfeits its melee attack in favor of ranged barrages. +//It will fire flurries of solar energy, and occasionally charge up a powerful blast that makes it vulnerable to attack. +/mob/living/simple_animal/hostile/jungle/seedling + name = "seedling" + desc = "This oversized, predatory flower conceals what can only be described as an organic energy cannon, and it will not die until its hidden vital organs are sliced out. \ + The concentrated streams of energy it sometimes produces require its full attention, attacking it during this time will prevent it from finishing its attack." + icon = 'icons/mob/jungle/seedling.dmi' + icon_state = "seedling" + icon_living = "seedling" + icon_dead = "seedling_dead" + maxHealth = 100 + health = 100 + melee_damage_lower = 30 + melee_damage_upper = 30 + pixel_x = -16 + pixel_y = -14 + minimum_distance = 3 + move_to_delay = 20 + vision_range = 9 + aggro_vision_range = 15 + ranged = TRUE + ranged_cooldown_time = 10 + projectiletype = /obj/item/projectile/seedling + projectilesound = 'sound/weapons/pierce.ogg' + robust_searching = TRUE + stat_attack = UNCONSCIOUS + anchored = TRUE + var/combatant_state = SEEDLING_STATE_NEUTRAL + var/obj/seedling_weakpoint/weak_point + var/mob/living/beam_debuff_target + var/solar_beam_identifier = 0 + +/obj/item/projectile/seedling + name = "solar energy" + icon_state = "seedling" + damage = 10 + damage_type = BURN + light_range = 2 + flag = "energy" + light_color = LIGHT_COLOR_YELLOW + hitsound = 'sound/weapons/sear.ogg' + hitsound_wall = 'sound/weapons/effects/searwall.ogg' + nondirectional_sprite = TRUE + +/obj/item/projectile/seedling/Collide(atom/A)//Stops seedlings from destroying other jungle mobs through FF + if(isliving(A)) + var/mob/living/L = A + if("jungle" in L.faction) + return FALSE + return ..() + +/obj/effect/temp_visual/solarbeam_killsat + name = "beam of solar energy" + icon_state = "solar_beam" + icon = 'icons/effects/beam.dmi' + layer = LIGHTING_LAYER + duration = 5 + randomdir = FALSE + +/datum/status_effect/seedling_beam_indicator + id = "seedling beam indicator" + duration = 30 + status_type = STATUS_EFFECT_MULTIPLE + alert_type = null + tick_interval = 1 + var/obj/screen/seedling/seedling_screen_object + var/atom/target + + +/datum/status_effect/seedling_beam_indicator/on_creation(mob/living/new_owner, target_plant) + . = ..() + if(.) + target = target_plant + tick() + +/datum/status_effect/seedling_beam_indicator/on_apply() + if(owner.client) + seedling_screen_object = new /obj/screen/seedling() + owner.client.screen += seedling_screen_object + tick() + return ..() + +/datum/status_effect/seedling_beam_indicator/Destroy() + if(owner) + if(owner.client) + owner.client.screen -= seedling_screen_object + return ..() + +/datum/status_effect/seedling_beam_indicator/tick() + var/target_angle = Get_Angle(owner, target) + var/matrix/final = matrix() + final.Turn(target_angle) + seedling_screen_object.transform = final + +/obj/screen/seedling + icon = 'icons/mob/jungle/arachnid.dmi' + icon_state = "seedling_beam_indicator" + screen_loc = "CENTER:-16,CENTER:-16" + +/mob/living/simple_animal/hostile/jungle/seedling/Goto() + if(combatant_state != SEEDLING_STATE_NEUTRAL) + return + return ..() + +/mob/living/simple_animal/hostile/jungle/seedling/AttackingTarget() + if(isliving(target)) + if(ranged_cooldown <= world.time && combatant_state == SEEDLING_STATE_NEUTRAL) + OpenFire(target) + return + return ..() + +/mob/living/simple_animal/hostile/jungle/seedling/OpenFire() + WarmupAttack() + +/mob/living/simple_animal/hostile/jungle/seedling/proc/WarmupAttack() + if(combatant_state == SEEDLING_STATE_NEUTRAL) + combatant_state = SEEDLING_STATE_WARMUP + walk(src,0) + update_icons() + var/target_dist = get_dist(src,target) + var/living_target_check = isliving(target) + if(living_target_check) + if(target_dist > 7)//Offscreen check + SolarBeamStartup(target) + return + if(get_dist(src,target) >= 4 && prob(40)) + SolarBeamStartup(target) + return + addtimer(CALLBACK(src, .proc/Volley), 5) + +/mob/living/simple_animal/hostile/jungle/seedling/proc/SolarBeamStartup(mob/living/living_target)//It's more like requiem than final spark + if(combatant_state == SEEDLING_STATE_WARMUP && target) + combatant_state = SEEDLING_STATE_ACTIVE + living_target.apply_status_effect(/datum/status_effect/seedling_beam_indicator, src) + beam_debuff_target = living_target + playsound(src,'sound/effects/seedling_chargeup.ogg', 100, 0) + if(get_dist(src,living_target) > 7) + playsound(living_target,'sound/effects/seedling_chargeup.ogg', 100, 0) + solar_beam_identifier = world.time + addtimer(CALLBACK(src, .proc/Beamu, living_target, solar_beam_identifier), 35) + +/mob/living/simple_animal/hostile/jungle/seedling/proc/Beamu(mob/living/living_target, beam_id = 0) + if(combatant_state == SEEDLING_STATE_ACTIVE && living_target && beam_id == solar_beam_identifier) + if(living_target.z == z) + update_icons() + var/obj/effect/temp_visual/solarbeam_killsat/S = new (get_turf(src)) + var/matrix/starting = matrix() + starting.Scale(1,32) + starting.Translate(0,520) + S.transform = starting + var/obj/effect/temp_visual/solarbeam_killsat/K = new (get_turf(living_target)) + var/matrix/final = matrix() + final.Scale(1,32) + final.Translate(0,512) + K.transform = final + living_target.adjustFireLoss(30) + living_target.adjust_fire_stacks(0.2)//Just here for the showmanship + living_target.IgniteMob() + playsound(living_target,'sound/weapons/sear.ogg', 50, 1) + addtimer(CALLBACK(src, .proc/AttackRecovery), 5) + return + AttackRecovery() + +/mob/living/simple_animal/hostile/jungle/seedling/proc/Volley() + if(combatant_state == SEEDLING_STATE_WARMUP && target) + combatant_state = SEEDLING_STATE_ACTIVE + update_icons() + var/datum/callback/cb = CALLBACK(src, .proc/InaccurateShot) + for(var/i in 1 to 13) + addtimer(cb, i) + addtimer(CALLBACK(src, .proc/AttackRecovery), 14) + +/mob/living/simple_animal/hostile/jungle/seedling/proc/InaccurateShot() + if(!QDELETED(target) && combatant_state == SEEDLING_STATE_ACTIVE && !stat) + if(get_dist(src,target) <= 3)//If they're close enough just aim straight at them so we don't miss at point blank ranges + Shoot(target) + return + var/turf/our_turf = get_turf(src) + var/obj/item/projectile/seedling/readied_shot = new /obj/item/projectile/seedling(our_turf) + readied_shot.current = our_turf + readied_shot.starting = our_turf + readied_shot.firer = src + readied_shot.original = target + readied_shot.yo = target.y - our_turf.y + rand(-1,1) + readied_shot.xo = target.x - our_turf.x + rand(-1,1) + readied_shot.fire() + playsound(src, projectilesound, 100, 1) + +/mob/living/simple_animal/hostile/jungle/seedling/proc/AttackRecovery() + if(combatant_state == SEEDLING_STATE_ACTIVE) + combatant_state = SEEDLING_STATE_RECOVERY + update_icons() + ranged_cooldown = world.time + ranged_cooldown_time + if(target) + face_atom(target) + addtimer(CALLBACK(src, .proc/ResetNeutral), 10) + +/mob/living/simple_animal/hostile/jungle/seedling/proc/ResetNeutral() + combatant_state = SEEDLING_STATE_NEUTRAL + if(target && !stat) + update_icons() + Goto(target, move_to_delay, minimum_distance) + +/mob/living/simple_animal/hostile/jungle/seedling/adjustHealth() + . = ..() + if(combatant_state == SEEDLING_STATE_ACTIVE && beam_debuff_target) + beam_debuff_target.remove_status_effect(/datum/status_effect/seedling_beam_indicator) + beam_debuff_target = null + solar_beam_identifier = 0 + AttackRecovery() + +/mob/living/simple_animal/hostile/jungle/seedling/update_icons() + . = ..() + if(!stat) + switch(combatant_state) + if(SEEDLING_STATE_NEUTRAL) + icon_state = "seedling" + if(SEEDLING_STATE_WARMUP) + icon_state = "seedling_charging" + if(SEEDLING_STATE_ACTIVE) + icon_state = "seedling_fire" + if(SEEDLING_STATE_RECOVERY) + icon_state = "seedling" + +/mob/living/simple_animal/hostile/jungle/seedling/GiveTarget() + if(target) + if(combatant_state == SEEDLING_STATE_WARMUP || combatant_state == SEEDLING_STATE_ACTIVE)//So it doesn't 180 and blast you in the face while it's firing at someone else + return + return ..() + +/mob/living/simple_animal/hostile/jungle/seedling/LoseTarget() + if(combatant_state == SEEDLING_STATE_WARMUP || combatant_state == SEEDLING_STATE_ACTIVE) + return + return ..() + +#undef SEEDLING_STATE_NEUTRAL +#undef SEEDLING_STATE_WARMUP +#undef SEEDLING_STATE_ACTIVE +#undef SEEDLING_STATE_RECOVERY diff --git a/code/modules/mob/living/simple_animal/hostile/jungle_mobs.dm b/code/modules/mob/living/simple_animal/hostile/jungle_mobs.dm deleted file mode 100644 index b3e1479b91..0000000000 --- a/code/modules/mob/living/simple_animal/hostile/jungle_mobs.dm +++ /dev/null @@ -1,833 +0,0 @@ -/mob/living/simple_animal/hostile/jungle - vision_range = 5 - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - faction = list("jungle") - weather_immunities = list("acid") - obj_damage = 30 - environment_smash = ENVIRONMENT_SMASH_WALLS - minbodytemp = 0 - maxbodytemp = 450 - response_help = "pokes" - response_disarm = "shoves" - response_harm = "strikes" - status_flags = 0 - a_intent = INTENT_HARM - see_in_dark = 4 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - mob_size = MOB_SIZE_LARGE - - - -//Mega arachnid - -/mob/living/simple_animal/hostile/jungle/mega_arachnid - name = "mega arachnid" - desc = "Though physically imposing, it prefers to ambush its prey, and it will only engage with an already crippled opponent." - melee_damage_lower = 30 - melee_damage_upper = 30 - maxHealth = 300 - health = 300 - speed = 1 - ranged = 1 - pixel_x = -16 - move_to_delay = 10 - aggro_vision_range = 9 - speak_emote = list("chitters") - attack_sound = 'sound/weapons/bladeslice.ogg' - ranged_cooldown_time = 60 - projectiletype = /obj/item/projectile/mega_arachnid - projectilesound = 'sound/weapons/pierce.ogg' - icon = 'icons/mob/jungle/arachnid.dmi' - icon_state = "arachnid" - icon_living = "arachnid" - icon_dead = "dead_purple" - alpha = 50 - -/mob/living/simple_animal/hostile/jungle/mega_arachnid/Life() - ..() - if(target && ranged_cooldown > world.time && iscarbon(target)) - var/mob/living/carbon/C = target - if(!C.legcuffed && C.health < 50) - retreat_distance = 9 - minimum_distance = 9 - alpha = 125 - return - retreat_distance = 0 - minimum_distance = 0 - alpha = 255 - - -/mob/living/simple_animal/hostile/jungle/mega_arachnid/Aggro() - ..() - alpha = 255 - -/mob/living/simple_animal/hostile/jungle/mega_arachnid/LoseAggro() - ..() - alpha = 50 - -/obj/item/projectile/mega_arachnid - name = "flesh snare" - nodamage = 1 - damage = 0 - icon_state = "tentacle_end" - -/obj/item/projectile/mega_arachnid/on_hit(atom/target, blocked = FALSE) - if(iscarbon(target) && blocked < 100) - var/obj/item/restraints/legcuffs/beartrap/mega_arachnid/B = new /obj/item/restraints/legcuffs/beartrap/mega_arachnid(get_turf(target)) - B.Crossed(target) - ..() - -/obj/item/restraints/legcuffs/beartrap/mega_arachnid - name = "fleshy restraints" - desc = "Used by mega arachnids to immobilize their prey." - flags_1 = DROPDEL_1 - icon_state = "tentacle_end" - icon = 'icons/obj/projectiles.dmi' - -////Leaper//// - -#define PLAYER_HOP_DELAY 25 - -/mob/living/simple_animal/hostile/jungle/leaper - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - faction = list("jungle") - weather_immunities = list("acid") - obj_damage = 30 - environment_smash = ENVIRONMENT_SMASH_WALLS - maxHealth = 300 - health = 300 - minbodytemp = 0 - maxbodytemp = 450 - response_help = "pokes" - response_disarm = "shoves" - response_harm = "strikes" - status_flags = 0 - a_intent = INTENT_HARM - see_in_dark = 4 - ranged = TRUE - projectiletype = /obj/item/projectile/leaper - projectilesound = 'sound/weapons/pierce.ogg' - ranged_cooldown_time = 30 - pixel_x = -16 - icon = 'icons/mob/jungle/arachnid.dmi' - icon_state = "leaper" - icon_living = "leaper" - icon_dead = "leaper_dead" - layer = LARGE_MOB_LAYER - name = "leaper" - desc = "Commonly referred to as 'leapers', the Geron Toad is a massive beast that spits out highly pressurized bubbles containing a unique toxin, knocking down its prey and then crushing it with its girth." - speed = 10 - stat_attack = 1 - robust_searching = 1 - var/hopping = FALSE - var/hop_cooldown = 0 //Strictly for player controlled leapers - var/projectile_ready = FALSE //Stopping AI leapers from firing whenever they want, and only doing it after a hop has finished instead - -/obj/item/projectile/leaper - name = "leaper bubble" - icon_state = "leaper" - knockdown = 50 - damage = 0 - range = 7 - hitsound = 'sound/effects/snap.ogg' - nondirectional_sprite = TRUE - impact_effect_type = /obj/effect/temp_visual/leaper_projectile_impact - -/obj/item/projectile/leaper/on_hit(atom/target, blocked = FALSE) - ..() - if(iscarbon(target)) - var/mob/living/carbon/C = target - C.reagents.add_reagent("leaper_venom", 5) - return - if(isanimal(target)) - var/mob/living/simple_animal/L = target - L.adjustHealth(25) - -/obj/item/projectile/leaper/on_range() - var/turf/T = get_turf(src) - ..() - new /obj/structure/leaper_bubble(T) - -/obj/effect/temp_visual/leaper_projectile_impact - name = "leaper bubble" - icon = 'icons/obj/projectiles.dmi' - icon_state = "leaper_bubble_pop" - layer = ABOVE_ALL_MOB_LAYER - duration = 3 - -/obj/effect/temp_visual/leaper_projectile_impact/Initialize() - . = ..() - new /obj/effect/decal/cleanable/leaper_sludge(get_turf(src)) - -/obj/effect/decal/cleanable/leaper_sludge - name = "leaper sludge" - desc = "A small pool of sludge, containing trace amounts of leaper venom" - icon = 'icons/effects/tomatodecal.dmi' - icon_state = "tomato_floor1" - -/obj/structure/leaper_bubble - name = "leaper bubble" - desc = "A floating bubble containing leaper venom, the contents are under a surprising amount of pressure." - icon = 'icons/obj/projectiles.dmi' - icon_state = "leaper" - max_integrity = 10 - density = FALSE - -/obj/structure/leaper_bubble/Initialize() - . = ..() - float(on = TRUE) - QDEL_IN(src, 100) - -/obj/structure/leaper_bubble/Destroy() - new /obj/effect/temp_visual/leaper_projectile_impact(get_turf(src)) - playsound(src,'sound/effects/snap.ogg',50, 1, -1) - return ..() - -/obj/structure/leaper_bubble/Crossed(atom/movable/AM) - if(isliving(AM)) - var/mob/living/L = AM - if(!istype(L, /mob/living/simple_animal/hostile/jungle/leaper)) - playsound(src,'sound/effects/snap.ogg',50, 1, -1) - L.Knockdown(50) - if(iscarbon(L)) - var/mob/living/carbon/C = L - C.reagents.add_reagent("leaper_venom", 5) - if(isanimal(L)) - var/mob/living/simple_animal/A = L - A.adjustHealth(25) - qdel(src) - return ..() - -/datum/reagent/toxin/leaper_venom - name = "Leaper venom" - id = "leaper_venom" - description = "A toxin spat out by leapers that while harmless in small doses, quickly creates a toxic reaction if too much is in the body." - color = "#801E28" // rgb: 128, 30, 40 - toxpwr = 0 - taste_description = "french cuisine" - taste_mult = 1.3 - -/datum/reagent/toxin/leaper_venom/on_mob_life(mob/living/M) - if(volume >= 10) - M.adjustToxLoss(5, 0) - ..() - -/obj/effect/temp_visual/leaper_crush - name = "Grim tidings" - desc = "Incoming leaper!" - icon = 'icons/effects/96x96.dmi' - icon_state = "lily_pad" - layer = BELOW_MOB_LAYER - pixel_x = -32 - pixel_y = -32 - duration = 30 - -/mob/living/simple_animal/hostile/jungle/leaper/Initialize() - . = ..() - verbs -= /mob/living/verb/pulled - -/mob/living/simple_animal/hostile/jungle/leaper/CtrlClickOn(atom/A) - face_atom(A) - target = A - if(!isturf(loc)) - return - if(next_move > world.time) - return - if(hopping) - return - if(isliving(A)) - var/mob/living/L = A - if(L.incapacitated()) - BellyFlop() - return - if(hop_cooldown <= world.time) - Hop(player_hop = TRUE) - -/mob/living/simple_animal/hostile/jungle/leaper/AttackingTarget() - if(isliving(target)) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/leaper/handle_automated_action() - if(hopping || projectile_ready) - return - . = ..() - if(target) - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated()) - BellyFlop() - return - if(!hopping) - Hop() - -/mob/living/simple_animal/hostile/jungle/leaper/Life() - . = ..() - update_icons() - -/mob/living/simple_animal/hostile/jungle/leaper/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - if(prob(33) && !ckey) - ranged_cooldown = 0 //Keeps em on their toes instead of a constant rotation - ..() - -/mob/living/simple_animal/hostile/jungle/leaper/OpenFire() - face_atom(target) - if(ranged_cooldown <= world.time) - if(ckey) - if(hopping) - return - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated()) - return //No stunlocking. Hop on them after you stun them, you donk. - if(AIStatus == AI_ON && !projectile_ready && !ckey) - return - . = ..(target) - projectile_ready = FALSE - update_icons() - -/mob/living/simple_animal/hostile/jungle/leaper/proc/Hop(player_hop = FALSE) - if(z != target.z) - return - hopping = TRUE - density = FALSE - pass_flags |= PASSMOB - notransform = TRUE - var/turf/new_turf = locate((target.x + rand(-3,3)),(target.y + rand(-3,3)),target.z) - if(player_hop) - new_turf = get_turf(target) - hop_cooldown = world.time + PLAYER_HOP_DELAY - if(AIStatus == AI_ON && ranged_cooldown <= world.time) - projectile_ready = TRUE - update_icons() - throw_at(new_turf, max(3,get_dist(src,new_turf)), 1, src, FALSE, callback = CALLBACK(src, .FinishHop)) - -/mob/living/simple_animal/hostile/jungle/leaper/proc/FinishHop() - density = TRUE - notransform = FALSE - pass_flags &= ~PASSMOB - hopping = FALSE - playsound(src.loc, 'sound/effects/meteorimpact.ogg', 100, 1) - if(target && AIStatus == AI_ON && projectile_ready && !ckey) - face_atom(target) - addtimer(CALLBACK(src, .proc/OpenFire, target), 5) - -/mob/living/simple_animal/hostile/jungle/leaper/proc/BellyFlop() - var/turf/new_turf = get_turf(target) - hopping = TRUE - notransform = TRUE - new /obj/effect/temp_visual/leaper_crush(new_turf) - addtimer(CALLBACK(src, .proc/BellyFlopHop, new_turf), 30) - -/mob/living/simple_animal/hostile/jungle/leaper/proc/BellyFlopHop(turf/T) - density = FALSE - throw_at(T, get_dist(src,T),1,src, FALSE, callback = CALLBACK(src, .proc/Crush)) - -/mob/living/simple_animal/hostile/jungle/leaper/proc/Crush() - hopping = FALSE - density = TRUE - notransform = FALSE - playsound(src, 'sound/effects/meteorimpact.ogg', 200, 1) - for(var/mob/living/L in orange(1, src)) - L.adjustBruteLoss(35) - if(!QDELETED(L)) // Some mobs are deleted on death - var/throw_dir = get_dir(src, L) - if(L.loc == loc) - throw_dir = pick(GLOB.alldirs) - var/throwtarget = get_edge_target_turf(src, throw_dir) - L.throw_at(throwtarget, 3, 1) - visible_message("[L] is thrown clear of [src]!") - if(ckey)//Lessens ability to chain stun as a player - ranged_cooldown = ranged_cooldown_time + world.time - update_icons() - -/mob/living/simple_animal/hostile/jungle/leaper/Goto() - return - -/mob/living/simple_animal/hostile/jungle/leaper/throw_impact() - return - -/mob/living/simple_animal/hostile/jungle/leaper/update_icons() - . = ..() - if(stat) - icon_state = "leaper_dead" - return - if(ranged_cooldown <= world.time) - if(AIStatus == AI_ON && projectile_ready || ckey) - icon_state = "leaper_alert" - return - icon_state = "leaper" - -#undef PLAYER_HOP_DELAY - -////JUNGLE MOOK//// - -#define MOOK_ATTACK_NEUTRAL 0 -#define MOOK_ATTACK_WARMUP 1 -#define MOOK_ATTACK_ACTIVE 2 -#define MOOK_ATTACK_RECOVERY 3 - -#define ATTACK_INTERMISSION_TIME 5 - -/mob/living/simple_animal/hostile/jungle/mook - name = "wanderer" - desc = "This unhealthy looking primitive is wielding a rudimentary hatchet, swinging it with wild abandon. One isn't much of a threat, but in numbers they can quickly overwhelm a superior opponent." - maxHealth = 45 - health = 45 - melee_damage_lower = 30 - melee_damage_upper = 30 - icon = 'icons/mob/jungle/arachnid.dmi' - icon_state = "mook" - icon_living = "mook" - icon_dead = "mook_dead" - pixel_x = -16 - pixel_y = -8 - ranged = TRUE - ranged_cooldown_time = 10 - pass_flags = LETPASSTHROW - robust_searching = TRUE - stat_attack = UNCONSCIOUS - attack_sound = 'sound/weapons/rapierhit.ogg' - death_sound = 'sound/voice/mook_death.ogg' - aggro_vision_range = 15 //A little more aggressive once in combat to balance out their really low HP - var/attack_state = MOOK_ATTACK_NEUTRAL - var/struck_target_leap = FALSE - -/mob/living/simple_animal/hostile/jungle/mook/CanPass(atom/movable/O) - if(istype(O, /mob/living/simple_animal/hostile/jungle/mook)) - var/mob/living/simple_animal/hostile/jungle/mook/M = O - if(M.attack_state == MOOK_ATTACK_ACTIVE && M.throwing) - return TRUE - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/death() - desc = "A deceased primitive. Upon closer inspection, it was suffering from severe cellular degeneration and its garments are machine made..."//Can you guess the twist - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/AttackingTarget() - if(isliving(target)) - if(ranged_cooldown <= world.time && attack_state == MOOK_ATTACK_NEUTRAL) - var/mob/living/L = target - if(L.incapacitated()) - WarmupAttack(forced_slash_combo = TRUE) - return - WarmupAttack() - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/Goto() - if(attack_state != MOOK_ATTACK_NEUTRAL) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/Move() - if(attack_state == MOOK_ATTACK_WARMUP || attack_state == MOOK_ATTACK_RECOVERY) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/proc/WarmupAttack(forced_slash_combo = FALSE) - if(attack_state == MOOK_ATTACK_NEUTRAL && target) - attack_state = MOOK_ATTACK_WARMUP - walk(src,0) - update_icons() - if(prob(50) && get_dist(src,target) <= 3 || forced_slash_combo) - addtimer(CALLBACK(src, .proc/SlashCombo), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, .proc/LeapAttack), ATTACK_INTERMISSION_TIME + rand(0,3)) - return - attack_state = MOOK_ATTACK_RECOVERY - ResetNeutral() - -/mob/living/simple_animal/hostile/jungle/mook/proc/SlashCombo() - if(attack_state == MOOK_ATTACK_WARMUP && !stat) - attack_state = MOOK_ATTACK_ACTIVE - update_icons() - SlashAttack() - addtimer(CALLBACK(src, .proc/SlashAttack), 3) - addtimer(CALLBACK(src, .proc/SlashAttack), 6) - addtimer(CALLBACK(src, .proc/AttackRecovery), 9) - -/mob/living/simple_animal/hostile/jungle/mook/proc/SlashAttack() - if(target && !stat && attack_state == MOOK_ATTACK_ACTIVE) - melee_damage_lower = 15 - melee_damage_upper = 15 - var/mob_direction = get_dir(src,target) - if(get_dist(src,target) > 1) - step(src,mob_direction) - if(targets_from && isturf(targets_from.loc) && target.Adjacent(targets_from) && isliving(target)) - var/mob/living/L = target - L.attack_animal(src) - return - var/swing_turf = get_step(src,mob_direction) - new /obj/effect/temp_visual/kinetic_blast(swing_turf) - playsound(src, 'sound/weapons/slashmiss.ogg', 50, 1) - -/mob/living/simple_animal/hostile/jungle/mook/proc/LeapAttack() - if(target && !stat && attack_state == MOOK_ATTACK_WARMUP) - attack_state = MOOK_ATTACK_ACTIVE - density = FALSE - melee_damage_lower = 30 - melee_damage_upper = 30 - update_icons() - new /obj/effect/temp_visual/mook_dust(get_turf(src)) - playsound(src, 'sound/weapons/thudswoosh.ogg', 25, 1) - playsound(src, 'sound/voice/mook_leap_yell.ogg', 100, 1) - var/target_turf = get_turf(target) - throw_at(target_turf, 7, 1, src, FALSE, callback = CALLBACK(src, .proc/AttackRecovery)) - return - attack_state = MOOK_ATTACK_RECOVERY - ResetNeutral() - -/mob/living/simple_animal/hostile/jungle/mook/proc/AttackRecovery() - if(attack_state == MOOK_ATTACK_ACTIVE && !stat) - attack_state = MOOK_ATTACK_RECOVERY - density = TRUE - face_atom(target) - if(!struck_target_leap) - update_icons() - struck_target_leap = FALSE - if(prob(40)) - attack_state = MOOK_ATTACK_NEUTRAL - if(target) - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated() && L.stat != DEAD) - addtimer(CALLBACK(src, .proc/WarmupAttack, TRUE), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, .proc/WarmupAttack), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, .proc/ResetNeutral), ATTACK_INTERMISSION_TIME) - -/mob/living/simple_animal/hostile/jungle/mook/proc/ResetNeutral() - if(attack_state == MOOK_ATTACK_RECOVERY) - attack_state = MOOK_ATTACK_NEUTRAL - ranged_cooldown = world.time + ranged_cooldown_time - update_icons() - if(target && !stat) - update_icons() - Goto(target, move_to_delay, minimum_distance) - -/mob/living/simple_animal/hostile/jungle/mook/throw_impact(atom/hit_atom, throwingdatum) - . = ..() - if(isliving(hit_atom) && attack_state == MOOK_ATTACK_ACTIVE) - var/mob/living/L = hit_atom - if(CanAttack(L)) - L.attack_animal(src) - struck_target_leap = TRUE - density = TRUE - update_icons() - var/mook_under_us = FALSE - for(var/A in get_turf(src)) - if(struck_target_leap && mook_under_us) - break - if(A == src) - continue - if(isliving(A)) - var/mob/living/ML = A - if(!struck_target_leap && CanAttack(ML))//Check if some joker is attempting to use rest to evade us - struck_target_leap = TRUE - ML.attack_animal(src) - density = TRUE - struck_target_leap = TRUE - update_icons() - continue - if(istype(ML, /mob/living/simple_animal/hostile/jungle/mook) && !mook_under_us)//If we land on the same tile as another mook, spread out so we don't stack our sprite on the same tile - var/mob/living/simple_animal/hostile/jungle/mook/M = ML - if(!M.stat) - mook_under_us = TRUE - var/anydir = pick(GLOB.cardinals) - Move(get_step(src, anydir), anydir) - continue - -/mob/living/simple_animal/hostile/jungle/mook/handle_automated_action() - if(attack_state) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/OpenFire() - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated()) - return - WarmupAttack() - -/mob/living/simple_animal/hostile/jungle/mook/update_icons() - . = ..() - if(!stat) - switch(attack_state) - if(MOOK_ATTACK_NEUTRAL) - icon_state = "mook" - if(MOOK_ATTACK_WARMUP) - icon_state = "mook_warmup" - if(MOOK_ATTACK_ACTIVE) - if(!density) - icon_state = "mook_leap" - return - if(struck_target_leap) - icon_state = "mook_strike" - return - icon_state = "mook_slash_combo" - if(MOOK_ATTACK_RECOVERY) - icon_state = "mook" - -/obj/effect/temp_visual/mook_dust - name = "dust" - desc = "it's just a dust cloud!" - icon = 'icons/mob/jungle/arachnid.dmi' - icon_state = "mook_leap_cloud" - layer = BELOW_MOB_LAYER - pixel_x = -16 - pixel_y = -16 - duration = 10 - -#undef MOOK_ATTACK_NEUTRAL -#undef MOOK_ATTACK_WARMUP -#undef MOOK_ATTACK_ACTIVE -#undef MOOK_ATTACK_RECOVERY -#undef ATTACK_INTERMISSION_TIME - -////Jungle Seedling//// - -#define SEEDLING_STATE_NEUTRAL 0 -#define SEEDLING_STATE_WARMUP 1 -#define SEEDLING_STATE_ACTIVE 2 -#define SEEDLING_STATE_RECOVERY 3 - - -/mob/living/simple_animal/hostile/jungle/seedling - name = "seedling" - desc = "This oversized, predatory flower conceals what can only be described as an organic energy cannon, and it will not die until its hidden vital organs are sliced out. \ - The concentrated streams of energy it sometimes produces require its full attention, attacking it during this time will prevent it from finishing its attack." - maxHealth = 100 - health = 100 - melee_damage_lower = 30 - melee_damage_upper = 30 - icon = 'icons/mob/jungle/arachnid.dmi' - icon_state = "seedling" - icon_living = "seedling" - icon_dead = "seedling_dead" - pixel_x = -16 - pixel_y = -14 - minimum_distance = 3 - move_to_delay = 20 - vision_range = 9 - aggro_vision_range = 15 - ranged = TRUE - ranged_cooldown_time = 10 - projectiletype = /obj/item/projectile/seedling - projectilesound = 'sound/weapons/pierce.ogg' - robust_searching = TRUE - stat_attack = UNCONSCIOUS - anchored = TRUE - var/combatant_state = SEEDLING_STATE_NEUTRAL - var/obj/seedling_weakpoint/weak_point - var/mob/living/beam_debuff_target - var/solar_beam_identifier = 0 - -/obj/item/projectile/seedling - name = "solar energy" - icon_state = "seedling" - damage = 10 - damage_type = BURN - light_range = 2 - flag = "energy" - light_color = LIGHT_COLOR_YELLOW - hitsound = 'sound/weapons/sear.ogg' - hitsound_wall = 'sound/weapons/effects/searwall.ogg' - nondirectional_sprite = TRUE - -/obj/item/projectile/seedling/Collide(atom/A)//Stops seedlings from destroying other jungle mobs through FF - if(isliving(A)) - var/mob/living/L = A - if("jungle" in L.faction) - return FALSE - return ..() - -/obj/effect/temp_visual/solarbeam_killsat - name = "beam of solar energy" - icon_state = "solar_beam" - icon = 'icons/effects/beam.dmi' - layer = LIGHTING_LAYER - duration = 5 - randomdir = FALSE - -/datum/status_effect/seedling_beam_indicator - id = "seedling beam indicator" - duration = 30 - status_type = STATUS_EFFECT_MULTIPLE - alert_type = null - tick_interval = 1 - var/obj/screen/seedling/seedling_screen_object - var/atom/target - - -/datum/status_effect/seedling_beam_indicator/on_creation(mob/living/new_owner, target_plant) - . = ..() - if(.) - target = target_plant - tick() - -/datum/status_effect/seedling_beam_indicator/on_apply() - if(owner.client) - seedling_screen_object = new /obj/screen/seedling() - owner.client.screen += seedling_screen_object - tick() - return ..() - -/datum/status_effect/seedling_beam_indicator/Destroy() - if(owner) - if(owner.client) - owner.client.screen -= seedling_screen_object - return ..() - -/datum/status_effect/seedling_beam_indicator/tick() - var/target_angle = Get_Angle(owner, target) - var/matrix/final = matrix() - final.Turn(target_angle) - seedling_screen_object.transform = final - -/obj/screen/seedling - icon = 'icons/mob/jungle/arachnid.dmi' - icon_state = "seedling_beam_indicator" - screen_loc = "CENTER:-16,CENTER:-16" - -/mob/living/simple_animal/hostile/jungle/seedling/Goto() - if(combatant_state != SEEDLING_STATE_NEUTRAL) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/seedling/AttackingTarget() - if(isliving(target)) - if(ranged_cooldown <= world.time && combatant_state == SEEDLING_STATE_NEUTRAL) - OpenFire(target) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/seedling/OpenFire() - WarmupAttack() - -/mob/living/simple_animal/hostile/jungle/seedling/proc/WarmupAttack() - if(combatant_state == SEEDLING_STATE_NEUTRAL) - combatant_state = SEEDLING_STATE_WARMUP - walk(src,0) - update_icons() - var/target_dist = get_dist(src,target) - var/living_target_check = isliving(target) - if(living_target_check) - if(target_dist > 7)//Offscreen check - SolarBeamStartup(target) - return - if(get_dist(src,target) >= 4 && prob(40)) - SolarBeamStartup(target) - return - addtimer(CALLBACK(src, .proc/Volley), 5) - -/mob/living/simple_animal/hostile/jungle/seedling/proc/SolarBeamStartup(mob/living/living_target)//It's more like requiem than final spark - if(combatant_state == SEEDLING_STATE_WARMUP && target) - combatant_state = SEEDLING_STATE_ACTIVE - living_target.apply_status_effect(/datum/status_effect/seedling_beam_indicator, src) - beam_debuff_target = living_target - playsound(src,'sound/effects/seedling_chargeup.ogg', 100, 0) - if(get_dist(src,living_target) > 7) - playsound(living_target,'sound/effects/seedling_chargeup.ogg', 100, 0) - solar_beam_identifier = world.time - addtimer(CALLBACK(src, .proc/Beamu, living_target, solar_beam_identifier), 35) - -/mob/living/simple_animal/hostile/jungle/seedling/proc/Beamu(mob/living/living_target, beam_id = 0) - if(combatant_state == SEEDLING_STATE_ACTIVE && living_target && beam_id == solar_beam_identifier) - if(living_target.z == z) - update_icons() - var/obj/effect/temp_visual/solarbeam_killsat/S = new (get_turf(src)) - var/matrix/starting = matrix() - starting.Scale(1,32) - starting.Translate(0,520) - S.transform = starting - var/obj/effect/temp_visual/solarbeam_killsat/K = new (get_turf(living_target)) - var/matrix/final = matrix() - final.Scale(1,32) - final.Translate(0,512) - K.transform = final - living_target.adjustFireLoss(30) - living_target.adjust_fire_stacks(0.2)//Just here for the showmanship - living_target.IgniteMob() - playsound(living_target,'sound/weapons/sear.ogg', 50, 1) - addtimer(CALLBACK(src, .proc/AttackRecovery), 5) - return - AttackRecovery() - -/mob/living/simple_animal/hostile/jungle/seedling/proc/Volley() - if(combatant_state == SEEDLING_STATE_WARMUP && target) - combatant_state = SEEDLING_STATE_ACTIVE - update_icons() - var/datum/callback/cb = CALLBACK(src, .proc/InaccurateShot) - for(var/i in 1 to 13) - addtimer(cb, i) - addtimer(CALLBACK(src, .proc/AttackRecovery), 14) - -/mob/living/simple_animal/hostile/jungle/seedling/proc/InaccurateShot() - if(!QDELETED(target) && combatant_state == SEEDLING_STATE_ACTIVE && !stat) - if(get_dist(src,target) <= 3)//If they're close enough just aim straight at them so we don't miss at point blank ranges - Shoot(target) - return - var/turf/our_turf = get_turf(src) - var/obj/item/projectile/seedling/readied_shot = new /obj/item/projectile/seedling(our_turf) - readied_shot.current = our_turf - readied_shot.starting = our_turf - readied_shot.firer = src - readied_shot.original = target - readied_shot.yo = target.y - our_turf.y + rand(-1,1) - readied_shot.xo = target.x - our_turf.x + rand(-1,1) - readied_shot.fire() - playsound(src, projectilesound, 100, 1) - -/mob/living/simple_animal/hostile/jungle/seedling/proc/AttackRecovery() - if(combatant_state == SEEDLING_STATE_ACTIVE) - combatant_state = SEEDLING_STATE_RECOVERY - update_icons() - ranged_cooldown = world.time + ranged_cooldown_time - if(target) - face_atom(target) - addtimer(CALLBACK(src, .proc/ResetNeutral), 10) - -/mob/living/simple_animal/hostile/jungle/seedling/proc/ResetNeutral() - combatant_state = SEEDLING_STATE_NEUTRAL - if(target && !stat) - update_icons() - Goto(target, move_to_delay, minimum_distance) - -/mob/living/simple_animal/hostile/jungle/seedling/adjustHealth() - . = ..() - if(combatant_state == SEEDLING_STATE_ACTIVE && beam_debuff_target) - beam_debuff_target.remove_status_effect(/datum/status_effect/seedling_beam_indicator) - beam_debuff_target = null - solar_beam_identifier = 0 - AttackRecovery() - -/mob/living/simple_animal/hostile/jungle/seedling/update_icons() - . = ..() - if(!stat) - switch(combatant_state) - if(SEEDLING_STATE_NEUTRAL) - icon_state = "seedling" - if(SEEDLING_STATE_WARMUP) - icon_state = "seedling_charging" - if(SEEDLING_STATE_ACTIVE) - icon_state = "seedling_fire" - if(SEEDLING_STATE_RECOVERY) - icon_state = "seedling" - -/mob/living/simple_animal/hostile/jungle/seedling/GiveTarget() - if(target) - if(combatant_state == SEEDLING_STATE_WARMUP || combatant_state == SEEDLING_STATE_ACTIVE)//So it doesn't 180 and blast you in the face while it's firing at someone else - return - return ..() - -/mob/living/simple_animal/hostile/jungle/seedling/LoseTarget() - if(combatant_state == SEEDLING_STATE_WARMUP || combatant_state == SEEDLING_STATE_ACTIVE) - return - return ..() - -#undef SEEDLING_STATE_NEUTRAL -#undef SEEDLING_STATE_WARMUP -#undef SEEDLING_STATE_ACTIVE -#undef SEEDLING_STATE_RECOVERY \ No newline at end of file diff --git a/icons/mob/jungle/arachnid.dmi b/icons/mob/jungle/arachnid.dmi index ccd52eeb7d..23482d00eb 100644 Binary files a/icons/mob/jungle/arachnid.dmi and b/icons/mob/jungle/arachnid.dmi differ diff --git a/icons/mob/jungle/leaper.dmi b/icons/mob/jungle/leaper.dmi new file mode 100644 index 0000000000..39f807a191 Binary files /dev/null and b/icons/mob/jungle/leaper.dmi differ diff --git a/icons/mob/jungle/mook.dmi b/icons/mob/jungle/mook.dmi new file mode 100644 index 0000000000..c9265b22a0 Binary files /dev/null and b/icons/mob/jungle/mook.dmi differ diff --git a/icons/mob/jungle/seedling.dmi b/icons/mob/jungle/seedling.dmi new file mode 100644 index 0000000000..01e91c6c29 Binary files /dev/null and b/icons/mob/jungle/seedling.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 3b58a075e4..c3775a64ec 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1784,7 +1784,6 @@ #include "code\modules\mob\living\simple_animal\hostile\hivebot.dm" #include "code\modules\mob\living\simple_animal\hostile\hostile.dm" #include "code\modules\mob\living\simple_animal\hostile\illusion.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle_mobs.dm" #include "code\modules\mob\living\simple_animal\hostile\killertomato.dm" #include "code\modules\mob\living\simple_animal\hostile\mecha_pilot.dm" #include "code\modules\mob\living\simple_animal\hostile\mimic.dm" diff --git a/tgstation.dme.rej b/tgstation.dme.rej new file mode 100644 index 0000000000..a5ff4dc3cb --- /dev/null +++ b/tgstation.dme.rej @@ -0,0 +1,13 @@ +diff a/tgstation.dme b/tgstation.dme (rejected hunks) +@@ -1746,6 +1745,11 @@ + #include "code\modules\mob\living\simple_animal\hostile\gorilla\emotes.dm" + #include "code\modules\mob\living\simple_animal\hostile\gorilla\gorilla.dm" + #include "code\modules\mob\living\simple_animal\hostile\gorilla\visuals_icons.dm" ++#include "code\modules\mob\living\simple_animal\hostile\jungle\_jungle_mobs.dm" ++#include "code\modules\mob\living\simple_animal\hostile\jungle\leaper.dm" ++#include "code\modules\mob\living\simple_animal\hostile\jungle\mega_arachnid.dm" ++#include "code\modules\mob\living\simple_animal\hostile\jungle\mook.dm" ++#include "code\modules\mob\living\simple_animal\hostile\jungle\seedling.dm" + #include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm" + #include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm" + #include "code\modules\mob\living\simple_animal\hostile\megafauna\colossus.dm"