diff --git a/Elites/empyrean.lua b/Elites/empyrean.lua new file mode 100644 index 00000000..d01c4979 --- /dev/null +++ b/Elites/empyrean.lua @@ -0,0 +1,656 @@ +-- ugh ..... ugmhgg .... it appreas it is me azuline again ..... +-- this elite is gay af.... just like me ... so it was only appropriate that i would do it .... + +local SPRITE_PATH = path.combine(PATH, "Sprites/Elites/Empyrean") +local SOUND_PATH = path.combine(PATH, "Sounds/Elites/Empyrean") + +local sprite_icon = Resources.sprite_load(NAMESPACE, "EliteIconEmpyrean", path.combine(SPRITE_PATH, "icon.png"), 1, 25, 22) +local splash_sprite = Resources.sprite_load(NAMESPACE, "ParticleEmpyreanSpawnSplash", path.combine(SPRITE_PATH, "splash.png"), 6) +local beam_sprite = Resources.sprite_load(NAMESPACE, "ParticleEmpyreanSpawnBeam", path.combine(SPRITE_PATH, "beam.png"), 11) +local star_sprite = Resources.sprite_load(NAMESPACE, "EmpyreanWormStar", path.combine(SPRITE_PATH, "star.png"), 8, 16, 16) + +local gotanythingsharp = Resources.sfx_load(NAMESPACE, "EmpyreanSpawn", path.combine(SOUND_PATH, "beam.ogg")) +local sound_spawn = Resources.sfx_load(NAMESPACE, "EmpyreanSpawnShort", path.combine(SOUND_PATH, "spawn.ogg")) +local sound_spawn_alt = Resources.sfx_load(NAMESPACE, "EmpyreanSpawnShortAlt", path.combine(SOUND_PATH, "spawn_alt.ogg")) +local sound_spawn_worm = Resources.sfx_load(NAMESPACE, "EmpyreanSpawnWorm", path.combine(SOUND_PATH, "spawn_worm.ogg")) +local sound_star_spawn = Resources.sfx_load(NAMESPACE, "EmpyreanStarSpawn", path.combine(SOUND_PATH, "star_spawn.ogg")) +local sound_star_shoot = Resources.sfx_load(NAMESPACE, "EmpyreanStarShoot", path.combine(SOUND_PATH, "star_shoot.ogg")) +local sound_teleport1 = Resources.sfx_load(NAMESPACE, "EmpyreanTeleport1", path.combine(SOUND_PATH, "teleport1.ogg")) +local sound_teleport2 = Resources.sfx_load(NAMESPACE, "EmpyreanTeleport2", path.combine(SOUND_PATH, "teleport2.ogg")) +local sound_teleport3 = Resources.sfx_load(NAMESPACE, "EmpyreanTeleport3", path.combine(SOUND_PATH, "teleport3.ogg")) + +local empy = Elite.new(NAMESPACE, "empyrean") +empy.healthbar_icon = sprite_icon +empy.palette = gm.constants.sElitePaletteDummy +empy.blend_col = Color.WHITE + +GM.elite_generate_palettes() + +local evil = Particle.new(NAMESPACE, "EmpyreanSpawnEvil") +evil:set_sprite(splash_sprite, true, true, false) +evil:set_life(15, 30) + +local beam = Particle.new(NAMESPACE, "EmpyreanSpawnBeam") +beam:set_direction(270, 270, 0, 0) +beam:set_speed(8, 8, 0, 0) +beam:set_sprite(beam_sprite, true, true, false) +beam:set_life(10, 10) + +local splash = Particle.new(NAMESPACE, "EmpyreanSpawnSplash") +splash:set_sprite(splash_sprite, true, true, false) +splash:set_scale(1.5, 1.5) +splash:set_life(15, 30) + +local rainbowspark = Particle.new(NAMESPACE, "EmpyreanRainbowSpark") +rainbowspark:set_shape(Particle.SHAPE.line) +rainbowspark:set_blend(true) +rainbowspark:set_alpha3(1, 1, 0) +rainbowspark:set_size(0, 1, 0, 0.01) +rainbowspark:set_orientation(0, 0, 0, 0, true) +rainbowspark:set_speed(0, 4, -0.04, 0.2) +rainbowspark:set_direction(0, 180, 0, 10) +rainbowspark:set_scale(0.1, 0.1) +rainbowspark:set_life(20, 100) + +local teleport = Particle.new(NAMESPACE, "EmpyreanTeleport") +teleport:set_shape(Particle.SHAPE.star) +teleport:set_alpha3(0.75, 0.75, 0) +teleport:set_color2(Color.WHITE, Color.WHITE) +teleport:set_orientation(0, 0, 0, 0, true) +teleport:set_speed(0, 3, -0.03, 0.05) +teleport:set_direction(0, 360, 0, 10) +teleport:set_scale(0.1, 0.1) +teleport:set_life(20, 100) + +local telegraph = Particle.new(NAMESPACE, "EmpyreanStarTelegraph") +telegraph:set_shape(Particle.SHAPE.disk) +telegraph:set_alpha2(0.6, 0) +telegraph:set_blend(true) +telegraph:set_speed(6, 6, 0, 0) +telegraph:set_scale(0.1, 0.1) +telegraph:set_life(100, 100) + +local empyorb = Item.new(NAMESPACE, "eliteOrbEmpyrean", true) +empyorb.is_hidden = true + +local teleorb = Item.new(NAMESPACE, "elitePassiveTeleportEmpyrean", true) +teleorb.is_hidden = true + +-- PUT ASPECTS HERE -- +-- format is {"namespace-identifier", stack} +local aspects = { + -- VANILLA -- + + -- volatile + {"ror-eliteOrbExplosiveShot", 2}, + {"ror-elitePassiveVolatile", 1}, + + -- overloading + {"ror-eliteOrbLightning", 1}, + {"ror-elitePassiveOverloading", 1}, + + -- leeching + {"ror-elitePassiveLeeching", 1}, + {"ror-eliteOrbLifesteal", 7}, + + -- frenzied (minus their teleporting orb) + {"ror-eliteOrbAttackSpeed", 3}, + {"ror-eliteOrbMoveSpeed", 5}, + + -- blazing + {"ror-eliteOrbFireTrail", 5}, + + + + -- STARSTORM -- + + -- poison + {"ssr-eliteOrbPoison", 1} +} +function ssr_give_empyrean_aspects(actor) + for _, aspect in ipairs(aspects) do + local item = Item.find(aspect[1]) + + -- if the elite already has these orbs, remove them + if actor:item_stack_count(item) > 0 then + actor:item_remove(item, actor:item_stack_count(item)) + end + + -- give the orbs + actor:item_give(item, aspect[2]) + end + + for id, stack in ipairs(actor.inventory_item_stack) do + if stack > 0 then + local item = Item.wrap(id - 1) -- subtract by one cuz lua tables start at 1 instead of 0 + end + end +end + +empy:clear_callbacks() +empy:onApply(function(actor) + actor:item_give(empyorb) -- applies most empyrean effects + actor:item_give(teleorb) -- makes it teleport to the player from any location + + -- stat changes are multiplicative with normal elite stat changes + -- so max hp for example will be base * 12 * 2.8 = 33.6x total multiplier + + -- giga health + actor.maxhp_base = actor.maxhp_base * 12 + actor.hp = actor.maxhp + + -- giga gold and exp + if actor.exp_worth then + actor.exp_worth = actor.exp_worth * 30 -- totals to 60x + end + + -- immune to stun, knockback and fall damage + actor.knockback_immune = true + actor.stun_immune = true + actor.fall_immune = true + + -- make maxhp_base modification take effect + GM.actor_queue_dirty(actor) +end) + +empyorb:clear_callbacks() +empyorb:onAcquire(function(actor, stack) + -- move the actor to the ground + actor:move_contact_solid(270, -1) + + -- apply screenshake + actor:screen_shake(4) + + -- play the spawn sound + + -- start the beam + if gm.inside_view(actor.x, actor.y) == 1 then + actor:get_data().empy_beam = 180 + actor:get_data().empy_beam_over = 0 + actor:sound_play(gotanythingsharp, 2, 1) + else + actor:get_data().empy_beam = 0 + actor:get_data().empy_beam_over = 1 + actor:get_data().no_beam_loser = 5 + + if Helper.chance(0.5) then + actor:sound_play(sound_spawn, 2, 1) + else + actor:sound_play(sound_spawn_alt, 2, 1) + end + + -- give them all elite aspects + ssr_give_empyrean_aspects(actor) + end + + -- remove the passive teleport orb since empyreans have a custom one + if actor:item_stack_count(Item.find("ror-elitePassiveTeleport")) > 0 then + actor:item_remove(Item.find("ror-elitePassiveTeleport"), actor:item_stack_count(Item.find("ror-elitePassiveTeleport"))) + end +end) + +empyorb:onPostDraw(function(actor, stack) + if actor:get_data().empy_beam and actor:get_data().empy_beam_over then + if actor:get_data().empy_beam > 0 and actor:get_data().empy_beam_over == 0 then + local width = gm.sprite_get_width(actor.mask_index) / 2 + 32 + local part_width = gm.round((((actor.x - actor.bbox_left) + (actor.bbox_right - actor.x)) / 2) * 1.5) + local part_height = gm.round(((actor.y - actor.bbox_top) + (actor.bbox_bottom - actor.y)) / 2) + local silhouette_y = actor.y - 16 - (16 * math.sin(gm.degtorad(270 + actor:get_data().empy_beam * 2))) + + if actor:get_data().empy_beam <= 10 then + width = width * (1000 - (10 - actor:get_data().empy_beam) ^ 3) / 1000 -- make the beam shrink when its lifetime ends + elseif actor:get_data().empy_beam > 168 then + width = width * ((180 - actor:get_data().empy_beam) ^ 3) / 1000 -- make the beam widen when it appears + elseif actor:get_data().empy_beam > 165 then + width = width * (1.3 - ((169 - actor:get_data().empy_beam) / 10)) -- make the beam shrink back to its normal size after it widens + else + if math.random() >= 0.5 then + -- create the black particles around the enemy + evil:create_color(actor.x + math.random(-part_width, part_width), silhouette_y - part_height / 2 - math.random(part_height), Color.BLACK, 1, Particle.SYSTEM.middle) + + -- create the beam splashing particles + local side = 1 + if math.random() >= 0.5 then + side = -1 + end + local ori = 180 + math.random(-45, 45) - 45 * side + splash:set_orientation(ori, ori, 0, 0, false) + splash:create_color(actor.x + (width + math.random(3)) * side, actor.bbox_bottom, Color.from_hsv(Global._current_frame % 360, 100, 100), 1, Particle.SYSTEM.middle) + end + end + + if actor:get_data().empy_beam > 15 and actor:get_data().empy_beam <= 170 then + -- create the beam particles that touch the ground + beam:create_color(actor.x - width - math.random(3), actor.bbox_bottom - 88, Color.from_hsv(Global._current_frame % 360, 100, 100), 1, Particle.SYSTEM.middle) + beam:create_color(actor.x + width + math.random(3), actor.bbox_bottom - 88, Color.from_hsv(Global._current_frame % 360, 100, 100), 1, Particle.SYSTEM.middle) + + -- create the beam particles around the beam + for i = 1, 22 do + local rnd = math.random(80, 1728) + if gm.inside_view(actor.x, actor.y - rnd) == 1 then + beam:create_color(actor.x - width - math.random(3), actor.bbox_bottom - math.random(88, 1152), Color.from_hsv(Global._current_frame % 360, 100, 100), 1, Particle.SYSTEM.middle) + beam:create_color(actor.x + width + math.random(3), actor.bbox_bottom - math.random(88, 1152), Color.from_hsv(Global._current_frame % 360, 100, 100), 1, Particle.SYSTEM.middle) + end + end + end + + -- draw the beam + if actor:get_data()._imalpha then + gm.draw_set_alpha(actor:get_data()._imalpha) + else + gm.draw_set_alpha(1) + end + gm.draw_set_color(Color.WHITE) + gm.draw_rectangle(actor.x - width, 0, actor.x + width, actor.bbox_bottom, false) + + -- make it black and move + gm.draw_sprite_ext(actor.sprite_index, actor.image_index, actor.x, silhouette_y, actor.image_xscale, actor.image_yscale, actor.image_angle, Color.BLACK, math.min(1, ((180 - actor:get_data().empy_beam) ^ 3) / 1000)) + gm.draw_set_alpha(1) + else + -- make it rainbow + if actor.object_index ~= gm.constants.oWorm then + gm.draw_sprite_ext(actor.sprite_index, actor.image_index, actor.x, actor.y, actor.image_xscale, actor.image_yscale, actor.image_angle, Color.from_hsv(Global._current_frame % 360, 100, 100), actor.image_alpha) + end + + actor.hud_health_color = Color.WHITE + end + end +end) + +-- dont draw the healthbar while the beam is there +gm.pre_script_hook(gm.constants.draw_hp_bar, function(self, other, result, args) + if Wrap.wrap(self):get_data().empy_beam then + if Wrap.wrap(self):get_data().empy_beam > 0 then + return false + end + end +end) + +local guarded = false + +-- disable fall damage for the elite to prevent cheese +gm.pre_script_hook(gm.constants.actor_phy_on_landed, function(self, other, result, args) + local real_self = Instance.wrap(self) + if not gm.bool(self.invincible) and real_self.fall_immune == true then + self.invincible = 1 + guarded = true + end +end) + +gm.post_script_hook(gm.constants.actor_phy_on_landed, function(self, other, result, args) + if guarded then + self.invincible = 0 + guarded = false + end +end) + +empyorb:onPostStep(function(actor, stack) + if actor:get_data().empy_beam > 0 and actor:get_data().empy_beam_over == 0 then -- freeze the enemy + -- store the enemy's certain stats so we can restore it later + if not actor:get_data()._hmax then + actor:get_data()._hmax = actor.pHmax + actor:get_data()._vmax = actor.pVmax + actor:get_data()._imspeed = actor.image_speed + actor:get_data()._imalpha = actor.image_alpha + actor:get_data()._intang = actor.intangible + end + + -- make it not move + actor.image_speed = 0.25 + actor.image_alpha = 0 + actor.pHmax = 0 + actor.pVmax = 0 + actor.intangible = true + actor.activity = 50 + actor.__activity_handler_state = 50 + actor.state = 0 + + -- smoother transition + if actor:get_data()._imalpha then + actor.image_alpha = actor:get_data()._imalpha * (10 - actor:get_data().empy_beam) / 10 + else + actor.image_alpha = (10 - actor:get_data().empy_beam) / 10 + end + + -- (duke nukem voice) shake it baby + actor:screen_shake(0.5) + + -- make it look like its floating + if actor.sprite_fall then + actor.sprite_index = actor.sprite_fall + elseif actor.sprite_idle then + actor.sprite_index = actor.sprite_idle + end + + actor:get_data().empy_beam = actor:get_data().empy_beam - 1 + elseif actor:get_data().empy_beam_over == 0 and actor:get_data().empy_beam <= 0 then -- unfreeze the enemy + actor:get_data().empy_beam_over = 1 + + -- restore their speed + actor.pHmax = actor:get_data()._hmax + actor.pVmax = actor:get_data()._vmax + actor.image_speed = actor:get_data()._imspeed + actor.image_alpha = actor:get_data()._imalpha + actor.intangible = actor:get_data()._intang + + --remove all the temporary variables + actor:get_data()._hmax = nil + actor:get_data()._vmax = nil + actor:get_data()._imspeed = nil + actor:get_data()._imalpha = nil + actor:get_data()._intang = nil + + -- give them all elite aspects + ssr_give_empyrean_aspects(actor) + + -- make the boss bar appear + if actor.team ~= 1 and GM._mod_net_isHost() then + local arr = Array.new({actor}) + local party = actor:actor_create_enemy_party_from_ids(arr) + local director = gm._mod_game_getDirector() + gm.call("register_boss_party@gml_Object_oDirectorControl_Create_0", director, director, party) + end + + -- make them move again !! yippie!! + actor.state = 1 + actor:skill_util_reset_activity_state() + end + + if actor:get_data().no_beam_loser and gm._mod_net_isHost() then + if actor:get_data().no_beam_loser > 0 then -- wait 5 frames before adding empyreans that didnt play the animation to the boss party (prevents issues with max hp being fucked up on the boss bar) + actor:get_data().no_beam_loser = actor:get_data().no_beam_loser - 1 + else + actor:get_data().no_beam_loser = nil + if actor.team ~= 1 then -- make the boss bar appear + local arr = Array.new({actor}) + local party = actor:actor_create_enemy_party_from_ids(arr) + local director = gm._mod_game_getDirector() + gm.call("register_boss_party@gml_Object_oDirectorControl_Create_0", director, director, party) + end + end + end + + if gm.inside_view(actor.x, actor.y) == 1 and actor:get_data().empy_beam_over == 1 and actor:get_data().empy_beam <= 0 and Global._current_frame % 2 == 0 then + rainbowspark:create_color(actor.x, actor.y, Color.from_hsv((Global._current_frame + actor.y * (0.75)) % 360, 100, 100), 1, Particle.SYSTEM.below) + end + + if actor.object_index == gm.constants.oWorm or actor.object_index == gm.constants.oJellyG or actor.object_index == gm.constants.oJellyG2 then + actor.image_blend = Color.from_hsv(Global._current_frame % 360, 65, 100) + end +end) + +empyorb:onPostStatRecalc(function(actor) + -- giga damage + if actor.elite_type then + if actor.elite_type == empy.value then + actor.damage = actor.damage * (5.4 / 1.9) -- totals to 5.4x + actor.cdr = actor.cdr * (0.5 / 0.3) -- totals to 50% + end + end +end) + +teleorb:clear_callbacks() +teleorb:onAcquire(function(actor, stack) + actor:get_data().empyrean_teleport = 480 + math.random(240) +end) + +teleorb:onPostStep(function(actor, stack) + if GM.actor_is_classic(actor) then + if actor:get_data().empyrean_teleport > 0 then + actor:get_data().empyrean_teleport = actor:get_data().empyrean_teleport - 1 + elseif not gm.actor_state_is_climb_state(actor.actor_state_current_id) then + local targets = Instance.find_all(gm.constants.oP) + local target_fin = nil + + -- go through all players and find one that is grounded and not climbing + for _, target in ipairs(targets) do + if not gm.bool(target.free) and not gm.actor_state_is_climb_state(target.actor_state_current_id) and gm.point_distance(actor.x, actor.y, target.x, target.y) > 200 and not target.dead then + target_fin = target + break + end + end + + -- disable skills for a second to prevent unfair deaths + if gm._mod_net_isHost() then + for i = 0, 3 do + local skill = actor:get_active_skill(i) + skill.use_next_frame = math.max(skill.use_next_frame, Global._current_frame + 60) + end + end + + -- teleport! + if target_fin then + if gm._mod_net_isHost() then + gm.teleport_nearby(actor.id, target_fin.x - (80 + math.random(20)) * gm.sign(target_fin.pHspeed), target_fin.y) -- teleport to this fool + actor:net_send_instance_message(25) + + actor.ghost_x = actor.x + actor.ghost_y = actor.y + actor.pVspeed = 0 + actor.pHspeed = 0 + actor.ai_tick_rate = 1 + end + + actor:get_data().empyrean_teleport = 480 + math.random(240) + + -- create some effects so you know youre about to die + local circle = GM.instance_create(actor.x, actor.y, gm.constants.oEfCircle) + circle.parent = actor + circle.radius = gm.round((((actor.x - actor.bbox_left) + (actor.bbox_right - actor.x) + (actor.y - actor.bbox_top) + (actor.bbox_bottom - actor.y)) / 4) * 1.5 ) + + local flash = GM.instance_create(actor.x, actor.y, gm.constants.oEfFlash) + flash.parent = actor + flash.rate = 0.02 + flash.image_alpha = 1 + + for i = 1, 36 do + teleport:set_direction(10 * i, 10 * i, 0, 10) + teleport:set_color2(Color.WHITE, gm.round(math.random(360))) + teleport:create(actor.x, actor.y, 1, Particle.SYSTEM.middle) + end + + -- play one of these 3 sounds randomly + local tpsound = math.random() + if tpsound >= 0.67 then + actor:sound_play(sound_teleport1, 1, 0.8 + math.random() * 0.2) + elseif tpsound >= 0.33 then + actor:sound_play(sound_teleport2, 1, 0.8 + math.random() * 0.2) + else + actor:sound_play(sound_teleport3, 1, 0.8 + math.random() * 0.2) + end + end + end + end +end) + +-- empyrean worms !!! +-- make the worm bodies rainbow too and also make them create sparks +gm.pre_code_execute("gml_Object_oWormBody_Step_2", function(self, other) + if self.parent.elite_type == empy.value then + self.image_blend = Color.from_hsv((Global._current_frame + (self.m_id - self.parent.m_id) * 18) % 360, 65, 100) + + if gm.inside_view(self.x, self.y) == 1 and Global._current_frame % 3 == 0 then + rainbowspark:create_color(self.x, self.y, Color.from_hsv((Global._current_frame + self.y * (0.75)) % 360, 100, 100), 1, Particle.SYSTEM.middle) + end + end +end) + +-- make the worm bodies set their first alarm (needed to shoot the orbs) +gm.pre_code_execute("gml_Object_oWorm_Alarm_4", function(self, other) + if self.elite_type == empy.value then + for _, segment in ipairs(self.body) do + segment:alarm_set(1, 150 + (segment.m_id - self.m_id) * 2) + end + end +end) + +-- play the spawn sound +gm.pre_code_execute("gml_Object_oWorm_Alarm_3", function(self, other) + if self.elite_type == empy.value then + gm.sound_play_global(sound_spawn_worm, 1, 1) + end +end) + +-- special empyrean worm projectile +local oStarStorm = Object.new(NAMESPACE, "EmpyreanWormStar") +oStarStorm.obj_sprite = star_sprite +oStarStorm:clear_callbacks() + +oStarStorm:onCreate(function(self) + self.life = 600 + self.damage = 1 + self.targetX = 0 + self.targetY = 0 + self.team = 2 + self.image_speed = 0.4 + self.image_alpha = 0.75 + self.direction = 0 + self.speed = 0 + + self:sound_play(sound_star_spawn, 0.5, 0.8 + math.random() * 0.2) +end) + +oStarStorm:onDraw(function(self) + gm.draw_set_alpha(0.4) + gm.draw_circle_colour(self.x, self.y, 20, self.image_blend, self.image_blend, false) + gm.draw_set_alpha(1) +end) + +oStarStorm:onStep(function(self) + if self.life > 0 then + self.life = self.life - 1 + else + self:destroy() + end + + if not self.spawn_speed then + self.spawn_speed = self.speed + end + + if self.life >= 570 then -- do this for the first 30 frames after spawning + self.speed = self.speed - self.spawn_speed / 30 + elseif self.life == 481 then -- make it idle for a second and a half, then >> + local flash = GM.instance_create(self.x, self.y, gm.constants.oEfFlash) -- >> make it flash + flash.parent = self + flash.rate = 0.03 + flash.image_alpha = 0.6 + + self:sound_play(sound_star_shoot, 0.5, 0.8 + math.random() * 0.2) -- >> play this sound + + self.direction = gm.point_direction(self.x, self.y, self.targetX, self.targetY) -- >> make it aim at the player + elseif self.life >= 480 then -- while idling, create telegraph particles + if self.life % 10 == 0 then + telegraph:set_direction(gm.point_direction(self.x, self.y, self.targetX, self.targetY), gm.point_direction(self.x, self.y, self.targetX, self.targetY), 0, 0) + telegraph:create_color(self.x, self.y, self.image_blend, 1) + end + elseif self.life >= 470 then -- once were done idling, descrease its speed to create a wind up effect for 10 frames + self.speed = self.speed - 0.4 + else + self.speed = math.min(30, self.speed + 0.6) -- then start increasing its speed for the rest of the duration + + if self.life % 3 == 0 then -- create trails cuz theyre cool + local trail = GM.instance_create(self.x, self.y, gm.constants.oEfTrail) + trail.sprite_index = self.sprite_index + trail.image_index = self.image_index + trail.image_blend = self.image_blend + trail.image_alpha = self.image_alpha + trail.image_xscale = self.image_xscale + trail.image_yscale = self.image_yscale + trail.direction = self.direction + trail.speed = self.speed * 0.75 + trail.depth = self.depth + 1 + end + + for _, actor in ipairs(self:get_collisions(gm.constants.pActorCollisionBase)) do -- make it deal damage if it hits the opposite team + if self:attack_collision_canhit(actor) and Instance.exists(self.parent) then + if gm._mod_net_isHost() then + local attack = self.parent:fire_direct(actor, self.damage / self.parent.damage) + end + + self:sound_play(gm.constants.wExplosiveShot, 1, 0.8 + math.random() * 0.2) + self:destroy() + end + end + end + + if gm.inside_view(self.x, self.y) == 1 and self.life % 5 == 0 then + rainbowspark:create_color(self.x, self.y, self.image_blend, 1, Particle.SYSTEM.middle) + end +end) + +-- make the worm shoot the star storm +gm.pre_code_execute("gml_Object_oWormBody_Alarm_1", function(self, other) + if self.parent.elite_type == empy.value then + self:alarm_set(1, 330) + + if self.parent.target then + local star = oStarStorm:create(self.x, self.y) + star.parent = self.parent + star.team = self.parent.team + star.targetX = self.parent.target.x + star.targetY = self.parent.target.y + star.damage = self.parent.damage * 0.07 + if Helper.chance(0.5) then + star.direction = self.image_angle - 90 + math.random(-45, 45) + else + star.direction = self.image_angle + 90 + math.random(-45, 45) + end + star.speed = math.random(4, 8) + star.image_blend = self.image_blend + end + end +end) + +-- make the warning change color +gm.pre_code_execute("gml_Object_oWormWarning_Step_0", function(self, other) + if self.image_blend == Color.from_hsv((Global._current_frame - 1) % 360, 65, 100) then -- check what color it was on the previous frame + self.image_blend = Color.from_hsv(Global._current_frame % 360, 65, 100) + end +end) + +local blacklist = { + ["lemrider"] = true, -- the spawn anim breaks since its 2 of them at once, also doesnt actually do most elite effects + ["bramble"] = true, -- requires major fixes that im not sure are even possible + ["spitter"] = true, -- technically fully works but FUCK no +} + +local whitelist = { + ["jellyfish"] = true, + ["magmaWorm"] = true, + ["swift"] = true, -- lmao cope + ["archerBug"] = true, + ["colossus"] = true, + ["ancientWisp"] = true, + ["ifrit"] = true, + ["wanderingVagrant"] = true, + ["youngVagrant"] = true, +} + +Callback.add(Callback.TYPE.onGameStart, "SSResetEmpyreanChance", function() + GM._mod_game_getDirector().__ssr_empyrean_chance = 0.02 -- higher chance so you see your first empyrean sooner +end) + +Callback.add(Callback.TYPE.onEliteInit, "SSSpawnEmpyrean", function(actor) + if GM._mod_game_getDirector().stages_passed < 8 then return end -- only spawns if its stage 9+ + if not GM._mod_net_isHost() then return end + + if actor.elite_type ~= empy.value then -- if the actor is not already empyrean + local all_monster_cards = Monster_Card.find_all() + local chance = GM._mod_game_getDirector().__ssr_empyrean_chance -- a value from 0 to 1 + local diff = math.max(1, (GM._mod_game_getDirector().enemy_buff - 16) / 4) + for i, card in ipairs(all_monster_cards) do + if card.object_id == actor.object_index then -- if the actor has a monster card + if not blacklist[card.identifier] and (card.can_be_blighted == true or whitelist[card.identifier]) then -- if the actor is not blacklisted and can be blighted or is in the whitelist + local cost = math.min(4, math.max(1, card.spawn_cost / 40 * diff)) + if Helper.chance(chance / cost) then + GM.elite_set(actor, empy.value) -- make it empyrean + GM._mod_game_getDirector().__ssr_empyrean_chance = 0.005 * diff -- reset the chance + else + GM._mod_game_getDirector().__ssr_empyrean_chance = GM._mod_game_getDirector().__ssr_empyrean_chance + 0.002 * diff -- increase the chance on fail + end + break + end + end + end + end +end) \ No newline at end of file diff --git a/Language/english.json b/Language/english.json index afe1e878..636fa8cc 100644 --- a/Language/english.json +++ b/Language/english.json @@ -515,7 +515,10 @@ }, "elite" : { - "poison.name" : "Poisoning %s" + "poison.name" : "Poisoning %s", + "empyrean.name" : "Empyrean %s", + "empyrean.text" : "Harbinger of Judgement", + "empyrean.worm" : "Prismatic Leviathan" }, "stage" : { "whistlingBasin.name" : "Whistling Basin", diff --git a/Language/russian.json b/Language/russian.json index 29a5125d..b8c365b3 100644 --- a/Language/russian.json +++ b/Language/russian.json @@ -461,7 +461,9 @@ }, "elite" : { - "poison.name" : "%s яда" + "poison.name" : "%s яда", + "empyrean.name" : "%s Эмпиреи", + "empyrean.text" : "Предвестник Суда", }, "stage" : { "whistlingBasin.name" : "Свистящий Бассейн", diff --git a/Sounds/Elites/Empyrean/beam.ogg b/Sounds/Elites/Empyrean/beam.ogg new file mode 100644 index 00000000..fd77c31d Binary files /dev/null and b/Sounds/Elites/Empyrean/beam.ogg differ diff --git a/Sounds/Elites/Empyrean/spawn.ogg b/Sounds/Elites/Empyrean/spawn.ogg new file mode 100644 index 00000000..08d6ec92 Binary files /dev/null and b/Sounds/Elites/Empyrean/spawn.ogg differ diff --git a/Sounds/Elites/Empyrean/spawn_alt.ogg b/Sounds/Elites/Empyrean/spawn_alt.ogg new file mode 100644 index 00000000..92942e1c Binary files /dev/null and b/Sounds/Elites/Empyrean/spawn_alt.ogg differ diff --git a/Sounds/Elites/Empyrean/spawn_worm.ogg b/Sounds/Elites/Empyrean/spawn_worm.ogg new file mode 100644 index 00000000..82ef7fc1 Binary files /dev/null and b/Sounds/Elites/Empyrean/spawn_worm.ogg differ diff --git a/Sounds/Elites/Empyrean/star_shoot.ogg b/Sounds/Elites/Empyrean/star_shoot.ogg new file mode 100644 index 00000000..079f9d4d Binary files /dev/null and b/Sounds/Elites/Empyrean/star_shoot.ogg differ diff --git a/Sounds/Elites/Empyrean/star_spawn.ogg b/Sounds/Elites/Empyrean/star_spawn.ogg new file mode 100644 index 00000000..e31d5828 Binary files /dev/null and b/Sounds/Elites/Empyrean/star_spawn.ogg differ diff --git a/Sounds/Elites/Empyrean/teleport1.ogg b/Sounds/Elites/Empyrean/teleport1.ogg new file mode 100644 index 00000000..1b1ae00d Binary files /dev/null and b/Sounds/Elites/Empyrean/teleport1.ogg differ diff --git a/Sounds/Elites/Empyrean/teleport2.ogg b/Sounds/Elites/Empyrean/teleport2.ogg new file mode 100644 index 00000000..407dcc5a Binary files /dev/null and b/Sounds/Elites/Empyrean/teleport2.ogg differ diff --git a/Sounds/Elites/Empyrean/teleport3.ogg b/Sounds/Elites/Empyrean/teleport3.ogg new file mode 100644 index 00000000..1407fd8a Binary files /dev/null and b/Sounds/Elites/Empyrean/teleport3.ogg differ diff --git a/Sprites/Elites/Empyrean/beam.png b/Sprites/Elites/Empyrean/beam.png new file mode 100644 index 00000000..739e6179 Binary files /dev/null and b/Sprites/Elites/Empyrean/beam.png differ diff --git a/Sprites/Elites/Empyrean/icon.png b/Sprites/Elites/Empyrean/icon.png new file mode 100644 index 00000000..4bb3599e Binary files /dev/null and b/Sprites/Elites/Empyrean/icon.png differ diff --git a/Sprites/Elites/Empyrean/palette.png b/Sprites/Elites/Empyrean/palette.png new file mode 100644 index 00000000..f84c2777 Binary files /dev/null and b/Sprites/Elites/Empyrean/palette.png differ diff --git a/Sprites/Elites/Empyrean/splash.png b/Sprites/Elites/Empyrean/splash.png new file mode 100644 index 00000000..32db3e6f Binary files /dev/null and b/Sprites/Elites/Empyrean/splash.png differ diff --git a/Sprites/Elites/Empyrean/star.png b/Sprites/Elites/Empyrean/star.png new file mode 100644 index 00000000..d0bc8bf9 Binary files /dev/null and b/Sprites/Elites/Empyrean/star.png differ